Announcements improvements (#1209)
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Mon, 8 Apr 2024 09:19:26 +0000 (11:19 +0200)
committerGitHub <noreply@github.com>
Mon, 8 Apr 2024 09:19:26 +0000 (11:19 +0200)
* Prefer group for announcements

* Fix some issues with Google cast and announcements

* ensure announce volume is int

* fallback image for cast metadata

music_assistant/server/controllers/players.py
music_assistant/server/providers/chromecast/__init__.py

index 264a8d6debe1b2d7c87718d331406071e3663f21..056c98ee635fc59a3bf849f008c269ad4dbe01aa 100644 (file)
@@ -643,24 +643,26 @@ class PlayerController(CoreController):
                 )
                 return
             if player.type in (PlayerType.SYNC_GROUP, PlayerType.GROUP) and not player.powered:
-                # announcement request sent to inactive group,
-                # redirect to all underlying players instead
-                self.logger.warning(
-                    "Detected announcement request to an inactive playergroup, "
-                    "this will be redirected to the individual players."
-                )
-                async with asyncio.TaskGroup() as tg:
-                    for group_member in player.group_childs:
-                        tg.create_task(
-                            self.play_announcement(
-                                group_member,
-                                url=url,
-                                use_pre_announce=use_pre_announce,
-                                volume_level=volume_level,
+                # announcement request sent to inactive group, check if any child's are playing
+                if len(list(self.iter_group_members(player, True, True))) > 0:
+                    # just for the sake of simplicity we handle this request per-player
+                    # so we can restore the individual players again.
+                    self.logger.warning(
+                        "Detected announcement request to an inactive playergroup, "
+                        "while one or more individual players are playing. "
+                        "This announcement will be redirected to the individual players."
+                    )
+                    async with asyncio.TaskGroup() as tg:
+                        for group_member in player.group_childs:
+                            tg.create_task(
+                                self.play_announcement(
+                                    group_member,
+                                    url=url,
+                                    use_pre_announce=use_pre_announce,
+                                    volume_level=volume_level,
+                                )
                             )
-                        )
-                return
-
+                    return
             # determine pre-announce from (group)player config
             if use_pre_announce is None and "tts" in url:
                 use_pre_announce = self.mass.config.get_raw_player_config_value(
@@ -859,7 +861,8 @@ class PlayerController(CoreController):
                 CONF_ENTRY_ANNOUNCE_VOLUME_MAX.default_value,
             )
             volume_level = min(announce_volume_max, volume_level)
-        return volume_level
+        # ensure the result is an integer
+        return int(volume_level)
 
     def _check_redirect(self, player_id: str) -> str:
         """Check if playback related command should be redirected."""
index 7bb06958b4c051fdf47575f8dd94fe2cea18f75b..fd5511e8da3c43c139ed991564aa6b3a2a3b52b6 100644 (file)
@@ -36,6 +36,7 @@ from music_assistant.constants import (
     CONF_CROSSFADE,
     CONF_FLOW_MODE,
     CONF_PLAYERS,
+    MASS_LOGO_ONLINE,
     VERBOSE_LOG_LEVEL,
 )
 from music_assistant.server.models.player_provider import PlayerProvider
@@ -70,8 +71,7 @@ PLAYER_CONFIG_ENTRIES = (
     CONF_ENTRY_CROSSFADE_DURATION,
 )
 
-DEFAULT_APP_ID = "CC1AD845"
-ALT_APP_ID = "46C1A819"
+MASS_APP_ID = "46C1A819"  # use the cast receiver app from philippe44 for now until we get our own
 
 
 # Monkey patch the Media controller here to store the queue items
@@ -237,14 +237,12 @@ class ChromecastProvider(PlayerProvider):
     ) -> None:
         """Handle PLAY MEDIA on given player."""
         castplayer = self.castplayers[player_id]
-        is_flow_mode = "/flow/" in media.uri
         queuedata = {
             "type": "LOAD",
             "media": self._create_cc_media_item(media),
         }
-        # make sure that the media controller app is launched
-        app_id = ALT_APP_ID if is_flow_mode else DEFAULT_APP_ID
-        await self._launch_app(castplayer, app_id)
+        # make sure that our media controller app is launched
+        await self._launch_app(castplayer)
         # send queue info to the CC
         media_controller = castplayer.cc.media_controller
         await asyncio.to_thread(media_controller.send_message, data=queuedata, inc_session_id=True)
@@ -542,7 +540,7 @@ class ChromecastProvider(PlayerProvider):
 
     ### Helpers / utils
 
-    async def _launch_app(self, castplayer: CastPlayer, app_id: str = DEFAULT_APP_ID) -> None:
+    async def _launch_app(self, castplayer: CastPlayer, app_id: str = MASS_APP_ID) -> None:
         """Launch the default Media Receiver App on a Chromecast."""
         event = asyncio.Event()
 
@@ -610,28 +608,47 @@ class ChromecastProvider(PlayerProvider):
             return
         if castplayer.player.state != PlayerState.PLAYING:
             return
-        if castplayer.cc.app_id != ALT_APP_ID:
+        if castplayer.player.announcement_in_progress:
             return
         queue = self.mass.player_queues.get_active_queue(castplayer.player_id)
         if not (current_item := queue.current_item):
             return
+        if not (queue.flow_mode or current_item.media_type == MediaType.RADIO):
+            return
         media_controller = castplayer.cc.media_controller
         # update metadata of current item chromecast
         if media_controller.status.media_custom_data["queue_item_id"] != current_item.queue_item_id:
-            image_url = self.mass.metadata.get_image_url(current_item.image)
+            image_url = (
+                self.mass.metadata.get_image_url(current_item.image)
+                if current_item.image
+                else MASS_LOGO_ONLINE
+            )
+            if (streamdetails := current_item.streamdetails) and streamdetails.stream_title:
+                album = current_item.media_item.name
+                if " - " in streamdetails.stream_title:
+                    artist, title = streamdetails.stream_title.split(" - ", 1)
+                else:
+                    artist = ""
+                    title = streamdetails.stream_title
+            elif media_item := current_item.media_item:
+                album = _album.name if (_album := getattr(media_item, "album", None)) else ""
+                artist = getattr(media_item, "artist_str", "")
+                title = media_item.name
+            else:
+                album = ""
+                artist = ""
+                title = current_item.name
             queuedata = {
                 "type": "PLAY",
                 "mediaSessionId": media_controller.status.media_session_id,
                 "customData": {
                     "metadata": {
                         "metadataType": 3,
-                        "albumName": album.name
-                        if (album := getattr(current_item.media_item, "album", None))
-                        else "",
-                        "songName": current_item.media_item.name,
-                        "artist": getattr(current_item.media_item, "artist_str", ""),
-                        "title": current_item.media_item.name,
-                        "images": [{"url": image_url}] if image_url else None,
+                        "albumName": album,
+                        "songName": title,
+                        "artist": artist,
+                        "title": title,
+                        "images": [{"url": image_url}],
                     }
                 },
             }