chore(02-03): delete lightsync/audio/engine.py (MPV engine removed)
- Browser plays audio now; server-side MPV engine is no longer used - Beat detection via librosa replaces position-polling engine
This commit is contained in:
@@ -1,113 +0,0 @@
|
||||
"""MPV-based audio engine — headless playback with 10Hz position polling."""
|
||||
import ctypes.util as _ctypes_util
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
# Alpine/musl: ctypes.util.find_library can't discover libmpv without ldconfig or objdump.
|
||||
# Patch it to return the known path so python-mpv can load the library.
|
||||
_orig_find_library = _ctypes_util.find_library
|
||||
def _patched_find_library(name: str) -> str | None:
|
||||
if name == "mpv":
|
||||
return "/usr/lib/libmpv.so.2"
|
||||
return _orig_find_library(name)
|
||||
_ctypes_util.find_library = _patched_find_library
|
||||
|
||||
import mpv # noqa: E402
|
||||
|
||||
_ctypes_util.find_library = _orig_find_library # restore after mpv loaded
|
||||
|
||||
|
||||
def apply_timer_fix() -> None:
|
||||
"""Apply 1ms timer resolution on Windows. No-op on Linux/macOS (INF-03)."""
|
||||
if sys.platform == "win32":
|
||||
import ctypes
|
||||
winmm = ctypes.WinDLL("winmm")
|
||||
winmm.timeBeginPeriod(1)
|
||||
|
||||
|
||||
class MPVEngine:
|
||||
"""Wraps libmpv for headless audio playback with position polling.
|
||||
|
||||
Usage:
|
||||
engine = MPVEngine()
|
||||
engine.start() # starts 10Hz polling thread
|
||||
engine.load("/path/to/song.mp3")
|
||||
engine.play()
|
||||
pos = engine.get_state() # {"position": 42.1, "paused": False, ...}
|
||||
engine.stop()
|
||||
"""
|
||||
|
||||
def __init__(self, ao: str = "null"):
|
||||
self._player: mpv.MPV = mpv.MPV(
|
||||
vo="null",
|
||||
ao=ao,
|
||||
input_default_bindings=False,
|
||||
input_vo_keyboard=False,
|
||||
)
|
||||
self._position: float = 0.0
|
||||
self._duration: float | None = None
|
||||
self._loaded_path: str | None = None
|
||||
self._lock = threading.Lock()
|
||||
self._running = False
|
||||
self._thread: threading.Thread | None = None
|
||||
|
||||
def start(self) -> None:
|
||||
"""Start the 10Hz position polling thread."""
|
||||
apply_timer_fix()
|
||||
self._running = True
|
||||
self._thread = threading.Thread(target=self._poll_loop, daemon=True)
|
||||
self._thread.start()
|
||||
|
||||
def _poll_loop(self) -> None:
|
||||
"""Poll time-pos at 10Hz. Do NOT use observe_property — fires per audio frame."""
|
||||
while self._running:
|
||||
pos = self._player.time_pos
|
||||
dur = self._player.duration
|
||||
with self._lock:
|
||||
self._position = pos if pos is not None else 0.0
|
||||
if dur is not None:
|
||||
self._duration = dur
|
||||
time.sleep(0.1)
|
||||
|
||||
def get_state(self) -> dict[str, Any]:
|
||||
"""Thread-safe snapshot of current playback state."""
|
||||
with self._lock:
|
||||
return {
|
||||
"position": self._position,
|
||||
"paused": bool(self._player.pause),
|
||||
"duration": self._duration,
|
||||
"loaded": self._loaded_path is not None,
|
||||
"file": self._loaded_path,
|
||||
}
|
||||
|
||||
def load(self, path: str) -> None:
|
||||
"""Load an audio file. Starts paused. Supports MP3, WAV, FLAC, OGG (AUD-01)."""
|
||||
self._loaded_path = path
|
||||
self._player.loadfile(path, mode="replace")
|
||||
# loadfile is async in mpv — file loads in background
|
||||
# pause=True will take effect once mpv has loaded the file
|
||||
self._player.pause = True
|
||||
|
||||
def play(self) -> None:
|
||||
"""Resume playback (AUD-03)."""
|
||||
self._player.pause = False
|
||||
|
||||
def pause(self) -> None:
|
||||
"""Pause playback (AUD-03)."""
|
||||
self._player.pause = True
|
||||
|
||||
def seek(self, seconds: float) -> None:
|
||||
"""Seek to absolute position in seconds (AUD-03)."""
|
||||
self._player.seek(seconds, reference="absolute")
|
||||
|
||||
def stop(self) -> None:
|
||||
"""Stop polling and terminate mpv."""
|
||||
self._running = False
|
||||
if self._thread:
|
||||
self._thread.join(timeout=2.0)
|
||||
try:
|
||||
self._player.terminate()
|
||||
except Exception:
|
||||
pass
|
||||
Reference in New Issue
Block a user