mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 12:17:07 +00:00
Add support for shuffle toggling on Spotify component. (#7339)
* add support for shuffle toggling on Spotify component. this also required adding support for shuffle on the media_player component. * lint * Use ATTR_MEDIA_SHUFFLING for service handler param * Line too long fix * fix tests * add shuffle set to demo mediaplayer * rename shuffle attribute
This commit is contained in:
parent
5d7403bd81
commit
c085f06df5
@ -31,7 +31,8 @@ from homeassistant.const import (
|
|||||||
SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_SET,
|
SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_SET,
|
||||||
SERVICE_VOLUME_MUTE, SERVICE_TOGGLE, SERVICE_MEDIA_STOP,
|
SERVICE_VOLUME_MUTE, SERVICE_TOGGLE, SERVICE_MEDIA_STOP,
|
||||||
SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE,
|
SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE,
|
||||||
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_SEEK)
|
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_SEEK,
|
||||||
|
SERVICE_SHUFFLE_SET)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
_RND = SystemRandom()
|
_RND = SystemRandom()
|
||||||
@ -81,6 +82,7 @@ ATTR_APP_NAME = 'app_name'
|
|||||||
ATTR_INPUT_SOURCE = 'source'
|
ATTR_INPUT_SOURCE = 'source'
|
||||||
ATTR_INPUT_SOURCE_LIST = 'source_list'
|
ATTR_INPUT_SOURCE_LIST = 'source_list'
|
||||||
ATTR_MEDIA_ENQUEUE = 'enqueue'
|
ATTR_MEDIA_ENQUEUE = 'enqueue'
|
||||||
|
ATTR_MEDIA_SHUFFLE = 'shuffle'
|
||||||
|
|
||||||
MEDIA_TYPE_MUSIC = 'music'
|
MEDIA_TYPE_MUSIC = 'music'
|
||||||
MEDIA_TYPE_TVSHOW = 'tvshow'
|
MEDIA_TYPE_TVSHOW = 'tvshow'
|
||||||
@ -104,6 +106,7 @@ SUPPORT_SELECT_SOURCE = 2048
|
|||||||
SUPPORT_STOP = 4096
|
SUPPORT_STOP = 4096
|
||||||
SUPPORT_CLEAR_PLAYLIST = 8192
|
SUPPORT_CLEAR_PLAYLIST = 8192
|
||||||
SUPPORT_PLAY = 16384
|
SUPPORT_PLAY = 16384
|
||||||
|
SUPPORT_SHUFFLE_SET = 32768
|
||||||
|
|
||||||
# Service call validation schemas
|
# Service call validation schemas
|
||||||
MEDIA_PLAYER_SCHEMA = vol.Schema({
|
MEDIA_PLAYER_SCHEMA = vol.Schema({
|
||||||
@ -133,6 +136,10 @@ MEDIA_PLAYER_PLAY_MEDIA_SCHEMA = MEDIA_PLAYER_SCHEMA.extend({
|
|||||||
vol.Optional(ATTR_MEDIA_ENQUEUE): cv.boolean,
|
vol.Optional(ATTR_MEDIA_ENQUEUE): cv.boolean,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
MEDIA_PLAYER_SET_SHUFFLE_SCHEMA = MEDIA_PLAYER_SCHEMA.extend({
|
||||||
|
vol.Required(ATTR_MEDIA_SHUFFLE): cv.boolean,
|
||||||
|
})
|
||||||
|
|
||||||
SERVICE_TO_METHOD = {
|
SERVICE_TO_METHOD = {
|
||||||
SERVICE_TURN_ON: {'method': 'async_turn_on'},
|
SERVICE_TURN_ON: {'method': 'async_turn_on'},
|
||||||
SERVICE_TURN_OFF: {'method': 'async_turn_off'},
|
SERVICE_TURN_OFF: {'method': 'async_turn_off'},
|
||||||
@ -161,6 +168,9 @@ SERVICE_TO_METHOD = {
|
|||||||
SERVICE_PLAY_MEDIA: {
|
SERVICE_PLAY_MEDIA: {
|
||||||
'method': 'async_play_media',
|
'method': 'async_play_media',
|
||||||
'schema': MEDIA_PLAYER_PLAY_MEDIA_SCHEMA},
|
'schema': MEDIA_PLAYER_PLAY_MEDIA_SCHEMA},
|
||||||
|
SERVICE_SHUFFLE_SET: {
|
||||||
|
'method': 'async_set_shuffle',
|
||||||
|
'schema': MEDIA_PLAYER_SET_SHUFFLE_SCHEMA},
|
||||||
}
|
}
|
||||||
|
|
||||||
ATTR_TO_PROPERTY = [
|
ATTR_TO_PROPERTY = [
|
||||||
@ -185,6 +195,7 @@ ATTR_TO_PROPERTY = [
|
|||||||
ATTR_APP_NAME,
|
ATTR_APP_NAME,
|
||||||
ATTR_INPUT_SOURCE,
|
ATTR_INPUT_SOURCE,
|
||||||
ATTR_INPUT_SOURCE_LIST,
|
ATTR_INPUT_SOURCE_LIST,
|
||||||
|
ATTR_MEDIA_SHUFFLE,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -322,6 +333,16 @@ def clear_playlist(hass, entity_id=None):
|
|||||||
hass.services.call(DOMAIN, SERVICE_CLEAR_PLAYLIST, data)
|
hass.services.call(DOMAIN, SERVICE_CLEAR_PLAYLIST, data)
|
||||||
|
|
||||||
|
|
||||||
|
def set_shuffle(hass, shuffle, entity_id=None):
|
||||||
|
"""Send the media player the command to enable/disable shuffle mode."""
|
||||||
|
data = {ATTR_MEDIA_SHUFFLE: shuffle}
|
||||||
|
|
||||||
|
if entity_id:
|
||||||
|
data[ATTR_ENTITY_ID] = entity_id
|
||||||
|
|
||||||
|
hass.services.call(DOMAIN, SERVICE_SHUFFLE_SET, data)
|
||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_setup(hass, config):
|
def async_setup(hass, config):
|
||||||
"""Track states and offer events for media_players."""
|
"""Track states and offer events for media_players."""
|
||||||
@ -358,6 +379,9 @@ def async_setup(hass, config):
|
|||||||
params['media_id'] = service.data.get(ATTR_MEDIA_CONTENT_ID)
|
params['media_id'] = service.data.get(ATTR_MEDIA_CONTENT_ID)
|
||||||
params[ATTR_MEDIA_ENQUEUE] = \
|
params[ATTR_MEDIA_ENQUEUE] = \
|
||||||
service.data.get(ATTR_MEDIA_ENQUEUE)
|
service.data.get(ATTR_MEDIA_ENQUEUE)
|
||||||
|
elif service.service == SERVICE_SHUFFLE_SET:
|
||||||
|
params[ATTR_MEDIA_SHUFFLE] = \
|
||||||
|
service.data.get(ATTR_MEDIA_SHUFFLE)
|
||||||
target_players = component.async_extract_from_service(service)
|
target_players = component.async_extract_from_service(service)
|
||||||
|
|
||||||
update_tasks = []
|
update_tasks = []
|
||||||
@ -539,6 +563,11 @@ class MediaPlayerDevice(Entity):
|
|||||||
"""List of available input sources."""
|
"""List of available input sources."""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def shuffle(self):
|
||||||
|
"""Boolean if shuffle is enabled."""
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@deprecated_substitute('supported_media_commands')
|
@deprecated_substitute('supported_media_commands')
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
@ -701,6 +730,18 @@ class MediaPlayerDevice(Entity):
|
|||||||
return self.hass.loop.run_in_executor(
|
return self.hass.loop.run_in_executor(
|
||||||
None, self.clear_playlist)
|
None, self.clear_playlist)
|
||||||
|
|
||||||
|
def set_shuffle(self, shuffle):
|
||||||
|
"""Enable/disable shuffle mode."""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def async_set_shuffle(self, shuffle):
|
||||||
|
"""Enable/disable shuffle mode.
|
||||||
|
|
||||||
|
This method must be run in the event loop and returns a coroutine.
|
||||||
|
"""
|
||||||
|
return self.hass.loop.run_in_executor(
|
||||||
|
None, self.set_shuffle, shuffle)
|
||||||
|
|
||||||
# No need to overwrite these.
|
# No need to overwrite these.
|
||||||
@property
|
@property
|
||||||
def support_play(self):
|
def support_play(self):
|
||||||
@ -757,6 +798,11 @@ class MediaPlayerDevice(Entity):
|
|||||||
"""Boolean if clear playlist command supported."""
|
"""Boolean if clear playlist command supported."""
|
||||||
return bool(self.supported_features & SUPPORT_CLEAR_PLAYLIST)
|
return bool(self.supported_features & SUPPORT_CLEAR_PLAYLIST)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def support_shuffle_set(self):
|
||||||
|
"""Boolean if shuffle is supported."""
|
||||||
|
return bool(self.supported_features & SUPPORT_SHUFFLE_SET)
|
||||||
|
|
||||||
def async_toggle(self):
|
def async_toggle(self):
|
||||||
"""Toggle the power on the media player.
|
"""Toggle the power on the media player.
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ from homeassistant.components.media_player import (
|
|||||||
SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK,
|
SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK,
|
||||||
SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
|
SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
|
||||||
SUPPORT_SELECT_SOURCE, SUPPORT_CLEAR_PLAYLIST, SUPPORT_PLAY,
|
SUPPORT_SELECT_SOURCE, SUPPORT_CLEAR_PLAYLIST, SUPPORT_PLAY,
|
||||||
MediaPlayerDevice)
|
SUPPORT_SHUFFLE_SET, MediaPlayerDevice)
|
||||||
from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING
|
from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
@ -31,15 +31,17 @@ YOUTUBE_COVER_URL_FORMAT = 'https://img.youtube.com/vi/{}/hqdefault.jpg'
|
|||||||
|
|
||||||
YOUTUBE_PLAYER_SUPPORT = \
|
YOUTUBE_PLAYER_SUPPORT = \
|
||||||
SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
||||||
SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PLAY_MEDIA | SUPPORT_PLAY
|
SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PLAY_MEDIA | SUPPORT_PLAY | \
|
||||||
|
SUPPORT_SHUFFLE_SET
|
||||||
|
|
||||||
MUSIC_PLAYER_SUPPORT = \
|
MUSIC_PLAYER_SUPPORT = \
|
||||||
SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
||||||
SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_CLEAR_PLAYLIST | SUPPORT_PLAY
|
SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_CLEAR_PLAYLIST | \
|
||||||
|
SUPPORT_PLAY | SUPPORT_SHUFFLE_SET
|
||||||
|
|
||||||
NETFLIX_PLAYER_SUPPORT = \
|
NETFLIX_PLAYER_SUPPORT = \
|
||||||
SUPPORT_PAUSE | SUPPORT_TURN_ON | SUPPORT_TURN_OFF | \
|
SUPPORT_PAUSE | SUPPORT_TURN_ON | SUPPORT_TURN_OFF | \
|
||||||
SUPPORT_SELECT_SOURCE | SUPPORT_PLAY
|
SUPPORT_SELECT_SOURCE | SUPPORT_PLAY | SUPPORT_SHUFFLE_SET
|
||||||
|
|
||||||
|
|
||||||
class AbstractDemoPlayer(MediaPlayerDevice):
|
class AbstractDemoPlayer(MediaPlayerDevice):
|
||||||
@ -53,6 +55,7 @@ class AbstractDemoPlayer(MediaPlayerDevice):
|
|||||||
self._player_state = STATE_PLAYING
|
self._player_state = STATE_PLAYING
|
||||||
self._volume_level = 1.0
|
self._volume_level = 1.0
|
||||||
self._volume_muted = False
|
self._volume_muted = False
|
||||||
|
self._shuffle = False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def should_poll(self):
|
def should_poll(self):
|
||||||
@ -79,6 +82,11 @@ class AbstractDemoPlayer(MediaPlayerDevice):
|
|||||||
"""Return boolean if volume is currently muted."""
|
"""Return boolean if volume is currently muted."""
|
||||||
return self._volume_muted
|
return self._volume_muted
|
||||||
|
|
||||||
|
@property
|
||||||
|
def shuffle(self):
|
||||||
|
"""Boolean if shuffling is enabled."""
|
||||||
|
return self._shuffle
|
||||||
|
|
||||||
def turn_on(self):
|
def turn_on(self):
|
||||||
"""Turn the media player on."""
|
"""Turn the media player on."""
|
||||||
self._player_state = STATE_PLAYING
|
self._player_state = STATE_PLAYING
|
||||||
@ -109,6 +117,11 @@ class AbstractDemoPlayer(MediaPlayerDevice):
|
|||||||
self._player_state = STATE_PAUSED
|
self._player_state = STATE_PAUSED
|
||||||
self.schedule_update_ha_state()
|
self.schedule_update_ha_state()
|
||||||
|
|
||||||
|
def set_shuffle(self, shuffle):
|
||||||
|
"""Enable/disable shuffle mode."""
|
||||||
|
self._shuffle = shuffle
|
||||||
|
self.schedule_update_ha_state()
|
||||||
|
|
||||||
|
|
||||||
class DemoYoutubePlayer(AbstractDemoPlayer):
|
class DemoYoutubePlayer(AbstractDemoPlayer):
|
||||||
"""A Demo media player that only supports YouTube."""
|
"""A Demo media player that only supports YouTube."""
|
||||||
|
@ -154,6 +154,17 @@ clear_playlist:
|
|||||||
description: Name(s) of entites to change source on
|
description: Name(s) of entites to change source on
|
||||||
example: 'media_player.living_room_chromecast'
|
example: 'media_player.living_room_chromecast'
|
||||||
|
|
||||||
|
shuffle_set:
|
||||||
|
description: Set shuffling state
|
||||||
|
|
||||||
|
fields:
|
||||||
|
entity_id:
|
||||||
|
description: Name(s) of entities to set
|
||||||
|
example: 'media_player.spotify'
|
||||||
|
shuffle:
|
||||||
|
description: True/false for enabling/disabling shuffle
|
||||||
|
example: true
|
||||||
|
|
||||||
sonos_join:
|
sonos_join:
|
||||||
description: Group player together.
|
description: Group player together.
|
||||||
|
|
||||||
|
@ -16,8 +16,8 @@ from homeassistant.components.http import HomeAssistantView
|
|||||||
from homeassistant.components.media_player import (
|
from homeassistant.components.media_player import (
|
||||||
MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, SUPPORT_VOLUME_SET,
|
MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, SUPPORT_VOLUME_SET,
|
||||||
SUPPORT_PLAY, SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA, SUPPORT_NEXT_TRACK,
|
SUPPORT_PLAY, SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA, SUPPORT_NEXT_TRACK,
|
||||||
SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, PLATFORM_SCHEMA,
|
SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_SHUFFLE_SET,
|
||||||
MediaPlayerDevice)
|
PLATFORM_SCHEMA, MediaPlayerDevice)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_NAME, STATE_PLAYING, STATE_PAUSED, STATE_IDLE, STATE_UNKNOWN)
|
CONF_NAME, STATE_PLAYING, STATE_PAUSED, STATE_IDLE, STATE_UNKNOWN)
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
@ -33,7 +33,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
SUPPORT_SPOTIFY = SUPPORT_VOLUME_SET | SUPPORT_PAUSE | SUPPORT_PLAY |\
|
SUPPORT_SPOTIFY = SUPPORT_VOLUME_SET | SUPPORT_PAUSE | SUPPORT_PLAY |\
|
||||||
SUPPORT_NEXT_TRACK | SUPPORT_PREVIOUS_TRACK | SUPPORT_SELECT_SOURCE |\
|
SUPPORT_NEXT_TRACK | SUPPORT_PREVIOUS_TRACK | SUPPORT_SELECT_SOURCE |\
|
||||||
SUPPORT_PLAY_MEDIA
|
SUPPORT_PLAY_MEDIA | SUPPORT_SHUFFLE_SET
|
||||||
|
|
||||||
SCOPE = 'user-read-playback-state user-modify-playback-state'
|
SCOPE = 'user-read-playback-state user-modify-playback-state'
|
||||||
DEFAULT_CACHE_PATH = '.spotify-token-cache'
|
DEFAULT_CACHE_PATH = '.spotify-token-cache'
|
||||||
@ -132,6 +132,7 @@ class SpotifyMediaPlayer(MediaPlayerDevice):
|
|||||||
self._current_device = None
|
self._current_device = None
|
||||||
self._devices = None
|
self._devices = None
|
||||||
self._volume = None
|
self._volume = None
|
||||||
|
self._shuffle = False
|
||||||
self._player = None
|
self._player = None
|
||||||
self._token_info = self._oauth.get_cached_token()
|
self._token_info = self._oauth.get_cached_token()
|
||||||
|
|
||||||
@ -185,11 +186,17 @@ class SpotifyMediaPlayer(MediaPlayerDevice):
|
|||||||
self._volume = device.get('volume_percent') / 100
|
self._volume = device.get('volume_percent') / 100
|
||||||
if device.get('name'):
|
if device.get('name'):
|
||||||
self._current_device = device.get('name')
|
self._current_device = device.get('name')
|
||||||
|
if device.get('shuffle_state'):
|
||||||
|
self._shuffle = device.get('shuffle_state')
|
||||||
|
|
||||||
def set_volume_level(self, volume):
|
def set_volume_level(self, volume):
|
||||||
"""Set the volume level."""
|
"""Set the volume level."""
|
||||||
self._player.volume(int(volume * 100))
|
self._player.volume(int(volume * 100))
|
||||||
|
|
||||||
|
def set_shuffle(self, shuffle):
|
||||||
|
"""Enable/Disable shuffle mode."""
|
||||||
|
self._player.shuffle(shuffle)
|
||||||
|
|
||||||
def media_next_track(self):
|
def media_next_track(self):
|
||||||
"""Skip to next track."""
|
"""Skip to next track."""
|
||||||
self._player.next_track()
|
self._player.next_track()
|
||||||
@ -245,6 +252,11 @@ class SpotifyMediaPlayer(MediaPlayerDevice):
|
|||||||
"""Device volume."""
|
"""Device volume."""
|
||||||
return self._volume
|
return self._volume
|
||||||
|
|
||||||
|
@property
|
||||||
|
def shuffle(self):
|
||||||
|
"""Shuffling state."""
|
||||||
|
return self._shuffle
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def source_list(self):
|
def source_list(self):
|
||||||
"""Playback devices."""
|
"""Playback devices."""
|
||||||
|
@ -17,19 +17,19 @@ from homeassistant.components.media_player import (
|
|||||||
ATTR_MEDIA_PLAYLIST, ATTR_MEDIA_SEASON, ATTR_MEDIA_SEEK_POSITION,
|
ATTR_MEDIA_PLAYLIST, ATTR_MEDIA_SEASON, ATTR_MEDIA_SEEK_POSITION,
|
||||||
ATTR_MEDIA_SERIES_TITLE, ATTR_MEDIA_TITLE, ATTR_MEDIA_TRACK,
|
ATTR_MEDIA_SERIES_TITLE, ATTR_MEDIA_TITLE, ATTR_MEDIA_TRACK,
|
||||||
ATTR_MEDIA_VOLUME_LEVEL, ATTR_MEDIA_VOLUME_MUTED, ATTR_INPUT_SOURCE_LIST,
|
ATTR_MEDIA_VOLUME_LEVEL, ATTR_MEDIA_VOLUME_MUTED, ATTR_INPUT_SOURCE_LIST,
|
||||||
ATTR_MEDIA_POSITION,
|
ATTR_MEDIA_POSITION, ATTR_MEDIA_SHUFFLE,
|
||||||
ATTR_MEDIA_POSITION_UPDATED_AT, DOMAIN, SERVICE_PLAY_MEDIA,
|
ATTR_MEDIA_POSITION_UPDATED_AT, DOMAIN, SERVICE_PLAY_MEDIA,
|
||||||
SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
|
SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
|
||||||
SUPPORT_VOLUME_STEP, SUPPORT_SELECT_SOURCE, SUPPORT_CLEAR_PLAYLIST,
|
SUPPORT_VOLUME_STEP, SUPPORT_SELECT_SOURCE, SUPPORT_CLEAR_PLAYLIST,
|
||||||
ATTR_INPUT_SOURCE, SERVICE_SELECT_SOURCE, SERVICE_CLEAR_PLAYLIST,
|
SUPPORT_SHUFFLE_SET, ATTR_INPUT_SOURCE, SERVICE_SELECT_SOURCE,
|
||||||
MediaPlayerDevice)
|
SERVICE_CLEAR_PLAYLIST, MediaPlayerDevice)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE, CONF_NAME, SERVICE_MEDIA_NEXT_TRACK,
|
ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE, CONF_NAME, SERVICE_MEDIA_NEXT_TRACK,
|
||||||
SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PLAY_PAUSE,
|
SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PLAY_PAUSE,
|
||||||
SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_SEEK, SERVICE_TURN_OFF,
|
SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_SEEK, SERVICE_TURN_OFF,
|
||||||
SERVICE_TURN_ON, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_MUTE,
|
SERVICE_TURN_ON, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_MUTE,
|
||||||
SERVICE_VOLUME_SET, SERVICE_VOLUME_UP, STATE_IDLE, STATE_OFF, STATE_ON,
|
SERVICE_VOLUME_SET, SERVICE_VOLUME_UP, SERVICE_SHUFFLE_SET, STATE_IDLE,
|
||||||
SERVICE_MEDIA_STOP, ATTR_SUPPORTED_FEATURES)
|
STATE_OFF, STATE_ON, SERVICE_MEDIA_STOP, ATTR_SUPPORTED_FEATURES)
|
||||||
from homeassistant.helpers.event import async_track_state_change
|
from homeassistant.helpers.event import async_track_state_change
|
||||||
from homeassistant.helpers.service import async_call_from_config
|
from homeassistant.helpers.service import async_call_from_config
|
||||||
|
|
||||||
@ -356,6 +356,11 @@ class UniversalMediaPlayer(MediaPlayerDevice):
|
|||||||
"""List of available input sources."""
|
"""List of available input sources."""
|
||||||
return self._override_or_child_attr(ATTR_INPUT_SOURCE_LIST)
|
return self._override_or_child_attr(ATTR_INPUT_SOURCE_LIST)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def shuffle(self):
|
||||||
|
"""Boolean if shuffling is enabled."""
|
||||||
|
return self._override_or_child_attr(ATTR_MEDIA_SHUFFLE)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
"""Flag media player features that are supported."""
|
"""Flag media player features that are supported."""
|
||||||
@ -383,6 +388,10 @@ class UniversalMediaPlayer(MediaPlayerDevice):
|
|||||||
if SERVICE_CLEAR_PLAYLIST in self._cmds:
|
if SERVICE_CLEAR_PLAYLIST in self._cmds:
|
||||||
flags |= SUPPORT_CLEAR_PLAYLIST
|
flags |= SUPPORT_CLEAR_PLAYLIST
|
||||||
|
|
||||||
|
if SERVICE_SHUFFLE_SET in self._cmds and \
|
||||||
|
ATTR_MEDIA_SHUFFLE in self._attrs:
|
||||||
|
flags |= SUPPORT_SHUFFLE_SET
|
||||||
|
|
||||||
return flags
|
return flags
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -524,6 +533,15 @@ class UniversalMediaPlayer(MediaPlayerDevice):
|
|||||||
"""
|
"""
|
||||||
return self._async_call_service(SERVICE_CLEAR_PLAYLIST)
|
return self._async_call_service(SERVICE_CLEAR_PLAYLIST)
|
||||||
|
|
||||||
|
def async_set_shuffle(self, shuffle):
|
||||||
|
"""Enable/disable shuffling.
|
||||||
|
|
||||||
|
This method must be run in the event loop and returns a coroutine.
|
||||||
|
"""
|
||||||
|
data = {ATTR_MEDIA_SHUFFLE: shuffle}
|
||||||
|
return self._async_call_service(
|
||||||
|
SERVICE_SHUFFLE_SET, data, allow_override=True)
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_update(self):
|
def async_update(self):
|
||||||
"""Update state in HA."""
|
"""Update state in HA."""
|
||||||
|
@ -324,6 +324,7 @@ SERVICE_MEDIA_STOP = 'media_stop'
|
|||||||
SERVICE_MEDIA_NEXT_TRACK = 'media_next_track'
|
SERVICE_MEDIA_NEXT_TRACK = 'media_next_track'
|
||||||
SERVICE_MEDIA_PREVIOUS_TRACK = 'media_previous_track'
|
SERVICE_MEDIA_PREVIOUS_TRACK = 'media_previous_track'
|
||||||
SERVICE_MEDIA_SEEK = 'media_seek'
|
SERVICE_MEDIA_SEEK = 'media_seek'
|
||||||
|
SERVICE_SHUFFLE_SET = 'shuffle_set'
|
||||||
|
|
||||||
SERVICE_ALARM_DISARM = 'alarm_disarm'
|
SERVICE_ALARM_DISARM = 'alarm_disarm'
|
||||||
SERVICE_ALARM_ARM_HOME = 'alarm_arm_home'
|
SERVICE_ALARM_ARM_HOME = 'alarm_arm_home'
|
||||||
|
@ -30,6 +30,7 @@ class MockMediaPlayer(media_player.MediaPlayerDevice):
|
|||||||
self._source = None
|
self._source = None
|
||||||
self._tracks = 12
|
self._tracks = 12
|
||||||
self._media_image_url = None
|
self._media_image_url = None
|
||||||
|
self._shuffle = False
|
||||||
|
|
||||||
self.service_calls = {
|
self.service_calls = {
|
||||||
'turn_on': mock_service(
|
'turn_on': mock_service(
|
||||||
@ -67,6 +68,9 @@ class MockMediaPlayer(media_player.MediaPlayerDevice):
|
|||||||
'clear_playlist': mock_service(
|
'clear_playlist': mock_service(
|
||||||
hass, media_player.DOMAIN,
|
hass, media_player.DOMAIN,
|
||||||
media_player.SERVICE_CLEAR_PLAYLIST),
|
media_player.SERVICE_CLEAR_PLAYLIST),
|
||||||
|
'shuffle_set': mock_service(
|
||||||
|
hass, media_player.DOMAIN,
|
||||||
|
media_player.SERVICE_SHUFFLE_SET),
|
||||||
}
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -99,6 +103,11 @@ class MockMediaPlayer(media_player.MediaPlayerDevice):
|
|||||||
"""Image url of current playing media."""
|
"""Image url of current playing media."""
|
||||||
return self._media_image_url
|
return self._media_image_url
|
||||||
|
|
||||||
|
@property
|
||||||
|
def shuffle(self):
|
||||||
|
"""Return true if the media player is shuffling."""
|
||||||
|
return self._shuffle
|
||||||
|
|
||||||
def turn_on(self):
|
def turn_on(self):
|
||||||
"""Mock turn_on function."""
|
"""Mock turn_on function."""
|
||||||
self._state = STATE_UNKNOWN
|
self._state = STATE_UNKNOWN
|
||||||
@ -131,6 +140,10 @@ class MockMediaPlayer(media_player.MediaPlayerDevice):
|
|||||||
"""Clear players playlist."""
|
"""Clear players playlist."""
|
||||||
self._tracks = 0
|
self._tracks = 0
|
||||||
|
|
||||||
|
def set_shuffle(self, shuffle):
|
||||||
|
"""Clear players playlist."""
|
||||||
|
self._shuffle = shuffle
|
||||||
|
|
||||||
|
|
||||||
class TestMediaPlayer(unittest.TestCase):
|
class TestMediaPlayer(unittest.TestCase):
|
||||||
"""Test the media_player module."""
|
"""Test the media_player module."""
|
||||||
@ -164,6 +177,9 @@ class TestMediaPlayer(unittest.TestCase):
|
|||||||
self.mock_source_id = input_select.ENTITY_ID_FORMAT.format('source')
|
self.mock_source_id = input_select.ENTITY_ID_FORMAT.format('source')
|
||||||
self.hass.states.set(self.mock_source_id, 'dvd')
|
self.hass.states.set(self.mock_source_id, 'dvd')
|
||||||
|
|
||||||
|
self.mock_shuffle_switch_id = switch.ENTITY_ID_FORMAT.format('shuffle')
|
||||||
|
self.hass.states.set(self.mock_shuffle_switch_id, STATE_OFF)
|
||||||
|
|
||||||
self.config_children_only = {
|
self.config_children_only = {
|
||||||
'name': 'test', 'platform': 'universal',
|
'name': 'test', 'platform': 'universal',
|
||||||
'children': [media_player.ENTITY_ID_FORMAT.format('mock1'),
|
'children': [media_player.ENTITY_ID_FORMAT.format('mock1'),
|
||||||
@ -178,7 +194,8 @@ class TestMediaPlayer(unittest.TestCase):
|
|||||||
'volume_level': self.mock_volume_id,
|
'volume_level': self.mock_volume_id,
|
||||||
'source': self.mock_source_id,
|
'source': self.mock_source_id,
|
||||||
'source_list': self.mock_source_list_id,
|
'source_list': self.mock_source_list_id,
|
||||||
'state': self.mock_state_switch_id
|
'state': self.mock_state_switch_id,
|
||||||
|
'shuffle': self.mock_shuffle_switch_id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -541,6 +558,7 @@ class TestMediaPlayer(unittest.TestCase):
|
|||||||
config['commands']['volume_mute'] = 'test'
|
config['commands']['volume_mute'] = 'test'
|
||||||
config['commands']['volume_set'] = 'test'
|
config['commands']['volume_set'] = 'test'
|
||||||
config['commands']['select_source'] = 'test'
|
config['commands']['select_source'] = 'test'
|
||||||
|
config['commands']['shuffle_set'] = 'test'
|
||||||
|
|
||||||
ump = universal.UniversalMediaPlayer(self.hass, **config)
|
ump = universal.UniversalMediaPlayer(self.hass, **config)
|
||||||
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config['name'])
|
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config['name'])
|
||||||
@ -553,7 +571,7 @@ class TestMediaPlayer(unittest.TestCase):
|
|||||||
|
|
||||||
check_flags = universal.SUPPORT_TURN_ON | universal.SUPPORT_TURN_OFF \
|
check_flags = universal.SUPPORT_TURN_ON | universal.SUPPORT_TURN_OFF \
|
||||||
| universal.SUPPORT_VOLUME_STEP | universal.SUPPORT_VOLUME_MUTE \
|
| universal.SUPPORT_VOLUME_STEP | universal.SUPPORT_VOLUME_MUTE \
|
||||||
| universal.SUPPORT_SELECT_SOURCE
|
| universal.SUPPORT_SELECT_SOURCE | universal.SUPPORT_SHUFFLE_SET
|
||||||
|
|
||||||
self.assertEqual(check_flags, ump.supported_features)
|
self.assertEqual(check_flags, ump.supported_features)
|
||||||
|
|
||||||
@ -674,6 +692,11 @@ class TestMediaPlayer(unittest.TestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
1, len(self.mock_mp_2.service_calls['clear_playlist']))
|
1, len(self.mock_mp_2.service_calls['clear_playlist']))
|
||||||
|
|
||||||
|
run_coroutine_threadsafe(
|
||||||
|
ump.async_set_shuffle(True),
|
||||||
|
self.hass.loop).result()
|
||||||
|
self.assertEqual(1, len(self.mock_mp_2.service_calls['shuffle_set']))
|
||||||
|
|
||||||
def test_service_call_to_command(self):
|
def test_service_call_to_command(self):
|
||||||
"""Test service call to command."""
|
"""Test service call to command."""
|
||||||
config = self.config_children_only
|
config = self.config_children_only
|
||||||
|
Loading…
x
Reference in New Issue
Block a user