From: Fabian Munkes <105975993+fmunkes@users.noreply.github.com> Date: Mon, 16 Feb 2026 17:30:04 +0000 (+0100) Subject: abs: report correct time_listened in sessions (#3163) X-Git-Url: https://git.kitaultman.com/?a=commitdiff_plain;h=81718359faa998634a715061e66d4d6270c72816;p=music-assistant-server.git abs: report correct time_listened in sessions (#3163) * abs sessions statistics * small progress sync fix --- diff --git a/music_assistant/constants.py b/music_assistant/constants.py index f82c797d..c052dcab 100644 --- a/music_assistant/constants.py +++ b/music_assistant/constants.py @@ -939,6 +939,9 @@ SOUNDTRACK_INDICATORS = [ r"\boriginal.*cast.*recording\b", ] +# how often we report the playback progress in the player_queues controller +PLAYBACK_REPORT_INTERVAL_SECONDS = 30 + # List of providers that do not use HTTP streaming # but consume raw audio data over other protocols # for provider domains in this list, we won't show the default diff --git a/music_assistant/controllers/player_queues.py b/music_assistant/controllers/player_queues.py index 620f22a6..3dd3a827 100644 --- a/music_assistant/controllers/player_queues.py +++ b/music_assistant/controllers/player_queues.py @@ -64,6 +64,7 @@ from music_assistant_models.queue_item import QueueItem from music_assistant.constants import ( ATTR_ANNOUNCEMENT_IN_PROGRESS, MASS_LOGO_ONLINE, + PLAYBACK_REPORT_INTERVAL_SECONDS, PLAYLIST_MEDIA_TYPES, VERBOSE_LOG_LEVEL, PlaylistPlayableItem, @@ -2315,7 +2316,7 @@ class PlayerQueuesController(CoreController): # we do this every 30 seconds or when the state changes if ( changed_keys.intersection({"state", "current_item_id"}) - or int(queue.elapsed_time) % 30 == 0 + or int(queue.elapsed_time) % PLAYBACK_REPORT_INTERVAL_SECONDS == 0 ): self._handle_playback_progress_report(queue, prev_state, new_state) diff --git a/music_assistant/providers/audiobookshelf/__init__.py b/music_assistant/providers/audiobookshelf/__init__.py index 4b6415a1..01199c57 100644 --- a/music_assistant/providers/audiobookshelf/__init__.py +++ b/music_assistant/providers/audiobookshelf/__init__.py @@ -71,6 +71,7 @@ from music_assistant_models.media_items import ( from music_assistant_models.media_items.media_item import RecommendationFolder from music_assistant_models.streamdetails import MultiPartPath, StreamDetails +from music_assistant.constants import PLAYBACK_REPORT_INTERVAL_SECONDS from music_assistant.models.music_provider import MusicProvider from music_assistant.providers.audiobookshelf.parsers import ( parse_audiobook, @@ -664,6 +665,8 @@ for more details. async with self.create_session_lock: # check for an available open session if session_helper := self.sessions.get(mass_item_id): + # reset here, as this is our "time listened". + session_helper.last_sync_time = time.time() with suppress(AbsSessionNotFoundError): return await self._client.get_open_session( session_id=session_helper.abs_session_id @@ -774,7 +777,7 @@ for more details. # this method is called _before_ get_stream_details, so the playback session # is created here. session = await self._get_playback_session(mass_item_id=item_id) - finished = session.current_time > session.duration - 30 + finished = session.current_time > session.duration - PLAYBACK_REPORT_INTERVAL_SECONDS self.logger.debug("Resume position: obtained.") return finished, int(session.current_time * 1000) @@ -1007,12 +1010,20 @@ for more details. async def _update_by_session(session_helper: SessionHelper, duration: int) -> bool: now = time.time() + time_listened = now - session_helper.last_sync_time + if time_listened > PLAYBACK_REPORT_INTERVAL_SECONDS + 3: + # See player_queues controller, we get an update every 30s, and immediately on pause + # or play. + # We reset above 33, as this indicates a trigger after a longer absence and should + # not count into abs' statistics + self.logger.debug("Resetting time_listened due to longer absence.") + time_listened = 0.0 try: await self._client.sync_open_session( session_id=session_helper.abs_session_id, parameters=SyncOpenSessionParameters( current_time=position, - time_listened=now - session_helper.last_sync_time, + time_listened=time_listened, duration=duration, ), ) @@ -1036,7 +1047,7 @@ for more details. if media_item is None or not isinstance(media_item, PodcastEpisode): return - if fully_played and position < media_item.duration - 30: + if fully_played and position < media_item.duration - PLAYBACK_REPORT_INTERVAL_SECONDS: # faulty position update # occurs sometimes, if a player disconnects unexpectedly, or reports # a false position - seen this for MC players, but not for sendspin @@ -1077,7 +1088,7 @@ for more details. if media_item is None or not isinstance(media_item, Audiobook): return - if fully_played and position < media_item.duration - 30: + if fully_played and position < media_item.duration - PLAYBACK_REPORT_INTERVAL_SECONDS: # faulty position update, see above return @@ -1591,7 +1602,10 @@ for more details. if not self.progress_guard.guard_ok_abs(progress): continue if progress.current_time is not None: - if int(progress.current_time) != 0 and not progress.current_time >= 30: + if ( + int(progress.current_time) != 0 + and not progress.current_time >= PLAYBACK_REPORT_INTERVAL_SECONDS + ): # same as mass default, only > 30s continue if progress.library_item_id not in known_ids: @@ -1642,7 +1656,7 @@ for more details. ) if mass_audiobook is None: return - if int(progress.current_time) == 0: + if int(progress.current_time) == 0 and not progress.is_finished: await self.mass.music.mark_item_unplayed(mass_audiobook) else: await self.mass.music.mark_item_played( @@ -1663,7 +1677,7 @@ for more details. mass_episode = await self.get_podcast_episode(_episode_id, add_progress=False) except MediaNotFoundError: return - if int(progress.current_time) == 0: + if int(progress.current_time) == 0 and not progress.is_finished: await self.mass.music.mark_item_unplayed(mass_episode) else: await self.mass.music.mark_item_played(