From: Eric Munson Date: Sat, 27 Jan 2024 17:41:51 +0000 (-0500) Subject: Subsonic: Better scrobbling and track enumeration (#1035) X-Git-Url: https://git.kitaultman.com/?a=commitdiff_plain;h=985e4e286d82f986e527897a9bb9bfba2a79288d;p=music-assistant-server.git Subsonic: Better scrobbling and track enumeration (#1035) * Subsonic: Fix stream scrobbling get_audio_stream can be called multiple times when a track is played to allow for MA to analyze the audio. Scrobbling at the end of the stream was causing multiple reports when placed here. The correct way to do is is using a callback on the StreamDetails object we create. Now we scrobble playback when MA tells us that more than half the track has been played. We also now make a "Now Playing" scrobble when the StreamDetails object is built. Signed-off-by: Eric B Munson * Subsonic: Better song enumeration Rely on pagination instead of one massive request. Signed-off-by: Eric B Munson --------- Signed-off-by: Eric B Munson --- diff --git a/music_assistant/server/providers/opensubsonic/sonic_provider.py b/music_assistant/server/providers/opensubsonic/sonic_provider.py index 1d6f72ef..00a7bc8a 100644 --- a/music_assistant/server/providers/opensubsonic/sonic_provider.py +++ b/music_assistant/server/providers/opensubsonic/sonic_provider.py @@ -453,11 +453,28 @@ class OpenSonicProvider(MusicProvider): Note the lack of item count on this method. """ + offset = 0 + count = 500 results = await self._run_async( - self._conn.search3, query="", artistCount=0, albumCount=0, songCount=999999 + self._conn.search3, + query="", + artistCount=0, + albumCount=0, + songOffset=offset, + songCount=count, ) - for entry in results["songs"]: - yield self._parse_track(entry) + while results["songs"]: + for entry in results["songs"]: + yield self._parse_track(entry) + offset += count + results = await self._run_async( + self._conn.search3, + query="", + artistCount=0, + albumCount=0, + songOffset=offset, + songCount=count, + ) async def get_album(self, prov_album_id: str) -> Album: """Return the requested Album.""" @@ -572,16 +589,28 @@ class OpenSonicProvider(MusicProvider): sonic_song: SonicSong = await self._run_async(self._conn.getSong, item_id) except (ParameterError, DataNotFoundError) as e: raise MediaNotFoundError(f"Item {item_id} not found") from e + + self.mass.create_task(self._report_playback_started(item_id)) + mime_type = sonic_song.content_type if mime_type.endswith("mpeg"): mime_type = sonic_song.suffix + return StreamDetails( item_id=sonic_song.id, provider=self.instance_id, audio_format=AudioFormat(content_type=ContentType.try_parse(mime_type)), duration=sonic_song.duration if sonic_song.duration is not None else 0, + callback=self._report_playback_stopped, ) + async def _report_playback_started(self, item_id: str) -> None: + await self._run_async(self._conn.scrobble, sid=item_id, submission=False) + + async def _report_playback_stopped(self, streamdetails: StreamDetails) -> None: + if streamdetails.seconds_streamed >= streamdetails.duration / 2: + await self._run_async(self._conn.scrobble, sid=streamdetails.item_id, submission=True) + async def get_audio_stream( self, streamdetails: StreamDetails, seek_position: int = 0 ) -> AsyncGenerator[bytes, None]: @@ -606,7 +635,6 @@ class OpenSonicProvider(MusicProvider): # keep reading from the audio buffer until there is no more data chunk = await audio_buffer.get() if chunk == b"": - await self._run_async(self._conn.scrobble, streamdetails.item_id) break yield chunk finally: