From fd4d68c0851d25bb38e7b8ceb39a53a29b0eab75 Mon Sep 17 00:00:00 2001 From: dersger Date: Sun, 8 Apr 2018 22:18:58 +0200 Subject: [PATCH 1/5] Avoid unnecessary cast state updates --- homeassistant/components/media_player/cast.py | 46 ++++++++++++++----- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/media_player/cast.py b/homeassistant/components/media_player/cast.py index 91b8d362c4307..1545f68bd47eb 100644 --- a/homeassistant/components/media_player/cast.py +++ b/homeassistant/components/media_player/cast.py @@ -288,7 +288,8 @@ def __init__(self, cast_info): self._chromecast = None # type: Optional[pychromecast.Chromecast] self.cast_status = None self.media_status = None - self.media_status_received = None + self.media_status_position = None + self.media_status_position_received = None self._available = False # type: bool self._status_listener = None # type: Optional[CastStatusListener] @@ -361,7 +362,8 @@ def _async_disconnect(self): self._chromecast = None self.cast_status = None self.media_status = None - self.media_status_received = None + self.media_status_position = None + self.media_status_position_received = None self._status_listener.invalidate() self._status_listener = None @@ -388,8 +390,36 @@ def new_cast_status(self, cast_status): def new_media_status(self, media_status): """Handle updates of the media status.""" + # Only use media position for playing/paused, + # and for normal playback rate + if ((media_status is None) or (media_status.current_time is None) or + (abs((media_status.playback_rate or 1) - 1) > 0.01) or + not (media_status.player_is_playing or + media_status.player_is_paused)): + self.media_status_position = None + self.media_status_position_received = None + else: + # Avoid unnecessary state attribute updates if player_state and + # position stay the same + now = dt_util.utcnow() + do_update = \ + ((self.media_status is None) or + (self.media_status_position is None) or + (self.media_status.player_state != media_status.player_state)) + if not do_update: + if media_status.player_is_playing: + elapsed = now - self.media_status_position_received + do_update = abs(media_status.current_time - + (self.media_status_position + + elapsed.total_seconds())) > 1 + else: + do_update = \ + self.media_status_position != media_status.current_time + if do_update: + self.media_status_position = media_status.current_time + self.media_status_position_received = now + self.media_status = media_status - self.media_status_received = dt_util.utcnow() self.schedule_update_ha_state() def new_connection_status(self, connection_status): @@ -595,13 +625,7 @@ def supported_features(self): @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 + return self.media_status_position @property def media_position_updated_at(self): @@ -609,7 +633,7 @@ def media_position_updated_at(self): Returns value from homeassistant.util.dt.utcnow(). """ - return self.media_status_received + return self.media_status_position_received @property def unique_id(self) -> Optional[str]: From 923de85a81abfc0278d45d96e37f5192a6a35328 Mon Sep 17 00:00:00 2001 From: dersger Date: Tue, 10 Apr 2018 20:19:25 +0200 Subject: [PATCH 2/5] Add test --- homeassistant/components/media_player/cast.py | 16 ++-- tests/components/media_player/test_cast.py | 83 ++++++++++++++++++- 2 files changed, 90 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/media_player/cast.py b/homeassistant/components/media_player/cast.py index 1545f68bd47eb..c0f1060ee2784 100644 --- a/homeassistant/components/media_player/cast.py +++ b/homeassistant/components/media_player/cast.py @@ -392,20 +392,20 @@ def new_media_status(self, media_status): """Handle updates of the media status.""" # Only use media position for playing/paused, # and for normal playback rate - if ((media_status is None) or (media_status.current_time is None) or - (abs((media_status.playback_rate or 1) - 1) > 0.01) or - not (media_status.player_is_playing or - media_status.player_is_paused)): + if (media_status is None or + abs(media_status.playback_rate - 1) > 0.01 or + not (media_status.player_is_playing or + media_status.player_is_paused)): self.media_status_position = None self.media_status_position_received = None else: # Avoid unnecessary state attribute updates if player_state and - # position stay the same + # calculated position stay the same now = dt_util.utcnow() do_update = \ - ((self.media_status is None) or - (self.media_status_position is None) or - (self.media_status.player_state != media_status.player_state)) + (self.media_status is None or + self.media_status_position is None or + self.media_status.player_state != media_status.player_state) if not do_update: if media_status.player_is_playing: elapsed = now - self.media_status_position_received diff --git a/tests/components/media_player/test_cast.py b/tests/components/media_player/test_cast.py index ee69ec1c85d37..555bfd0714c81 100644 --- a/tests/components/media_player/test_cast.py +++ b/tests/components/media_player/test_cast.py @@ -1,6 +1,7 @@ -"""The tests for the Cast Media player platform.""" +z"""The tests for the Cast Media player platform.""" # pylint: disable=protected-access import asyncio +import datetime as dt from typing import Optional from unittest.mock import patch, MagicMock, Mock from uuid import UUID @@ -286,6 +287,8 @@ async def test_entity_media_states(hass: HomeAssistantType): assert entity.unique_id == full_info.uuid media_status = MagicMock(images=None) + media_status.current_time = 0 + media_status.playback_rate = 1 media_status.player_is_playing = True entity.new_media_status(media_status) await hass.async_block_till_done() @@ -320,6 +323,84 @@ async def test_entity_media_states(hass: HomeAssistantType): assert state.state == 'unknown' +async def test_entity_media_position(hass: HomeAssistantType): + """Test various entity media states.""" + info = get_fake_chromecast_info() + full_info = attr.evolve(info, model_name='google home', + friendly_name='Speaker', uuid=FakeUUID) + + with patch('pychromecast.dial.get_device_status', + return_value=full_info): + chromecast, entity = await async_setup_media_player_cast(hass, info) + + media_status = MagicMock(images=None) + media_status.current_time = 10 + media_status.playback_rate = 1 + media_status.player_is_playing = True + media_status.player_is_paused = False + media_status.player_is_idle = False + now = dt.datetime.now(dt.timezone.utc) + with patch('homeassistant.util.dt.utcnow', return_value=now): + entity.new_media_status(media_status) + await hass.async_block_till_done() + state = hass.states.get('media_player.speaker') + assert state.attributes[ATTR_MEDIA_POSITION] == 10 + assert state.attributes[ATTR_MEDIA_POSITION_UPDATED_AT] == now + + media_status.current_time = 15 + now_plus_5 = now + dt.timedelta(seconds=5) + with patch('homeassistant.util.dt.utcnow', return_value=now_plus_5): + entity.new_media_status(media_status) + await hass.async_block_till_done() + state = hass.states.get('media_player.speaker') + assert state.attributes[ATTR_MEDIA_POSITION] == 10 + assert state.attributes[ATTR_MEDIA_POSITION_UPDATED_AT] == now + + media_status.current_time = 20 + with patch('homeassistant.util.dt.utcnow', return_value=now_plus_5): + entity.new_media_status(media_status) + await hass.async_block_till_done() + state = hass.states.get('media_player.speaker') + assert state.attributes[ATTR_MEDIA_POSITION] == 20 + assert state.attributes[ATTR_MEDIA_POSITION_UPDATED_AT] == now_plus_5 + + media_status.current_time = 25 + now_plus_10 = now + dt.timedelta(seconds=10) + media_status.player_is_playing = False + media_status.player_is_paused = True + with patch('homeassistant.util.dt.utcnow', return_value=now_plus_10): + entity.new_media_status(media_status) + await hass.async_block_till_done() + state = hass.states.get('media_player.speaker') + assert state.attributes[ATTR_MEDIA_POSITION] == 25 + assert state.attributes[ATTR_MEDIA_POSITION_UPDATED_AT] == now_plus_10 + + now_plus_15 = now + dt.timedelta(seconds=15) + with patch('homeassistant.util.dt.utcnow', return_value=now_plus_15): + entity.new_media_status(media_status) + await hass.async_block_till_done() + state = hass.states.get('media_player.speaker') + assert state.attributes[ATTR_MEDIA_POSITION] == 25 + assert state.attributes[ATTR_MEDIA_POSITION_UPDATED_AT] == now_plus_10 + + media_status.current_time = 30 + now_plus_20 = now + dt.timedelta(seconds=20) + with patch('homeassistant.util.dt.utcnow', return_value=now_plus_20): + entity.new_media_status(media_status) + await hass.async_block_till_done() + state = hass.states.get('media_player.speaker') + assert state.attributes[ATTR_MEDIA_POSITION] == 30 + assert state.attributes[ATTR_MEDIA_POSITION_UPDATED_AT] == now_plus_20 + + media_status.player_is_paused = False + media_status.player_is_idle = True + with patch('homeassistant.util.dt.utcnow', return_value=now_plus_20): + entity.new_media_status(media_status) + await hass.async_block_till_done() + assert ATTR_MEDIA_POSITION not in state.attributes + assert ATTR_MEDIA_POSITION_UPDATED_AT not in state.attributes + + async def test_switched_host(hass: HomeAssistantType): """Test cast device listens for changed hosts and disconnects old cast.""" info = get_fake_chromecast_info() From d3f3d2d0490bca4b77fe3e275d545364b294e59d Mon Sep 17 00:00:00 2001 From: dersger Date: Tue, 10 Apr 2018 20:34:19 +0200 Subject: [PATCH 3/5] Fixed bad syntax --- tests/components/media_player/test_cast.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/components/media_player/test_cast.py b/tests/components/media_player/test_cast.py index 555bfd0714c81..79136c39b1e25 100644 --- a/tests/components/media_player/test_cast.py +++ b/tests/components/media_player/test_cast.py @@ -1,4 +1,4 @@ -z"""The tests for the Cast Media player platform.""" +"""The tests for the Cast Media player platform.""" # pylint: disable=protected-access import asyncio import datetime as dt From 7537154be8274e4160e157f1a27feeee361e5cbf Mon Sep 17 00:00:00 2001 From: dersger Date: Tue, 10 Apr 2018 20:37:07 +0200 Subject: [PATCH 4/5] Fixed imports --- tests/components/media_player/test_cast.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/components/media_player/test_cast.py b/tests/components/media_player/test_cast.py index 79136c39b1e25..a88e9f98a348a 100644 --- a/tests/components/media_player/test_cast.py +++ b/tests/components/media_player/test_cast.py @@ -15,7 +15,8 @@ from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.helpers.dispatcher import async_dispatcher_connect, \ async_dispatcher_send -from homeassistant.components.media_player import cast +from homeassistant.components.media_player import cast, \ + ATTR_MEDIA_POSITION, ATTR_MEDIA_POSITION_UPDATED_AT from homeassistant.setup import async_setup_component From fec553cfcb1301ef460d88d4ea4caba5aacde465 Mon Sep 17 00:00:00 2001 From: dersger Date: Tue, 10 Apr 2018 21:09:12 +0200 Subject: [PATCH 5/5] Fixed test --- tests/components/media_player/test_cast.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/components/media_player/test_cast.py b/tests/components/media_player/test_cast.py index a88e9f98a348a..0c0f3906dc2ba 100644 --- a/tests/components/media_player/test_cast.py +++ b/tests/components/media_player/test_cast.py @@ -398,6 +398,7 @@ async def test_entity_media_position(hass: HomeAssistantType): with patch('homeassistant.util.dt.utcnow', return_value=now_plus_20): entity.new_media_status(media_status) await hass.async_block_till_done() + state = hass.states.get('media_player.speaker') assert ATTR_MEDIA_POSITION not in state.attributes assert ATTR_MEDIA_POSITION_UPDATED_AT not in state.attributes