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))
|
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"""
|
"""Convert milliseconds to mm:ss"""
|
||||||
|
|
||||||
if ms is None:
|
minutes: int
|
||||||
return none
|
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:
|
def normalise_track(path: str) -> None:
|
||||||
|
|||||||
@ -53,6 +53,7 @@ class _FadeTrack(QThread):
|
|||||||
)
|
)
|
||||||
sleep(1 / Config.FADEOUT_STEPS_PER_SECOND)
|
sleep(1 / Config.FADEOUT_STEPS_PER_SECOND)
|
||||||
|
|
||||||
|
self.player.stop()
|
||||||
self.finished.emit()
|
self.finished.emit()
|
||||||
|
|
||||||
|
|
||||||
@ -80,9 +81,11 @@ class Music:
|
|||||||
self.vlc_instance = vlc_manager.get_instance()
|
self.vlc_instance = vlc_manager.get_instance()
|
||||||
self.vlc_instance.set_user_agent(name, name)
|
self.vlc_instance.set_user_agent(name, name)
|
||||||
self.player: vlc.MediaPlayer | None = None
|
self.player: vlc.MediaPlayer | None = None
|
||||||
|
self.vlc_event_manager: vlc.EventManager | None = None
|
||||||
self.max_volume: int = Config.VLC_VOLUME_DEFAULT
|
self.max_volume: int = Config.VLC_VOLUME_DEFAULT
|
||||||
self.start_dt: dt.datetime | None = None
|
self.start_dt: dt.datetime | None = None
|
||||||
self.signals = MusicMusterSignals()
|
self.signals = MusicMusterSignals()
|
||||||
|
self.end_of_track_signalled = False
|
||||||
|
|
||||||
def fade(self, fade_seconds: int) -> None:
|
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():
|
if not self.player.get_position() > 0 and self.player.is_playing():
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self.signal_track_ended()
|
||||||
|
|
||||||
self.fader_worker = _FadeTrack(self.player, fade_seconds=fade_seconds)
|
self.fader_worker = _FadeTrack(self.player, fade_seconds=fade_seconds)
|
||||||
self.fader_worker.finished.connect(self.player.release)
|
self.fader_worker.finished.connect(self.player.release)
|
||||||
self.fader_worker.start()
|
self.fader_worker.start()
|
||||||
@ -142,10 +147,12 @@ class Music:
|
|||||||
< dt.timedelta(microseconds=Config.PLAY_SETTLE)
|
< dt.timedelta(microseconds=Config.PLAY_SETTLE)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# @log_call
|
||||||
def play(
|
def play(
|
||||||
self,
|
self,
|
||||||
path: str,
|
path: str,
|
||||||
start_time: dt.datetime,
|
start_time: dt.datetime,
|
||||||
|
playlist_id: int,
|
||||||
position: float | None = None,
|
position: float | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
@ -157,7 +164,7 @@ class Music:
|
|||||||
the start time is the same
|
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):
|
if helpers.file_is_unreadable(path):
|
||||||
log.error(f"play({path}): path not readable")
|
log.error(f"play({path}): path not readable")
|
||||||
@ -171,6 +178,10 @@ class Music:
|
|||||||
)
|
)
|
||||||
return
|
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.player.play()
|
||||||
self.set_volume(self.max_volume)
|
self.set_volume(self.max_volume)
|
||||||
|
|
||||||
@ -213,6 +224,21 @@ class Music:
|
|||||||
log.debug(f"Reset from {volume=}")
|
log.debug(f"Reset from {volume=}")
|
||||||
sleep(0.1)
|
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:
|
def stop(self) -> None:
|
||||||
"""Immediately stop playing"""
|
"""Immediately stop playing"""
|
||||||
|
|
||||||
@ -227,3 +253,11 @@ class Music:
|
|||||||
self.player.stop()
|
self.player.stop()
|
||||||
self.player.release()
|
self.player.release()
|
||||||
self.player = None
|
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:
|
if self.track_sequence.current:
|
||||||
try:
|
try:
|
||||||
self.track_sequence.current.check_for_end_of_track()
|
|
||||||
|
|
||||||
# Update intro counter if applicable and, if updated,
|
# Update intro counter if applicable and, if updated,
|
||||||
# return because playing an intro uses the intro field to
|
# return because playing an intro uses the intro field to
|
||||||
# show timing and this takes precedence over timing a
|
# 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
|
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
|
# only invalidate required roles
|
||||||
roles = [
|
roles = [
|
||||||
@ -1349,13 +1349,13 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
plr = self.playlist_rows[row_number]
|
plr = self.playlist_rows[row_number]
|
||||||
|
|
||||||
if column == Col.NOTE.value:
|
if column == Col.NOTE.value:
|
||||||
plr.note = value
|
plr.note = str(value)
|
||||||
|
|
||||||
elif column == Col.TITLE.value:
|
elif column == Col.TITLE.value:
|
||||||
plr.title = value
|
plr.title = str(value)
|
||||||
|
|
||||||
elif column == Col.ARTIST.value:
|
elif column == Col.ARTIST.value:
|
||||||
plr.artist = value
|
plr.artist = str(value)
|
||||||
|
|
||||||
elif column == Col.INTRO.value:
|
elif column == Col.INTRO.value:
|
||||||
intro = int(round(float(value), 1) * 1000)
|
intro = int(round(float(value), 1) * 1000)
|
||||||
|
|||||||
@ -103,7 +103,7 @@ class PlaylistRow:
|
|||||||
@property
|
@property
|
||||||
def intro(self) -> int:
|
def intro(self) -> int:
|
||||||
if self.dto.track:
|
if self.dto.track:
|
||||||
return self.dto.track.intro
|
return self.dto.track.intro or 0
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@ -531,12 +531,17 @@ class TrackSequence:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if self.current is None:
|
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
|
# 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.previous = self.current
|
||||||
self.current = None
|
self.current = None
|
||||||
|
self.start_time = None
|
||||||
|
|
||||||
def move_previous_to_next(self) -> None:
|
def move_previous_to_next(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user