This commit is contained in:
Keith Edmunds 2024-08-02 12:44:55 +01:00
parent 50d1e8bd4a
commit 5f5bb27a5f
2 changed files with 87 additions and 36 deletions

View File

@ -5,6 +5,7 @@ import ctypes
from dataclasses import dataclass, field from dataclasses import dataclass, field
import datetime as dt import datetime as dt
from enum import auto, Enum from enum import auto, Enum
import os
import platform import platform
import threading import threading
from time import sleep from time import sleep
@ -12,6 +13,7 @@ from typing import Any, Optional, NamedTuple
# Third party imports # Third party imports
import numpy as np import numpy as np
from pprint import pprint as pp
import pyqtgraph as pg # type: ignore import pyqtgraph as pg # type: ignore
import vlc # type: ignore import vlc # type: ignore
@ -39,6 +41,7 @@ from helpers import (
) )
lock = threading.Lock() lock = threading.Lock()
players: dict[int, str] = {}
# Define the VLC callback function type # Define the VLC callback function type
VLC_LOG_CB = ctypes.CFUNCTYPE( VLC_LOG_CB = ctypes.CFUNCTYPE(
@ -199,6 +202,7 @@ class _FadeTrack(QRunnable):
if not self.player: if not self.player:
return return
log.info("fade starting")
# Reduce volume logarithmically # Reduce volume logarithmically
total_steps = self.fade_seconds * Config.FADEOUT_STEPS_PER_SECOND total_steps = self.fade_seconds * Config.FADEOUT_STEPS_PER_SECOND
db_reduction_per_step = Config.FADEOUT_DB / total_steps db_reduction_per_step = Config.FADEOUT_DB / total_steps
@ -212,7 +216,13 @@ class _FadeTrack(QRunnable):
) )
sleep(1 / Config.FADEOUT_STEPS_PER_SECOND) 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: class _Music:
@ -275,27 +285,6 @@ class _Music:
else: else:
self.start_dt = dt.datetime.now() - dt.timedelta(milliseconds=ms) 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: def fade(self, fade_seconds: int) -> None:
""" """
Fade the currently playing track. Fade the currently playing track.
@ -318,6 +307,13 @@ class _Music:
# started without interfering here # started without interfering here
with lock: with lock:
p = self.player 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 self.player = None
pool = QThreadPool.globalInstance() pool = QThreadPool.globalInstance()
@ -367,6 +363,18 @@ class _Music:
< dt.timedelta(microseconds=Config.PLAY_SETTLE) < 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( def play(
self, self,
path: str, path: str,
@ -394,6 +402,15 @@ class _Music:
show_warning(None, "Error loading file", f"Cannot play file ({path})") show_warning(None, "Error loading file", f"Cannot play file ({path})")
return return
self.player = media.player_new_from_media() 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: if self.player:
_ = self.player.play() _ = self.player.play()
self.set_volume(self.max_volume) self.set_volume(self.max_volume)
@ -456,6 +473,31 @@ 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.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 @singleton
@dataclass @dataclass
@ -514,7 +556,9 @@ class RowAndTrack:
self.fade_at = playlist_row.track.fade_at self.fade_at = playlist_row.track.fade_at
self.intro = playlist_row.track.intro self.intro = playlist_row.track.intro
if playlist_row.track.playdates: 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: else:
self.lastplayed = Config.EPOCH self.lastplayed = Config.EPOCH
self.path = playlist_row.track.path self.path = playlist_row.track.path
@ -544,6 +588,7 @@ class RowAndTrack:
self.start_time: Optional[dt.datetime] = None self.start_time: Optional[dt.datetime] = None
# Other object initialisation # Other object initialisation
self.music = _Music(name=Config.VLC_MAIN_PLAYER_NAME)
self.signals = MusicMusterSignals() self.signals = MusicMusterSignals()
def __repr__(self) -> str: def __repr__(self) -> str:
@ -564,7 +609,7 @@ class RowAndTrack:
if self.end_of_track_signalled: if self.end_of_track_signalled:
return return
if not self.player.is_playing(): if not self.music.is_playing():
self.start_time = None self.start_time = None
if self.fade_graph: if self.fade_graph:
self.fade_graph.clear() self.fade_graph.clear()
@ -596,15 +641,15 @@ class RowAndTrack:
""" """
if enable: 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: 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: def fade(self, fade_seconds: int = Config.FADEOUT_SECONDS) -> None:
"""Fade music""" """Fade music"""
self.resume_marker = self.player.get_position() self.resume_marker = self.music.get_position()
self.player.fade(fade_seconds) self.music.fade(fade_seconds)
self.signal_end_of_track() self.signal_end_of_track()
def is_playing(self) -> bool: def is_playing(self) -> bool:
@ -615,21 +660,21 @@ class RowAndTrack:
if self.start_time is None: if self.start_time is None:
return False return False
return self.player.is_playing() return self.music.is_playing()
def move_back(self, ms: int = Config.PREVIEW_BACK_MS) -> None: def move_back(self, ms: int = Config.PREVIEW_BACK_MS) -> None:
""" """
Rewind player by ms milliseconds 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: def move_forward(self, ms: int = Config.PREVIEW_ADVANCE_MS) -> None:
""" """
Rewind player by ms milliseconds 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: def play(self, position: Optional[float] = None) -> None:
"""Play track""" """Play track"""
@ -639,8 +684,7 @@ class RowAndTrack:
self.start_time = now self.start_time = now
# Initialise player # Initialise player
self.player = _Music(name=Config.VLC_MAIN_PLAYER_NAME) self.music.play(self.path, start_time=now, position=position)
self.player.play(self.path, start_time=now, position=position)
self.end_time = now + dt.timedelta(milliseconds=self.duration) self.end_time = now + dt.timedelta(milliseconds=self.duration)
@ -658,7 +702,7 @@ class RowAndTrack:
Restart player 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( def set_forecast_start_time(
self, modified_rows: list[int], start: Optional[dt.datetime] self, modified_rows: list[int], start: Optional[dt.datetime]
@ -705,7 +749,7 @@ class RowAndTrack:
Stop this track playing Stop this track playing
""" """
self.resume_marker = self.player.get_position() self.resume_marker = self.music.get_position()
self.fade(fade_seconds) self.fade(fade_seconds)
# Reset fade graph # Reset fade graph
@ -720,7 +764,7 @@ class RowAndTrack:
if self.start_time is None: if self.start_time is None:
return 0 return 0
return self.player.get_playtime() return self.music.get_playtime()
def time_remaining_intro(self) -> int: def time_remaining_intro(self) -> int:
""" """

View File

@ -316,6 +316,7 @@ class Window(QMainWindow, Ui_MainWindow):
self.action_quicklog.activated.connect(self.quicklog) self.action_quicklog.activated.connect(self.quicklog)
self.load_last_playlists() self.load_last_playlists()
self.stop_autoplay = False
def about(self) -> None: def about(self) -> None:
"""Get git tag and database name""" """Get git tag and database name"""
@ -680,6 +681,10 @@ class Window(QMainWindow, Ui_MainWindow):
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)
# autoplay
if not self.stop_autoplay:
self.play_next()
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"""
@ -1151,6 +1156,7 @@ class Window(QMainWindow, Ui_MainWindow):
self.btnDrop3db.setChecked(False) self.btnDrop3db.setChecked(False)
# Play (new) current track # Play (new) current track
log.info(f"Play: {track_sequence.current.title}")
track_sequence.current.play(position) track_sequence.current.play(position)
# Update clocks now, don't wait for next tick # Update clocks now, don't wait for next tick
@ -1623,6 +1629,7 @@ class Window(QMainWindow, Ui_MainWindow):
def stop(self) -> None: def stop(self) -> None:
"""Stop playing immediately""" """Stop playing immediately"""
self.stop_autoplay = True
if track_sequence.current: if track_sequence.current:
track_sequence.current.stop() track_sequence.current.stop()