Files
linux-voice-assistant/linux_voice_assistant/mpv_player.py
T
Philipp Grafe b2d4ec1413 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.
2026-02-10 14:27:22 +01:00

108 lines
3.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# mpv_player.py
import logging
from typing import Union, List, Callable, Optional
from .player.libmpv import LibMpvPlayer
class MpvMediaPlayer:
"""
Linux Voice Assistant MediaPlayer implementation based on libmpv.
This class provides the MediaPlayer interface expected by LVA and
delegates all playback logic to LibMpvPlayer.
"""
def __init__(self, device: str | None = None) -> None:
self._log = logging.getLogger(self.__class__.__name__)
self._player = LibMpvPlayer(device=device)
self._done_callback: Optional[Callable[[], None]] = None
self._log.debug("MpvMediaPlayer initialized (device=%s)", device)
def play(
self,
url: Union[str, List[str]],
done_callback: Optional[Callable[[], None]] = None,
stop_first: bool = False,
) -> None:
"""
Play a media URL.
Args:
url: Media URL or list of URLs (LVA currently uses a single URL).
done_callback: Optional callback invoked when playback finishes.
stop_first: Kept for API compatibility.
"""
# LVA currently only uses single URLs
if isinstance(url, list):
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)",
url,
stop_first,
bool(done_callback),
)
self._done_callback = done_callback
self._player.play(url, done_callback=done_callback, stop_first=stop_first)
def pause(self) -> None:
"""Pause playback."""
self._log.debug("pause() called")
self._player.pause()
def resume(self) -> None:
"""Resume playback."""
self._log.debug("resume() called")
self._player.resume()
def stop(self) -> None:
"""Stop playback and invoke the done callback if present."""
self._log.debug("stop() called")
self._player.stop()
if self._done_callback:
self._log.debug("Invoking done_callback due to stop()")
try:
self._done_callback()
finally:
self._done_callback = None
def set_volume(self, volume: float) -> None:
"""
Set playback volume.
Args:
volume: Volume in percent (0.0–100.0).
"""
self._log.debug("set_volume(volume=%.2f)", volume)
self._player.set_volume(volume)
def duck(self, factor: float = 0.5) -> None:
"""
Temporarily reduce volume.
Args:
factor: Volume multiplier (0.0–1.0).
"""
self._log.debug("duck(factor=%.2f)", factor)
self._player.duck(factor)
def unduck(self) -> None:
"""Restore volume after ducking."""
self._log.debug("unduck() called")
self._player.unduck()