From 5f5bb27a5fb9e9593c88b915ff51d21cfe935a97 Mon Sep 17 00:00:00 2001 From: Keith Edmunds Date: Fri, 2 Aug 2024 12:44:55 +0100 Subject: [PATCH] . --- app/classes.py | 116 +++++++++++++++++++++++++++++++-------------- app/musicmuster.py | 7 +++ 2 files changed, 87 insertions(+), 36 deletions(-) diff --git a/app/classes.py b/app/classes.py index 90838e9..53d563d 100644 --- a/app/classes.py +++ b/app/classes.py @@ -5,6 +5,7 @@ import ctypes from dataclasses import dataclass, field import datetime as dt from enum import auto, Enum +import os import platform import threading from time import sleep @@ -12,6 +13,7 @@ from typing import Any, Optional, NamedTuple # Third party imports import numpy as np +from pprint import pprint as pp import pyqtgraph as pg # type: ignore import vlc # type: ignore @@ -39,6 +41,7 @@ from helpers import ( ) lock = threading.Lock() +players: dict[int, str] = {} # Define the VLC callback function type VLC_LOG_CB = ctypes.CFUNCTYPE( @@ -199,6 +202,7 @@ class _FadeTrack(QRunnable): if not self.player: return + log.info("fade starting") # Reduce volume logarithmically total_steps = self.fade_seconds * Config.FADEOUT_STEPS_PER_SECOND db_reduction_per_step = Config.FADEOUT_DB / total_steps @@ -212,7 +216,13 @@ class _FadeTrack(QRunnable): ) sleep(1 / Config.FADEOUT_STEPS_PER_SECOND) - self.player.stop() + log.info("fade ended") + if self.player: + log.info(f"Releasing {self.player=}") + self.player.release() # Release resources + del players[id(self.player)] + pp(players) + self.player = None # Clear the reference class _Music: @@ -275,27 +285,6 @@ class _Music: else: self.start_dt = dt.datetime.now() - 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() - self.player_count -= 1 - log.debug(f"_Music.stop: Releasing player {p=}, {self.player_count=}") - p = None - def fade(self, fade_seconds: int) -> None: """ Fade the currently playing track. @@ -318,6 +307,13 @@ class _Music: # started without interfering here with lock: p = self.player + # Connect to the end-of-playback event + p.event_manager().event_attach( + vlc.EventType.MediaPlayerEndReached, self.on_playback_end + ) + del players[id(self.player)] + players[id(p)] = f"From fade {self.player=}" + pp(players) self.player = None pool = QThreadPool.globalInstance() @@ -367,6 +363,18 @@ class _Music: < dt.timedelta(microseconds=Config.PLAY_SETTLE) ) + def on_playback_end(self, *args, **kwargs): + """ + Release player when playing has ended + """ + + if self.player: + log.info(f"Releasing {self.player=}") + del players[id(self.player)] + pp(players) + self.player.release() # Release resources + self.player = None # Clear the reference + def play( self, path: str, @@ -394,6 +402,15 @@ class _Music: show_warning(None, "Error loading file", f"Cannot play file ({path})") return self.player = media.player_new_from_media() + log.info(f"Created {self.player=}") + players[id(self.player)] = os.path.basename(path) + pp(players) + + # Connect to the end-of-playback event + self.player.event_manager().event_attach( + vlc.EventType.MediaPlayerEndReached, self.on_playback_end + ) + if self.player: _ = self.player.play() self.set_volume(self.max_volume) @@ -456,6 +473,31 @@ class _Music: log.debug(f"Reset from {volume=}") sleep(0.1) + 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 + del players[id(self.player)] + players[id(p)] = f"From stop {self.player=}" + pp(players) + self.player = None + self.start_dt = None + + with lock: + p.stop() + p.release() + del players[id(p)] + pp(players) + log.info(f"_Music.stop: Releasing player {p=}, {self.player_count=}") + p = None + @singleton @dataclass @@ -514,7 +556,9 @@ class RowAndTrack: self.fade_at = playlist_row.track.fade_at self.intro = playlist_row.track.intro if playlist_row.track.playdates: - self.lastplayed = max([a.lastplayed for a in playlist_row.track.playdates]) + self.lastplayed = max( + [a.lastplayed for a in playlist_row.track.playdates] + ) else: self.lastplayed = Config.EPOCH self.path = playlist_row.track.path @@ -544,6 +588,7 @@ class RowAndTrack: self.start_time: Optional[dt.datetime] = None # Other object initialisation + self.music = _Music(name=Config.VLC_MAIN_PLAYER_NAME) self.signals = MusicMusterSignals() def __repr__(self) -> str: @@ -564,7 +609,7 @@ class RowAndTrack: if self.end_of_track_signalled: return - if not self.player.is_playing(): + if not self.music.is_playing(): self.start_time = None if self.fade_graph: self.fade_graph.clear() @@ -596,15 +641,15 @@ class RowAndTrack: """ if enable: - self.player.set_volume(volume=Config.VLC_VOLUME_DROP3db, set_default=False) + self.music.set_volume(volume=Config.VLC_VOLUME_DROP3db, set_default=False) else: - self.player.set_volume(volume=Config.VLC_VOLUME_DEFAULT, set_default=False) + self.music.set_volume(volume=Config.VLC_VOLUME_DEFAULT, set_default=False) def fade(self, fade_seconds: int = Config.FADEOUT_SECONDS) -> None: """Fade music""" - self.resume_marker = self.player.get_position() - self.player.fade(fade_seconds) + self.resume_marker = self.music.get_position() + self.music.fade(fade_seconds) self.signal_end_of_track() def is_playing(self) -> bool: @@ -615,21 +660,21 @@ class RowAndTrack: if self.start_time is None: return False - return self.player.is_playing() + return self.music.is_playing() def move_back(self, ms: int = Config.PREVIEW_BACK_MS) -> None: """ Rewind player by ms milliseconds """ - self.player.adjust_by_ms(ms * -1) + self.music.adjust_by_ms(ms * -1) def move_forward(self, ms: int = Config.PREVIEW_ADVANCE_MS) -> None: """ Rewind player by ms milliseconds """ - self.player.adjust_by_ms(ms) + self.music.adjust_by_ms(ms) def play(self, position: Optional[float] = None) -> None: """Play track""" @@ -639,8 +684,7 @@ class RowAndTrack: self.start_time = now # Initialise player - self.player = _Music(name=Config.VLC_MAIN_PLAYER_NAME) - self.player.play(self.path, start_time=now, position=position) + self.music.play(self.path, start_time=now, position=position) self.end_time = now + dt.timedelta(milliseconds=self.duration) @@ -658,7 +702,7 @@ class RowAndTrack: Restart player """ - self.player.adjust_by_ms(self.time_playing() * -1) + self.music.adjust_by_ms(self.time_playing() * -1) def set_forecast_start_time( self, modified_rows: list[int], start: Optional[dt.datetime] @@ -705,7 +749,7 @@ class RowAndTrack: Stop this track playing """ - self.resume_marker = self.player.get_position() + self.resume_marker = self.music.get_position() self.fade(fade_seconds) # Reset fade graph @@ -720,7 +764,7 @@ class RowAndTrack: if self.start_time is None: return 0 - return self.player.get_playtime() + return self.music.get_playtime() def time_remaining_intro(self) -> int: """ diff --git a/app/musicmuster.py b/app/musicmuster.py index e2ada0a..5d3ebf5 100755 --- a/app/musicmuster.py +++ b/app/musicmuster.py @@ -316,6 +316,7 @@ class Window(QMainWindow, Ui_MainWindow): self.action_quicklog.activated.connect(self.quicklog) self.load_last_playlists() + self.stop_autoplay = False def about(self) -> None: """Get git tag and database name""" @@ -680,6 +681,10 @@ class Window(QMainWindow, Ui_MainWindow): 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) -> None: """Export the current playlist to an m3u file""" @@ -1151,6 +1156,7 @@ class Window(QMainWindow, Ui_MainWindow): self.btnDrop3db.setChecked(False) # Play (new) current track + log.info(f"Play: {track_sequence.current.title}") track_sequence.current.play(position) # Update clocks now, don't wait for next tick @@ -1623,6 +1629,7 @@ class Window(QMainWindow, Ui_MainWindow): def stop(self) -> None: """Stop playing immediately""" + self.stop_autoplay = True if track_sequence.current: track_sequence.current.stop()