From 1f1ff924462b131ec56d355a96b059a64c426c00 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Apr 2026 15:37:10 +0000 Subject: [PATCH] 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 --- lightsync/audio/engine.py | 113 -------------------------------------- 1 file changed, 113 deletions(-) delete mode 100644 lightsync/audio/engine.py diff --git a/lightsync/audio/engine.py b/lightsync/audio/engine.py deleted file mode 100644 index 056515f..0000000 --- a/lightsync/audio/engine.py +++ /dev/null @@ -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