WIP: implemented trackmanager, tracks play, clocks work
This commit is contained in:
parent
fbcedb6c3b
commit
5278b124ca
@ -48,6 +48,7 @@ class MusicMusterSignals(QObject):
|
|||||||
show_warning_signal = pyqtSignal(str, str)
|
show_warning_signal = pyqtSignal(str, str)
|
||||||
span_cells_signal = pyqtSignal(int, int, int, int, int)
|
span_cells_signal = pyqtSignal(int, int, int, int, int)
|
||||||
status_message_signal = pyqtSignal(str, int)
|
status_message_signal = pyqtSignal(str, int)
|
||||||
|
track_ended_signal = pyqtSignal()
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@ -47,7 +47,6 @@ from PyQt6.QtWidgets import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Third party imports
|
# Third party imports
|
||||||
# from pygame import mixer
|
|
||||||
import pipeclient
|
import pipeclient
|
||||||
from sqlalchemy.exc import IntegrityError
|
from sqlalchemy.exc import IntegrityError
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
@ -55,11 +54,7 @@ import stackprinter # type: ignore
|
|||||||
|
|
||||||
# App imports
|
# App imports
|
||||||
from classes import (
|
from classes import (
|
||||||
track_sequence,
|
|
||||||
FadeCurve,
|
|
||||||
MusicMusterSignals,
|
MusicMusterSignals,
|
||||||
PlaylistTrack,
|
|
||||||
PreviewTrackPlayer,
|
|
||||||
TrackFileData,
|
TrackFileData,
|
||||||
)
|
)
|
||||||
from config import Config
|
from config import Config
|
||||||
@ -68,6 +63,11 @@ from log import log
|
|||||||
from models import db, Carts, Playdates, PlaylistRows, Playlists, Settings, Tracks
|
from models import db, Carts, Playdates, PlaylistRows, Playlists, Settings, Tracks
|
||||||
from playlistmodel import PlaylistModel, PlaylistProxyModel
|
from playlistmodel import PlaylistModel, PlaylistProxyModel
|
||||||
from playlists import PlaylistTab
|
from playlists import PlaylistTab
|
||||||
|
from trackmanager import (
|
||||||
|
MainTrackManager,
|
||||||
|
PreviewTrackManager,
|
||||||
|
track_sequence,
|
||||||
|
)
|
||||||
from ui import icons_rc # noqa F401
|
from ui import icons_rc # noqa F401
|
||||||
from ui.dlg_cart_ui import Ui_DialogCartEdit # type: ignore
|
from ui.dlg_cart_ui import Ui_DialogCartEdit # type: ignore
|
||||||
from ui.dlg_SelectPlaylist_ui import Ui_dlgSelectPlaylist # type: ignore
|
from ui.dlg_SelectPlaylist_ui import Ui_dlgSelectPlaylist # type: ignore
|
||||||
@ -75,7 +75,6 @@ from ui.downloadcsv_ui import Ui_DateSelect # type: ignore
|
|||||||
from ui.main_window_ui import Ui_MainWindow # type: ignore
|
from ui.main_window_ui import Ui_MainWindow # type: ignore
|
||||||
from utilities import check_db, update_bitrates
|
from utilities import check_db, update_bitrates
|
||||||
import helpers
|
import helpers
|
||||||
import music
|
|
||||||
|
|
||||||
|
|
||||||
class CartButton(QPushButton):
|
class CartButton(QPushButton):
|
||||||
@ -231,10 +230,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
self.timer500: QTimer = QTimer()
|
self.timer500: QTimer = QTimer()
|
||||||
self.timer1000: QTimer = QTimer()
|
self.timer1000: QTimer = QTimer()
|
||||||
|
|
||||||
self.music: music.Music = music.Music(name=Config.VLC_MAIN_PLAYER_NAME)
|
self.preview_track_player: Optional[PreviewTrackManager] = None
|
||||||
self.preview_track_player: Optional[PreviewTrackPlayer] = None
|
|
||||||
|
|
||||||
self.playing: bool = False
|
|
||||||
|
|
||||||
self.set_main_window_size()
|
self.set_main_window_size()
|
||||||
self.lblSumPlaytime = QLabel("")
|
self.lblSumPlaytime = QLabel("")
|
||||||
@ -420,6 +416,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
btn.setEnabled(True)
|
btn.setEnabled(True)
|
||||||
# Setting to position 0 doesn't seem to work
|
# Setting to position 0 doesn't seem to work
|
||||||
btn.player = self.music.VLC.media_player_new(btn.path)
|
btn.player = self.music.VLC.media_player_new(btn.path)
|
||||||
|
MainTrackManager,
|
||||||
btn.player.audio_set_volume(Config.VLC_VOLUME_DEFAULT)
|
btn.player.audio_set_volume(Config.VLC_VOLUME_DEFAULT)
|
||||||
colour = Config.COLOUR_CART_READY
|
colour = Config.COLOUR_CART_READY
|
||||||
btn.setStyleSheet("background-color: " + colour + ";\n")
|
btn.setStyleSheet("background-color: " + colour + ";\n")
|
||||||
@ -449,7 +446,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Don't allow window to close when a track is playing
|
# Don't allow window to close when a track is playing
|
||||||
if self.playing:
|
if track_sequence.current and track_sequence.current.is_playing():
|
||||||
event.ignore()
|
event.ignore()
|
||||||
helpers.show_warning(
|
helpers.show_warning(
|
||||||
self, "Track playing", "Can't close application while track is playing"
|
self, "Track playing", "Can't close application while track is playing"
|
||||||
@ -604,6 +601,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
self.signals.next_track_changed_signal.connect(self.update_headers)
|
self.signals.next_track_changed_signal.connect(self.update_headers)
|
||||||
self.signals.status_message_signal.connect(self.show_status_message)
|
self.signals.status_message_signal.connect(self.show_status_message)
|
||||||
self.signals.show_warning_signal.connect(self.show_warning)
|
self.signals.show_warning_signal.connect(self.show_warning)
|
||||||
|
self.signals.track_ended_signal.connect(self.end_of_track_actions)
|
||||||
|
|
||||||
self.timer10.timeout.connect(self.tick_10ms)
|
self.timer10.timeout.connect(self.tick_10ms)
|
||||||
self.timer500.timeout.connect(self.tick_500ms)
|
self.timer500.timeout.connect(self.tick_500ms)
|
||||||
@ -724,10 +722,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
def drop3db(self) -> None:
|
def drop3db(self) -> None:
|
||||||
"""Drop music level by 3db if button checked"""
|
"""Drop music level by 3db if button checked"""
|
||||||
|
|
||||||
if self.btnDrop3db.isChecked():
|
if track_sequence.current:
|
||||||
self.music.set_volume(Config.VLC_VOLUME_DROP3db, set_default=False)
|
track_sequence.current.drop3db(self.btnDrop3db.isChecked())
|
||||||
else:
|
|
||||||
self.music.set_volume(Config.VLC_VOLUME_DEFAULT, set_default=False)
|
|
||||||
|
|
||||||
def enable_escape(self, enabled: bool) -> None:
|
def enable_escape(self, enabled: bool) -> None:
|
||||||
"""
|
"""
|
||||||
@ -741,6 +737,38 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
|
|
||||||
self.action_Clear_selection.setEnabled(enabled)
|
self.action_Clear_selection.setEnabled(enabled)
|
||||||
|
|
||||||
|
def end_of_track_actions(self) -> None:
|
||||||
|
"""
|
||||||
|
|
||||||
|
Actions required:
|
||||||
|
- Reset track_sequence objects
|
||||||
|
- Tell model track has finished
|
||||||
|
- Reset clocks
|
||||||
|
- Update headers
|
||||||
|
- Enable controls
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Reset track_sequence objects
|
||||||
|
track_sequence.previous = track_sequence.current
|
||||||
|
track_sequence.current = None
|
||||||
|
|
||||||
|
# Tell model previous track has finished
|
||||||
|
self.active_proxy_model().previous_track_ended()
|
||||||
|
|
||||||
|
# Reset clocks
|
||||||
|
self.frame_fade.setStyleSheet("")
|
||||||
|
self.frame_silent.setStyleSheet("")
|
||||||
|
self.label_elapsed_timer.setText("00:00 / 00:00")
|
||||||
|
self.label_fade_timer.setText("00:00")
|
||||||
|
self.label_silent_timer.setText("00:00")
|
||||||
|
|
||||||
|
# Update headers
|
||||||
|
self.update_headers()
|
||||||
|
|
||||||
|
# Enable controls
|
||||||
|
self.catch_return_key = False
|
||||||
|
self.show_status_message("Play controls: Enabled", 0)
|
||||||
|
|
||||||
def export_playlist_tab(self) -> None:
|
def export_playlist_tab(self) -> None:
|
||||||
"""Export the current playlist to an m3u file"""
|
"""Export the current playlist to an m3u file"""
|
||||||
|
|
||||||
@ -788,7 +816,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
def fade(self) -> None:
|
def fade(self) -> None:
|
||||||
"""Fade currently playing track"""
|
"""Fade currently playing track"""
|
||||||
|
|
||||||
self.stop_playing(fade=True)
|
if track_sequence.current:
|
||||||
|
track_sequence.current.fade()
|
||||||
|
|
||||||
def hide_played(self):
|
def hide_played(self):
|
||||||
"""Toggle hide played tracks"""
|
"""Toggle hide played tracks"""
|
||||||
@ -1152,8 +1181,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
# Suppress inadvertent double press
|
# Suppress inadvertent double press
|
||||||
if (
|
if (
|
||||||
track_sequence.current
|
track_sequence.current
|
||||||
and track_sequence.current.track_player.start_time
|
and track_sequence.current.start_time
|
||||||
and track_sequence.current.track_player.start_time
|
and track_sequence.current.start_time
|
||||||
+ dt.timedelta(milliseconds=Config.RETURN_KEY_DEBOUNCE_MS)
|
+ dt.timedelta(milliseconds=Config.RETURN_KEY_DEBOUNCE_MS)
|
||||||
> dt.datetime.now()
|
> dt.datetime.now()
|
||||||
):
|
):
|
||||||
@ -1161,16 +1190,10 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
# If return is pressed during first PLAY_NEXT_GUARD_MS then
|
# If return is pressed during first PLAY_NEXT_GUARD_MS then
|
||||||
# default to NOT playing the next track, else default to
|
# default to NOT playing the next track, else default to
|
||||||
# playing it.
|
# playing it.
|
||||||
default_yes: bool = (
|
default_yes: bool = track_sequence.current.start_time is not None and (
|
||||||
track_sequence.current.track_player.start_time is not None
|
(dt.datetime.now() - track_sequence.current.start_time).total_seconds()
|
||||||
and (
|
* 1000
|
||||||
(
|
> Config.PLAY_NEXT_GUARD_MS
|
||||||
dt.datetime.now()
|
|
||||||
- track_sequence.current.track_player.start_time
|
|
||||||
).total_seconds()
|
|
||||||
* 1000
|
|
||||||
> Config.PLAY_NEXT_GUARD_MS
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
if not helpers.ask_yes_no(
|
if not helpers.ask_yes_no(
|
||||||
"Track playing",
|
"Track playing",
|
||||||
@ -1194,14 +1217,15 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
# seconds of playback. Re-enabled tick_1000ms
|
# seconds of playback. Re-enabled tick_1000ms
|
||||||
|
|
||||||
self.timer10.stop()
|
self.timer10.stop()
|
||||||
self.show_status_message("10ms timer disabled", 0)
|
log.debug("10ms timer disabled", 0)
|
||||||
|
|
||||||
# If there's currently a track playing, fade it.
|
# If there's currently a track playing, fade it.
|
||||||
self.stop_playing(fade=True)
|
if track_sequence.current:
|
||||||
|
track_sequence.current.fade()
|
||||||
|
|
||||||
# Move next track to current track.
|
# Move next track to current track.
|
||||||
# stop_playing() above has called end_of_track_actions()
|
# end_of_track_actions() will have saved current track to
|
||||||
# which will have populated self.previous_track
|
# previous_track
|
||||||
track_sequence.current = track_sequence.next
|
track_sequence.current = track_sequence.next
|
||||||
|
|
||||||
# Clear next track
|
# Clear next track
|
||||||
@ -1213,7 +1237,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
self.btnDrop3db.setChecked(False)
|
self.btnDrop3db.setChecked(False)
|
||||||
|
|
||||||
# Play (new) current track
|
# Play (new) current track
|
||||||
track_sequence.current.track_player.play(position)
|
track_sequence.current.play(position)
|
||||||
|
|
||||||
# Disable play next controls
|
# Disable play next controls
|
||||||
self.catch_return_key = True
|
self.catch_return_key = True
|
||||||
@ -1251,7 +1275,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
self.btnPreview.setChecked(False)
|
self.btnPreview.setChecked(False)
|
||||||
return
|
return
|
||||||
with db.Session() as session:
|
with db.Session() as session:
|
||||||
self.preview_track_player = PreviewTrackPlayer(session, track_id)
|
self.preview_track_player = PreviewTrackManager(session, track_id)
|
||||||
self.preview_track_player.play()
|
self.preview_track_player.play()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@ -1586,7 +1610,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
|
|
||||||
self.statusbar.showMessage(message, timing)
|
self.statusbar.showMessage(message, timing)
|
||||||
|
|
||||||
def show_track(self, playlist_track: PlaylistTrack) -> None:
|
def show_track(self, playlist_track: MainTrackManager) -> None:
|
||||||
"""Scroll to show track in plt"""
|
"""Scroll to show track in plt"""
|
||||||
|
|
||||||
# Switch to the correct tab
|
# Switch to the correct tab
|
||||||
@ -1604,7 +1628,9 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
display_row = (
|
display_row = (
|
||||||
self.active_proxy_model()
|
self.active_proxy_model()
|
||||||
.mapFromSource(
|
.mapFromSource(
|
||||||
self.active_proxy_model().source_model.index(playlist_track.row_number, 0)
|
self.active_proxy_model().source_model.index(
|
||||||
|
playlist_track.row_number, 0
|
||||||
|
)
|
||||||
)
|
)
|
||||||
.row()
|
.row()
|
||||||
)
|
)
|
||||||
@ -1640,66 +1666,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
def stop(self) -> None:
|
def stop(self) -> None:
|
||||||
"""Stop playing immediately"""
|
"""Stop playing immediately"""
|
||||||
|
|
||||||
self.stop_playing(fade=False)
|
if track_sequence.current:
|
||||||
|
track_sequence.current.stop()
|
||||||
def stop_playing(self, fade: bool = True) -> None:
|
|
||||||
"""
|
|
||||||
Stop playing current track
|
|
||||||
|
|
||||||
Actions required:
|
|
||||||
- Set flag to say we're not playing a track
|
|
||||||
- Return if not playing
|
|
||||||
- Stop/fade track
|
|
||||||
- Reset playlist_tab colour
|
|
||||||
- Tell playlist_tab track has finished
|
|
||||||
- Reset PlaylistTrack objects
|
|
||||||
- Reset clocks
|
|
||||||
- Reset fade graph
|
|
||||||
- Update headers
|
|
||||||
- Enable controls
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Set flag to say we're not playing a track so that timer ticks
|
|
||||||
# don't see player=None and kick off end-of-track actions
|
|
||||||
if self.playing:
|
|
||||||
self.playing = False
|
|
||||||
else:
|
|
||||||
# Return if not playing
|
|
||||||
log.info("stop_playing() called but not playing")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Stop/fade track
|
|
||||||
track_sequence.now.resume_marker = self.music.get_position()
|
|
||||||
if fade:
|
|
||||||
self.music.fade()
|
|
||||||
else:
|
|
||||||
self.music.stop()
|
|
||||||
|
|
||||||
# Reset fade graph
|
|
||||||
if track_sequence.now.fade_graph:
|
|
||||||
track_sequence.now.fade_graph.clear()
|
|
||||||
|
|
||||||
# Reset track_sequence objects
|
|
||||||
if track_sequence.now.track_id:
|
|
||||||
track_sequence.previous = track_sequence.now
|
|
||||||
track_sequence.now = PlaylistTrack()
|
|
||||||
|
|
||||||
# Tell model previous track has finished
|
|
||||||
self.active_proxy_model().previous_track_ended()
|
|
||||||
|
|
||||||
# Reset clocks
|
|
||||||
self.frame_fade.setStyleSheet("")
|
|
||||||
self.frame_silent.setStyleSheet("")
|
|
||||||
self.label_elapsed_timer.setText("00:00 / 00:00")
|
|
||||||
self.label_fade_timer.setText("00:00")
|
|
||||||
self.label_silent_timer.setText("00:00")
|
|
||||||
|
|
||||||
# Update headers
|
|
||||||
self.update_headers()
|
|
||||||
|
|
||||||
# Enable controls
|
|
||||||
self.catch_return_key = False
|
|
||||||
self.show_status_message("Play controls: Enabled", 0)
|
|
||||||
|
|
||||||
def tab_change(self):
|
def tab_change(self):
|
||||||
"""Called when active tab changed"""
|
"""Called when active tab changed"""
|
||||||
@ -1743,11 +1711,15 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
Called every 100ms
|
Called every 100ms
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if track_sequence.current:
|
||||||
|
track_sequence.current.check_for_end_of_track()
|
||||||
|
|
||||||
|
return
|
||||||
# Update intro counter if applicable and, if updated, return
|
# Update intro counter if applicable and, if updated, return
|
||||||
# because playing an intro takes precedence over timing a
|
# because playing an intro takes precedence over timing a
|
||||||
# preview.
|
# preview.
|
||||||
if self.music.is_playing() and track_sequence.now.intro:
|
if self.music.is_playing() and track_sequence.current.intro:
|
||||||
remaining_ms = track_sequence.now.intro - self.music.get_playtime()
|
remaining_ms = track_sequence.current.intro - self.music.get_playtime()
|
||||||
if remaining_ms > 0:
|
if remaining_ms > 0:
|
||||||
self.label_intro_timer.setText(f"{remaining_ms / 1000:.1f}")
|
self.label_intro_timer.setText(f"{remaining_ms / 1000:.1f}")
|
||||||
if remaining_ms <= Config.INTRO_SECONDS_WARNING_MS:
|
if remaining_ms <= Config.INTRO_SECONDS_WARNING_MS:
|
||||||
@ -1784,28 +1756,34 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
# Only update play clocks once a second so that their updates
|
# Only update play clocks once a second so that their updates
|
||||||
# are synchronised (otherwise it looks odd)
|
# are synchronised (otherwise it looks odd)
|
||||||
|
|
||||||
if not self.playing:
|
self.update_clocks()
|
||||||
return
|
|
||||||
|
def update_clocks(self) -> None:
|
||||||
|
"""
|
||||||
|
Update track clocks.
|
||||||
|
"""
|
||||||
|
|
||||||
# If track is playing, update track clocks time and colours
|
# If track is playing, update track clocks time and colours
|
||||||
if self.music.player and self.music.player.is_playing():
|
if track_sequence.current and track_sequence.current.is_playing():
|
||||||
playtime = self.music.get_playtime()
|
# see play_next() and issue #223.
|
||||||
time_to_fade = track_sequence.now.fade_at - playtime
|
# TODO: find a better way of handling this
|
||||||
time_to_silence = track_sequence.now.silence_at - playtime
|
if (
|
||||||
|
track_sequence.current.time_playing() > 10000
|
||||||
# see play_next() and issue #223
|
and not self.timer10.isActive()
|
||||||
if playtime > 10000 and not self.timer10.isActive():
|
):
|
||||||
self.timer10.start(10)
|
self.timer10.start(10)
|
||||||
self.show_status_message("10ms timer enabled", 0)
|
log.debug("10ms timer enabled")
|
||||||
|
|
||||||
# Elapsed time
|
# Elapsed time
|
||||||
self.label_elapsed_timer.setText(
|
self.label_elapsed_timer.setText(
|
||||||
helpers.ms_to_mmss(playtime)
|
helpers.ms_to_mmss(track_sequence.current.time_playing())
|
||||||
+ " / "
|
+ " / "
|
||||||
+ helpers.ms_to_mmss(track_sequence.now.duration)
|
+ helpers.ms_to_mmss(track_sequence.current.duration)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Time to fade
|
# Time to fade
|
||||||
|
time_to_fade = track_sequence.current.time_to_fade()
|
||||||
|
time_to_silence = track_sequence.current.time_to_silence()
|
||||||
self.label_fade_timer.setText(helpers.ms_to_mmss(time_to_fade))
|
self.label_fade_timer.setText(helpers.ms_to_mmss(time_to_fade))
|
||||||
|
|
||||||
# If silent in the next 5 seconds, put warning colour on
|
# If silent in the next 5 seconds, put warning colour on
|
||||||
@ -1816,11 +1794,13 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
self.frame_silent.setStyleSheet(css_silence)
|
self.frame_silent.setStyleSheet(css_silence)
|
||||||
self.catch_return_key = False
|
self.catch_return_key = False
|
||||||
self.show_status_message("Play controls: Enabled", 0)
|
self.show_status_message("Play controls: Enabled", 0)
|
||||||
|
|
||||||
# Set warning colour on time to silence box when fade starts
|
# Set warning colour on time to silence box when fade starts
|
||||||
elif time_to_fade <= 500:
|
elif time_to_fade <= 500:
|
||||||
css_fade = f"background: {Config.COLOUR_WARNING_TIMER}"
|
css_fade = f"background: {Config.COLOUR_WARNING_TIMER}"
|
||||||
if self.frame_silent.styleSheet() != css_fade:
|
if self.frame_silent.styleSheet() != css_fade:
|
||||||
self.frame_silent.setStyleSheet(css_fade)
|
self.frame_silent.setStyleSheet(css_fade)
|
||||||
|
|
||||||
# Five seconds before fade starts, set warning colour on
|
# Five seconds before fade starts, set warning colour on
|
||||||
# time to silence box and enable play controls
|
# time to silence box and enable play controls
|
||||||
elif time_to_fade <= Config.WARNING_MS_BEFORE_FADE:
|
elif time_to_fade <= Config.WARNING_MS_BEFORE_FADE:
|
||||||
@ -1835,38 +1815,30 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
|
|
||||||
self.label_silent_timer.setText(helpers.ms_to_mmss(time_to_silence))
|
self.label_silent_timer.setText(helpers.ms_to_mmss(time_to_silence))
|
||||||
|
|
||||||
# Autoplay next track
|
|
||||||
# if time_to_silence <= 1500:
|
|
||||||
# self.play_next()
|
|
||||||
else:
|
|
||||||
if self.playing:
|
|
||||||
self.stop_playing()
|
|
||||||
|
|
||||||
def update_headers(self) -> None:
|
def update_headers(self) -> None:
|
||||||
"""
|
"""
|
||||||
Update last / current / next track headers
|
Update last / current / next track headers
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if track_sequence.previous:
|
if track_sequence.previous:
|
||||||
player = track_sequence.previous.track_player
|
self.hdrPreviousTrack.setText(
|
||||||
self.hdrPreviousTrack.setText(f"{player.title} - {player.artist}")
|
f"{track_sequence.previous.title} - {track_sequence.previous.artist}"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.hdrPreviousTrack.setText("")
|
self.hdrPreviousTrack.setText("")
|
||||||
|
|
||||||
if track_sequence.current:
|
if track_sequence.current:
|
||||||
player = track_sequence.current.track_player
|
|
||||||
self.hdrCurrentTrack.setText(
|
self.hdrCurrentTrack.setText(
|
||||||
f"{player.title.replace('&', '&&')} - "
|
f"{track_sequence.current.title.replace('&', '&&')} - "
|
||||||
f"{player.artist.replace('&', '&&')}"
|
f"{track_sequence.current.artist.replace('&', '&&')}"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.hdrCurrentTrack.setText("")
|
self.hdrCurrentTrack.setText("")
|
||||||
|
|
||||||
if track_sequence.next:
|
if track_sequence.next:
|
||||||
player = track_sequence.next.track_player
|
|
||||||
self.hdrNextTrack.setText(
|
self.hdrNextTrack.setText(
|
||||||
f"{player.title.replace('&', '&&')} - "
|
f"{track_sequence.next.title.replace('&', '&&')} - "
|
||||||
f"{player.artist.replace('&', '&&')}"
|
f"{track_sequence.next.artist.replace('&', '&&')}"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.hdrNextTrack.setText("")
|
self.hdrNextTrack.setText("")
|
||||||
|
|||||||
@ -30,7 +30,7 @@ import obswebsocket # type: ignore
|
|||||||
# import snoop # type: ignore
|
# import snoop # type: ignore
|
||||||
|
|
||||||
# App imports
|
# App imports
|
||||||
from classes import Col, track_sequence, MusicMusterSignals, PlaylistTrack
|
from classes import Col, MusicMusterSignals
|
||||||
from config import Config
|
from config import Config
|
||||||
from helpers import (
|
from helpers import (
|
||||||
file_is_unreadable,
|
file_is_unreadable,
|
||||||
@ -41,6 +41,10 @@ from helpers import (
|
|||||||
)
|
)
|
||||||
from log import log
|
from log import log
|
||||||
from models import db, NoteColours, Playdates, PlaylistRows, Tracks
|
from models import db, NoteColours, Playdates, PlaylistRows, Tracks
|
||||||
|
from trackmanager import (
|
||||||
|
MainTrackManager,
|
||||||
|
track_sequence,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
HEADER_NOTES_COLUMN = 1
|
HEADER_NOTES_COLUMN = 1
|
||||||
@ -684,7 +688,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
end_time_str = ""
|
end_time_str = ""
|
||||||
if (
|
if (
|
||||||
track_sequence.current
|
track_sequence.current
|
||||||
and track_sequence.current.track_player.end_time
|
and track_sequence.current.end_time
|
||||||
and (
|
and (
|
||||||
row_number
|
row_number
|
||||||
< track_sequence.current.row_number
|
< track_sequence.current.row_number
|
||||||
@ -692,7 +696,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
)
|
)
|
||||||
):
|
):
|
||||||
section_end_time = (
|
section_end_time = (
|
||||||
track_sequence.current.track_player.end_time
|
track_sequence.current.end_time
|
||||||
+ dt.timedelta(milliseconds=duration)
|
+ dt.timedelta(milliseconds=duration)
|
||||||
)
|
)
|
||||||
end_time_str = (
|
end_time_str = (
|
||||||
@ -1257,12 +1261,13 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
with db.Session() as session:
|
||||||
track_sequence.next = PlaylistTrack(prd.plrid)
|
try:
|
||||||
self.invalidate_row(row_number)
|
track_sequence.next = MainTrackManager(session, prd.plrid)
|
||||||
except ValueError as e:
|
self.invalidate_row(row_number)
|
||||||
log.error(f"Error creating PlaylistTrack({prd=}): ({str(e)})")
|
except ValueError as e:
|
||||||
return
|
log.error(f"Error creating PlaylistTrack({prd=}): ({str(e)})")
|
||||||
|
return
|
||||||
|
|
||||||
self.signals.search_wikipedia_signal.emit(
|
self.signals.search_wikipedia_signal.emit(
|
||||||
self.playlist_rows[row_number].title
|
self.playlist_rows[row_number].title
|
||||||
@ -1555,9 +1560,9 @@ class PlaylistProxyModel(QSortFilterProxyModel):
|
|||||||
and previous_plr.playlist_id
|
and previous_plr.playlist_id
|
||||||
== self.source_model.playlist_id
|
== self.source_model.playlist_id
|
||||||
):
|
):
|
||||||
if track_sequence.current.track_player.start_time:
|
if track_sequence.current.start_time:
|
||||||
if dt.datetime.now() > (
|
if dt.datetime.now() > (
|
||||||
track_sequence.current.track_player.start_time
|
track_sequence.current.start_time
|
||||||
+ dt.timedelta(
|
+ dt.timedelta(
|
||||||
milliseconds=Config.HIDE_AFTER_PLAYING_OFFSET
|
milliseconds=Config.HIDE_AFTER_PLAYING_OFFSET
|
||||||
)
|
)
|
||||||
|
|||||||
@ -35,7 +35,7 @@ from PyQt6.QtWidgets import (
|
|||||||
# Third party imports
|
# Third party imports
|
||||||
|
|
||||||
# App imports
|
# App imports
|
||||||
from classes import Col, MusicMusterSignals, track_sequence
|
from classes import Col, MusicMusterSignals
|
||||||
from config import Config
|
from config import Config
|
||||||
from dialogs import TrackSelectDialog
|
from dialogs import TrackSelectDialog
|
||||||
from helpers import (
|
from helpers import (
|
||||||
@ -47,6 +47,7 @@ from helpers import (
|
|||||||
from log import log
|
from log import log
|
||||||
from models import db, Settings
|
from models import db, Settings
|
||||||
from playlistmodel import PlaylistModel, PlaylistProxyModel
|
from playlistmodel import PlaylistModel, PlaylistProxyModel
|
||||||
|
from trackmanager import track_sequence
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from musicmuster import Window
|
from musicmuster import Window
|
||||||
|
|||||||
@ -21,6 +21,7 @@ from PyQt6.QtCore import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
# App imports
|
# App imports
|
||||||
|
from classes import MusicMusterSignals
|
||||||
from config import Config
|
from config import Config
|
||||||
from log import log
|
from log import log
|
||||||
from models import db, PlaylistRows, Tracks
|
from models import db, PlaylistRows, Tracks
|
||||||
@ -185,6 +186,25 @@ class _Music:
|
|||||||
else:
|
else:
|
||||||
self.start_dt -= dt.timedelta(milliseconds=ms)
|
self.start_dt -= dt.timedelta(milliseconds=ms)
|
||||||
|
|
||||||
|
def stop(self) -> None:
|
||||||
|
"""Immediately stop playing"""
|
||||||
|
|
||||||
|
log.debug(f"Music[{self.name}].stop()")
|
||||||
|
|
||||||
|
self.start_dt = None
|
||||||
|
|
||||||
|
if not self.player:
|
||||||
|
return
|
||||||
|
|
||||||
|
p = self.player
|
||||||
|
self.player = None
|
||||||
|
self.start_dt = None
|
||||||
|
|
||||||
|
with lock:
|
||||||
|
p.stop()
|
||||||
|
p.release()
|
||||||
|
p = None
|
||||||
|
|
||||||
def fade(self, fade_seconds: int) -> None:
|
def fade(self, fade_seconds: int) -> None:
|
||||||
"""
|
"""
|
||||||
Fade the currently playing track.
|
Fade the currently playing track.
|
||||||
@ -193,8 +213,6 @@ class _Music:
|
|||||||
to hold up the UI during the fade.
|
to hold up the UI during the fade.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
log.info(f"Music[{self.name}].stop()")
|
|
||||||
|
|
||||||
if not self.player:
|
if not self.player:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -202,7 +220,7 @@ class _Music:
|
|||||||
return
|
return
|
||||||
|
|
||||||
if fade_seconds <= 0:
|
if fade_seconds <= 0:
|
||||||
self._stop()
|
self.stop()
|
||||||
return
|
return
|
||||||
|
|
||||||
# Take a copy of current player to allow another track to be
|
# Take a copy of current player to allow another track to be
|
||||||
@ -249,13 +267,10 @@ class _Music:
|
|||||||
# player.is_playing() returning True, so assume playing if less
|
# player.is_playing() returning True, so assume playing if less
|
||||||
# than Config.PLAY_SETTLE microseconds have passed since
|
# than Config.PLAY_SETTLE microseconds have passed since
|
||||||
# starting play.
|
# starting play.
|
||||||
return (
|
return self.start_dt is not None and (
|
||||||
self.start_dt is not None
|
self.player.is_playing()
|
||||||
and (
|
or (dt.datetime.now() - self.start_dt)
|
||||||
self.player.is_playing()
|
< dt.timedelta(microseconds=Config.PLAY_SETTLE)
|
||||||
or (dt.datetime.now() - self.start_dt)
|
|
||||||
< dt.timedelta(microseconds=Config.PLAY_SETTLE)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def move_back(self, ms: int) -> None:
|
def move_back(self, ms: int) -> None:
|
||||||
@ -342,25 +357,6 @@ class _Music:
|
|||||||
log.debug(f"Reset from {volume=}")
|
log.debug(f"Reset from {volume=}")
|
||||||
sleep(0.1)
|
sleep(0.1)
|
||||||
|
|
||||||
def _stop(self) -> None:
|
|
||||||
"""Immediately stop playing"""
|
|
||||||
|
|
||||||
log.info(f"Music[{self.name}].stop()")
|
|
||||||
|
|
||||||
self.start_dt = None
|
|
||||||
|
|
||||||
if not self.player:
|
|
||||||
return
|
|
||||||
|
|
||||||
p = self.player
|
|
||||||
self.player = None
|
|
||||||
self.start_dt = None
|
|
||||||
|
|
||||||
with lock:
|
|
||||||
p.stop()
|
|
||||||
p.release()
|
|
||||||
p = None
|
|
||||||
|
|
||||||
|
|
||||||
class _TrackManager:
|
class _TrackManager:
|
||||||
"""
|
"""
|
||||||
@ -397,9 +393,10 @@ class _TrackManager:
|
|||||||
self.resume_marker: Optional[float]
|
self.resume_marker: Optional[float]
|
||||||
self.start_time: Optional[dt.datetime] = None
|
self.start_time: Optional[dt.datetime] = None
|
||||||
|
|
||||||
self.player = _Music(name=player_name)
|
self.signals = MusicMusterSignals()
|
||||||
|
|
||||||
# Initialise player
|
# Initialise player
|
||||||
self.track_player = MainTrackManager(session=session, track_id=self.track_id)
|
self.player = _Music(name=player_name)
|
||||||
|
|
||||||
# Initialise and add FadeCurve in a thread as it's slow
|
# Initialise and add FadeCurve in a thread as it's slow
|
||||||
self.fadecurve_thread = QThread()
|
self.fadecurve_thread = QThread()
|
||||||
@ -416,23 +413,50 @@ class _TrackManager:
|
|||||||
self.fadecurve_thread.finished.connect(self.fadecurve_thread.deleteLater)
|
self.fadecurve_thread.finished.connect(self.fadecurve_thread.deleteLater)
|
||||||
self.fadecurve_thread.start()
|
self.fadecurve_thread.start()
|
||||||
|
|
||||||
|
def check_for_end_of_track(self) -> None:
|
||||||
|
"""
|
||||||
|
Check whether track has ended. If so, emit track_ended_signal
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.start_time is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.player.is_playing():
|
||||||
|
self.start_time = None
|
||||||
|
self.signals.track_ended_signal.emit()
|
||||||
|
|
||||||
|
def drop3db(self, enable: bool) -> None:
|
||||||
|
"""
|
||||||
|
If enable is true, drop output by 3db else restore to full volume
|
||||||
|
"""
|
||||||
|
|
||||||
|
if enable:
|
||||||
|
self.player.set_volume(volume=Config.VLC_VOLUME_DROP3db, set_default=False)
|
||||||
|
else:
|
||||||
|
self.player.set_volume(volume=Config.VLC_VOLUME_DEFAULT, set_default=False)
|
||||||
|
|
||||||
def fade(self, fade_seconds: int = Config.FADEOUT_SECONDS) -> None:
|
def fade(self, fade_seconds: int = Config.FADEOUT_SECONDS) -> None:
|
||||||
"""Fade music"""
|
"""Fade music"""
|
||||||
|
|
||||||
self.player.fade(fade_seconds)
|
self.player.fade(fade_seconds)
|
||||||
|
|
||||||
@property
|
|
||||||
def is_playing(self) -> bool:
|
def is_playing(self) -> bool:
|
||||||
return self.track_player.is_playing()
|
"""
|
||||||
|
Return True if we're currently playing else False
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.start_time is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return self.player.is_playing()
|
||||||
|
|
||||||
def play(self, position: Optional[float] = None) -> None:
|
def play(self, position: Optional[float] = None) -> None:
|
||||||
"""Play track"""
|
"""Play track"""
|
||||||
|
|
||||||
now = dt.datetime.now()
|
|
||||||
self.start_time = now
|
|
||||||
self.player.play(self.path, position)
|
self.player.play(self.path, position)
|
||||||
|
|
||||||
|
now = dt.datetime.now()
|
||||||
|
self.start_time = now
|
||||||
self.end_time = self.start_time + dt.timedelta(milliseconds=self.duration)
|
self.end_time = self.start_time + dt.timedelta(milliseconds=self.duration)
|
||||||
|
|
||||||
# Calculate time fade_graph should start updating
|
# Calculate time fade_graph should start updating
|
||||||
@ -444,7 +468,7 @@ class _TrackManager:
|
|||||||
milliseconds=update_graph_at_ms
|
milliseconds=update_graph_at_ms
|
||||||
)
|
)
|
||||||
|
|
||||||
def stop_playing(self, fade_seconds: int = 0) -> None:
|
def stop(self, fade_seconds: int = 0) -> None:
|
||||||
"""
|
"""
|
||||||
Stop this track playing
|
Stop this track playing
|
||||||
"""
|
"""
|
||||||
@ -456,41 +480,64 @@ class _TrackManager:
|
|||||||
if self.fade_graph:
|
if self.fade_graph:
|
||||||
self.fade_graph.clear()
|
self.fade_graph.clear()
|
||||||
|
|
||||||
|
def time_playing(self) -> int:
|
||||||
|
"""
|
||||||
|
Return time track has been playing in milliseconds, zero if not playing
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.start_time is None:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
return self.player.get_playtime()
|
||||||
|
|
||||||
def time_to_fade(self) -> int:
|
def time_to_fade(self) -> int:
|
||||||
"""
|
"""
|
||||||
Return milliseconds until fade time. Return zero if we're not playing.
|
Return milliseconds until fade time. Return zero if we're not playing.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not self.player.is_playing:
|
if self.start_time is None:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
return self.fade_at - self.time_playing()
|
||||||
|
|
||||||
|
def time_to_silence(self) -> int:
|
||||||
|
"""
|
||||||
|
Return milliseconds until silent. Return zero if we're not playing.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.start_time is None:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
return self.silence_at - self.time_playing()
|
||||||
|
|
||||||
|
|
||||||
class MainTrackManager(_TrackManager):
|
class MainTrackManager(_TrackManager):
|
||||||
"""
|
"""
|
||||||
Manage playing tracks from the playlist with associated data
|
Manage playing tracks from the playlist with associated data
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, plr_id: int) -> None:
|
def __init__(self, session: db.Session, plr_id: int) -> None:
|
||||||
"""
|
"""
|
||||||
Set up manager for playlist tracks
|
Set up manager for playlist tracks
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with db.Session() as session:
|
# Ensure we have a track
|
||||||
# Ensure we have a track
|
plr = session.get(PlaylistRows, plr_id)
|
||||||
plr = session.get(PlaylistRows, plr_id)
|
if not plr:
|
||||||
if not plr:
|
raise ValueError(f"PlaylistTrack: unable to retreive plr {plr_id=}")
|
||||||
raise ValueError(f"PlaylistTrack: unable to retreive plr {plr_id=}")
|
|
||||||
|
|
||||||
self.track_id: int = plr.track_id
|
self.track_id: int = plr.track_id
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
session=session, player_name=Config.VLC_MAIN_PLAYER_NAME, track_id=self.track_id
|
session=session,
|
||||||
)
|
player_name=Config.VLC_MAIN_PLAYER_NAME,
|
||||||
|
track_id=self.track_id,
|
||||||
|
)
|
||||||
|
|
||||||
# Save non-track plr info
|
# Save non-track plr info
|
||||||
self.plr_id: int = plr.id
|
self.plr_id: int = plr.id
|
||||||
self.playlist_id: int = plr.playlist_id
|
self.playlist_id: int = plr.playlist_id
|
||||||
self.row_number: int = plr.plr_rownum
|
self.row_number: int = plr.plr_rownum
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
@ -504,8 +551,9 @@ class PreviewTrackManager(_TrackManager):
|
|||||||
Manage previewing tracks
|
Manage previewing tracks
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, track_id: int) -> None:
|
def __init__(self, session: db.Session, track_id: int) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
|
session=session,
|
||||||
player_name=Config.VLC_PREVIEW_PLAYER_NAME,
|
player_name=Config.VLC_PREVIEW_PLAYER_NAME,
|
||||||
track_id=track_id,
|
track_id=track_id,
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user