Files
linux-voice-assistant/linux_voice_assistant/player/libmpv.py
T
Philipp Grafe d42abaa2c9 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
2026-02-03 21:34:57 +01:00

87 lines
2.5 KiB
Python

import threading
import logging
from .base import AudioPlayer
from .state import PlayerState
import mpv
class LibMpvPlayer(AudioPlayer):
def __init__(self, device: str | None = None):
self._log = logging.getLogger(self.__class__.__name__)
self._state = PlayerState.IDLE
self._state_lock = threading.Lock()
self._last_volume: float = 100.0 # mpv Volume 0-100
self._mpv = mpv.MPV(
audio_display=False,
log_handler=self._on_mpv_log,
loglevel="error",
)
if device:
self._log.info("Using audio device: %s", device)
self._mpv["audio-device"] = device
# -------- Core Methods --------
def play(self, url: str, paused: bool = False) -> None:
with self._state_lock:
self._set_state(PlayerState.LOADING)
self._mpv.pause = paused
self._log.info("Loading media: %s (paused=%s)", url, paused)
self._mpv.play(url)
def pause(self):
with self._state_lock:
self._mpv.pause = True
self._set_state(PlayerState.PAUSED)
def resume(self):
with self._state_lock:
self._mpv.pause = False
self._set_state(PlayerState.PLAYING)
def stop(self):
with self._state_lock:
self._mpv.stop()
self._set_state(PlayerState.IDLE)
def state(self):
with self._state_lock:
return self._state
# -------- Volume / Ducking --------
def set_volume(self, volume: float):
"""
volume: float 0.0-1.0
"""
with self._state_lock:
self._last_volume = volume * 100
self._mpv.volume = self._last_volume
def duck(self, factor: float = 0.5):
with self._state_lock:
self._last_volume = getattr(self, "_last_volume", self._mpv.volume)
self._mpv.volume = self._last_volume * factor
def unduck(self):
with self._state_lock:
self._mpv.volume = getattr(self, "_last_volume", self._mpv.volume)
# -------- Internal Helpers --------
def _on_mpv_log(self, level, prefix, text):
if level in ("error", "fatal"):
self._log.error("[mpv] %s", text.strip())
with self._state_lock:
self._set_state(PlayerState.ERROR)
def _set_state(self, new_state: PlayerState):
if self._state != new_state:
self._log.debug("State %s → %s", self._state.name, new_state.name)
self._state = new_state