.
This commit is contained in:
parent
50d1e8bd4a
commit
5f5bb27a5f
116
app/classes.py
116
app/classes.py
@ -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:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -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()
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user