import os import random import threading from mpv import MPV # type: ignore class PersistentPlayer: def __init__(self, name: str, folder: str): self.name = name self.folder = folder self.lock = threading.RLock() self.tracks = [] self.current_index = 0 self.reload_tracks() # LET python-mpv start its own event thread self.player = MPV( ao="jack", jack_name=f"{name}_out", jack_connect="no", keep_open="yes", idle="yes", terminal=False, log_handler=lambda level, prefix, text: print(f"[{self.name}][mpv][{level}] {text}"), loglevel="info" ) @self.player.event_callback("end-file") def _on_end(event): print(f"[{self.name}] ↩ EVENT end-file fired: {event}") self._next_track() print(f"[{self.name}] ▶️ Registered end-file handler") def reload_tracks(self): with self.lock: files = [ os.path.join(self.folder, f) for f in os.listdir(self.folder) if f.lower().endswith(".mp3") ] random.shuffle(files) self.tracks = files if self.tracks: self.current_index %= len(self.tracks) else: self.current_index = 0 print(f"[{self.name}] playlist loaded ({len(self.tracks)} tracks)") def add_track(self, path: str): if not path.lower().endswith(".mp3") or not os.path.isfile(path): return print(f"[{self.name}] invalid mp3: {path}") with self.lock: if path in self.tracks: return print(f"[{self.name}] already in playlist") self.tracks.append(path) print(f"[{self.name}] added: {os.path.basename(path)}") def remove_track(self, path: str): with self.lock: if path not in self.tracks: return print(f"[{self.name}] not in playlist") idx = self.tracks.index(path) if idx < self.current_index: self.current_index -= 1 self.tracks.pop(idx) print(f"[{self.name}] removed: {os.path.basename(path)}") def play_current(self): with self.lock: if not self.tracks: return print(f"[{self.name}] no tracks!") track = self.tracks[self.current_index] print(f"[{self.name}] ▶ Playing: {os.path.basename(track)}") self.player.command("loadfile", track, "replace") def _next_track(self): with self.lock: if not self.tracks: return self.current_index = (self.current_index + 1) % len(self.tracks) self.play_current()