@openmoq/playback 0.0.0-stage → 0.6.0
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/LICENSE.txt +202 -0
- package/README.md +18 -2
- package/dist/adaptive-tolerance.d.ts +128 -0
- package/dist/adaptive-tolerance.d.ts.map +1 -0
- package/dist/adaptive-tolerance.js +339 -0
- package/dist/adaptive-tolerance.js.map +1 -0
- package/dist/bandwidth-estimator.d.ts +49 -0
- package/dist/bandwidth-estimator.d.ts.map +1 -0
- package/dist/bandwidth-estimator.js +77 -0
- package/dist/bandwidth-estimator.js.map +1 -0
- package/dist/browser/index.global.js +2813 -0
- package/dist/browser/index.global.js.map +1 -0
- package/dist/browser/index.js +2788 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/buffer-based-controller.d.ts +87 -0
- package/dist/buffer-based-controller.d.ts.map +1 -0
- package/dist/buffer-based-controller.js +85 -0
- package/dist/buffer-based-controller.js.map +1 -0
- package/dist/decoder-state.d.ts +103 -0
- package/dist/decoder-state.d.ts.map +1 -0
- package/dist/decoder-state.js +145 -0
- package/dist/decoder-state.js.map +1 -0
- package/dist/gap-detector.d.ts +83 -0
- package/dist/gap-detector.d.ts.map +1 -0
- package/dist/gap-detector.js +129 -0
- package/dist/gap-detector.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/jitter-buffer.d.ts +73 -0
- package/dist/jitter-buffer.d.ts.map +1 -0
- package/dist/jitter-buffer.js +216 -0
- package/dist/jitter-buffer.js.map +1 -0
- package/dist/keyframe-validator.d.ts +26 -0
- package/dist/keyframe-validator.d.ts.map +1 -0
- package/dist/keyframe-validator.js +170 -0
- package/dist/keyframe-validator.js.map +1 -0
- package/dist/pipeline.d.ts +215 -0
- package/dist/pipeline.d.ts.map +1 -0
- package/dist/pipeline.js +852 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/recovery.d.ts +166 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +176 -0
- package/dist/recovery.js.map +1 -0
- package/dist/render-scheduler.d.ts +50 -0
- package/dist/render-scheduler.d.ts.map +1 -0
- package/dist/render-scheduler.js +84 -0
- package/dist/render-scheduler.js.map +1 -0
- package/dist/startup-buffer.d.ts +68 -0
- package/dist/startup-buffer.d.ts.map +1 -0
- package/dist/startup-buffer.js +107 -0
- package/dist/startup-buffer.js.map +1 -0
- package/dist/sync.d.ts +234 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +343 -0
- package/dist/sync.js.map +1 -0
- package/dist/types.d.ts +229 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +12 -0
- package/dist/types.js.map +1 -0
- package/package.json +56 -3
package/dist/sync.js
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A/V sync controller with injectable clock.
|
|
3
|
+
*
|
|
4
|
+
* Audio-master model: the first audio sample establishes a sync reference
|
|
5
|
+
* mapping CaptureTimestamp (wall-clock microseconds) to local clock time.
|
|
6
|
+
* Video render times are computed relative to this reference.
|
|
7
|
+
*
|
|
8
|
+
* Drift detection: reportPresentationTiming measures VIDEO PRESENTATION SCHEDULE
|
|
9
|
+
* drift — actual presentation time minus the exact scheduled time carried with
|
|
10
|
+
* the frame. If it exceeds the adaptive threshold, presentationDriftExceeded
|
|
11
|
+
* signals and a sync_drift event is emitted — but no automatic re-anchor occurs.
|
|
12
|
+
* Actual audio/video skew is a separate metric (sync_skew).
|
|
13
|
+
* The reference is set once to avoid audio glitching from re-anchor jumps.
|
|
14
|
+
* In practice, gap recovery (skip_forward) and seek/pause naturally re-anchor.
|
|
15
|
+
*
|
|
16
|
+
* Live catch-up: measures end-to-end latency by comparing CaptureTimestamp
|
|
17
|
+
* (publisher wall-clock) to subscriber wall-clock. When latency exceeds
|
|
18
|
+
* targetLatency + threshold, activates catch-up (playback rate > 1.0).
|
|
19
|
+
* Hysteresis prevents oscillation near the threshold boundary.
|
|
20
|
+
*
|
|
21
|
+
* @see draft-ietf-moq-loc-01 §2.3.1.1 (CaptureTimestamp)
|
|
22
|
+
* @see draft-ietf-moq-msf-00 §5.1.16 (targetLatency)
|
|
23
|
+
* @module
|
|
24
|
+
*/
|
|
25
|
+
/** Default: frames more than 500ms late are dropped. */
|
|
26
|
+
const DEFAULT_DROP_THRESHOLD_US = 500_000;
|
|
27
|
+
/** Default wall clock — Date.now() converted to microseconds. */
|
|
28
|
+
const DEFAULT_WALL_CLOCK = { now: () => Date.now() * 1000 };
|
|
29
|
+
// ─── SyncController ─────────────────────────────────────────────────
|
|
30
|
+
/**
|
|
31
|
+
* A/V sync controller with live catch-up.
|
|
32
|
+
*
|
|
33
|
+
* @see draft-ietf-moq-loc-01 §2.3.1.1
|
|
34
|
+
* @see draft-ietf-moq-msf-00 §5.1.16
|
|
35
|
+
*/
|
|
36
|
+
export class SyncController {
|
|
37
|
+
_driftThresholdUs;
|
|
38
|
+
dropThresholdUs;
|
|
39
|
+
clock;
|
|
40
|
+
/** Local clock time when audio reference was set. */
|
|
41
|
+
localBaselineUs;
|
|
42
|
+
/** CaptureTimestamp of the audio reference sample. */
|
|
43
|
+
captureBaselineUs;
|
|
44
|
+
/** Accumulated drift measurement. */
|
|
45
|
+
_currentDriftUs = 0;
|
|
46
|
+
/**
|
|
47
|
+
* Video join offset — shifts video render times forward during live join.
|
|
48
|
+
*
|
|
49
|
+
* When joining a live stream, video starts from the GOP boundary
|
|
50
|
+
* (which has an older CaptureTimestamp than the current audio).
|
|
51
|
+
* This offset shifts video render times forward so the first frames
|
|
52
|
+
* render in the future instead of the past, preventing startup stutter.
|
|
53
|
+
*
|
|
54
|
+
* Snaps to zero when the video timeline catches up to the audio live edge.
|
|
55
|
+
* Audio render times are unaffected.
|
|
56
|
+
*/
|
|
57
|
+
_videoJoinOffsetUs = 0;
|
|
58
|
+
// ── Catch-up state ─────────────────────────────────────────
|
|
59
|
+
wallClock;
|
|
60
|
+
targetLatencyMs;
|
|
61
|
+
maxCatchUpRate;
|
|
62
|
+
catchUpThresholdMs;
|
|
63
|
+
catchUpRecoveryMs;
|
|
64
|
+
_catchUpActive = false;
|
|
65
|
+
_currentRate = 1.0;
|
|
66
|
+
_lastLatencyUs = 0;
|
|
67
|
+
constructor(config) {
|
|
68
|
+
this._driftThresholdUs = config.driftThresholdUs;
|
|
69
|
+
this.dropThresholdUs = config.dropThresholdUs ?? DEFAULT_DROP_THRESHOLD_US;
|
|
70
|
+
this.clock = config.clock;
|
|
71
|
+
// Catch-up config
|
|
72
|
+
this.wallClock = config.wallClock ?? DEFAULT_WALL_CLOCK;
|
|
73
|
+
this.targetLatencyMs = config.targetLatencyMs;
|
|
74
|
+
this.maxCatchUpRate = config.maxCatchUpRate ?? 1.0;
|
|
75
|
+
this.catchUpThresholdMs = config.catchUpThresholdMs ?? 500;
|
|
76
|
+
this.catchUpRecoveryMs = config.catchUpRecoveryMs ?? 50;
|
|
77
|
+
}
|
|
78
|
+
/** Whether a sync reference has been established. */
|
|
79
|
+
get hasReference() {
|
|
80
|
+
return this.localBaselineUs !== undefined;
|
|
81
|
+
}
|
|
82
|
+
/** Current drift magnitude in microseconds. */
|
|
83
|
+
get currentDriftUs() {
|
|
84
|
+
return this._currentDriftUs;
|
|
85
|
+
}
|
|
86
|
+
/** Current drift threshold in microseconds. Mutable for adaptive tolerance. */
|
|
87
|
+
get driftThresholdUs() { return this._driftThresholdUs; }
|
|
88
|
+
set driftThresholdUs(value) { this._driftThresholdUs = value; }
|
|
89
|
+
/**
|
|
90
|
+
* Whether PRESENTATION SCHEDULE drift exceeds the threshold.
|
|
91
|
+
*
|
|
92
|
+
* Deliberately has NO live-join suppression. The exact queued schedule
|
|
93
|
+
* already contains the join offset, so missing that schedule during a join
|
|
94
|
+
* is a real miss. (The legacy PTS-era metric needed the suppression because
|
|
95
|
+
* the offset itself showed up as drift; see {@link needsResync}.)
|
|
96
|
+
*/
|
|
97
|
+
get presentationDriftExceeded() {
|
|
98
|
+
return Math.abs(this._currentDriftUs) >= this._driftThresholdUs;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* @deprecated Threshold check for the legacy {@link reportActualRenderTime}
|
|
102
|
+
* metric, retaining its live-join suppression. Use
|
|
103
|
+
* {@link presentationDriftExceeded} with schedule-based measurement.
|
|
104
|
+
*/
|
|
105
|
+
get needsResync() {
|
|
106
|
+
// Suppress during video join — under the LEGACY PTS-anchored metric the
|
|
107
|
+
// offset itself appeared as drift.
|
|
108
|
+
if (this._videoJoinOffsetUs > 0)
|
|
109
|
+
return false;
|
|
110
|
+
return Math.abs(this._currentDriftUs) >= this._driftThresholdUs;
|
|
111
|
+
}
|
|
112
|
+
/** Whether catch-up is currently active. */
|
|
113
|
+
get catchUpActive() {
|
|
114
|
+
return this._catchUpActive;
|
|
115
|
+
}
|
|
116
|
+
/** Current playback rate (1.0 when catch-up inactive). */
|
|
117
|
+
get currentRate() {
|
|
118
|
+
return this._currentRate;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Last measured latency in microseconds.
|
|
122
|
+
* 0 before first measurement.
|
|
123
|
+
* @see draft-ietf-moq-loc-01 §2.3.1.1
|
|
124
|
+
*/
|
|
125
|
+
get latencyUs() {
|
|
126
|
+
return this._lastLatencyUs;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Establish sync reference from an audio sample.
|
|
130
|
+
* Maps captureTimestamp to the current local clock time.
|
|
131
|
+
* Resets drift measurement.
|
|
132
|
+
*
|
|
133
|
+
* @param captureTimestampUs CaptureTimestamp in microseconds
|
|
134
|
+
*/
|
|
135
|
+
setAudioReference(captureTimestampUs) {
|
|
136
|
+
this.localBaselineUs = this.clock.now();
|
|
137
|
+
this.captureBaselineUs = captureTimestampUs;
|
|
138
|
+
this._currentDriftUs = 0;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Establish sync reference from a video frame (video-only mode).
|
|
142
|
+
* No-op if a reference already exists — audio has priority.
|
|
143
|
+
*
|
|
144
|
+
* @param captureTimestampUs CaptureTimestamp of the first video frame
|
|
145
|
+
*/
|
|
146
|
+
setVideoReference(captureTimestampUs) {
|
|
147
|
+
if (this.hasReference)
|
|
148
|
+
return;
|
|
149
|
+
this.localBaselineUs = this.clock.now();
|
|
150
|
+
this.captureBaselineUs = captureTimestampUs;
|
|
151
|
+
this._currentDriftUs = 0;
|
|
152
|
+
}
|
|
153
|
+
/** Current video join offset in microseconds. 0 = no offset active. */
|
|
154
|
+
get videoJoinOffsetUs() {
|
|
155
|
+
return this._videoJoinOffsetUs;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Apply video join re-anchor for live stream join.
|
|
159
|
+
*
|
|
160
|
+
* Called when the first video keyframe is decoded after a live join.
|
|
161
|
+
* Computes an offset that shifts video render times forward so the
|
|
162
|
+
* first frame renders at `now + cushion` instead of in the past.
|
|
163
|
+
*
|
|
164
|
+
* The offset is a constant added to all video render times. It preserves
|
|
165
|
+
* inter-frame timing (33ms spacing) while shifting the whole timeline.
|
|
166
|
+
* Snaps to zero when video catches up to the live edge.
|
|
167
|
+
*
|
|
168
|
+
* @param firstKeyframeCaptureUs CaptureTimestamp of the first keyframe
|
|
169
|
+
* @param cushionUs Extra future cushion (default: 16ms ≈ 1 rAF tick)
|
|
170
|
+
*/
|
|
171
|
+
onVideoJoin(firstKeyframeCaptureUs, cushionUs = 16_000) {
|
|
172
|
+
if (this.localBaselineUs === undefined || this.captureBaselineUs === undefined)
|
|
173
|
+
return;
|
|
174
|
+
const naiveRenderTimeUs = this.localBaselineUs +
|
|
175
|
+
Number(firstKeyframeCaptureUs - this.captureBaselineUs);
|
|
176
|
+
const nowUs = this.clock.now();
|
|
177
|
+
const gapUs = nowUs + cushionUs - naiveRenderTimeUs;
|
|
178
|
+
if (gapUs > 0) {
|
|
179
|
+
this._videoJoinOffsetUs = gapUs;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Compute render timing for a video frame.
|
|
184
|
+
*
|
|
185
|
+
* @param captureTimestampUs CaptureTimestamp in microseconds
|
|
186
|
+
*/
|
|
187
|
+
computeVideoRenderTime(captureTimestampUs) {
|
|
188
|
+
const timing = this.computeRenderTime(captureTimestampUs);
|
|
189
|
+
if (timing === null)
|
|
190
|
+
return null;
|
|
191
|
+
if (this._videoJoinOffsetUs > 0) {
|
|
192
|
+
// Apply offset — shift render time forward
|
|
193
|
+
const adjustedRenderTimeUs = timing.renderTimeUs + this._videoJoinOffsetUs;
|
|
194
|
+
const nowUs = this.clock.now();
|
|
195
|
+
// Check if base render time (without offset) is now in the future
|
|
196
|
+
// → video has caught up to live edge, snap offset to zero
|
|
197
|
+
if (timing.renderTimeUs > nowUs) {
|
|
198
|
+
this._videoJoinOffsetUs = 0;
|
|
199
|
+
return timing; // use unmodified timing from here
|
|
200
|
+
}
|
|
201
|
+
const offsetUs = adjustedRenderTimeUs - nowUs;
|
|
202
|
+
return {
|
|
203
|
+
renderTimeUs: adjustedRenderTimeUs,
|
|
204
|
+
offsetUs,
|
|
205
|
+
shouldDrop: offsetUs < -this.dropThresholdUs,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
return timing;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Compute render timing for an audio sample.
|
|
212
|
+
*
|
|
213
|
+
* @param captureTimestampUs CaptureTimestamp in microseconds
|
|
214
|
+
*/
|
|
215
|
+
computeAudioRenderTime(captureTimestampUs) {
|
|
216
|
+
return this.computeRenderTime(captureTimestampUs);
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Record PRESENTATION SCHEDULE drift: did the renderer present the frame
|
|
220
|
+
* when it was told to?
|
|
221
|
+
*
|
|
222
|
+
* `scheduledUs` must be the EXACT timestamp stored with the rendered frame.
|
|
223
|
+
* It already accounts for the playout cushion actually applied to that
|
|
224
|
+
* frame, the live-join offset, decode-output recomputation and late-frame
|
|
225
|
+
* handling — none of which may be recomputed or subtracted later, because an
|
|
226
|
+
* adaptive cushion can move between enqueue and presentation.
|
|
227
|
+
*
|
|
228
|
+
* The schedule is REQUIRED here. Optionality lives at
|
|
229
|
+
* `FrameRenderedFeedback`, where the pipeline suppresses the whole
|
|
230
|
+
* measurement/event transaction cleanly: missing evidence is not drift.
|
|
231
|
+
*
|
|
232
|
+
* Actual audio/video skew is measured separately as `sync_skew`.
|
|
233
|
+
*/
|
|
234
|
+
reportPresentationTiming(scheduledUs, actualUs) {
|
|
235
|
+
this._currentDriftUs = actualUs - scheduledUs;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* @deprecated Compares against the UNCUSHIONED PTS-anchored time, so a
|
|
239
|
+
* stream presenting exactly on its intended schedule reports the whole
|
|
240
|
+
* playout cushion as drift. Retained as a compatibility path only; use
|
|
241
|
+
* {@link reportPresentationTiming}.
|
|
242
|
+
*/
|
|
243
|
+
reportActualRenderTime(captureTimestampUs, actualLocalUs) {
|
|
244
|
+
if (this.localBaselineUs === undefined || this.captureBaselineUs === undefined)
|
|
245
|
+
return;
|
|
246
|
+
const expectedRenderUs = this.localBaselineUs +
|
|
247
|
+
Number(captureTimestampUs - this.captureBaselineUs);
|
|
248
|
+
this._currentDriftUs = actualLocalUs - expectedRenderUs;
|
|
249
|
+
}
|
|
250
|
+
// ─── Live Catch-Up ──────────────────────────────────────────────
|
|
251
|
+
/**
|
|
252
|
+
* Measure end-to-end latency from a wall-clock timestamp.
|
|
253
|
+
*
|
|
254
|
+
* Compares the publisher's wall-clock timestamp to the subscriber's
|
|
255
|
+
* wall clock, both in Unix epoch microseconds. A media-time timestamp
|
|
256
|
+
* (LOC-04 with Timescale) has no wall-clock meaning, so latency is
|
|
257
|
+
* unmeasurable and this returns null.
|
|
258
|
+
*
|
|
259
|
+
* @param captureTimestampUs Timestamp in microseconds
|
|
260
|
+
* @param isWallClock False when the timestamp is media time. Undefined means wall clock.
|
|
261
|
+
* @returns Latency in microseconds, or null if unmeasurable
|
|
262
|
+
* @see draft-ietf-moq-loc-01 §2.3.1.1
|
|
263
|
+
* @see draft-ietf-moq-loc-04 §2.3.1.2
|
|
264
|
+
*/
|
|
265
|
+
measureLatency(captureTimestampUs, isWallClock) {
|
|
266
|
+
if (captureTimestampUs === 0n || isWallClock === false)
|
|
267
|
+
return null;
|
|
268
|
+
return this.wallClock.now() - Number(captureTimestampUs);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Evaluate catch-up state based on current latency.
|
|
272
|
+
*
|
|
273
|
+
* Hysteresis: activates when latency > target + threshold,
|
|
274
|
+
* deactivates when latency <= target + recovery.
|
|
275
|
+
*
|
|
276
|
+
* Rate is proportional to overshoot, quantized to 0.05x increments.
|
|
277
|
+
*
|
|
278
|
+
* @param captureTimestampUs CaptureTimestamp in microseconds (Unix epoch)
|
|
279
|
+
* @returns CatchUpState if evaluated, null if catch-up is disabled or no timestamp
|
|
280
|
+
* @see draft-ietf-moq-msf-00 §5.1.16 (targetLatency)
|
|
281
|
+
*/
|
|
282
|
+
evaluateCatchUp(captureTimestampUs, isWallClock) {
|
|
283
|
+
// Measure latency
|
|
284
|
+
const latencyUs = this.measureLatency(captureTimestampUs, isWallClock);
|
|
285
|
+
if (latencyUs === null)
|
|
286
|
+
return null;
|
|
287
|
+
this._lastLatencyUs = latencyUs;
|
|
288
|
+
// Catch-up disabled?
|
|
289
|
+
if (this.targetLatencyMs === undefined || this.maxCatchUpRate <= 1.0)
|
|
290
|
+
return null;
|
|
291
|
+
const latencyMs = latencyUs / 1000;
|
|
292
|
+
const targetMs = this.targetLatencyMs;
|
|
293
|
+
// Hysteresis: activate/deactivate
|
|
294
|
+
if (!this._catchUpActive && latencyMs > targetMs + this.catchUpThresholdMs) {
|
|
295
|
+
this._catchUpActive = true;
|
|
296
|
+
}
|
|
297
|
+
else if (this._catchUpActive && latencyMs <= targetMs + this.catchUpRecoveryMs) {
|
|
298
|
+
this._catchUpActive = false;
|
|
299
|
+
this._currentRate = 1.0;
|
|
300
|
+
return { active: false, currentRate: 1.0, latencyMs, targetMs };
|
|
301
|
+
}
|
|
302
|
+
if (this._catchUpActive) {
|
|
303
|
+
// Proportional rate: scale linearly with overshoot
|
|
304
|
+
const overshoot = latencyMs - targetMs;
|
|
305
|
+
const ratio = Math.min(Math.max(overshoot / this.catchUpThresholdMs, 0), 1);
|
|
306
|
+
const rawRate = 1.0 + (this.maxCatchUpRate - 1.0) * ratio;
|
|
307
|
+
// Quantize to 0.05x increments
|
|
308
|
+
const quantized = Math.round(rawRate * 20) / 20;
|
|
309
|
+
// Clamp to [1.0, maxCatchUpRate]
|
|
310
|
+
this._currentRate = Math.min(Math.max(quantized, 1.0), this.maxCatchUpRate);
|
|
311
|
+
}
|
|
312
|
+
return {
|
|
313
|
+
active: this._catchUpActive,
|
|
314
|
+
currentRate: this._currentRate,
|
|
315
|
+
latencyMs,
|
|
316
|
+
targetMs,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Reset the sync reference (e.g., after a skip-forward).
|
|
321
|
+
*/
|
|
322
|
+
reset() {
|
|
323
|
+
this.localBaselineUs = undefined;
|
|
324
|
+
this.captureBaselineUs = undefined;
|
|
325
|
+
this._currentDriftUs = 0;
|
|
326
|
+
this._videoJoinOffsetUs = 0;
|
|
327
|
+
this._catchUpActive = false;
|
|
328
|
+
this._currentRate = 1.0;
|
|
329
|
+
}
|
|
330
|
+
// ─── Internal ───────────────────────────────────────────────────
|
|
331
|
+
computeRenderTime(captureTimestampUs) {
|
|
332
|
+
if (this.localBaselineUs === undefined || this.captureBaselineUs === undefined) {
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
const now = this.clock.now();
|
|
336
|
+
const renderTimeUs = this.localBaselineUs +
|
|
337
|
+
Number(captureTimestampUs - this.captureBaselineUs);
|
|
338
|
+
const offsetUs = renderTimeUs - now;
|
|
339
|
+
const shouldDrop = offsetUs < -this.dropThresholdUs;
|
|
340
|
+
return { renderTimeUs, offsetUs, shouldDrop };
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
//# sourceMappingURL=sync.js.map
|
package/dist/sync.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sans-I/O playback core type definitions.
|
|
3
|
+
*
|
|
4
|
+
* These types define the boundary between the playback core (testable in
|
|
5
|
+
* Node.js) and the browser adapter (WebCodecs, Canvas, AudioContext).
|
|
6
|
+
*
|
|
7
|
+
* @see draft-ietf-moq-loc-01 §4 (Application Mapping)
|
|
8
|
+
* @see draft-ietf-moq-transport-16 §10 (Objects)
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
import type { VideoChunkInit, AudioChunkInit } from '@openmoq/loc';
|
|
12
|
+
import type { RecoveryAction } from './recovery.js';
|
|
13
|
+
/**
|
|
14
|
+
* Injectable clock source for testability.
|
|
15
|
+
*
|
|
16
|
+
* Browser adapter injects `performance.now() * 1000` (microseconds).
|
|
17
|
+
* Tests inject a manual clock with deterministic time.
|
|
18
|
+
*
|
|
19
|
+
* @see draft-ietf-moq-loc-01 §2.3.1.1 (CaptureTimestamp uses microseconds)
|
|
20
|
+
*/
|
|
21
|
+
export interface ClockSource {
|
|
22
|
+
/** Current time in microseconds (monotonic). */
|
|
23
|
+
now(): number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Commands emitted by the playback core to the browser decoder adapter.
|
|
27
|
+
* The core never calls WebCodecs directly — it produces these commands.
|
|
28
|
+
*/
|
|
29
|
+
export type DecoderCommand = ConfigureCommand | DecodeVideoCommand | DecodeAudioCommand | FlushCommand | ResetCommand | SetPlaybackRateCommand;
|
|
30
|
+
/** Configure the decoder with codec extradata. @see draft-ietf-moq-loc-01 §2.3.2.1 */
|
|
31
|
+
export interface ConfigureCommand {
|
|
32
|
+
readonly type: 'configure';
|
|
33
|
+
readonly mediaType: 'video' | 'audio';
|
|
34
|
+
readonly config: Uint8Array;
|
|
35
|
+
}
|
|
36
|
+
/** Decode a video chunk at the specified render time. @see draft-ietf-moq-loc-01 §2.1 */
|
|
37
|
+
export interface DecodeVideoCommand {
|
|
38
|
+
readonly type: 'decode_video';
|
|
39
|
+
readonly chunk: VideoChunkInit;
|
|
40
|
+
readonly renderTimeUs: number;
|
|
41
|
+
/**
|
|
42
|
+
* CaptureTimestamp in microseconds — for PTS-anchored render scheduling.
|
|
43
|
+
* The renderer should use this (via RenderScheduler) instead of renderTimeUs
|
|
44
|
+
* for correct frame pacing after async decode.
|
|
45
|
+
* @see draft-ietf-moq-loc-01 §2.3.1.1
|
|
46
|
+
*/
|
|
47
|
+
readonly captureTimestampUs?: bigint;
|
|
48
|
+
}
|
|
49
|
+
/** Decode an audio chunk at the specified render time. @see draft-ietf-moq-loc-01 §2 */
|
|
50
|
+
export interface DecodeAudioCommand {
|
|
51
|
+
readonly type: 'decode_audio';
|
|
52
|
+
readonly chunk: AudioChunkInit;
|
|
53
|
+
readonly renderTimeUs: number;
|
|
54
|
+
}
|
|
55
|
+
/** Flush the decoder (drain pending frames). */
|
|
56
|
+
export interface FlushCommand {
|
|
57
|
+
readonly type: 'flush';
|
|
58
|
+
readonly mediaType: 'video' | 'audio';
|
|
59
|
+
}
|
|
60
|
+
/** Reset the decoder (e.g., after a gap). */
|
|
61
|
+
export interface ResetCommand {
|
|
62
|
+
readonly type: 'reset';
|
|
63
|
+
readonly mediaType: 'video' | 'audio';
|
|
64
|
+
readonly reason: string;
|
|
65
|
+
}
|
|
66
|
+
/** Adjust audio playback rate for live catch-up. @see draft-ietf-moq-msf-00 §5.1.16 */
|
|
67
|
+
export interface SetPlaybackRateCommand {
|
|
68
|
+
readonly type: 'set_playback_rate';
|
|
69
|
+
readonly rate: number;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Observability events emitted by the playback core.
|
|
73
|
+
*/
|
|
74
|
+
export type PlaybackEvent = GapDetectedEvent | SkipForwardEvent | SyncDriftEvent | KeyframeWaitingEvent | KeyframeValidationFailedEvent | TrackEndedEvent | RecoveryEvent | CatchUpChangedEvent | BacklogShedEvent | PartialGroupAbandonedEvent;
|
|
75
|
+
/** A partial video GOP was abandoned before END_OF_GROUP was received. */
|
|
76
|
+
export interface PartialGroupAbandonedEvent {
|
|
77
|
+
readonly type: 'partial_group_abandoned';
|
|
78
|
+
readonly fromGroupId: bigint;
|
|
79
|
+
readonly toGroupId: bigint;
|
|
80
|
+
readonly reason: string;
|
|
81
|
+
}
|
|
82
|
+
/** Old groups were dropped before decode to prevent backlog buildup. */
|
|
83
|
+
export interface BacklogShedEvent {
|
|
84
|
+
readonly type: 'backlog_shed';
|
|
85
|
+
readonly droppedGroups: number;
|
|
86
|
+
readonly remainingGroups: number;
|
|
87
|
+
readonly reason: string;
|
|
88
|
+
}
|
|
89
|
+
/** A group gap was detected. @see draft-ietf-moq-transport-16 §10.2.1.1 */
|
|
90
|
+
export interface GapDetectedEvent {
|
|
91
|
+
readonly type: 'gap_detected';
|
|
92
|
+
readonly groupId: bigint;
|
|
93
|
+
}
|
|
94
|
+
/** Skipped forward past a gap to a new group. */
|
|
95
|
+
export interface SkipForwardEvent {
|
|
96
|
+
readonly type: 'skip_forward';
|
|
97
|
+
readonly fromGroupId: bigint;
|
|
98
|
+
readonly toGroupId: bigint;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Video PRESENTATION SCHEDULE drift: the renderer presented a frame later (or
|
|
102
|
+
* earlier) than the exact time it was queued to present. This is NOT audio/video
|
|
103
|
+
* skew — see `sync_skew` for that.
|
|
104
|
+
*/
|
|
105
|
+
export interface SyncDriftEvent {
|
|
106
|
+
readonly type: 'sync_drift';
|
|
107
|
+
readonly driftUs: number;
|
|
108
|
+
}
|
|
109
|
+
/** Waiting for a keyframe after a gap. @see draft-ietf-moq-loc-01 §2.3.2.2 */
|
|
110
|
+
export interface KeyframeWaitingEvent {
|
|
111
|
+
readonly type: 'keyframe_waiting';
|
|
112
|
+
readonly groupId: bigint;
|
|
113
|
+
}
|
|
114
|
+
/** Keyframe payload validation failed — bitstream does not start with a keyframe. @see draft-ietf-moq-loc-01 §4.2 */
|
|
115
|
+
export interface KeyframeValidationFailedEvent {
|
|
116
|
+
readonly type: 'keyframe_validation_failed';
|
|
117
|
+
readonly groupId: bigint;
|
|
118
|
+
readonly objectId: bigint;
|
|
119
|
+
readonly codec: string;
|
|
120
|
+
}
|
|
121
|
+
/** Track has ended (END_OF_TRACK received). @see draft-ietf-moq-transport-16 §10.2.1.1 */
|
|
122
|
+
export interface TrackEndedEvent {
|
|
123
|
+
readonly type: 'track_ended';
|
|
124
|
+
}
|
|
125
|
+
/** Recovery controller recommends an action (local policy). */
|
|
126
|
+
export interface RecoveryEvent {
|
|
127
|
+
readonly type: 'recovery';
|
|
128
|
+
readonly action: RecoveryAction;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Catch-up state — latency measurement and rate adjustment.
|
|
132
|
+
*
|
|
133
|
+
* @see draft-ietf-moq-msf-00 §5.1.16 (targetLatency)
|
|
134
|
+
* @see draft-ietf-moq-loc-01 §2.3.1.1 (CaptureTimestamp for latency measurement)
|
|
135
|
+
*/
|
|
136
|
+
export interface CatchUpState {
|
|
137
|
+
/** Whether catch-up is currently active. */
|
|
138
|
+
readonly active: boolean;
|
|
139
|
+
/** Current playback rate (1.0 = normal, >1.0 = catching up). */
|
|
140
|
+
readonly currentRate: number;
|
|
141
|
+
/** Current end-to-end latency in milliseconds. */
|
|
142
|
+
readonly latencyMs: number;
|
|
143
|
+
/** Target latency in milliseconds. */
|
|
144
|
+
readonly targetMs: number;
|
|
145
|
+
}
|
|
146
|
+
/** Catch-up state changed (activated, rate adjusted, or deactivated). @see draft-ietf-moq-msf-00 §5.1.16 */
|
|
147
|
+
export interface CatchUpChangedEvent {
|
|
148
|
+
readonly type: 'catch_up_changed';
|
|
149
|
+
readonly state: CatchUpState;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Feedback from browser adapters back to the pipeline.
|
|
153
|
+
*
|
|
154
|
+
* Flow: adapter → CommandDispatcher → player → pipeline.handleFeedback()
|
|
155
|
+
*
|
|
156
|
+
* @see draft-ietf-moq-loc-01 §2.3.1.1 (CaptureTimestamp for drift detection)
|
|
157
|
+
*/
|
|
158
|
+
export type DecoderFeedback = QueuePressureFeedback | DecodeErrorFeedback | FrameRenderedFeedback | FlushCompleteFeedback;
|
|
159
|
+
/** Decoder queue getting deep — signal to throttle draining. */
|
|
160
|
+
export interface QueuePressureFeedback {
|
|
161
|
+
readonly type: 'queue_pressure';
|
|
162
|
+
readonly mediaType: 'video' | 'audio';
|
|
163
|
+
readonly depth: number;
|
|
164
|
+
readonly maxRecommended: number;
|
|
165
|
+
}
|
|
166
|
+
/** A decode() call failed. */
|
|
167
|
+
export interface DecodeErrorFeedback {
|
|
168
|
+
readonly type: 'decode_error';
|
|
169
|
+
readonly mediaType: 'video' | 'audio';
|
|
170
|
+
readonly message: string;
|
|
171
|
+
}
|
|
172
|
+
/** Actual render time for drift detection — video only. @see draft-ietf-moq-loc-01 §2.3.1.1 */
|
|
173
|
+
export interface FrameRenderedFeedback {
|
|
174
|
+
readonly type: 'frame_rendered';
|
|
175
|
+
readonly mediaType: 'video';
|
|
176
|
+
readonly captureTimestampUs: bigint;
|
|
177
|
+
readonly actualRenderUs: number;
|
|
178
|
+
/**
|
|
179
|
+
* The exact time this frame was scheduled to present. Absent when the
|
|
180
|
+
* renderer cannot report it, in which case presentation-schedule drift is
|
|
181
|
+
* suppressed rather than fabricated.
|
|
182
|
+
*/
|
|
183
|
+
readonly scheduledRenderUs?: number;
|
|
184
|
+
}
|
|
185
|
+
/** Flush completed (forward compatibility — currently a no-op). */
|
|
186
|
+
export interface FlushCompleteFeedback {
|
|
187
|
+
readonly type: 'flush_complete';
|
|
188
|
+
readonly mediaType: 'video' | 'audio';
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Playback pipeline configuration.
|
|
192
|
+
*/
|
|
193
|
+
export interface PlaybackConfig {
|
|
194
|
+
/** Maximum wait time in microseconds for a missing group before skip-forward. */
|
|
195
|
+
readonly gapTimeoutUs: number;
|
|
196
|
+
/** Video presentation-schedule drift threshold in microseconds. */
|
|
197
|
+
readonly driftThresholdUs: number;
|
|
198
|
+
/** Maximum number of objects in the jitter buffer. */
|
|
199
|
+
readonly maxBufferDepth: number;
|
|
200
|
+
/** Target end-to-end latency in milliseconds. @see draft-ietf-moq-msf-00 §5.1.16 */
|
|
201
|
+
readonly targetLatencyMs?: number;
|
|
202
|
+
/** Maximum playback rate for catch-up (e.g., 1.1 = 10% faster). Default: 1.0 (disabled). */
|
|
203
|
+
readonly maxCatchUpRate?: number;
|
|
204
|
+
/** Latency above target before catch-up activates (ms). Default: 500. */
|
|
205
|
+
readonly catchUpThresholdMs?: number;
|
|
206
|
+
/** Latency above target before catch-up deactivates (ms, hysteresis). Default: 50. */
|
|
207
|
+
readonly catchUpRecoveryMs?: number;
|
|
208
|
+
/**
|
|
209
|
+
* Maximum objects to release from the jitter buffer per tick (video only).
|
|
210
|
+
* Prevents greedy drain from flooding the decoder with backlog.
|
|
211
|
+
* 0 = unlimited (greedy drain, legacy behavior). Default: 5.
|
|
212
|
+
*/
|
|
213
|
+
readonly maxReleasePerTick?: number;
|
|
214
|
+
/**
|
|
215
|
+
* Maximum groups allowed in the jitter buffer before shedding old ones.
|
|
216
|
+
* When exceeded, oldest groups are dropped before decode.
|
|
217
|
+
* 0 = unlimited (no shedding). Default: 3.
|
|
218
|
+
*/
|
|
219
|
+
readonly maxBacklogGroups?: number;
|
|
220
|
+
/** Enable adaptive jitter tolerance. Auto-calibrates gap timeout and drift threshold. */
|
|
221
|
+
readonly adaptiveTolerance?: boolean;
|
|
222
|
+
/**
|
|
223
|
+
* QUIC handshake RTT in milliseconds. Used to classify network conditions
|
|
224
|
+
* and determine startup buffer depth. If not provided, defaults to
|
|
225
|
+
* conservative WAN parameters (8 frames / 300ms).
|
|
226
|
+
*/
|
|
227
|
+
readonly handshakeRttMs?: number;
|
|
228
|
+
}
|
|
229
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sans-I/O playback core type definitions.
|
|
3
|
+
*
|
|
4
|
+
* These types define the boundary between the playback core (testable in
|
|
5
|
+
* Node.js) and the browser adapter (WebCodecs, Canvas, AudioContext).
|
|
6
|
+
*
|
|
7
|
+
* @see draft-ietf-moq-loc-01 §4 (Application Mapping)
|
|
8
|
+
* @see draft-ietf-moq-transport-16 §10 (Objects)
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,59 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openmoq/playback",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "Sans-I/O media playback core for Media over QUIC (MoQ): jitter buffer, A/V sync, gap detection, decoder state machine, and pipeline orchestration.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"author": "OpenMOQ (https://github.com/openmoq)",
|
|
7
|
+
"contributors": [
|
|
8
|
+
"Raymond Lucke <ray@raylucke.com>"
|
|
9
|
+
],
|
|
10
|
+
"type": "module",
|
|
11
|
+
"main": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./browser": {
|
|
19
|
+
"import": "./dist/browser/index.js",
|
|
20
|
+
"default": "./dist/browser/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
"keywords": [
|
|
27
|
+
"moq",
|
|
28
|
+
"media-over-quic",
|
|
29
|
+
"moqt",
|
|
30
|
+
"quic",
|
|
31
|
+
"streaming",
|
|
32
|
+
"low-latency",
|
|
33
|
+
"live-video",
|
|
34
|
+
"playback",
|
|
35
|
+
"sans-io",
|
|
36
|
+
"jitter-buffer",
|
|
37
|
+
"av-sync"
|
|
38
|
+
],
|
|
39
|
+
"homepage": "https://github.com/openmoq/moq-playa/tree/main/packages/playback#readme",
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/openmoq/moq-playa/issues"
|
|
42
|
+
},
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "git+https://github.com/openmoq/moq-playa.git",
|
|
46
|
+
"directory": "packages/playback"
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@openmoq/transport": "0.6.0",
|
|
53
|
+
"@openmoq/loc": "0.6.0"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "tsc && tsup",
|
|
57
|
+
"build:browser": "tsup"
|
|
58
|
+
}
|
|
6
59
|
}
|