From: Marcel van der Veldt Date: Sun, 22 Feb 2026 15:06:49 +0000 (+0100) Subject: Fix playback speed handling on queue item and not on queue X-Git-Url: https://git.kitaultman.com/?a=commitdiff_plain;h=90f369da23027c5a880d7a48854f214757e31156;p=music-assistant-server.git Fix playback speed handling on queue item and not on queue --- diff --git a/music_assistant/controllers/player_queues.py b/music_assistant/controllers/player_queues.py index f33da0ae..d9703164 100644 --- a/music_assistant/controllers/player_queues.py +++ b/music_assistant/controllers/player_queues.py @@ -390,8 +390,14 @@ class PlayerQueuesController(CoreController): self._enqueue_next_item(queue_id, next_item) @api_command("player_queues/set_playback_speed") - async def set_playback_speed(self, queue_id: str, speed: float) -> None: - """Set the playback speed for the given queue. + async def set_playback_speed( + self, queue_id: str, speed: float, queue_item_id: str | None = None + ) -> None: + """ + Set the playback speed for the given queue item. + + If queue_item_id is not provided, + the speed will be set for the current item in the queue. :param queue_id: queue_id of the queue to configure. :param speed: playback speed multiplier (0.5 to 2.0). 1.0 = normal speed. @@ -399,10 +405,19 @@ class PlayerQueuesController(CoreController): if not (0.5 <= speed <= 2.0): raise InvalidDataError(f"Playback speed must be between 0.5 and 2.0, got {speed}") queue = self._queues[queue_id] - current_speed = float(queue.extra_attributes.get("playback_speed") or 1.0) + if not queue.current_item: + raise QueueEmpty("Cannot set playback speed: queue is empty") + queue_item_id = queue_item_id or queue.current_item.queue_item_id + queue_item = self.get_item(queue_id, queue_item_id) + if not queue_item: + raise InvalidDataError(f"Queue item {queue_item_id} not found in queue") + if not queue_item.duration or queue_item.media_type == MediaType.RADIO: + raise InvalidCommand("Cannot set playback speed for items with unknown duration") + current_speed = float(queue_item.extra_attributes.get("playback_speed") or 1.0) if abs(current_speed - speed) < 0.001: return # no change - queue.extra_attributes["playback_speed"] = speed + # use extra_attributes of the queue item to store the playback speed + queue_item.extra_attributes["playback_speed"] = speed self.signal_update(queue_id) if queue.state == PlaybackState.PLAYING: await self.resume(queue_id) diff --git a/music_assistant/controllers/streams/streams_controller.py b/music_assistant/controllers/streams/streams_controller.py index 5d3611b1..09d3cdd5 100644 --- a/music_assistant/controllers/streams/streams_controller.py +++ b/music_assistant/controllers/streams/streams_controller.py @@ -518,6 +518,9 @@ class StreamsController(CoreController): queue_item=queue_item, pcm_format=pcm_format, seek_position=queue_item.streamdetails.seek_position, + playback_speed=cast( + "float", queue_item.extra_attributes.get("playback_speed", 1.0) + ), ) # stream the audio # this final ffmpeg process in the chain will convert the raw, lossless PCM audio into @@ -934,6 +937,9 @@ class StreamsController(CoreController): self.get_queue_item_stream( queue_item=queue_item, pcm_format=pcm_format, + playback_speed=cast( + "float", queue_item.extra_attributes.get("playback_speed", 1.0) + ), ), buffer_size=10, min_buffer_before_yield=2, @@ -1053,6 +1059,9 @@ class StreamsController(CoreController): queue_track, pcm_format=pcm_format, seek_position=queue_track.streamdetails.seek_position, + playback_speed=cast( + "float", queue_track.extra_attributes.get("playback_speed", 1.0) + ), raise_on_error=False, ): total_chunks_received += 1 @@ -1356,6 +1365,7 @@ class StreamsController(CoreController): queue_item: QueueItem, pcm_format: AudioFormat, seek_position: int = 0, + playback_speed: float = 1.0, raise_on_error: bool = True, ) -> AsyncGenerator[bytes, None]: """Get the (PCM) audio stream for a single queue item.""" @@ -1403,6 +1413,10 @@ class StreamsController(CoreController): filter_params.append(f"volume={gain_correct}dB") streamdetails.volume_normalization_gain_correct = gain_correct + # handle playback speed + if playback_speed != 1.0: + filter_params.append(f"atempo={playback_speed}") + allow_buffer = bool( self.mass.config.get_raw_core_config_value( self.domain, CONF_ALLOW_BUFFER, CONF_ALLOW_BUFFER_DEFAULT @@ -1611,7 +1625,10 @@ class StreamsController(CoreController): total_chunks_received = 0 req_buffer_size = crossfade_buffer_size async for chunk in self.get_queue_item_stream( - queue_item, pcm_format, seek_position=discard_seconds + queue_item, + pcm_format, + seek_position=discard_seconds, + playback_speed=cast("float", queue_item.extra_attributes.get("playback_speed", 1.0)), ): total_chunks_received += 1 if discard_leftover: @@ -1768,7 +1785,11 @@ class StreamsController(CoreController): buffer = b"" try: async for chunk in self.get_queue_item_stream( - next_queue_item, next_queue_item_pcm_format + next_queue_item, + next_queue_item_pcm_format, + playback_speed=cast( + "float", queue_item.extra_attributes.get("playback_speed", 1.0) + ), ): # append to buffer until we reach crossfade size # we only need the first X seconds of the NEXT track so we can diff --git a/music_assistant/helpers/audio.py b/music_assistant/helpers/audio.py index e44f1d02..2dd60995 100644 --- a/music_assistant/helpers/audio.py +++ b/music_assistant/helpers/audio.py @@ -1465,12 +1465,6 @@ def get_player_filter_params( elif conf_channels == "right": filter_params.append("pan=mono|c0=FR") - # Apply playback speed via atempo filter if a non-default speed is set on the queue. - if _speed_queue := mass.player_queues.get_active_queue(player_id): - speed = float(_speed_queue.extra_attributes.get("playback_speed") or 1.0) - if speed != 1.0: - filter_params.append(f"atempo={speed:.4f}") - # Add safety limiter at the end if limiter_enabled: filter_params.append("alimiter=limit=-2dB:level=false:asc=true")