From bae6333c26d866f60ee893637272fa0a5e8b1909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20St=C3=A5hl?= Date: Thu, 2 Mar 2017 09:12:55 +0100 Subject: [PATCH] Use push updates in Apple TV (#6323) * Use push updates in Apple TV * Fix review comments --- .../components/media_player/apple_tv.py | 84 +++++++++++++------ requirements_all.txt | 2 +- 2 files changed, 58 insertions(+), 28 deletions(-) diff --git a/homeassistant/components/media_player/apple_tv.py b/homeassistant/components/media_player/apple_tv.py index 566ad7d6933..ad0adfb008a 100644 --- a/homeassistant/components/media_player/apple_tv.py +++ b/homeassistant/components/media_player/apple_tv.py @@ -8,7 +8,6 @@ import asyncio import logging import hashlib -import aiohttp import voluptuous as vol from homeassistant.core import callback @@ -19,13 +18,13 @@ from homeassistant.components.media_player import ( 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__) @@ -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): @@ -86,18 +92,34 @@ class AppleTvDevice(MediaPlayerDevice): 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() + 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.""" @@ -120,29 +142,19 @@ class AppleTvDevice(MediaPlayerDevice): else: return STATE_STANDBY # Bad or unknown state? - @asyncio.coroutine - def async_update(self): - """Retrieve latest state.""" - if self._is_off: - return + @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() - 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') + 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: @@ -151,6 +163,21 @@ class AppleTvDevice(MediaPlayerDevice): 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.""" @@ -191,7 +218,8 @@ class AppleTvDevice(MediaPlayerDevice): @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): @@ -207,6 +235,8 @@ class AppleTvDevice(MediaPlayerDevice): 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.""" diff --git a/requirements_all.txt b/requirements_all.txt index 4cc886d90cb..e1591ceb40c 100755 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -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