<script>
const RETRY_INTERVAL_S = 5;
-const PING_INTERVAL_S = 1;
const PATH_EVENTS = "/{{page_names.events}}";
-const PATH_EVENTS_PING = "/{{page_names.events_ping}}";
const PATH_PLAYER = "/{{page_names.player}}";
const PATH_PLAYLIST = "/{{page_names.playlist}}";
const PATH_PREFIX_FILE = "/{{page_names.file}}/";
var event_handlers = [];
var events_params = "";
-var client_id = null;
+var events_stream = null;
-setInterval(function() {if (client_id) { send_to({client_id: [client_id]}, PATH_EVENTS_PING); }},
- PING_INTERVAL_S * 1000);
+window.addEventListener(
+ "beforeunload", function() {
+ if (events_stream) {
+ events_stream.close(); } });
function connect_events() {
- const events_stream = new EventSource(`${PATH_EVENTS}?${events_params}`);
+ events_stream = new EventSource(`${PATH_EVENTS}?${events_params}`);
events_stream.onmessage = function(event) {
const data = JSON.parse(event.data);
- if (data.your_id) {
- client_id = data.your_id;
+ if (data.ping) {
return; }
for (let i = 0; i < event_handlers.length; i++) {
event_handlers[i](data); }}
events_stream.onerror = function(error) {
- client_id = null;
const while_connecting = events_stream.readyState == events_stream.CONNECTING;
console.log(`Error on ${PATH_EVENTS} connection:`, error);
events_stream.close();
from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
from json import dumps as json_dumps, loads as json_loads
from pathlib import Path
-from time import time, sleep
+from time import sleep, time
from typing import Any, Generator, Optional
from urllib.parse import parse_qs, urlparse
from urllib.request import urlretrieve
from urllib.error import HTTPError
-from uuid import uuid4
from jinja2 import ( # type: ignore
Environment as JinjaEnv, FileSystemLoader as JinjaFSLoader)
from ytplom.db import Hash, DbConn
PAGE_NAMES: dict[str, Path] = {
'download': Path('dl'),
'events': Path('events'),
- 'events_ping': Path('events_ping'),
'file': Path('file'),
'files': Path('files'),
'missing': Path('missing'),
}
# misc
-_MAX_PING_AGE_S = 2
+_PING_INTERVAL_S = 1
+_EVENTS_UPDATE_INTERVAL_S = 0.1
_HEADER_CONTENT_TYPE = 'Content-Type'
_HEADER_APP_JSON = 'application/json'
*args, **kwargs)
self.config = config
self.jinja = JinjaEnv(loader=JinjaFSLoader(_PATH_TEMPLATES))
- self.event_pings: dict[str, float] = {}
self.player = Player(config.whitelist_tags_display,
config.whitelist_tags_prefilter,
config.needed_tags_prefilter)
self._receive_yt_query(QueryText(postvars.first_for('query')))
elif PAGE_NAMES['player'] == page_name:
self._receive_player_command(postvars)
- elif PAGE_NAMES['events_ping'] == page_name:
- self._receive_events_ping(postvars.first_for('client_id'))
-
- def _receive_events_ping(self, client_id: str) -> None:
- if client_id not in self.server.event_pings:
- self._send_http('unknown client ID', code=400)
- else:
- self.server.event_pings[client_id] = time()
- self._send_http('OK')
def _receive_player_command(self, postvars: _ReqMap) -> None:
command = postvars.first_for('command')
self._send_http(headers=[(_HEADER_CONTENT_TYPE, 'text/event-stream'),
('Cache-Control', 'no-cache'),
('Connection', 'keep-alive')])
- client_id = str(uuid4())
- self.server.event_pings[client_id] = time()
playing: Optional[VideoFile] = None
last_sent = ''
- init_msg = {'your_id': client_id}
- self.wfile.write(f'data: {json_dumps(init_msg)}\n\n'.encode())
- self.wfile.flush()
+ payload: dict[str, Any] = {}
+ time_last_write = 0.0
while True:
- min_ping_time = time() - _MAX_PING_AGE_S
- if (client_id in self.server.event_pings
- and self.server.event_pings[client_id] < min_ping_time):
- del self.server.event_pings[client_id]
- break
+ if not payload and time_last_write < time() - _PING_INTERVAL_S:
+ payload['ping'] = True
+ if payload:
+ payload_encoded = f'data: {json_dumps(payload)}\n\n'.encode()
+ try:
+ self.wfile.write(payload_encoded)
+ self.wfile.flush()
+ except BrokenPipeError:
+ break
+ 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)):
with DbConn() as conn:
playing = VideoFile.get_one_with_whitelist_tags_display(
- conn,
- self.server.player.current_digest,
- self.server.config.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 = '', ''
if playing:
tags = playing.tags_showable.joined
title = str(playing.rel_path)
- data = {
- 'last_update': self.server.player.last_update,
- 'running': self.server.player.is_running,
- 'paused': self.server.player.is_paused,
- 'idx': self.server.player.idx,
- 'title_tags': tags,
- 'title': title}
+ 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
+ payload['title_tags'] = tags
+ payload['title'] = title
if 'playlist' in params.as_dict:
- data['playlist_files'] = [
+ payload['playlist_files'] = [
{'rel_path': str(f.rel_path), 'digest': f.digest.b64}
for f in self.server.player.playlist]
- try:
- self.wfile.write(
- f'data: {json_dumps(data)}\n\n'.encode())
- self.wfile.flush()
- except BrokenPipeError:
- break
- sleep(0.25)
+ else:
+ sleep(_EVENTS_UPDATE_INTERVAL_S)