Skip to content

feat(publish): Safari hardware encode, worker capture, and a stable video catalog#2190

Open
fperex wants to merge 2 commits into
moq-dev:devfrom
fperex:split/publish-video
Open

feat(publish): Safari hardware encode, worker capture, and a stable video catalog#2190
fperex wants to merge 2 commits into
moq-dev:devfrom
fperex:split/publish-video

Conversation

@fperex

@fperex fperex commented Jul 12, 2026

Copy link
Copy Markdown

Split out of #2163.

Stacked on #2184 (it consumes Util.Hacks.isSafari / safariWorkerCapture). The first commit belongs to that PR; review the second commit only. The diff collapses once #2184 merges and I rebase.

Publishing on Safari was software-only, and the capture path stalled whenever the tab was hidden.

  • Hardware encode. On Apple Silicon, VideoToolbox only hardware-encodes H.264 and HEVC. Safari nonetheless reports the others as prefer-hardware supported, so the support matrix was keying off an echoed hint that lies (they actually run on software libvpx). Key off the codec instead, and prefer the codecs that genuinely have a hardware path.
  • Worker capture. WebKit 18+ exposes MediaStreamTrackProcessor in a worker (plus transferable MediaStreamTrack), so capture runs there instead of the requestAnimationFrame fallback, which pauses whenever the tab is hidden. The gate reads the iOS OS version when the Safari Version/ token is absent, because iOS Chrome and Firefox are WebKit too and omit it, and the iOS major tracks the WebKit major. Anything older or undetectable keeps the rAF path.
  • Camera and microphone re-acquire when their track ends (all browsers), and a backgrounded Safari tab no longer comes back with the camera stuck muted.
  • The video catalog no longer flaps. It folds in the encoder-produced codec string and the hvcC/av1C description after the first keyframe, tagged against the requested codec and dimensions, so bandwidth-driven bitrate churn (which fires ~10x a second) can never rewrite the description underneath watchers.
  • The stats tab graphs a measured upload bitrate from encoded bytes rather than a transport estimate, which Safari does not have at all (no getStats()).

Public API changes: @moq/publish gains Video.Encoder.bytesEncoded; @moq/hang gains Util.Hacks.safariWorkerCapture (both additive).

Test plan: bun test js/publish/src/video js/hang/src/util: 22 tests (codec ordering, catalog description folding against bitrate churn, worker-capture UA gate). just js check green. Verified on device: Safari publishes with hardware H.264 and keeps capturing in a background tab.

fperex added 2 commits July 11, 2026 22:19
Reading WebTransport.datagrams.readable tears down the whole session on
Safari, and moq-lite-05 always reads it once the negotiated version carries
datagrams. There is no catchable failure, so a Safari client that wins the
WebTransport race is simply dead. Route Safari onto the qmux WebSocket
fallback the same way Firefox already is.

The rule lives in a pure pickTransport(userAgent, hasWebTransport) with a
user-agent matrix covering Safari, iOS Chrome (CriOS), iOS Firefox (FxiOS),
Android WebViews and the legacy Android stock browser, so 'is this WebKit'
is testable instead of an inline boolean.

@moq/hang gets the twin detectSafari predicate (the support matrix cannot
import net's internal pickTransport), and both packages share a user-agent
corpus so a drift between them fails a test in each. hang had no test script,
so its suite never ran; it does now.

Both support matrices report webtransport: partial on Safari and Firefox to
surface the degraded path. Safari consequently has no getStats() bandwidth
estimate, same as Firefox.
…ideo catalog

Publishing on Safari was software-only and the capture path stalled whenever
the tab was hidden.

- Hardware encode: on Apple Silicon, VideoToolbox only hardware-encodes H.264
  and HEVC, but Safari reports the others as prefer-hardware supported anyway,
  so the support matrix keyed off an echoed hint that lies. Key off the codec
  instead, and prefer the codecs that actually have a hardware path.
- Worker capture: WebKit 18+ exposes MediaStreamTrackProcessor in a worker
  (plus transferable MediaStreamTrack), so capture runs there. iOS Chrome and
  Firefox are WebKit too and omit the Safari Version/ token, so the gate reads
  the iOS OS version, which tracks the WebKit major. Everything older keeps the
  rAF fallback.
- Camera and microphone re-acquire when their track ends, and a backgrounded
  Safari tab no longer comes back with the camera stuck muted.
- The video catalog folds in the encoder-produced codec string and the
  hvcC/av1C description after the first keyframe, tagged against the requested
  codec and dimensions, so bandwidth-driven bitrate churn (which fires ~10x a
  second) can never flap the description.
- The stats tab graphs a measured upload bitrate from encoded bytes rather than
  a transport estimate, which Safari does not have (no getStats).

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @fperex, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

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.

1 participant