home · contact · privacy
Don't start right into playback, simplify player control.
authorChristian Heller <c.heller@plomlompom.de>
Sun, 22 Dec 2024 13:21:27 +0000 (14:21 +0100)
committerChristian Heller <c.heller@plomlompom.de>
Sun, 22 Dec 2024 13:21:27 +0000 (14:21 +0100)
src/templates/_base.tmpl
src/ytplom/http.py
src/ytplom/misc.py

index 62483a3d3596292da9804fd928a4624b6d523675..aa190a9ab6b74dada6aee22bd5ab1cf52a21ba81 100644 (file)
@@ -54,12 +54,14 @@ function player_command(command) {
     send_to({command: [command]}, PATH_PLAYER); }
 
 event_handlers.push(function(data) {  // update player state
-    for (const [id, text] of [["playing_tags", data.title_tags ? `(tags: ${data.title_tags})` : ""],
-                              ["a_playing", data.title],
-                              ["player_state", data.running ? (data.paused ? "paused" : "playing") : "stopped"],
-                              ["btn_pause", data.paused ? "resume" : "pause"],
-                              ["btn_stop", data.running ? "stop" : "play"]]) {
+    for (const [id, text] of [
+            ["playing_tags", data.title_tags ? `(tags: ${data.title_tags})` : ""],
+            ["a_playing", data.title],
+            ["player_state", data.is_running ? (data.is_playing ? "playing:" : "paused:") : "stopped" + (data.title ? ':' : '')],
+            ["btn_play", data.is_playing ? "pause" : "play"]]) {
         document.getElementById(id).textContent = text; }
+    for (const btn of document.getElementsByClassName("btn_if_can_play")) {
+        btn.disabled = !data.can_play; }
     document.getElementById("a_playing").href = data.title_digest ? `${PATH_PREFIX_FILE}${data.title_digest}` : PATH_PLAYLIST ; })
 
 {% block script %}
@@ -80,11 +82,10 @@ td, th { vertical-align: top; text-align: left; }
 · {{ macros.link_if("files" != selected, page_names.files) }}
 · {{ macros.link_if("yt_queries" != selected, page_names.yt_queries, "queries") }}
 <hr />
-<button onclick="player_command('prev')">prev</button>
-<button onclick="player_command('next')">next</button>
-<button id="btn_pause" onclick="player_command('pause')"></button>
-<button id="btn_stop" onclick="player_command('stop')"></button>
-· <span id="player_state" /></span>: <a id="a_playing"></a> <span id="playing_tags"></span>
+<button class="btn_if_can_play" onclick="player_command('prev')">prev</button>
+<button class="btn_if_can_play" onclick="player_command('next')">next</button>
+<button id="btn_play" class="btn_if_can_play" onclick="player_command('play')">play</button>
+· <span id="player_state" /></span> <a id="a_playing"></a> <span id="playing_tags"></span>
 <hr />
 {% block body %}
 {% endblock %}
index c31d62bdd42f0e698a4507c6b47f3c7fb825be5e..7f4780f9da9b88a9a3595a01e89fa4142b52bff5 100644 (file)
@@ -144,16 +144,14 @@ class _TaskHandler(BaseHTTPRequestHandler):
 
     def _receive_player_command(self, postvars: _ReqMap) -> None:
         command = postvars.first_for('command')
-        if 'pause' == command:
-            self.server.player.toggle_pause()
+        if 'play' == command:
+            self.server.player.toggle_play()
         elif 'prev' == command:
             self.server.player.prev()
         elif 'next' == command:
             self.server.player.next()
-        elif 'stop' == command:
-            self.server.player.toggle_run()
         elif 'reload' == command:
-            self.server.player.load_files_and_start()
+            self.server.player.load_files_and_mpv()
         elif command.startswith('jump_'):
             self.server.player.jump_to(int(command.split('_')[1]))
         elif command.startswith('up_'):
@@ -358,8 +356,6 @@ class _TaskHandler(BaseHTTPRequestHandler):
                         headers=[(_HEADER_CONTENT_TYPE, _HEADER_APP_JSON)])
 
     def _send_playlist(self) -> None:
-        if self.server.player.empty:
-            self.server.player.load_files_and_start()
         self._send_rendered_template(
                 _NAME_TEMPLATE_PLAYLIST,
                 {'selected': 'playlist',
@@ -370,7 +366,7 @@ class _TaskHandler(BaseHTTPRequestHandler):
         self._send_http(headers=[(_HEADER_CONTENT_TYPE, 'text/event-stream'),
                                  ('Cache-Control', 'no-cache'),
                                  ('Connection', 'keep-alive')])
-        playing: Optional[VideoFile] = None
+        selected: Optional[VideoFile] = None
         last_sent = ''
         payload: dict[str, Any] = {}
         time_last_write = 0.0
@@ -387,29 +383,29 @@ class _TaskHandler(BaseHTTPRequestHandler):
                 time_last_write = time()
             payload.clear()
             if not self.server.player.current_digest:
-                playing = None
-            elif ((not playing)
-                  or (playing.digest != self.server.player.current_digest)):
+                selected = None
+            elif ((not selected)
+                  or (selected.digest != self.server.player.current_digest)):
                 with DbConn() as conn:
-                    playing = VideoFile.get_one_with_whitelist_tags_display(
+                    selected = VideoFile.get_one_with_whitelist_tags_display(
                         conn,
                         self.server.player.current_digest,
                         self.server.config.whitelist_tags_display)
             if last_sent < self.server.player.last_update:
                 last_sent = self.server.player.last_update
                 title, tags, digest = '', '', ''
-                if playing:
-                    tags = playing.tags_showable.joined
-                    title = str(playing.rel_path)
-                    digest = playing.digest.b64
-                payload['last_update'] = self.server.player.last_update
-                payload['running'] = self.server.player.is_running
-                payload['paused'] = self.server.player.is_paused
-                payload['idx'] = self.server.player.idx
+                if selected:
+                    tags = selected.tags_showable.joined
+                    title = str(selected.rel_path)
+                    digest = selected.digest.b64
+                payload['is_running'] = self.server.player.is_running
+                payload['is_playing'] = self.server.player.is_playing
+                payload['can_play'] = self.server.player.can_play
                 payload['title_tags'] = tags
                 payload['title_digest'] = digest
                 payload['title'] = title
                 if 'playlist' in params.as_dict:
+                    payload['idx'] = self.server.player.idx
                     payload['playlist_files'] = [
                         {'rel_path': str(f.rel_path), 'digest': f.digest.b64}
                         for f in self.server.player.playlist]
index 367177964ed7e3f0e28a17d93e524cc4ef92f675..86254bf726440c19f8c6091817c00a47ce3ecd0f 100644 (file)
@@ -521,7 +521,7 @@ class Player:
         self._monitoring_kill: bool = False
         self._kill_queue: Queue = Queue()
         self.playlist: list[VideoFile] = []
-        self.load_files_and_start()
+        self.load_files_and_mpv()
 
     def _signal_update(self) -> None:
         """Update .last_update as signal player state has changed relevantly"""
@@ -598,9 +598,8 @@ class Player:
         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 load_files_and_start(self) -> None:
+    def load_files_and_mpv(self) -> None:
         """Collect filtered files into playlist, shuffle, start player."""
         with DbConn() as conn:
             known_files = {
@@ -625,11 +624,6 @@ class Player:
         """Read-only access to ._idx."""
         return self._idx
 
-    @property
-    def empty(self) -> bool:
-        """Return if playlist empty."""
-        return 0 == len(self.playlist)
-
     @property
     def current_digest(self) -> Optional[Hash]:
         """Return hash digest ID of currently playing file."""
@@ -639,29 +633,30 @@ class Player:
 
     @property
     def is_running(self) -> bool:
-        """Return if player is running/available."""
-        return bool(self._mpv)
+        """Return if player is up and has "playing" (possibly paused) title."""
+        return bool(self._mpv
+                    and len(self._mpv.playlist) > self._idx
+                    and 'playing' in self._mpv.playlist[self._idx])
 
     @property
-    def is_paused(self) -> bool:
-        """Return if player is paused."""
-        if self._mpv:
-            return self._mpv.pause
-        return False
+    def is_playing(self) -> bool:
+        """Return if currently playing (non-paused)."""
+        return bool(self._mpv and self.is_running and not self._mpv.pause)
 
-    def toggle_run(self) -> None:
-        """Toggle player running."""
-        if self._mpv:
-            self._kill_mpv()
-        else:
-            self._start_mpv()
-        self._signal_update()
+    @property
+    def can_play(self) -> bool:
+        """Return if playlist non-empty."""
+        return len(self.playlist) > 0
 
-    def toggle_pause(self) -> None:
-        """Toggle player pausing."""
-        if self._mpv:
+    def toggle_play(self) -> None:
+        """Toggle playback, i.e. player pause _if_ playing, else start play."""
+        if not self._mpv:
+            self._start_mpv()
+        assert self._mpv is not None
+        if 'playing' in self._mpv.playlist[self._idx]:
             self._mpv.pause = not self._mpv.pause
-            self._signal_update()
+        else:
+            self._play_at_index()
 
     def prev(self) -> None:
         """Move player to previous item in playlist."""