home · contact · privacy
Re-organize Player code.
authorChristian Heller <c.heller@plomlompom.de>
Mon, 16 Dec 2024 13:52:49 +0000 (14:52 +0100)
committerChristian Heller <c.heller@plomlompom.de>
Mon, 16 Dec 2024 13:52:49 +0000 (14:52 +0100)
src/ytplom/http.py
src/ytplom/misc.py
src/ytplom/primitives.py

index 4dbe6514b6e0f34fe5be70e437b12700291ad713..165e5eb5d0716ba760728a7537ed98272838c709 100644 (file)
@@ -161,7 +161,7 @@ class _TaskHandler(BaseHTTPRequestHandler):
         elif 'stop' == command:
             self.server.player.toggle_run()
         elif 'reload' == command:
-            self.server.player.reload()
+            self.server.player.load_files_and_start()
         elif command.startswith('jump_'):
             self.server.player.jump_to(int(command.split('_')[1]))
         elif command.startswith('up_'):
@@ -416,7 +416,7 @@ class _TaskHandler(BaseHTTPRequestHandler):
                 if 'playlist' in params.as_dict:
                     data['playlist_files'] = [
                         {'rel_path': str(f.rel_path), 'digest': f.digest.b64}
-                        for f in self.server.player.files]
+                        for f in self.server.player.playlist]
                 try:
                     self.wfile.write(
                             f'data: {json_dumps(data)}\n\n'.encode())
index 6cadf49b76889bb1c8879e8d29c8150f66735f57..4f30cf5a82f08f2feb7ada652f420cb97b6cfede 100644 (file)
@@ -501,25 +501,32 @@ class QuotaLog(DbData):
 
 
 class Player:
-    """MPV representation with some additional features."""
+    """Feature-adding wrapper/manager of MPV and its playlist."""
     _idx: int
+    last_update: DatetimeStr
 
     def __init__(self,
                  whitelist_tags_display: TagSet,
                  whitelist_tags_prefilter: TagSet,
                  needed_tags_prefilter: TagSet,
                  ) -> None:
-        self.last_update = DatetimeStr('')
-        self._mpv: Optional[MPV] = None
-        self._kill_queue: Queue = Queue()
-        self._monitoring_kill = False
+        # filters setup
         self.filter_path = FilterStr('')
+        self.needed_tags = TagSet()
         self._whitelist_tags_prefilter = whitelist_tags_prefilter
         self._whitelist_tags_display = whitelist_tags_display
         self._needed_tags_prefilter = needed_tags_prefilter
-        self.needed_tags = TagSet()
+        # actual playlist and player setup
+        self._mpv: Optional[MPV] = None
+        self._monitoring_kill: bool = False
+        self._kill_queue: Queue = Queue()
+        self.playlist: list[VideoFile] = []
         self.load_files_and_start()
 
+    def _signal_update(self) -> None:
+        """Update .last_update as signal player state has changed relevantly"""
+        self.last_update = DatetimeStr(datetime.now().strftime(TIMESTAMP_FMT))
+
     def _monitor_kill(self) -> None:
         """Properly enforce mpv shutdown from direct interaction with mpv
         client, as may happen with the "q" keystroke.  If not for the handling
@@ -540,37 +547,33 @@ class Player:
 
         Thread(target=kill_on_queue_get, daemon=True).start()
 
-    def load_files_and_start(self) -> None:
-        """Collect filtered files into playlist, start player."""
-        with DbConn() as conn:
-            known_files = {
-                f.full_path: f for f
-                in VideoFile.get_filtered(
-                        conn,
-                        self.filter_path,
-                        self._needed_tags_prefilter,
-                        self.needed_tags,
-                        self._whitelist_tags_prefilter,
-                        self._whitelist_tags_display)}
-        self.files = [known_files[p] for p in PATH_DOWNLOADS.iterdir()
-                      if p in known_files
-                      and p.is_file()
-                      and p.suffix[1:] in LEGAL_EXTENSIONS]
-        shuffle(self.files)
-        self._idx = 0
-        self._start_mpv()
+    def _kill_mpv(self) -> None:
+        if self._mpv:
+            self._mpv.terminate()
+            self._mpv = None
+        self._signal_update()
 
-    def _signal_update(self) -> None:
-        self.last_update = DatetimeStr(datetime.now().strftime(TIMESTAMP_FMT))
+    def _play_at_index(self):
+        self._signal_update()
+        if self._mpv:
+            self._mpv.command('playlist-play-index', self._idx)
 
     def _start_mpv(self) -> None:
+        """Start MPV at ._mpv and add some house-keeping and event handlers.
+
+        In detail:
+        - to properly enforce shutdowns even on direct client interaction, init
+          ._kill_queue and ._monitor_kill (see more thorough explanation there)
+        - bind changes to MPV's 'pause' property to ._signal_update
+        - build MPV's internal playlist from .playlist
+        - bind starting of files to ._signal_update and setting ._idx to MPV's
+          own playlist position index
+        - start playing
+        """
         self._mpv = MPV(input_default_bindings=True,
                         input_vo_keyboard=True,
                         config=True)
         self._monitor_kill()
-        self._mpv.observe_property('pause', lambda a, b: self._signal_update())
-        for path in [f.full_path for f in self.files]:
-            self._mpv.command('loadfile', path, 'append')
 
         @self._mpv.event_callback('start-file')
         def on_start_file(_) -> None:
@@ -581,35 +584,45 @@ class Player:
 
         @self._mpv.event_callback('shutdown')
         def on_shutdown(_) -> None:
-            """To properly enforce shutdown even on direct client interaction,
-            see self._monitor_kill for more thorough explanation.
-            """
             self._kill_queue.put(True)
 
+        self._mpv.observe_property('pause', lambda a, b: self._signal_update())
+        for path in [f.full_path for f in self.playlist]:
+            self._mpv.command('loadfile', path, 'append')
+        self._idx = 0
         self._play_at_index()
 
-    def _kill_mpv(self) -> None:
-        if self._mpv:
-            self._mpv.terminate()
-            self._mpv = None
-        self._signal_update()
-
-    def _play_at_index(self):
-        self._signal_update()
-        if self._mpv:
-            self._mpv.command('playlist-play-index', self._idx)
+    def load_files_and_start(self) -> None:
+        """Collect filtered files into playlist, shuffle, start player."""
+        with DbConn() as conn:
+            known_files = {
+                f.full_path: f for f
+                in VideoFile.get_filtered(
+                        conn,
+                        self.filter_path,
+                        self._needed_tags_prefilter,
+                        self.needed_tags,
+                        self._whitelist_tags_prefilter,
+                        self._whitelist_tags_display)}
+        self.playlist = [known_files[p] for p in PATH_DOWNLOADS.iterdir()
+                         if p in known_files
+                         and p.is_file()
+                         and p.suffix[1:] in LEGAL_EXTENSIONS]
+        shuffle(self.playlist)
+        self._kill_mpv()
+        self._start_mpv()
 
     @property
     def empty(self) -> bool:
         """Return if playlist empty."""
-        return 0 == len(self.files)
+        return 0 == len(self.playlist)
 
     @property
     def current_digest(self) -> Optional[Hash]:
         """Return hash digest ID of currently playing file."""
-        if not self.files:
+        if not self.playlist:
             return None
-        return self.files[self._idx].digest
+        return self.playlist[self._idx].digest
 
     @property
     def is_running(self) -> bool:
@@ -645,7 +658,7 @@ class Player:
 
     def next(self) -> None:
         """Move player to next item in playlist."""
-        if self._idx < len(self.files) - 1:
+        if self._idx < len(self.playlist) - 1:
             self._idx += 1
         self._play_at_index()
 
@@ -654,35 +667,30 @@ class Player:
         self._idx = target_idx
         self._play_at_index()
 
-    def move_entry(self, start_idx: int, upwards=True) -> None:
+    def move_entry(self, start_idx: int, upwards: bool = True) -> None:
         """Move playlist entry at start_idx up or down one step."""
         if (start_idx == self._idx
                 or (upwards and start_idx == self._idx + 1)
                 or ((not upwards) and start_idx == self._idx - 1)
                 or (upwards and start_idx < 1)
-                or ((not upwards) and start_idx > len(self.files) - 2)):
+                or ((not upwards) and start_idx > len(self.playlist) - 2)):
             return
         i0, i1 = start_idx, start_idx + (-1 if upwards else 1)
         if self._mpv:
             # NB: a functional playlist-move would do this in a single step,
             # but for some reason I don't seem to get it to do anything
-            path = self.files[i1].full_path
+            path = self.playlist[i1].full_path
             self._mpv.command('playlist-remove', i1)
             self._mpv.command('loadfile', path, 'insert-at', i0)
-        self.files[i0], self.files[i1] = self.files[i1], self.files[i0]
+        self.playlist[i0], self.playlist[i1] = (self.playlist[i1],
+                                                self.playlist[i0])
         self._signal_update()
 
-    def reload(self) -> None:
-        """Close MPV, empty filenames, restart."""
-        self._kill_mpv()
-        self.files.clear()
-        self.load_files_and_start()
-
     def inject_and_play(self, file: VideoFile) -> None:
         """Inject file after current title, then jump to it."""
-        if self.files:
+        if self.playlist:
             self._idx += 1
-        self.files.insert(self._idx, file)
+        self.playlist.insert(self._idx, file)
         if self._mpv:
             self._mpv.command('loadfile', file.full_path,
                               'insert-at', self._idx)
index ddc64d5d5b713333144337ba1c72987c3bee5342..50de4ea7eb92065d63b8e6236d0c73193fffc05f 100644 (file)
@@ -1,3 +1,4 @@
+"""Basic depended-ons not depending on anything else."""
 from pathlib import Path