mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Add media seek for sources other than Deezer for Bang & Olufsen (#128661)
* Add seeking for sources other than Deezer * Add is_seekable attribute to fallback sources and BangOlufsenSource Add testing * Update comment * Use support flags instead of raising errors when seeking on incompatible source
This commit is contained in:
parent
dbb80dd6c0
commit
6c365fffde
@ -17,14 +17,46 @@ from homeassistant.components.media_player import (
|
||||
class BangOlufsenSource:
|
||||
"""Class used for associating device source ids with friendly names. May not include all sources."""
|
||||
|
||||
URI_STREAMER: Final[Source] = Source(name="Audio Streamer", id="uriStreamer")
|
||||
BLUETOOTH: Final[Source] = Source(name="Bluetooth", id="bluetooth")
|
||||
CHROMECAST: Final[Source] = Source(name="Chromecast built-in", id="chromeCast")
|
||||
LINE_IN: Final[Source] = Source(name="Line-In", id="lineIn")
|
||||
SPDIF: Final[Source] = Source(name="Optical", id="spdif")
|
||||
NET_RADIO: Final[Source] = Source(name="B&O Radio", id="netRadio")
|
||||
DEEZER: Final[Source] = Source(name="Deezer", id="deezer")
|
||||
TIDAL: Final[Source] = Source(name="Tidal", id="tidal")
|
||||
URI_STREAMER: Final[Source] = Source(
|
||||
name="Audio Streamer",
|
||||
id="uriStreamer",
|
||||
is_seekable=False,
|
||||
)
|
||||
BLUETOOTH: Final[Source] = Source(
|
||||
name="Bluetooth",
|
||||
id="bluetooth",
|
||||
is_seekable=False,
|
||||
)
|
||||
CHROMECAST: Final[Source] = Source(
|
||||
name="Chromecast built-in",
|
||||
id="chromeCast",
|
||||
is_seekable=False,
|
||||
)
|
||||
LINE_IN: Final[Source] = Source(
|
||||
name="Line-In",
|
||||
id="lineIn",
|
||||
is_seekable=False,
|
||||
)
|
||||
SPDIF: Final[Source] = Source(
|
||||
name="Optical",
|
||||
id="spdif",
|
||||
is_seekable=False,
|
||||
)
|
||||
NET_RADIO: Final[Source] = Source(
|
||||
name="B&O Radio",
|
||||
id="netRadio",
|
||||
is_seekable=False,
|
||||
)
|
||||
DEEZER: Final[Source] = Source(
|
||||
name="Deezer",
|
||||
id="deezer",
|
||||
is_seekable=True,
|
||||
)
|
||||
TIDAL: Final[Source] = Source(
|
||||
name="Tidal",
|
||||
id="tidal",
|
||||
is_seekable=True,
|
||||
)
|
||||
|
||||
|
||||
BANG_OLUFSEN_STATES: dict[str, MediaPlayerState] = {
|
||||
@ -162,6 +194,7 @@ FALLBACK_SOURCES: Final[SourceArray] = SourceArray(
|
||||
is_playable=False,
|
||||
name="Audio Streamer",
|
||||
type=SourceTypeEnum(value="uriStreamer"),
|
||||
is_seekable=False,
|
||||
),
|
||||
Source(
|
||||
id="bluetooth",
|
||||
@ -169,6 +202,7 @@ FALLBACK_SOURCES: Final[SourceArray] = SourceArray(
|
||||
is_playable=False,
|
||||
name="Bluetooth",
|
||||
type=SourceTypeEnum(value="bluetooth"),
|
||||
is_seekable=False,
|
||||
),
|
||||
Source(
|
||||
id="spotify",
|
||||
@ -176,6 +210,7 @@ FALLBACK_SOURCES: Final[SourceArray] = SourceArray(
|
||||
is_playable=False,
|
||||
name="Spotify Connect",
|
||||
type=SourceTypeEnum(value="spotify"),
|
||||
is_seekable=True,
|
||||
),
|
||||
Source(
|
||||
id="lineIn",
|
||||
@ -183,6 +218,7 @@ FALLBACK_SOURCES: Final[SourceArray] = SourceArray(
|
||||
is_playable=True,
|
||||
name="Line-In",
|
||||
type=SourceTypeEnum(value="lineIn"),
|
||||
is_seekable=False,
|
||||
),
|
||||
Source(
|
||||
id="spdif",
|
||||
@ -190,6 +226,7 @@ FALLBACK_SOURCES: Final[SourceArray] = SourceArray(
|
||||
is_playable=True,
|
||||
name="Optical",
|
||||
type=SourceTypeEnum(value="spdif"),
|
||||
is_seekable=False,
|
||||
),
|
||||
Source(
|
||||
id="netRadio",
|
||||
@ -197,6 +234,7 @@ FALLBACK_SOURCES: Final[SourceArray] = SourceArray(
|
||||
is_playable=True,
|
||||
name="B&O Radio",
|
||||
type=SourceTypeEnum(value="netRadio"),
|
||||
is_seekable=False,
|
||||
),
|
||||
Source(
|
||||
id="deezer",
|
||||
@ -204,6 +242,7 @@ FALLBACK_SOURCES: Final[SourceArray] = SourceArray(
|
||||
is_playable=True,
|
||||
name="Deezer",
|
||||
type=SourceTypeEnum(value="deezer"),
|
||||
is_seekable=True,
|
||||
),
|
||||
Source(
|
||||
id="tidalConnect",
|
||||
@ -211,6 +250,7 @@ FALLBACK_SOURCES: Final[SourceArray] = SourceArray(
|
||||
is_playable=True,
|
||||
name="Tidal Connect",
|
||||
type=SourceTypeEnum(value="tidalConnect"),
|
||||
is_seekable=True,
|
||||
),
|
||||
]
|
||||
)
|
||||
|
@ -94,7 +94,6 @@ BANG_OLUFSEN_FEATURES = (
|
||||
| MediaPlayerEntityFeature.PLAY_MEDIA
|
||||
| MediaPlayerEntityFeature.PREVIOUS_TRACK
|
||||
| MediaPlayerEntityFeature.REPEAT_SET
|
||||
| MediaPlayerEntityFeature.SEEK
|
||||
| MediaPlayerEntityFeature.SELECT_SOURCE
|
||||
| MediaPlayerEntityFeature.STOP
|
||||
| MediaPlayerEntityFeature.TURN_OFF
|
||||
@ -124,7 +123,6 @@ class BangOlufsenMediaPlayer(BangOlufsenEntity, MediaPlayerEntity):
|
||||
_attr_icon = "mdi:speaker-wireless"
|
||||
_attr_name = None
|
||||
_attr_device_class = MediaPlayerDeviceClass.SPEAKER
|
||||
_attr_supported_features = BANG_OLUFSEN_FEATURES
|
||||
|
||||
def __init__(self, entry: ConfigEntry, client: MozartClient) -> None:
|
||||
"""Initialize the media player."""
|
||||
@ -485,6 +483,17 @@ class BangOlufsenMediaPlayer(BangOlufsenEntity, MediaPlayerEntity):
|
||||
|
||||
self.async_write_ha_state()
|
||||
|
||||
@property
|
||||
def supported_features(self) -> MediaPlayerEntityFeature:
|
||||
"""Flag media player features that are supported."""
|
||||
features = BANG_OLUFSEN_FEATURES
|
||||
|
||||
# Add seeking if supported by the current source
|
||||
if self._source_change.is_seekable is True:
|
||||
features |= MediaPlayerEntityFeature.SEEK
|
||||
|
||||
return features
|
||||
|
||||
@property
|
||||
def state(self) -> MediaPlayerState:
|
||||
"""Return the current state of the media player."""
|
||||
@ -631,17 +640,12 @@ class BangOlufsenMediaPlayer(BangOlufsenEntity, MediaPlayerEntity):
|
||||
|
||||
async def async_media_seek(self, position: float) -> None:
|
||||
"""Seek to position in ms."""
|
||||
if self._source_change.id == BangOlufsenSource.DEEZER.id:
|
||||
await self._client.seek_to_position(position_ms=int(position * 1000))
|
||||
# Try to prevent the playback progress from bouncing in the UI.
|
||||
self._attr_media_position_updated_at = utcnow()
|
||||
self._playback_progress = PlaybackProgress(progress=int(position))
|
||||
await self._client.seek_to_position(position_ms=int(position * 1000))
|
||||
# Try to prevent the playback progress from bouncing in the UI.
|
||||
self._attr_media_position_updated_at = utcnow()
|
||||
self._playback_progress = PlaybackProgress(progress=int(position))
|
||||
|
||||
self.async_write_ha_state()
|
||||
else:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN, translation_key="non_deezer_seeking"
|
||||
)
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_media_previous_track(self) -> None:
|
||||
"""Send the previous track command."""
|
||||
|
@ -29,9 +29,6 @@
|
||||
"m3u_invalid_format": {
|
||||
"message": "Media sources with the .m3u extension are not supported."
|
||||
},
|
||||
"non_deezer_seeking": {
|
||||
"message": "Seeking is currently only supported when using Deezer"
|
||||
},
|
||||
"invalid_source": {
|
||||
"message": "Invalid source: {invalid_source}. Valid sources are: {valid_sources}"
|
||||
},
|
||||
|
@ -673,10 +673,12 @@ async def test_async_media_next_track(
|
||||
@pytest.mark.parametrize(
|
||||
("source", "expected_result", "seek_called_times"),
|
||||
[
|
||||
# Deezer source, seek expected
|
||||
# Seekable source, seek expected
|
||||
(BangOlufsenSource.DEEZER, does_not_raise(), 1),
|
||||
# Non deezer source, seek shouldn't work
|
||||
(BangOlufsenSource.TIDAL, pytest.raises(HomeAssistantError), 0),
|
||||
# Non seekable source, seek shouldn't work
|
||||
(BangOlufsenSource.LINE_IN, pytest.raises(HomeAssistantError), 0),
|
||||
# Malformed source, seek shouldn't work
|
||||
(Source(), pytest.raises(HomeAssistantError), 0),
|
||||
],
|
||||
)
|
||||
async def test_async_media_seek(
|
||||
|
Loading…
x
Reference in New Issue
Block a user