diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index 42a641922f7..6acbb068953 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -804,8 +804,22 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity): @property def state(self) -> MediaPlayerState | None: """Return the state of the player.""" - # The lovelace app loops media to prevent timing out, don't show that + if (chromecast := self._chromecast) is None or ( + cast_status := self.cast_status + ) is None: + # Not connected to any chromecast, or not yet got any status + return None + + if ( + chromecast.cast_type == pychromecast.const.CAST_TYPE_CHROMECAST + and not chromecast.ignore_cec + and cast_status.is_active_input is False + ): + # The display interface for the device has been turned off or switched away + return MediaPlayerState.OFF + if self.app_id == CAST_APP_ID_HOMEASSISTANT_LOVELACE: + # The lovelace app loops media to prevent timing out, don't show that return MediaPlayerState.PLAYING if (media_status := self._media_status()[0]) is not None: @@ -822,16 +836,12 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity): # Some apps don't report media status, show the player as playing return MediaPlayerState.PLAYING - if self.app_id is not None and self.app_id != pychromecast.config.APP_BACKDROP: - # We have an active app - return MediaPlayerState.IDLE - - if self._chromecast is not None and self._chromecast.is_idle: - # If library consider us idle, that is our off state - # it takes HDMI status into account for cast devices. + if self.app_id in (pychromecast.IDLE_APP_ID, None): + # We have no active app or the home screen app. This is + # same app as APP_BACKDROP. return MediaPlayerState.OFF - return None + return MediaPlayerState.IDLE @property def media_content_id(self) -> str | None: diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index 5dfb99e3f2d..ff5b5e39ff5 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -68,7 +68,7 @@ def get_fake_chromecast(info: ChromecastInfo): mock = MagicMock(uuid=info.uuid) mock.app_id = None mock.media_controller.status = None - mock.is_idle = True + mock.ignore_cec = False return mock @@ -888,7 +888,6 @@ async def test_entity_cast_status( assert not state.attributes.get("is_volume_muted") chromecast.app_id = "1234" - chromecast.is_idle = False cast_status = MagicMock() cast_status.volume_level = 0.5 cast_status.volume_muted = False @@ -1601,7 +1600,6 @@ async def test_entity_media_states( # App id updated, but no media status chromecast.app_id = app_id - chromecast.is_idle = False cast_status = MagicMock() cast_status_cb(cast_status) await hass.async_block_till_done() @@ -1644,7 +1642,6 @@ async def test_entity_media_states( # App no longer running chromecast.app_id = pychromecast.IDLE_APP_ID - chromecast.is_idle = True cast_status = MagicMock() cast_status_cb(cast_status) await hass.async_block_till_done() @@ -1653,7 +1650,6 @@ async def test_entity_media_states( # No cast status chromecast.app_id = None - chromecast.is_idle = False cast_status_cb(None) await hass.async_block_till_done() state = hass.states.get(entity_id) @@ -1721,20 +1717,70 @@ async def test_entity_media_states_lovelace_app( chromecast.app_id = pychromecast.IDLE_APP_ID media_status.player_is_idle = False - chromecast.is_idle = True media_status_cb(media_status) await hass.async_block_till_done() state = hass.states.get(entity_id) assert state.state == "off" chromecast.app_id = None - chromecast.is_idle = False + cast_status_cb(None) media_status_cb(media_status) await hass.async_block_till_done() state = hass.states.get(entity_id) assert state.state == "unknown" +async def test_entity_media_states_active_input( + hass: HomeAssistant, entity_registry: er.EntityRegistry +) -> None: + """Test various entity media states when the lovelace app is active.""" + entity_id = "media_player.speaker" + + info = get_fake_chromecast_info() + + chromecast, _ = await async_setup_media_player_cast(hass, info) + chromecast.cast_type = pychromecast.const.CAST_TYPE_CHROMECAST + cast_status_cb, conn_status_cb, _ = get_status_callbacks(chromecast) + + chromecast.app_id = "84912283" + cast_status = MagicMock() + + connection_status = MagicMock() + connection_status.status = "CONNECTED" + conn_status_cb(connection_status) + await hass.async_block_till_done() + + # Unknown input status + cast_status.is_active_input = None + cast_status_cb(cast_status) + state = hass.states.get(entity_id) + assert state is not None + assert state.state == "idle" + + # Active input status + cast_status.is_active_input = True + cast_status_cb(cast_status) + await hass.async_block_till_done() + state = hass.states.get(entity_id) + assert state.state == "idle" + + # Inactive input status + cast_status.is_active_input = False + cast_status_cb(cast_status) + await hass.async_block_till_done() + state = hass.states.get(entity_id) + assert state is not None + assert state.state == "off" + + # Inactive input status, but ignored + chromecast.ignore_cec = True + cast_status_cb(cast_status) + await hass.async_block_till_done() + state = hass.states.get(entity_id) + assert state is not None + assert state.state == "idle" + + async def test_group_media_states( hass: HomeAssistant, entity_registry: er.EntityRegistry, mz_mock ) -> None: @@ -2404,7 +2450,6 @@ async def test_entity_media_states_active_app_reported_idle( # Scenario: Custom App is running (e.g. DashCast), but device reports is_idle=True chromecast.app_id = "84912283" # Example Custom App ID - chromecast.is_idle = True # Device thinks it's idle/standby # Trigger a status update cast_status = MagicMock() @@ -2417,7 +2462,6 @@ async def test_entity_media_states_active_app_reported_idle( # Scenario: Backdrop (Screensaver) is running. Should still be OFF. chromecast.app_id = pychromecast.config.APP_BACKDROP - chromecast.is_idle = True cast_status_cb(cast_status) await hass.async_block_till_done()