@urun-sh/core 0.2.52 → 0.2.53

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,34 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ - **Audio receiver jitter-buffer cap** (`AudioPlayoutOptions`, new export). A
6
+ muxed A/V egress reaches the browser as TWO RTP tracks with independent
7
+ receive buffers, and the receiver integrates delivery gaps asymmetrically:
8
+ video renders on arrival (a gap freezes, then drops/fast-forwards — no
9
+ standing delay), while audio's NetEq conceals every gap and plays the late
10
+ real audio AFTER the concealment, so gaps ACCUMULATE standing playout delay.
11
+ The RTCP-SR sync group is the only payback mechanism, and where an SFU
12
+ re-mints sender reports per consumer (each anchored at its own forwarding
13
+ instant, audio's refreshed 5x less often) it structurally cannot pay it back
14
+ — liveavatar measured 2.2–2.7s of standing audio delay behind video.
15
+
16
+ `_onConsume` now caps every inbound AUDIO consumer through the receiver's own
17
+ W3C knob: `RTCRtpReceiver.jitterBufferTarget` (milliseconds) plus the legacy
18
+ Chromium `playoutDelayHint` (seconds) where an engine still exposes it, so
19
+ either vintage is capped. Default **300ms**, tunable via
20
+ `AppOptions.audioPlayout.jitterBufferTargetMs` (a constructor option by
21
+ design — never an env var); `<= 0` sets no target at all. **Video receivers
22
+ are never touched** — video renders on arrival and must not be delayed.
23
+ Nothing bespoke: no resampling, no `playbackRate` nudging, no NetEq shadow.
24
+
25
+ Two new diagnostics make the edges observable instead of silent:
26
+ `audio-jitter-buffer-unsupported` (`debug`) when an engine exposes NEITHER
27
+ property — Firefox and Safari today, a receiver capability the SDK cannot
28
+ emulate, so the cap is inert there — and `audio-jitter-buffer-invalid`
29
+ (`warning`) for a non-finite configured target, which would otherwise slip
30
+ past the `<= 0` check and throw on assignment to the WebIDL double, aborting
31
+ the consume before `resumeConsumer`.
32
+
5
33
  - **`SessionDiagnostic.level` gains `'debug'`** (union widened: an exhaustive
6
34
  `switch`/`Record` over the level needs a new arm). `debug` diagnostics mirror
7
35
  to `console.debug` — hidden at the browser console's default verbosity —
package/README.md CHANGED
@@ -54,6 +54,33 @@ consumes the runtime's output. A second producer on the same name fails loud wit
54
54
  media encode; the client only attaches the inbound `track` to a media element. Tracks arrive
55
55
  asynchronously — subscribe with `.on('track', …)`.
56
56
 
57
+ ## Muxed A/V arrives as two tracks — play them through one element
58
+
59
+ A runtime that egresses `ctx.stream("av", video_codec=…, audio_codec=…)` puts TWO RTP tracks on
60
+ the wire. Put both into ONE `MediaStream` on ONE media element so the browser's native A/V sync
61
+ machinery owns the alignment (`@urun-sh/react`'s `<Video stream="av" muted={false} />` is that
62
+ shape, prebaked):
63
+
64
+ ```ts
65
+ const stream = new MediaStream()
66
+ element.srcObject = stream
67
+ session.stream('video').on('track', (t) => t && stream.addTrack(t))
68
+ session.stream('audio').on('track', (t) => t && stream.addTrack(t))
69
+ ```
70
+
71
+ Two separate elements give the browser two independently-buffered playout paths with nothing
72
+ aligning them: the video track renders on arrival (a delivery gap freezes, then drops — no
73
+ standing delay), while the audio track's jitter buffer conceals each gap and plays the late
74
+ audio after it, so gaps ACCUMULATE into standing playout delay. RTCP sender reports are the
75
+ only payback mechanism, and an SFU that re-mints them per track weakens it.
76
+
77
+ The SDK therefore also caps every inbound AUDIO receiver's jitter buffer at 300 ms
78
+ (`RTCRtpReceiver.jitterBufferTarget`, plus the legacy `playoutDelayHint` where present), which
79
+ bounds that accumulation regardless of sender-report quality. Retune it with
80
+ `App(appId, { audioPlayout: { jitterBufferTargetMs: 500 } })`; `<= 0` sets no target at all.
81
+ Engines exposing neither property (Firefox, Safari today) are unaffected and emit an
82
+ `audio-jitter-buffer-unsupported` diagnostic so the gap is visible rather than silent.
83
+
57
84
  ## The control doc is the reactive desired-state surface
58
85
 
59
86
  The client does NOT call an imperative `start/update/stop`. It writes `desired.*` keys into a