# import os import threading import vlc # type: ignore # from config import Config from helpers import file_is_unreadable from typing import Optional from time import sleep from log import log from PyQt6.QtCore import ( # type: ignore QRunnable, QThreadPool, ) lock = threading.Lock() class FadeTrack(QRunnable): def __init__(self, player: vlc.MediaPlayer) -> None: super().__init__() self.player = player def run(self): """ Implementation of fading the player """ if not self.player: return fade_time = Config.FADE_TIME / 1000 steps = Config.FADE_STEPS sleep_time = fade_time / steps original_volume = self.player.audio_get_volume() # We reduce volume by one mesure first, then by two measures, # then three, and so on. # The sum of the arithmetic sequence 1, 2, 3, ..n is # (n**2 + n) / 2 total_measures_count = (steps**2 + steps) / 2 measures_to_reduce_by = 0 for i in range(1, steps + 1): measures_to_reduce_by += i volume_factor = 1 - (measures_to_reduce_by / total_measures_count) self.player.audio_set_volume(int(original_volume * volume_factor)) sleep(sleep_time) self.player.stop() log.debug(f"Releasing player {self.player=}") self.player.release() class Music: """ Manage the playing of music tracks """ def __init__(self) -> None: self.VLC = vlc.Instance() self.player = None self.max_volume = Config.VOLUME_VLC_DEFAULT def fade(self) -> None: """ Fade the currently playing track. The actual management of fading runs in its own thread so as not to hold up the UI during the fade. """ if not self.player: return if not self.player.get_position() > 0 and self.player.is_playing(): return # Take a copy of current player to allow another track to be # started without interfering here with lock: p = self.player self.player = None pool = QThreadPool.globalInstance() fader = FadeTrack(p) pool.start(fader) def get_position(self) -> Optional[float]: """Return current position""" if not self.player: return None return self.player.get_position() def play(self, path: str, position: Optional[float] = None) -> Optional[int]: """ Start playing the track at path. Log and return if path not found. """ if file_is_unreadable(path): log.error(f"play({path}): path not readable") return None status = -1 media = self.VLC.media_new_path(path) self.player = media.player_new_from_media() if self.player: status = self.player.play() self.set_volume(self.max_volume) if position: self.player.set_position(position) return status def set_volume(self, volume=None, set_default=True): """Set maximum volume used for player""" if not self.player: return if set_default: self.max_volume = volume if volume is None: volume = Config.VOLUME_VLC_DEFAULT self.player.audio_set_volume(volume) def stop(self) -> float: """Immediately stop playing""" if not self.player: return 0.0 p = self.player self.player = None with lock: position = p.get_position() p.stop() p.release() p = None return position