Skip to content

feat: SRT/WHIP resilience parity via a shared supervision layer (#20) - #31

Merged
joeblau merged 1 commit into
mainfrom
feat/20-srt-whip-resilience-parity
Jul 4, 2026
Merged

feat: SRT/WHIP resilience parity via a shared supervision layer (#20)#31
joeblau merged 1 commit into
mainfrom
feat/20-srt-whip-resilience-parity

Conversation

@joeblau

@joeblau joeblau commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Brings SRT/WHIP (SessionPublisher) up to the RTMP publisher's network-resilience, by extracting RTMP's supervision decisions into a shared, unit-tested StreamCore layer that both publishers use.

The gaps (SRT/WHIP had none of these)

NWPath supervision · queue-stall watchdog · per-path bitrate ceiling · frame-shedding admission (setQueueDepth was never called → admit() always true) · mic-stall failover · path-gated backoff (the old 1s→30s loop burned attempts while down).

Approach — hybrid (pure deciders shared, transport machinery per-publisher)

  • StreamCore (pure, CI-tested, +27 tests): ReconnectBackoff, classifyPathTransition, WatchdogEvaluator, MicStallEvaluator, PathDebounce. Relocated VideoFrameAdmission + NetworkPathSnapshot here (public). The tests pin RTMP's exact constants/rules — including the frozen-queue-no-recycle (app-backgrounding) and metadata-flip-no-clock-bump guards.
  • RTMPPublisher: delegates only isolated arithmetic to those deciders; control flow, socket-close ordering, and supervisors are byte-for-byte unchanged (−141 lines, no behavior change).
  • SessionPublisher: gains the full supervision additively — NWPath supervision, a 2s watchdog, single-flight path-gated jittered reconnect (parks instead of burning attempts), per-path setPathProfile (re-seeded on reconnect), live frame-shedding, mic-stall failover, setMaxRetryCount(0). Recovery from a path debounce runs on a dedicated task so a route-return cancellation can't contaminate the parking reconnect loop.

Process

Built via an understand→design workflow and three adversarial multi-agent review passes, which caught and this fixes: a permanent-dead-air-after-dropout bug (confirmPathLost stranded a still-connected session) and a backoff-defeating task-cancellation bug (recovery ran inside a debounce task the route-return cancels).

Verification

  • StreamCore: 96 tests pass (incl. new shared-supervision suites)
  • Full app builds against the iOS 27 SDK
  • ⚠️ Network resilience itself is device-only — it needs a physical iOS 27 device + real SRT/WHIP endpoints + induced drops (Wi-Fi↔5G handoff, dead-zone, mic-mute, congestion), which can't be exercised in CI. RTMP core is untouched + test-pinned; the SRT/WHIP changes are additive. Recommend on-device validation before relying on it.

fixes #20

🤖 Generated with Claude Code

@joeblau
joeblau force-pushed the feat/20-srt-whip-resilience-parity branch from 896350c to b6b1ee7 Compare July 4, 2026 22:30
SRT/WHIP — chosen for low latency — were the LEAST resilient transports: no
NWPath supervision, no queue-stall watchdog, no per-path bitrate ceiling, an
inert VideoFrameAdmission (setQueueDepth never called → admit() always true),
no mic-stall failover, and a plain 1s→30s backoff that burned reconnect
attempts while the network was down.

Extract RTMP's supervision DECISIONS into a shared, unit-tested StreamCore
layer and adopt it in both publishers:

- StreamCore (pure, CI-tested; +27 tests): ReconnectBackoff (jittered 1s→30s
  ladder), classifyPathTransition (path-update classifier), WatchdogEvaluator
  (queue-stall recycle math), MicStallEvaluator, PathDebounce. Relocated
  VideoFrameAdmission + NetworkPathSnapshot here (public). The tests pin RTMP's
  exact constants/rules — including the frozen-queue-no-recycle
  (app-backgrounding) and metadata-flip-no-clock-bump guards — so the
  extraction can't drift RTMP's hard-won behavior.
- RTMPPublisher: delegates ONLY isolated arithmetic to those deciders; control
  flow, socket-close ordering, and supervisors are byte-for-byte unchanged
  (−141 lines, no behavior change).
- SessionPublisher: gains the full supervision additively — NWPath supervision,
  a 2s queue-stall watchdog, single-flight path-gated jittered reconnect (parks
  instead of burning attempts while down), per-path setPathProfile ceilings
  (re-seeded on reconnect), live frame-shedding (setQueueDepth + reset),
  mic-stall failover, and setMaxRetryCount(0) so HaishinKit's retry can't race
  the app loop. Recovery kicked off from a path debounce runs on a dedicated
  task so a route-return cancellation can't contaminate the parking reconnect
  loop. Closes all ~5 gaps.

Built via an understand→design pass and three adversarial multi-agent review
passes, which caught and this fixes a permanent-dead-air-after-dropout bug and
a backoff-defeating task-cancellation bug. StreamCore: 96 tests pass; full app
builds against the iOS 27 SDK. Network resilience itself is device-only and
must be validated on hardware.

fixes #20

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@joeblau
joeblau force-pushed the feat/20-srt-whip-resilience-parity branch from b6b1ee7 to 8a8ba40 Compare July 4, 2026 22:47
@joeblau
joeblau merged commit 671b704 into main Jul 4, 2026
1 check passed
@joeblau
joeblau deleted the feat/20-srt-whip-resilience-parity branch July 4, 2026 22:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

M5 · SRT/WHIP resilience parity (shared supervision layer)

1 participant