Use vlc events to trigger end-of-track actions
This commit is contained in:
parent
edd8c36c53
commit
223c7cd3ab
@ -270,15 +270,39 @@ def leading_silence(
|
||||
return min(trim_ms, len(audio_segment))
|
||||
|
||||
|
||||
def ms_to_mmss(ms: int | None, none: str = "-") -> str:
|
||||
def ms_to_mmss(
|
||||
ms: Optional[int],
|
||||
decimals: int = 0,
|
||||
negative: bool = False,
|
||||
none: Optional[str] = None,
|
||||
) -> str:
|
||||
"""Convert milliseconds to mm:ss"""
|
||||
|
||||
if ms is None:
|
||||
return none
|
||||
minutes: int
|
||||
remainder: int
|
||||
seconds: float
|
||||
|
||||
minutes, seconds = divmod(ms // 1000, 60)
|
||||
if not ms:
|
||||
if none:
|
||||
return none
|
||||
else:
|
||||
return "-"
|
||||
sign = ""
|
||||
if ms < 0:
|
||||
if negative:
|
||||
sign = "-"
|
||||
else:
|
||||
ms = 0
|
||||
|
||||
return f"{minutes}:{seconds:02d}"
|
||||
minutes, remainder = divmod(ms, 60 * 1000)
|
||||
seconds = remainder / 1000
|
||||
|
||||
# if seconds >= 59.5, it will be represented as 60, which looks odd.
|
||||
# So, fake it under those circumstances
|
||||
if seconds >= 59.5:
|
||||
seconds = 59.0
|
||||
|
||||
return f"{sign}{minutes:.0f}:{seconds:02.{decimals}f}"
|
||||
|
||||
|
||||
def normalise_track(path: str) -> None:
|
||||
|
||||
@ -53,6 +53,7 @@ class _FadeTrack(QThread):
|
||||
)
|
||||
sleep(1 / Config.FADEOUT_STEPS_PER_SECOND)
|
||||
|
||||
self.player.stop()
|
||||
self.finished.emit()
|
||||
|
||||
|
||||
@ -80,9 +81,11 @@ class Music:
|
||||
self.vlc_instance = vlc_manager.get_instance()
|
||||
self.vlc_instance.set_user_agent(name, name)
|
||||
self.player: vlc.MediaPlayer | None = None
|
||||
self.vlc_event_manager: vlc.EventManager | None = None
|
||||
self.max_volume: int = Config.VLC_VOLUME_DEFAULT
|
||||
self.start_dt: dt.datetime | None = None
|
||||
self.signals = MusicMusterSignals()
|
||||
self.end_of_track_signalled = False
|
||||
|
||||
def fade(self, fade_seconds: int) -> None:
|
||||
"""
|
||||
@ -98,6 +101,8 @@ class Music:
|
||||
if not self.player.get_position() > 0 and self.player.is_playing():
|
||||
return
|
||||
|
||||
self.signal_track_ended()
|
||||
|
||||
self.fader_worker = _FadeTrack(self.player, fade_seconds=fade_seconds)
|
||||
self.fader_worker.finished.connect(self.player.release)
|
||||
self.fader_worker.start()
|
||||
@ -142,10 +147,12 @@ class Music:
|
||||
< dt.timedelta(microseconds=Config.PLAY_SETTLE)
|
||||
)
|
||||
|
||||
# @log_call
|
||||
def play(
|
||||
self,
|
||||
path: str,
|
||||
start_time: dt.datetime,
|
||||
playlist_id: int,
|
||||
position: float | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
@ -157,7 +164,7 @@ class Music:
|
||||
the start time is the same
|
||||
"""
|
||||
|
||||
log.debug(f"Music[{self.name}].play({path=}, {position=}")
|
||||
self.playlist_id = playlist_id
|
||||
|
||||
if helpers.file_is_unreadable(path):
|
||||
log.error(f"play({path}): path not readable")
|
||||
@ -171,6 +178,10 @@ class Music:
|
||||
)
|
||||
return
|
||||
|
||||
self.events = self.player.event_manager()
|
||||
self.events.event_attach(vlc.EventType.MediaPlayerEndReached, self.track_end_event_handler)
|
||||
self.events.event_attach(vlc.EventType.MediaPlayerStopped, self.track_end_event_handler)
|
||||
|
||||
_ = self.player.play()
|
||||
self.set_volume(self.max_volume)
|
||||
|
||||
@ -213,6 +224,21 @@ class Music:
|
||||
log.debug(f"Reset from {volume=}")
|
||||
sleep(0.1)
|
||||
|
||||
def signal_track_ended(self) -> None:
|
||||
"""
|
||||
Multiple parts of the Music class can signal that the track has
|
||||
ended. Handle them all here to ensure that only one such signal
|
||||
is raised. Make this thead safe.
|
||||
"""
|
||||
|
||||
lock = threading.Lock()
|
||||
|
||||
with lock:
|
||||
if self.end_of_track_signalled:
|
||||
return
|
||||
self.signals.track_ended_signal.emit(self.playlist_id)
|
||||
self.end_of_track_signalled = True
|
||||
|
||||
def stop(self) -> None:
|
||||
"""Immediately stop playing"""
|
||||
|
||||
@ -227,3 +253,11 @@ class Music:
|
||||
self.player.stop()
|
||||
self.player.release()
|
||||
self.player = None
|
||||
|
||||
def track_end_event_handler(self, event: vlc.Event) -> None:
|
||||
"""
|
||||
Handler for MediaPlayerEndReached
|
||||
"""
|
||||
|
||||
log.debug("track_end_event_handler() called")
|
||||
self.signal_track_ended()
|
||||
|
||||
@ -2563,8 +2563,6 @@ class Window(QMainWindow):
|
||||
|
||||
if self.track_sequence.current:
|
||||
try:
|
||||
self.track_sequence.current.check_for_end_of_track()
|
||||
|
||||
# Update intro counter if applicable and, if updated,
|
||||
# return because playing an intro uses the intro field to
|
||||
# show timing and this takes precedence over timing a
|
||||
|
||||
@ -1057,7 +1057,7 @@ class PlaylistModel(QAbstractTableModel):
|
||||
Remove track from row, retaining row as a header row
|
||||
"""
|
||||
|
||||
self.playlist_rows[row_number].track_id = None
|
||||
self.playlist_rows[row_number].track_id = 0
|
||||
|
||||
# only invalidate required roles
|
||||
roles = [
|
||||
@ -1349,13 +1349,13 @@ class PlaylistModel(QAbstractTableModel):
|
||||
plr = self.playlist_rows[row_number]
|
||||
|
||||
if column == Col.NOTE.value:
|
||||
plr.note = value
|
||||
plr.note = str(value)
|
||||
|
||||
elif column == Col.TITLE.value:
|
||||
plr.title = value
|
||||
plr.title = str(value)
|
||||
|
||||
elif column == Col.ARTIST.value:
|
||||
plr.artist = value
|
||||
plr.artist = str(value)
|
||||
|
||||
elif column == Col.INTRO.value:
|
||||
intro = int(round(float(value), 1) * 1000)
|
||||
|
||||
@ -103,7 +103,7 @@ class PlaylistRow:
|
||||
@property
|
||||
def intro(self) -> int:
|
||||
if self.dto.track:
|
||||
return self.dto.track.intro
|
||||
return self.dto.track.intro or 0
|
||||
else:
|
||||
return 0
|
||||
|
||||
@ -531,12 +531,17 @@ class TrackSequence:
|
||||
"""
|
||||
|
||||
if self.current is None:
|
||||
raise ApplicationError("Tried to move non-existent track from current to previous")
|
||||
raise ApplicationError(
|
||||
"Tried to move non-existent track from current to previous"
|
||||
)
|
||||
|
||||
# Dereference the fade curve so it can be garbage collected
|
||||
self.current.fade_graph = None
|
||||
if self.current.fade_graph:
|
||||
self.current.fade_graph.clear()
|
||||
self.current.fade_graph = None
|
||||
self.previous = self.current
|
||||
self.current = None
|
||||
self.start_time = None
|
||||
|
||||
def move_previous_to_next(self) -> None:
|
||||
"""
|
||||
|
||||
Loading…
Reference in New Issue
Block a user