84 lines
2.7 KiB
Python
84 lines
2.7 KiB
Python
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()
|