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