Improve player controls and media seeking

This commit is contained in:
human insan
2026-08-27 22:28:58 +01:00
parent 4904deb621
commit 3d38e32b1b
2 changed files with 53 additions and 3 deletions
+3
View File
@@ -156,6 +156,7 @@
let initialOffset = 0;
let zoom = 1;
let focus = 43200;
let encodingWasRunning = false;
// encoding
const encToggle = document.querySelector("#encode-toggle");
@@ -175,6 +176,8 @@
document.querySelector("#encode-status").textContent = d.running
? `${d.completed}/${d.total} ${d.skipped} skip ${d.failed} err`
: d.total ? `${d.completed} done ${d.skipped} skip ${d.failed} err` : "Ready";
if (encodingWasRunning && !d.running) loadDate(dateSelect.value);
encodingWasRunning = d.running;
if (d.running) setTimeout(updateEncodeStatus, 1000);
}
document.querySelector("#encode-btn").addEventListener("click", async () => {
+50 -3
View File
@@ -45,6 +45,12 @@ def scan_recordings(root):
class PlayerHandler(BaseHTTPRequestHandler):
protocol_version = "HTTP/1.1"
def do_HEAD(self):
if urlparse(self.path).path == "/video":
self.send_video(self.path.split("?", 1)[1] if "?" in self.path else "", head_only=True)
else:
self.send_error(404)
def do_GET(self):
request = urlparse(self.path)
if request.path == "/":
@@ -95,7 +101,7 @@ class PlayerHandler(BaseHTTPRequestHandler):
}
self.send_json(payload)
def send_video(self, query):
def send_video(self, query, head_only=False):
params = parse_qs(query)
day = params.get("date", [""])[0]
name = params.get("name", [""])[0]
@@ -113,7 +119,7 @@ class PlayerHandler(BaseHTTPRequestHandler):
# Pre-encoded clips can be served immediately; the offset is applied by the UI.
if clip["path"].suffix.lower() == ".mp4":
return self.send_file(clip["path"], "video/mp4")
return self.send_range_file(clip["path"], "video/mp4", head_only)
command = [
"ffmpeg", "-hide_banner", "-loglevel", "error", "-ss", str(offset),
@@ -139,6 +145,47 @@ class PlayerHandler(BaseHTTPRequestHandler):
process.kill()
process.wait()
def send_range_file(self, path, content_type, head_only=False):
size = path.stat().st_size
range_header = self.headers.get("Range")
start, end = 0, size - 1
if range_header and range_header.startswith("bytes="):
try:
value = range_header[6:].split(",", 1)[0]
first, last = value.split("-", 1)
if first:
start = int(first)
end = int(last) if last else size - 1
else:
start = max(0, size - int(last))
end = min(end, size - 1)
if start > end or start >= size:
self.send_response(416)
self.send_header("Content-Range", f"bytes */{size}")
self.end_headers()
return
except ValueError:
pass
length = end - start + 1
self.send_response(206 if range_header else 200)
self.send_header("Content-Type", content_type)
self.send_header("Accept-Ranges", "bytes")
self.send_header("Content-Length", str(length))
if range_header:
self.send_header("Content-Range", f"bytes {start}-{end}/{size}")
self.end_headers()
if head_only:
return
with path.open("rb") as source:
source.seek(start)
remaining = length
while remaining:
chunk = source.read(min(256 * 1024, remaining))
if not chunk:
break
self.wfile.write(chunk)
remaining -= len(chunk)
def log_message(self, message, *args):
print(f"{self.client_address[0]} - {message % args}")
@@ -177,7 +224,7 @@ def main():
def run_encoding(server):
source_files = sorted(server.source_root.rglob("*.avi"))
source_files = sorted(path for path in server.source_root.rglob("*") if path.is_file() and path.suffix.lower() == ".avi")
status = {"running": True, "total": len(source_files), "completed": 0, "skipped": 0, "failed": 0}
server.encode_status = status
lock = threading.Lock()