Chore: Enhance the on_played callback with extra data
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Mon, 24 Feb 2025 20:22:51 +0000 (21:22 +0100)
committerMarcel van der Veldt <m.vanderveldt@outlook.com>
Mon, 24 Feb 2025 20:22:51 +0000 (21:22 +0100)
music_assistant/controllers/music.py
music_assistant/controllers/player_queues.py
music_assistant/models/music_provider.py
music_assistant/providers/_template_music_provider/__init__.py
music_assistant/providers/audible/__init__.py
music_assistant/providers/audiobookshelf/__init__.py
music_assistant/providers/opensubsonic/sonic_provider.py

index 06cf31334b3d6f44554f89e4923d08fb871739d9..7fa5763799c65e9482c04a04f49cb737fc181293 100644 (file)
@@ -825,6 +825,7 @@ class MusicController(CoreController):
         media_item: MediaItemType,
         fully_played: bool = True,
         seconds_played: int | None = None,
+        is_playing: bool = False,
     ) -> None:
         """Mark item as played in playlog."""
         timestamp = utc_timestamp()
@@ -836,23 +837,24 @@ class MusicController(CoreController):
             # one-off items like TTS or some sound effect etc.
             return
 
-        # update generic playlog table
-        await self.database.insert(
-            DB_TABLE_PLAYLOG,
-            {
-                "item_id": media_item.item_id,
-                "provider": media_item.provider,
-                "media_type": media_item.media_type.value,
-                "name": media_item.name,
-                "image": serialize_to_json(media_item.image.to_dict())
-                if media_item.image
-                else None,
-                "fully_played": fully_played,
-                "seconds_played": seconds_played,
-                "timestamp": timestamp,
-            },
-            allow_replace=True,
-        )
+        # update generic playlog table (when not playing)
+        if not is_playing:
+            await self.database.insert(
+                DB_TABLE_PLAYLOG,
+                {
+                    "item_id": media_item.item_id,
+                    "provider": media_item.provider,
+                    "media_type": media_item.media_type.value,
+                    "name": media_item.name,
+                    "image": serialize_to_json(media_item.image.to_dict())
+                    if media_item.image
+                    else None,
+                    "fully_played": fully_played,
+                    "seconds_played": seconds_played,
+                    "timestamp": timestamp,
+                },
+                allow_replace=True,
+            )
 
         # forward to provider(s) to sync resume state (e.g. for audiobooks)
         for prov_mapping in media_item.provider_mappings:
@@ -863,11 +865,13 @@ class MusicController(CoreController):
                         item_id=prov_mapping.item_id,
                         fully_played=fully_played,
                         position=seconds_played,
+                        is_playing=is_playing,
+                        media_item=media_item,
                     )
                 )
 
         # also update playcount in library table (if fully played)
-        if not fully_played:
+        if not fully_played or is_playing:
             return
         if not (ctrl := self.get_controller(media_item.media_type)):
             # skip non media items (e.g. plugin source)
@@ -912,6 +916,7 @@ class MusicController(CoreController):
                         item_id=prov_mapping.item_id,
                         fully_played=False,
                         position=0,
+                        media_item=media_item,
                     )
                 )
         # also update playcount in library table
index e34b68dfaaf0c470f385f90735bdeec8d6a5deab..2a55943e609808fc02a6f388bbbbfeefc32ca46d 100644 (file)
@@ -1925,6 +1925,7 @@ class PlayerQueuesController(CoreController):
                 item_to_report.media_item,
                 fully_played=fully_played,
                 seconds_played=seconds_played,
+                is_playing=is_playing,
             )
         )
         # signal 'media item played' event,
index 650ff631383e3e0b920008823b91cbc3e9b56dcf..e18713902688dc91abf5403be25334dd1cc4add6 100644 (file)
@@ -359,6 +359,8 @@ class MusicProvider(Provider):
         item_id: str,
         fully_played: bool,
         position: int,
+        is_playing: bool = False,
+        media_item: MediaItemType | None = None,
     ) -> None:
         """
         Handle callback when a (playable) media item has been played.
@@ -369,9 +371,14 @@ class MusicProvider(Provider):
             - every 30s when a track is playing
 
         Fully played is True when the track has been played to the end.
+
         Position is the last known position of the track in seconds, to sync resume state.
         When fully_played is set to false and position is 0,
         the user marked the item as unplayed in the UI.
+
+        is_playing is True when the track is currently playing.
+
+        media_item is the full media item details of the played/playing track.
         """
 
     async def resolve_image(self, path: str) -> str | bytes:
index b960057509957dde914ad8eedd780051296f8c19..5a8f2e23ac930ec686cfed885ddb7de438bfa702 100644 (file)
@@ -451,6 +451,8 @@ class MyDemoMusicprovider(MusicProvider):
         item_id: str,
         fully_played: bool,
         position: int,
+        is_playing: bool = False,
+        media_item: MediaItemType | None = None,
     ) -> None:
         """
         Handle callback when a (playable) media item has been played.
@@ -461,9 +463,14 @@ class MyDemoMusicprovider(MusicProvider):
             - every 30s when a track is playing
 
         Fully played is True when the track has been played to the end.
+
         Position is the last known position of the track in seconds, to sync resume state.
         When fully_played is set to false and position is 0,
         the user marked the item as unplayed in the UI.
+
+        is_playing is True when the track is currently playing.
+
+        media_item is the full media item details of the played/playing track.
         """
         # This is an OPTIONAL callback that is called when an item has been streamed.
         # You can use this e.g. for playback reporting or statistics.
index 0f348d97b33371843a225cb1dc62d62579e352ed..a5e8a72a1675df9197b7fbb55a474736634623c9 100644 (file)
@@ -29,7 +29,7 @@ from music_assistant.providers.audible.audible_helper import (
 )
 
 if TYPE_CHECKING:
-    from music_assistant_models.media_items import Audiobook
+    from music_assistant_models.media_items import Audiobook, MediaItemType
     from music_assistant_models.provider import ProviderManifest
     from music_assistant_models.streamdetails import StreamDetails
 
@@ -285,6 +285,8 @@ class Audibleprovider(MusicProvider):
         item_id: str,
         fully_played: bool,
         position: int,
+        is_playing: bool = False,
+        media_item: MediaItemType | None = None,
     ) -> None:
         """
         Handle callback when a (playable) media item has been played.
@@ -295,9 +297,14 @@ class Audibleprovider(MusicProvider):
             - every 30s when a track is playing
 
         Fully played is True when the track has been played to the end.
+
         Position is the last known position of the track in seconds, to sync resume state.
         When fully_played is set to false and position is 0,
         the user marked the item as unplayed in the UI.
+
+        is_playing is True when the track is currently playing.
+
+        media_item is the full media item details of the played/playing track.
         """
         await self.helper.set_last_position(item_id, position)
 
index a760bd6be6ffca591527601868226f434dece362..285480ab184e457dede0ca73429f51aff84dc25d 100644 (file)
@@ -30,7 +30,12 @@ from music_assistant_models.enums import (
     StreamType,
 )
 from music_assistant_models.errors import LoginFailed, MediaNotFoundError
-from music_assistant_models.media_items import AudioFormat, BrowseFolder, MediaItemTypeOrItemMapping
+from music_assistant_models.media_items import (
+    AudioFormat,
+    BrowseFolder,
+    MediaItemType,
+    MediaItemTypeOrItemMapping,
+)
 from music_assistant_models.streamdetails import StreamDetails
 
 from music_assistant.helpers.ffmpeg import get_ffmpeg_stream
@@ -538,7 +543,13 @@ class Audiobookshelf(MusicProvider):
         return False, 0
 
     async def on_played(
-        self, media_type: MediaType, item_id: str, fully_played: bool, position: int
+        self,
+        media_type: MediaType,
+        item_id: str,
+        fully_played: bool,
+        position: int,
+        is_playing: bool = False,
+        media_item: MediaItemType | None = None,
     ) -> None:
         """Update progress in Audiobookshelf.
 
index 731a6ab7c12cda1b85c88af8f8c2153da90a2b8d..ac0e01e74d50b25a51ef774e4dc55e439163e3e9 100644 (file)
@@ -33,6 +33,7 @@ from music_assistant_models.media_items import (
     AudioFormat,
     ItemMapping,
     MediaItemImage,
+    MediaItemType,
     Playlist,
     Podcast,
     PodcastEpisode,
@@ -797,6 +798,8 @@ class OpenSonicProvider(MusicProvider):
         item_id: str,
         fully_played: bool,
         position: int,
+        is_playing: bool = False,
+        media_item: MediaItemType | None = None,
     ) -> None:
         """
         Handle callback when a (playable) media item has been played.
@@ -807,9 +810,14 @@ class OpenSonicProvider(MusicProvider):
             - every 30s when a track is playing
 
         Fully played is True when the track has been played to the end.
+
         Position is the last known position of the track in seconds, to sync resume state.
         When fully_played is set to false and position is 0,
         the user marked the item as unplayed in the UI.
+
+        is_playing is True when the track is currently playing.
+
+        media_item is the full media item details of the played/playing track.
         """
         # Leave this function as the place where we will create a bookmark for podcasts when they
         # are stopped early and delete the bookmark when they are finished.