@norskvideo/moq-hang 0.1.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.
Files changed (130) hide show
  1. package/README.md +65 -0
  2. package/catalog/audio.d.ts +139 -0
  3. package/catalog/audio.d.ts.map +1 -0
  4. package/catalog/audio.js +55 -0
  5. package/catalog/audio.js.map +1 -0
  6. package/catalog/consumer.d.ts +34 -0
  7. package/catalog/consumer.d.ts.map +1 -0
  8. package/catalog/consumer.js +30 -0
  9. package/catalog/consumer.js.map +1 -0
  10. package/catalog/container.d.ts +24 -0
  11. package/catalog/container.d.ts.map +1 -0
  12. package/catalog/container.js +30 -0
  13. package/catalog/container.js.map +1 -0
  14. package/catalog/format.d.ts +13 -0
  15. package/catalog/format.d.ts.map +1 -0
  16. package/catalog/format.js +24 -0
  17. package/catalog/format.js.map +1 -0
  18. package/catalog/index.d.ts +18 -0
  19. package/catalog/index.d.ts.map +1 -0
  20. package/catalog/index.js +19 -0
  21. package/catalog/index.js.map +1 -0
  22. package/catalog/integers.d.ts +23 -0
  23. package/catalog/integers.d.ts.map +1 -0
  24. package/catalog/integers.js +24 -0
  25. package/catalog/integers.js.map +1 -0
  26. package/catalog/priority.d.ts +7 -0
  27. package/catalog/priority.d.ts.map +1 -0
  28. package/catalog/priority.js +9 -0
  29. package/catalog/priority.js.map +1 -0
  30. package/catalog/producer.d.ts +31 -0
  31. package/catalog/producer.d.ts.map +1 -0
  32. package/catalog/producer.js +25 -0
  33. package/catalog/producer.js.map +1 -0
  34. package/catalog/root.d.ts +252 -0
  35. package/catalog/root.d.ts.map +1 -0
  36. package/catalog/root.js +17 -0
  37. package/catalog/root.js.map +1 -0
  38. package/catalog/root.test.d.ts +2 -0
  39. package/catalog/root.test.d.ts.map +1 -0
  40. package/catalog/root.test.js +20 -0
  41. package/catalog/root.test.js.map +1 -0
  42. package/catalog/timeline.d.ts +26 -0
  43. package/catalog/timeline.d.ts.map +1 -0
  44. package/catalog/timeline.js +33 -0
  45. package/catalog/timeline.js.map +1 -0
  46. package/catalog/track.d.ts +8 -0
  47. package/catalog/track.d.ts.map +1 -0
  48. package/catalog/track.js +7 -0
  49. package/catalog/track.js.map +1 -0
  50. package/catalog/video.d.ts +171 -0
  51. package/catalog/video.d.ts.map +1 -0
  52. package/catalog/video.js +87 -0
  53. package/catalog/video.js.map +1 -0
  54. package/container/cmaf/decode.d.ts +68 -0
  55. package/container/cmaf/decode.d.ts.map +1 -0
  56. package/container/cmaf/decode.js +303 -0
  57. package/container/cmaf/decode.js.map +1 -0
  58. package/container/cmaf/encode.d.ts +51 -0
  59. package/container/cmaf/encode.d.ts.map +1 -0
  60. package/container/cmaf/encode.js +834 -0
  61. package/container/cmaf/encode.js.map +1 -0
  62. package/container/cmaf/format.d.ts +12 -0
  63. package/container/cmaf/format.d.ts.map +1 -0
  64. package/container/cmaf/format.js +19 -0
  65. package/container/cmaf/format.js.map +1 -0
  66. package/container/cmaf/index.d.ts +9 -0
  67. package/container/cmaf/index.d.ts.map +1 -0
  68. package/container/cmaf/index.js +10 -0
  69. package/container/cmaf/index.js.map +1 -0
  70. package/container/consumer.d.ts +41 -0
  71. package/container/consumer.d.ts.map +1 -0
  72. package/container/consumer.js +424 -0
  73. package/container/consumer.js.map +1 -0
  74. package/container/consumer.test.d.ts +2 -0
  75. package/container/consumer.test.d.ts.map +1 -0
  76. package/container/consumer.test.js +396 -0
  77. package/container/consumer.test.js.map +1 -0
  78. package/container/format.d.ts +12 -0
  79. package/container/format.d.ts.map +1 -0
  80. package/container/format.js +3 -0
  81. package/container/format.js.map +1 -0
  82. package/container/index.d.ts +14 -0
  83. package/container/index.d.ts.map +1 -0
  84. package/container/index.js +18 -0
  85. package/container/index.js.map +1 -0
  86. package/container/legacy.d.ts +39 -0
  87. package/container/legacy.d.ts.map +1 -0
  88. package/container/legacy.js +53 -0
  89. package/container/legacy.js.map +1 -0
  90. package/container/timeline.d.ts +67 -0
  91. package/container/timeline.d.ts.map +1 -0
  92. package/container/timeline.js +87 -0
  93. package/container/timeline.js.map +1 -0
  94. package/container/types.d.ts +22 -0
  95. package/container/types.d.ts.map +1 -0
  96. package/container/types.js +22 -0
  97. package/container/types.js.map +1 -0
  98. package/index.d.ts +13 -0
  99. package/index.d.ts.map +1 -0
  100. package/index.js +18 -0
  101. package/index.js.map +1 -0
  102. package/package.json +37 -0
  103. package/util/aac.d.ts +8 -0
  104. package/util/aac.d.ts.map +1 -0
  105. package/util/aac.js +57 -0
  106. package/util/aac.js.map +1 -0
  107. package/util/aac.test.d.ts +2 -0
  108. package/util/aac.test.d.ts.map +1 -0
  109. package/util/aac.test.js +34 -0
  110. package/util/aac.test.js.map +1 -0
  111. package/util/hacks.d.ts +5 -0
  112. package/util/hacks.d.ts.map +1 -0
  113. package/util/hacks.js +6 -0
  114. package/util/hacks.js.map +1 -0
  115. package/util/hex.d.ts +5 -0
  116. package/util/hex.d.ts.map +1 -0
  117. package/util/hex.js +18 -0
  118. package/util/hex.js.map +1 -0
  119. package/util/index.d.ts +12 -0
  120. package/util/index.d.ts.map +1 -0
  121. package/util/index.js +18 -0
  122. package/util/index.js.map +1 -0
  123. package/util/latency.d.ts +38 -0
  124. package/util/latency.d.ts.map +1 -0
  125. package/util/latency.js +49 -0
  126. package/util/latency.js.map +1 -0
  127. package/util/libav.d.ts +3 -0
  128. package/util/libav.d.ts.map +1 -0
  129. package/util/libav.js +26 -0
  130. package/util/libav.js.map +1 -0
@@ -0,0 +1,424 @@
1
+ /* @ts-self-types="./consumer.d.ts" */
2
+ import * as Moq from "@norskvideo/moq-net";
3
+ import { Effect, Signal } from "@norskvideo/moq-signals";
4
+ /**
5
+ * A recorded rewind boundary.
6
+ *
7
+ * After a backwards timestamp jump, groups can still arrive out of order, so a single
8
+ * sequence floor is not enough: a late new-epoch group can have a lower sequence than the
9
+ * group that triggered detection. This keeps just enough state to classify any group by
10
+ * its (sequence, timestamp).
11
+ */
12
+ class Reset {
13
+ /** Highest-sequence old-epoch group seen at detection. At or below this is old: drop. */
14
+ prevMax;
15
+ /** The group whose backwards timestamp triggered detection. At or above this is new: keep. */
16
+ group;
17
+ /** That group's timestamp; in the ambiguous span, old stragglers sit at or above it. */
18
+ timestamp;
19
+ constructor(prevMax, group, timestamp) {
20
+ this.prevMax = prevMax;
21
+ this.group = group;
22
+ this.timestamp = timestamp;
23
+ }
24
+ /** Classify by sequence alone: true=old, false=new, undefined=ambiguous (resolve by timestamp). */
25
+ bySequence(sequence) {
26
+ if (sequence <= this.prevMax)
27
+ return true;
28
+ if (sequence >= this.group)
29
+ return false;
30
+ return undefined;
31
+ }
32
+ /** Whether a group belongs to the reneged old epoch and should be dropped. */
33
+ isStale(sequence, timestamp) {
34
+ return this.bySequence(sequence) ?? timestamp >= this.timestamp;
35
+ }
36
+ }
37
+ /**
38
+ * Live state for detecting timeline rewinds and classifying out-of-order groups.
39
+ *
40
+ * A publisher reneges its buffered tail by rewinding timestamps while group sequence keeps
41
+ * climbing (e.g. a voice agent interrupted mid-utterance). We track the live edge to spot the
42
+ * jump, a {@link Reset} boundary to classify out-of-order groups across it, and a counter that
43
+ * downstream consumers watch to flush their own queues.
44
+ */
45
+ class Rewind {
46
+ /** The live edge of playback: max delivered timestamp and the group that carried it. */
47
+ liveEdge;
48
+ /** The active rewind boundary, if any. */
49
+ boundary;
50
+ /** Increments on every rewind; downstream consumers flush their queues when it changes. */
51
+ discontinuity = 0;
52
+ }
53
+ /** Reads frames from a MoQ track in order, buffering groups and skipping slow ones to meet the latency target. */
54
+ export class Consumer {
55
+ #track;
56
+ #format;
57
+ #latency;
58
+ #groups = [];
59
+ #active; // the active group sequence number
60
+ #rewind = new Rewind(); // live edge + active boundary + discontinuity count
61
+ // Wake up the consumer when a new frame is available.
62
+ #notify;
63
+ #buffered = new Signal([]);
64
+ /** The time ranges currently buffered and ready to play. */
65
+ buffered = this.#buffered;
66
+ #signals = new Effect();
67
+ /** Start consuming the given track, decoding frames with `props.format`. */
68
+ constructor(track, props) {
69
+ this.#track = track;
70
+ this.#format = props.format;
71
+ this.#latency = Signal.from(props.latency ?? Moq.Time.Milli.zero);
72
+ this.#signals.spawn(this.#run.bind(this));
73
+ this.#signals.cleanup(() => {
74
+ this.#track.close();
75
+ for (const group of this.#groups) {
76
+ group.consumer.close();
77
+ }
78
+ this.#groups.length = 0;
79
+ });
80
+ }
81
+ async #run() {
82
+ // Start fetching groups in the background
83
+ for (;;) {
84
+ const consumer = await this.#track.recvGroup();
85
+ if (!consumer)
86
+ break;
87
+ // To improve TTV, we always start with the first group.
88
+ // For higher latencies we might need to figure something else out, as its racey.
89
+ if (this.#active === undefined) {
90
+ this.#active = consumer.sequence;
91
+ }
92
+ // Normally we drop anything behind the cursor. With an active reset the cursor isn't
93
+ // a valid floor (a late new-epoch group can sit below it); defer to the boundary and
94
+ // admit ambiguous groups so #runGroup can rule on them once their timestamps arrive.
95
+ let drop;
96
+ if (this.#rewind.boundary) {
97
+ const verdict = this.#rewind.boundary.bySequence(consumer.sequence);
98
+ if (verdict === undefined)
99
+ drop = false;
100
+ else if (verdict)
101
+ drop = true;
102
+ else
103
+ drop = consumer.sequence < this.#active;
104
+ }
105
+ else {
106
+ drop = consumer.sequence < this.#active;
107
+ }
108
+ if (drop) {
109
+ console.warn(`skipping old group: track=${this.#track.name} ${consumer.sequence}`);
110
+ consumer.close();
111
+ continue;
112
+ }
113
+ const group = {
114
+ consumer,
115
+ frames: [],
116
+ };
117
+ // Insert into #groups based on the group sequence number (ascending).
118
+ // This is used to cancel old groups.
119
+ this.#groups.push(group);
120
+ this.#groups.sort((a, b) => a.consumer.sequence - b.consumer.sequence);
121
+ // If #active parked in a gap below the (new) oldest group, snap it
122
+ // forward so this group plays rather than stalling until a skip.
123
+ this.#syncActive();
124
+ // Start buffering frames from this group
125
+ this.#signals.spawn(this.#runGroup.bind(this, group));
126
+ }
127
+ }
128
+ async #runGroup(group) {
129
+ try {
130
+ let index = 0;
131
+ for (;;) {
132
+ const next = await group.consumer.readFrameSequence();
133
+ if (!next)
134
+ break;
135
+ const decoded = this.#format.decode(next.data, next.extensions);
136
+ for (const sample of decoded) {
137
+ const frame = {
138
+ data: sample.data,
139
+ timestamp: sample.timestamp,
140
+ // Protocol invariant: groups always start at a keyframe.
141
+ // For index 0, we enforce this regardless of what the format reports.
142
+ // For index > 0, we trust the format's keyframe detection.
143
+ keyframe: index === 0 ? true : sample.keyframe,
144
+ };
145
+ index++;
146
+ group.frames.push(frame);
147
+ if (group.latest === undefined || frame.timestamp > group.latest) {
148
+ group.latest = frame.timestamp;
149
+ }
150
+ this.#updateBuffered();
151
+ if (group.consumer.sequence === this.#active) {
152
+ this.#notify?.();
153
+ this.#notify = undefined;
154
+ }
155
+ else {
156
+ // Newer group: resolve it against an active reset (dropping a reneged
157
+ // straggler), else detect a new rewind, then check latency.
158
+ if (this.#classifyStale(group))
159
+ return;
160
+ this.#checkReset(group);
161
+ this.#checkLatency();
162
+ }
163
+ }
164
+ }
165
+ }
166
+ catch (_err) {
167
+ // Stop reading the group but keep already-decoded frames.
168
+ // A decode error or stream RESET truncates the tail of the GoP;
169
+ // frames decoded before the error are still valid and playable.
170
+ }
171
+ finally {
172
+ group.done = true;
173
+ // #active advances when a group is *removed* (fully played) in next(),
174
+ // via #syncActive — a group finishing reading doesn't mean it's played.
175
+ // (Group ids are not consecutive, so we never `+= 1`; see #syncActive.)
176
+ // Recompute buffered ranges now that this group is done,
177
+ // so consecutive done groups can merge into a single range.
178
+ this.#updateBuffered();
179
+ // Always notify - the consumer may need to advance past this group
180
+ // even if it wasn't active when this task finished.
181
+ this.#notify?.();
182
+ this.#notify = undefined;
183
+ group.consumer.close();
184
+ }
185
+ }
186
+ // Snap #active forward to the oldest buffered group when it has fallen into a
187
+ // gap below it. MoQ group ids track the media clock and are NOT necessarily
188
+ // consecutive (spec §2.3.1; gaps are explicit via §12.8 Prior Group ID Gap),
189
+ // so advancing by `+ 1` lands in a gap and the next real group never becomes
190
+ // active — it buffers until #checkLatency skips it. Snapping to the oldest
191
+ // actual sequence lets next() play each group. Only ever moves forward to the
192
+ // oldest available group (never past it), so it can't skip content.
193
+ #syncActive() {
194
+ const oldest = this.#groups[0]?.consumer.sequence;
195
+ if (oldest !== undefined && (this.#active === undefined || this.#active < oldest)) {
196
+ this.#active = oldest;
197
+ }
198
+ }
199
+ #checkLatency() {
200
+ if (this.#active === undefined)
201
+ return;
202
+ let skipped = false;
203
+ // Keep skipping the oldest group while the buffered span exceeds the latency target.
204
+ // This also handles gaps in group sequence numbers: if #active points to a missing
205
+ // group, the latency span proves the missing content is too old to wait for.
206
+ while (this.#groups.length >= 2) {
207
+ const threshold = Moq.Time.Micro.fromMilli(this.#latency.peek());
208
+ // Check the difference between the earliest and latest known frames.
209
+ let min;
210
+ let max;
211
+ for (const group of this.#groups) {
212
+ if (group.latest === undefined)
213
+ continue;
214
+ const frame = group.frames.at(0)?.timestamp ?? group.latest;
215
+ if (min === undefined || frame < min)
216
+ min = frame;
217
+ if (max === undefined || group.latest > max)
218
+ max = group.latest;
219
+ }
220
+ if (min === undefined || max === undefined)
221
+ break;
222
+ const latency = max - min;
223
+ if (latency <= threshold)
224
+ break;
225
+ const first = this.#groups.shift();
226
+ if (!first)
227
+ break;
228
+ this.#active = this.#groups[0]?.consumer.sequence;
229
+ console.warn(`skipping slow group: track=${this.#track.name} ${first.consumer.sequence} -> ${this.#active}`);
230
+ first.consumer.close();
231
+ first.frames.length = 0;
232
+ skipped = true;
233
+ }
234
+ if (skipped) {
235
+ this.#updateBuffered();
236
+ // Wake up any consumers waiting for a new frame.
237
+ this.#notify?.();
238
+ this.#notify = undefined;
239
+ }
240
+ }
241
+ // Detect a publisher "rewind" and record the reneged boundary. A newer group (sequence
242
+ // climbs) whose earliest frame lands before the live edge (timestamp goes backwards) can
243
+ // only be an explicit reneg of the buffered tail; record the boundary, bump the
244
+ // discontinuity counter, drop the groups it proves stale, and resume from the earliest
245
+ // survivor. Groups still ambiguous (a late new-epoch group vs. an old straggler) are kept
246
+ // and resolved by #classifyStale once their timestamps arrive.
247
+ #checkReset(group) {
248
+ if (this.#active === undefined)
249
+ return;
250
+ const live = this.#rewind.liveEdge;
251
+ if (live === undefined)
252
+ return;
253
+ // Only a group newer than the active one can rewind the timeline.
254
+ if (group.consumer.sequence <= this.#active)
255
+ return;
256
+ const start = group.frames.at(0)?.timestamp;
257
+ if (start === undefined)
258
+ return;
259
+ // A rewind is the timestamp going strictly backwards past the live edge. Anything at
260
+ // or ahead of it is normal forward motion.
261
+ if (start >= live.timestamp)
262
+ return;
263
+ const reset = new Reset(live.group, group.consumer.sequence, start);
264
+ this.#rewind.boundary = reset;
265
+ this.#rewind.discontinuity++;
266
+ // Drop buffered groups the boundary can already prove stale; keep ambiguous ones.
267
+ this.#groups = this.#groups.filter((g) => {
268
+ const verdict = reset.bySequence(g.consumer.sequence);
269
+ const first = g.frames.at(0);
270
+ const stale = verdict ?? (first !== undefined && reset.isStale(g.consumer.sequence, first.timestamp));
271
+ if (stale) {
272
+ g.consumer.close();
273
+ g.frames.length = 0;
274
+ }
275
+ return !stale;
276
+ });
277
+ console.warn(`buffer reset: track=${this.#track.name} group timestamps rewound (prevMax ${reset.prevMax}, group ${reset.group})`);
278
+ // Resume from the earliest survivor; if none, from the rewound group. Anchor the live
279
+ // edge at the rewind point (delivered), not group.latest (buffered ahead of playback).
280
+ this.#active = this.#groups[0]?.consumer.sequence ?? reset.group;
281
+ this.#rewind.liveEdge = { group: reset.group, timestamp: start };
282
+ this.#updateBuffered();
283
+ // Wake up any consumer waiting for a new frame.
284
+ this.#notify?.();
285
+ this.#notify = undefined;
286
+ }
287
+ // Drop a group that an active reset resolves as a reneged old straggler (its timestamp
288
+ // landed at or above the reset point). Returns true if the group was dropped.
289
+ #classifyStale(group) {
290
+ const reset = this.#rewind.boundary;
291
+ if (!reset)
292
+ return false;
293
+ const first = group.frames.at(0);
294
+ if (first === undefined)
295
+ return false;
296
+ if (!reset.isStale(group.consumer.sequence, first.timestamp))
297
+ return false;
298
+ this.#groups = this.#groups.filter((g) => g !== group);
299
+ group.consumer.close();
300
+ group.frames.length = 0;
301
+ this.#updateBuffered();
302
+ return true;
303
+ }
304
+ // Re-check buffered newer groups against the current live edge. #checkReset otherwise only
305
+ // runs when a group receives a frame, so a group that buffered while the live edge was lower
306
+ // (or undefined) is never reconsidered once delivery advances the edge past it, and the
307
+ // rewind would be missed. Highest sequence first, mirroring the Rust scan: the first rewound
308
+ // group becomes the boundary, and #checkReset's own guards make the rest no-ops.
309
+ #checkBufferedReset() {
310
+ if (this.#active === undefined || this.#rewind.liveEdge === undefined)
311
+ return;
312
+ for (const group of [...this.#groups].reverse()) {
313
+ if (group.consumer.sequence <= this.#active)
314
+ break;
315
+ this.#checkReset(group);
316
+ }
317
+ }
318
+ /**
319
+ * Returns the next frame in order along with its group number and the current
320
+ * {@link discontinuity} count, awaiting one if needed. A `frame` of undefined signals the
321
+ * end of that group; the overall result is undefined once closed. When `discontinuity`
322
+ * jumps relative to the previous call, the publisher rewound the timeline: flush any
323
+ * downstream decoder or render buffers before playing this frame.
324
+ */
325
+ async next() {
326
+ for (;;) {
327
+ // A group may have buffered a rewind while the live edge was still behind it; catch it
328
+ // now that delivery has advanced the edge.
329
+ this.#checkBufferedReset();
330
+ // Keep #active tracking the oldest buffered group across non-consecutive
331
+ // group ids (see #syncActive).
332
+ this.#syncActive();
333
+ if (this.#groups.length > 0 &&
334
+ this.#active !== undefined &&
335
+ this.#groups[0].consumer.sequence <= this.#active) {
336
+ const frame = this.#groups[0].frames.shift();
337
+ if (frame) {
338
+ const seq = this.#groups[0].consumer.sequence;
339
+ // Track the live edge (max timestamp + its group) so a later backwards jump
340
+ // is detectable and the old epoch's tail is anchored.
341
+ const live = this.#rewind.liveEdge;
342
+ if (live === undefined || frame.timestamp > live.timestamp) {
343
+ this.#rewind.liveEdge = { group: seq, timestamp: frame.timestamp };
344
+ }
345
+ this.#updateBuffered();
346
+ return { frame, group: seq, discontinuity: this.#rewind.discontinuity };
347
+ }
348
+ // Check if the group is done and then remove it.
349
+ // A group is removable when #active has advanced past it, OR when
350
+ // its #runGroup task has finished (done) and all frames are consumed.
351
+ // The latter handles the case where #runGroup finished before
352
+ // #active reached this group (e.g. after a latency skip).
353
+ if (this.#active > this.#groups[0].consumer.sequence || this.#groups[0].done) {
354
+ const group = this.#groups.shift();
355
+ if (group) {
356
+ // Advance to the next buffered group (ids are not consecutive).
357
+ this.#syncActive();
358
+ this.#updateBuffered();
359
+ return {
360
+ frame: undefined,
361
+ group: group.consumer.sequence,
362
+ discontinuity: this.#rewind.discontinuity,
363
+ };
364
+ }
365
+ }
366
+ }
367
+ if (this.#notify) {
368
+ throw new Error("multiple calls to next not supported");
369
+ }
370
+ const abort = this.#signals.abort;
371
+ if (abort.aborted)
372
+ return undefined;
373
+ const aborted = await new Promise((resolve) => {
374
+ const onAbort = () => resolve(true);
375
+ abort.addEventListener("abort", onAbort, { once: true });
376
+ this.#notify = () => {
377
+ abort.removeEventListener("abort", onAbort);
378
+ resolve(false);
379
+ };
380
+ });
381
+ this.#notify = undefined;
382
+ if (aborted)
383
+ return undefined;
384
+ }
385
+ }
386
+ #updateBuffered() {
387
+ const ranges = [];
388
+ let prev;
389
+ for (const group of this.#groups) {
390
+ const first = group.frames.at(0);
391
+ if (!first || group.latest === undefined)
392
+ continue;
393
+ const start = Moq.Time.Milli.fromMicro(first.timestamp);
394
+ const end = Moq.Time.Milli.fromMicro(group.latest);
395
+ const last = ranges.at(-1);
396
+ // `prev` is the immediately-preceding buffered group (this loop walks
397
+ // #groups in ascending order), so a done `prev` means these two groups
398
+ // are buffer-adjacent. We can't test `prev.sequence + 1 === sequence`
399
+ // because group ids are not consecutive (spec §2.3.1).
400
+ const contiguous = prev?.done === true;
401
+ if (last && (last.end >= start || contiguous)) {
402
+ last.end = Moq.Time.Milli.max(last.end, end);
403
+ }
404
+ else {
405
+ ranges.push({ start, end });
406
+ }
407
+ prev = group;
408
+ }
409
+ this.#buffered.set(ranges);
410
+ }
411
+ /**
412
+ * A counter that increments each time the consumer detects a timeline rewind and drops the
413
+ * reneged buffer. Also surfaced per-read via {@link next}; downstream consumers flush their
414
+ * decoder and render buffers when it changes.
415
+ */
416
+ get discontinuity() {
417
+ return this.#rewind.discontinuity;
418
+ }
419
+ /** Stop consuming and release the track and all buffered groups. */
420
+ close() {
421
+ this.#signals.close();
422
+ }
423
+ }
424
+ //# sourceMappingURL=consumer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consumer.js","sourceRoot":"","sources":["../../src/container/consumer.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Time } from \"@moq/net\";\nimport * as Moq from \"@moq/net\";\nimport { Effect, type Getter, Signal } from \"@moq/signals\";\n\nimport type { Format } from \"./format\";\nimport type { BufferedRanges, Frame } from \"./types\";\n\n/** Options for constructing a {@link Consumer}. */\nexport interface ConsumerProps {\n\t/** The container format used to decode each MoQ frame. */\n\tformat: Format;\n\t/** Target latency in milliseconds, controlling how aggressively slow groups are skipped (default: 0). */\n\tlatency?: Signal<Time.Milli> | Time.Milli;\n}\n\ninterface Group {\n\tconsumer: Moq.Group;\n\tframes: Frame[]; // decode order\n\tlatest?: Time.Micro; // The timestamp of the latest known frame\n\tdone?: boolean; // Set when #runGroup finishes reading all frames\n}\n\n/**\n * A recorded rewind boundary.\n *\n * After a backwards timestamp jump, groups can still arrive out of order, so a single\n * sequence floor is not enough: a late new-epoch group can have a lower sequence than the\n * group that triggered detection. This keeps just enough state to classify any group by\n * its (sequence, timestamp).\n */\nclass Reset {\n\t/** Highest-sequence old-epoch group seen at detection. At or below this is old: drop. */\n\treadonly prevMax: number;\n\t/** The group whose backwards timestamp triggered detection. At or above this is new: keep. */\n\treadonly group: number;\n\t/** That group's timestamp; in the ambiguous span, old stragglers sit at or above it. */\n\treadonly timestamp: Time.Micro;\n\n\tconstructor(prevMax: number, group: number, timestamp: Time.Micro) {\n\t\tthis.prevMax = prevMax;\n\t\tthis.group = group;\n\t\tthis.timestamp = timestamp;\n\t}\n\n\t/** Classify by sequence alone: true=old, false=new, undefined=ambiguous (resolve by timestamp). */\n\tbySequence(sequence: number): boolean | undefined {\n\t\tif (sequence <= this.prevMax) return true;\n\t\tif (sequence >= this.group) return false;\n\t\treturn undefined;\n\t}\n\n\t/** Whether a group belongs to the reneged old epoch and should be dropped. */\n\tisStale(sequence: number, timestamp: Time.Micro): boolean {\n\t\treturn this.bySequence(sequence) ?? timestamp >= this.timestamp;\n\t}\n}\n\n/**\n * Live state for detecting timeline rewinds and classifying out-of-order groups.\n *\n * A publisher reneges its buffered tail by rewinding timestamps while group sequence keeps\n * climbing (e.g. a voice agent interrupted mid-utterance). We track the live edge to spot the\n * jump, a {@link Reset} boundary to classify out-of-order groups across it, and a counter that\n * downstream consumers watch to flush their own queues.\n */\nclass Rewind {\n\t/** The live edge of playback: max delivered timestamp and the group that carried it. */\n\tliveEdge?: { group: number; timestamp: Time.Micro };\n\t/** The active rewind boundary, if any. */\n\tboundary?: Reset;\n\t/** Increments on every rewind; downstream consumers flush their queues when it changes. */\n\tdiscontinuity = 0;\n}\n\n/** Reads frames from a MoQ track in order, buffering groups and skipping slow ones to meet the latency target. */\nexport class Consumer {\n\t#track: Moq.Track;\n\t#format: Format;\n\t#latency: Signal<Time.Milli>;\n\t#groups: Group[] = [];\n\t#active?: number; // the active group sequence number\n\t#rewind = new Rewind(); // live edge + active boundary + discontinuity count\n\n\t// Wake up the consumer when a new frame is available.\n\t#notify?: () => void;\n\n\t#buffered = new Signal<BufferedRanges>([]);\n\t/** The time ranges currently buffered and ready to play. */\n\treadonly buffered: Getter<BufferedRanges> = this.#buffered;\n\n\t#signals = new Effect();\n\n\t/** Start consuming the given track, decoding frames with `props.format`. */\n\tconstructor(track: Moq.Track, props: ConsumerProps) {\n\t\tthis.#track = track;\n\t\tthis.#format = props.format;\n\t\tthis.#latency = Signal.from(props.latency ?? Moq.Time.Milli.zero);\n\n\t\tthis.#signals.spawn(this.#run.bind(this));\n\t\tthis.#signals.cleanup(() => {\n\t\t\tthis.#track.close();\n\t\t\tfor (const group of this.#groups) {\n\t\t\t\tgroup.consumer.close();\n\t\t\t}\n\t\t\tthis.#groups.length = 0;\n\t\t});\n\t}\n\n\tasync #run() {\n\t\t// Start fetching groups in the background\n\t\tfor (;;) {\n\t\t\tconst consumer = await this.#track.recvGroup();\n\t\t\tif (!consumer) break;\n\n\t\t\t// To improve TTV, we always start with the first group.\n\t\t\t// For higher latencies we might need to figure something else out, as its racey.\n\t\t\tif (this.#active === undefined) {\n\t\t\t\tthis.#active = consumer.sequence;\n\t\t\t}\n\n\t\t\t// Normally we drop anything behind the cursor. With an active reset the cursor isn't\n\t\t\t// a valid floor (a late new-epoch group can sit below it); defer to the boundary and\n\t\t\t// admit ambiguous groups so #runGroup can rule on them once their timestamps arrive.\n\t\t\tlet drop: boolean;\n\t\t\tif (this.#rewind.boundary) {\n\t\t\t\tconst verdict = this.#rewind.boundary.bySequence(consumer.sequence);\n\t\t\t\tif (verdict === undefined) drop = false;\n\t\t\t\telse if (verdict) drop = true;\n\t\t\t\telse drop = consumer.sequence < this.#active;\n\t\t\t} else {\n\t\t\t\tdrop = consumer.sequence < this.#active;\n\t\t\t}\n\n\t\t\tif (drop) {\n\t\t\t\tconsole.warn(`skipping old group: track=${this.#track.name} ${consumer.sequence}`);\n\t\t\t\tconsumer.close();\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst group: Group = {\n\t\t\t\tconsumer,\n\t\t\t\tframes: [],\n\t\t\t};\n\n\t\t\t// Insert into #groups based on the group sequence number (ascending).\n\t\t\t// This is used to cancel old groups.\n\t\t\tthis.#groups.push(group);\n\t\t\tthis.#groups.sort((a, b) => a.consumer.sequence - b.consumer.sequence);\n\n\t\t\t// If #active parked in a gap below the (new) oldest group, snap it\n\t\t\t// forward so this group plays rather than stalling until a skip.\n\t\t\tthis.#syncActive();\n\n\t\t\t// Start buffering frames from this group\n\t\t\tthis.#signals.spawn(this.#runGroup.bind(this, group));\n\t\t}\n\t}\n\n\tasync #runGroup(group: Group) {\n\t\ttry {\n\t\t\tlet index = 0;\n\n\t\t\tfor (;;) {\n\t\t\t\tconst next = await group.consumer.readFrameSequence();\n\t\t\t\tif (!next) break;\n\n\t\t\t\tconst decoded = this.#format.decode(next.data, next.extensions);\n\n\t\t\t\tfor (const sample of decoded) {\n\t\t\t\t\tconst frame: Frame = {\n\t\t\t\t\t\tdata: sample.data,\n\t\t\t\t\t\ttimestamp: sample.timestamp,\n\t\t\t\t\t\t// Protocol invariant: groups always start at a keyframe.\n\t\t\t\t\t\t// For index 0, we enforce this regardless of what the format reports.\n\t\t\t\t\t\t// For index > 0, we trust the format's keyframe detection.\n\t\t\t\t\t\tkeyframe: index === 0 ? true : sample.keyframe,\n\t\t\t\t\t};\n\n\t\t\t\t\tindex++;\n\n\t\t\t\t\tgroup.frames.push(frame);\n\n\t\t\t\t\tif (group.latest === undefined || frame.timestamp > group.latest) {\n\t\t\t\t\t\tgroup.latest = frame.timestamp;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.#updateBuffered();\n\n\t\t\t\t\tif (group.consumer.sequence === this.#active) {\n\t\t\t\t\t\tthis.#notify?.();\n\t\t\t\t\t\tthis.#notify = undefined;\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Newer group: resolve it against an active reset (dropping a reneged\n\t\t\t\t\t\t// straggler), else detect a new rewind, then check latency.\n\t\t\t\t\t\tif (this.#classifyStale(group)) return;\n\t\t\t\t\t\tthis.#checkReset(group);\n\t\t\t\t\t\tthis.#checkLatency();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (_err) {\n\t\t\t// Stop reading the group but keep already-decoded frames.\n\t\t\t// A decode error or stream RESET truncates the tail of the GoP;\n\t\t\t// frames decoded before the error are still valid and playable.\n\t\t} finally {\n\t\t\tgroup.done = true;\n\n\t\t\t// #active advances when a group is *removed* (fully played) in next(),\n\t\t\t// via #syncActive — a group finishing reading doesn't mean it's played.\n\t\t\t// (Group ids are not consecutive, so we never `+= 1`; see #syncActive.)\n\n\t\t\t// Recompute buffered ranges now that this group is done,\n\t\t\t// so consecutive done groups can merge into a single range.\n\t\t\tthis.#updateBuffered();\n\n\t\t\t// Always notify - the consumer may need to advance past this group\n\t\t\t// even if it wasn't active when this task finished.\n\t\t\tthis.#notify?.();\n\t\t\tthis.#notify = undefined;\n\n\t\t\tgroup.consumer.close();\n\t\t}\n\t}\n\n\t// Snap #active forward to the oldest buffered group when it has fallen into a\n\t// gap below it. MoQ group ids track the media clock and are NOT necessarily\n\t// consecutive (spec §2.3.1; gaps are explicit via §12.8 Prior Group ID Gap),\n\t// so advancing by `+ 1` lands in a gap and the next real group never becomes\n\t// active — it buffers until #checkLatency skips it. Snapping to the oldest\n\t// actual sequence lets next() play each group. Only ever moves forward to the\n\t// oldest available group (never past it), so it can't skip content.\n\t#syncActive(): void {\n\t\tconst oldest = this.#groups[0]?.consumer.sequence;\n\t\tif (oldest !== undefined && (this.#active === undefined || this.#active < oldest)) {\n\t\t\tthis.#active = oldest;\n\t\t}\n\t}\n\n\t#checkLatency() {\n\t\tif (this.#active === undefined) return;\n\n\t\tlet skipped = false;\n\n\t\t// Keep skipping the oldest group while the buffered span exceeds the latency target.\n\t\t// This also handles gaps in group sequence numbers: if #active points to a missing\n\t\t// group, the latency span proves the missing content is too old to wait for.\n\t\twhile (this.#groups.length >= 2) {\n\t\t\tconst threshold = Moq.Time.Micro.fromMilli(this.#latency.peek());\n\n\t\t\t// Check the difference between the earliest and latest known frames.\n\t\t\tlet min: number | undefined;\n\t\t\tlet max: number | undefined;\n\n\t\t\tfor (const group of this.#groups) {\n\t\t\t\tif (group.latest === undefined) continue;\n\n\t\t\t\tconst frame = group.frames.at(0)?.timestamp ?? group.latest;\n\t\t\t\tif (min === undefined || frame < min) min = frame;\n\t\t\t\tif (max === undefined || group.latest > max) max = group.latest;\n\t\t\t}\n\n\t\t\tif (min === undefined || max === undefined) break;\n\n\t\t\tconst latency = max - min;\n\t\t\tif (latency <= threshold) break;\n\n\t\t\tconst first = this.#groups.shift();\n\t\t\tif (!first) break;\n\t\t\tthis.#active = this.#groups[0]?.consumer.sequence;\n\t\t\tconsole.warn(\n\t\t\t\t`skipping slow group: track=${this.#track.name} ${first.consumer.sequence} -> ${this.#active}`,\n\t\t\t);\n\n\t\t\tfirst.consumer.close();\n\t\t\tfirst.frames.length = 0;\n\t\t\tskipped = true;\n\t\t}\n\n\t\tif (skipped) {\n\t\t\tthis.#updateBuffered();\n\n\t\t\t// Wake up any consumers waiting for a new frame.\n\t\t\tthis.#notify?.();\n\t\t\tthis.#notify = undefined;\n\t\t}\n\t}\n\n\t// Detect a publisher \"rewind\" and record the reneged boundary. A newer group (sequence\n\t// climbs) whose earliest frame lands before the live edge (timestamp goes backwards) can\n\t// only be an explicit reneg of the buffered tail; record the boundary, bump the\n\t// discontinuity counter, drop the groups it proves stale, and resume from the earliest\n\t// survivor. Groups still ambiguous (a late new-epoch group vs. an old straggler) are kept\n\t// and resolved by #classifyStale once their timestamps arrive.\n\t#checkReset(group: Group) {\n\t\tif (this.#active === undefined) return;\n\n\t\tconst live = this.#rewind.liveEdge;\n\t\tif (live === undefined) return;\n\n\t\t// Only a group newer than the active one can rewind the timeline.\n\t\tif (group.consumer.sequence <= this.#active) return;\n\n\t\tconst start = group.frames.at(0)?.timestamp;\n\t\tif (start === undefined) return;\n\n\t\t// A rewind is the timestamp going strictly backwards past the live edge. Anything at\n\t\t// or ahead of it is normal forward motion.\n\t\tif (start >= live.timestamp) return;\n\n\t\tconst reset = new Reset(live.group, group.consumer.sequence, start);\n\t\tthis.#rewind.boundary = reset;\n\t\tthis.#rewind.discontinuity++;\n\n\t\t// Drop buffered groups the boundary can already prove stale; keep ambiguous ones.\n\t\tthis.#groups = this.#groups.filter((g) => {\n\t\t\tconst verdict = reset.bySequence(g.consumer.sequence);\n\t\t\tconst first = g.frames.at(0);\n\t\t\tconst stale = verdict ?? (first !== undefined && reset.isStale(g.consumer.sequence, first.timestamp));\n\t\t\tif (stale) {\n\t\t\t\tg.consumer.close();\n\t\t\t\tg.frames.length = 0;\n\t\t\t}\n\t\t\treturn !stale;\n\t\t});\n\n\t\tconsole.warn(\n\t\t\t`buffer reset: track=${this.#track.name} group timestamps rewound (prevMax ${reset.prevMax}, group ${reset.group})`,\n\t\t);\n\n\t\t// Resume from the earliest survivor; if none, from the rewound group. Anchor the live\n\t\t// edge at the rewind point (delivered), not group.latest (buffered ahead of playback).\n\t\tthis.#active = this.#groups[0]?.consumer.sequence ?? reset.group;\n\t\tthis.#rewind.liveEdge = { group: reset.group, timestamp: start };\n\t\tthis.#updateBuffered();\n\n\t\t// Wake up any consumer waiting for a new frame.\n\t\tthis.#notify?.();\n\t\tthis.#notify = undefined;\n\t}\n\n\t// Drop a group that an active reset resolves as a reneged old straggler (its timestamp\n\t// landed at or above the reset point). Returns true if the group was dropped.\n\t#classifyStale(group: Group): boolean {\n\t\tconst reset = this.#rewind.boundary;\n\t\tif (!reset) return false;\n\n\t\tconst first = group.frames.at(0);\n\t\tif (first === undefined) return false;\n\t\tif (!reset.isStale(group.consumer.sequence, first.timestamp)) return false;\n\n\t\tthis.#groups = this.#groups.filter((g) => g !== group);\n\t\tgroup.consumer.close();\n\t\tgroup.frames.length = 0;\n\t\tthis.#updateBuffered();\n\t\treturn true;\n\t}\n\n\t// Re-check buffered newer groups against the current live edge. #checkReset otherwise only\n\t// runs when a group receives a frame, so a group that buffered while the live edge was lower\n\t// (or undefined) is never reconsidered once delivery advances the edge past it, and the\n\t// rewind would be missed. Highest sequence first, mirroring the Rust scan: the first rewound\n\t// group becomes the boundary, and #checkReset's own guards make the rest no-ops.\n\t#checkBufferedReset(): void {\n\t\tif (this.#active === undefined || this.#rewind.liveEdge === undefined) return;\n\t\tfor (const group of [...this.#groups].reverse()) {\n\t\t\tif (group.consumer.sequence <= this.#active) break;\n\t\t\tthis.#checkReset(group);\n\t\t}\n\t}\n\n\t/**\n\t * Returns the next frame in order along with its group number and the current\n\t * {@link discontinuity} count, awaiting one if needed. A `frame` of undefined signals the\n\t * end of that group; the overall result is undefined once closed. When `discontinuity`\n\t * jumps relative to the previous call, the publisher rewound the timeline: flush any\n\t * downstream decoder or render buffers before playing this frame.\n\t */\n\tasync next(): Promise<{ frame: Frame | undefined; group: number; discontinuity: number } | undefined> {\n\t\tfor (;;) {\n\t\t\t// A group may have buffered a rewind while the live edge was still behind it; catch it\n\t\t\t// now that delivery has advanced the edge.\n\t\t\tthis.#checkBufferedReset();\n\n\t\t\t// Keep #active tracking the oldest buffered group across non-consecutive\n\t\t\t// group ids (see #syncActive).\n\t\t\tthis.#syncActive();\n\n\t\t\tif (\n\t\t\t\tthis.#groups.length > 0 &&\n\t\t\t\tthis.#active !== undefined &&\n\t\t\t\tthis.#groups[0].consumer.sequence <= this.#active\n\t\t\t) {\n\t\t\t\tconst frame = this.#groups[0].frames.shift();\n\t\t\t\tif (frame) {\n\t\t\t\t\tconst seq = this.#groups[0].consumer.sequence;\n\t\t\t\t\t// Track the live edge (max timestamp + its group) so a later backwards jump\n\t\t\t\t\t// is detectable and the old epoch's tail is anchored.\n\t\t\t\t\tconst live = this.#rewind.liveEdge;\n\t\t\t\t\tif (live === undefined || frame.timestamp > live.timestamp) {\n\t\t\t\t\t\tthis.#rewind.liveEdge = { group: seq, timestamp: frame.timestamp };\n\t\t\t\t\t}\n\t\t\t\t\tthis.#updateBuffered();\n\t\t\t\t\treturn { frame, group: seq, discontinuity: this.#rewind.discontinuity };\n\t\t\t\t}\n\n\t\t\t\t// Check if the group is done and then remove it.\n\t\t\t\t// A group is removable when #active has advanced past it, OR when\n\t\t\t\t// its #runGroup task has finished (done) and all frames are consumed.\n\t\t\t\t// The latter handles the case where #runGroup finished before\n\t\t\t\t// #active reached this group (e.g. after a latency skip).\n\t\t\t\tif (this.#active > this.#groups[0].consumer.sequence || this.#groups[0].done) {\n\t\t\t\t\tconst group = this.#groups.shift();\n\t\t\t\t\tif (group) {\n\t\t\t\t\t\t// Advance to the next buffered group (ids are not consecutive).\n\t\t\t\t\t\tthis.#syncActive();\n\t\t\t\t\t\tthis.#updateBuffered();\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tframe: undefined,\n\t\t\t\t\t\t\tgroup: group.consumer.sequence,\n\t\t\t\t\t\t\tdiscontinuity: this.#rewind.discontinuity,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (this.#notify) {\n\t\t\t\tthrow new Error(\"multiple calls to next not supported\");\n\t\t\t}\n\n\t\t\tconst abort = this.#signals.abort;\n\t\t\tif (abort.aborted) return undefined;\n\n\t\t\tconst aborted = await new Promise<boolean>((resolve) => {\n\t\t\t\tconst onAbort = () => resolve(true);\n\t\t\t\tabort.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t\tthis.#notify = () => {\n\t\t\t\t\tabort.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\tresolve(false);\n\t\t\t\t};\n\t\t\t});\n\n\t\t\tthis.#notify = undefined;\n\t\t\tif (aborted) return undefined;\n\t\t}\n\t}\n\n\t#updateBuffered(): void {\n\t\tconst ranges: BufferedRanges = [];\n\n\t\tlet prev: Group | undefined;\n\n\t\tfor (const group of this.#groups) {\n\t\t\tconst first = group.frames.at(0);\n\t\t\tif (!first || group.latest === undefined) continue;\n\n\t\t\tconst start = Moq.Time.Milli.fromMicro(first.timestamp);\n\t\t\tconst end = Moq.Time.Milli.fromMicro(group.latest);\n\n\t\t\tconst last = ranges.at(-1);\n\t\t\t// `prev` is the immediately-preceding buffered group (this loop walks\n\t\t\t// #groups in ascending order), so a done `prev` means these two groups\n\t\t\t// are buffer-adjacent. We can't test `prev.sequence + 1 === sequence`\n\t\t\t// because group ids are not consecutive (spec §2.3.1).\n\t\t\tconst contiguous = prev?.done === true;\n\t\t\tif (last && (last.end >= start || contiguous)) {\n\t\t\t\tlast.end = Moq.Time.Milli.max(last.end, end);\n\t\t\t} else {\n\t\t\t\tranges.push({ start, end });\n\t\t\t}\n\n\t\t\tprev = group;\n\t\t}\n\n\t\tthis.#buffered.set(ranges);\n\t}\n\n\t/**\n\t * A counter that increments each time the consumer detects a timeline rewind and drops the\n\t * reneged buffer. Also surfaced per-read via {@link next}; downstream consumers flush their\n\t * decoder and render buffers when it changes.\n\t */\n\tget discontinuity(): number {\n\t\treturn this.#rewind.discontinuity;\n\t}\n\n\t/** Stop consuming and release the track and all buffered groups. */\n\tclose(): void {\n\t\tthis.#signals.close();\n\t}\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=consumer.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consumer.test.d.ts","sourceRoot":"","sources":["../../src/container/consumer.test.ts"],"names":[],"mappings":""}