mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 04:07:08 +00:00
add media_player/clear_playlist and line-in/tv support to sonos (#2527)
* add media_player/clear_playlist and line-in/tv support to sonos * add support source radio * fix bug * print TV/Line-In as media_title * implement universal player * add to demo platform * Update demo.py Better handling for demo object * add unit tests * fix unit test
This commit is contained in:
parent
c1798dbe1f
commit
6694f29918
@ -31,6 +31,7 @@ ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
|
||||
SERVICE_PLAY_MEDIA = 'play_media'
|
||||
SERVICE_SELECT_SOURCE = 'select_source'
|
||||
SERVICE_CLEAR_PLAYLIST = 'clear_playlist'
|
||||
|
||||
ATTR_MEDIA_VOLUME_LEVEL = 'volume_level'
|
||||
ATTR_MEDIA_VOLUME_MUTED = 'is_volume_muted'
|
||||
@ -75,6 +76,7 @@ SUPPORT_PLAY_MEDIA = 512
|
||||
SUPPORT_VOLUME_STEP = 1024
|
||||
SUPPORT_SELECT_SOURCE = 2048
|
||||
SUPPORT_STOP = 4096
|
||||
SUPPORT_CLEAR_PLAYLIST = 8192
|
||||
|
||||
# simple services that only take entity_id(s) as optional argument
|
||||
SERVICE_TO_METHOD = {
|
||||
@ -89,7 +91,8 @@ SERVICE_TO_METHOD = {
|
||||
SERVICE_MEDIA_STOP: 'media_stop',
|
||||
SERVICE_MEDIA_NEXT_TRACK: 'media_next_track',
|
||||
SERVICE_MEDIA_PREVIOUS_TRACK: 'media_previous_track',
|
||||
SERVICE_SELECT_SOURCE: 'select_source'
|
||||
SERVICE_SELECT_SOURCE: 'select_source',
|
||||
SERVICE_CLEAR_PLAYLIST: 'clear_playlist'
|
||||
}
|
||||
|
||||
ATTR_TO_PROPERTY = [
|
||||
@ -272,6 +275,12 @@ def select_source(hass, source, entity_id=None):
|
||||
hass.services.call(DOMAIN, SERVICE_SELECT_SOURCE, data)
|
||||
|
||||
|
||||
def clear_playlist(hass, entity_id=None):
|
||||
"""Send the media player the command for clear playlist."""
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||
hass.services.call(DOMAIN, SERVICE_CLEAR_PLAYLIST, data)
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""Track states and offer events for media_players."""
|
||||
component = EntityComponent(
|
||||
@ -542,6 +551,10 @@ class MediaPlayerDevice(Entity):
|
||||
"""Select input source."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def clear_playlist(self):
|
||||
"""Clear players playlist."""
|
||||
raise NotImplementedError()
|
||||
|
||||
# No need to overwrite these.
|
||||
@property
|
||||
def support_pause(self):
|
||||
@ -588,6 +601,11 @@ class MediaPlayerDevice(Entity):
|
||||
"""Boolean if select source command supported."""
|
||||
return bool(self.supported_media_commands & SUPPORT_SELECT_SOURCE)
|
||||
|
||||
@property
|
||||
def support_clear_playlist(self):
|
||||
"""Boolean if clear playlist command supported."""
|
||||
return bool(self.supported_media_commands & SUPPORT_CLEAR_PLAYLIST)
|
||||
|
||||
def toggle(self):
|
||||
"""Toggle the power on the media player."""
|
||||
if self.state in [STATE_OFF, STATE_IDLE]:
|
||||
|
@ -8,7 +8,7 @@ from homeassistant.components.media_player import (
|
||||
MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO, SUPPORT_NEXT_TRACK,
|
||||
SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK,
|
||||
SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
|
||||
SUPPORT_SELECT_SOURCE, MediaPlayerDevice)
|
||||
SUPPORT_SELECT_SOURCE, SUPPORT_CLEAR_PLAYLIST, MediaPlayerDevice)
|
||||
from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING
|
||||
|
||||
|
||||
@ -32,7 +32,7 @@ YOUTUBE_PLAYER_SUPPORT = \
|
||||
|
||||
MUSIC_PLAYER_SUPPORT = \
|
||||
SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
||||
SUPPORT_TURN_ON | SUPPORT_TURN_OFF
|
||||
SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_CLEAR_PLAYLIST
|
||||
|
||||
NETFLIX_PLAYER_SUPPORT = \
|
||||
SUPPORT_PAUSE | SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_SELECT_SOURCE
|
||||
@ -214,12 +214,12 @@ class DemoMusicPlayer(AbstractDemoPlayer):
|
||||
@property
|
||||
def media_title(self):
|
||||
"""Return the title of current playing media."""
|
||||
return self.tracks[self._cur_track][1]
|
||||
return self.tracks[self._cur_track][1] if len(self.tracks) > 0 else ""
|
||||
|
||||
@property
|
||||
def media_artist(self):
|
||||
"""Return the artist of current playing media (Music track only)."""
|
||||
return self.tracks[self._cur_track][0]
|
||||
return self.tracks[self._cur_track][0] if len(self.tracks) > 0 else ""
|
||||
|
||||
@property
|
||||
def media_album_name(self):
|
||||
@ -257,6 +257,13 @@ class DemoMusicPlayer(AbstractDemoPlayer):
|
||||
self._cur_track += 1
|
||||
self.update_ha_state()
|
||||
|
||||
def clear_playlist(self):
|
||||
"""Clear players playlist."""
|
||||
self.tracks = []
|
||||
self._cur_track = 0
|
||||
self._player_state = STATE_OFF
|
||||
self.update_ha_state()
|
||||
|
||||
|
||||
class DemoTVShowPlayer(AbstractDemoPlayer):
|
||||
"""A Demo media player that only supports YouTube."""
|
||||
|
@ -146,6 +146,14 @@ select_source:
|
||||
description: Name of the source to switch to. Platform dependent.
|
||||
example: 'video1'
|
||||
|
||||
clear_playlist:
|
||||
description: Send the media player the command to clear players playlist.
|
||||
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name(s) of entites to change source on
|
||||
example: 'media_player.living_room_chromecast'
|
||||
|
||||
sonos_group_players:
|
||||
description: Send Sonos media player the command for grouping all players into one (party mode).
|
||||
|
||||
|
@ -12,7 +12,8 @@ from os import path
|
||||
from homeassistant.components.media_player import (
|
||||
ATTR_MEDIA_ENQUEUE, DOMAIN, MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK,
|
||||
SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK,
|
||||
SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, MediaPlayerDevice)
|
||||
SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_CLEAR_PLAYLIST,
|
||||
SUPPORT_SELECT_SOURCE, MediaPlayerDevice)
|
||||
from homeassistant.const import (
|
||||
STATE_IDLE, STATE_PAUSED, STATE_PLAYING, STATE_UNKNOWN, STATE_OFF)
|
||||
from homeassistant.config import load_yaml_config_file
|
||||
@ -31,13 +32,17 @@ _REQUESTS_LOGGER.setLevel(logging.ERROR)
|
||||
|
||||
SUPPORT_SONOS = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE |\
|
||||
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_PLAY_MEDIA |\
|
||||
SUPPORT_SEEK
|
||||
SUPPORT_SEEK | SUPPORT_CLEAR_PLAYLIST | SUPPORT_SELECT_SOURCE
|
||||
|
||||
SERVICE_GROUP_PLAYERS = 'sonos_group_players'
|
||||
SERVICE_UNJOIN = 'sonos_unjoin'
|
||||
SERVICE_SNAPSHOT = 'sonos_snapshot'
|
||||
SERVICE_RESTORE = 'sonos_restore'
|
||||
|
||||
SUPPORT_SOURCE_LINEIN = 'Line-in'
|
||||
SUPPORT_SOURCE_TV = 'TV'
|
||||
SUPPORT_SOURCE_RADIO = 'Radio'
|
||||
|
||||
|
||||
# pylint: disable=unused-argument, too-many-locals
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
@ -162,12 +167,12 @@ class SonosDevice(MediaPlayerDevice):
|
||||
# pylint: disable=too-many-arguments
|
||||
def __init__(self, hass, player):
|
||||
"""Initialize the Sonos device."""
|
||||
from soco.snapshot import Snapshot
|
||||
|
||||
self.hass = hass
|
||||
self.volume_increment = 5
|
||||
super(SonosDevice, self).__init__()
|
||||
self._player = player
|
||||
self.update()
|
||||
from soco.snapshot import Snapshot
|
||||
self.soco_snapshot = Snapshot(self._player)
|
||||
|
||||
@property
|
||||
@ -268,6 +273,10 @@ class SonosDevice(MediaPlayerDevice):
|
||||
)
|
||||
if 'title' in self._status:
|
||||
return self._trackinfo['title']
|
||||
if self._player.is_playing_line_in:
|
||||
return SUPPORT_SOURCE_LINEIN
|
||||
if self._player.is_playing_tv:
|
||||
return SUPPORT_SOURCE_TV
|
||||
|
||||
@property
|
||||
def supported_media_commands(self):
|
||||
@ -290,6 +299,36 @@ class SonosDevice(MediaPlayerDevice):
|
||||
"""Mute (true) or unmute (false) media player."""
|
||||
self._player.mute = mute
|
||||
|
||||
def select_source(self, source):
|
||||
"""Select input source."""
|
||||
if source == SUPPORT_SOURCE_LINEIN:
|
||||
self._player.switch_to_line_in()
|
||||
elif source == SUPPORT_SOURCE_TV:
|
||||
self._player.switch_to_tv()
|
||||
|
||||
@property
|
||||
def source_list(self):
|
||||
"""List of available input sources."""
|
||||
source = []
|
||||
|
||||
# generate list of supported device
|
||||
source.append(SUPPORT_SOURCE_LINEIN)
|
||||
source.append(SUPPORT_SOURCE_TV)
|
||||
source.append(SUPPORT_SOURCE_RADIO)
|
||||
|
||||
return source
|
||||
|
||||
@property
|
||||
def source(self):
|
||||
"""Name of the current input source."""
|
||||
if self._player.is_playing_line_in:
|
||||
return SUPPORT_SOURCE_LINEIN
|
||||
if self._player.is_playing_tv:
|
||||
return SUPPORT_SOURCE_TV
|
||||
if self._player.is_playing_radio:
|
||||
return SUPPORT_SOURCE_RADIO
|
||||
return None
|
||||
|
||||
@only_if_coordinator
|
||||
def turn_off(self):
|
||||
"""Turn off media player."""
|
||||
@ -320,6 +359,11 @@ class SonosDevice(MediaPlayerDevice):
|
||||
"""Send seek command."""
|
||||
self._player.seek(str(datetime.timedelta(seconds=int(position))))
|
||||
|
||||
@only_if_coordinator
|
||||
def clear_playlist(self):
|
||||
"""Clear players playlist."""
|
||||
self._player.clear_queue()
|
||||
|
||||
@only_if_coordinator
|
||||
def turn_on(self):
|
||||
"""Turn the media player on."""
|
||||
|
@ -17,8 +17,9 @@ from homeassistant.components.media_player import (
|
||||
ATTR_MEDIA_VOLUME_LEVEL, ATTR_MEDIA_VOLUME_MUTED,
|
||||
ATTR_SUPPORTED_MEDIA_COMMANDS, DOMAIN, SERVICE_PLAY_MEDIA,
|
||||
SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
|
||||
SUPPORT_VOLUME_STEP, SUPPORT_SELECT_SOURCE, ATTR_INPUT_SOURCE,
|
||||
SERVICE_SELECT_SOURCE, MediaPlayerDevice)
|
||||
SUPPORT_VOLUME_STEP, SUPPORT_SELECT_SOURCE, SUPPORT_CLEAR_PLAYLIST,
|
||||
ATTR_INPUT_SOURCE, SERVICE_SELECT_SOURCE, SERVICE_CLEAR_PLAYLIST,
|
||||
MediaPlayerDevice)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE, CONF_NAME, SERVICE_MEDIA_NEXT_TRACK,
|
||||
SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PLAY_PAUSE,
|
||||
@ -349,6 +350,9 @@ class UniversalMediaPlayer(MediaPlayerDevice):
|
||||
if SERVICE_SELECT_SOURCE in self._cmds:
|
||||
flags |= SUPPORT_SELECT_SOURCE
|
||||
|
||||
if SERVICE_CLEAR_PLAYLIST in self._cmds:
|
||||
flags |= SUPPORT_CLEAR_PLAYLIST
|
||||
|
||||
return flags
|
||||
|
||||
@property
|
||||
@ -424,6 +428,10 @@ class UniversalMediaPlayer(MediaPlayerDevice):
|
||||
data = {ATTR_INPUT_SOURCE: source}
|
||||
self._call_service(SERVICE_SELECT_SOURCE, data)
|
||||
|
||||
def clear_playlist(self):
|
||||
"""Clear players playlist."""
|
||||
self._call_service(SERVICE_CLEAR_PLAYLIST)
|
||||
|
||||
def update(self):
|
||||
"""Update state in HA."""
|
||||
for child_name in self._children:
|
||||
|
@ -38,6 +38,15 @@ class TestDemoMediaPlayer(unittest.TestCase):
|
||||
state = self.hass.states.get(entity_id)
|
||||
assert 'xbox' == state.attributes.get('source')
|
||||
|
||||
def test_clear_playlist(self):
|
||||
"""Test clear playlist."""
|
||||
assert mp.setup(self.hass, {'media_player': {'platform': 'demo'}})
|
||||
assert self.hass.states.is_state(entity_id, 'playing')
|
||||
|
||||
mp.clear_playlist(self.hass, entity_id)
|
||||
self.hass.pool.block_till_done()
|
||||
assert self.hass.states.is_state(entity_id, 'off')
|
||||
|
||||
def test_volume_services(self):
|
||||
"""Test the volume service."""
|
||||
assert mp.setup(self.hass, {'media_player': {'platform': 'demo'}})
|
||||
|
@ -25,6 +25,7 @@ class MockMediaPlayer(media_player.MediaPlayerDevice):
|
||||
self._media_title = None
|
||||
self._supported_media_commands = 0
|
||||
self._source = None
|
||||
self._tracks = 12
|
||||
|
||||
self.service_calls = {
|
||||
'turn_on': mock_service(
|
||||
@ -59,6 +60,9 @@ class MockMediaPlayer(media_player.MediaPlayerDevice):
|
||||
'select_source': mock_service(
|
||||
hass, media_player.DOMAIN,
|
||||
media_player.SERVICE_SELECT_SOURCE),
|
||||
'clear_playlist': mock_service(
|
||||
hass, media_player.DOMAIN,
|
||||
media_player.SERVICE_CLEAR_PLAYLIST),
|
||||
}
|
||||
|
||||
@property
|
||||
@ -114,6 +118,10 @@ class MockMediaPlayer(media_player.MediaPlayerDevice):
|
||||
"""Set the input source."""
|
||||
self._state = source
|
||||
|
||||
def clear_playlist(self):
|
||||
"""Clear players playlist."""
|
||||
self._tracks = 0
|
||||
|
||||
|
||||
class TestMediaPlayer(unittest.TestCase):
|
||||
"""Test the media_player module."""
|
||||
@ -510,6 +518,10 @@ class TestMediaPlayer(unittest.TestCase):
|
||||
self.assertEqual(
|
||||
1, len(self.mock_mp_2.service_calls['select_source']))
|
||||
|
||||
ump.clear_playlist()
|
||||
self.assertEqual(
|
||||
1, len(self.mock_mp_2.service_calls['clear_playlist']))
|
||||
|
||||
def test_service_call_to_command(self):
|
||||
"""Test service call to command."""
|
||||
config = self.config_children_only
|
||||
|
Loading…
x
Reference in New Issue
Block a user