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.
This commit is contained in:
Philipp Grafe committed 2026-02-10 14:27:22 +01:00
1 parent c5032a0605
commit b2d4ec1413
2 files changed
+28 -8

No files matched your search

+9
View File
@@ -39,6 +39,15 @@ class MpvMediaPlayer:
self._log.debug("Received URL list, using first entry") self._log.debug("Received URL list, using first entry")
url = url[0] 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.info("Playing media: %s", url)
self._log.debug( self._log.debug(
"play(url=%s, stop_first=%s, done_callback=%s)", "play(url=%s, stop_first=%s, done_callback=%s)",
+19 -8
View File
@@ -25,8 +25,6 @@ class LibMpvPlayer(AudioPlayer):
self._user_volume: float = 100.0 # 0.0 – 100.0 self._user_volume: float = 100.0 # 0.0 – 100.0
self._duck_factor: float = 1.0 # 0.0 – 1.0 self._duck_factor: float = 1.0 # 0.0 – 1.0
self._done_callback: Optional[Callable[[], None]] = None
# mpv setup # mpv setup
self._mpv = mpv.MPV( self._mpv = mpv.MPV(
audio_display=False, audio_display=False,
@@ -37,6 +35,9 @@ class LibMpvPlayer(AudioPlayer):
if device: if device:
self._mpv["audio-device"] = 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) self._mpv.event_callback("end-file")(self._on_end_file)
# -------- Playback control -------- # -------- Playback control --------
@@ -55,11 +56,10 @@ class LibMpvPlayer(AudioPlayer):
done_callback: Optional callback invoked when playback finishes. done_callback: Optional callback invoked when playback finishes.
stop_first: If True, start playback in paused state. stop_first: If True, start playback in paused state.
""" """
with self._state_lock:
self._set_state(PlayerState.LOADING)
self._done_callback = done_callback self._done_callback = done_callback
self._mpv.pause = stop_first self._mpv.pause = stop_first
with self._state_lock:
self._set_state(PlayerState.LOADING)
self._mpv.play(url) self._mpv.play(url)
def pause(self) -> None: def pause(self) -> None:
@@ -74,11 +74,18 @@ class LibMpvPlayer(AudioPlayer):
self._mpv.pause = False self._mpv.pause = False
self._set_state(PlayerState.PLAYING) self._set_state(PlayerState.PLAYING)
def stop(self) -> None: def stop(self, for_replacement: bool = False) -> None:
"""Stop playback and reset state to IDLE.""" """
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: with self._state_lock:
if for_replacement:
self._suppress_end_event = True
self._mpv.stop() self._mpv.stop()
self._set_state(PlayerState.IDLE)
def state(self) -> PlayerState: def state(self) -> PlayerState:
"""Return the current player state.""" """Return the current player state."""
@@ -126,6 +133,10 @@ class LibMpvPlayer(AudioPlayer):
callback: Optional[Callable[[], None]] = None callback: Optional[Callable[[], None]] = None
with self._state_lock: with self._state_lock:
if self._suppress_end_event:
self._suppress_end_event = False
return
self._set_state(PlayerState.IDLE) self._set_state(PlayerState.IDLE)
callback = self._done_callback callback = self._done_callback
self._done_callback = None self._done_callback = None