fix: reconnect interrupted video streams
Enable bounded FFmpeg reconnection for recorded range-seekable HTTP inputs, preserving compressed cache, byte offset and playback intent. A five-second socket timeout leaves recovery room inside the existing fifteen-second I/O deadline. Keep live/non-range replay and signed-URL refresh disabled. Native regression failed before the fix with Reading network media: Input/output error. Real combined/split playback now survives dropped responses and socket timeout; persistent failures remain visible and retries are cancellable by seek, close, replacement and destruction. Release, network, adaptive, media, reporting, runtime, relocated bundle and local playback checks passed. Packaged CLI/GUI playback started successfully; the full-suite keyboard smoke stopped because macOS refused foreground focus for its transparent check window.
This commit is contained in:
+9
-4
@@ -181,7 +181,7 @@ class StreamingServer:
|
||||
|
||||
mode = parts[0]
|
||||
token = None
|
||||
if len(parts) == 3 and mode in ("stall", "stall-open", "chunked-media"):
|
||||
if len(parts) == 3 and mode in ("stall", "stall-open", "chunked-media", "disconnect"):
|
||||
token, name = parts[1:]
|
||||
if not re.fullmatch(r"[A-Za-z0-9_-]+", token):
|
||||
self._reply(request, 400, b"Invalid gate token", "text/plain")
|
||||
@@ -229,14 +229,14 @@ class StreamingServer:
|
||||
query = parse_qs(parsed.query)
|
||||
try:
|
||||
gate_at = (self._timestamp_offset(name, float(query["after_seconds"][0]))
|
||||
if mode in ("stall", "chunked-media") and "after_seconds" in query else
|
||||
int(query.get("after", ["131072"])[0]) if mode in ("stall", "chunked-media") else start)
|
||||
if mode in ("stall", "chunked-media", "disconnect") and "after_seconds" in query else
|
||||
int(query.get("after", ["131072"])[0]) if mode in ("stall", "chunked-media", "disconnect") else start)
|
||||
except (ValueError, OverflowError):
|
||||
self._reply(request, 400, b"Invalid gate offset", "text/plain")
|
||||
return
|
||||
with self.lock:
|
||||
if token not in self.gates:
|
||||
prefix = self._prefix_ends(name, gate_at) if mode in ("stall", "chunked-media") else {}
|
||||
prefix = self._prefix_ends(name, gate_at) if mode in ("stall", "chunked-media", "disconnect") else {}
|
||||
self.gates[token] = _Gate(size, prefix)
|
||||
gate = self.gates[token]
|
||||
gate.requests += 1
|
||||
@@ -266,6 +266,9 @@ class StreamingServer:
|
||||
request.end_headers()
|
||||
request.wfile.flush()
|
||||
request.close_connection = not persistent
|
||||
if (mode == "disconnect" and not should_gate and start <= gate_at
|
||||
and query.get("fail_reconnect") == ["1"]):
|
||||
return # Every resumed response at the broken offset is also truncated.
|
||||
cursor = start
|
||||
with path.open("rb") as media:
|
||||
media.seek(start)
|
||||
@@ -280,6 +283,8 @@ class StreamingServer:
|
||||
with self.lock:
|
||||
gate.waiting -= 1
|
||||
should_gate = False
|
||||
if mode == "disconnect":
|
||||
return # Advertise the complete range, then cut its body short.
|
||||
amount = min(16384, end - cursor + 1)
|
||||
if should_gate:
|
||||
amount = min(amount, gate_at - cursor)
|
||||
|
||||
Reference in New Issue
Block a user