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:
2026-10-12 02:06:06 +02:00
parent 31c4759c1d
commit e54cb2b549
4 changed files with 140 additions and 7 deletions
+8 -1
View File
@@ -529,10 +529,17 @@ struct Player {
input.format->interrupt_callback = {playback::NetworkInput::interrupt, &input};
AVDictionary *options = nullptr;
int result = av_dict_set(&options, "protocol_whitelist", "http,https,tcp,tls,crypto", 0);
if (result >= 0) result = av_dict_set(&options, "rw_timeout", "15000000", 0);
// Leave room for socket-timeout recovery within the reader's total 15-second deadline.
if (result >= 0) result = av_dict_set(&options, "rw_timeout", live ? "15000000" : "5000000", 0);
// Some YouTube CDNs throttle open-ended ranges; FFmpeg continues bounded requests.
if (result >= 0 && !live) result = av_dict_set(&options, "request_size", "10485760", 0);
if (result >= 0 && !live) result = av_dict_set(&options, "multiple_requests", "1", 0);
// Resume seekable inputs at their byte offset; never restart non-range streams or EOF.
if (result >= 0 && !live) result = av_dict_set(&options, "reconnect", "1", 0);
if (result >= 0 && !live) result = av_dict_set(&options, "reconnect_on_network_error", "1", 0);
if (result >= 0 && !live) result = av_dict_set(&options, "reconnect_max_retries", "3", 0);
if (result >= 0 && !live) result = av_dict_set(&options, "reconnect_delay_max", "3", 0);
if (result >= 0 && !live) result = av_dict_set(&options, "reconnect_delay_total_max", "5", 0);
// Leave short-seek read-ahead disabled: cache misses must escape stalled responses.
if (result >= 0 && !headers.empty()) result = av_dict_set(&options, "headers", headers.c_str(), 0);
if (result < 0) {