<head>
<meta charset="UTF-8">
<script>
+const MS_IN_S = 1000;
const RETRY_INTERVAL_S = 5;
const PATH_EVENTS = "/{{page_names.events}}";
const PATH_PLAYER = "/{{page_names.player}}";
events_stream.close();
if (while_connecting) {
console.log("Error seemed connection-related, trying reconnect.");
- setTimeout(connect_events, RETRY_INTERVAL_S * 1000);
+ setTimeout(connect_events, RETRY_INTERVAL_S * MS_IN_S);
} else {
console.log("Error does not seem connection-related, therefore aborting.");
}
});
}
+var timestamp_interval = null;
+
event_handlers.player = function(data) {
const div = document.getElementById("player_controls");
div.innerHTML = "";
add_player_btn_to(div, "next", "next", !data.can_play);
add_player_btn_to(div, data.is_playing ? "pause" : "play", "play", !data.can_play);
add_text_to(div, " · ");
- add_text_to(div, data.is_running ? (data.is_playing ? "playing:" : "paused:")
- : "stopped" + (data.title ? ':' : ''));
+ add_text_to(div, data.is_running ? (data.is_playing ? "playing" : "paused")
+ : "stopped");
if (data.title_digest) {
+ function format_seconds(total_seconds) {
+ if (total_seconds < 0) {
+ return "?";
+ }
+ const seconds = total_seconds % 60;
+ const minutes = Math.floor(total_seconds / 60);
+ return `${minutes}:` + (seconds < 10 ? '0' : '') + `${seconds}`
+ }
+ add_text_to(div, " (");
+ const timestamp_span = add_child_to("span", div);
+ clearInterval(timestamp_interval);
+ let timestamp = data.timestamp;
+ timestamp_span.textContent = format_seconds(timestamp);
+ if (data.is_playing) {
+ timestamp_interval = setInterval(function() {
+ timestamp += 1;
+ timestamp_span.textContent = format_seconds(timestamp);
+ }, MS_IN_S / data.speed);
+ }
+ add_text_to(div, "/" + format_seconds(data.duration) + "): ");
add_a_to(div, data.title, `${PATH_PREFIX_FILE}${data.title_digest}`);
add_text_to(div, " · ");
add_tag_links_to(div, data.title_tags);
+
}
};
self.server.player.move_entry(int(command.split('_')[1]), False)
elif command.startswith('rm_'):
self.server.player.remove_by_idx(int(command.split('_')[1]))
- elif command.startswith('inject_') or command.startswith('injectplay_'):
+ elif (command.startswith('inject_')
+ or command.startswith('injectplay_')):
command, digest = command.split('_', 1)
with DbConn() as conn:
file = VideoFile.get_one(conn, Hash.from_b64(digest))
'is_running': self.server.player.is_running,
'is_playing': self.server.player.is_playing,
'can_play': self.server.player.can_play,
+ 'timestamp': self.server.player.timestamp,
+ 'duration': self.server.player.duration,
+ 'speed': self.server.player.speed,
'title_tags': tags,
'title_digest': digest,
'title': title
self._monitoring_kill: bool = False
self._kill_queue: Queue = Queue()
self.playlist: list[VideoFile] = []
+ self.speed = -1.0
+ self.timestamp = -1
+ self.duration = -1
self.load_files_and_mpv()
def _signal_update(self) -> None:
- """Update .last_update as signal player state has changed relevantly"""
+ """Update .last_update as signal player state has changed relevantly.
+
+ If possible, also updates current player timestamp.
+ """
self.last_update = _now_string()
+ if self._mpv:
+ self.timestamp = (int(self._mpv.time_pos) if self._mpv.time_pos
+ else -1)
def _monitor_kill(self) -> None:
"""Properly enforce mpv shutdown from direct interaction with mpv
- bind starting of files to ._signal_update and setting ._idx to MPV's
own playlist position index
- bind ending last file to re-starting at playlist start
+ - bind what affects currently played duration, timestamp to respective
+ updates
- start playing
"""
self._mpv = MPV(input_default_bindings=True,
def on_shutdown(_) -> None:
self._kill_queue.put(True)
+ @self._mpv.event_callback('seek')
+ def on_seek(*_) -> None:
+ self._signal_update()
+
+ def on_duration_change(_, duration_in_s):
+ self.duration = int(duration_in_s) if duration_in_s else -1
+ self._signal_update()
+
+ def on_speed_change(_, speed):
+ self.speed = speed
+ self._signal_update()
+
self._mpv.observe_property('pause', lambda a, b: self._signal_update())
+ self._mpv.observe_property('duration', on_duration_change)
+ self._mpv.observe_property('speed', on_speed_change)
for path in [f.full_path for f in self.playlist]:
self._mpv.command('loadfile', path, 'append')
self._idx = 0
video_id = self._to_download.pop(0)
url = f'{YOUTUBE_URL_PREFIX}{video_id}'
with YoutubeDL(YT_DL_PARAMS | {'progress_hooks': [hook]}) as ydl:
- self._update_status(video_id, f'preparing download')
+ self._update_status(video_id, 'preparing download')
info = ydl.sanitize_info(ydl.extract_info(url, download=False))
for requested in info['requested_formats']:
estimated_total += requested['filesize_approx']