Home-AssistantConfig/custom_components/media_player/cast.py

329 lines
10 KiB
Python
Executable File

"""
Provide functionality to interact with Cast devices on the network.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.cast/
"""
# pylint: disable=import-error
import logging
import voluptuous as vol
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_STOP, SUPPORT_PLAY, MediaPlayerDevice, PLATFORM_SCHEMA)
from homeassistant.const import (
CONF_HOST, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING,
STATE_UNKNOWN)
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
# Do not upgrade to 1.0.2, it breaks a bunch of stuff
# https://github.com/home-assistant/home-assistant/issues/10926
REQUIREMENTS = ['pychromecast==1.0.3']
_LOGGER = logging.getLogger(__name__)
CONF_IGNORE_CEC = 'ignore_cec'
CAST_SPLASH = 'https://home-assistant.io/images/cast/splash.png'
DEFAULT_PORT = 8009
SUPPORT_CAST = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PREVIOUS_TRACK | \
SUPPORT_NEXT_TRACK | SUPPORT_PLAY_MEDIA | SUPPORT_STOP | SUPPORT_PLAY
KNOWN_HOSTS_KEY = 'cast_known_hosts'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOST): cv.string,
vol.Optional(CONF_IGNORE_CEC): [cv.string],
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the cast platform."""
import pychromecast
# Import CEC IGNORE attributes
pychromecast.IGNORE_CEC += config.get(CONF_IGNORE_CEC, [])
known_hosts = hass.data.get(KNOWN_HOSTS_KEY)
if known_hosts is None:
known_hosts = hass.data[KNOWN_HOSTS_KEY] = []
if discovery_info:
host = (discovery_info.get('host'), discovery_info.get('port'))
if host in known_hosts:
return
hosts = [host]
elif CONF_HOST in config:
host = (config.get(CONF_HOST), DEFAULT_PORT)
if host in known_hosts:
return
hosts = [host]
else:
hosts = [tuple(dev[:2]) for dev in pychromecast.discover_chromecasts()
if tuple(dev[:2]) not in known_hosts]
casts = []
# get_chromecasts() returns Chromecast objects with the correct friendly
# name for grouped devices
all_chromecasts = pychromecast.get_chromecasts()
for host in hosts:
(_, port) = host
found = [device for device in all_chromecasts
if (device.host, device.port) == host]
if found:
try:
casts.append(CastDevice(found[0]))
known_hosts.append(host)
except pychromecast.ChromecastConnectionError:
pass
# do not add groups using pychromecast.Chromecast as it leads to names
# collision since pychromecast.Chromecast will get device name instead
# of group name
elif port == DEFAULT_PORT:
try:
# add the device anyway, get_chromecasts couldn't find it
casts.append(CastDevice(pychromecast.Chromecast(*host)))
known_hosts.append(host)
except pychromecast.ChromecastConnectionError:
pass
add_devices(casts)
class CastDevice(MediaPlayerDevice):
"""Representation of a Cast device on the network."""
def __init__(self, chromecast):
"""Initialize the Cast device."""
self.cast = chromecast
self.cast.socket_client.receiver_controller.register_status_listener(
self)
self.cast.socket_client.media_controller.register_status_listener(self)
self.cast_status = self.cast.status
self.media_status = self.cast.media_controller.status
self.media_status_received = None
@property
def should_poll(self):
"""No polling needed."""
return False
@property
def name(self):
"""Return the name of the device."""
return self.cast.device.friendly_name
# MediaPlayerDevice properties and methods
@property
def state(self):
"""Return the state of the player."""
if self.media_status is None:
return STATE_UNKNOWN
elif self.media_status.player_is_playing:
return STATE_PLAYING
elif self.media_status.player_is_paused:
return STATE_PAUSED
elif self.media_status.player_is_idle:
return STATE_IDLE
elif self.cast.is_idle:
return STATE_OFF
return STATE_UNKNOWN
@property
def volume_level(self):
"""Volume level of the media player (0..1)."""
return self.cast_status.volume_level if self.cast_status else None
@property
def is_volume_muted(self):
"""Boolean if volume is currently muted."""
return self.cast_status.volume_muted if self.cast_status else None
@property
def media_content_id(self):
"""Content ID of current playing media."""
return self.media_status.content_id if self.media_status else None
@property
def media_content_type(self):
"""Content type of current playing media."""
if self.media_status is None:
return None
elif self.media_status.media_is_tvshow:
return MEDIA_TYPE_TVSHOW
elif self.media_status.media_is_movie:
return MEDIA_TYPE_VIDEO
elif self.media_status.media_is_musictrack:
return MEDIA_TYPE_MUSIC
return None
@property
def media_duration(self):
"""Duration of current playing media in seconds."""
return self.media_status.duration if self.media_status else None
@property
def media_image_url(self):
"""Image url of current playing media."""
if self.media_status is None:
return None
images = self.media_status.images
return images[0].url if images else None
@property
def media_title(self):
"""Title of current playing media."""
return self.media_status.title if self.media_status else None
@property
def media_artist(self):
"""Artist of current playing media (Music track only)."""
return self.media_status.artist if self.media_status else None
@property
def media_album(self):
"""Album of current playing media (Music track only)."""
return self.media_status.album_name if self.media_status else None
@property
def media_album_artist(self):
"""Album arist of current playing media (Music track only)."""
return self.media_status.album_artist if self.media_status else None
@property
def media_track(self):
"""Track number of current playing media (Music track only)."""
return self.media_status.track if self.media_status else None
@property
def media_series_title(self):
"""Return the title of the series of current playing media."""
return self.media_status.series_title if self.media_status else None
@property
def media_season(self):
"""Season of current playing media (TV Show only)."""
return self.media_status.season if self.media_status else None
@property
def media_episode(self):
"""Episode of current playing media (TV Show only)."""
return self.media_status.episode if self.media_status else None
@property
def app_id(self):
"""Return the ID of the current running app."""
return self.cast.app_id
@property
def app_name(self):
"""Name of the current running app."""
return self.cast.app_display_name
@property
def supported_features(self):
"""Flag media player features that are supported."""
return SUPPORT_CAST
@property
def media_position(self):
"""Position of current playing media in seconds."""
if self.media_status is None or \
not (self.media_status.player_is_playing or
self.media_status.player_is_paused or
self.media_status.player_is_idle):
return None
return self.media_status.current_time
@property
def media_position_updated_at(self):
"""When was the position of the current playing media valid.
Returns value from homeassistant.util.dt.utcnow().
"""
return self.media_status_received
def turn_on(self):
"""Turn on the ChromeCast."""
# The only way we can turn the Chromecast is on is by launching an app
if not self.cast.status or not self.cast.status.is_active_input:
import pychromecast
if self.cast.app_id:
self.cast.quit_app()
self.cast.play_media(
CAST_SPLASH, pychromecast.STREAM_TYPE_BUFFERED)
def turn_off(self):
"""Turn Chromecast off."""
self.cast.quit_app()
def mute_volume(self, mute):
"""Mute the volume."""
self.cast.set_volume_muted(mute)
def set_volume_level(self, volume):
"""Set volume level, range 0..1."""
self.cast.set_volume(volume)
def media_play(self):
"""Send play command."""
self.cast.media_controller.play()
def media_pause(self):
"""Send pause command."""
self.cast.media_controller.pause()
def media_stop(self):
"""Send stop command."""
self.cast.media_controller.stop()
def media_previous_track(self):
"""Send previous track command."""
self.cast.media_controller.rewind()
def media_next_track(self):
"""Send next track command."""
self.cast.media_controller.skip()
def media_seek(self, position):
"""Seek the media to a specific location."""
self.cast.media_controller.seek(position)
def play_media(self, media_type, media_id, **kwargs):
"""Play media from a URL."""
self.cast.media_controller.play_media(media_id, media_type)
# Implementation of chromecast status_listener methods
def new_cast_status(self, status):
"""Handle updates of the cast status."""
self.cast_status = status
self.schedule_update_ha_state()
def new_media_status(self, status):
"""Handle updates of the media status."""
self.media_status = status
self.media_status_received = dt_util.utcnow()
self.schedule_update_ha_state()