Initial refactor media player

This commit is contained in:
Paulus Schoutsen 2015-06-01 00:24:17 -07:00
parent dbf2f6223c
commit a7b79fc8b2
5 changed files with 345 additions and 154 deletions

View File

@ -8,7 +8,7 @@ import logging
from homeassistant.const import (
SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_MUTE,
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREV_TRACK,
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK,
SERVICE_MEDIA_PLAY_PAUSE)
@ -43,7 +43,7 @@ def media_next_track(hass):
def media_prev_track(hass):
""" Press the keyboard button for prev track. """
hass.services.call(DOMAIN, SERVICE_MEDIA_PREV_TRACK)
hass.services.call(DOMAIN, SERVICE_MEDIA_PREVIOUS_TRACK)
def setup(hass, config):
@ -79,7 +79,7 @@ def setup(hass, config):
lambda service:
keyboard.tap_key(keyboard.media_next_track_key))
hass.services.register(DOMAIN, SERVICE_MEDIA_PREV_TRACK,
hass.services.register(DOMAIN, SERVICE_MEDIA_PREVIOUS_TRACK,
lambda service:
keyboard.tap_key(keyboard.media_prev_track_key))

View File

@ -10,11 +10,12 @@ from homeassistant.components import discovery
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON,
STATE_OFF, STATE_UNKNOWN, STATE_PLAYING,
ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE, SERVICE_TURN_OFF, SERVICE_TURN_ON,
SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_SET,
SERVICE_VOLUME_MUTE,
SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE,
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREV_TRACK)
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK)
DOMAIN = 'media_player'
DEPENDENCIES = []
@ -28,29 +29,67 @@ DISCOVERY_PLATFORMS = {
SERVICE_YOUTUBE_VIDEO = 'play_youtube_video'
STATE_NO_APP = 'idle'
ATTR_STATE = 'state'
ATTR_OPTIONS = 'options'
ATTR_MEDIA_STATE = 'media_state'
ATTR_MEDIA_VOLUME_LEVEL = 'volume_level'
ATTR_MEDIA_VOLUME_MUTED = 'volume_muted'
ATTR_MEDIA_CONTENT_ID = 'media_content_id'
ATTR_MEDIA_CONTENT_TYPE = 'media_content_type'
ATTR_MEDIA_DURATION = 'media_duration'
ATTR_MEDIA_TITLE = 'media_title'
ATTR_MEDIA_ARTIST = 'media_artist'
ATTR_MEDIA_ALBUM = 'media_album'
ATTR_MEDIA_IMAGE_URL = 'media_image_url'
ATTR_MEDIA_VOLUME = 'media_volume'
ATTR_MEDIA_IS_VOLUME_MUTED = 'media_is_volume_muted'
ATTR_MEDIA_DURATION = 'media_duration'
ATTR_MEDIA_DATE = 'media_date'
ATTR_MEDIA_SERIES_TITLE = 'media_series_title'
ATTR_MEDIA_SEASON = 'media_season'
ATTR_MEDIA_EPISODE = 'media_episode'
ATTR_APP_ID = 'app_id'
ATTR_APP_NAME = 'app_name'
ATTR_SUPPORTED_MEDIA_COMMANDS = 'supported_media_commands'
MEDIA_STATE_UNKNOWN = 'unknown'
MEDIA_STATE_PLAYING = 'playing'
MEDIA_STATE_PAUSED = 'paused'
MEDIA_STATE_STOPPED = 'stopped'
MEDIA_TYPE_MUSIC = 'music'
MEDIA_TYPE_TVSHOW = 'tvshow'
MEDIA_TYPE_VIDEO = 'movie'
SUPPORT_PAUSE = 1
SUPPORT_SEEK = 2
SUPPORT_VOLUME_SET = 4
SUPPORT_VOLUME_MUTE = 8
SUPPORT_PREVIOUS_TRACK = 16
SUPPORT_NEXT_TRACK = 32
SUPPORT_YOUTUBE = 64
SUPPORT_TURN_ON = 128
SUPPORT_TURN_OFF = 256
YOUTUBE_COVER_URL_FORMAT = 'http://img.youtube.com/vi/{}/1.jpg'
SERVICE_TO_METHOD = {
SERVICE_TURN_ON: 'turn_on',
SERVICE_TURN_OFF: 'turn_off',
SERVICE_VOLUME_UP: 'volume_up',
SERVICE_VOLUME_DOWN: 'volume_down',
SERVICE_MEDIA_PLAY_PAUSE: 'media_play_pause',
SERVICE_MEDIA_PLAY: 'media_play',
SERVICE_MEDIA_PAUSE: 'media_pause',
SERVICE_MEDIA_NEXT_TRACK: 'media_next_track',
SERVICE_MEDIA_PREVIOUS_TRACK: 'media_previous_track',
}
ATTR_TO_PROPERTY = {
ATTR_MEDIA_VOLUME_LEVEL: 'volume_level',
ATTR_MEDIA_VOLUME_MUTED: 'is_volume_muted',
ATTR_MEDIA_CONTENT_ID: 'media_content_id',
ATTR_MEDIA_CONTENT_TYPE: 'media_content_type',
ATTR_MEDIA_DURATION: 'media_duration',
ATTR_ENTITY_PICTURE: 'media_image_url',
ATTR_MEDIA_TITLE: 'media_title',
ATTR_MEDIA_ARTIST: 'media_artist',
ATTR_MEDIA_ALBUM: 'media_album',
ATTR_MEDIA_SERIES_TITLE: 'media_series_title',
ATTR_MEDIA_SEASON: 'media_season',
ATTR_MEDIA_EPISODE: 'media_episode',
ATTR_APP_ID: 'app_id',
ATTR_APP_NAME: 'app_name',
ATTR_SUPPORTED_MEDIA_COMMANDS: 'supported_media_commands',
}
def is_on(hass, entity_id=None):
""" Returns true if specified media player entity_id is on.
@ -58,7 +97,7 @@ def is_on(hass, entity_id=None):
entity_ids = [entity_id] if entity_id else hass.states.entity_ids(DOMAIN)
return any(not hass.states.is_state(entity_id, STATE_NO_APP)
return any(not hass.states.is_state(entity_id, STATE_OFF)
for entity_id in entity_ids)
@ -90,21 +129,22 @@ def volume_down(hass, entity_id=None):
hass.services.call(DOMAIN, SERVICE_VOLUME_DOWN, data)
def volume_mute(hass, entity_id=None):
""" Send the media player the command to toggle its mute state. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
def mute_volume(hass, mute, entity_id=None):
""" Send the media player the command for volume down. """
data = {ATTR_MEDIA_VOLUME_MUTED: mute}
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_VOLUME_MUTE, data)
def volume_set(hass, entity_id=None, volume=None):
""" Set volume on media player. """
data = {
key: value for key, value in [
(ATTR_ENTITY_ID, entity_id),
(ATTR_MEDIA_VOLUME, volume),
] if value is not None
}
def set_volume_level(hass, volume, entity_id=None):
""" Send the media player the command for volume down. """
data = {ATTR_MEDIA_VOLUME_LEVEL: volume}
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_VOLUME_SET, data)
@ -137,24 +177,11 @@ def media_next_track(hass, entity_id=None):
hass.services.call(DOMAIN, SERVICE_MEDIA_NEXT_TRACK, data)
def media_prev_track(hass, entity_id=None):
def media_previous_track(hass, entity_id=None):
""" Send the media player the command for prev track. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_MEDIA_PREV_TRACK, data)
SERVICE_TO_METHOD = {
SERVICE_TURN_ON: 'turn_on',
SERVICE_TURN_OFF: 'turn_off',
SERVICE_VOLUME_UP: 'volume_up',
SERVICE_VOLUME_DOWN: 'volume_down',
SERVICE_MEDIA_PLAY_PAUSE: 'media_play_pause',
SERVICE_MEDIA_PLAY: 'media_play',
SERVICE_MEDIA_PAUSE: 'media_pause',
SERVICE_MEDIA_NEXT_TRACK: 'media_next_track',
SERVICE_MEDIA_PREV_TRACK: 'media_prev_track',
}
hass.services.call(DOMAIN, SERVICE_MEDIA_PREVIOUS_TRACK, data)
def setup(hass, config):
@ -183,17 +210,16 @@ def setup(hass, config):
def volume_set_service(service, volume):
""" Set specified volume on the media player. """
target_players = component.extract_from_service(service)
volume = service.data.get(ATTR_MEDIA_VOLUME_LEVEL)
for player in target_players:
player.volume_set(volume)
if volume is not None:
for player in target_players:
player.set_volume_level(volume)
if player.should_poll:
player.update_ha_state(True)
if player.should_poll:
player.update_ha_state(True)
hass.services.register(DOMAIN, SERVICE_VOLUME_SET,
lambda service:
volume_set_service(
service, service.data.get('volume')))
hass.services.register(DOMAIN, SERVICE_VOLUME_SET, volume_set_service)
def volume_mute_service(service, mute):
""" Mute (true) or unmute (false) the media player. """
@ -239,51 +265,198 @@ def setup(hass, config):
class MediaPlayerDevice(Entity):
""" ABC for media player devices. """
# pylint: disable=too-many-public-methods,no-self-use
# Implement these for your media player
@property
def state(self):
""" State of the player. """
return STATE_UNKNOWN
@property
def volume_level(self):
""" Volume level of the media player (0..1). """
return None
@property
def is_volume_muted(self):
""" Boolean if volume is currently muted. """
return None
@property
def media_content_id(self):
""" Content ID of current playing media. """
return None
@property
def media_content_type(self):
""" Content type of current playing media. """
return None
@property
def media_duration(self):
""" Duration of current playing media in seconds. """
return None
@property
def media_image_url(self):
""" Image url of current playing media. """
return None
@property
def media_title(self):
""" Title of current playing media. """
return None
@property
def media_artist(self):
""" Artist of current playing media. (Music track only) """
return None
@property
def media_album(self):
""" Album of current playing media. (Music track only) """
return None
@property
def media_series_title(self):
""" Series title of current playing media. (TV Show only)"""
return None
@property
def media_season(self):
""" Season of current playing media. (TV Show only) """
return None
@property
def media_episode(self):
""" Episode of current playing media. (TV Show only) """
return None
@property
def app_id(self):
""" ID of the current running app. """
return None
@property
def app_name(self):
""" Name of the current running app. """
return None
@property
def supported_media_commands(self):
""" Flags of media commands that are supported. """
return 0
@property
def device_state_attributes(self):
""" Extra attributes a device wants to expose. """
return None
def turn_on(self):
""" turn media player on. """
pass
""" turn the media player on. """
raise NotImplementedError()
def turn_off(self):
""" turn media player off. """
pass
""" turn the media player off. """
raise NotImplementedError()
def volume_up(self):
""" volume_up media player. """
pass
def mute_volume(self, mute):
""" mute the volume. """
raise NotImplementedError()
def volume_down(self):
""" volume_down media player. """
pass
def volume_mute(self, mute):
""" mute (true) or unmute (false) media player. """
pass
def volume_set(self, volume):
""" set volume level of media player. """
pass
def media_play_pause(self):
""" media_play_pause media player. """
pass
def set_volume_level(self, volume):
""" set volume level, range 0..1. """
raise NotImplementedError()
def media_play(self):
""" media_play media player. """
pass
""" Send play commmand. """
raise NotImplementedError()
def media_pause(self):
""" media_pause media player. """
pass
""" Send pause command. """
raise NotImplementedError()
def media_prev_track(self):
""" media_prev_track media player. """
pass
def media_previous_track(self):
""" Send previous track command. """
raise NotImplementedError()
def media_next_track(self):
""" media_next_track media player. """
pass
""" Send next track command. """
raise NotImplementedError()
def play_youtube(self, media_id):
""" Plays a YouTube media. """
pass
raise NotImplementedError()
# No need to overwrite these.
@property
def support_pause(self):
""" Boolean if pause is supported. """
return bool(self.supported_media_commands & SUPPORT_PAUSE)
@property
def support_seek(self):
""" Boolean if seek is supported. """
return bool(self.supported_media_commands & SUPPORT_SEEK)
@property
def support_volume_set(self):
""" Boolean if setting volume is supported. """
return bool(self.supported_media_commands & SUPPORT_VOLUME_SET)
@property
def support_volume_mute(self):
""" Boolean if muting volume is supported. """
return bool(self.supported_media_commands & SUPPORT_VOLUME_MUTE)
@property
def support_previous_track(self):
""" Boolean if previous track command supported. """
return bool(self.supported_media_commands & SUPPORT_PREVIOUS_TRACK)
@property
def support_next_track(self):
""" Boolean if next track command supported. """
return bool(self.supported_media_commands & SUPPORT_NEXT_TRACK)
@property
def support_youtube(self):
""" Boolean if YouTube is supported. """
return bool(self.supported_media_commands & SUPPORT_YOUTUBE)
def volume_up(self):
""" volume_up media player. """
if self.volume_level < 1:
self.set_volume_level(min(1, self.volume_level + .1))
def volume_down(self):
""" volume_down media player. """
if self.volume_level > 0:
self.set_volume_level(max(0, self.volume_level - .1))
def media_play_pause(self):
""" media_play_pause media player. """
if self.player_state == STATE_PLAYING:
self.media_pause()
else:
self.media_play()
@property
def state_attributes(self):
""" Return the state attributes. """
if self.state == STATE_OFF:
state_attr = {}
else:
state_attr = {
attr: getattr(self, prop) for attr, prop
in ATTR_TO_PROPERTY.items() if getattr(self, prop)
}
device_attr = self.device_state_attributes
if device_attr:
state_attr.update(device_attr)
return state_attr

View File

@ -5,121 +5,136 @@ homeassistant.components.media_player.demo
Demo implementation of the media player.
"""
from homeassistant.const import (
STATE_PLAYING, STATE_PAUSED, STATE_OFF)
from homeassistant.components.media_player import (
MediaPlayerDevice, STATE_NO_APP, ATTR_MEDIA_STATE,
ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_TITLE, ATTR_MEDIA_DURATION,
ATTR_MEDIA_VOLUME, MEDIA_STATE_PLAYING, MEDIA_STATE_STOPPED,
YOUTUBE_COVER_URL_FORMAT, ATTR_MEDIA_IS_VOLUME_MUTED)
from homeassistant.const import ATTR_ENTITY_PICTURE
MediaPlayerDevice, YOUTUBE_COVER_URL_FORMAT, MEDIA_TYPE_VIDEO,
SUPPORT_PAUSE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE, SUPPORT_YOUTUBE,
SUPPORT_TURN_ON, SUPPORT_TURN_OFF)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the cast platform. """
add_devices([
DemoMediaPlayer(
DemoYoutubePlayer(
'Living Room', 'eyU3bRy2x44',
'♥♥ The Best Fireplace Video (3 hours)'),
DemoMediaPlayer('Bedroom', 'kxopViU98Xo', 'Epic sax guy 10 hours')
DemoYoutubePlayer('Bedroom', 'kxopViU98Xo', 'Epic sax guy 10 hours')
])
class DemoMediaPlayer(MediaPlayerDevice):
YOUTUBE_PLAYER_SUPPORT = \
SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
SUPPORT_YOUTUBE | SUPPORT_TURN_ON | SUPPORT_TURN_OFF
class DemoYoutubePlayer(MediaPlayerDevice):
""" A Demo media player that only supports YouTube. """
# We only implement the methods that we support
# pylint: disable=abstract-method
def __init__(self, name, youtube_id=None, media_title=None):
self._name = name
self.is_playing = youtube_id is not None
self._player_state = STATE_PLAYING
self.youtube_id = youtube_id
self.media_title = media_title
self.volume = 1.0
self.is_volume_muted = False
self._media_title = media_title
self._volume_level = 1.0
self._volume_muted = False
@property
def should_poll(self):
""" No polling needed for a demo componentn. """
""" We will push an update after each command. """
return False
@property
def name(self):
""" Returns the name of the device. """
""" Name of the media player. """
return self._name
@property
def state(self):
""" Returns the state of the device. """
return STATE_NO_APP if self.youtube_id is None else "YouTube"
""" State of the player. """
return self._player_state
@property
def state_attributes(self):
""" Returns the state attributes. """
if self.youtube_id is None:
return
def volume_level(self):
""" Volume level of the media player (0..1). """
return self._volume_level
state_attr = {
ATTR_MEDIA_CONTENT_ID: self.youtube_id,
ATTR_MEDIA_TITLE: self.media_title,
ATTR_MEDIA_DURATION: 100,
ATTR_MEDIA_VOLUME: self.volume,
ATTR_MEDIA_IS_VOLUME_MUTED: self.is_volume_muted,
ATTR_ENTITY_PICTURE:
YOUTUBE_COVER_URL_FORMAT.format(self.youtube_id)
}
@property
def is_volume_muted(self):
""" Boolean if volume is currently muted. """
return self._volume_muted
if self.is_playing:
state_attr[ATTR_MEDIA_STATE] = MEDIA_STATE_PLAYING
else:
state_attr[ATTR_MEDIA_STATE] = MEDIA_STATE_STOPPED
@property
def media_content_id(self):
""" Content ID of current playing media. """
return self.youtube_id
return state_attr
@property
def media_content_type(self):
""" Content type of current playing media. """
return MEDIA_TYPE_VIDEO
@property
def media_duration(self):
""" Duration of current playing media in seconds. """
return 360
@property
def media_image_url(self):
""" Image url of current playing media. """
return YOUTUBE_COVER_URL_FORMAT.format(self.youtube_id)
@property
def media_title(self):
""" Title of current playing media. """
return self._media_title
@property
def app_name(self):
""" Current running app. """
return "YouTube"
@property
def supported_media_commands(self):
""" Flags of media commands that are supported. """
return YOUTUBE_PLAYER_SUPPORT
def turn_on(self):
""" turn_off media player. """
self.youtube_id = "eyU3bRy2x44"
self.is_playing = False
""" turn the media player on. """
self._player_state = STATE_PLAYING
self.update_ha_state()
def turn_off(self):
""" turn_off media player. """
self.youtube_id = None
self.is_playing = False
""" turn the media player off. """
self._player_state = STATE_OFF
self.update_ha_state()
def volume_up(self):
""" volume_up media player. """
if self.volume < 1:
self.volume += 0.1
self.update_ha_state()
def volume_down(self):
""" volume_down media player. """
if self.volume > 0:
self.volume -= 0.1
self.update_ha_state()
def volume_mute(self, mute):
""" mute (true) or unmute (false) media player. """
self.is_volume_muted = mute
def mute_volume(self, mute):
""" mute the volume. """
self._volume_muted = mute
self.update_ha_state()
def media_play_pause(self):
""" media_play_pause media player. """
self.is_playing = not self.is_playing
def set_volume_level(self, volume):
""" set volume level, range 0..1. """
self._volume_level = volume
self.update_ha_state()
def media_play(self):
""" media_play media player. """
self.is_playing = True
""" Send play commmand. """
self._player_state = STATE_PLAYING
self.update_ha_state()
def media_pause(self):
""" media_pause media player. """
self.is_playing = False
""" Send pause command. """
self._player_state = STATE_PAUSED
self.update_ha_state()
def play_youtube(self, media_id):
""" Plays a YouTube media. """
self.youtube_id = media_id
self.media_title = 'Demo media title'
self.is_playing = True
self._media_title = 'some YouTube video'
self.update_ha_state()

View File

@ -40,6 +40,8 @@ STATE_NOT_HOME = 'not_home'
STATE_UNKNOWN = "unknown"
STATE_OPEN = 'open'
STATE_CLOSED = 'closed'
STATE_PLAYING = 'playing'
STATE_PAUSED = 'paused'
# #### STATE AND EVENT ATTRIBUTES ####
# Contains current time for a TIME_CHANGED event
@ -104,7 +106,7 @@ SERVICE_MEDIA_PLAY_PAUSE = "media_play_pause"
SERVICE_MEDIA_PLAY = "media_play"
SERVICE_MEDIA_PAUSE = "media_pause"
SERVICE_MEDIA_NEXT_TRACK = "media_next_track"
SERVICE_MEDIA_PREV_TRACK = "media_prev_track"
SERVICE_MEDIA_PREVIOUS_TRACK = "media_previous_track"
# #### API / REMOTE ####
SERVER_PORT = 8123

View File

@ -10,9 +10,10 @@ import unittest
import homeassistant as ha
from homeassistant.const import (
STATE_OFF,
SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN,
SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE,
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREV_TRACK, ATTR_ENTITY_ID)
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, ATTR_ENTITY_ID)
import homeassistant.components.media_player as media_player
from helpers import mock_service
@ -29,7 +30,7 @@ class TestMediaPlayer(unittest.TestCase):
self.hass = ha.HomeAssistant()
self.test_entity = media_player.ENTITY_ID_FORMAT.format('living_room')
self.hass.states.set(self.test_entity, media_player.STATE_NO_APP)
self.hass.states.set(self.test_entity, STATE_OFF)
self.test_entity2 = media_player.ENTITY_ID_FORMAT.format('bedroom')
self.hass.states.set(self.test_entity2, "YouTube")
@ -56,7 +57,7 @@ class TestMediaPlayer(unittest.TestCase):
SERVICE_MEDIA_PLAY: media_player.media_play,
SERVICE_MEDIA_PAUSE: media_player.media_pause,
SERVICE_MEDIA_NEXT_TRACK: media_player.media_next_track,
SERVICE_MEDIA_PREV_TRACK: media_player.media_prev_track
SERVICE_MEDIA_PREVIOUS_TRACK: media_player.media_previous_track
}
for service_name, service_method in services.items():