Avoid unnecessary cast state updates (#13770)

* Avoid unnecessary cast state updates

* Add test

* Fixed bad syntax

* Fixed imports

* Fixed test
This commit is contained in:
dersger 2018-04-14 04:22:02 +02:00 committed by Paulus Schoutsen
parent 99ded8a0a6
commit 80a3220b88
2 changed files with 119 additions and 12 deletions

View File

@ -288,7 +288,8 @@ class CastDevice(MediaPlayerDevice):
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 @@ class CastDevice(MediaPlayerDevice):
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 @@ class CastDevice(MediaPlayerDevice):
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
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
# 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)
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 @@ class CastDevice(MediaPlayerDevice):
@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 @@ class CastDevice(MediaPlayerDevice):
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]:

View File

@ -1,6 +1,7 @@
"""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
@ -14,7 +15,8 @@ from homeassistant.components.media_player.cast import ChromecastInfo
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
@ -286,6 +288,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 +324,85 @@ 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()
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
async def test_switched_host(hass: HomeAssistantType):
"""Test cast device listens for changed hosts and disconnects old cast."""
info = get_fake_chromecast_info()