Move player functionality into music.py

This commit is contained in:
Keith Edmunds 2024-05-06 15:55:51 +01:00
parent 4a5fe74a9f
commit be0fc27896
3 changed files with 63 additions and 52 deletions

View File

@ -1,18 +1,23 @@
# Standard library imports
import datetime as dt
import threading import threading
from time import sleep
from typing import Optional
# Third party imports
import vlc # type: ignore import vlc # type: ignore
from config import Config # PyQt imports
from helpers import file_is_unreadable
from typing import Optional
from time import sleep
from log import log
from PyQt6.QtCore import ( from PyQt6.QtCore import (
QRunnable, QRunnable,
QThreadPool, QThreadPool,
) )
# App imports
from config import Config
from helpers import file_is_unreadable
from log import log
lock = threading.Lock() lock = threading.Lock()
@ -57,6 +62,7 @@ class Music:
self.VLC = vlc.Instance() self.VLC = vlc.Instance()
self.player = None self.player = None
self.max_volume = Config.VOLUME_VLC_DEFAULT self.max_volume = Config.VOLUME_VLC_DEFAULT
self.start_dt: Optional[dt.datetime] = None
def fade(self, fade_seconds: int = Config.FADEOUT_SECONDS) -> None: def fade(self, fade_seconds: int = Config.FADEOUT_SECONDS) -> None:
""" """
@ -83,6 +89,21 @@ class Music:
pool = QThreadPool.globalInstance() pool = QThreadPool.globalInstance()
fader = FadeTrack(p, fade_seconds=fade_seconds) fader = FadeTrack(p, fade_seconds=fade_seconds)
pool.start(fader) pool.start(fader)
self.start_dt = None
def get_playtime(self) -> int:
"""
Return number of milliseconds current track has been playing or
zero if not playing. The vlc function get_time() only updates 3-4
times a second; this function has much better resolution.
"""
if self.start_dt is None:
return 0
now = dt.datetime.now()
elapsed_seconds = (now - self.start_dt).total_seconds()
return int(elapsed_seconds * 1000)
def get_position(self) -> Optional[float]: def get_position(self) -> Optional[float]:
"""Return current position""" """Return current position"""
@ -91,6 +112,23 @@ class Music:
return None return None
return self.player.get_position() return self.player.get_position()
def is_playing(self) -> bool:
"""Return True if playing"""
# There is a discrete time between starting playing a track and
# player.is_playing() returning True, so assume playing if less
# than Config.PLAY_SETTLE microseconds have passed since
# starting play.
return (
self.player is not None
and self.start_dt is not None
and (
self.player.is_playing()
or (dt.datetime.now() - self.start_dt)
< dt.timedelta(microseconds=Config.PLAY_SETTLE)
)
)
def play(self, path: str, position: Optional[float] = None) -> None: def play(self, path: str, position: Optional[float] = None) -> None:
""" """
Start playing the track at path. Start playing the track at path.
@ -109,8 +147,10 @@ class Music:
if self.player: if self.player:
_ = self.player.play() _ = self.player.play()
self.set_volume(self.max_volume) self.set_volume(self.max_volume)
if position: if position:
self.player.set_position(position) self.player.set_position(position)
self.start_dt = dt.datetime.now()
def set_volume(self, volume=None, set_default=True) -> None: def set_volume(self, volume=None, set_default=True) -> None:
"""Set maximum volume used for player""" """Set maximum volume used for player"""
@ -125,6 +165,18 @@ class Music:
volume = Config.VOLUME_VLC_DEFAULT volume = Config.VOLUME_VLC_DEFAULT
self.player.audio_set_volume(volume) self.player.audio_set_volume(volume)
# Ensure volume correct
# For as-yet unknown reasons. sometimes the volume gets
# reset to zero within 200mS or so of starting play. This
# only happened since moving to Debian 12, which uses
# Pipewire for sound (which may be irrelevant).
for _ in range(3):
current_volume = self.player.audio_get_volume()
if current_volume < volume:
self.player.audio_set_volume(volume)
log.debug(f"Reset from {volume=}")
break
sleep(0.1)
def stop(self) -> float: def stop(self) -> float:
"""Immediately stop playing""" """Immediately stop playing"""
@ -136,6 +188,7 @@ class Music:
p = self.player p = self.player
self.player = None self.player = None
self.start_dt = None
with lock: with lock:
position = p.get_position() position = p.get_position()

View File

@ -781,21 +781,6 @@ class Window(QMainWindow, Ui_MainWindow):
self.stop_playing(fade=True) self.stop_playing(fade=True)
def get_playtime(self) -> int:
"""
Return number of milliseconds current track has been playing or
zero if not playing. The vlc function get_time() only updates 3-4
times a second; this function has much better resolution.
"""
if track_sequence.now.track_id is None or track_sequence.now.start_time is None:
return 0
now = dt.datetime.now()
track_start = track_sequence.now.start_time
elapsed_seconds = (now - track_start).total_seconds()
return int(elapsed_seconds * 1000)
def hide_played(self): def hide_played(self):
"""Toggle hide played tracks""" """Toggle hide played tracks"""
@ -1146,7 +1131,6 @@ class Window(QMainWindow, Ui_MainWindow):
- Clear next track - Clear next track
- Restore volume if -3dB active - Restore volume if -3dB active
- Play (new) current track. - Play (new) current track.
- Ensure 100% volume
- Show closing volume graph - Show closing volume graph
- Notify model - Notify model
- Note that track is now playing - Note that track is now playing
@ -1204,20 +1188,6 @@ class Window(QMainWindow, Ui_MainWindow):
return return
self.music.play(track_sequence.now.path, position) self.music.play(track_sequence.now.path, position)
# Ensure 100% volume
# For as-yet unknown reasons. sometimes the volume gets
# reset to zero within 200mS or so of starting play. This
# only happened since moving to Debian 12, which uses
# Pipewire for sound (which may be irrelevant).
for _ in range(3):
if self.music.player:
volume = self.music.player.audio_get_volume()
if volume < Config.VOLUME_VLC_DEFAULT:
self.music.set_volume()
log.debug(f"Reset from {volume=}")
break
sleep(0.1)
# Show closing volume graph # Show closing volume graph
if track_sequence.now.fade_graph: if track_sequence.now.fade_graph:
track_sequence.now.fade_graph.plot() track_sequence.now.fade_graph.plot()
@ -1693,20 +1663,8 @@ class Window(QMainWindow, Ui_MainWindow):
return return
# If track is playing, update track clocks time and colours # If track is playing, update track clocks time and colours
# There is a discrete time between starting playing a track and if self.music.player and self.music.player.is_playing():
# player.is_playing() returning True, so assume playing if less playtime = self.music.player.get_playtime()
# than Config.PLAY_SETTLE microseconds have passed since
# starting play.
if (
self.music.player
and track_sequence.now.start_time
and (
self.music.player.is_playing()
or (dt.datetime.now() - track_sequence.now.start_time)
< dt.timedelta(microseconds=Config.PLAY_SETTLE)
)
):
playtime = self.get_playtime()
time_to_fade = track_sequence.now.fade_at - playtime time_to_fade = track_sequence.now.fade_at - playtime
time_to_silence = track_sequence.now.silence_at - playtime time_to_silence = track_sequence.now.silence_at - playtime

View File

@ -614,7 +614,7 @@ class PlaylistTab(QTableView):
else: else:
result = self.source_model.get_row_track_path(model_row_number) result = self.source_model.get_row_track_path(model_row_number)
log.info(f"get_selected_row_track_path() returned: {result=}") log.debug(f"get_selected_row_track_path() returned: {result=}")
return result return result
def get_selected_rows(self) -> List[int]: def get_selected_rows(self) -> List[int]: