diff --git a/clients/apple/Sources/PunktfunkKit/Video/Stage2Pipeline.swift b/clients/apple/Sources/PunktfunkKit/Video/Stage2Pipeline.swift index bd7e9d52..17e54014 100644 --- a/clients/apple/Sources/PunktfunkKit/Video/Stage2Pipeline.swift +++ b/clients/apple/Sources/PunktfunkKit/Video/Stage2Pipeline.swift @@ -57,6 +57,8 @@ let presentDebug = ProcessInfo.processInfo.environment["PUNKTFUNK_PRESENT_DEBUG" /// to Console.app wirelessly with no env var / Xcode attach. Always on for deadline pacing (the /// stats are a few arrays + one log line per second); other pacings keep the env-gated print. private let presentLog = Logger(subsystem: "io.unom.punktfunk", category: "present") +/// Pump-side events (loss recovery, format seeding) — the stage-2 sibling of StreamPump's log. +private let pumpLog = Logger(subsystem: "io.unom.punktfunk", category: "pump") /// Decoded-frame hand-off between the decode half and the render thread. The POLICY is the /// user's presentation intent (design/apple-presentation-rebuild.md — the 2026-07 rebuild that @@ -932,6 +934,21 @@ public final class Stage2Pipeline { } awaitingIDR = false // a fresh IDR re-anchored decode — recovery complete } + if format == nil { + // No decodable format yet: the opening IDR's parameter sets never + // arrived (or never parsed), and under the host's infinite GOP nothing + // re-delivers them unless we ASK. Without this the guard below drops + // every AU silently, forever — the field "black stream, zero recovery + // requests" state (2026-08-12): the host streams perfectly, the client + // shows nothing and says nothing. awaitingIDR routes through the same + // 100 ms-throttled recovery.request() at the top of the loop. + if !awaitingIDR { + pumpLog.warning( + "video: received AUs but no decodable format (missing/unparsed parameter sets) — requesting an IDR until one seeds it" + ) + } + awaitingIDR = true + } guard let f = format, !token.isStopped else { return true } if decoder.decode(au: au, format: f) { decodeFailRun = 0 diff --git a/clients/apple/Sources/PunktfunkKit/Video/StreamPump.swift b/clients/apple/Sources/PunktfunkKit/Video/StreamPump.swift index e647f44f..215a5aea 100644 --- a/clients/apple/Sources/PunktfunkKit/Video/StreamPump.swift +++ b/clients/apple/Sources/PunktfunkKit/Video/StreamPump.swift @@ -116,6 +116,21 @@ final class StreamPump { } awaitingIDR = false // a fresh IDR re-anchored decode — recovery complete } + if format == nil { + // No decodable format yet: the opening IDR's parameter sets never + // arrived (or never parsed), and under the host's infinite GOP nothing + // re-delivers them unless we ASK. Without this the format guard below + // drops every AU silently, forever — the field "black stream, zero + // recovery requests" state (2026-08-12). awaitingIDR routes through the + // same 100 ms-throttled recovery.request() at the top of the loop. + if !awaitingIDR { + awaitingSince = Date() + pumpLog.warning( + "video: received AUs but no decodable format (missing/unparsed parameter sets) — requesting an IDR until one seeds it" + ) + } + awaitingIDR = true + } let failed = layer.status == .failed if failed { // Decode wedged hard (the cold-first-connect case — a lost/corrupt opening