Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
86 changes: 58 additions & 28 deletions homeassistant/components/media_player/apple_tv.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import logging
import hashlib

import aiohttp
import voluptuous as vol

from homeassistant.core import callback
Expand All @@ -19,13 +18,13 @@
MEDIA_TYPE_VIDEO, MEDIA_TYPE_TVSHOW)
from homeassistant.const import (
STATE_IDLE, STATE_PAUSED, STATE_PLAYING, STATE_STANDBY, CONF_HOST,
STATE_OFF, CONF_NAME)
STATE_OFF, CONF_NAME, EVENT_HOMEASSISTANT_STOP)
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util


REQUIREMENTS = ['pyatv==0.1.4']
REQUIREMENTS = ['pyatv==0.2.1']

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -73,7 +72,14 @@ def async_setup_platform(hass, config, async_add_entities,
atv = pyatv.connect_to_apple_tv(details, hass.loop, session=session)
entity = AppleTvDevice(atv, name, start_off)

yield from async_add_entities([entity], update_before_add=True)
@callback
def on_hass_stop(event):
"""Stop push updates when hass stops."""
atv.push_updater.stop()

hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, on_hass_stop)

yield from async_add_entities([entity])


class AppleTvDevice(MediaPlayerDevice):
Expand All @@ -86,18 +92,34 @@ def __init__(self, atv, name, is_off):
self._is_off = is_off
self._playing = None
self._artwork_hash = None
self._atv.push_updater.listener = self

@asyncio.coroutine
def async_added_to_hass(self):
"""Called when entity is about to be added to HASS."""
self._atv.push_updater.start()

@callback
def _set_power_off(self, is_off):
self._playing = None
self._artwork_hash = None
self._is_off = is_off
if is_off:
self._atv.push_updater.stop()
else:
self._atv.push_updater.start()
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.

Are these methods async friendly ? (_set_power_off is marked as an async callback)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, they are from what I have gathered (with same reasoning as earlier regarding canceling a future - start effectively adds a future to the event loop).

self.hass.async_add_job(self.async_update_ha_state())

@property
def name(self):
"""Return the name of the device."""
return self._name

@property
def should_poll(self):
"""No polling needed."""
return False

@property
def state(self):
"""Return the state of the device."""
Expand All @@ -120,29 +142,19 @@ def state(self):
else:
return STATE_STANDBY # Bad or unknown state?

@asyncio.coroutine
def async_update(self):
"""Retrieve latest state."""
if self._is_off:
return

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')
@callback
def playstatus_update(self, updater, playing):
"""Print what is currently playing when it changes."""
if self.state == STATE_IDLE:
self._artwork_hash = None
elif 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
self.hass.async_add_job(self.async_update_ha_state())

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

@callback
def playstatus_error(self, updater, exception):
"""Inform about an error and restart push updates."""
_LOGGER.warning('A %s error occurred: %s',
exception.__class__, exception)

# This will wait 10 seconds before restarting push updates. If the
# connection continues to fail, it will flood the log (every 10
# seconds) until it succeeds. A better approach should probably be
# implemented here later.
updater.start(initial_delay=10)
self._playing = None
self._artwork_hash = None
self.hass.async_add_job(self.async_update_ha_state())

@property
def media_content_type(self):
"""Content type of current playing media."""
Expand Down Expand Up @@ -191,7 +218,8 @@ def async_play_media(self, media_type, media_id, **kwargs):
@property
def media_image_hash(self):
"""Hash value for media image."""
return self._artwork_hash
if self.state != STATE_IDLE:
return self._artwork_hash

@asyncio.coroutine
def async_get_media_image(self):
Expand All @@ -207,6 +235,8 @@ def media_title(self):
title = self._playing.title
return title if title else "No title"

return 'Not connected to Apple TV'

@property
def supported_features(self):
"""Flag media player features that are supported."""
Expand Down
2 changes: 1 addition & 1 deletion requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ pyasn1-modules==0.0.8
pyasn1==0.2.2

# homeassistant.components.media_player.apple_tv
pyatv==0.1.4
pyatv==0.2.1

# homeassistant.components.device_tracker.bbox
# homeassistant.components.sensor.bbox
Expand Down