diff --git a/homeassistant/components/cast/helpers.py b/homeassistant/components/cast/helpers.py index 0f0dc5f6358..bd8ce68c5f1 100644 --- a/homeassistant/components/cast/helpers.py +++ b/homeassistant/components/cast/helpers.py @@ -23,7 +23,6 @@ class ChromecastInfo: ) # always convert UUID to string if not None model_name = attr.ib(type=str, default="") friendly_name = attr.ib(type=Optional[str], default=None) - is_dynamic_group = attr.ib(type=Optional[bool], default=None) @property def is_audio_group(self) -> bool: @@ -33,19 +32,7 @@ class ChromecastInfo: @property def is_information_complete(self) -> bool: """Return if all information is filled out.""" - want_dynamic_group = self.is_audio_group - have_dynamic_group = self.is_dynamic_group is not None - have_all_except_dynamic_group = all( - attr.astuple( - self, - filter=attr.filters.exclude( - attr.fields(ChromecastInfo).is_dynamic_group - ), - ) - ) - return have_all_except_dynamic_group and ( - not want_dynamic_group or have_dynamic_group - ) + return all(attr.astuple(self)) @property def host_port(self) -> Tuple[str, int]: @@ -70,8 +57,6 @@ class ChromecastInfo: return self if self.is_audio_group: - is_dynamic_group = False - return ChromecastInfo( service=self.service, host=self.host, @@ -79,7 +64,6 @@ class ChromecastInfo: uuid=self.uuid, friendly_name=self.friendly_name, model_name=self.model_name, - is_dynamic_group=is_dynamic_group, ) # Fill out some missing information (friendly_name, uuid) via HTTP dial. @@ -99,14 +83,6 @@ class ChromecastInfo: model_name=self.model_name, ) - def same_dynamic_group(self, other: "ChromecastInfo") -> bool: - """Test chromecast info is same dynamic group.""" - return ( - self.is_audio_group - and other.is_dynamic_group - and self.friendly_name == other.friendly_name - ) - class ChromeCastZeroconf: """Class to hold a zeroconf instance.""" @@ -190,45 +166,3 @@ class CastStatusListener: else: self._mz_mgr.deregister_listener(self._uuid, self) self._valid = False - - -class DynamicGroupCastStatusListener: - """Helper class to handle pychromecast status callbacks. - - Necessary because a CastDevice entity can create a new socket client - and therefore callbacks from multiple chromecast connections can - potentially arrive. This class allows invalidating past chromecast objects. - """ - - def __init__(self, cast_device, chromecast, mz_mgr): - """Initialize the status listener.""" - self._cast_device = cast_device - self._uuid = chromecast.uuid - self._valid = True - self._mz_mgr = mz_mgr - - chromecast.register_status_listener(self) - chromecast.socket_client.media_controller.register_status_listener(self) - chromecast.register_connection_listener(self) - self._mz_mgr.add_multizone(chromecast) - - def new_cast_status(self, cast_status): - """Handle reception of a new CastStatus.""" - - def new_media_status(self, media_status): - """Handle reception of a new MediaStatus.""" - if self._valid: - self._cast_device.new_dynamic_group_media_status(media_status) - - def new_connection_status(self, connection_status): - """Handle reception of a new ConnectionStatus.""" - if self._valid: - self._cast_device.new_dynamic_group_connection_status(connection_status) - - def invalidate(self): - """Invalidate this status listener. - - All following callbacks won't be forwarded. - """ - self._mz_mgr.remove_multizone(self._uuid) - self._valid = False diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index e687dc715df..5fcbbaf1caa 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -56,12 +56,7 @@ from .const import ( SIGNAL_HASS_CAST_SHOW_VIEW, ) from .discovery import setup_internal_discovery -from .helpers import ( - CastStatusListener, - ChromecastInfo, - ChromeCastZeroconf, - DynamicGroupCastStatusListener, -) +from .helpers import CastStatusListener, ChromecastInfo, ChromeCastZeroconf _LOGGER = logging.getLogger(__name__) @@ -101,10 +96,6 @@ def _async_create_cast_device(hass: HomeAssistantType, info: ChromecastInfo): return CastDevice(info) # Found a cast with UUID - if info.is_dynamic_group: - # This is a dynamic group, do not add it. - return None - added_casts = hass.data[ADDED_CAST_DEVICES_KEY] if info.uuid in added_casts: # Already added this one, the entity will take care of moved hosts @@ -201,19 +192,11 @@ class CastDevice(MediaPlayerDevice): self.cast_status = None self.media_status = None self.media_status_received = None - self._dynamic_group_cast_info: ChromecastInfo = None - self._dynamic_group_cast: Optional[pychromecast.Chromecast] = None - self.dynamic_group_media_status = None - self.dynamic_group_media_status_received = None self.mz_media_status = {} self.mz_media_status_received = {} self.mz_mgr = None self._available = False - self._dynamic_group_available = False self._status_listener: Optional[CastStatusListener] = None - self._dynamic_group_status_listener: Optional[ - DynamicGroupCastStatusListener - ] = None self._hass_cast_controller: Optional[HomeAssistantController] = None self._add_remove_handler = None @@ -232,20 +215,6 @@ class CastDevice(MediaPlayerDevice): self.hass.async_create_task( async_create_catching_coro(self.async_set_cast_info(self._cast_info)) ) - for info in self.hass.data[KNOWN_CHROMECAST_INFO_KEY]: - if self._cast_info.same_dynamic_group(info): - _LOGGER.debug( - "[%s %s (%s:%s)] Found dynamic group: %s", - self.entity_id, - self._cast_info.friendly_name, - self._cast_info.host, - self._cast_info.port, - info, - ) - self.hass.async_create_task( - async_create_catching_coro(self.async_set_dynamic_group(info)) - ) - break self._cast_view_remove_handler = async_dispatcher_connect( self.hass, SIGNAL_HASS_CAST_SHOW_VIEW, self._handle_signal_show_view @@ -358,69 +327,6 @@ class CastDevice(MediaPlayerDevice): self.services, ) - async def async_set_dynamic_group(self, cast_info): - """Set the cast information and set up the chromecast object.""" - - _LOGGER.debug( - "[%s %s (%s:%s)] Connecting to dynamic group by host %s", - self.entity_id, - self._cast_info.friendly_name, - self._cast_info.host, - self._cast_info.port, - cast_info, - ) - - await self.async_del_dynamic_group() - self._dynamic_group_cast_info = cast_info - - # pylint: disable=protected-access - chromecast = await self.hass.async_add_executor_job( - pychromecast._get_chromecast_from_host, - ( - cast_info.host, - cast_info.port, - cast_info.uuid, - cast_info.model_name, - cast_info.friendly_name, - ), - ) - - self._dynamic_group_cast = chromecast - - if CAST_MULTIZONE_MANAGER_KEY not in self.hass.data: - self.hass.data[CAST_MULTIZONE_MANAGER_KEY] = MultizoneManager() - - mz_mgr = self.hass.data[CAST_MULTIZONE_MANAGER_KEY] - - self._dynamic_group_status_listener = DynamicGroupCastStatusListener( - self, chromecast, mz_mgr - ) - self._dynamic_group_available = False - self.dynamic_group_media_status = chromecast.media_controller.status - self._dynamic_group_cast.start() - self.async_write_ha_state() - - async def async_del_dynamic_group(self): - """Remove the dynamic group.""" - cast_info = self._dynamic_group_cast_info - _LOGGER.debug( - "[%s %s (%s:%s)] Remove dynamic group: %s", - self.entity_id, - self._cast_info.friendly_name, - self._cast_info.host, - self._cast_info.port, - cast_info.service if cast_info else None, - ) - - self._dynamic_group_available = False - self._dynamic_group_cast_info = None - if self._dynamic_group_cast is not None: - await self.hass.async_add_executor_job(self._dynamic_group_cast.disconnect) - - self._dynamic_group_invalidate() - - self.async_write_ha_state() - async def _async_disconnect(self): """Disconnect Chromecast object if it is set.""" if self._chromecast is None: @@ -437,8 +343,6 @@ class CastDevice(MediaPlayerDevice): self.async_write_ha_state() await self.hass.async_add_executor_job(self._chromecast.disconnect) - if self._dynamic_group_cast is not None: - await self.hass.async_add_executor_job(self._dynamic_group_cast.disconnect) self._invalidate() @@ -458,15 +362,6 @@ class CastDevice(MediaPlayerDevice): self._status_listener.invalidate() self._status_listener = None - def _dynamic_group_invalidate(self): - """Invalidate some attributes.""" - self._dynamic_group_cast = None - self.dynamic_group_media_status = None - self.dynamic_group_media_status_received = None - if self._dynamic_group_status_listener is not None: - self._dynamic_group_status_listener.invalidate() - self._dynamic_group_status_listener = None - # ========== Callbacks ========== def new_cast_status(self, cast_status): """Handle updates of the cast status.""" @@ -515,44 +410,6 @@ class CastDevice(MediaPlayerDevice): self._available = new_available self.schedule_update_ha_state() - def new_dynamic_group_media_status(self, media_status): - """Handle updates of the media status.""" - self.dynamic_group_media_status = media_status - self.dynamic_group_media_status_received = dt_util.utcnow() - self.schedule_update_ha_state() - - def new_dynamic_group_connection_status(self, connection_status): - """Handle updates of connection status.""" - _LOGGER.debug( - "[%s %s (%s:%s)] Received dynamic group connection status: %s", - self.entity_id, - self._cast_info.friendly_name, - self._cast_info.host, - self._cast_info.port, - connection_status.status, - ) - if connection_status.status == CONNECTION_STATUS_DISCONNECTED: - self._dynamic_group_available = False - self._dynamic_group_invalidate() - self.schedule_update_ha_state() - return - - new_available = connection_status.status == CONNECTION_STATUS_CONNECTED - if new_available != self._dynamic_group_available: - # Connection status callbacks happen often when disconnected. - # Only update state when availability changed to put less pressure - # on state machine. - _LOGGER.debug( - "[%s %s (%s:%s)] Dynamic group availability changed: %s", - self.entity_id, - self._cast_info.friendly_name, - self._cast_info.host, - self._cast_info.port, - connection_status.status, - ) - self._dynamic_group_available = new_available - self.schedule_update_ha_state() - def multizone_new_media_status(self, group_uuid, media_status): """Handle updates of audio group media status.""" _LOGGER.debug( @@ -573,18 +430,11 @@ class CastDevice(MediaPlayerDevice): """ Return media status. - First try from our own cast, then dynamic groups and finally - groups which our cast is a member in. + First try from our own cast, then groups which our cast is a member in. """ media_status = self.media_status media_controller = self._chromecast.media_controller - if ( - media_status is None or media_status.player_state == "UNKNOWN" - ) and self._dynamic_group_cast is not None: - media_status = self.dynamic_group_media_status - media_controller = self._dynamic_group_cast.media_controller - if media_status is None or media_status.player_state == "UNKNOWN": groups = self.mz_media_status for k, val in groups.items(): @@ -652,7 +502,7 @@ class CastDevice(MediaPlayerDevice): def play_media(self, media_type, media_id, **kwargs): """Play media from a URL.""" - # We do not want this to be forwarded to a group / dynamic group + # We do not want this to be forwarded to a group self._chromecast.media_controller.play_media(media_id, media_type) # ========== Properties ========== @@ -685,18 +535,11 @@ class CastDevice(MediaPlayerDevice): """ Return media status. - First try from our own cast, then dynamic groups and finally - groups which our cast is a member in. + First try from our own cast, then groups which our cast is a member in. """ media_status = self.media_status media_status_received = self.media_status_received - if ( - media_status is None or media_status.player_state == "UNKNOWN" - ) and self._dynamic_group_cast is not None: - media_status = self.dynamic_group_media_status - media_status_received = self.dynamic_group_media_status_received - if media_status is None or media_status.player_state == "UNKNOWN": groups = self.mz_media_status for k, val in groups.items(): @@ -887,11 +730,6 @@ class CastDevice(MediaPlayerDevice): # We can't handle empty UUIDs return - if self._cast_info.same_dynamic_group(discover): - _LOGGER.debug("Discovered matching dynamic group: %s", discover) - await self.async_set_dynamic_group(discover) - return - if self._cast_info.uuid != discover.uuid: # Discovered is not our device. return @@ -915,14 +753,6 @@ class CastDevice(MediaPlayerDevice): # We can't handle empty UUIDs return - if ( - self._dynamic_group_cast_info is not None - and self._dynamic_group_cast_info.uuid == discover.uuid - ): - _LOGGER.debug("Removed matching dynamic group: %s", discover) - await self.async_del_dynamic_group() - return - if self._cast_info.uuid != discover.uuid: # Removed is not our device. return diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index 9809710f372..b05be45e79f 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -435,58 +435,6 @@ async def test_group_media_states(hass: HomeAssistantType): assert state.state == "playing" -async def test_dynamic_group_media_states(hass: HomeAssistantType): - """Test media states are read from group if entity has no state.""" - info = get_fake_chromecast_info() - full_info = attr.evolve( - info, model_name="google home", friendly_name="Speaker", uuid=FakeUUID - ) - - with patch( - "homeassistant.components.cast.helpers.dial.get_device_status", - return_value=full_info, - ): - chromecast, entity = await async_setup_media_player_cast(hass, info) - - entity._available = True - entity.schedule_update_ha_state() - await hass.async_block_till_done() - - state = hass.states.get("media_player.speaker") - assert state is not None - assert state.name == "Speaker" - assert state.state == "unknown" - assert entity.unique_id == full_info.uuid - - group_media_status = MagicMock(images=None) - player_media_status = MagicMock(images=None) - - # Player has no state, dynamic group is playing -> Should report 'playing' - entity._dynamic_group_cast = MagicMock() - group_media_status.player_is_playing = True - entity.new_dynamic_group_media_status(group_media_status) - await hass.async_block_till_done() - state = hass.states.get("media_player.speaker") - assert state.state == "playing" - - # Player is paused, dynamic group is playing -> Should report 'paused' - player_media_status.player_is_playing = False - player_media_status.player_is_paused = True - entity.new_media_status(player_media_status) - await hass.async_block_till_done() - await hass.async_block_till_done() - state = hass.states.get("media_player.speaker") - assert state.state == "paused" - - # Player is in unknown state, dynamic group is playing -> Should report - # 'playing' - player_media_status.player_state = "UNKNOWN" - entity.new_media_status(player_media_status) - await hass.async_block_till_done() - state = hass.states.get("media_player.speaker") - assert state.state == "playing" - - async def test_group_media_control(hass: HomeAssistantType): """Test media states are read from group if entity has no state.""" info = get_fake_chromecast_info() @@ -543,61 +491,6 @@ async def test_group_media_control(hass: HomeAssistantType): assert chromecast.media_controller.play_media.called -async def test_dynamic_group_media_control(hass: HomeAssistantType): - """Test media states are read from group if entity has no state.""" - info = get_fake_chromecast_info() - full_info = attr.evolve( - info, model_name="google home", friendly_name="Speaker", uuid=FakeUUID - ) - - with patch( - "homeassistant.components.cast.helpers.dial.get_device_status", - return_value=full_info, - ): - chromecast, entity = await async_setup_media_player_cast(hass, info) - - entity._available = True - entity.schedule_update_ha_state() - entity._dynamic_group_cast = MagicMock() - await hass.async_block_till_done() - - state = hass.states.get("media_player.speaker") - assert state is not None - assert state.name == "Speaker" - assert state.state == "unknown" - assert entity.unique_id == full_info.uuid - - group_media_status = MagicMock(images=None) - player_media_status = MagicMock(images=None) - - # Player has no state, dynamic group is playing -> Should forward - group_media_status.player_is_playing = True - entity.new_dynamic_group_media_status(group_media_status) - entity.media_previous_track() - assert entity._dynamic_group_cast.media_controller.queue_prev.called - assert not chromecast.media_controller.queue_prev.called - - # Player is paused, dynamic group is playing -> Should not forward - player_media_status.player_is_playing = False - player_media_status.player_is_paused = True - entity.new_media_status(player_media_status) - entity.media_next_track() - assert not entity._dynamic_group_cast.media_controller.queue_next.called - assert chromecast.media_controller.queue_next.called - - # Player is in unknown state, dynamic group is playing -> Should forward - player_media_status.player_state = "UNKNOWN" - entity.new_media_status(player_media_status) - entity.media_seek(None) - assert entity._dynamic_group_cast.media_controller.seek.called - assert not chromecast.media_controller.seek.called - - # Verify play_media is not forwarded - entity.play_media(None, None) - assert not entity._dynamic_group_cast.media_controller.play_media.called - assert chromecast.media_controller.play_media.called - - async def test_disconnect_on_stop(hass: HomeAssistantType): """Test cast device disconnects socket on stop.""" info = get_fake_chromecast_info()