From b2d4ec1413c55b1477388e4ffb94893cd6af2680 Mon Sep 17 00:00:00 2001 From: Philipp Grafe Date: Tue, 10 Feb 2026 14:27:22 +0100 Subject: [PATCH] Fix mpv pause state when loading new media Prevent IDLE state and done callback on track replacement mpv emits an end-file event when playback is stopped. During track replacement this caused the media player to transition to IDLE and notify Home Assistant even though a new track was already playing. Introduce a suppression flag to distinguish track replacement from natural playback end and avoid incorrect state transitions. --- linux_voice_assistant/mpv_player.py | 9 +++++++++ linux_voice_assistant/player/libmpv.py | 27 ++++++++++++++++++-------- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/linux_voice_assistant/mpv_player.py b/linux_voice_assistant/mpv_player.py index 0add1c3..3725e52 100644 --- a/linux_voice_assistant/mpv_player.py +++ b/linux_voice_assistant/mpv_player.py @@ -39,6 +39,15 @@ class MpvMediaPlayer: self._log.debug("Received URL list, using first entry") url = url[0] + # Track is changing + if self._done_callback is not None: + self._log.debug( + "Stopping active playback before starting new media" + ) + # Not self.stop() → this would call done_callback + self._player.stop(for_replacement=True) + self._done_callback = None + self._log.info("Playing media: %s", url) self._log.debug( "play(url=%s, stop_first=%s, done_callback=%s)", diff --git a/linux_voice_assistant/player/libmpv.py b/linux_voice_assistant/player/libmpv.py index 855a7b7..4c7311c 100644 --- a/linux_voice_assistant/player/libmpv.py +++ b/linux_voice_assistant/player/libmpv.py @@ -25,8 +25,6 @@ class LibMpvPlayer(AudioPlayer): self._user_volume: float = 100.0 # 0.0 – 100.0 self._duck_factor: float = 1.0 # 0.0 – 1.0 - self._done_callback: Optional[Callable[[], None]] = None - # mpv setup self._mpv = mpv.MPV( audio_display=False, @@ -37,6 +35,9 @@ class LibMpvPlayer(AudioPlayer): if device: self._mpv["audio-device"] = device + # Callback Handling + self._done_callback: Optional[Callable[[], None]] = None + self._suppress_end_event = False self._mpv.event_callback("end-file")(self._on_end_file) # -------- Playback control -------- @@ -55,11 +56,10 @@ class LibMpvPlayer(AudioPlayer): done_callback: Optional callback invoked when playback finishes. stop_first: If True, start playback in paused state. """ - with self._state_lock: - self._set_state(PlayerState.LOADING) - self._done_callback = done_callback self._mpv.pause = stop_first + with self._state_lock: + self._set_state(PlayerState.LOADING) self._mpv.play(url) def pause(self) -> None: @@ -74,11 +74,18 @@ class LibMpvPlayer(AudioPlayer): self._mpv.pause = False self._set_state(PlayerState.PLAYING) - def stop(self) -> None: - """Stop playback and reset state to IDLE.""" + def stop(self, for_replacement: bool = False) -> None: + """ + Stop playback. + + If called for track replacement, suppresses end-of-playback handling + (state transition to IDLE and done callback) to allow seamless + playback transitions. + """ with self._state_lock: + if for_replacement: + self._suppress_end_event = True self._mpv.stop() - self._set_state(PlayerState.IDLE) def state(self) -> PlayerState: """Return the current player state.""" @@ -126,6 +133,10 @@ class LibMpvPlayer(AudioPlayer): callback: Optional[Callable[[], None]] = None with self._state_lock: + if self._suppress_end_event: + self._suppress_end_event = False + return + self._set_state(PlayerState.IDLE) callback = self._done_callback self._done_callback = None