Recover streams after five seconds and start from complete keys

Preserve queued media under backpressure, advertise retained keyframe ranges in stream wire v6, and reopen stalled subflows without resetting playback.

Cover large fragmented keys, sustained slow delivery, missing subflows, buffer eviction and stale bootstrap snapshots in integration regressions.
This commit is contained in:
evgeny committed 2026-10-25 12:03:49 +01:00
1 parent 4a785c2598
commit c3cf86b9e0
15 files changed
+461 -160

No files matched your search

@@ -90,7 +90,6 @@ class StreamPlayerEngine(private val group: Long, private val source: Long, priv
var video: StreamVideoPlayback? = null
var waitingKey = true
var audioEnabled = false
var opened = false
var ended = false
var videoInputEnded = false
var audioInputEnded = false
@@ -113,7 +112,6 @@ class StreamPlayerEngine(private val group: Long, private val source: Long, priv
var underrunBase = 0L
var silenceBase = 0L
var workMaxUs = 0L
var deadline = System.nanoTime() + 10_000_000_000
var clockState: LongArray? = null
fun observeStatus(meta: LongArray?) {
@@ -139,7 +137,7 @@ class StreamPlayerEngine(private val group: Long, private val source: Long, priv
playback = null; position = -1; generation = 0; needsAudio = true; clockState = null; playbackStarted = false
underrunBase = 0; silenceBase = 0
records.clear(); ended = false; videoInputEnded = false; audioInputEnded = false; endUntil = 0
resetVideo(); deadline = System.nanoTime() + 10_000_000_000
resetVideo()
if (request) check(NativeLib.streamResync(id)) { "Media resync refused" }
}
try {
@@ -180,7 +178,6 @@ class StreamPlayerEngine(private val group: Long, private val source: Long, priv
1 -> {
if (revision != videoRevision) {
check(revision > videoRevision); resetVideo(); videoRevision = revision
deadline = System.nanoTime() + 10_000_000_000
}
val key = flags and 1 != 0
if (!waitingKey || key) {
@@ -203,7 +200,7 @@ class StreamPlayerEngine(private val group: Long, private val source: Long, priv
if (result == -2) resetTimeline(true) else check(result >= 0) { "Opus feed failed=$result" }
}
3 -> { LogManager.addLog("INFO", "stream", "playback GAP id=$id"); resetTimeline(false) }
4 -> opened = true
4 -> Unit
5, 7, 8 -> {
if (kind != 8 && !videoInputEnded) { videoInputEnded = true; video.finish() }
if (kind != 7 && !audioInputEnded) { audioInputEnded = true; playback?.finish() }
@@ -224,7 +221,7 @@ class StreamPlayerEngine(private val group: Long, private val source: Long, priv
}
}
if (!running) break
if (!ended && (!opened || waitingKey) && System.nanoTime() > deadline) error("Viewer open timeout")
// Ядро перезапрашивает DATA после 5с без прогресса; большой KEY может собираться дольше.
val decoded = video.state
if (!playbackStarted && decoded.firstOutputPts != null) playbackStarted = true
if (playbackStarted && playback != null && ready >= 0) {