Improve player controls and media seeking
This commit is contained in:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user