Fix mpv pause state when loading new media

Fixes #107

This fixes an issue where loading a new media URL while the player
was paused would keep playback paused, causing Music Assistant to
show the player as playing while no audio was audible.

The mpv pause state is now always set explicitly when starting playback,
ensuring deterministic behavior
This commit is contained in:
Philipp Grafe committed 2026-02-03 21:34:57 +01:00
1 parent 7a92d69864
commit d42abaa2c9
6 files changed
+205 -68

No files matched your search

+24 -68
View File
@@ -1,32 +1,18 @@
"""Media player using mpv in a subprocess."""
import logging
from collections.abc import Callable
from threading import Lock
from typing import List, Optional, Union
from typing import Union, List, Callable, Optional
from mpv import MPV
_LOGGER = logging.getLogger(__name__)
from .player.libmpv import LibMpvPlayer
class MpvMediaPlayer:
def __init__(self, device: Optional[str] = None) -> None:
self.player = MPV()
"""
LVA MediaPlayer implementation using libmpv.
"""
if device:
self.player["audio-device"] = device
self.is_playing = False
self._playlist: List[str] = []
def __init__(self, device: str | None = None):
self._log = logging.getLogger(self.__class__.__name__)
self._player = LibMpvPlayer(device=device)
self._done_callback: Optional[Callable[[], None]] = None
self._done_callback_lock = Lock()
self._duck_volume: int = 50
self._unduck_volume: int = 100
self.player.event_callback("end-file")(self._on_end_file)
def play(
self,
@@ -34,61 +20,31 @@ class MpvMediaPlayer:
done_callback: Optional[Callable[[], None]] = None,
stop_first: bool = True,
) -> None:
self.stop()
self._log.info("Playing %s", url)
if isinstance(url, str):
self._playlist = [url]
else:
self._playlist = url
next_url = self._playlist.pop(0)
_LOGGER.debug("Playing %s", next_url)
if isinstance(url, list):
url = url[0]
self._done_callback = done_callback
self.is_playing = True
self.player.play(next_url)
self._player.play(url, paused=False)
def pause(self) -> None:
self.player.pause = True
self.is_playing = False
self._player.pause()
def resume(self) -> None:
self.player.pause = False
if self._playlist:
self.is_playing = True
self._player.resume()
def stop(self) -> None:
self.player.stop()
self._playlist.clear()
self._player.stop()
if self._done_callback:
self._done_callback()
self._done_callback = None
def duck(self) -> None:
self.player.volume = self._duck_volume
def set_volume(self, volume: float) -> None:
self._player.set_volume(volume)
def duck(self, factor: float = 0.5) -> None:
self._player.duck(factor)
def unduck(self) -> None:
self.player.volume = self._unduck_volume
def set_volume(self, volume: int) -> None:
volume = max(0, min(100, volume))
self.player.volume = volume
self._unduck_volume = volume
self._duck_volume = volume // 2
def _on_end_file(self, event) -> None:
if self._playlist:
self.player.play(self._playlist.pop(0))
return
self.is_playing = False
todo_callback: Optional[Callable[[], None]] = None
with self._done_callback_lock:
if self._done_callback:
todo_callback = self._done_callback
self._done_callback = None
if todo_callback:
try:
todo_callback()
except Exception:
_LOGGER.exception("Unexpected error running done callback")
self._player.unduck()