@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/src/takeover.ts
CHANGED
|
@@ -28,7 +28,12 @@ export function makeSourceSwitchHandler(options: {
|
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
export interface LossArcSession {
|
|
31
|
+
export interface LossArcSession {
|
|
32
|
+
/** TrackSession state. Optional so non-TrackSession hosts keep the generic API. */
|
|
33
|
+
readonly state?: string;
|
|
34
|
+
lossDetected(): void;
|
|
35
|
+
lossRecovered(): void;
|
|
36
|
+
}
|
|
32
37
|
export interface FecStarvationSnapshot {
|
|
33
38
|
sourceSymbols: number;
|
|
34
39
|
repairSymbols: number;
|
|
@@ -57,15 +62,48 @@ export async function wireStarvationDetector(args: {
|
|
|
57
62
|
trackName: string;
|
|
58
63
|
fecInterleaveMs: number;
|
|
59
64
|
fecK: number;
|
|
65
|
+
deliveryWindowMs?: number;
|
|
60
66
|
makeDetector(config: { windowMs: number; onStarvation(): void; onRecovery(): void }): StarvationDetectorLike;
|
|
61
67
|
warn?(message: string): void;
|
|
62
68
|
}): Promise<StarvationDetectorLike | null> {
|
|
63
69
|
if (!args.session || args.fecInterleaveMs <= 0 || args.fecK <= 0) return null;
|
|
70
|
+
const windowMs = args.deliveryWindowMs ?? Math.ceil(args.fecInterleaveMs * 3);
|
|
71
|
+
if (!Number.isFinite(windowMs) || windowMs <= 0) {
|
|
72
|
+
args.warn?.(`Unable to create starvation detector for ${args.trackName}: invalid delivery window ${windowMs}`);
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
64
75
|
const detector = wireStallDetector({
|
|
65
76
|
session: args.session,
|
|
66
|
-
windowMs
|
|
77
|
+
windowMs,
|
|
67
78
|
makeDetector: args.makeDetector,
|
|
68
79
|
});
|
|
69
80
|
if (!detector) args.warn?.(`Unable to create starvation detector for ${args.trackName}`);
|
|
70
|
-
return detector;
|
|
81
|
+
if (!detector || typeof args.session.state !== "string") return detector;
|
|
82
|
+
|
|
83
|
+
// Stats flow while TrackSession is still `joining`. Feeding them directly can
|
|
84
|
+
// age a starvation episode past its window before the first multicast RAP,
|
|
85
|
+
// causing the first post-cutover sample to flip a healthy session to
|
|
86
|
+
// `failing`. Start with a fresh baseline only once multicast/AMT is primary;
|
|
87
|
+
// retain the detector through `failing` so it can observe recovery.
|
|
88
|
+
let active = false;
|
|
89
|
+
return {
|
|
90
|
+
update(snapshot) {
|
|
91
|
+
const state = args.session!.state;
|
|
92
|
+
const shouldObserve = state === "multicast" || state === "amt" || state === "failing";
|
|
93
|
+
if (!shouldObserve) {
|
|
94
|
+
if (active) detector.reset();
|
|
95
|
+
active = false;
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (!active) {
|
|
99
|
+
detector.reset();
|
|
100
|
+
active = true;
|
|
101
|
+
}
|
|
102
|
+
detector.update(snapshot);
|
|
103
|
+
},
|
|
104
|
+
reset() {
|
|
105
|
+
active = false;
|
|
106
|
+
detector.reset();
|
|
107
|
+
},
|
|
108
|
+
};
|
|
71
109
|
}
|
package/src/transport-manager.ts
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
import type {
|
|
24
24
|
TransportConfig,
|
|
25
|
+
TransportConnectConfig,
|
|
25
26
|
TransportSubscription,
|
|
26
27
|
TransportCapabilities,
|
|
27
28
|
TransportState,
|
|
@@ -47,9 +48,6 @@ import { normalizeMulticastEndpoint } from "./endpoint-config.js";
|
|
|
47
48
|
*
|
|
48
49
|
* Manages transport connections with automatic mode selection:
|
|
49
50
|
* WebTransport -> window.multicast -> AMT proxy -> WebSocket
|
|
50
|
-
*
|
|
51
|
-
* Renamed from SSMTransportManager - the old name is preserved
|
|
52
|
-
* as a deprecated alias for backwards compatibility.
|
|
53
51
|
*/
|
|
54
52
|
export class TransportManager {
|
|
55
53
|
private state: TransportState = {
|
|
@@ -63,7 +61,8 @@ export class TransportManager {
|
|
|
63
61
|
private capabilities: TransportCapabilities | null = null;
|
|
64
62
|
private subscription: TransportSubscription | null = null;
|
|
65
63
|
private driad = new DRIADDiscovery();
|
|
66
|
-
|
|
64
|
+
/** Built by `connect()` from the caller's ladder; there is no built-in one. */
|
|
65
|
+
private abr: AdaptiveBitrateController | null = null;
|
|
67
66
|
private multicastOrchestrator: MulticastOrchestrator | null = null;
|
|
68
67
|
private onOrchestratorCommand?: (command: OrchestratorCommand) => void;
|
|
69
68
|
private connectionGeneration = 0;
|
|
@@ -72,10 +71,6 @@ export class TransportManager {
|
|
|
72
71
|
private onError?: (error: Error) => void;
|
|
73
72
|
private onStateChange?: (state: TransportState) => void;
|
|
74
73
|
|
|
75
|
-
constructor() {
|
|
76
|
-
this.abr = new AdaptiveBitrateController();
|
|
77
|
-
}
|
|
78
|
-
|
|
79
74
|
private getWindow(): Window | undefined {
|
|
80
75
|
return typeof window === "undefined" ? undefined : window;
|
|
81
76
|
}
|
|
@@ -138,11 +133,38 @@ export class TransportManager {
|
|
|
138
133
|
* 1. window.multicast gateway bytes
|
|
139
134
|
* 2. AMT proxy (specified or discovered via DRIAD)
|
|
140
135
|
* 3. MOQ WebTransport (final fallback)
|
|
136
|
+
*
|
|
137
|
+
* The ABR ladder (`abrLadder`) and initial rendition (`initialQuality`)
|
|
138
|
+
* are checked first, before anything is torn down. A config that fails is
|
|
139
|
+
* refused with the same shape as an invalid endpoint, the error to the
|
|
140
|
+
* error callback and `false` resolved; unlike one, it is caught before the
|
|
141
|
+
* live subscription closes, so a refused reconnect leaves that subscription,
|
|
142
|
+
* its state and any connect already in flight untouched.
|
|
141
143
|
*/
|
|
142
|
-
async connect(config:
|
|
144
|
+
async connect(config: TransportConnectConfig): Promise<boolean> {
|
|
145
|
+
let abr: AdaptiveBitrateController;
|
|
146
|
+
try {
|
|
147
|
+
abr = new AdaptiveBitrateController({
|
|
148
|
+
ladder: config.abrLadder,
|
|
149
|
+
initialQuality: config.initialQuality?.name,
|
|
150
|
+
});
|
|
151
|
+
} catch (error) {
|
|
152
|
+
// A refused config is a caller bug. Without an error callback, say so
|
|
153
|
+
// on the console, since `false` alone also means "no multicast path";
|
|
154
|
+
// with one, the callback is the report.
|
|
155
|
+
const refused = error instanceof Error ? error : new Error(String(error));
|
|
156
|
+
if (this.onError) this.onError(refused);
|
|
157
|
+
else console.error("[Transport] ABR config refused, not connecting:", refused);
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
143
160
|
const generation = ++this.connectionGeneration;
|
|
144
161
|
this.subscription?.close();
|
|
145
162
|
this.subscription = null;
|
|
163
|
+
// The previous connection's ladder ends with it, as in disconnect(): a
|
|
164
|
+
// reconnect that bails below (invalid endpoint, or superseded while
|
|
165
|
+
// detecting capabilities) leaves no ladder, and only the success path
|
|
166
|
+
// installs `abr`.
|
|
167
|
+
this.abr = null;
|
|
146
168
|
this.state = {
|
|
147
169
|
mode: "disconnected",
|
|
148
170
|
connected: false,
|
|
@@ -175,12 +197,11 @@ export class TransportManager {
|
|
|
175
197
|
const caps = await this.detectCapabilities();
|
|
176
198
|
if (generation !== this.connectionGeneration) return false;
|
|
177
199
|
|
|
178
|
-
//
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
200
|
+
// ABR adapts over the caller's ladder (the catalog's renditions),
|
|
201
|
+
// validated above before anything was torn down.
|
|
202
|
+
this.abr = abr;
|
|
182
203
|
|
|
183
|
-
|
|
204
|
+
abr.setCallback((quality) => {
|
|
184
205
|
this.state.quality = quality;
|
|
185
206
|
this.subscription?.onqualitychange?.(quality);
|
|
186
207
|
this.onStateChange?.(this.state);
|
|
@@ -255,7 +276,7 @@ export class TransportManager {
|
|
|
255
276
|
connected: true,
|
|
256
277
|
group: config.group,
|
|
257
278
|
source: config.source,
|
|
258
|
-
quality: this.
|
|
279
|
+
quality: this.requireAbr().getCurrentQuality(),
|
|
259
280
|
packetsReceived: 0,
|
|
260
281
|
bytesReceived: 0,
|
|
261
282
|
lastPacketTime: 0,
|
|
@@ -311,8 +332,8 @@ export class TransportManager {
|
|
|
311
332
|
private async connectAmtProxy(config: TransportConfig, amtRelay: string, generation: number): Promise<boolean> {
|
|
312
333
|
try {
|
|
313
334
|
const relayHost = amtRelay.includes(":") && !amtRelay.startsWith("[") ? `[${amtRelay}]` : amtRelay;
|
|
314
|
-
|
|
315
|
-
const wsUrl = new URL(`wss://${
|
|
335
|
+
// amtRelayPort is the relay's RFC 7450 UDP port, not the proxy's HTTPS port.
|
|
336
|
+
const wsUrl = new URL(`wss://${relayHost}/amt-proxy`);
|
|
316
337
|
wsUrl.searchParams.set("group", config.group);
|
|
317
338
|
wsUrl.searchParams.set("source", config.source || "");
|
|
318
339
|
wsUrl.searchParams.set("port", String(config.port));
|
|
@@ -347,7 +368,7 @@ export class TransportManager {
|
|
|
347
368
|
group: config.group,
|
|
348
369
|
source: config.source,
|
|
349
370
|
amtRelay,
|
|
350
|
-
quality: this.
|
|
371
|
+
quality: this.requireAbr().getCurrentQuality(),
|
|
351
372
|
packetsReceived: 0,
|
|
352
373
|
bytesReceived: 0,
|
|
353
374
|
lastPacketTime: 0,
|
|
@@ -399,7 +420,8 @@ export class TransportManager {
|
|
|
399
420
|
this.state.lastPacketTime = Date.now();
|
|
400
421
|
|
|
401
422
|
// Update ABR with sample
|
|
402
|
-
|
|
423
|
+
// Packets reach here only through a connect()ed join, which built the ABR.
|
|
424
|
+
this.abr?.recordSample(data.byteLength, 0);
|
|
403
425
|
|
|
404
426
|
// Forward to callback
|
|
405
427
|
this.onPacket?.(data);
|
|
@@ -413,17 +435,28 @@ export class TransportManager {
|
|
|
413
435
|
}
|
|
414
436
|
|
|
415
437
|
/**
|
|
416
|
-
*
|
|
438
|
+
* The rung of the live connection's `abrLadder` ABR is on; `null` when
|
|
439
|
+
* there is no ladder (before `connect(config)` succeeds, after
|
|
440
|
+
* `disconnect()`, or after a reconnect that was torn down and bailed).
|
|
417
441
|
*/
|
|
418
|
-
getQuality(): QualityLevel {
|
|
419
|
-
return this.abr
|
|
442
|
+
getQuality(): QualityLevel | null {
|
|
443
|
+
return this.abr?.getCurrentQuality() ?? null;
|
|
420
444
|
}
|
|
421
445
|
|
|
422
446
|
/**
|
|
423
|
-
* Force
|
|
447
|
+
* Force the rung named `name` of the live connection's `abrLadder`.
|
|
448
|
+
* Throws `Error` when there is no ladder (see `getQuality()`), and
|
|
449
|
+
* `RangeError` when the ladder has no rung of that name; it never falls
|
|
450
|
+
* back to another rung.
|
|
424
451
|
*/
|
|
425
452
|
setQuality(name: string) {
|
|
426
|
-
this.
|
|
453
|
+
this.requireAbr().setQuality(name);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/** The controller `connect()` built; before it there is no ladder to adapt over. */
|
|
457
|
+
private requireAbr(): AdaptiveBitrateController {
|
|
458
|
+
if (!this.abr) throw new Error("[Transport] ABR has no ladder before connect(config)");
|
|
459
|
+
return this.abr;
|
|
427
460
|
}
|
|
428
461
|
|
|
429
462
|
/** Configures multicast-only ABR while leaving the caller's MoQ ABR untouched. */
|
|
@@ -441,6 +474,10 @@ export class TransportManager {
|
|
|
441
474
|
return this.dispatchOrchestratorCommand(this.multicastOrchestrator?.onGoodput({ bitrate }));
|
|
442
475
|
}
|
|
443
476
|
|
|
477
|
+
recordMulticastHeadroom(healthy: boolean): OrchestratorCommand | undefined {
|
|
478
|
+
return this.dispatchOrchestratorCommand(this.multicastOrchestrator?.onHeadroom({ healthy }));
|
|
479
|
+
}
|
|
480
|
+
|
|
444
481
|
recordMulticastTierPacket(packet: MulticastTierPacket): OrchestratorCommand | undefined {
|
|
445
482
|
return this.dispatchOrchestratorCommand(this.multicastOrchestrator?.onTierPacket(packet));
|
|
446
483
|
}
|
|
@@ -477,6 +514,9 @@ export class TransportManager {
|
|
|
477
514
|
this.connectionGeneration++;
|
|
478
515
|
this.subscription?.close();
|
|
479
516
|
this.subscription = null;
|
|
517
|
+
// No ladder after a disconnect, as before the first connect():
|
|
518
|
+
// getQuality() is null and setQuality() throws until connect(config).
|
|
519
|
+
this.abr = null;
|
|
480
520
|
this.dispatchOrchestratorCommand(this.multicastOrchestrator?.cancel());
|
|
481
521
|
this.multicastOrchestrator = null;
|
|
482
522
|
this.onOrchestratorCommand = undefined;
|
|
@@ -498,13 +538,3 @@ export class TransportManager {
|
|
|
498
538
|
|
|
499
539
|
// Export singleton instance
|
|
500
540
|
export const transportManager = new TransportManager();
|
|
501
|
-
|
|
502
|
-
/**
|
|
503
|
-
* @deprecated Use `TransportManager` instead. This alias is preserved for backwards compatibility.
|
|
504
|
-
*/
|
|
505
|
-
export const SSMTransportManager = TransportManager;
|
|
506
|
-
|
|
507
|
-
/**
|
|
508
|
-
* @deprecated Use `transportManager` instead. This alias is preserved for backwards compatibility.
|
|
509
|
-
*/
|
|
510
|
-
export const ssmTransport = transportManager;
|
package/src/types.ts
CHANGED
|
@@ -4,29 +4,85 @@
|
|
|
4
4
|
* Shared interfaces for unified transport (WebTransport, SSM, AMT, DRIAD) and FEC.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
/**
|
|
8
|
-
export interface
|
|
7
|
+
/** Native IP-multicast request for the window.multicast gateway. */
|
|
8
|
+
export interface WindowMulticastSubscribeConfigBytes {
|
|
9
9
|
group: string;
|
|
10
10
|
port: number;
|
|
11
11
|
source?: string;
|
|
12
12
|
consume: "bytes";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
origin?: "ip-multicast";
|
|
14
|
+
/** MMTP packet IDs to forward before data enters the page transport queue. */
|
|
15
|
+
packetIds?: readonly number[];
|
|
16
|
+
/** Catalog track identities corresponding to the selected packet IDs. */
|
|
17
|
+
tracks?: ReadonlyArray<{ name: string; packetId: number }>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** AMT relay request for the window.multicast gateway. */
|
|
21
|
+
export interface WindowMulticastSubscribeConfigAmt {
|
|
22
|
+
group: string;
|
|
23
|
+
port: number;
|
|
24
|
+
source?: string;
|
|
25
|
+
consume: "bytes";
|
|
26
|
+
origin: "amt";
|
|
27
|
+
relay: string;
|
|
28
|
+
relayPort: number;
|
|
29
|
+
/** MMTP packet IDs to forward before data enters the page transport queue. */
|
|
30
|
+
packetIds?: readonly number[];
|
|
16
31
|
}
|
|
17
32
|
|
|
33
|
+
/** Canonical window.multicast config for byte subscriptions. */
|
|
34
|
+
export type WindowMulticastSubscribeConfig =
|
|
35
|
+
| WindowMulticastSubscribeConfigBytes
|
|
36
|
+
| WindowMulticastSubscribeConfigAmt;
|
|
37
|
+
|
|
18
38
|
export type WindowMulticastSubscriptionState = "connecting" | "connected" | "reconnecting" | "closed";
|
|
19
39
|
|
|
20
40
|
export interface WindowMulticastSubscriptionEventMap {
|
|
21
41
|
bytes: CustomEvent<ArrayBuffer>;
|
|
42
|
+
/**
|
|
43
|
+
* Provider extension, not part of the frozen v1 contract (BLO-36599): every
|
|
44
|
+
* packet the provider decoded in one dispatch batch, in arrival order. Each
|
|
45
|
+
* element is exactly what one `bytes` event would have carried. Emitted
|
|
46
|
+
* only by a subscription whose `eventTypes` lists `"packets"`; see
|
|
47
|
+
* {@link subscriptionDeliversPacketBatches}. A consumer listens to exactly
|
|
48
|
+
* one of `bytes` and `packets`, because both carry the same packets.
|
|
49
|
+
*/
|
|
50
|
+
packets: CustomEvent<readonly ArrayBuffer[]>;
|
|
22
51
|
state: CustomEvent<WindowMulticastSubscriptionState>;
|
|
23
52
|
error: CustomEvent<unknown>;
|
|
24
53
|
}
|
|
25
54
|
|
|
55
|
+
/**
|
|
56
|
+
* How a window.multicast subscription hands packets to the page.
|
|
57
|
+
*
|
|
58
|
+
* - `"datagram"`: packets are independent datagrams; a pause in arrivals may
|
|
59
|
+
* mean loss.
|
|
60
|
+
* - `"ordered-relay"`: the provider relays one receiving socket over a
|
|
61
|
+
* reliable, ordered stream (for example a BCDS bridge WebTransport uni
|
|
62
|
+
* stream). The relay may drop packets (a bounded queue discarding its
|
|
63
|
+
* oldest on overflow) but never reorders them: what arrives is a
|
|
64
|
+
* subsequence of the socket's receive order, so a pause in arrivals
|
|
65
|
+
* carries no loss information.
|
|
66
|
+
*
|
|
67
|
+
* A provider declares `"ordered-relay"` only when every packet of the
|
|
68
|
+
* subscription travels that one ordered stream for the life of the
|
|
69
|
+
* subscription. Absent means no ordering promise, which consumers treat as
|
|
70
|
+
* `"datagram"`.
|
|
71
|
+
*/
|
|
72
|
+
export type WindowMulticastDelivery = "datagram" | "ordered-relay";
|
|
73
|
+
|
|
26
74
|
/** window.multicast subscription returned by the IWA gateway surface. */
|
|
27
75
|
export interface WindowMulticastSubscription {
|
|
28
76
|
readonly id?: string;
|
|
29
77
|
readonly state?: WindowMulticastSubscriptionState;
|
|
78
|
+
/** How this subscription delivers packets; see WindowMulticastDelivery. */
|
|
79
|
+
readonly delivery?: WindowMulticastDelivery;
|
|
80
|
+
/**
|
|
81
|
+
* Events this subscription emits, when the provider advertises them. A
|
|
82
|
+
* provider without the field emits the v1 events only (`bytes`, `state`,
|
|
83
|
+
* `error`).
|
|
84
|
+
*/
|
|
85
|
+
readonly eventTypes?: readonly string[];
|
|
30
86
|
addEventListener<K extends keyof WindowMulticastSubscriptionEventMap>(
|
|
31
87
|
type: K,
|
|
32
88
|
listener: (ev: WindowMulticastSubscriptionEventMap[K]) => void,
|
|
@@ -75,19 +131,39 @@ export interface TransportConfig {
|
|
|
75
131
|
mode?: "auto" | "native" | "tunnel";
|
|
76
132
|
/** DRIAD discovery enabled */
|
|
77
133
|
driadEnabled?: boolean;
|
|
78
|
-
/** Initial bitrate quality level */
|
|
79
|
-
initialQuality?: QualityLevel;
|
|
80
134
|
}
|
|
81
135
|
|
|
82
|
-
/**
|
|
83
|
-
export
|
|
136
|
+
/** What `TransportManager.connect()` needs beyond the subscription itself. */
|
|
137
|
+
export interface TransportConnectConfig extends TransportConfig {
|
|
138
|
+
/**
|
|
139
|
+
* The stream's renditions the ABR controller adapts over, e.g. its catalog
|
|
140
|
+
* video tracks. Required: there is no built-in ladder. `connect()` checks
|
|
141
|
+
* it (`validateQualityLadder`) and `initialQuality` before tearing anything
|
|
142
|
+
* down; a config that fails is refused through the error callback and
|
|
143
|
+
* `connect()` resolves `false`, leaving a live subscription untouched.
|
|
144
|
+
* `catalogQualityLadder(catalog)` builds it from a catalog.
|
|
145
|
+
*/
|
|
146
|
+
abrLadder: readonly QualityLevel[];
|
|
147
|
+
/**
|
|
148
|
+
* Ladder rendition to start on, matched by `name` against `abrLadder`; the
|
|
149
|
+
* lowest-bitrate rung when omitted. A name `abrLadder` does not carry is
|
|
150
|
+
* not a fallback to that rung: `TransportManager.connect()` refuses the
|
|
151
|
+
* config (error callback, resolves `false`) before tearing anything down.
|
|
152
|
+
*/
|
|
153
|
+
initialQuality?: QualityLevel;
|
|
154
|
+
}
|
|
84
155
|
|
|
85
156
|
export interface QualityLevel {
|
|
86
157
|
name: string;
|
|
158
|
+
/** Bits per second: what the ABR controller ranks and costs a rendition by. */
|
|
87
159
|
bitrate: number;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
160
|
+
/**
|
|
161
|
+
* Dimensions and frame rate as the stream's catalog states them; absent
|
|
162
|
+
* when it does not. ABR never reads them, so none is defaulted.
|
|
163
|
+
*/
|
|
164
|
+
width?: number;
|
|
165
|
+
height?: number;
|
|
166
|
+
fps?: number;
|
|
91
167
|
}
|
|
92
168
|
|
|
93
169
|
export interface TransportCapabilities {
|
|
@@ -123,9 +199,6 @@ export interface TransportSubscription {
|
|
|
123
199
|
close: () => void;
|
|
124
200
|
}
|
|
125
201
|
|
|
126
|
-
/** @deprecated Use TransportSubscription instead */
|
|
127
|
-
export type SSMSubscription = TransportSubscription;
|
|
128
|
-
|
|
129
202
|
/** DRIAD Relay information (RFC 8777) */
|
|
130
203
|
export interface DRIADRelay {
|
|
131
204
|
host: string;
|
|
@@ -159,67 +232,6 @@ export interface PacketMetadata {
|
|
|
159
232
|
timestamp: number;
|
|
160
233
|
}
|
|
161
234
|
|
|
162
|
-
/** MFU Fragment for reassembly */
|
|
163
|
-
export interface MfuFragment {
|
|
164
|
-
mpuSequenceNumber: number;
|
|
165
|
-
fragmentationIndicator: number; // 0=complete, 1=first, 2=middle, 3=last
|
|
166
|
-
fragmentCounter: number;
|
|
167
|
-
data: Uint8Array;
|
|
168
|
-
timestamp: bigint;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/** Reassembled MFU */
|
|
172
|
-
export interface ReassembledMfu {
|
|
173
|
-
mpuSequenceNumber: number;
|
|
174
|
-
data: Uint8Array;
|
|
175
|
-
timestamp: bigint;
|
|
176
|
-
fragmentCount: number;
|
|
177
|
-
recoveredViaFec: boolean;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/** FEC + MFU processing statistics */
|
|
181
|
-
export interface ProcessingStats {
|
|
182
|
-
// MFU stats
|
|
183
|
-
mfuComplete: number;
|
|
184
|
-
mfuReassembled: number;
|
|
185
|
-
mfuTimedOut: number;
|
|
186
|
-
|
|
187
|
-
// FEC stats
|
|
188
|
-
fecSourcePackets: number;
|
|
189
|
-
fecRepairPackets: number;
|
|
190
|
-
fecBlocksComplete: number;
|
|
191
|
-
fecBlocksRecovered: number;
|
|
192
|
-
fecBlocksFailed: number;
|
|
193
|
-
fecRecoveryRate: number;
|
|
194
|
-
|
|
195
|
-
// Green-fill stats
|
|
196
|
-
greenFillFrames: number;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/** Options for MmtFecClient */
|
|
200
|
-
export interface MmtFecClientOptions {
|
|
201
|
-
/** Interleave depth for FEC (default: 30 for ~1s at 30fps) */
|
|
202
|
-
interleaveDepth?: number;
|
|
203
|
-
|
|
204
|
-
/** Block timeout in milliseconds (default: 2000) */
|
|
205
|
-
blockTimeoutMs?: number;
|
|
206
|
-
|
|
207
|
-
/** MFU timeout in milliseconds (default: 1000) */
|
|
208
|
-
mfuTimeoutMs?: number;
|
|
209
|
-
|
|
210
|
-
/** Enable green-fill for failed frames (default: true) */
|
|
211
|
-
greenFillEnabled?: boolean;
|
|
212
|
-
|
|
213
|
-
/** Callback when MFU is ready */
|
|
214
|
-
onMfuReady?: (mfu: ReassembledMfu) => void;
|
|
215
|
-
|
|
216
|
-
/** Callback for green-fill frame */
|
|
217
|
-
onGreenFill?: (timestamp: bigint) => void;
|
|
218
|
-
|
|
219
|
-
/** Callback for stats update */
|
|
220
|
-
onStatsUpdate?: (stats: ProcessingStats) => void;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
235
|
/** Time types (microseconds) */
|
|
224
236
|
export type Micro = number & { readonly __micro: unique symbol };
|
|
225
237
|
|
|
@@ -279,38 +291,11 @@ export interface MulticastSubscriptionHandle {
|
|
|
279
291
|
close(): Promise<void>;
|
|
280
292
|
}
|
|
281
293
|
|
|
282
|
-
/** FEC Configuration for MMT streams */
|
|
283
|
-
export interface FecConfig {
|
|
284
|
-
enabled?: boolean;
|
|
285
|
-
oti?: Uint8Array;
|
|
286
|
-
interleaveDepth?: number;
|
|
287
|
-
blockTimeoutMs?: number;
|
|
288
|
-
greenFillEnabled?: boolean;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/** FEC Statistics */
|
|
292
|
-
export interface FecStats {
|
|
293
|
-
sourcePackets: number;
|
|
294
|
-
repairPackets: number;
|
|
295
|
-
blocksComplete: number;
|
|
296
|
-
blocksRecovered: number;
|
|
297
|
-
blocksFailed: number;
|
|
298
|
-
recoveryRate: number;
|
|
299
|
-
greenFillFrames: number;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/** Extended MulticastConfig with FEC support */
|
|
303
|
-
export interface MulticastConfigWithFec extends MulticastConfig {
|
|
304
|
-
fec?: FecConfig;
|
|
305
|
-
onFecStats?: (stats: FecStats) => void;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
294
|
/** Main Multicast Bridge API */
|
|
309
295
|
export interface MulticastBridge {
|
|
310
|
-
subscribe(config: MulticastConfig
|
|
296
|
+
subscribe(config: MulticastConfig): Promise<MulticastSubscriptionHandle>;
|
|
311
297
|
getSubscriptions(): Promise<MulticastSubscriptionHandle[]>;
|
|
312
298
|
isNativeSupported(): Promise<boolean>;
|
|
313
|
-
getFecStats?(): FecStats | null;
|
|
314
299
|
}
|
|
315
300
|
|
|
316
301
|
// ============================================================
|
|
@@ -318,22 +303,17 @@ export interface MulticastBridge {
|
|
|
318
303
|
// ============================================================
|
|
319
304
|
|
|
320
305
|
/**
|
|
321
|
-
*
|
|
322
|
-
*
|
|
323
|
-
*/
|
|
324
|
-
export const FEC_CONFIG_MESSAGE_TYPE = 0x50;
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Repair track suffix per draft-ramadan-moq-fec-00 Section 5.1
|
|
328
|
-
* Repair tracks are named by appending this suffix to the source track path
|
|
329
|
-
*/
|
|
330
|
-
export const REPAIR_TRACK_SUFFIX = "repair";
|
|
331
|
-
|
|
332
|
-
/**
|
|
333
|
-
* Get the repair track path for a given source track
|
|
306
|
+
* Build a conventional repair track path by appending "repair" to a source
|
|
307
|
+
* track path.
|
|
334
308
|
*
|
|
335
|
-
*
|
|
336
|
-
*
|
|
309
|
+
* Track names carry no protocol meaning. A repair track's layer, its source
|
|
310
|
+
* and its symbol budget are resolved from the catalog via `resolveRepairLayers`
|
|
311
|
+
* in `@blockcast/mmt-container`, never from a name or a name suffix. This
|
|
312
|
+
* helper is a naming convenience for producers only.
|
|
313
|
+
*
|
|
314
|
+
* @deprecated Naming convenience only. Never classify a track or resolve a
|
|
315
|
+
* repair layer from its path; use `resolveRepairLayers` from
|
|
316
|
+
* `@blockcast/mmt-container`.
|
|
337
317
|
*
|
|
338
318
|
* @param sourceTrack - The source track path (e.g., "live/video")
|
|
339
319
|
* @returns The repair track path (e.g., "live/video/repair")
|
|
@@ -344,53 +324,16 @@ export const REPAIR_TRACK_SUFFIX = "repair";
|
|
|
344
324
|
*/
|
|
345
325
|
export function repairTrackPath(sourceTrack: string): string {
|
|
346
326
|
if (!sourceTrack || sourceTrack.length === 0) {
|
|
347
|
-
return
|
|
348
|
-
}
|
|
349
|
-
return `${sourceTrack}/${REPAIR_TRACK_SUFFIX}`;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* Check if a track path is a repair track
|
|
354
|
-
*
|
|
355
|
-
* @param path - The track path to check
|
|
356
|
-
* @returns True if the path ends with the repair track suffix
|
|
357
|
-
*
|
|
358
|
-
* @example
|
|
359
|
-
* isRepairTrack("live/video/repair") // returns true
|
|
360
|
-
* isRepairTrack("repair") // returns true
|
|
361
|
-
* isRepairTrack("live/video") // returns false
|
|
362
|
-
*/
|
|
363
|
-
export function isRepairTrack(path: string): boolean {
|
|
364
|
-
return path === REPAIR_TRACK_SUFFIX || path.endsWith(`/${REPAIR_TRACK_SUFFIX}`);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
/**
|
|
368
|
-
* Get the source track path from a repair track path
|
|
369
|
-
*
|
|
370
|
-
* @param repairTrack - The repair track path
|
|
371
|
-
* @returns The source track path, or null if not a valid repair track
|
|
372
|
-
*
|
|
373
|
-
* @example
|
|
374
|
-
* sourceTrackFromRepair("live/video/repair") // returns "live/video"
|
|
375
|
-
* sourceTrackFromRepair("repair") // returns ""
|
|
376
|
-
* sourceTrackFromRepair("live/video") // returns null
|
|
377
|
-
*/
|
|
378
|
-
export function sourceTrackFromRepair(repairTrack: string): string | null {
|
|
379
|
-
if (repairTrack === REPAIR_TRACK_SUFFIX) {
|
|
380
|
-
return "";
|
|
381
|
-
}
|
|
382
|
-
const suffix = `/${REPAIR_TRACK_SUFFIX}`;
|
|
383
|
-
if (repairTrack.endsWith(suffix)) {
|
|
384
|
-
return repairTrack.slice(0, -suffix.length);
|
|
327
|
+
return "repair";
|
|
385
328
|
}
|
|
386
|
-
return
|
|
329
|
+
return `${sourceTrack}/repair`;
|
|
387
330
|
}
|
|
388
331
|
|
|
389
332
|
/**
|
|
390
333
|
* MoQ FEC Configuration per draft-ramadan-moq-fec-00
|
|
391
334
|
*
|
|
392
|
-
*
|
|
393
|
-
*
|
|
335
|
+
* These parameters are signalled in each track's catalog `fec` block;
|
|
336
|
+
* no FEC_CONFIG control message is sent or parsed.
|
|
394
337
|
*/
|
|
395
338
|
export interface MoqFecConfig {
|
|
396
339
|
/** Subscribe ID this config applies to */
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"endpoint-config.test.d.ts","sourceRoot":"","sources":["../src/endpoint-config.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { isMulticastExplicitlyDisabled, multicastEndpointKey, normalizeMulticastEndpoint, readUrlMulticastConfig, } from "./endpoint-config.js";
|
|
3
|
-
describe("multicast endpoint config", () => {
|
|
4
|
-
it("normalizes endpoint identity", () => {
|
|
5
|
-
const endpoint = normalizeMulticastEndpoint({
|
|
6
|
-
groupAddress: " 232.99.0.10 ",
|
|
7
|
-
sourceAddress: " 69.25.95.101 ",
|
|
8
|
-
port: 5050,
|
|
9
|
-
});
|
|
10
|
-
expect(endpoint).toEqual({
|
|
11
|
-
groupAddress: "232.99.0.10",
|
|
12
|
-
sourceAddress: "69.25.95.101",
|
|
13
|
-
port: 5050,
|
|
14
|
-
});
|
|
15
|
-
expect(multicastEndpointKey(endpoint)).toBe('["69.25.95.101","232.99.0.10",5050,"auto","",""]');
|
|
16
|
-
});
|
|
17
|
-
it("does not alias endpoint fields containing key delimiters", () => {
|
|
18
|
-
const base = { groupAddress: "232.99.0.10", port: 5050, mode: "tunnel" };
|
|
19
|
-
expect(multicastEndpointKey({ ...base, amtRelay: "relay|2268" })).not.toBe(multicastEndpointKey({ ...base, amtRelay: "relay", amtRelayPort: 2268 }));
|
|
20
|
-
});
|
|
21
|
-
it("rejects invalid groups and ports", () => {
|
|
22
|
-
expect(normalizeMulticastEndpoint({ groupAddress: "127.0.0.1", port: 5050 })).toBeUndefined();
|
|
23
|
-
expect(normalizeMulticastEndpoint({ groupAddress: "232.1.1.1", port: 0 })).toBeUndefined();
|
|
24
|
-
});
|
|
25
|
-
it("preserves explicit AMT URL configuration", () => {
|
|
26
|
-
expect(readUrlMulticastConfig("?mc_group=232.99.0.10&mc_source=69.25.95.101&mc_port=5050&mc_amt_relay=relay.example&mc_amt_relay_port=2268")).toEqual({
|
|
27
|
-
groupAddress: "232.99.0.10",
|
|
28
|
-
sourceAddress: "69.25.95.101",
|
|
29
|
-
port: 5050,
|
|
30
|
-
mode: "tunnel",
|
|
31
|
-
amtRelay: "relay.example",
|
|
32
|
-
amtRelayPort: 2268,
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
it("recognizes explicit MoQ-only selection", () => {
|
|
36
|
-
expect(isMulticastExplicitlyDisabled("?transport=moq-only")).toBe(true);
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
//# sourceMappingURL=endpoint-config.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"endpoint-config.test.js","sourceRoot":"","sources":["../src/endpoint-config.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EACN,6BAA6B,EAC7B,oBAAoB,EACpB,0BAA0B,EAC1B,sBAAsB,GACtB,MAAM,sBAAsB,CAAC;AAE9B,QAAQ,CAAC,2BAA2B,EAAE,GAAG,EAAE;IAC1C,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACvC,MAAM,QAAQ,GAAG,0BAA0B,CAAC;YAC3C,YAAY,EAAE,eAAe;YAC7B,aAAa,EAAE,gBAAgB;YAC/B,IAAI,EAAE,IAAI;SACV,CAAC,CAAC;QACH,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC;YACxB,YAAY,EAAE,aAAa;YAC3B,aAAa,EAAE,cAAc;YAC7B,IAAI,EAAE,IAAI;SACV,CAAC,CAAC;QACH,MAAM,CAAC,oBAAoB,CAAC,QAAS,CAAC,CAAC,CAAC,IAAI,CAC3C,kDAAkD,CAClD,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QACnE,MAAM,IAAI,GAAG,EAAE,YAAY,EAAE,aAAa,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,QAAiB,EAAE,CAAC;QAClF,MAAM,CAAC,oBAAoB,CAAC,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CACzE,oBAAoB,CAAC,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CACxE,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC3C,MAAM,CAAC,0BAA0B,CAAC,EAAE,YAAY,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QAC9F,MAAM,CAAC,0BAA0B,CAAC,EAAE,YAAY,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IAC5F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QACnD,MAAM,CAAC,sBAAsB,CAC5B,6GAA6G,CAC7G,CAAC,CAAC,OAAO,CAAC;YACV,YAAY,EAAE,aAAa;YAC3B,aAAa,EAAE,cAAc;YAC7B,IAAI,EAAE,IAAI;YACV,IAAI,EAAE,QAAQ;YACd,QAAQ,EAAE,eAAe;YACzB,YAAY,EAAE,IAAI;SAClB,CAAC,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QACjD,MAAM,CAAC,6BAA6B,CAAC,qBAAqB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC"}
|