Fix: allow seek support in providers which support it
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Wed, 5 Feb 2025 13:37:00 +0000 (14:37 +0100)
committerMarcel van der Veldt <m.vanderveldt@outlook.com>
Wed, 5 Feb 2025 13:37:00 +0000 (14:37 +0100)
19 files changed:
music_assistant/providers/_template_music_provider/__init__.py
music_assistant/providers/apple_music/__init__.py
music_assistant/providers/audible/audible_helper.py
music_assistant/providers/audiobookshelf/__init__.py
music_assistant/providers/builtin/__init__.py
music_assistant/providers/deezer/__init__.py
music_assistant/providers/filesystem_local/__init__.py
music_assistant/providers/ibroadcast/__init__.py
music_assistant/providers/jellyfin/__init__.py
music_assistant/providers/plex/__init__.py
music_assistant/providers/podcastfeed/__init__.py
music_assistant/providers/qobuz/__init__.py
music_assistant/providers/radiobrowser/__init__.py
music_assistant/providers/siriusxm/__init__.py
music_assistant/providers/soundcloud/__init__.py
music_assistant/providers/test/__init__.py
music_assistant/providers/tidal/__init__.py
music_assistant/providers/tunein/__init__.py
music_assistant/providers/ytmusic/__init__.py

index ce6b9b08f6519481b4a3da7152a479fcf28a42f9..c305d7b8853551c6c0d56ee2ae91f5358050cf18 100644 (file)
@@ -392,6 +392,10 @@ class MyDemoMusicprovider(MusicProvider):
             stream_type=StreamType.HTTP,
             # explore the StreamDetails model and StreamType enum for more options
             # but the above should be the mandatory fields to set.
+            allow_seek=True,
+            # set allow_seek to True if the stream may be seeked
+            can_seek=True,
+            # set can_seek to True if the stream supports seeking
         )
 
     async def get_audio_stream(
index fdda7cc03995a680b59504b63fbf4f5d2edb5071..bfee2038c8615614142d59827f34d16ffb745a22 100644 (file)
@@ -370,6 +370,7 @@ class AppleMusicProvider(MusicProvider):
             path=stream_url,
             decryption_key=await self._get_decryption_key(license_url, key_id, uri, item_id),
             can_seek=True,
+            allow_seek=True,
         )
 
     def _parse_artist(self, artist_obj):
index 37bcfcb940759d2988e36e270d045610fd733ba8..f7d11086b632f3963aa471aae394849467d53722 100644 (file)
@@ -16,12 +16,7 @@ from urllib.parse import parse_qs, urlparse
 import audible
 import audible.register
 from audible import AsyncClient
-from music_assistant_models.enums import (
-    ContentType,
-    ImageType,
-    MediaType,
-    StreamType,
-)
+from music_assistant_models.enums import ContentType, ImageType, MediaType, StreamType
 from music_assistant_models.errors import LoginFailed
 from music_assistant_models.media_items import (
     Audiobook,
@@ -169,6 +164,7 @@ class AudibleHelper:
             stream_type=StreamType.HTTP,
             path=m3u8_url,
             can_seek=True,
+            allow_seek=True,
             duration=duration,
             data={"acr": acr},
         )
index fa8f454cee5c86a48ef612e200c40ed9ccc1d853..2c5791487a8e017f74ff663ca325981c0785d044 100644 (file)
@@ -445,6 +445,8 @@ class Audiobookshelf(MusicProvider):
             media_type=media_type,
             stream_type=StreamType.HLS,
             path=stream_url,
+            can_seek=True,
+            allow_seek=True,
         )
 
     async def get_stream_details(self, item_id: str, media_type: MediaType) -> StreamDetails:
@@ -490,6 +492,8 @@ class Audiobookshelf(MusicProvider):
             media_type=MediaType.AUDIOBOOK,
             stream_type=StreamType.HTTP,
             path=stream_url,
+            can_seek=True,
+            allow_seek=True,
         )
 
     async def _get_stream_details_podcast_episode(self, podcast_id: str) -> StreamDetails:
@@ -517,6 +521,8 @@ class Audiobookshelf(MusicProvider):
             media_type=MediaType.PODCAST_EPISODE,
             stream_type=StreamType.HTTP,
             path=full_url,
+            can_seek=True,
+            allow_seek=True,
         )
 
     async def on_played(
index 1f2170a83c96e418e2213ee13754031fd7274f21..f8b44288345672d74efea38d23e1b35240f07578 100644 (file)
@@ -558,6 +558,7 @@ class BuiltinProvider(MusicProvider):
             stream_type=StreamType.HTTP,
             path=item_id,
             can_seek=not is_radio,
+            allow_seek=not is_radio,
         )
 
     async def _get_builtin_playlist_random_favorite_tracks(self) -> list[Track]:
index d435063b2347ff535122f1de9f3ed93879ce0205..a7a5ae3ea578e92ab673c164ad859e90e9b87628 100644 (file)
@@ -444,6 +444,8 @@ class DeezerProvider(MusicProvider):
             # separately so we can use it later on.
             data={"url": url, "format": url_details["format"], "track_id": song_data["SNG_ID"]},
             size=int(song_data[f"FILESIZE_{url_details['format']}"]),
+            can_seek=True,
+            allow_seek=True,
         )
 
     async def get_audio_stream(
index b9f52054b96ab83ee12a87bfcd0fd0f122e9e18e..d27566e211becd05a110f9627dda40734fd33184 100644 (file)
@@ -1550,6 +1550,7 @@ class LocalFileSystemProvider(MusicProvider):
             data=file_item,
             path=file_item.absolute_path,
             can_seek=True,
+            allow_seek=True,
         )
 
     async def _get_stream_details_for_podcast_episode(self, item_id: str) -> StreamDetails:
index 0e0440922a50217102b82f0a7ef4316dd42682d6..2a3a11bc8fbeb60db8087673d43b9c4f5f0e2d72 100644 (file)
@@ -246,6 +246,8 @@ class IBroadcastProvider(MusicProvider):
             ),
             stream_type=StreamType.HTTP,
             path=url,
+            can_seek=True,
+            allow_seek=True,
         )
 
     async def _get_tracks(self, track_ids: list[int], is_playlist: bool = False) -> list[Track]:
index a4309ca470906aa9859230c7c792bc6e5c84b203..9e698dcf8702c713398a524da5f5f97c96d3f75b 100644 (file)
@@ -450,6 +450,8 @@ class JellyfinProvider(MusicProvider):
                 jellyfin_track[ITEM_KEY_RUNTIME_TICKS] / 10000000
             ),  # 10000000 ticks per millisecond)
             path=url,
+            can_seek=True,
+            allow_seek=True,
         )
 
     async def get_similar_tracks(self, prov_track_id: str, limit: int = 25) -> list[Track]:
index d22121954977e3419c1d2e5aeaa43850d954fea3..d4aa15a03faf5d914b02a04dac43c76d305d828e 100644 (file)
@@ -915,6 +915,8 @@ class PlexProvider(MusicProvider):
             stream_type=StreamType.HTTP,
             duration=plex_track.duration,
             data=plex_track,
+            can_seek=True,
+            allow_seek=True,
         )
 
         if content_type != ContentType.M4A:
index 0314ecab0fadd914b8136189520f255d28d974db..bf9148dd4c631e24c18a2ab7ebc622123efc0334 100644 (file)
@@ -179,6 +179,8 @@ class PodcastMusicprovider(MusicProvider):
                     media_type=MediaType.PODCAST_EPISODE,
                     stream_type=StreamType.HTTP,
                     path=episode["enclosures"][0]["url"],
+                    can_seek=True,
+                    allow_seek=True,
                 )
         raise MediaNotFoundError("Stream not found")
 
index df820fda52c8e2cf75b8984ac6454ba020a8cf91..0549e45412b22d191c4da96b1931bcb782128130 100644 (file)
@@ -440,6 +440,8 @@ class QobuzProvider(MusicProvider):
             duration=streamdata["duration"],
             data=streamdata,  # we need these details for reporting playback
             path=streamdata["url"],
+            can_seek=True,
+            allow_seek=True,
         )
 
     async def _report_playback_started(self, streamdata: dict) -> None:
index 68a8a0100913affc86d85e93cfc6aa10d297d35f..1b85736b6ceb00c20a73ef13a980cc34ccf88806 100644 (file)
@@ -193,7 +193,10 @@ class RadioBrowserProvider(MusicProvider):
         if TYPE_CHECKING:
             stored_radios = cast(list[str], stored_radios)
         for item in stored_radios:
-            yield await self.get_radio(item)
+            try:
+                yield await self.get_radio(item)
+            except MediaNotFoundError as err:
+                self.logger.warning("Radio station %s not found: %s", item, err)
 
     async def library_add(self, item: MediaItemType) -> bool:
         """Add item to provider's library. Return true on success."""
@@ -364,4 +367,5 @@ class RadioBrowserProvider(MusicProvider):
             stream_type=StreamType.HTTP,
             path=stream.url_resolved,
             can_seek=False,
+            allow_seek=False,
         )
index 998d26f2b17654cbba5c8191ec9d2a9fba5196d3..0649cabadad9acc38ec8b37f6fa23bf7b172acb2 100644 (file)
@@ -238,6 +238,7 @@ class SiriusXMProvider(MusicProvider):
             media_type=MediaType.RADIO,
             path=hls_path,
             can_seek=False,
+            allow_seek=False,
         )
 
         return self._current_stream_details
index e4567654aa525a1cf56bfb0119bac78408597c6a..e0363ed9918608f0954e6db3e17eadafc3c665fc 100644 (file)
@@ -319,6 +319,8 @@ class SoundcloudMusicProvider(MusicProvider):
             if url.startswith("https://cf-hls-media.sndcdn.com")
             else StreamType.HTTP,
             path=url,
+            can_seek=True,
+            allow_seek=True,
         )
 
     async def _parse_artist(self, artist_obj: dict[str, Any]) -> Artist:
index f26d8f560d66e3b9c0f0b0de904bae307fa65e3b..d645ad526476db4c893042566ecd77f70ab49a8e 100644 (file)
@@ -353,4 +353,5 @@ class TestProvider(MusicProvider):
             stream_type=StreamType.HTTP,
             path=SILENCE_FILE_LONG,
             can_seek=True,
+            allow_seek=True,
         )
index 5e27d37e2d141f94182f97b851ba0ee9de939542..b9c3026f968a42896e0d95659e581b2f4f8344eb 100644 (file)
@@ -611,6 +611,8 @@ class TidalProvider(MusicProvider):
             stream_type=StreamType.HTTP,
             duration=track.duration,
             path=url,
+            can_seek=True,
+            allow_seek=True,
         )
 
     @throttle_with_retries
index a0e3ea8b0b02a03ca23e5b31369480d1f5b35f60..c03a2ddfc33e4e6e5baa623cdb1f2744a7ad1adf 100644 (file)
@@ -251,6 +251,7 @@ class TuneInProvider(MusicProvider):
                 media_type=MediaType.RADIO,
                 stream_type=StreamType.HTTP,
                 path=item_id,
+                allow_seek=False,
                 can_seek=False,
             )
         if "--" in item_id:
@@ -269,6 +270,7 @@ class TuneInProvider(MusicProvider):
                 media_type=MediaType.RADIO,
                 stream_type=StreamType.HTTP,
                 path=stream["url"],
+                allow_seek=False,
                 can_seek=False,
             )
         msg = f"Unable to retrieve stream details for {item_id}"
index 3a2a06da43c5f4e80bcf466a4142488c320800db..4e39a0cf352002f297456e3519cf3243dc97aea1 100644 (file)
@@ -551,6 +551,8 @@ class YoutubeMusicProvider(MusicProvider):
             ),
             stream_type=StreamType.HTTP,
             path=stream_format["url"],
+            can_seek=True,
+            allow_seek=True,
         )
         if (
             stream_format.get("audio_channels")