import os import threading import vlc from config import Config from datetime import datetime from time import sleep from log import DEBUG, ERROR class Music: """ Manage the playing of music tracks """ def __init__(self): self.current_track_start_time = None self.fading = False self.VLC = vlc.Instance() self.player = None self.track_path = None self.max_volume = Config.VOLUME_VLC_DEFAULT def fade(self): """ 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. """ DEBUG("music.fade()") if not self.playing(): return None self.fading = True thread = threading.Thread(target=self._fade) thread.start() def _fade(self): """ Implementation of fading the current track in a separate thread. """ DEBUG("music._fade()") fade_time = Config.FADE_TIME / 1000 steps = Config.FADE_STEPS sleep_time = fade_time / steps # 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 # Take a copy of current player to allow another track to be # started without interfering here p = self.player 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) p.audio_set_volume(int(self.max_volume * volume_factor)) sleep(sleep_time) p.stop() p.release() self.fading = False def get_playtime(self): "Return elapsed play time" if not self.player: return None return self.player.get_time() def get_position(self): "Return current position" DEBUG("music.get_position") return self.player.get_position() def play(self, path): """ Start playing the track at path. Log and return if path not found. """ DEBUG(f"music.play({path})") if not os.access(path, os.R_OK): ERROR(f"play({path}): path not found") return self.track_path = path self.player = self.VLC.media_player_new(path) self.player.audio_set_volume(self.max_volume) self.player.play() self.current_track_start_time = datetime.now() def playing(self): """ Return True if currently playing a track, else False vlc.is_playing() returns True if track was faded out. get_position seems more reliable. """ if self.player: if self.player.get_position() > 0 and self.player.is_playing(): return True # We take a copy of the player when fading, so we could be # playing in a fade nowFalse return self.fading def set_position(self, ms): "Set current play time in milliseconds from start" return self.player.set_time(ms) def set_volume(self, volume): "Set maximum volume used for player" if not self.player: return self.max_volume = volume self.player.audio_set_volume(volume) def stop(self): "Immediately stop playing" DEBUG("music.stop()") if not self.player: return None position = self.player.get_position() self.player.stop() self.player.release() # Ensure we don't reference player after release self.player = None return position