310 lines
9.2 KiB
Python
310 lines
9.2 KiB
Python
"""Support for Apple TV media player."""
|
|
import logging
|
|
|
|
from pyatv.const import (
|
|
DeviceState,
|
|
FeatureName,
|
|
FeatureState,
|
|
MediaType,
|
|
RepeatState,
|
|
ShuffleState,
|
|
)
|
|
|
|
from homeassistant.components.media_player import MediaPlayerEntity
|
|
from homeassistant.components.media_player.const import (
|
|
MEDIA_TYPE_MUSIC,
|
|
MEDIA_TYPE_TVSHOW,
|
|
MEDIA_TYPE_VIDEO,
|
|
REPEAT_MODE_ALL,
|
|
REPEAT_MODE_OFF,
|
|
REPEAT_MODE_ONE,
|
|
SUPPORT_NEXT_TRACK,
|
|
SUPPORT_PAUSE,
|
|
SUPPORT_PLAY,
|
|
SUPPORT_PLAY_MEDIA,
|
|
SUPPORT_PREVIOUS_TRACK,
|
|
SUPPORT_REPEAT_SET,
|
|
SUPPORT_SEEK,
|
|
SUPPORT_SHUFFLE_SET,
|
|
SUPPORT_STOP,
|
|
SUPPORT_TURN_OFF,
|
|
SUPPORT_TURN_ON,
|
|
SUPPORT_VOLUME_STEP,
|
|
)
|
|
from homeassistant.const import (
|
|
CONF_NAME,
|
|
STATE_IDLE,
|
|
STATE_OFF,
|
|
STATE_PAUSED,
|
|
STATE_PLAYING,
|
|
STATE_STANDBY,
|
|
)
|
|
from homeassistant.core import callback
|
|
import homeassistant.util.dt as dt_util
|
|
|
|
from . import AppleTVEntity
|
|
from .const import DOMAIN
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
PARALLEL_UPDATES = 0
|
|
|
|
SUPPORT_APPLE_TV = (
|
|
SUPPORT_TURN_ON
|
|
| SUPPORT_TURN_OFF
|
|
| SUPPORT_PLAY_MEDIA
|
|
| SUPPORT_PAUSE
|
|
| SUPPORT_PLAY
|
|
| SUPPORT_SEEK
|
|
| SUPPORT_STOP
|
|
| SUPPORT_NEXT_TRACK
|
|
| SUPPORT_PREVIOUS_TRACK
|
|
| SUPPORT_VOLUME_STEP
|
|
| SUPPORT_REPEAT_SET
|
|
| SUPPORT_SHUFFLE_SET
|
|
)
|
|
|
|
|
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
|
"""Load Apple TV media player based on a config entry."""
|
|
name = config_entry.data[CONF_NAME]
|
|
manager = hass.data[DOMAIN][config_entry.unique_id]
|
|
async_add_entities([AppleTvMediaPlayer(name, config_entry.unique_id, manager)])
|
|
|
|
|
|
class AppleTvMediaPlayer(AppleTVEntity, MediaPlayerEntity):
|
|
"""Representation of an Apple TV media player."""
|
|
|
|
_attr_supported_features = SUPPORT_APPLE_TV
|
|
|
|
def __init__(self, name, identifier, manager, **kwargs):
|
|
"""Initialize the Apple TV media player."""
|
|
super().__init__(name, identifier, manager, **kwargs)
|
|
self._playing = None
|
|
|
|
@callback
|
|
def async_device_connected(self, atv):
|
|
"""Handle when connection is made to device."""
|
|
self.atv.push_updater.listener = self
|
|
self.atv.push_updater.start()
|
|
|
|
@callback
|
|
def async_device_disconnected(self):
|
|
"""Handle when connection was lost to device."""
|
|
self.atv.push_updater.stop()
|
|
self.atv.push_updater.listener = None
|
|
|
|
@property
|
|
def state(self):
|
|
"""Return the state of the device."""
|
|
if self.manager.is_connecting:
|
|
return None
|
|
if self.atv is None:
|
|
return STATE_OFF
|
|
if self._playing:
|
|
state = self._playing.device_state
|
|
if state in (DeviceState.Idle, DeviceState.Loading):
|
|
return STATE_IDLE
|
|
if state == DeviceState.Playing:
|
|
return STATE_PLAYING
|
|
if state in (DeviceState.Paused, DeviceState.Seeking, DeviceState.Stopped):
|
|
return STATE_PAUSED
|
|
return STATE_STANDBY # Bad or unknown state?
|
|
return None
|
|
|
|
@callback
|
|
def playstatus_update(self, _, playing):
|
|
"""Print what is currently playing when it changes."""
|
|
self._playing = playing
|
|
self.async_write_ha_state()
|
|
|
|
@callback
|
|
def playstatus_error(self, _, exception):
|
|
"""Inform about an error and restart push updates."""
|
|
_LOGGER.warning("A %s error occurred: %s", exception.__class__, exception)
|
|
self._playing = None
|
|
self.async_write_ha_state()
|
|
|
|
@property
|
|
def app_id(self):
|
|
"""ID of the current running app."""
|
|
if self._is_feature_available(FeatureName.App):
|
|
return self.atv.metadata.app.identifier
|
|
return None
|
|
|
|
@property
|
|
def app_name(self):
|
|
"""Name of the current running app."""
|
|
if self._is_feature_available(FeatureName.App):
|
|
return self.atv.metadata.app.name
|
|
return None
|
|
|
|
@property
|
|
def media_content_type(self):
|
|
"""Content type of current playing media."""
|
|
if self._playing:
|
|
return {
|
|
MediaType.Video: MEDIA_TYPE_VIDEO,
|
|
MediaType.Music: MEDIA_TYPE_MUSIC,
|
|
MediaType.TV: MEDIA_TYPE_TVSHOW,
|
|
}.get(self._playing.media_type)
|
|
return None
|
|
|
|
@property
|
|
def media_duration(self):
|
|
"""Duration of current playing media in seconds."""
|
|
if self._playing:
|
|
return self._playing.total_time
|
|
return None
|
|
|
|
@property
|
|
def media_position(self):
|
|
"""Position of current playing media in seconds."""
|
|
if self._playing:
|
|
return self._playing.position
|
|
return None
|
|
|
|
@property
|
|
def media_position_updated_at(self):
|
|
"""Last valid time of media position."""
|
|
if self.state in (STATE_PLAYING, STATE_PAUSED):
|
|
return dt_util.utcnow()
|
|
return None
|
|
|
|
async def async_play_media(self, media_type, media_id, **kwargs):
|
|
"""Send the play_media command to the media player."""
|
|
await self.atv.stream.play_url(media_id)
|
|
|
|
@property
|
|
def media_image_hash(self):
|
|
"""Hash value for media image."""
|
|
state = self.state
|
|
if self._playing and state not in [None, STATE_OFF, STATE_IDLE]:
|
|
return self.atv.metadata.artwork_id
|
|
return None
|
|
|
|
async def async_get_media_image(self):
|
|
"""Fetch media image of current playing image."""
|
|
state = self.state
|
|
if self._playing and state not in [STATE_OFF, STATE_IDLE]:
|
|
artwork = await self.atv.metadata.artwork()
|
|
if artwork:
|
|
return artwork.bytes, artwork.mimetype
|
|
|
|
return None, None
|
|
|
|
@property
|
|
def media_title(self):
|
|
"""Title of current playing media."""
|
|
if self._playing:
|
|
return self._playing.title
|
|
return None
|
|
|
|
@property
|
|
def media_artist(self):
|
|
"""Artist of current playing media, music track only."""
|
|
if self._is_feature_available(FeatureName.Artist):
|
|
return self._playing.artist
|
|
return None
|
|
|
|
@property
|
|
def media_album_name(self):
|
|
"""Album name of current playing media, music track only."""
|
|
if self._is_feature_available(FeatureName.Album):
|
|
return self._playing.album
|
|
return None
|
|
|
|
@property
|
|
def repeat(self):
|
|
"""Return current repeat mode."""
|
|
if self._is_feature_available(FeatureName.Repeat):
|
|
return {
|
|
RepeatState.Track: REPEAT_MODE_ONE,
|
|
RepeatState.All: REPEAT_MODE_ALL,
|
|
}.get(self._playing.repeat, REPEAT_MODE_OFF)
|
|
return None
|
|
|
|
@property
|
|
def shuffle(self):
|
|
"""Boolean if shuffle is enabled."""
|
|
if self._is_feature_available(FeatureName.Shuffle):
|
|
return self._playing.shuffle != ShuffleState.Off
|
|
return None
|
|
|
|
def _is_feature_available(self, feature):
|
|
"""Return if a feature is available."""
|
|
if self.atv and self._playing:
|
|
return self.atv.features.in_state(FeatureState.Available, feature)
|
|
return False
|
|
|
|
async def async_turn_on(self):
|
|
"""Turn the media player on."""
|
|
await self.manager.connect()
|
|
|
|
async def async_turn_off(self):
|
|
"""Turn the media player off."""
|
|
self._playing = None
|
|
await self.manager.disconnect()
|
|
|
|
async def async_media_play_pause(self):
|
|
"""Pause media on media player."""
|
|
if self._playing:
|
|
await self.atv.remote_control.play_pause()
|
|
return None
|
|
|
|
async def async_media_play(self):
|
|
"""Play media."""
|
|
if self.atv:
|
|
await self.atv.remote_control.play()
|
|
|
|
async def async_media_stop(self):
|
|
"""Stop the media player."""
|
|
if self.atv:
|
|
await self.atv.remote_control.stop()
|
|
|
|
async def async_media_pause(self):
|
|
"""Pause the media player."""
|
|
if self.atv:
|
|
await self.atv.remote_control.pause()
|
|
|
|
async def async_media_next_track(self):
|
|
"""Send next track command."""
|
|
if self.atv:
|
|
await self.atv.remote_control.next()
|
|
|
|
async def async_media_previous_track(self):
|
|
"""Send previous track command."""
|
|
if self.atv:
|
|
await self.atv.remote_control.previous()
|
|
|
|
async def async_media_seek(self, position):
|
|
"""Send seek command."""
|
|
if self.atv:
|
|
await self.atv.remote_control.set_position(position)
|
|
|
|
async def async_volume_up(self):
|
|
"""Turn volume up for media player."""
|
|
if self.atv:
|
|
await self.atv.remote_control.volume_up()
|
|
|
|
async def async_volume_down(self):
|
|
"""Turn volume down for media player."""
|
|
if self.atv:
|
|
await self.atv.remote_control.volume_down()
|
|
|
|
async def async_set_repeat(self, repeat):
|
|
"""Set repeat mode."""
|
|
if self.atv:
|
|
mode = {
|
|
REPEAT_MODE_ONE: RepeatState.Track,
|
|
REPEAT_MODE_ALL: RepeatState.All,
|
|
}.get(repeat, RepeatState.Off)
|
|
await self.atv.remote_control.set_repeat(mode)
|
|
|
|
async def async_set_shuffle(self, shuffle):
|
|
"""Enable/disable shuffle mode."""
|
|
if self.atv:
|
|
await self.atv.remote_control.set_shuffle(
|
|
ShuffleState.Songs if shuffle else ShuffleState.Off
|
|
)
|