From 747f28f4f9df597a500cd9ac0913305cbf3fe220 Mon Sep 17 00:00:00 2001 From: Keith Edmunds Date: Mon, 14 Apr 2025 19:18:26 +0100 Subject: [PATCH] WIP: track ending signal --- app/classes.py | 3 +- app/ds.py | 3 + app/musicmuster.py | 349 +++++++++++++++++++------------------------ app/playlistmodel.py | 11 +- app/playlistrow.py | 5 +- 5 files changed, 172 insertions(+), 199 deletions(-) diff --git a/app/classes.py b/app/classes.py index 0db282d..ab78d9e 100644 --- a/app/classes.py +++ b/app/classes.py @@ -291,7 +291,8 @@ class MusicMusterSignals(QObject): # Dispay status message to user status_message_signal = pyqtSignal(str, int) - track_ended_signal = pyqtSignal() + # Emitted when track ends or is manually faded + track_ended_signal = pyqtSignal(int) def __post_init__(self): super().__init__() diff --git a/app/ds.py b/app/ds.py index da9e95a..32ba067 100644 --- a/app/ds.py +++ b/app/ds.py @@ -1063,6 +1063,9 @@ def playdates_update(track_id: int, when: dt.datetime | None = None) -> None: Update playdates for passed track """ + if not when: + when = dt.datetime.now() + with db.Session() as session: _ = Playdates(session, track_id, when) diff --git a/app/musicmuster.py b/app/musicmuster.py index 68d2da8..93b84de 100755 --- a/app/musicmuster.py +++ b/app/musicmuster.py @@ -3,7 +3,7 @@ # Standard library imports from __future__ import annotations from slugify import slugify # type: ignore -from typing import Callable, Optional +from typing import Callable import argparse from dataclasses import dataclass import datetime as dt @@ -72,24 +72,24 @@ from classes import ( TrackAndPlaylist, TrackInfo, ) + from config import Config from dialogs import TrackInsertDialog from file_importer import FileImporter from helpers import ask_yes_no, file_is_unreadable, get_name from log import log, log_call -from playlistrow import PlaylistRow, TrackSequence from playlistmodel import PlaylistModel, PlaylistProxyModel +from playlistrow import PlaylistRow, TrackSequence from playlists import PlaylistTab -import ds from querylistmodel import QuerylistModel -from ui import icons_rc # noqa F401 from ui.dlg_SelectPlaylist_ui import Ui_dlgSelectPlaylist # type: ignore from ui.downloadcsv_ui import Ui_DateSelect # type: ignore +from ui import icons_rc # noqa F401 +from ui.main_window_footer_ui import Ui_FooterSection # type: ignore from ui.main_window_header_ui import Ui_HeaderSection # type: ignore from ui.main_window_playlist_ui import Ui_PlaylistSection # type: ignore -from ui.main_window_footer_ui import Ui_FooterSection # type: ignore - from utilities import check_db, update_bitrates +import ds import helpers @@ -661,15 +661,6 @@ class ManageTemplates(ItemlistManager): ds.playlist_rename(template_id, new_name) -@dataclass -class ItemlistManagerCallbacks: - delete: Callable[[int], None] - edit: Callable[[int], None] - favourite: Callable[[int, bool], None] - new_item: Callable[[], None] - rename: Callable[[int], Optional[str]] - - class PreviewManager: """ Manage track preview player @@ -677,10 +668,10 @@ class PreviewManager: def __init__(self) -> None: mixer.init() - self.intro: Optional[int] = None + self.intro: int | None = None self.path: str = "" - self.row_number: Optional[int] = None - self.start_time: Optional[dt.datetime] = None + self.row_number: int | None = None + self.start_time: dt.datetime | None = None self.track_id: int = 0 def back(self, ms: int) -> None: @@ -1036,7 +1027,7 @@ class TemplateSelectorDialog(QDialog): """ def __init__( - self, templates: list[tuple[str, int]], template_prompt: Optional[str] + self, templates: list[tuple[str, int]], template_prompt: str | None ) -> None: super().__init__() self.templates = templates @@ -1111,7 +1102,7 @@ class FooterSection(QWidget, Ui_FooterSection): class Window(QMainWindow): def __init__( - self, parent: Optional[QWidget] = None, *args: list, **kwargs: dict + self, parent: QWidget | None = None, *args: list, **kwargs: dict ) -> None: super().__init__(parent) @@ -1128,45 +1119,48 @@ class Window(QMainWindow): layout.addWidget(self.playlist_section) layout.addWidget(self.footer_section) - self.setWindowTitle(Config.MAIN_WINDOW_TITLE) - # Add menu bar - self.create_menu_bar() - - self.timer10: QTimer = QTimer() - self.timer100: QTimer = QTimer() - self.timer500: QTimer = QTimer() - self.timer1000: QTimer = QTimer() - - self.set_main_window_size() - self.lblSumPlaytime = QLabel("") - self.statusbar = self.statusBar() - if self.statusbar: - self.statusbar.addPermanentWidget(self.lblSumPlaytime) - self.txtSearch = QLineEdit() - self.txtSearch.setHidden(True) - self.statusbar.addWidget(self.txtSearch) - self.hide_played_tracks = False - self.preview_manager = PreviewManager() - self.footer_section.widgetFadeVolume.hideAxis("bottom") self.footer_section.widgetFadeVolume.hideAxis("left") self.footer_section.widgetFadeVolume.setDefaultPadding(0) self.footer_section.widgetFadeVolume.setBackground(Config.FADE_CURVE_BACKGROUND) - self.move_source: MoveSource | None = None + self.setWindowTitle(Config.MAIN_WINDOW_TITLE) - self.disable_selection_timing = False - self.clock_counter = 0 + # Add menu bar + self.create_menu_bar() + + # Configure main window + self.set_main_window_size() + self.lblSumPlaytime = QLabel("") + self.statusbar = self.statusBar() + if not self.statusbar: + raise ApplicationError("Can't create status bar") + self.statusbar.addPermanentWidget(self.lblSumPlaytime) + self.txtSearch = QLineEdit() + self.txtSearch.setHidden(True) + self.statusbar.addWidget(self.txtSearch) + self.hide_played_tracks = False + + # Timers + self.timer10: QTimer = QTimer() + self.timer100: QTimer = QTimer() + self.timer500: QTimer = QTimer() + self.timer1000: QTimer = QTimer() self.timer10.start(10) self.timer100.start(100) self.timer500.start(500) self.timer1000.start(1000) - self.signals = MusicMusterSignals() - self.connect_signals_slots() + + # Misc + self.preview_manager = PreviewManager() + self.move_source: MoveSource | None = None + self.disable_selection_timing = False self.catch_return_key = False - self.importer: Optional[FileImporter] = None + self.importer: FileImporter | None = None self.current = Current() self.track_sequence = TrackSequence() + self.signals = MusicMusterSignals() + self.connect_signals_slots() webbrowser.register( "browser", @@ -1178,12 +1172,12 @@ class Window(QMainWindow): self.action_quicklog = QShortcut(QKeySequence("Ctrl+L"), self) self.action_quicklog.activated.connect(self.quicklog) + # Load playlists self.load_last_playlists() - self.stop_autoplay = False # # # # # # # # # # Overrides # # # # # # # # # # - def closeEvent(self, event: Optional[QCloseEvent]) -> None: + def closeEvent(self, event: QCloseEvent | None) -> None: """Handle attempt to close main window""" if not event: @@ -1210,7 +1204,7 @@ class Window(QMainWindow): mainwindow_width=self.width(), mainwindow_x=self.x(), mainwindow_y=self.y(), - active_tab=self.playlist_section.tabPlaylist.currentIndex(), + active_index=self.playlist_section.tabPlaylist.currentIndex(), ) for name, value in attributes_to_save.items(): ds.setting_set(name, value) @@ -1219,16 +1213,17 @@ class Window(QMainWindow): # # # # # # # # # # Internal utility functions # # # # # # # # # # - def active_tab(self) -> PlaylistTab: + def _active_tab(self) -> PlaylistTab: return self.playlist_section.tabPlaylist.currentWidget() # # # # # # # # # # Menu functions # # # # # # # # # # def create_action( - self, text: str, handler: Callable, shortcut: Optional[str] = None + self, text: str, handler: Callable, shortcut: str | None = None ) -> QAction: """ - Helper function to create an action, bind it to a method, and set a shortcut if provided. + Helper function for menu creation. Create an action, bind it to a + method, and set a shortcut if provided. """ action = QAction(text, self) @@ -1281,34 +1276,6 @@ class Window(QMainWindow): menu.addAction(action) - def populate_dynamic_submenu(self): - """Dynamically populates submenus when they are selected.""" - submenu = self.sender() # Get the submenu that triggered the event - - # Find which submenu it is - for key, stored_submenu in self.dynamic_submenus.items(): - if submenu == stored_submenu: - submenu.clear() - # Dynamically call the correct function - items = getattr(self, f"get_{key}_items")() - for item in items: - # Check for separator - if "separator" in item and item["separator"]: - submenu.addSeparator() - continue - action = QAction(item["text"], self) - - # Extract handler and arguments - handler = getattr(self, item["handler"], None) - args = item.get("args", ()) - - if handler: - # Use a lambda to pass arguments to the function - action.triggered.connect(lambda _, h=handler, a=args: h(a)) - - submenu.addAction(action) - break - def get_new_playlist_dynamic_submenu_items( self, ) -> list[dict[str, str | int | bool]]: @@ -1376,41 +1343,33 @@ class Window(QMainWindow): return submenu_items - def show_query(self, query_id: int) -> None: - """ - Show query dialog with query_id selected - """ + def populate_dynamic_submenu(self): + """Dynamically populates submenus when they are selected.""" + submenu = self.sender() # Get the submenu that triggered the event - # Keep a reference else it will be gc'd - self.query_dialog = QueryDialog(query_id) - if self.query_dialog.exec(): - new_row_number = self.current_row_or_end() - base_model = self.current.base_model - for track_id in self.query_dialog.selected_tracks: - # Check whether track is already in playlist - move_existing = False - existing_prd = base_model.is_track_in_playlist(track_id) - if existing_prd is not None: - if ask_yes_no( - "Duplicate row", - "Track already in playlist. " "Move to new location?", - default_yes=True, - ): - move_existing = True + # Find which submenu it is + for key, stored_submenu in self.dynamic_submenus.items(): + if submenu == stored_submenu: + submenu.clear() + # Dynamically call the correct function + items = getattr(self, f"get_{key}_items")() + for item in items: + # Check for separator + if "separator" in item and item["separator"]: + submenu.addSeparator() + continue + action = QAction(item["text"], self) - if move_existing and existing_prd: - base_model.move_track_add_note( - new_row_number, existing_prd, note="" - ) - else: - self.signals.signal_insert_track.emit( - InsertTrack( - playlist_id=base_model.playlist_id, - track_id=track_id, - note="", - ) - ) - new_row_number += 1 + # Extract handler and arguments + handler = getattr(self, item["handler"], None) + args = item.get("args", ()) + + if handler: + # Use a lambda to pass arguments to the function + action.triggered.connect(lambda _, h=handler, a=args: h(a)) + + submenu.addAction(action) + break # # # # # # # # # # Playlist management functions # # # # # # # # # # @@ -1418,7 +1377,7 @@ class Window(QMainWindow): def _create_playlist(self, name: str, template_id: int) -> PlaylistDTO: """ Create a playlist in the database, populate it from the template - if template_id > 0, and return the Playlists object. + if template_id > 0, and return the PlaylistDTO object. """ return ds.playlist_create(name, template_id) @@ -1455,7 +1414,7 @@ class Window(QMainWindow): # @log_call def create_playlist_from_template(self, template_id: int) -> None: """ - Prompt for new playlist name and create from passed template_id + Prompt for new playlist name and create from passed template_id. """ if template_id == 0: @@ -1463,8 +1422,7 @@ class Window(QMainWindow): selected_template_id = self.solicit_template_to_use() if selected_template_id is None: return - else: - template_id = selected_template_id + template_id = selected_template_id playlist_name = self.get_playlist_name() if not playlist_name: @@ -1526,7 +1484,7 @@ class Window(QMainWindow): def get_playlist_name( self, default: str = "", prompt: str = "Playlist name:" - ) -> Optional[str]: + ) -> str | None: """Get a name from the user""" dlg = QInputDialog(self) @@ -1554,8 +1512,8 @@ class Window(QMainWindow): return None def solicit_template_to_use( - self, template_prompt: Optional[str] = None - ) -> Optional[int]: + self, template_prompt: str | None = None + ) -> int | None: """ Have user select a template. Return the template.id, or None if they cancel. template_id of zero means don't use a template. @@ -1589,12 +1547,48 @@ class Window(QMainWindow): _ = ManageTemplates(self) + def show_query(self, query_id: int) -> None: + """ + Show query dialog with query_id selected + """ + + # Keep a reference else it will be gc'd + self.query_dialog = QueryDialog(query_id) + if self.query_dialog.exec(): + new_row_number = self.current_row_or_end() + base_model = self.current.base_model + for track_id in self.query_dialog.selected_tracks: + # Check whether track is already in playlist + move_existing = False + existing_prd = base_model.is_track_in_playlist(track_id) + if existing_prd is not None: + if ask_yes_no( + "Duplicate row", + "Track already in playlist. " "Move to new location?", + default_yes=True, + ): + move_existing = True + + if move_existing and existing_prd: + base_model.move_track_add_note( + new_row_number, existing_prd, note="" + ) + else: + self.signals.signal_insert_track.emit( + InsertTrack( + playlist_id=base_model.playlist_id, + track_id=track_id, + note="", + ) + ) + new_row_number += 1 + # # # # # # # # # # Miscellaneous functions # # # # # # # # # # def select_duplicate_rows(self, checked: bool = False) -> None: """Call playlist to select duplicate rows""" - self.active_tab().select_duplicate_rows() + self._active_tab().select_duplicate_rows() def about(self, checked: bool = False) -> None: """Get git tag and database name""" @@ -1621,14 +1615,14 @@ class Window(QMainWindow): """ self.track_sequence.set_next(None) - self.update_headers() + self.signals.next_track_changed_signal.emit() def clear_selection(self, checked: bool = False) -> None: """Clear row selection""" # Unselect any selected rows - if self.active_tab(): - self.active_tab().clear_selection() + if self._active_tab(): + self._active_tab().clear_selection() # Clear the search bar self.search_playlist_clear() @@ -1782,6 +1776,7 @@ class Window(QMainWindow): # @log_call def end_of_track_actions(self) -> None: """ + Called by track_ended_signal Actions required: - Reset track_sequence objects @@ -1794,10 +1789,6 @@ class Window(QMainWindow): if self.track_sequence.current: self.track_sequence.move_current_to_previous() - # Tell playlist previous track has finished - # TODO: it should just catch track_ended_signal - self.current.base_model.previous_track_ended() - # Reset clocks self.footer_section.frame_fade.setStyleSheet("") self.footer_section.frame_silent.setStyleSheet("") @@ -1811,10 +1802,6 @@ class Window(QMainWindow): self.catch_return_key = False self.show_status_message("Play controls: Enabled", 0) - # autoplay - # if not self.stop_autoplay: - # self.play_next() - def export_playlist_tab(self, checked: bool = False) -> None: """Export the current playlist to an m3u file""" @@ -1859,7 +1846,7 @@ class Window(QMainWindow): if self.track_sequence.current: self.track_sequence.current.fade() - def get_tab_index_for_playlist(self, playlist_id: int) -> Optional[int]: + def get_tab_index_for_playlist(self, playlist_id: int) -> int | None: """ Return the tab index for the passed playlist_id if it is displayed, else return None. @@ -1882,12 +1869,12 @@ class Window(QMainWindow): self.hide_played_tracks = True self.footer_section.btnHidePlayed.setText("Show played") if Config.HIDE_PLAYED_MODE == Config.HIDE_PLAYED_MODE_SECTIONS: - self.active_tab().hide_played_sections() + self._active_tab().hide_played_sections() else: self.current.base_model.hide_played_tracks(True) # Reset row heights - self.active_tab().resize_rows() + self._active_tab().resize_rows() def import_files_wrapper(self, checked: bool = False) -> None: """ @@ -1920,7 +1907,7 @@ class Window(QMainWindow): def insert_track(self, checked: bool = False) -> None: """Show dialog box to select and add track from database""" - dlg = TrackInsertDialog(parent=self, playlist_id=self.active_tab().playlist_id) + dlg = TrackInsertDialog(parent=self, playlist_id=self.current.playlist_id) dlg.exec() # @log_call @@ -1934,7 +1921,7 @@ class Window(QMainWindow): playlist_ids.append(self._open_playlist(playlist)) # Set active tab - value = ds.setting_get("active_tab") + value = ds.setting_get("active_index") if value is not None and value >= 0: self.playlist_section.tabPlaylist.setCurrentIndex(value) @@ -1969,7 +1956,7 @@ class Window(QMainWindow): # paste self.move_source = MoveSource( model=self.current.base_model, - rows=[a.row_number for a in self.current.base_model.selected_rows], + rows=self.current.selected_row_numbers ) log.debug(f"mark_rows_for_moving(): {self.move_source=}") @@ -1980,6 +1967,9 @@ class Window(QMainWindow): Move passed playlist rows to another playlist """ + if not row_numbers: + return + # Identify destination playlist playlists = [] source_playlist_id = self.current.playlist_id @@ -2013,11 +2003,7 @@ class Window(QMainWindow): Move selected rows to another playlist """ - selected_rows = self.current.selected_row_numbers - if not selected_rows: - return - - self.move_playlist_rows(selected_rows) + self.move_playlist_rows(self.current.selected_row_numbers) def move_unplayed(self, checked: bool = False) -> None: """ @@ -2074,8 +2060,8 @@ class Window(QMainWindow): from_rows, to_row, to_playlist_model.playlist_id ) - self.active_tab().resize_rows() - self.active_tab().clear_selection() + self._active_tab().resize_rows() + self._active_tab().clear_selection() # If we move a row to immediately under the current track, make # that moved row the next track @@ -2088,7 +2074,7 @@ class Window(QMainWindow): # @log_call def play_next( - self, position: Optional[float] = None, checked: bool = False + self, position: float | None = None, checked: bool = False ) -> None: """ Play next track, optionally from passed position. @@ -2118,7 +2104,7 @@ class Window(QMainWindow): return # Issue #223 concerns a very short pause (maybe 0.1s) sometimes - # when starting to play at track. Resolution appears to be to + # just after a track starts playing. Resolution appears to be to # disable timer10 for a short time. Timer is re-enabled in # update_clocks. @@ -2129,9 +2115,11 @@ class Window(QMainWindow): if self.track_sequence.current: self.track_sequence.current.fade() - # Move next track to current track. - # end_of_track_actions() will have saved current track to + # Move next track to current track. end_of_track_actions() will + # have been called when previous track ended or when fade() was + # called above, and that in turn will have saved current track to # previous_track + self.track_sequence.move_next_to_current() if self.track_sequence.current is None: raise ApplicationError("No current track") @@ -2159,6 +2147,9 @@ class Window(QMainWindow): self.catch_return_key = True self.show_status_message("Play controls: Disabled", 0) + # Record playdate + ds.playdates_update(self.track_sequence.current.track_id) + # Notify others self.signals.signal_track_started.emit( TrackAndPlaylist( @@ -2167,21 +2158,6 @@ class Window(QMainWindow): ) ) - # TODO: ensure signal_track_started does all this: - # self.active_tab().current_track_started() - # Update playdates - # Set toolips for hdrPreviousTrack (but let's do that dynamically - # on hover in future) - # with s-e-s-s-i-o-n: - # last_played = Playdates.last_played_tracks(s-e-s-s-i-o-n) - # tracklist = [] - # for lp in last_played: - # track = s-e-s-s-i-o-n.get(Tracks, lp.track_id) - # tracklist.append(f"{track.title} ({track.artist})") - # tt = "
".join(tracklist) - - # self.header_section.hdrPreviousTrack.setToolTip(tt) - # Update headers self.update_headers() @@ -2194,7 +2170,7 @@ class Window(QMainWindow): if self.footer_section.btnPreview.isChecked(): # Get track path for first selected track if there is one - track_info = self.active_tab().get_selected_row_track_info() + track_info = self._active_tab().get_selected_row_track_info() if not track_info: # Otherwise get track_id to next track to play if self.track_sequence.next: @@ -2203,10 +2179,9 @@ class Window(QMainWindow): self.track_sequence.next.track_id, self.track_sequence.next.row_number, ) - else: - return if not track_info: return + self.preview_manager.row_number = track_info.row_number track = ds.track_by_id(track_info.track_id) if not track: @@ -2394,7 +2369,7 @@ class Window(QMainWindow): # Disable play controls so that 'return' in search box doesn't # play next track self.catch_return_key = True - self.txtSearch.setHidden(False) + self.txtSearch.setVisible(True) self.txtSearch.setFocus() # Select any text that may already be there self.txtSearch.selectAll() @@ -2413,13 +2388,13 @@ class Window(QMainWindow): self.current.proxy_model.set_incremental_search(self.txtSearch.text()) - def selected_or_next_track_info(self) -> Optional[PlaylistRow]: + def selected_or_next_track_info(self) -> PlaylistRow | None: """ Return RowAndTrack info for selected track. If no selected track, return for next track. If no next track, return None. """ - row_number: Optional[int] = None + row_number: int | None = None if self.current.selected_row_numbers: row_number = self.current.selected_row_numbers[0] @@ -2453,20 +2428,6 @@ class Window(QMainWindow): self.signals.signal_set_next_row.emit(self.current.playlist_id) self.clear_selection() - # playlist_tab = self.active_tab() - # if playlist_tab: - # playlist_tab.set_row_as_next_track() - # else: - # log.error("No active tab") - - # @log_call - def set_tab_colour(self, widget: PlaylistTab, colour: QColor) -> None: - """ - Find the tab containing the widget and set the text colour - """ - - idx = self.playlist_section.tabPlaylist.indexOf(widget) - self.playlist_section.tabPlaylist.tabBar().setTabTextColor(idx, colour) def show_current(self) -> None: """Scroll to show current track""" @@ -2476,10 +2437,9 @@ class Window(QMainWindow): def show_warning(self, title: str, body: str) -> None: """ - Display a warning dialog + Handle show_warning_signal and display a warning dialog """ - print(f"show_warning({title=}, {body=})") QMessageBox.warning(self, title, body) def show_next(self) -> None: @@ -2490,6 +2450,7 @@ class Window(QMainWindow): def show_status_message(self, message: str, timing: int) -> None: """ + Handle status_message_signal. Show status message in status bar for timing milliseconds Clear message if message is null string """ @@ -2501,7 +2462,7 @@ class Window(QMainWindow): self.statusbar.clearMessage() def show_track(self, playlist_track: PlaylistRow) -> None: - """Scroll to show track in plt""" + """Scroll to show track""" # Switch to the correct tab playlist_id = playlist_track.playlist_id @@ -2519,20 +2480,19 @@ class Window(QMainWindow): f"show_track() can't find current playlist tab {playlist_id=}" ) - self.active_tab().scroll_to_top(playlist_track.row_number) + self._active_tab().scroll_to_top(playlist_track.row_number) # @log_call def stop(self, checked: bool = False) -> None: """Stop playing immediately""" - self.stop_autoplay = True if self.track_sequence.current: self.track_sequence.current.stop() def tab_change(self) -> None: """Called when active tab changed""" - self.active_tab().tab_live() + self._active_tab().tab_live() def tick_10ms(self) -> None: """ @@ -2551,9 +2511,11 @@ class Window(QMainWindow): try: self.track_sequence.current.check_for_end_of_track() - # Update intro counter if applicable and, if updated, return - # because playing an intro takes precedence over timing a + # 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 # preview. + intro_ms_remaining = self.track_sequence.current.time_remaining_intro() if intro_ms_remaining > 0: self.footer_section.label_intro_timer.setText( @@ -2572,7 +2534,6 @@ class Window(QMainWindow): # current track ended during servicing tick pass - # Ensure preview button is reset if preview finishes playing # Update preview timer if self.footer_section.btnPreview.isChecked(): if self.preview_manager.is_playing(): @@ -2584,6 +2545,8 @@ class Window(QMainWindow): f"{int(minutes)}:{seconds:04.1f}" ) else: + # Ensure preview button is reset if preview has finished + # playing self.footer_section.btnPreview.setChecked(False) self.footer_section.label_intro_timer.setText("0.0") self.footer_section.label_intro_timer.setStyleSheet("") diff --git a/app/playlistmodel.py b/app/playlistmodel.py index 7187773..50746c4 100644 --- a/app/playlistmodel.py +++ b/app/playlistmodel.py @@ -100,6 +100,7 @@ class PlaylistModel(QAbstractTableModel): self.signals.signal_playlist_selected_rows.connect(self.set_selected_rows) self.signals.signal_set_next_row.connect(self.set_next_row) self.signals.signal_track_started.connect(self.track_started) + self.signals.track_ended_signal.connect(self.previous_track_ended) # Populate self.playlist_rows for dto in ds.playlistrows_by_playlist(self.playlist_id): @@ -267,7 +268,7 @@ class PlaylistModel(QAbstractTableModel): track_id = play_track.track_id # Sanity check - 1 if not track_id: - raise ApplicationError("current_track_started() called with no track_id") + raise ApplicationError("track_started() called with no track_id") # Sanity check - 2 if self.track_sequence.current is None: @@ -960,15 +961,19 @@ class PlaylistModel(QAbstractTableModel): return # @log_call - def previous_track_ended(self) -> None: + def previous_track_ended(self, playlist_id: int) -> None: """ - Notification from musicmuster that the previous track has ended. + Notification from track_ended_signal that the previous track has ended. Actions required: - sanity check - update display """ + if playlist_id != self.playlist_id: + # Not for us + return + # Sanity check if not self.track_sequence.previous: log.error( diff --git a/app/playlistrow.py b/app/playlistrow.py index b813efb..15ffd03 100644 --- a/app/playlistrow.py +++ b/app/playlistrow.py @@ -203,7 +203,7 @@ class PlaylistRow: self.fade_graph.clear() # Ensure that player is released self.music.fade(0) - self.signals.track_ended_signal.emit() + self.signals.track_ended_signal.emit(self.playlist_id) self.end_of_track_signalled = True def drop3db(self, enable: bool) -> None: @@ -221,7 +221,8 @@ class PlaylistRow: self.resume_marker = self.music.get_position() self.music.fade(fade_seconds) - self.signals.track_ended_signal.emit() + self.signals.track_ended_signal.emit(self.playlist_id) + self.end_of_track_signalled = True def is_playing(self) -> bool: """