@blockcast/mmt-transport 0.2.2 → 0.3.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/README.md +44 -58
- package/dist/abr-controller.d.ts +34 -4
- package/dist/abr-controller.d.ts.map +1 -1
- package/dist/abr-controller.js +66 -17
- package/dist/abr-controller.js.map +1 -1
- package/dist/amt-gateway.d.ts.map +1 -1
- package/dist/amt-gateway.js +3 -4
- package/dist/amt-gateway.js.map +1 -1
- package/dist/driad-discovery.d.ts.map +1 -1
- package/dist/driad-discovery.js +3 -6
- package/dist/driad-discovery.js.map +1 -1
- package/dist/endpoint-config.d.ts +68 -0
- package/dist/endpoint-config.d.ts.map +1 -1
- package/dist/endpoint-config.js +118 -3
- package/dist/endpoint-config.js.map +1 -1
- package/dist/index.d.ts +10 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -11
- package/dist/index.js.map +1 -1
- package/dist/mmt-transport.no-modules.js +881 -60
- package/dist/mmt-transport.no-modules.js.map +4 -4
- package/dist/moqt-priority.d.ts +39 -0
- package/dist/moqt-priority.d.ts.map +1 -0
- package/dist/moqt-priority.js +41 -0
- package/dist/moqt-priority.js.map +1 -0
- package/dist/multicast-catalog-policy.d.ts +96 -0
- package/dist/multicast-catalog-policy.d.ts.map +1 -0
- package/dist/multicast-catalog-policy.js +660 -0
- package/dist/multicast-catalog-policy.js.map +1 -0
- package/dist/multicast-orchestrator.d.ts +24 -0
- package/dist/multicast-orchestrator.d.ts.map +1 -1
- package/dist/multicast-orchestrator.js +102 -9
- package/dist/multicast-orchestrator.js.map +1 -1
- package/dist/multicast-signaling-reconciler.d.ts +37 -0
- package/dist/multicast-signaling-reconciler.d.ts.map +1 -0
- package/dist/multicast-signaling-reconciler.js +161 -0
- package/dist/multicast-signaling-reconciler.js.map +1 -0
- package/dist/multicast-subscription-pool.d.ts.map +1 -1
- package/dist/multicast-subscription-pool.js +37 -10
- package/dist/multicast-subscription-pool.js.map +1 -1
- package/dist/multicast-tier.d.ts +19 -0
- package/dist/multicast-tier.d.ts.map +1 -1
- package/dist/multicast-tier.js +21 -4
- package/dist/multicast-tier.js.map +1 -1
- package/dist/no-modules.d.ts +40 -3
- package/dist/no-modules.d.ts.map +1 -1
- package/dist/no-modules.js +30 -3
- package/dist/no-modules.js.map +1 -1
- package/dist/packet-batch.d.ts +13 -0
- package/dist/packet-batch.d.ts.map +1 -0
- package/dist/packet-batch.js +14 -0
- package/dist/packet-batch.js.map +1 -0
- package/dist/stall-detector.d.ts +10 -0
- package/dist/stall-detector.d.ts.map +1 -1
- package/dist/stall-detector.js +21 -0
- package/dist/stall-detector.js.map +1 -1
- package/dist/takeover.d.ts +3 -0
- package/dist/takeover.d.ts.map +1 -1
- package/dist/takeover.js +35 -2
- package/dist/takeover.js.map +1 -1
- package/dist/transport-manager.d.ts +21 -17
- package/dist/transport-manager.d.ts.map +1 -1
- package/dist/transport-manager.js +63 -29
- package/dist/transport-manager.js.map +1 -1
- package/dist/types.d.ts +98 -123
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +12 -50
- package/dist/types.js.map +1 -1
- package/node_modules/@blockcast/mmt-alta-parse/README.md +181 -0
- package/node_modules/@blockcast/mmt-alta-parse/dist/.build-stamp +0 -0
- package/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts +57 -0
- package/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-alta-parse/dist/index.js +115 -0
- package/node_modules/@blockcast/mmt-alta-parse/dist/index.js.map +1 -0
- package/node_modules/@blockcast/mmt-alta-parse/package.json +50 -0
- package/node_modules/@blockcast/mmt-alta-parse/src/index.ts +136 -0
- package/node_modules/@blockcast/mmt-base/README.md +134 -0
- package/node_modules/@blockcast/mmt-base/dist/.build-stamp +0 -0
- package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts +55 -0
- package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js +128 -0
- package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js.map +1 -0
- package/node_modules/@blockcast/mmt-base/dist/index.d.ts +7 -0
- package/node_modules/@blockcast/mmt-base/dist/index.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-base/dist/index.js +7 -0
- package/node_modules/@blockcast/mmt-base/dist/index.js.map +1 -0
- package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts +292 -0
- package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-base/dist/mmtp.js +612 -0
- package/node_modules/@blockcast/mmt-base/dist/mmtp.js.map +1 -0
- package/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts +18 -0
- package/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-base/dist/net-constants.js +18 -0
- package/node_modules/@blockcast/mmt-base/dist/net-constants.js.map +1 -0
- package/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts +47 -0
- package/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-base/dist/ntp-short.js +54 -0
- package/node_modules/@blockcast/mmt-base/dist/ntp-short.js.map +1 -0
- package/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts +57 -0
- package/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-base/dist/serial-number.js +98 -0
- package/node_modules/@blockcast/mmt-base/dist/serial-number.js.map +1 -0
- package/node_modules/@blockcast/mmt-base/dist/signalling.d.ts +312 -0
- package/node_modules/@blockcast/mmt-base/dist/signalling.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-base/dist/signalling.js +637 -0
- package/node_modules/@blockcast/mmt-base/dist/signalling.js.map +1 -0
- package/node_modules/@blockcast/mmt-base/package.json +49 -0
- package/node_modules/@blockcast/mmt-base/src/fec-block-profile.ts +145 -0
- package/node_modules/@blockcast/mmt-base/src/index.ts +6 -0
- package/node_modules/@blockcast/mmt-base/src/mmtp.ts +892 -0
- package/node_modules/@blockcast/mmt-base/src/net-constants.ts +17 -0
- package/node_modules/@blockcast/mmt-base/src/ntp-short.ts +67 -0
- package/node_modules/@blockcast/mmt-base/src/serial-number.ts +107 -0
- package/node_modules/@blockcast/mmt-base/src/signalling.ts +862 -0
- package/package.json +13 -6
- package/src/abr-controller.ts +81 -17
- package/src/amt-gateway.ts +3 -5
- package/src/driad-discovery.ts +3 -6
- package/src/endpoint-config.ts +157 -3
- package/src/index.ts +54 -33
- package/src/moqt-priority.ts +51 -0
- package/src/multicast-catalog-policy.ts +753 -0
- package/src/multicast-orchestrator.ts +114 -10
- package/src/multicast-signaling-reconciler.ts +156 -0
- package/src/multicast-subscription-pool.ts +46 -11
- package/src/multicast-tier.ts +30 -3
- package/src/no-modules.ts +54 -2
- package/src/packet-batch.ts +17 -0
- package/src/stall-detector.ts +37 -0
- package/src/takeover.ts +41 -3
- package/src/transport-manager.ts +64 -34
- package/src/types.ts +103 -160
- package/dist/endpoint-config.test.d.ts +0 -2
- package/dist/endpoint-config.test.d.ts.map +0 -1
- package/dist/endpoint-config.test.js +0 -39
- package/dist/endpoint-config.test.js.map +0 -1
- package/dist/fec-client.d.ts +0 -442
- package/dist/fec-client.d.ts.map +0 -1
- package/dist/fec-client.js +0 -789
- package/dist/fec-client.js.map +0 -1
- package/dist/fec-client.test.d.ts +0 -8
- package/dist/fec-client.test.d.ts.map +0 -1
- package/dist/fec-client.test.js +0 -112
- package/dist/fec-client.test.js.map +0 -1
- package/dist/multicast-subscription-pool.test.d.ts +0 -2
- package/dist/multicast-subscription-pool.test.d.ts.map +0 -1
- package/dist/multicast-subscription-pool.test.js +0 -108
- package/dist/multicast-subscription-pool.test.js.map +0 -1
- package/dist/multicast-tier.test.d.ts +0 -2
- package/dist/multicast-tier.test.d.ts.map +0 -1
- package/dist/multicast-tier.test.js +0 -75
- package/dist/multicast-tier.test.js.map +0 -1
- package/dist/stall-detector.test.d.ts +0 -2
- package/dist/stall-detector.test.d.ts.map +0 -1
- package/dist/stall-detector.test.js +0 -33
- package/dist/stall-detector.test.js.map +0 -1
- package/dist/transport-manager.test.d.ts +0 -2
- package/dist/transport-manager.test.d.ts.map +0 -1
- package/dist/transport-manager.test.js +0 -155
- package/dist/transport-manager.test.js.map +0 -1
- package/src/endpoint-config.test.ts +0 -54
- package/src/fec-client.test.ts +0 -140
- package/src/fec-client.ts +0 -1103
- package/src/multicast-subscription-pool.test.ts +0 -115
- package/src/multicast-tier.test.ts +0 -80
- package/src/stall-detector.test.ts +0 -34
- package/src/transport-manager.test.ts +0 -192
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blockcast/mmt-transport",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Unified transport manager for MoQ with WebTransport, SSM multicast, AMT, DRIAD, and FEC support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -10,10 +10,6 @@
|
|
|
10
10
|
"import": "./dist/index.js",
|
|
11
11
|
"types": "./dist/index.d.ts"
|
|
12
12
|
},
|
|
13
|
-
"./fec": {
|
|
14
|
-
"import": "./dist/fec-client.js",
|
|
15
|
-
"types": "./dist/fec-client.d.ts"
|
|
16
|
-
},
|
|
17
13
|
"./clock": {
|
|
18
14
|
"import": "./dist/clock.js",
|
|
19
15
|
"types": "./dist/clock.d.ts"
|
|
@@ -24,7 +20,12 @@
|
|
|
24
20
|
},
|
|
25
21
|
"files": [
|
|
26
22
|
"dist",
|
|
27
|
-
"src"
|
|
23
|
+
"src",
|
|
24
|
+
"!src/**/*.test.ts",
|
|
25
|
+
"!src/**/*.spec.ts",
|
|
26
|
+
"!src/**/__tests__",
|
|
27
|
+
"!dist/**/*.test.*",
|
|
28
|
+
"!dist/**/*.spec.*"
|
|
28
29
|
],
|
|
29
30
|
"scripts": {
|
|
30
31
|
"build": "tsc && node build.mjs",
|
|
@@ -34,6 +35,12 @@
|
|
|
34
35
|
"test": "vitest run",
|
|
35
36
|
"test:watch": "vitest"
|
|
36
37
|
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@blockcast/mmt-base": "^0.3.0"
|
|
40
|
+
},
|
|
41
|
+
"bundledDependencies": [
|
|
42
|
+
"@blockcast/mmt-base"
|
|
43
|
+
],
|
|
37
44
|
"devDependencies": {
|
|
38
45
|
"esbuild": "^0.21.5",
|
|
39
46
|
"typescript": "^5.9.3",
|
package/src/abr-controller.ts
CHANGED
|
@@ -27,9 +27,15 @@ export interface FecStatsForAbr {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
-
*
|
|
30
|
+
* A generic 240p..4K ladder, kept as public API only.
|
|
31
|
+
*
|
|
32
|
+
* @deprecated Not a stream's ladder and never a default: the controller
|
|
33
|
+
* adapts over the ladder its caller supplies (`AdaptiveBitrateControllerOptions.ladder`,
|
|
34
|
+
* e.g. the catalog's video renditions). Walking this list is how a player on a
|
|
35
|
+
* 360p/480p/720p catalog logged upgrades to 1080p and 1440p it could never
|
|
36
|
+
* switch to (BLO-35597).
|
|
31
37
|
*/
|
|
32
|
-
export const QUALITY_LEVELS: QualityLevel[] = [
|
|
38
|
+
export const QUALITY_LEVELS: readonly QualityLevel[] = Object.freeze([
|
|
33
39
|
{ name: "240p", bitrate: 300_000, width: 426, height: 240, fps: 15 },
|
|
34
40
|
{ name: "360p", bitrate: 500_000, width: 640, height: 360, fps: 24 },
|
|
35
41
|
{ name: "480p", bitrate: 1_000_000, width: 854, height: 480, fps: 30 },
|
|
@@ -37,7 +43,57 @@ export const QUALITY_LEVELS: QualityLevel[] = [
|
|
|
37
43
|
{ name: "1080p", bitrate: 5_000_000, width: 1920, height: 1080, fps: 30 },
|
|
38
44
|
{ name: "1440p", bitrate: 8_000_000, width: 2560, height: 1440, fps: 30 },
|
|
39
45
|
{ name: "4K", bitrate: 15_000_000, width: 3840, height: 2160, fps: 30 },
|
|
40
|
-
];
|
|
46
|
+
].map((level) => Object.freeze(level)));
|
|
47
|
+
|
|
48
|
+
/** What the controller adapts over and where it starts; both from the caller. */
|
|
49
|
+
export interface AdaptiveBitrateControllerOptions {
|
|
50
|
+
/**
|
|
51
|
+
* The renditions this stream carries (e.g. the catalog's video tracks), in
|
|
52
|
+
* any order: a unique name and a bitrate (bps) each, plus dimensions and
|
|
53
|
+
* fps when the catalog states them. Renditions may share a bitrate.
|
|
54
|
+
*/
|
|
55
|
+
ladder: readonly QualityLevel[];
|
|
56
|
+
/** Name of the ladder rendition presented at construction; the lowest
|
|
57
|
+
* rung when omitted. A name that matches no rung is refused. */
|
|
58
|
+
initialQuality?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function positiveFinite(value: unknown): value is number {
|
|
62
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Validate a caller ladder and return it frozen, ascending by bitrate. Throws
|
|
67
|
+
* on an empty ladder, a rung without a name or a positive finite bitrate, a
|
|
68
|
+
* width, height or fps that is present but not positive finite, or a name that
|
|
69
|
+
* appears twice: a name is how `setQuality` and the initial rendition find a
|
|
70
|
+
* rung. A shared bitrate is legal (e.g. 720p30 and 720p60, or two codecs);
|
|
71
|
+
* `Array.prototype.sort` is stable, so equal bitrates keep the caller's order.
|
|
72
|
+
*/
|
|
73
|
+
export function validateQualityLadder(ladder: readonly QualityLevel[]): readonly QualityLevel[] {
|
|
74
|
+
if (!Array.isArray(ladder) || ladder.length === 0) {
|
|
75
|
+
throw new TypeError("[ABR] ladder must list at least one rendition (e.g. the catalog's video tracks)");
|
|
76
|
+
}
|
|
77
|
+
const names = new Set<string>();
|
|
78
|
+
for (const level of ladder) {
|
|
79
|
+
if (typeof level?.name !== "string" || level.name.length === 0) {
|
|
80
|
+
throw new TypeError("[ABR] every ladder rendition needs a name");
|
|
81
|
+
}
|
|
82
|
+
if (!positiveFinite(level.bitrate)) {
|
|
83
|
+
throw new RangeError(`[ABR] rendition ${level.name}: bitrate must be a positive finite number, got ${String(level.bitrate)}`);
|
|
84
|
+
}
|
|
85
|
+
for (const field of ["width", "height", "fps"] as const) {
|
|
86
|
+
if (level[field] !== undefined && !positiveFinite(level[field])) {
|
|
87
|
+
throw new RangeError(`[ABR] rendition ${level.name}: ${field} must be a positive finite number when stated, got ${String(level[field])}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (names.has(level.name)) throw new RangeError(`[ABR] rendition ${level.name} appears twice in the ladder`);
|
|
91
|
+
names.add(level.name);
|
|
92
|
+
}
|
|
93
|
+
return Object.freeze(
|
|
94
|
+
[...ladder].sort((a, b) => a.bitrate - b.bitrate).map((level) => Object.freeze({ ...level })),
|
|
95
|
+
);
|
|
96
|
+
}
|
|
41
97
|
|
|
42
98
|
/**
|
|
43
99
|
* Progressive Adaptive Bitrate Controller
|
|
@@ -50,6 +106,8 @@ export const QUALITY_LEVELS: QualityLevel[] = [
|
|
|
50
106
|
* - Fast quality downgrades on congestion
|
|
51
107
|
*/
|
|
52
108
|
export class AdaptiveBitrateController {
|
|
109
|
+
/** The caller's renditions, ascending by bitrate (`validateQualityLadder`). */
|
|
110
|
+
readonly ladder: readonly QualityLevel[];
|
|
53
111
|
private currentQualityIndex: number;
|
|
54
112
|
private history: { timestamp: number; bytesReceived: number; packetsLost: number }[] = [];
|
|
55
113
|
private readonly historyWindow = 10_000; // 10 second window
|
|
@@ -68,10 +126,16 @@ export class AdaptiveBitrateController {
|
|
|
68
126
|
fecEnabled: false,
|
|
69
127
|
};
|
|
70
128
|
|
|
71
|
-
constructor(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
129
|
+
constructor(options: AdaptiveBitrateControllerOptions) {
|
|
130
|
+
if (!options || typeof options !== "object") {
|
|
131
|
+
throw new TypeError("[ABR] AdaptiveBitrateController needs { ladder, initialQuality }");
|
|
132
|
+
}
|
|
133
|
+
this.ladder = validateQualityLadder(options.ladder);
|
|
134
|
+
this.currentQualityIndex =
|
|
135
|
+
options.initialQuality === undefined ? 0 : this.ladder.findIndex((q) => q.name === options.initialQuality);
|
|
136
|
+
if (this.currentQualityIndex < 0) {
|
|
137
|
+
throw new RangeError(`[ABR] initial rendition ${String(options.initialQuality)} is not in the ladder`);
|
|
138
|
+
}
|
|
75
139
|
}
|
|
76
140
|
|
|
77
141
|
setCallback(callback: (quality: QualityLevel) => void) {
|
|
@@ -79,7 +143,7 @@ export class AdaptiveBitrateController {
|
|
|
79
143
|
}
|
|
80
144
|
|
|
81
145
|
getCurrentQuality(): QualityLevel {
|
|
82
|
-
return
|
|
146
|
+
return this.ladder[this.currentQualityIndex];
|
|
83
147
|
}
|
|
84
148
|
|
|
85
149
|
/** Selects the multicast (S,G) tier without replacing a player's unicast ABR manager. */
|
|
@@ -130,7 +194,7 @@ export class AdaptiveBitrateController {
|
|
|
130
194
|
if (
|
|
131
195
|
measuredBitrate > currentQuality.bitrate * this.upgradeThreshold &&
|
|
132
196
|
effectiveLossRate < 0.01 &&
|
|
133
|
-
this.currentQualityIndex <
|
|
197
|
+
this.currentQualityIndex < this.ladder.length - 1
|
|
134
198
|
) {
|
|
135
199
|
this.upgrade();
|
|
136
200
|
}
|
|
@@ -171,7 +235,7 @@ export class AdaptiveBitrateController {
|
|
|
171
235
|
}
|
|
172
236
|
|
|
173
237
|
private upgrade() {
|
|
174
|
-
if (this.currentQualityIndex >=
|
|
238
|
+
if (this.currentQualityIndex >= this.ladder.length - 1) return;
|
|
175
239
|
|
|
176
240
|
this.currentQualityIndex++;
|
|
177
241
|
this.lastQualityChange = Date.now();
|
|
@@ -195,15 +259,15 @@ export class AdaptiveBitrateController {
|
|
|
195
259
|
}
|
|
196
260
|
|
|
197
261
|
/**
|
|
198
|
-
*
|
|
262
|
+
* Select a ladder rendition: the caller switched to it (a manual override,
|
|
263
|
+
* or a switch the caller made). Throws for a name the ladder does not carry.
|
|
199
264
|
*/
|
|
200
265
|
setQuality(name: string) {
|
|
201
|
-
const index =
|
|
202
|
-
if (index
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
266
|
+
const index = this.ladder.findIndex((q) => q.name === name);
|
|
267
|
+
if (index < 0) throw new RangeError(`[ABR] rendition ${name} is not in the ladder`);
|
|
268
|
+
this.currentQualityIndex = index;
|
|
269
|
+
this.lastQualityChange = Date.now();
|
|
270
|
+
this.onQualityChange?.(this.getCurrentQuality());
|
|
207
271
|
}
|
|
208
272
|
|
|
209
273
|
/**
|
package/src/amt-gateway.ts
CHANGED
|
@@ -9,9 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import type { GroupKey, PacketMetadata, AmtRelayInfo } from "./types.js";
|
|
11
11
|
import { DRIADDiscovery } from "./driad-discovery.js";
|
|
12
|
-
|
|
13
|
-
/** Default AMT port (RFC 7450) */
|
|
14
|
-
const DEFAULT_AMT_PORT = 2268;
|
|
12
|
+
import { DEFAULT_AMT_RELAY_PORT } from "@blockcast/mmt-base";
|
|
15
13
|
|
|
16
14
|
/** AMT message types */
|
|
17
15
|
const AMT_MESSAGE_TYPES = {
|
|
@@ -133,7 +131,7 @@ export class AMTGatewayManager {
|
|
|
133
131
|
try {
|
|
134
132
|
let relayAddress: string;
|
|
135
133
|
let discoveryMethod: "driad" | "manual";
|
|
136
|
-
const relayPort = config.relayPort ||
|
|
134
|
+
const relayPort = config.relayPort || DEFAULT_AMT_RELAY_PORT;
|
|
137
135
|
|
|
138
136
|
if (config.relayAddress) {
|
|
139
137
|
relayAddress = config.relayAddress;
|
|
@@ -352,7 +350,7 @@ export class AMTGateway {
|
|
|
352
350
|
|
|
353
351
|
constructor(options: AMTGatewayOptions) {
|
|
354
352
|
this.relayAddress = options.relayAddress;
|
|
355
|
-
this.relayPort = options.relayPort ||
|
|
353
|
+
this.relayPort = options.relayPort || DEFAULT_AMT_RELAY_PORT;
|
|
356
354
|
|
|
357
355
|
this.onPacket = options.onPacket || (() => {});
|
|
358
356
|
this.onStateChange = options.onStateChange || (() => {});
|
package/src/driad-discovery.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* Query: 10.95.25.69.amt.in-addr.arpa (source-based, NOT group-based)
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
import { DEFAULT_AMT_RELAY_PORT } from "@blockcast/mmt-base";
|
|
13
14
|
import type { DRIADRelay } from "./types.js";
|
|
14
15
|
|
|
15
16
|
/**
|
|
@@ -89,11 +90,7 @@ export class DRIADDiscovery {
|
|
|
89
90
|
return [dohRelay];
|
|
90
91
|
}
|
|
91
92
|
|
|
92
|
-
|
|
93
|
-
return [
|
|
94
|
-
{ host: "amt-relay.m2icast.net", port: 2268, priority: 10, weight: 100, asn: 0 },
|
|
95
|
-
{ host: "amt.akamaistream.net", port: 2268, priority: 20, weight: 50, asn: 0 },
|
|
96
|
-
];
|
|
93
|
+
return [];
|
|
97
94
|
}
|
|
98
95
|
|
|
99
96
|
/**
|
|
@@ -121,7 +118,7 @@ export class DRIADDiscovery {
|
|
|
121
118
|
if (result.Answer && result.Answer.length > 0) {
|
|
122
119
|
return {
|
|
123
120
|
host: result.Answer[0].data,
|
|
124
|
-
port:
|
|
121
|
+
port: DEFAULT_AMT_RELAY_PORT,
|
|
125
122
|
priority: 10,
|
|
126
123
|
weight: 100,
|
|
127
124
|
asn: 0,
|
package/src/endpoint-config.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
|
+
import { MMTP_PACKET_ID_MAX, MMTP_SIGNALING_PACKET_ID } from "@blockcast/mmt-base";
|
|
2
|
+
import type {
|
|
3
|
+
WindowMulticastSubscribeConfig,
|
|
4
|
+
WindowMulticastSubscribeConfigBytes,
|
|
5
|
+
} from "./types.js";
|
|
6
|
+
|
|
1
7
|
export type MulticastTransportPreference = "auto" | "native" | "tunnel";
|
|
2
8
|
|
|
9
|
+
export { DEFAULT_AMT_RELAY_PORT } from "@blockcast/mmt-base";
|
|
10
|
+
|
|
3
11
|
export interface MulticastEndpointConfig {
|
|
4
12
|
groupAddress: string;
|
|
5
13
|
sourceAddress?: string;
|
|
@@ -9,6 +17,94 @@ export interface MulticastEndpointConfig {
|
|
|
9
17
|
amtRelayPort?: number;
|
|
10
18
|
}
|
|
11
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Input accepted by the canonical window.multicast request converter.
|
|
22
|
+
*
|
|
23
|
+
* `mode` and the `amtRelay*` fields are internal endpoint-selection state;
|
|
24
|
+
* they are deliberately not emitted on the page-facing request. The
|
|
25
|
+
* resulting `origin` is the only acquisition selector understood by the
|
|
26
|
+
* gateway.
|
|
27
|
+
*/
|
|
28
|
+
export interface WindowMulticastEndpointInput {
|
|
29
|
+
group: string;
|
|
30
|
+
port: number;
|
|
31
|
+
source?: string;
|
|
32
|
+
mode?: MulticastTransportPreference;
|
|
33
|
+
amtRelay?: string;
|
|
34
|
+
amtRelayPort?: number;
|
|
35
|
+
packetIds?: readonly number[];
|
|
36
|
+
tracks?: ReadonlyArray<{ name: string; packetId: number }>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Convert internal endpoint-selection state to the canonical page contract.
|
|
41
|
+
*
|
|
42
|
+
* Native is the default and an explicit `native` selection always wins over
|
|
43
|
+
* stale relay metadata. Tunnel/AMT requests must carry the resolved relay
|
|
44
|
+
* and port; there is no implicit relay-port default at this boundary.
|
|
45
|
+
*
|
|
46
|
+
* That rule is deliberate and is NOT the place to fix a relay that names no
|
|
47
|
+
* port: this is the last hop before the page-facing request, so a default
|
|
48
|
+
* applied here would be invisible to every caller and to the join it produces.
|
|
49
|
+
* A catalog relay with no port gets `DEFAULT_AMT_RELAY_PORT` upstream, in the
|
|
50
|
+
* readers that resolve it, and arrives here explicit like any other
|
|
51
|
+
* (BLO-38176).
|
|
52
|
+
*/
|
|
53
|
+
export function toWindowMulticastSubscribeConfig(
|
|
54
|
+
input: WindowMulticastEndpointInput,
|
|
55
|
+
): WindowMulticastSubscribeConfig {
|
|
56
|
+
if (input.mode !== undefined && !["auto", "native", "tunnel"].includes(input.mode)) {
|
|
57
|
+
throw new TypeError(`Unsupported multicast mode: ${String(input.mode)}`);
|
|
58
|
+
}
|
|
59
|
+
// An explicit non-array (the null "endpoint unusable" sentinel from
|
|
60
|
+
// selectEndpointPacketIdsForTracks) must not degrade into "no filter".
|
|
61
|
+
if (input.packetIds !== undefined && !Array.isArray(input.packetIds)) {
|
|
62
|
+
throw new TypeError(
|
|
63
|
+
`multicast packetIds must be an array, got ${input.packetIds === null ? "null" : typeof input.packetIds}`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
// Every join request passes through here, so the track packet_id range is
|
|
67
|
+
// enforced here rather than trusted from each caller.
|
|
68
|
+
if (input.packetIds?.some(id => !isMulticastTrackPacketId(id)) ||
|
|
69
|
+
input.tracks?.some(track => !isMulticastTrackPacketId(track.packetId))) {
|
|
70
|
+
throw new RangeError(
|
|
71
|
+
`multicast packetId must be an integer from 1 to ${MMTP_PACKET_ID_MAX}; ` +
|
|
72
|
+
"packetId 0 is the MMTP signaling flow and is never a track's",
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const base = {
|
|
77
|
+
group: input.group,
|
|
78
|
+
port: input.port,
|
|
79
|
+
...(input.source?.trim() ? { source: input.source.trim() } : {}),
|
|
80
|
+
consume: "bytes" as const,
|
|
81
|
+
...(input.packetIds?.length ? { packetIds: [...input.packetIds] } : {}),
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const relay = input.amtRelay?.trim();
|
|
85
|
+
const wantsAmt = input.mode === "tunnel" ||
|
|
86
|
+
(input.mode !== "native" && relay !== undefined && relay !== "");
|
|
87
|
+
if (!wantsAmt) {
|
|
88
|
+
const native: WindowMulticastSubscribeConfigBytes = {
|
|
89
|
+
...base,
|
|
90
|
+
origin: "ip-multicast",
|
|
91
|
+
...(input.tracks?.length ? { tracks: input.tracks.map(({ name, packetId }) => ({ name, packetId })) } : {}),
|
|
92
|
+
};
|
|
93
|
+
return native;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const relayPort = input.amtRelayPort;
|
|
97
|
+
if (!relay || relayPort === undefined || !isPositivePort(relayPort)) {
|
|
98
|
+
throw new TypeError("AMT requires a resolved relay and a valid relay port");
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
...base,
|
|
102
|
+
origin: "amt",
|
|
103
|
+
relay,
|
|
104
|
+
relayPort,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
12
108
|
export function isValidMulticastGroupAddress(address: string | undefined): boolean {
|
|
13
109
|
const value = address?.trim().toLowerCase();
|
|
14
110
|
if (!value) return false;
|
|
@@ -58,6 +154,19 @@ export function multicastEndpointKey(endpoint: MulticastEndpointConfig): string
|
|
|
58
154
|
]);
|
|
59
155
|
}
|
|
60
156
|
|
|
157
|
+
/**
|
|
158
|
+
* True when `value` may be advertised as a multicast track's MMTP `packet_id`:
|
|
159
|
+
* an integer in 1..MMTP_PACKET_ID_MAX. packet_id 0 (MMTP_SIGNALING_PACKET_ID)
|
|
160
|
+
* is the MMTP signaling flow -- the FFmpeg `moq_mmt` muxer sends the AL-FEC
|
|
161
|
+
* message (0x0203) on it into the same group as the media -- so it is never a
|
|
162
|
+
* track's. Only a number qualifies: a string, boolean or null is not coerced.
|
|
163
|
+
*/
|
|
164
|
+
export function isMulticastTrackPacketId(value: unknown): value is number {
|
|
165
|
+
return Number.isInteger(value) &&
|
|
166
|
+
(value as number) > MMTP_SIGNALING_PACKET_ID &&
|
|
167
|
+
(value as number) <= MMTP_PACKET_ID_MAX;
|
|
168
|
+
}
|
|
169
|
+
|
|
61
170
|
export function isUsableMulticastEndpoint(
|
|
62
171
|
endpoint: { groupAddress?: string; port?: unknown } | undefined,
|
|
63
172
|
): boolean {
|
|
@@ -65,6 +174,46 @@ export function isUsableMulticastEndpoint(
|
|
|
65
174
|
return isValidMulticastGroupAddress(endpoint?.groupAddress) && isPositivePort(port);
|
|
66
175
|
}
|
|
67
176
|
|
|
177
|
+
/**
|
|
178
|
+
* Unprefixed aliases of the `mc_` URL override keys, retired in BLO-37507, each
|
|
179
|
+
* paired with the key that replaces it.
|
|
180
|
+
*/
|
|
181
|
+
const RETIRED_MULTICAST_URL_PARAMS = [
|
|
182
|
+
["amt_relay", "mc_amt_relay"],
|
|
183
|
+
["amt_relay_port", "mc_amt_relay_port"],
|
|
184
|
+
] as const;
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Reject a URL multicast override that still carries a retired unprefixed alias.
|
|
188
|
+
*
|
|
189
|
+
* Both URL readers call this only once a valid override is established (a valid
|
|
190
|
+
* multicast `mc_group` and a valid `mc_port`; `readUrlMulticastConfig` also
|
|
191
|
+
* requires the whole endpoint to normalize), which is where they used to read
|
|
192
|
+
* the aliases: a link that
|
|
193
|
+
* relied on one fails here with its replacement named, instead of silently
|
|
194
|
+
* losing its AMT relay (and, in `readUrlMulticastConfig`, its tunnel mode).
|
|
195
|
+
* Presence is the error, whatever the value and whether or not the `mc_` form
|
|
196
|
+
* is also present: quietly preferring one of two keys is the tolerant read being
|
|
197
|
+
* retired. Outside an override the readers read no override key at all, the
|
|
198
|
+
* `mc_` forms included, so the bare names are left to the page; they are live
|
|
199
|
+
* page parameters elsewhere, and keeping clear of those is what the `mc_`
|
|
200
|
+
* namespace is for.
|
|
201
|
+
*/
|
|
202
|
+
export function assertNoRetiredMulticastUrlParams(params: URLSearchParams): void {
|
|
203
|
+
for (const [retired, replacement] of RETIRED_MULTICAST_URL_PARAMS) {
|
|
204
|
+
if (params.has(retired)) {
|
|
205
|
+
throw new Error(
|
|
206
|
+
`multicast URL override: query parameter "${retired}" is retired; use "${replacement}"`,
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Read an explicit multicast endpoint override from `mc_*` URL parameters.
|
|
214
|
+
* Throws when an override carries a retired unprefixed alias
|
|
215
|
+
* (`assertNoRetiredMulticastUrlParams`).
|
|
216
|
+
*/
|
|
68
217
|
export function readUrlMulticastConfig(
|
|
69
218
|
search: string | URLSearchParams = typeof location === "undefined" ? "" : location.search,
|
|
70
219
|
): MulticastEndpointConfig | undefined {
|
|
@@ -73,12 +222,12 @@ export function readUrlMulticastConfig(
|
|
|
73
222
|
const port = positivePort(params.get("mc_port"));
|
|
74
223
|
if (!groupAddress || !port) return undefined;
|
|
75
224
|
|
|
76
|
-
const amtRelay = params.get("mc_amt_relay") ||
|
|
77
|
-
const amtRelayPort = positivePort(params.get("mc_amt_relay_port")
|
|
225
|
+
const amtRelay = params.get("mc_amt_relay") || undefined;
|
|
226
|
+
const amtRelayPort = positivePort(params.get("mc_amt_relay_port"));
|
|
78
227
|
const requestedMode = params.get("mc_mode");
|
|
79
228
|
const mode = amtRelay ? "tunnel" : multicastMode(requestedMode);
|
|
80
229
|
|
|
81
|
-
|
|
230
|
+
const endpoint = normalizeMulticastEndpoint({
|
|
82
231
|
groupAddress,
|
|
83
232
|
sourceAddress: params.get("mc_source") || undefined,
|
|
84
233
|
port,
|
|
@@ -86,6 +235,11 @@ export function readUrlMulticastConfig(
|
|
|
86
235
|
...(amtRelay ? { amtRelay } : {}),
|
|
87
236
|
...(amtRelayPort ? { amtRelayPort } : {}),
|
|
88
237
|
});
|
|
238
|
+
// An endpoint that does not normalize (e.g. a non-multicast mc_group) is no
|
|
239
|
+
// override, so the page keeps its own bare names; the same scope
|
|
240
|
+
// readPlayerMulticastConfig applies by validating the group first.
|
|
241
|
+
if (endpoint) assertNoRetiredMulticastUrlParams(params);
|
|
242
|
+
return endpoint;
|
|
89
243
|
}
|
|
90
244
|
|
|
91
245
|
export function isMulticastExplicitlyDisabled(
|
package/src/index.ts
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
// Types (new generic names)
|
|
27
27
|
export type {
|
|
28
28
|
TransportConfig,
|
|
29
|
+
TransportConnectConfig,
|
|
29
30
|
TransportSubscription,
|
|
30
31
|
QualityLevel,
|
|
31
32
|
TransportCapabilities,
|
|
@@ -35,16 +36,15 @@ export type {
|
|
|
35
36
|
AmtRelayInfo,
|
|
36
37
|
GroupKey,
|
|
37
38
|
PacketMetadata,
|
|
38
|
-
MfuFragment,
|
|
39
|
-
ReassembledMfu,
|
|
40
|
-
ProcessingStats,
|
|
41
|
-
MmtFecClientOptions,
|
|
42
39
|
Micro,
|
|
43
40
|
SyncDecision,
|
|
44
41
|
WindowMulticastSubscribeConfig,
|
|
42
|
+
WindowMulticastSubscribeConfigBytes,
|
|
43
|
+
WindowMulticastSubscribeConfigAmt,
|
|
45
44
|
WindowMulticastSubscriptionState,
|
|
46
45
|
WindowMulticastSubscriptionEventMap,
|
|
47
46
|
WindowMulticastSubscription,
|
|
47
|
+
WindowMulticastDelivery,
|
|
48
48
|
WindowMulticastCapabilities,
|
|
49
49
|
WindowMulticastGateway,
|
|
50
50
|
// Consolidated from @blockcast/multicast
|
|
@@ -54,40 +54,28 @@ export type {
|
|
|
54
54
|
SubscriptionState,
|
|
55
55
|
SubscriptionStats,
|
|
56
56
|
MulticastSubscriptionHandle,
|
|
57
|
-
FecConfig,
|
|
58
|
-
FecStats,
|
|
59
|
-
MulticastConfigWithFec,
|
|
60
57
|
MulticastBridge,
|
|
61
58
|
// FEC types per draft-ramadan-moq-fec-00
|
|
62
59
|
MoqFecConfig,
|
|
63
60
|
RepairObjectHeader,
|
|
64
|
-
// @deprecated - use TransportConfig and TransportSubscription instead
|
|
65
|
-
SSMConfig,
|
|
66
|
-
SSMSubscription,
|
|
67
61
|
} from "./types.js";
|
|
68
62
|
|
|
69
63
|
// FEC constants and utilities per draft-ramadan-moq-fec-00
|
|
70
64
|
export {
|
|
71
|
-
FEC_CONFIG_MESSAGE_TYPE,
|
|
72
|
-
REPAIR_TRACK_SUFFIX,
|
|
73
65
|
repairTrackPath,
|
|
74
|
-
isRepairTrack,
|
|
75
|
-
sourceTrackFromRepair,
|
|
76
66
|
MoqFecAlgorithm,
|
|
77
67
|
} from "./types.js";
|
|
78
68
|
|
|
79
|
-
// Transport Manager (renamed from SSMTransportManager)
|
|
80
69
|
export { TransportManager, transportManager } from "./transport-manager.js";
|
|
81
70
|
|
|
82
|
-
// @deprecated - Use TransportManager and transportManager instead
|
|
83
|
-
export { SSMTransportManager, ssmTransport } from "./transport-manager.js";
|
|
84
|
-
|
|
85
71
|
// DRIAD Discovery
|
|
86
72
|
export { DRIADDiscovery, driadDiscovery } from "./driad-discovery.js";
|
|
87
73
|
|
|
88
74
|
// Adaptive Bitrate Controller
|
|
89
75
|
export {
|
|
90
76
|
AdaptiveBitrateController,
|
|
77
|
+
validateQualityLadder,
|
|
78
|
+
type AdaptiveBitrateControllerOptions,
|
|
91
79
|
QUALITY_LEVELS,
|
|
92
80
|
type FecStatsForAbr,
|
|
93
81
|
} from "./abr-controller.js";
|
|
@@ -104,23 +92,59 @@ export {
|
|
|
104
92
|
DEFAULT_JOIN_DEADLINE_MS,
|
|
105
93
|
goodputFromStats,
|
|
106
94
|
type GoodputSample,
|
|
95
|
+
type HeadroomSample,
|
|
107
96
|
type MulticastTierPacket,
|
|
108
97
|
type OrchestratorCommand,
|
|
109
98
|
type OrchestratorOptions,
|
|
110
99
|
type OrchestratorState,
|
|
111
100
|
} from "./multicast-orchestrator.js";
|
|
112
101
|
|
|
102
|
+
export {
|
|
103
|
+
createMulticastSignalingReconciler,
|
|
104
|
+
normalizeCatalogEndpoints,
|
|
105
|
+
type MulticastSignalingEndpoint,
|
|
106
|
+
} from "./multicast-signaling-reconciler.js";
|
|
107
|
+
|
|
108
|
+
export {
|
|
109
|
+
catalogTierEndpoints,
|
|
110
|
+
endpointCarriesMedia,
|
|
111
|
+
endpointToMulticastConfig,
|
|
112
|
+
hasMulticastCatalogBaseline,
|
|
113
|
+
hasUnicastMediaRenditions,
|
|
114
|
+
isMulticastOnlyCatalog,
|
|
115
|
+
isRecoverableMulticastSignalReadError,
|
|
116
|
+
mergeUrlMulticastPolicy,
|
|
117
|
+
normalizeMulticastConfig,
|
|
118
|
+
readPlayerMulticastConfig,
|
|
119
|
+
requiresMulticastCapability,
|
|
120
|
+
sameMulticastConfig,
|
|
121
|
+
selectEndpointPacketIds,
|
|
122
|
+
selectEndpointPacketIdsForTracks,
|
|
123
|
+
selectMulticastCandidate,
|
|
124
|
+
selectMulticastCandidateForTracks,
|
|
125
|
+
urlOverrideBlocksMulticastCandidate,
|
|
126
|
+
withMulticastEndpoints,
|
|
127
|
+
catalogQualityLadder,
|
|
128
|
+
type PlayerMulticastConfig,
|
|
129
|
+
type MulticastTrackSelection,
|
|
130
|
+
} from "./multicast-catalog-policy.js";
|
|
131
|
+
|
|
113
132
|
export {
|
|
114
133
|
isMulticastExplicitlyDisabled,
|
|
134
|
+
isMulticastTrackPacketId,
|
|
115
135
|
isUsableMulticastEndpoint,
|
|
116
136
|
isValidMulticastGroupAddress,
|
|
117
137
|
multicastEndpointKey,
|
|
118
138
|
normalizeMulticastEndpoint,
|
|
119
139
|
readUrlMulticastConfig,
|
|
140
|
+
toWindowMulticastSubscribeConfig,
|
|
120
141
|
type MulticastEndpointConfig,
|
|
121
142
|
type MulticastTransportPreference,
|
|
143
|
+
type WindowMulticastEndpointInput,
|
|
122
144
|
} from "./endpoint-config.js";
|
|
123
145
|
|
|
146
|
+
export { subscriptionDeliversPacketBatches } from "./packet-batch.js";
|
|
147
|
+
|
|
124
148
|
export {
|
|
125
149
|
MulticastSubscriptionPool,
|
|
126
150
|
multicastSubscriptionPool,
|
|
@@ -129,8 +153,11 @@ export {
|
|
|
129
153
|
} from "./multicast-subscription-pool.js";
|
|
130
154
|
|
|
131
155
|
export {
|
|
156
|
+
classifyStall,
|
|
132
157
|
TransportStallDetector,
|
|
133
158
|
type FecProgressSnapshot,
|
|
159
|
+
type PlayerStallClassification,
|
|
160
|
+
type PlayerStallSnapshot,
|
|
134
161
|
type StallDetectorConfig,
|
|
135
162
|
} from "./stall-detector.js";
|
|
136
163
|
|
|
@@ -158,6 +185,15 @@ export {
|
|
|
158
185
|
type FallbackTrack,
|
|
159
186
|
} from "./unicast-fallback.js";
|
|
160
187
|
|
|
188
|
+
export {
|
|
189
|
+
MOQT_PUBLISHER_PRIORITY,
|
|
190
|
+
MOQT_SUBSCRIBER_PRIORITY,
|
|
191
|
+
moqtPublisherPriority,
|
|
192
|
+
type MoqtPublisherPriority,
|
|
193
|
+
moqtSubscriberPriority,
|
|
194
|
+
type MoqtSubscriberPriority,
|
|
195
|
+
} from "./moqt-priority.js";
|
|
196
|
+
|
|
161
197
|
// AMT Gateway
|
|
162
198
|
export {
|
|
163
199
|
AMTGatewayManager,
|
|
@@ -167,21 +203,6 @@ export {
|
|
|
167
203
|
type AMTGatewayOptions,
|
|
168
204
|
} from "./amt-gateway.js";
|
|
169
205
|
|
|
170
|
-
// FEC Client
|
|
171
|
-
export {
|
|
172
|
-
MmtFecClient,
|
|
173
|
-
createMulticastFecHandler,
|
|
174
|
-
setupMmtFec,
|
|
175
|
-
// MoQ FEC Client (for WebTransport)
|
|
176
|
-
MoqFecClient,
|
|
177
|
-
createMoqFecClient,
|
|
178
|
-
loadMmtWasm,
|
|
179
|
-
type MoqFecClientConfig,
|
|
180
|
-
type MoqFecClientStats,
|
|
181
|
-
// FEC decoder configuration (RFC 6330 OTI parameters)
|
|
182
|
-
type FecDecoderConfig,
|
|
183
|
-
} from "./fec-client.js";
|
|
184
|
-
|
|
185
206
|
// A/V Sync Clock
|
|
186
207
|
export {
|
|
187
208
|
SharedClock,
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const MAX_MOQT_PRIORITY = 0xff;
|
|
2
|
+
|
|
3
|
+
declare const subscriberPriorityBrand: unique symbol;
|
|
4
|
+
declare const publisherPriorityBrand: unique symbol;
|
|
5
|
+
|
|
6
|
+
export type MoqtSubscriberPriority = number & { readonly [subscriberPriorityBrand]: true };
|
|
7
|
+
export type MoqtPublisherPriority = number & { readonly [publisherPriorityBrand]: true };
|
|
8
|
+
|
|
9
|
+
function moqtPriority(value: number, field: string): number {
|
|
10
|
+
if (!Number.isInteger(value) || value < 0 || value > MAX_MOQT_PRIORITY) {
|
|
11
|
+
throw new RangeError(`${field} must be an integer in [0, ${MAX_MOQT_PRIORITY}], got ${value}`);
|
|
12
|
+
}
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Validate a protocol-native MOQT subscriber priority without transforming it. */
|
|
17
|
+
export function moqtSubscriberPriority(value: number): MoqtSubscriberPriority {
|
|
18
|
+
return moqtPriority(value, "MOQT subscriber priority") as MoqtSubscriberPriority;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Validate a protocol-native MOQT publisher priority without transforming it. */
|
|
22
|
+
export function moqtPublisherPriority(value: number): MoqtPublisherPriority {
|
|
23
|
+
return moqtPriority(value, "MOQT publisher priority") as MoqtPublisherPriority;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Shared media subscription policy on MOQT's native lower-number-wins scale. */
|
|
27
|
+
export const MOQT_SUBSCRIBER_PRIORITY = Object.freeze({
|
|
28
|
+
catalog: moqtSubscriberPriority(0),
|
|
29
|
+
/**
|
|
30
|
+
* @deprecated A repair FETCH's SUBSCRIBER_PRIORITY is derived per request
|
|
31
|
+
* from the catalog source and repair priorities (draft-ramadan-moq-fec
|
|
32
|
+
* §11.4.5), not a player constant. Use the `subscriberPriority` that
|
|
33
|
+
* `@blockcast/mmt-container`'s `wireRepairFetch` puts on each
|
|
34
|
+
* `RepairFetchRequest` (`repairFetchSubscriberPriority`).
|
|
35
|
+
*/
|
|
36
|
+
deadlineRepair: moqtSubscriberPriority(0),
|
|
37
|
+
audio: moqtSubscriberPriority(1),
|
|
38
|
+
video: moqtSubscriberPriority(2),
|
|
39
|
+
/**
|
|
40
|
+
* @deprecated See `deadlineRepair`: use the request's catalog-derived
|
|
41
|
+
* `RepairFetchRequest.subscriberPriority` (draft-ramadan-moq-fec §11.4.5).
|
|
42
|
+
*/
|
|
43
|
+
backgroundRepair: moqtSubscriberPriority(3),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
/** Publisher priority bands from draft-ramadan-moq-fec section 10. */
|
|
47
|
+
export const MOQT_PUBLISHER_PRIORITY = Object.freeze({
|
|
48
|
+
catalog: moqtPublisherPriority(32),
|
|
49
|
+
source: moqtPublisherPriority(128),
|
|
50
|
+
repair: moqtPublisherPriority(240),
|
|
51
|
+
});
|