Fix issue with pluginsource (spotify connect) playback
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Wed, 19 Feb 2025 21:43:07 +0000 (22:43 +0100)
committerMarcel van der Veldt <m.vanderveldt@outlook.com>
Wed, 19 Feb 2025 21:43:07 +0000 (22:43 +0100)
music_assistant/controllers/players.py
music_assistant/controllers/streams.py
music_assistant/providers/airplay/provider.py
music_assistant/providers/snapcast/__init__.py

index 4ade6123ccdf9d730b2da20bfa58d71fad23fc2b..b7fc935ec6fce46713172fc601dca364be633fc9 100644 (file)
@@ -722,10 +722,12 @@ class PlayerController(CoreController):
         # check if player is already playing and source is different
         # in that case we need to stop the player first
         prev_source = player.active_source
-        if prev_source and source != prev_source and player.state != PlayerState.IDLE:
-            await self.cmd_stop(player_id)
-            await asyncio.sleep(0.5)  # small delay to allow stop to process
+        if prev_source and source != prev_source:
+            if player.state != PlayerState.IDLE:
+                await self.cmd_stop(player_id)
+                await asyncio.sleep(0.5)  # small delay to allow stop to process
             player.active_source = None
+            player.current_media = None
         # check if source is a pluginsource
         # in that case the source id is the lookup_key of the plugin provider
         if plugin_prov := self.mass.get_provider(source):
@@ -735,6 +737,7 @@ class PlayerController(CoreController):
         # this can be used to restore the queue after a source switch
         if mass_queue := self.mass.player_queues.get(source):
             player.active_source = mass_queue.queue_id
+            self.update(player_id)
             return
         # basic check if player supports source selection
         if PlayerFeature.SELECT_SOURCE not in player.supported_features:
@@ -1402,7 +1405,9 @@ class PlayerController(CoreController):
         # if player has plugin source active return that
         for plugin_source in self._get_plugin_sources():
             if player.active_source == plugin_source.id or (
-                player.current_media and plugin_source.id == player.current_media.queue_id
+                player.current_media
+                and plugin_source.id == player.current_media.queue_id
+                and player.state in (PlayerState.PLAYING, PlayerState.PAUSED)
             ):
                 # copy/set current media if available
                 if plugin_source.metadata:
index 909fa7ec7da75b29910c2b34c3440dd1dd0a536f..496b8b1c23ec3923fcdd8af4e162d70eb9e54c0d 100644 (file)
@@ -878,15 +878,18 @@ class StreamsController(CoreController):
         )
         chunk_size = int(get_chunksize(output_format, 1) / 10)
         player.active_source = plugin_source_id
-        async for chunk in get_ffmpeg_stream(
-            audio_input=audio_input,
-            input_format=plugin_source.audio_format,
-            output_format=output_format,
-            chunk_size=chunk_size,
-            filter_params=player_filter_params,
-            extra_input_args=["-re"],
-        ):
-            yield chunk
+        try:
+            async for chunk in get_ffmpeg_stream(
+                audio_input=audio_input,
+                input_format=plugin_source.audio_format,
+                output_format=output_format,
+                chunk_size=chunk_size,
+                filter_params=player_filter_params,
+                extra_input_args=["-re"],
+            ):
+                yield chunk
+        finally:
+            player.active_source = player.player_id
 
     async def get_queue_item_stream(
         self,
index 2f5663243939ffb73d5364c4fc4f0c06416c075b..b336578d6d224802dd990106f3d813feb6e57135 100644 (file)
@@ -17,7 +17,6 @@ from music_assistant_models.enums import (
     PlayerState,
     PlayerType,
     ProviderFeature,
-    StreamType,
 )
 from music_assistant_models.media_items import AudioFormat
 from music_assistant_models.player import DeviceInfo, Player, PlayerMedia
@@ -39,7 +38,6 @@ from music_assistant.helpers.datetime import utc
 from music_assistant.helpers.ffmpeg import get_ffmpeg_stream
 from music_assistant.helpers.util import TaskManager, get_ip_pton, lock, select_free_port
 from music_assistant.models.player_provider import PlayerProvider
-from music_assistant.models.plugin import PluginProvider
 from music_assistant.providers.airplay.raop import RaopStreamSession
 from music_assistant.providers.player_group import PlayerGroupProvider
 
@@ -321,21 +319,13 @@ class AirplayProvider(PlayerProvider):
             )
         elif media.media_type == MediaType.PLUGIN_SOURCE:
             # special case: plugin source stream
-            # consume the stream directly, so we can skip one step in between
-            assert media.custom_data is not None  # for type checking
-            provider = cast(PluginProvider, self.mass.get_provider(media.custom_data["provider"]))
-            plugin_source = provider.get_source()
-            assert plugin_source.audio_format is not None  # for type checking
-            if plugin_source.stream_type == StreamType.CUSTOM:
-                input_format = plugin_source.audio_format
-                audio_source = provider.get_audio_stream(player_id)
-            else:
-                input_format = AIRPLAY_PCM_FORMAT
-                audio_source = get_ffmpeg_stream(
-                    audio_input=media.uri,
-                    input_format=plugin_source.audio_format,
-                    output_format=AIRPLAY_PCM_FORMAT,
-                )
+            input_format = AIRPLAY_PCM_FORMAT
+            assert media.custom_data
+            audio_source = self.mass.streams.get_plugin_source_stream(
+                plugin_source_id=media.custom_data["provider"],
+                output_format=AIRPLAY_PCM_FORMAT,
+                player_id=player_id,
+            )
         elif media.queue_id and media.queue_id.startswith("ugp_"):
             # special case: UGP stream
             ugp_provider = cast(PlayerGroupProvider, self.mass.get_provider("player_group"))
index e5782ced25609aa2372607d39365770140ea7d5b..ae93935893eae5cb986a67b9740929433301f83e 100644 (file)
@@ -22,7 +22,6 @@ from music_assistant_models.enums import (
     PlayerState,
     PlayerType,
     ProviderFeature,
-    StreamType,
 )
 from music_assistant_models.errors import SetupFailedError
 from music_assistant_models.media_items import AudioFormat
@@ -43,7 +42,6 @@ from music_assistant.helpers.audio import FFMpeg, get_ffmpeg_stream, get_player_
 from music_assistant.helpers.process import AsyncProcess, check_output
 from music_assistant.helpers.util import get_ip_pton
 from music_assistant.models.player_provider import PlayerProvider
-from music_assistant.models.plugin import PluginProvider
 
 if TYPE_CHECKING:
     from music_assistant_models.config_entries import ProviderConfig
@@ -517,16 +515,11 @@ class SnapCastProvider(PlayerProvider):
             )
         elif media.media_type == MediaType.PLUGIN_SOURCE:
             # special case: plugin source stream
-            # consume the stream directly, so we can skip one step in between
-            assert media.custom_data is not None  # for type checking
-            provider = cast(PluginProvider, self.mass.get_provider(media.custom_data["provider"]))
-            plugin_source = provider.get_source()
-            assert plugin_source.audio_format is not None  # for type checking
-            input_format = plugin_source.audio_format
-            audio_source = (
-                provider.get_audio_stream(player_id)
-                if plugin_source.stream_type == StreamType.CUSTOM
-                else plugin_source.path
+            input_format = DEFAULT_SNAPCAST_FORMAT
+            audio_source = self.mass.streams.get_plugin_source_stream(
+                plugin_source_id=media.custom_data["provider"],
+                output_format=DEFAULT_SNAPCAST_FORMAT,
+                player_id=player_id,
             )
         elif media.queue_id.startswith("ugp_"):
             # special case: UGP stream