# 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, fade_seconds) -> None: super().__init__() self.player = player self.fade_seconds = fade_seconds def run(self) -> None: """ Implementation of fading the player """ if not self.player: return # Reduce volume logarithmically total_steps = self.fade_seconds * Config.FADEOUT_STEPS_PER_SECOND db_reduction_per_step = Config.FADEOUT_DB / total_steps reduction_factor_per_step = pow(10, (db_reduction_per_step / 20)) volume = self.player.audio_get_volume() for i in range(1, total_steps + 1): self.player.audio_set_volume( int(volume * pow(reduction_factor_per_step, i)) ) sleep(1 / Config.FADEOUT_STEPS_PER_SECOND) 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, fade_seconds: int = Config.FADEOUT_SECONDS) -> 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, fade_seconds=fade_seconds) 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) -> None: """ 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 media = self.VLC.media_new_path(path) self.player = media.player_new_from_media() if self.player: _ = self.player.play() self.set_volume(self.max_volume) if position: self.player.set_position(position) def set_volume(self, volume=None, set_default=True) -> None: """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