feat(resize/apple): resize overlay — blur + spinner during mid-stream resize
Make a Match-window resize deliberate instead of a stutter: blur the live stream and show a spinner while the host rebuilds its virtual display + encoder and VideoToolbox re-inits on the new-mode IDR. No new protocol — driven entirely by existing client signals. - ResizeIndicator (pure core, unit-tested): START = follower steering, END = a decoded frame at the target size, TIMEOUT = 2.5s safety net for a rejected/capped switch that never yields a new-size frame; re-arms only on a CHANGED target, not a repeated same-size drag. - MatchWindowFollower.onResizeTarget fires the instant the window differs from the live mode (deduped via lastSteered); a new onDecodedSize callback threads each new-mode IDR's coded dims through StreamPump/Stage2Pipeline → SessionPresenter → both stream views. - SessionModel gains @Published resizing (+ resizeTargeted/resizeDecoded, a tick on the 1 Hz stats timer, reset on disconnect); ContentView blurs the stream 16px and overlays ResizeIndicatorView while resizing (the 32px trust-prompt blur is unchanged and takes precedence). tvOS declares the props but never fires the follower (it drives modes via AVDisplayManager), so the overlay stays dormant there. Pure core verified on the Linux toolchain; full AppKit/UIKit build pending on a Mac. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -21,7 +21,8 @@ final class StreamPump {
|
||||
connection: PunktfunkConnection,
|
||||
layer: AVSampleBufferDisplayLayer,
|
||||
onFrame: (@Sendable (AccessUnit) -> Void)?,
|
||||
onSessionEnd: (@Sendable () -> Void)?
|
||||
onSessionEnd: (@Sendable () -> Void)?,
|
||||
onDecodedSize: (@Sendable (Int, Int) -> Void)? = nil
|
||||
) {
|
||||
let token = token
|
||||
// Coalesced host keyframe requests (100 ms throttle — see KeyframeRecovery).
|
||||
@@ -35,6 +36,9 @@ final class StreamPump {
|
||||
|
||||
let thread = Thread {
|
||||
var format: CMVideoFormatDescription?
|
||||
// Report the coded dims to the resize overlay only when they CHANGE (a new-mode IDR),
|
||||
// not on every loss-recovery IDR at the same size — so it fires once per real switch.
|
||||
var lastDecodedDims: CMVideoDimensions?
|
||||
var lastFramesDropped = connection.framesDropped()
|
||||
// Recovery is a persistent WANT, not a one-shot edge: set it on detected loss (or a
|
||||
// decoder reset), retry the throttled request EVERY iteration, and clear it only when a
|
||||
@@ -79,6 +83,11 @@ final class StreamPump {
|
||||
let idrFormat = connection.videoCodec.formatDescription(fromKeyframe: au.data)
|
||||
if let f = idrFormat {
|
||||
format = f // refreshed on every IDR (mode changes included)
|
||||
let dims = CMVideoFormatDescriptionGetDimensions(f)
|
||||
if lastDecodedDims?.width != dims.width || lastDecodedDims?.height != dims.height {
|
||||
lastDecodedDims = dims
|
||||
onDecodedSize?(Int(dims.width), Int(dims.height))
|
||||
}
|
||||
if awaitingIDR {
|
||||
let ms = Int(Date().timeIntervalSince(awaitingSince) * 1000)
|
||||
pumpLog.notice("video: recovery IDR received — resumed after \(ms, privacy: .public) ms")
|
||||
|
||||
Reference in New Issue
Block a user