Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 55 additions & 25 deletions homeassistant/components/media_player/apple_tv.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@

from homeassistant.components.media_player import (
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK,
SUPPORT_STOP, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, MediaPlayerDevice,
PLATFORM_SCHEMA, MEDIA_TYPE_MUSIC, MEDIA_TYPE_VIDEO, MEDIA_TYPE_TVSHOW)
SUPPORT_STOP, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_TURN_ON,
SUPPORT_TURN_OFF, MediaPlayerDevice, PLATFORM_SCHEMA, MEDIA_TYPE_MUSIC,
MEDIA_TYPE_VIDEO, MEDIA_TYPE_TVSHOW)
from homeassistant.const import (
STATE_IDLE, STATE_PAUSED, STATE_PLAYING, STATE_STANDBY, CONF_HOST,
CONF_NAME)
STATE_OFF, CONF_NAME)
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
Expand All @@ -28,6 +29,7 @@
_LOGGER = logging.getLogger(__name__)

CONF_LOGIN_ID = 'login_id'
CONF_START_OFF = 'start_off'

DEFAULT_NAME = 'Apple TV'

Expand All @@ -37,6 +39,7 @@
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_LOGIN_ID): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_START_OFF, default=False): cv.boolean
})


Expand All @@ -50,10 +53,12 @@ def async_setup_platform(hass, config, async_add_entities,
name = discovery_info['name']
host = discovery_info['host']
login_id = discovery_info['hsgid']
start_off = False
else:
name = config.get(CONF_NAME)
host = config.get(CONF_HOST)
login_id = config.get(CONF_LOGIN_ID)
start_off = config.get(CONF_START_OFF)

if DATA_APPLE_TV not in hass.data:
hass.data[DATA_APPLE_TV] = []
Expand All @@ -65,18 +70,23 @@ def async_setup_platform(hass, config, async_add_entities,
details = pyatv.AppleTVDevice(name, host, login_id)
session = async_get_clientsession(hass)
atv = pyatv.connect_to_apple_tv(details, hass.loop, session=session)
entity = AppleTvDevice(atv, name)
entity = AppleTvDevice(atv, name, start_off)

yield from async_add_entities([entity], update_before_add=True)


class AppleTvDevice(MediaPlayerDevice):
"""Representation of an Apple TV device."""

def __init__(self, atv, name):
def __init__(self, atv, name, is_off):
"""Initialize the Apple TV device."""
self._name = name
self._atv = atv
self._name = name
self._is_off = is_off
self._playing = None
self._artwork_hash = None

def _reset(self):
self._playing = None
self._artwork_hash = None

Expand All @@ -88,6 +98,9 @@ def name(self):
@property
def state(self):
"""Return the state of the device."""
if self._is_off:
return STATE_OFF

if self._playing is not None:
from pyatv import const
state = self._playing.play_state
Expand All @@ -107,23 +120,24 @@ def state(self):
@asyncio.coroutine
def async_update(self):
"""Retrieve latest state."""
from pyatv import exceptions
try:
playing = yield from self._atv.metadata.playing()

if self._has_playing_media_changed(playing):
base = str(playing.title) + str(playing.artist) + \
str(playing.album) + str(playing.total_time)
self._artwork_hash = hashlib.md5(
base.encode('utf-8')).hexdigest()

self._playing = playing
except exceptions.AuthenticationError as ex:
_LOGGER.warning('%s (bad login id?)', str(ex))
except aiohttp.errors.ClientOSError as ex:
_LOGGER.error('failed to connect to Apple TV (%s)', str(ex))
except asyncio.TimeoutError:
_LOGGER.warning('timed out while connecting to Apple TV')
if not self._is_off:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use a guard clause:

if self._is_off:
    return

It will save you from indentation hell 👍

from pyatv import exceptions
try:
playing = yield from self._atv.metadata.playing()

if self._has_playing_media_changed(playing):
base = str(playing.title) + str(playing.artist) + \
str(playing.album) + str(playing.total_time)
self._artwork_hash = hashlib.md5(
base.encode('utf-8')).hexdigest()

self._playing = playing
except exceptions.AuthenticationError as ex:
_LOGGER.warning('%s (bad login id?)', str(ex))
except aiohttp.errors.ClientOSError as ex:
_LOGGER.error('failed to connect to Apple TV (%s)', str(ex))
except asyncio.TimeoutError:
_LOGGER.warning('timed out while connecting to Apple TV')

def _has_playing_media_changed(self, new_playing):
if self._playing is None:
Expand All @@ -132,6 +146,18 @@ def _has_playing_media_changed(self, new_playing):
return new_playing.media_type != old_playing.media_type or \
new_playing.title != old_playing.title

def turn_on(self):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this only sets simple variables, please turn this into it's async variant async_turn_off. Has to be a coroutine. Make sure to also rename _reset to be called _async_reset and make it a @callback (imported from ha.core)

"""Turn the media player on."""
self._is_off = False
self._reset()
self.schedule_update_ha_state()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no need to call this. Your device does not overwrite should_poll to return False, so Home Assistant is polling this device, including polling it right after it calls a service.


def turn_off(self):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same

"""Turn the media player off."""
self._is_off = True
self._reset()
self.schedule_update_ha_state()

@property
def media_content_type(self):
"""Content type of current playing media."""
Expand Down Expand Up @@ -183,20 +209,24 @@ def async_get_media_image(self):
def media_title(self):
"""Title of current playing media."""
if self._playing is not None:
if self.state == STATE_IDLE:
return 'Nothing playing'
title = self._playing.title
return title if title else "No title"

@property
def supported_features(self):
"""Flag media player features that are supported."""
features = SUPPORT_TURN_ON | SUPPORT_TURN_OFF
if self._playing is not None:
if self.state != STATE_IDLE:
return SUPPORT_PAUSE | SUPPORT_PLAY | \
features |= SUPPORT_PAUSE | SUPPORT_PLAY | \
SUPPORT_SEEK | SUPPORT_STOP | \
SUPPORT_NEXT_TRACK | SUPPORT_PREVIOUS_TRACK | \
SUPPORT_PLAY_MEDIA
else:
return SUPPORT_PLAY_MEDIA
features |= SUPPORT_PLAY_MEDIA

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can move SUPPORT_PLAY_MEDIA to be always part of features and return when _playing is None or state == STATE_IDLE

return features

def async_media_play_pause(self):
"""Pause media on media player.
Expand Down