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
64 changes: 12 additions & 52 deletions homeassistant/components/media_player/cast.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,8 +288,7 @@ def __init__(self, cast_info):
self._chromecast = None # type: Optional[pychromecast.Chromecast]
self.cast_status = None
self.media_status = None
self.media_status_position = None
self.media_status_position_received = None
self.media_status_received = None
self._available = False # type: bool
self._status_listener = None # type: Optional[CastStatusListener]

Expand Down Expand Up @@ -362,26 +361,10 @@ def _async_disconnect(self):
self._chromecast = None
self.cast_status = None
self.media_status = None
self.media_status_position = None
self.media_status_position_received = None
self.media_status_received = None
self._status_listener.invalidate()
self._status_listener = None

def update(self):
"""Periodically update the properties.

Even though we receive callbacks for most state changes, some 3rd party
apps don't always send them. Better poll every now and then if the
chromecast is active (i.e. an app is running).
"""
if not self._available:
# Not connected or not available.
return

if self._chromecast.media_controller.is_active:
# We can only update status if the media namespace is active
self._chromecast.media_controller.update_status()

# ========== Callbacks ==========
def new_cast_status(self, cast_status):
"""Handle updates of the cast status."""
Expand All @@ -390,36 +373,8 @@ 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
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):
Expand Down Expand Up @@ -496,8 +451,8 @@ def play_media(self, media_type, media_id, **kwargs):
# ========== Properties ==========
@property
def should_poll(self):
"""Polling needed for cast integration, see async_update."""
return True
"""No polling needed."""
return False

@property
def name(self):
Expand Down Expand Up @@ -625,15 +580,20 @@ def supported_features(self):
@property
def media_position(self):
"""Position of current playing media in seconds."""
return self.media_status_position
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

@property
def media_position_updated_at(self):
"""When was the position of the current playing media valid.

Returns value from homeassistant.util.dt.utcnow().
"""
return self.media_status_position_received
return self.media_status_received

@property
def unique_id(self) -> Optional[str]:
Expand Down
85 changes: 1 addition & 84 deletions tests/components/media_player/test_cast.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""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
Expand All @@ -15,8 +14,7 @@
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, \
ATTR_MEDIA_POSITION, ATTR_MEDIA_POSITION_UPDATED_AT
from homeassistant.components.media_player import cast
from homeassistant.setup import async_setup_component


Expand Down Expand Up @@ -288,8 +286,6 @@ 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()
Expand Down Expand Up @@ -324,85 +320,6 @@ 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()
Expand Down