@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/index.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @openmoq/playback — Sans-I/O playback core.
|
|
3
|
+
*
|
|
4
|
+
* Jitter buffer, A/V sync, gap detection, decoder state machine,
|
|
5
|
+
* and pipeline orchestrator — all testable in Node.js without browser APIs.
|
|
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 type { ClockSource, DecoderCommand, ConfigureCommand, DecodeVideoCommand, DecodeAudioCommand, FlushCommand, ResetCommand, SetPlaybackRateCommand, PlaybackEvent, GapDetectedEvent, SkipForwardEvent, SyncDriftEvent, KeyframeWaitingEvent, KeyframeValidationFailedEvent, TrackEndedEvent, RecoveryEvent, CatchUpState, CatchUpChangedEvent, BacklogShedEvent, PlaybackConfig, DecoderFeedback, QueuePressureFeedback, DecodeErrorFeedback, FrameRenderedFeedback, FlushCompleteFeedback, } from './types.js';
|
|
12
|
+
export { JitterBuffer } from './jitter-buffer.js';
|
|
13
|
+
export { GapDetector, GapAction } from './gap-detector.js';
|
|
14
|
+
export type { GapActionValue, GapDecision } from './gap-detector.js';
|
|
15
|
+
export { DecoderStateMachine, DecoderState } from './decoder-state.js';
|
|
16
|
+
export type { DecoderStateValue, FrameDecision } from './decoder-state.js';
|
|
17
|
+
export { SyncController } from './sync.js';
|
|
18
|
+
export type { RenderTiming, WallClockSource, SyncControllerConfig } from './sync.js';
|
|
19
|
+
export { DefaultRecoveryController } from './recovery.js';
|
|
20
|
+
export type { DefaultRecoveryConfig, RecoveryController, RecoveryTrigger, RecoveryAction } from './recovery.js';
|
|
21
|
+
export { AdaptiveToleranceController, RecoveryPhase, DEFAULT_TOLERANCE_CONFIG } from './adaptive-tolerance.js';
|
|
22
|
+
export type { ToleranceConfig, TickResult } from './adaptive-tolerance.js';
|
|
23
|
+
export { RenderScheduler } from './render-scheduler.js';
|
|
24
|
+
export type { RenderSchedulerOptions } from './render-scheduler.js';
|
|
25
|
+
export { StartupBuffer, classifyNetwork } from './startup-buffer.js';
|
|
26
|
+
export type { StartupParams } from './startup-buffer.js';
|
|
27
|
+
export { isKeyframePayload } from './keyframe-validator.js';
|
|
28
|
+
export { PlaybackPipeline } from './pipeline.js';
|
|
29
|
+
export type { PipelineOptions } from './pipeline.js';
|
|
30
|
+
export { BandwidthEstimator } from './bandwidth-estimator.js';
|
|
31
|
+
export { BufferBasedController } from './buffer-based-controller.js';
|
|
32
|
+
export type { AbrTrack, AbrSignals, AbrDecision, BufferBasedConfig } from './buffer-based-controller.js';
|
|
33
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,YAAY,EACR,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,YAAY,EACZ,YAAY,EACZ,sBAAsB,EACtB,aAAa,EACb,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,oBAAoB,EACpB,6BAA6B,EAC7B,eAAe,EACf,aAAa,EACb,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,qBAAqB,EACrB,qBAAqB,GACxB,MAAM,YAAY,CAAC;AAIpB,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAIlD,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC3D,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIrE,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvE,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAI3E,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAIrF,OAAO,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAC1D,YAAY,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAIhH,OAAO,EAAE,2BAA2B,EAAE,aAAa,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AAC/G,YAAY,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAI3E,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,YAAY,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAIpE,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACrE,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAIzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAI5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAE9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @openmoq/playback — Sans-I/O playback core.
|
|
3
|
+
*
|
|
4
|
+
* Jitter buffer, A/V sync, gap detection, decoder state machine,
|
|
5
|
+
* and pipeline orchestrator — all testable in Node.js without browser APIs.
|
|
6
|
+
*
|
|
7
|
+
* @see draft-ietf-moq-loc-01 §4 (Application Mapping)
|
|
8
|
+
* @see draft-ietf-moq-transport-16 §10 (Objects)
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
// ─── Jitter Buffer ──────────────────────────────────────────────────
|
|
12
|
+
export { JitterBuffer } from './jitter-buffer.js';
|
|
13
|
+
// ─── Gap Detector ───────────────────────────────────────────────────
|
|
14
|
+
export { GapDetector, GapAction } from './gap-detector.js';
|
|
15
|
+
// ─── Decoder State ──────────────────────────────────────────────────
|
|
16
|
+
export { DecoderStateMachine, DecoderState } from './decoder-state.js';
|
|
17
|
+
// ─── Sync Controller ───────────────────────────────────────────────
|
|
18
|
+
export { SyncController } from './sync.js';
|
|
19
|
+
// ─── Recovery ───────────────────────────────────────────────────────
|
|
20
|
+
export { DefaultRecoveryController } from './recovery.js';
|
|
21
|
+
// ─── Adaptive Tolerance ─────────────────────────────────────────────
|
|
22
|
+
export { AdaptiveToleranceController, RecoveryPhase, DEFAULT_TOLERANCE_CONFIG } from './adaptive-tolerance.js';
|
|
23
|
+
// ─── Render Scheduler ───────────────────────────────────────────────
|
|
24
|
+
export { RenderScheduler } from './render-scheduler.js';
|
|
25
|
+
// ─── Startup Buffer ─────────────────────────────────────────────────
|
|
26
|
+
export { StartupBuffer, classifyNetwork } from './startup-buffer.js';
|
|
27
|
+
// ─── Keyframe Validator ─────────────────────────────────────────────
|
|
28
|
+
export { isKeyframePayload } from './keyframe-validator.js';
|
|
29
|
+
// ─── Pipeline ───────────────────────────────────────────────────────
|
|
30
|
+
export { PlaybackPipeline } from './pipeline.js';
|
|
31
|
+
export { BandwidthEstimator } from './bandwidth-estimator.js';
|
|
32
|
+
export { BufferBasedController } from './buffer-based-controller.js';
|
|
33
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAgCH,uEAAuE;AAEvE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,uEAAuE;AAEvE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAG3D,uEAAuE;AAEvE,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGvE,sEAAsE;AAEtE,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAG3C,uEAAuE;AAEvE,OAAO,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAG1D,uEAAuE;AAEvE,OAAO,EAAE,2BAA2B,EAAE,aAAa,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AAG/G,uEAAuE;AAEvE,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAGxD,uEAAuE;AAEvE,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAGrE,uEAAuE;AAEvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAE5D,uEAAuE;AAEvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAGjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAE9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Min-heap jitter buffer ordered by (groupId, objectId).
|
|
3
|
+
*
|
|
4
|
+
* Merges objects arriving from multiple subgroups (on separate QUIC streams)
|
|
5
|
+
* into strict decode order. Uses an array-based binary min-heap for
|
|
6
|
+
* O(log N) insert/extract — NOT Array.sort().
|
|
7
|
+
*
|
|
8
|
+
* @see draft-ietf-moq-loc-01 §4.2 (single track: groupId then objectId order)
|
|
9
|
+
* @see draft-ietf-moq-loc-01 §4.3 (temporal layers: cross-subgroup ordering)
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
import type { MoqtObject, MoqtObjectData } from '@openmoq/transport';
|
|
13
|
+
/**
|
|
14
|
+
* Min-heap jitter buffer for MoqtObject ordering.
|
|
15
|
+
*
|
|
16
|
+
* @see draft-ietf-moq-loc-01 §4.2, §4.3
|
|
17
|
+
*/
|
|
18
|
+
export declare class JitterBuffer {
|
|
19
|
+
private readonly heap;
|
|
20
|
+
private readonly maxDepth;
|
|
21
|
+
constructor(maxDepth: number);
|
|
22
|
+
/** Number of objects currently buffered. */
|
|
23
|
+
get size(): number;
|
|
24
|
+
/**
|
|
25
|
+
* Insert an object into the buffer.
|
|
26
|
+
* @returns true if accepted, false if buffer is full.
|
|
27
|
+
*/
|
|
28
|
+
insert(obj: MoqtObject): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Peek at the minimum object without removing it.
|
|
31
|
+
*/
|
|
32
|
+
peek(): MoqtObject | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Remove and return the minimum object.
|
|
35
|
+
*/
|
|
36
|
+
extract(): MoqtObject | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Check if the object at (groupId, objectId) is the minimum in the buffer.
|
|
39
|
+
*/
|
|
40
|
+
hasContiguous(groupId: bigint, objectId: bigint): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Remove and return all objects for a given group, in sorted order.
|
|
43
|
+
*/
|
|
44
|
+
drainGroup(groupId: bigint): MoqtObject[];
|
|
45
|
+
/**
|
|
46
|
+
* Remove all objects with groupId less than the given threshold.
|
|
47
|
+
* @returns Number of objects removed.
|
|
48
|
+
*/
|
|
49
|
+
discardBefore(groupId: bigint): number;
|
|
50
|
+
/**
|
|
51
|
+
* Evict the least-important object from the buffer.
|
|
52
|
+
*
|
|
53
|
+
* Finds the data object with the highest publisherPriority number
|
|
54
|
+
* (= lowest importance per §7: "lower priority number indicates higher
|
|
55
|
+
* priority"). Tiebreaker: oldest group (lowest groupId).
|
|
56
|
+
*
|
|
57
|
+
* Gap objects and objects with undefined priority are not evictable.
|
|
58
|
+
*
|
|
59
|
+
* @returns The evicted object, or undefined if no candidate exists.
|
|
60
|
+
* @see draft-ietf-moq-transport-16 §7 (Priority-based dropping)
|
|
61
|
+
*/
|
|
62
|
+
evictLowestImportance(): MoqtObjectData | undefined;
|
|
63
|
+
/**
|
|
64
|
+
* Clear all buffered objects.
|
|
65
|
+
*/
|
|
66
|
+
clear(): void;
|
|
67
|
+
private bubbleUp;
|
|
68
|
+
private sinkDown;
|
|
69
|
+
private swap;
|
|
70
|
+
/** Rebuild the heap from an unordered array (O(n)). */
|
|
71
|
+
private rebuild;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=jitter-buffer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jitter-buffer.d.ts","sourceRoot":"","sources":["../src/jitter-buffer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAcrE;;;;GAIG;AACH,qBAAa,YAAY;IACrB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAoB;IACzC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;gBAEtB,QAAQ,EAAE,MAAM;IAI5B,4CAA4C;IAC5C,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;OAGG;IACH,MAAM,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO;IAOhC;;OAEG;IACH,IAAI,IAAI,UAAU,GAAG,SAAS;IAI9B;;OAEG;IACH,OAAO,IAAI,UAAU,GAAG,SAAS;IAWjC;;OAEG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO;IAMzD;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE;IA0BzC;;;OAGG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAqBtC;;;;;;;;;;;OAWG;IACH,qBAAqB,IAAI,cAAc,GAAG,SAAS;IAoCnD;;OAEG;IACH,KAAK,IAAI,IAAI;IAMb,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,QAAQ;IAmBhB,OAAO,CAAC,IAAI;IAMZ,uDAAuD;IACvD,OAAO,CAAC,OAAO;CAKlB"}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Min-heap jitter buffer ordered by (groupId, objectId).
|
|
3
|
+
*
|
|
4
|
+
* Merges objects arriving from multiple subgroups (on separate QUIC streams)
|
|
5
|
+
* into strict decode order. Uses an array-based binary min-heap for
|
|
6
|
+
* O(log N) insert/extract — NOT Array.sort().
|
|
7
|
+
*
|
|
8
|
+
* @see draft-ietf-moq-loc-01 §4.2 (single track: groupId then objectId order)
|
|
9
|
+
* @see draft-ietf-moq-loc-01 §4.3 (temporal layers: cross-subgroup ordering)
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Compare two objects by (groupId, objectId) ascending.
|
|
14
|
+
* Returns negative if a < b, positive if a > b, 0 if equal.
|
|
15
|
+
*/
|
|
16
|
+
function compare(a, b) {
|
|
17
|
+
if (a.groupId < b.groupId)
|
|
18
|
+
return -1;
|
|
19
|
+
if (a.groupId > b.groupId)
|
|
20
|
+
return 1;
|
|
21
|
+
if (a.objectId < b.objectId)
|
|
22
|
+
return -1;
|
|
23
|
+
if (a.objectId > b.objectId)
|
|
24
|
+
return 1;
|
|
25
|
+
return 0;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Min-heap jitter buffer for MoqtObject ordering.
|
|
29
|
+
*
|
|
30
|
+
* @see draft-ietf-moq-loc-01 §4.2, §4.3
|
|
31
|
+
*/
|
|
32
|
+
export class JitterBuffer {
|
|
33
|
+
heap = [];
|
|
34
|
+
maxDepth;
|
|
35
|
+
constructor(maxDepth) {
|
|
36
|
+
this.maxDepth = maxDepth;
|
|
37
|
+
}
|
|
38
|
+
/** Number of objects currently buffered. */
|
|
39
|
+
get size() {
|
|
40
|
+
return this.heap.length;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Insert an object into the buffer.
|
|
44
|
+
* @returns true if accepted, false if buffer is full.
|
|
45
|
+
*/
|
|
46
|
+
insert(obj) {
|
|
47
|
+
if (this.heap.length >= this.maxDepth)
|
|
48
|
+
return false;
|
|
49
|
+
this.heap.push(obj);
|
|
50
|
+
this.bubbleUp(this.heap.length - 1);
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Peek at the minimum object without removing it.
|
|
55
|
+
*/
|
|
56
|
+
peek() {
|
|
57
|
+
return this.heap[0];
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Remove and return the minimum object.
|
|
61
|
+
*/
|
|
62
|
+
extract() {
|
|
63
|
+
if (this.heap.length === 0)
|
|
64
|
+
return undefined;
|
|
65
|
+
const min = this.heap[0];
|
|
66
|
+
const last = this.heap.pop();
|
|
67
|
+
if (this.heap.length > 0) {
|
|
68
|
+
this.heap[0] = last;
|
|
69
|
+
this.sinkDown(0);
|
|
70
|
+
}
|
|
71
|
+
return min;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Check if the object at (groupId, objectId) is the minimum in the buffer.
|
|
75
|
+
*/
|
|
76
|
+
hasContiguous(groupId, objectId) {
|
|
77
|
+
if (this.heap.length === 0)
|
|
78
|
+
return false;
|
|
79
|
+
const top = this.heap[0];
|
|
80
|
+
return top.groupId === groupId && top.objectId === objectId;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Remove and return all objects for a given group, in sorted order.
|
|
84
|
+
*/
|
|
85
|
+
drainGroup(groupId) {
|
|
86
|
+
const result = [];
|
|
87
|
+
const remaining = [];
|
|
88
|
+
// Extract all, separate target group from rest
|
|
89
|
+
for (const obj of this.heap) {
|
|
90
|
+
if (obj.groupId === groupId) {
|
|
91
|
+
result.push(obj);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
remaining.push(obj);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Sort the target group's objects by objectId
|
|
98
|
+
result.sort(compare);
|
|
99
|
+
// Rebuild heap from remaining
|
|
100
|
+
this.heap.length = 0;
|
|
101
|
+
for (const obj of remaining) {
|
|
102
|
+
this.heap.push(obj);
|
|
103
|
+
}
|
|
104
|
+
this.rebuild();
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Remove all objects with groupId less than the given threshold.
|
|
109
|
+
* @returns Number of objects removed.
|
|
110
|
+
*/
|
|
111
|
+
discardBefore(groupId) {
|
|
112
|
+
const remaining = [];
|
|
113
|
+
let removed = 0;
|
|
114
|
+
for (const obj of this.heap) {
|
|
115
|
+
if (obj.groupId < groupId) {
|
|
116
|
+
removed++;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
remaining.push(obj);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
this.heap.length = 0;
|
|
123
|
+
for (const obj of remaining) {
|
|
124
|
+
this.heap.push(obj);
|
|
125
|
+
}
|
|
126
|
+
this.rebuild();
|
|
127
|
+
return removed;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Evict the least-important object from the buffer.
|
|
131
|
+
*
|
|
132
|
+
* Finds the data object with the highest publisherPriority number
|
|
133
|
+
* (= lowest importance per §7: "lower priority number indicates higher
|
|
134
|
+
* priority"). Tiebreaker: oldest group (lowest groupId).
|
|
135
|
+
*
|
|
136
|
+
* Gap objects and objects with undefined priority are not evictable.
|
|
137
|
+
*
|
|
138
|
+
* @returns The evicted object, or undefined if no candidate exists.
|
|
139
|
+
* @see draft-ietf-moq-transport-16 §7 (Priority-based dropping)
|
|
140
|
+
*/
|
|
141
|
+
evictLowestImportance() {
|
|
142
|
+
if (this.heap.length === 0)
|
|
143
|
+
return undefined;
|
|
144
|
+
let worstIdx = -1;
|
|
145
|
+
let worstPriority = -1;
|
|
146
|
+
let worstGroupId = -1n;
|
|
147
|
+
for (let i = 0; i < this.heap.length; i++) {
|
|
148
|
+
const obj = this.heap[i];
|
|
149
|
+
if (obj.kind !== 'data' || obj.publisherPriority === undefined)
|
|
150
|
+
continue;
|
|
151
|
+
const p = obj.publisherPriority;
|
|
152
|
+
if (p > worstPriority ||
|
|
153
|
+
(p === worstPriority && obj.groupId < worstGroupId)) {
|
|
154
|
+
worstIdx = i;
|
|
155
|
+
worstPriority = p;
|
|
156
|
+
worstGroupId = obj.groupId;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (worstIdx === -1)
|
|
160
|
+
return undefined;
|
|
161
|
+
const evicted = this.heap[worstIdx];
|
|
162
|
+
// Remove by swapping with last element and rebuilding
|
|
163
|
+
const last = this.heap.pop();
|
|
164
|
+
if (worstIdx < this.heap.length) {
|
|
165
|
+
this.heap[worstIdx] = last;
|
|
166
|
+
this.rebuild();
|
|
167
|
+
}
|
|
168
|
+
return evicted;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Clear all buffered objects.
|
|
172
|
+
*/
|
|
173
|
+
clear() {
|
|
174
|
+
this.heap.length = 0;
|
|
175
|
+
}
|
|
176
|
+
// ─── Heap internals ─────────────────────────────────────────────
|
|
177
|
+
bubbleUp(index) {
|
|
178
|
+
while (index > 0) {
|
|
179
|
+
const parentIdx = (index - 1) >> 1;
|
|
180
|
+
if (compare(this.heap[index], this.heap[parentIdx]) >= 0)
|
|
181
|
+
break;
|
|
182
|
+
this.swap(index, parentIdx);
|
|
183
|
+
index = parentIdx;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
sinkDown(index) {
|
|
187
|
+
const length = this.heap.length;
|
|
188
|
+
while (true) {
|
|
189
|
+
let smallest = index;
|
|
190
|
+
const left = 2 * index + 1;
|
|
191
|
+
const right = 2 * index + 2;
|
|
192
|
+
if (left < length && compare(this.heap[left], this.heap[smallest]) < 0) {
|
|
193
|
+
smallest = left;
|
|
194
|
+
}
|
|
195
|
+
if (right < length && compare(this.heap[right], this.heap[smallest]) < 0) {
|
|
196
|
+
smallest = right;
|
|
197
|
+
}
|
|
198
|
+
if (smallest === index)
|
|
199
|
+
break;
|
|
200
|
+
this.swap(index, smallest);
|
|
201
|
+
index = smallest;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
swap(i, j) {
|
|
205
|
+
const tmp = this.heap[i];
|
|
206
|
+
this.heap[i] = this.heap[j];
|
|
207
|
+
this.heap[j] = tmp;
|
|
208
|
+
}
|
|
209
|
+
/** Rebuild the heap from an unordered array (O(n)). */
|
|
210
|
+
rebuild() {
|
|
211
|
+
for (let i = (this.heap.length >> 1) - 1; i >= 0; i--) {
|
|
212
|
+
this.sinkDown(i);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=jitter-buffer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jitter-buffer.js","sourceRoot":"","sources":["../src/jitter-buffer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keyframe payload validation — verifies codec bitstream actually starts
|
|
3
|
+
* with an independently decodable frame.
|
|
4
|
+
*
|
|
5
|
+
* Used to detect broken publishers that start MoQ groups with delta frames
|
|
6
|
+
* instead of keyframes. Per LOC §4.2, the first object in a group MUST be
|
|
7
|
+
* independently decodable (keyframe/IDR/key_frame).
|
|
8
|
+
*
|
|
9
|
+
* Supports H.264 (AVC), H.265 (HEVC), and AV1.
|
|
10
|
+
* Returns null for unknown codecs (no opinion).
|
|
11
|
+
*
|
|
12
|
+
* @see draft-ietf-moq-loc-01 §4.2 (decode order)
|
|
13
|
+
* @see ITU-T H.264 §7.3.1 (NAL unit syntax)
|
|
14
|
+
* @see ITU-T H.265 §7.3.1.2 (NAL unit header)
|
|
15
|
+
* @see AV1 §5.3.1 (OBU header)
|
|
16
|
+
* @module
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Check if a video payload starts with a keyframe.
|
|
20
|
+
*
|
|
21
|
+
* @param codec Codec string from catalog (e.g., 'avc1.42c01f', 'hvc1', 'av01.0.08M.10')
|
|
22
|
+
* @param payload Raw bitstream payload from MoQ object
|
|
23
|
+
* @returns true = keyframe, false = delta, null = can't determine (unknown codec or too short)
|
|
24
|
+
*/
|
|
25
|
+
export declare function isKeyframePayload(codec: string, payload: Uint8Array): boolean | null;
|
|
26
|
+
//# sourceMappingURL=keyframe-validator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keyframe-validator.d.ts","sourceRoot":"","sources":["../src/keyframe-validator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,GAAG,IAAI,CAepF"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keyframe payload validation — verifies codec bitstream actually starts
|
|
3
|
+
* with an independently decodable frame.
|
|
4
|
+
*
|
|
5
|
+
* Used to detect broken publishers that start MoQ groups with delta frames
|
|
6
|
+
* instead of keyframes. Per LOC §4.2, the first object in a group MUST be
|
|
7
|
+
* independently decodable (keyframe/IDR/key_frame).
|
|
8
|
+
*
|
|
9
|
+
* Supports H.264 (AVC), H.265 (HEVC), and AV1.
|
|
10
|
+
* Returns null for unknown codecs (no opinion).
|
|
11
|
+
*
|
|
12
|
+
* @see draft-ietf-moq-loc-01 §4.2 (decode order)
|
|
13
|
+
* @see ITU-T H.264 §7.3.1 (NAL unit syntax)
|
|
14
|
+
* @see ITU-T H.265 §7.3.1.2 (NAL unit header)
|
|
15
|
+
* @see AV1 §5.3.1 (OBU header)
|
|
16
|
+
* @module
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Check if a video payload starts with a keyframe.
|
|
20
|
+
*
|
|
21
|
+
* @param codec Codec string from catalog (e.g., 'avc1.42c01f', 'hvc1', 'av01.0.08M.10')
|
|
22
|
+
* @param payload Raw bitstream payload from MoQ object
|
|
23
|
+
* @returns true = keyframe, false = delta, null = can't determine (unknown codec or too short)
|
|
24
|
+
*/
|
|
25
|
+
export function isKeyframePayload(codec, payload) {
|
|
26
|
+
const codecBase = codec.split('.')[0].toLowerCase();
|
|
27
|
+
switch (codecBase) {
|
|
28
|
+
case 'avc1':
|
|
29
|
+
case 'avc3':
|
|
30
|
+
return isH264Keyframe(payload);
|
|
31
|
+
case 'hvc1':
|
|
32
|
+
case 'hev1':
|
|
33
|
+
return isH265Keyframe(payload);
|
|
34
|
+
case 'av01':
|
|
35
|
+
return isAV1Keyframe(payload);
|
|
36
|
+
default:
|
|
37
|
+
return null; // Unknown codec — no opinion
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* H.264: check for IDR slice (NAL type 5) or SPS (NAL type 7).
|
|
42
|
+
*
|
|
43
|
+
* LOC payloads use length-prefixed NAL units (AVCC format):
|
|
44
|
+
* [4-byte length][NAL unit][4-byte length][NAL unit]...
|
|
45
|
+
*
|
|
46
|
+
* NAL header byte: forbidden_zero_bit(1) + nal_ref_idc(2) + nal_unit_type(5)
|
|
47
|
+
* Keyframe NAL types: 5 (IDR slice), 7 (SPS), 8 (PPS)
|
|
48
|
+
* Non-IDR types to skip: 6 (SEI), 9 (AUD)
|
|
49
|
+
*
|
|
50
|
+
* @see ITU-T H.264 §7.4.1 (NAL unit semantics)
|
|
51
|
+
*/
|
|
52
|
+
/**
|
|
53
|
+
* Hard upper bound on NAL units inspected per access unit.
|
|
54
|
+
*
|
|
55
|
+
* Most real keyframe AUs have well under 32 NAL units (AUD + SPS + PPS +
|
|
56
|
+
* SEI + a few slices). Multi-slice encoding (parallelism, low-latency,
|
|
57
|
+
* error resilience) and SEI-heavy streams (HDR, captions, timecodes) can
|
|
58
|
+
* push the count higher; HEVC tiles and SVC layers can push it higher
|
|
59
|
+
* still. 256 is generous enough for legitimate content while preventing
|
|
60
|
+
* pathological loops on crafted payloads (e.g., zero-length-NAL spam
|
|
61
|
+
* or boundary bugs that fail to advance `pos`).
|
|
62
|
+
*
|
|
63
|
+
* If you hit this bound on a real stream, log + raise — the bound is
|
|
64
|
+
* defense-in-depth, not a spec claim.
|
|
65
|
+
*/
|
|
66
|
+
const MAX_NAL_UNITS_PER_AU = 256;
|
|
67
|
+
function isH264Keyframe(payload) {
|
|
68
|
+
if (payload.length < 5)
|
|
69
|
+
return null;
|
|
70
|
+
let pos = 0;
|
|
71
|
+
let iterations = 0;
|
|
72
|
+
while (pos + 4 < payload.length) {
|
|
73
|
+
if (++iterations > MAX_NAL_UNITS_PER_AU)
|
|
74
|
+
return null;
|
|
75
|
+
// Read 4-byte big-endian NAL unit length
|
|
76
|
+
const nalLen = (payload[pos] << 24) | (payload[pos + 1] << 16) |
|
|
77
|
+
(payload[pos + 2] << 8) | payload[pos + 3];
|
|
78
|
+
pos += 4;
|
|
79
|
+
if (nalLen <= 0 || pos >= payload.length)
|
|
80
|
+
break;
|
|
81
|
+
const nalType = payload[pos] & 0x1F;
|
|
82
|
+
// Keyframe indicators
|
|
83
|
+
if (nalType === 5)
|
|
84
|
+
return true; // IDR slice
|
|
85
|
+
if (nalType === 7)
|
|
86
|
+
return true; // SPS (always precedes IDR in keyframe AU)
|
|
87
|
+
// Non-VCL NALs to skip (may precede the actual slice)
|
|
88
|
+
if (nalType === 6 || nalType === 8 || nalType === 9) {
|
|
89
|
+
// SEI (6), PPS (8), AUD (9) — skip to next NAL
|
|
90
|
+
pos += nalLen;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
// Any other VCL NAL type = delta frame
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
return null; // Couldn't find a VCL NAL
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* H.265: check for IDR_W_RADL (type 19) or IDR_N_LP (type 20).
|
|
100
|
+
*
|
|
101
|
+
* H.265 NAL header is 2 bytes:
|
|
102
|
+
* forbidden_zero_bit(1) + nal_unit_type(6) + nuh_layer_id(6) + nuh_temporal_id_plus1(3)
|
|
103
|
+
* Type is in bits 1-6 of the first byte: (byte >> 1) & 0x3F
|
|
104
|
+
*
|
|
105
|
+
* @see ITU-T H.265 §7.4.2.2 (NAL unit header semantics)
|
|
106
|
+
*/
|
|
107
|
+
function isH265Keyframe(payload) {
|
|
108
|
+
if (payload.length < 6)
|
|
109
|
+
return null;
|
|
110
|
+
let pos = 0;
|
|
111
|
+
let iterations = 0;
|
|
112
|
+
while (pos + 4 < payload.length) {
|
|
113
|
+
if (++iterations > MAX_NAL_UNITS_PER_AU)
|
|
114
|
+
return null;
|
|
115
|
+
const nalLen = (payload[pos] << 24) | (payload[pos + 1] << 16) |
|
|
116
|
+
(payload[pos + 2] << 8) | payload[pos + 3];
|
|
117
|
+
pos += 4;
|
|
118
|
+
if (nalLen <= 0 || pos + 1 >= payload.length)
|
|
119
|
+
break;
|
|
120
|
+
const nalType = (payload[pos] >> 1) & 0x3F;
|
|
121
|
+
// IDR types
|
|
122
|
+
if (nalType === 19 || nalType === 20)
|
|
123
|
+
return true; // IDR_W_RADL, IDR_N_LP
|
|
124
|
+
if (nalType === 21)
|
|
125
|
+
return true; // CRA_NUT (clean random access)
|
|
126
|
+
// VPS (32), SPS (33), PPS (34) — parameter sets precede IDR
|
|
127
|
+
if (nalType === 32 || nalType === 33 || nalType === 34) {
|
|
128
|
+
pos += nalLen;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
// SEI (39, 40), AUD (35) — skip
|
|
132
|
+
if (nalType === 35 || nalType === 39 || nalType === 40) {
|
|
133
|
+
pos += nalLen;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
// Any other slice type = delta
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* AV1: check for key_frame by looking for Sequence Header OBU.
|
|
143
|
+
*
|
|
144
|
+
* AV1 OBU header: forbidden(1) + obu_type(4) + extension_flag(1) + has_size_field(1) + reserved(1)
|
|
145
|
+
* obu_type is bits 4-7: (byte >> 3) & 0x0F
|
|
146
|
+
*
|
|
147
|
+
* A keyframe access unit starts with OBU_SEQUENCE_HEADER (type 1).
|
|
148
|
+
* A delta frame starts with OBU_FRAME (type 6) without a preceding sequence header.
|
|
149
|
+
*
|
|
150
|
+
* @see AV1 §5.3.1 (OBU header syntax)
|
|
151
|
+
*/
|
|
152
|
+
function isAV1Keyframe(payload) {
|
|
153
|
+
if (payload.length < 2)
|
|
154
|
+
return null;
|
|
155
|
+
const obuType = (payload[0] >> 3) & 0x0F;
|
|
156
|
+
// Sequence header = keyframe (always precedes key frame in a temporal unit)
|
|
157
|
+
if (obuType === 1)
|
|
158
|
+
return true; // OBU_SEQUENCE_HEADER
|
|
159
|
+
// Temporal delimiter (type 2) — look at next OBU
|
|
160
|
+
if (obuType === 2 && payload.length >= 3) {
|
|
161
|
+
const nextObuType = (payload[2] >> 3) & 0x0F;
|
|
162
|
+
if (nextObuType === 1)
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
// Frame or frame header without sequence header = delta
|
|
166
|
+
if (obuType === 3 || obuType === 6)
|
|
167
|
+
return false; // OBU_FRAME_HEADER, OBU_FRAME
|
|
168
|
+
return null; // Unknown OBU type first — can't determine
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=keyframe-validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keyframe-validator.js","sourceRoot":"","sources":["../src/keyframe-validator.ts"],"names":[],"mappings":"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"}
|