155 lines
3.7 KiB
Python
155 lines
3.7 KiB
Python
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
|