mirror of
https://github.com/home-assistant/core.git
synced 2025-09-10 23:31:37 +02:00
Media player API enumeration alignment and feature flags (#149597)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
@@ -10,6 +10,7 @@ from urllib.parse import urlparse
|
||||
from aioesphomeapi import (
|
||||
EntityInfo,
|
||||
MediaPlayerCommand,
|
||||
MediaPlayerEntityFeature as EspMediaPlayerEntityFeature,
|
||||
MediaPlayerEntityState,
|
||||
MediaPlayerFormatPurpose,
|
||||
MediaPlayerInfo,
|
||||
@@ -53,6 +54,31 @@ _STATES: EsphomeEnumMapper[EspMediaPlayerState, MediaPlayerState] = EsphomeEnumM
|
||||
}
|
||||
)
|
||||
|
||||
_FEATURES = {
|
||||
EspMediaPlayerEntityFeature.PAUSE: MediaPlayerEntityFeature.PAUSE,
|
||||
EspMediaPlayerEntityFeature.SEEK: MediaPlayerEntityFeature.SEEK,
|
||||
EspMediaPlayerEntityFeature.VOLUME_SET: MediaPlayerEntityFeature.VOLUME_SET,
|
||||
EspMediaPlayerEntityFeature.VOLUME_MUTE: MediaPlayerEntityFeature.VOLUME_MUTE,
|
||||
EspMediaPlayerEntityFeature.PREVIOUS_TRACK: MediaPlayerEntityFeature.PREVIOUS_TRACK,
|
||||
EspMediaPlayerEntityFeature.NEXT_TRACK: MediaPlayerEntityFeature.NEXT_TRACK,
|
||||
EspMediaPlayerEntityFeature.TURN_ON: MediaPlayerEntityFeature.TURN_ON,
|
||||
EspMediaPlayerEntityFeature.TURN_OFF: MediaPlayerEntityFeature.TURN_OFF,
|
||||
EspMediaPlayerEntityFeature.PLAY_MEDIA: MediaPlayerEntityFeature.PLAY_MEDIA,
|
||||
EspMediaPlayerEntityFeature.VOLUME_STEP: MediaPlayerEntityFeature.VOLUME_STEP,
|
||||
EspMediaPlayerEntityFeature.SELECT_SOURCE: MediaPlayerEntityFeature.SELECT_SOURCE,
|
||||
EspMediaPlayerEntityFeature.STOP: MediaPlayerEntityFeature.STOP,
|
||||
EspMediaPlayerEntityFeature.CLEAR_PLAYLIST: MediaPlayerEntityFeature.CLEAR_PLAYLIST,
|
||||
EspMediaPlayerEntityFeature.PLAY: MediaPlayerEntityFeature.PLAY,
|
||||
EspMediaPlayerEntityFeature.SHUFFLE_SET: MediaPlayerEntityFeature.SHUFFLE_SET,
|
||||
EspMediaPlayerEntityFeature.SELECT_SOUND_MODE: MediaPlayerEntityFeature.SELECT_SOUND_MODE,
|
||||
EspMediaPlayerEntityFeature.BROWSE_MEDIA: MediaPlayerEntityFeature.BROWSE_MEDIA,
|
||||
EspMediaPlayerEntityFeature.REPEAT_SET: MediaPlayerEntityFeature.REPEAT_SET,
|
||||
EspMediaPlayerEntityFeature.GROUPING: MediaPlayerEntityFeature.GROUPING,
|
||||
EspMediaPlayerEntityFeature.MEDIA_ANNOUNCE: MediaPlayerEntityFeature.MEDIA_ANNOUNCE,
|
||||
EspMediaPlayerEntityFeature.MEDIA_ENQUEUE: MediaPlayerEntityFeature.MEDIA_ENQUEUE,
|
||||
EspMediaPlayerEntityFeature.SEARCH_MEDIA: MediaPlayerEntityFeature.SEARCH_MEDIA,
|
||||
}
|
||||
|
||||
ATTR_BYPASS_PROXY = "bypass_proxy"
|
||||
|
||||
|
||||
@@ -67,16 +93,12 @@ class EsphomeMediaPlayer(
|
||||
def _on_static_info_update(self, static_info: EntityInfo) -> None:
|
||||
"""Set attrs from static info."""
|
||||
super()._on_static_info_update(static_info)
|
||||
flags = (
|
||||
MediaPlayerEntityFeature.PLAY_MEDIA
|
||||
| MediaPlayerEntityFeature.BROWSE_MEDIA
|
||||
| MediaPlayerEntityFeature.STOP
|
||||
| MediaPlayerEntityFeature.VOLUME_SET
|
||||
| MediaPlayerEntityFeature.VOLUME_MUTE
|
||||
| MediaPlayerEntityFeature.MEDIA_ANNOUNCE
|
||||
esp_flags = EspMediaPlayerEntityFeature(
|
||||
self._static_info.feature_flags_compat(self._api_version)
|
||||
)
|
||||
if self._static_info.supports_pause:
|
||||
flags |= MediaPlayerEntityFeature.PAUSE | MediaPlayerEntityFeature.PLAY
|
||||
flags = MediaPlayerEntityFeature(0)
|
||||
for espflag in esp_flags:
|
||||
flags |= _FEATURES[espflag]
|
||||
self._attr_supported_features = flags
|
||||
self._entry_data.media_player_formats[self.unique_id] = cast(
|
||||
MediaPlayerInfo, static_info
|
||||
|
@@ -29,6 +29,7 @@ from homeassistant.components.media_player import (
|
||||
SERVICE_PLAY_MEDIA,
|
||||
SERVICE_VOLUME_MUTE,
|
||||
SERVICE_VOLUME_SET,
|
||||
STATE_PLAYING,
|
||||
BrowseMedia,
|
||||
MediaClass,
|
||||
MediaType,
|
||||
@@ -56,6 +57,8 @@ async def test_media_player_entity(
|
||||
key=1,
|
||||
name="my media_player",
|
||||
supports_pause=True,
|
||||
# PLAY_MEDIA,BROWSE_MEDIA,STOP,VOLUME_SET,VOLUME_MUTE,MEDIA_ANNOUNCE,PAUSE,PLAY
|
||||
feature_flags=1200653,
|
||||
)
|
||||
]
|
||||
states = [
|
||||
@@ -156,6 +159,88 @@ async def test_media_player_entity(
|
||||
mock_client.media_player_command.reset_mock()
|
||||
|
||||
|
||||
async def test_media_player_entity_with_undefined_flags(
|
||||
hass: HomeAssistant,
|
||||
mock_client: APIClient,
|
||||
mock_generic_device_entry: MockGenericDeviceEntryType,
|
||||
) -> None:
|
||||
"""Test that media_player handles undefined feature flags gracefully."""
|
||||
# Include existing flags (PAUSE=1, PLAY=16384, VOLUME_SET=4)
|
||||
# plus undefined bits (bit 6=64, bit 23=8388608)
|
||||
# Total: 1 + 16384 + 4 + 64 + 8388608 = 8405061
|
||||
entity_info = [
|
||||
MediaPlayerInfo(
|
||||
object_id="mymedia_player_undefined",
|
||||
key=1,
|
||||
name="my media_player undefined",
|
||||
supports_pause=True,
|
||||
# PAUSE,PLAY,VOLUME_SET + undefined bits 6 and 23
|
||||
feature_flags=8405061,
|
||||
)
|
||||
]
|
||||
states = [
|
||||
MediaPlayerEntityState(
|
||||
key=1, volume=50, muted=False, state=MediaPlayerState.PLAYING
|
||||
)
|
||||
]
|
||||
await mock_generic_device_entry(
|
||||
mock_client=mock_client,
|
||||
entity_info=entity_info,
|
||||
states=states,
|
||||
)
|
||||
|
||||
# Verify entity is created successfully despite undefined flags
|
||||
state = hass.states.get("media_player.test_my_media_player_undefined")
|
||||
assert state is not None
|
||||
assert state.state == STATE_PLAYING
|
||||
|
||||
# Verify supported features only include known flags
|
||||
# Should have PAUSE, PLAY, and VOLUME_SET
|
||||
supported_features = state.attributes.get("supported_features", 0)
|
||||
# PAUSE=1, VOLUME_SET=4, PLAY=16384 = 16389
|
||||
assert supported_features == 16389
|
||||
|
||||
# Verify entity works correctly with known features
|
||||
await hass.services.async_call(
|
||||
MEDIA_PLAYER_DOMAIN,
|
||||
SERVICE_MEDIA_PLAY,
|
||||
{
|
||||
ATTR_ENTITY_ID: "media_player.test_my_media_player_undefined",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
mock_client.media_player_command.assert_has_calls(
|
||||
[call(1, command=MediaPlayerCommand.PLAY, device_id=0)]
|
||||
)
|
||||
mock_client.media_player_command.reset_mock()
|
||||
|
||||
await hass.services.async_call(
|
||||
MEDIA_PLAYER_DOMAIN,
|
||||
SERVICE_MEDIA_PAUSE,
|
||||
{
|
||||
ATTR_ENTITY_ID: "media_player.test_my_media_player_undefined",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
mock_client.media_player_command.assert_has_calls(
|
||||
[call(1, command=MediaPlayerCommand.PAUSE, device_id=0)]
|
||||
)
|
||||
mock_client.media_player_command.reset_mock()
|
||||
|
||||
await hass.services.async_call(
|
||||
MEDIA_PLAYER_DOMAIN,
|
||||
SERVICE_VOLUME_SET,
|
||||
{
|
||||
ATTR_ENTITY_ID: "media_player.test_my_media_player_undefined",
|
||||
ATTR_MEDIA_VOLUME_LEVEL: 0.7,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
mock_client.media_player_command.assert_has_calls(
|
||||
[call(1, volume=0.7, device_id=0)]
|
||||
)
|
||||
|
||||
|
||||
async def test_media_player_entity_with_source(
|
||||
hass: HomeAssistant,
|
||||
mock_client: APIClient,
|
||||
@@ -202,6 +287,8 @@ async def test_media_player_entity_with_source(
|
||||
key=1,
|
||||
name="my media_player",
|
||||
supports_pause=True,
|
||||
# PLAY_MEDIA,BROWSE_MEDIA,STOP,VOLUME_SET,VOLUME_MUTE,MEDIA_ANNOUNCE,PAUSE,PLAY
|
||||
feature_flags=1200653,
|
||||
)
|
||||
]
|
||||
states = [
|
||||
@@ -317,6 +404,8 @@ async def test_media_player_proxy(
|
||||
key=1,
|
||||
name="my media_player",
|
||||
supports_pause=True,
|
||||
# PLAY_MEDIA,BROWSE_MEDIA,STOP,VOLUME_SET,VOLUME_MUTE,MEDIA_ANNOUNCE,PAUSE,PLAY
|
||||
feature_flags=1200653,
|
||||
supported_formats=[
|
||||
MediaPlayerSupportedFormat(
|
||||
format="flac",
|
||||
@@ -475,6 +564,8 @@ async def test_media_player_formats_reload_preserves_data(
|
||||
key=1,
|
||||
name="Test Media Player",
|
||||
supports_pause=True,
|
||||
# PLAY_MEDIA,BROWSE_MEDIA,STOP,VOLUME_SET,VOLUME_MUTE,MEDIA_ANNOUNCE,PAUSE,PLAY
|
||||
feature_flags=1200653,
|
||||
supported_formats=supported_formats,
|
||||
)
|
||||
],
|
||||
|
Reference in New Issue
Block a user