I gang
This commit is contained in:
168
player/player.py
Normal file
168
player/player.py
Normal file
@@ -0,0 +1,168 @@
|
||||
"""
|
||||
player.py — VLC-baseret afspiller med PyQt6 signals.
|
||||
|
||||
Sender signals til GUI:
|
||||
position_changed(float) — 0.0–1.0 progress
|
||||
time_changed(int, int) — (current_sec, total_sec)
|
||||
levels_changed(float, float) — VU-meter L/R 0.0–1.0
|
||||
song_ended() — sang færdig
|
||||
state_changed(str) — 'playing'|'paused'|'stopped'
|
||||
"""
|
||||
|
||||
from PyQt6.QtCore import QObject, pyqtSignal, QTimer
|
||||
import random
|
||||
|
||||
try:
|
||||
import vlc
|
||||
VLC_AVAILABLE = True
|
||||
except ImportError:
|
||||
VLC_AVAILABLE = False
|
||||
print("Advarsel: python-vlc ikke installeret — afspilning deaktiveret")
|
||||
|
||||
|
||||
class Player(QObject):
|
||||
position_changed = pyqtSignal(float)
|
||||
time_changed = pyqtSignal(int, int)
|
||||
levels_changed = pyqtSignal(float, float)
|
||||
song_ended = pyqtSignal()
|
||||
state_changed = pyqtSignal(str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._path: str | None = None
|
||||
self._duration: int = 0
|
||||
self._demo_mode = False
|
||||
self._demo_stop_sec = 10
|
||||
self._volume = 78
|
||||
|
||||
if VLC_AVAILABLE:
|
||||
self._instance = vlc.Instance("--no-video", "--quiet")
|
||||
self._media_player = self._instance.media_player_new()
|
||||
self._events = self._media_player.event_manager()
|
||||
self._events.event_attach(
|
||||
vlc.EventType.MediaPlayerEndReached,
|
||||
self._on_end_reached,
|
||||
)
|
||||
else:
|
||||
self._media_player = None
|
||||
|
||||
# Timer til polling af position + VU-simulation
|
||||
self._poll_timer = QTimer(self)
|
||||
self._poll_timer.setInterval(80)
|
||||
self._poll_timer.timeout.connect(self._poll)
|
||||
|
||||
# ── Indlæsning ────────────────────────────────────────────────────────────
|
||||
|
||||
def load(self, path: str, duration_sec: int = 0):
|
||||
"""Indlæs en lydfil uden at starte afspilning."""
|
||||
self._path = path
|
||||
self._duration = duration_sec
|
||||
self._demo_mode = False
|
||||
|
||||
if VLC_AVAILABLE and self._media_player:
|
||||
media = self._instance.media_new(path)
|
||||
self._media_player.set_media(media)
|
||||
self._media_player.audio_set_volume(self._volume)
|
||||
|
||||
self.position_changed.emit(0.0)
|
||||
self.time_changed.emit(0, self._duration)
|
||||
self.state_changed.emit("stopped")
|
||||
|
||||
# ── Transport ─────────────────────────────────────────────────────────────
|
||||
|
||||
def play(self):
|
||||
self._demo_mode = False
|
||||
if VLC_AVAILABLE and self._media_player:
|
||||
self._media_player.play()
|
||||
self._poll_timer.start()
|
||||
self.state_changed.emit("playing")
|
||||
|
||||
def play_demo(self, stop_at_sec: int = 10):
|
||||
"""Afspil fra start og stop automatisk ved stop_at_sec."""
|
||||
self._demo_mode = True
|
||||
self._demo_stop_sec = stop_at_sec
|
||||
if VLC_AVAILABLE and self._media_player:
|
||||
self._media_player.set_time(0)
|
||||
self._media_player.play()
|
||||
self._poll_timer.start()
|
||||
self.state_changed.emit("playing")
|
||||
|
||||
def pause(self):
|
||||
if VLC_AVAILABLE and self._media_player:
|
||||
self._media_player.pause()
|
||||
self.state_changed.emit("paused")
|
||||
|
||||
def stop(self):
|
||||
self._demo_mode = False
|
||||
if VLC_AVAILABLE and self._media_player:
|
||||
self._media_player.stop()
|
||||
self._poll_timer.stop()
|
||||
self.position_changed.emit(0.0)
|
||||
self.time_changed.emit(0, self._duration)
|
||||
self.state_changed.emit("stopped")
|
||||
|
||||
def is_playing(self) -> bool:
|
||||
if VLC_AVAILABLE and self._media_player:
|
||||
return self._media_player.is_playing()
|
||||
return False
|
||||
|
||||
def set_volume(self, volume: int):
|
||||
"""0–100"""
|
||||
self._volume = volume
|
||||
if VLC_AVAILABLE and self._media_player:
|
||||
self._media_player.audio_set_volume(volume)
|
||||
|
||||
def set_position(self, fraction: float):
|
||||
"""Søg til position 0.0–1.0"""
|
||||
if VLC_AVAILABLE and self._media_player:
|
||||
self._media_player.set_position(fraction)
|
||||
|
||||
# ── Intern polling ────────────────────────────────────────────────────────
|
||||
|
||||
def _poll(self):
|
||||
"""Køres ~12 gange per sekund — opdaterer position og VU-meter."""
|
||||
if VLC_AVAILABLE and self._media_player:
|
||||
pos = self._media_player.get_position()
|
||||
ms = self._media_player.get_time()
|
||||
cur = max(0, ms // 1000)
|
||||
else:
|
||||
# Simuleret tilstand (til UI-test uden VLC)
|
||||
pos = getattr(self, "_sim_pos", 0.0)
|
||||
self._sim_pos = min(1.0, pos + 0.001)
|
||||
cur = int(self._sim_pos * self._duration)
|
||||
pos = self._sim_pos
|
||||
if self._sim_pos >= 1.0:
|
||||
self._on_end_reached(None)
|
||||
return
|
||||
|
||||
self.position_changed.emit(pos)
|
||||
self.time_changed.emit(cur, self._duration)
|
||||
|
||||
# Demo-stop
|
||||
if self._demo_mode and cur >= self._demo_stop_sec:
|
||||
self.stop()
|
||||
self._demo_mode = False
|
||||
self.position_changed.emit(0.0)
|
||||
self.time_changed.emit(0, self._duration)
|
||||
self.state_changed.emit("demo_ended")
|
||||
return
|
||||
|
||||
# VU-meter: brug VLC's audio-amplitude hvis tilgængelig, ellers simulér
|
||||
if VLC_AVAILABLE and self._media_player and self._media_player.is_playing():
|
||||
# VLC eksponerer ikke amplitude direkte — vi bruger en blød simulation
|
||||
# der er baseret på position så det ser organisk ud
|
||||
base = 0.55 + 0.3 * abs(pos - 0.5)
|
||||
l = min(1.0, base + random.gauss(0, 0.12))
|
||||
r = min(1.0, base + random.gauss(0, 0.12))
|
||||
else:
|
||||
l = r = 0.0
|
||||
|
||||
self.levels_changed.emit(max(0.0, l), max(0.0, r))
|
||||
|
||||
def _on_end_reached(self, event):
|
||||
"""Kaldes fra VLC's event-tråd — send signal til GUI via Qt."""
|
||||
self._poll_timer.stop()
|
||||
# Qt-safe: brug QTimer.singleShot til at sende signal i main thread
|
||||
from PyQt6.QtCore import QTimer as _QTimer
|
||||
_QTimer.singleShot(0, self.song_ended.emit)
|
||||
_QTimer.singleShot(0, lambda: self.state_changed.emit("stopped"))
|
||||
Reference in New Issue
Block a user