@blockcast/mmt-transport 0.2.0 → 0.2.2
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 +264 -0
- package/dist/.build-stamp +0 -0
- package/dist/abr-controller.d.ts +3 -0
- package/dist/abr-controller.d.ts.map +1 -1
- package/dist/abr-controller.js +5 -0
- package/dist/abr-controller.js.map +1 -1
- package/dist/amt-gateway.d.ts.map +1 -1
- package/dist/amt-gateway.js +2 -1
- package/dist/amt-gateway.js.map +1 -1
- package/dist/endpoint-config.d.ts +20 -0
- package/dist/endpoint-config.d.ts.map +1 -0
- package/dist/endpoint-config.js +103 -0
- package/dist/endpoint-config.js.map +1 -0
- package/dist/endpoint-config.test.d.ts +2 -0
- package/dist/endpoint-config.test.d.ts.map +1 -0
- package/dist/endpoint-config.test.js +39 -0
- package/dist/endpoint-config.test.js.map +1 -0
- package/dist/fec-client.d.ts +2 -2
- package/dist/fec-client.d.ts.map +1 -1
- package/dist/fec-client.js +10 -5
- package/dist/fec-client.js.map +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/mmt-transport.no-modules.js +1154 -0
- package/dist/mmt-transport.no-modules.js.map +7 -0
- package/dist/multicast-orchestrator.d.ts +56 -0
- package/dist/multicast-orchestrator.d.ts.map +1 -0
- package/dist/multicast-orchestrator.js +123 -0
- package/dist/multicast-orchestrator.js.map +1 -0
- package/dist/multicast-subscription-pool.d.ts +18 -0
- package/dist/multicast-subscription-pool.d.ts.map +1 -0
- package/dist/multicast-subscription-pool.js +130 -0
- package/dist/multicast-subscription-pool.js.map +1 -0
- package/dist/multicast-subscription-pool.test.d.ts +2 -0
- package/dist/multicast-subscription-pool.test.d.ts.map +1 -0
- package/dist/multicast-subscription-pool.test.js +108 -0
- package/dist/multicast-subscription-pool.test.js.map +1 -0
- package/dist/multicast-tier.d.ts +14 -0
- package/dist/multicast-tier.d.ts.map +1 -0
- package/dist/multicast-tier.js +22 -0
- package/dist/multicast-tier.js.map +1 -0
- package/dist/multicast-tier.test.d.ts +2 -0
- package/dist/multicast-tier.test.d.ts.map +1 -0
- package/dist/multicast-tier.test.js +75 -0
- package/dist/multicast-tier.test.js.map +1 -0
- package/dist/no-modules.d.ts +29 -0
- package/dist/no-modules.d.ts.map +1 -0
- package/dist/no-modules.js +35 -0
- package/dist/no-modules.js.map +1 -0
- package/dist/stall-detector.d.ts +22 -0
- package/dist/stall-detector.d.ts.map +1 -0
- package/dist/stall-detector.js +46 -0
- package/dist/stall-detector.js.map +1 -0
- package/dist/stall-detector.test.d.ts +2 -0
- package/dist/stall-detector.test.d.ts.map +1 -0
- package/dist/stall-detector.test.js +33 -0
- package/dist/stall-detector.test.js.map +1 -0
- package/dist/takeover.d.ts +49 -0
- package/dist/takeover.d.ts.map +1 -0
- package/dist/takeover.js +43 -0
- package/dist/takeover.js.map +1 -0
- package/dist/transport-manager.d.ts +28 -18
- package/dist/transport-manager.d.ts.map +1 -1
- package/dist/transport-manager.js +192 -138
- package/dist/transport-manager.js.map +1 -1
- package/dist/transport-manager.test.d.ts +2 -0
- package/dist/transport-manager.test.d.ts.map +1 -0
- package/dist/transport-manager.test.js +155 -0
- package/dist/transport-manager.test.js.map +1 -0
- package/dist/types.d.ts +36 -36
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/unicast-fallback.d.ts +36 -0
- package/dist/unicast-fallback.d.ts.map +1 -0
- package/dist/unicast-fallback.js +53 -0
- package/dist/unicast-fallback.js.map +1 -0
- package/package.json +6 -6
- package/src/abr-controller.ts +9 -0
- package/src/amt-gateway.ts +2 -1
- package/src/endpoint-config.test.ts +54 -0
- package/src/endpoint-config.ts +121 -0
- package/src/fec-client.ts +11 -5
- package/src/index.ts +72 -2
- package/src/multicast-orchestrator.ts +150 -0
- package/src/multicast-subscription-pool.test.ts +115 -0
- package/src/multicast-subscription-pool.ts +164 -0
- package/src/multicast-tier.test.ts +80 -0
- package/src/multicast-tier.ts +32 -0
- package/src/no-modules.ts +44 -0
- package/src/stall-detector.test.ts +34 -0
- package/src/stall-detector.ts +63 -0
- package/src/takeover.ts +71 -0
- package/src/transport-manager.test.ts +192 -0
- package/src/transport-manager.ts +203 -153
- package/src/types.ts +50 -31
- package/src/unicast-fallback.ts +57 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { MulticastSubscriptionPool } from "./multicast-subscription-pool.js";
|
|
3
|
+
const config = {
|
|
4
|
+
group: "232.1.1.1",
|
|
5
|
+
source: "192.0.2.1",
|
|
6
|
+
port: 5000,
|
|
7
|
+
streamType: "video",
|
|
8
|
+
};
|
|
9
|
+
describe("MulticastSubscriptionPool", () => {
|
|
10
|
+
it("shares an endpoint and unsubscribes only after the last owner releases", async () => {
|
|
11
|
+
const listeners = new Map();
|
|
12
|
+
const unsubscribe = vi.fn();
|
|
13
|
+
const subscription = {
|
|
14
|
+
addEventListener: vi.fn((type, listener) => listeners.set(type, listener)),
|
|
15
|
+
removeEventListener: vi.fn(),
|
|
16
|
+
unsubscribe,
|
|
17
|
+
};
|
|
18
|
+
const subscribe = vi.fn().mockResolvedValue(subscription);
|
|
19
|
+
const gateway = { subscribe };
|
|
20
|
+
const pool = new MulticastSubscriptionPool();
|
|
21
|
+
const firstPacket = vi.fn();
|
|
22
|
+
const secondPacket = vi.fn();
|
|
23
|
+
const first = await pool.acquire(gateway, config, { onBytes: firstPacket });
|
|
24
|
+
const second = await pool.acquire(gateway, { ...config, group: " 232.1.1.1 " }, { onBytes: secondPacket });
|
|
25
|
+
expect(subscribe).toHaveBeenCalledOnce();
|
|
26
|
+
const payload = new Uint8Array([1, 2]).buffer;
|
|
27
|
+
listeners.get("bytes")?.({ detail: payload });
|
|
28
|
+
expect(firstPacket).toHaveBeenCalledWith(payload);
|
|
29
|
+
expect(secondPacket).toHaveBeenCalledWith(payload);
|
|
30
|
+
await first.release();
|
|
31
|
+
expect(unsubscribe).not.toHaveBeenCalled();
|
|
32
|
+
await second.release();
|
|
33
|
+
expect(unsubscribe).toHaveBeenCalledOnce();
|
|
34
|
+
expect(pool.size).toBe(0);
|
|
35
|
+
});
|
|
36
|
+
it("counts an awaiting acquire before the previous owner releases", async () => {
|
|
37
|
+
const unsubscribe = vi.fn();
|
|
38
|
+
const subscription = {
|
|
39
|
+
addEventListener: vi.fn(), removeEventListener: vi.fn(), unsubscribe,
|
|
40
|
+
};
|
|
41
|
+
const subscribe = vi.fn().mockResolvedValue(subscription);
|
|
42
|
+
const gateway = { subscribe };
|
|
43
|
+
const pool = new MulticastSubscriptionPool();
|
|
44
|
+
const first = await pool.acquire(gateway, config, { onBytes: vi.fn() });
|
|
45
|
+
const secondPending = pool.acquire(gateway, config, { onBytes: vi.fn() });
|
|
46
|
+
await first.release();
|
|
47
|
+
const second = await secondPending;
|
|
48
|
+
expect(unsubscribe).not.toHaveBeenCalled();
|
|
49
|
+
expect(pool.size).toBe(1);
|
|
50
|
+
await second.release();
|
|
51
|
+
expect(unsubscribe).toHaveBeenCalledOnce();
|
|
52
|
+
});
|
|
53
|
+
it("does not conflate transport preferences or gateway instances", async () => {
|
|
54
|
+
const makeSubscription = () => ({
|
|
55
|
+
addEventListener: vi.fn(), removeEventListener: vi.fn(), unsubscribe: vi.fn(),
|
|
56
|
+
});
|
|
57
|
+
const firstGateway = { subscribe: vi.fn().mockResolvedValue(makeSubscription()) };
|
|
58
|
+
const secondGateway = { subscribe: vi.fn().mockResolvedValue(makeSubscription()) };
|
|
59
|
+
const pool = new MulticastSubscriptionPool();
|
|
60
|
+
const native = await pool.acquire(firstGateway, { ...config, mode: "native" }, { onBytes: vi.fn() });
|
|
61
|
+
const tunnel = await pool.acquire(firstGateway, { ...config, mode: "tunnel", amtRelay: "relay.example" }, { onBytes: vi.fn() });
|
|
62
|
+
const replacement = await pool.acquire(secondGateway, { ...config, mode: "native" }, { onBytes: vi.fn() });
|
|
63
|
+
expect(firstGateway.subscribe).toHaveBeenCalledTimes(2);
|
|
64
|
+
expect(secondGateway.subscribe).toHaveBeenCalledOnce();
|
|
65
|
+
await Promise.all([native.release(), tunnel.release(), replacement.release()]);
|
|
66
|
+
});
|
|
67
|
+
it("evicts a closed entry so the next owner reconnects", async () => {
|
|
68
|
+
const stateListeners = [];
|
|
69
|
+
const subscribe = vi.fn().mockImplementation(async () => ({
|
|
70
|
+
addEventListener: vi.fn((type, listener) => {
|
|
71
|
+
if (type === "state")
|
|
72
|
+
stateListeners.push(listener);
|
|
73
|
+
}),
|
|
74
|
+
removeEventListener: vi.fn(),
|
|
75
|
+
unsubscribe: vi.fn(),
|
|
76
|
+
}));
|
|
77
|
+
const gateway = { subscribe };
|
|
78
|
+
const pool = new MulticastSubscriptionPool();
|
|
79
|
+
const first = await pool.acquire(gateway, config, { onBytes: vi.fn() });
|
|
80
|
+
stateListeners[0]({ detail: "closed" });
|
|
81
|
+
expect(pool.size).toBe(0);
|
|
82
|
+
const second = await pool.acquire(gateway, config, { onBytes: vi.fn() });
|
|
83
|
+
expect(subscribe).toHaveBeenCalledTimes(2);
|
|
84
|
+
await first.release();
|
|
85
|
+
await second.release();
|
|
86
|
+
});
|
|
87
|
+
it("isolates consumer callback failures", async () => {
|
|
88
|
+
let bytesListener;
|
|
89
|
+
const subscribe = vi.fn().mockResolvedValue({
|
|
90
|
+
addEventListener: vi.fn((type, listener) => {
|
|
91
|
+
if (type === "bytes")
|
|
92
|
+
bytesListener = listener;
|
|
93
|
+
}),
|
|
94
|
+
removeEventListener: vi.fn(), unsubscribe: vi.fn(),
|
|
95
|
+
});
|
|
96
|
+
const gateway = { subscribe };
|
|
97
|
+
const pool = new MulticastSubscriptionPool();
|
|
98
|
+
const secondConsumer = vi.fn();
|
|
99
|
+
const first = await pool.acquire(gateway, config, { onBytes: () => { throw new Error("boom"); } });
|
|
100
|
+
const second = await pool.acquire(gateway, config, { onBytes: secondConsumer });
|
|
101
|
+
const consoleError = vi.spyOn(console, "error").mockImplementation(() => { });
|
|
102
|
+
bytesListener?.({ detail: new ArrayBuffer(1) });
|
|
103
|
+
expect(secondConsumer).toHaveBeenCalledOnce();
|
|
104
|
+
consoleError.mockRestore();
|
|
105
|
+
await Promise.all([first.release(), second.release()]);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
//# sourceMappingURL=multicast-subscription-pool.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multicast-subscription-pool.test.js","sourceRoot":"","sources":["../src/multicast-subscription-pool.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface MulticastTierEndpoint {
|
|
2
|
+
sourceAddress?: string;
|
|
3
|
+
groupAddress: string;
|
|
4
|
+
port: number;
|
|
5
|
+
tracks: {
|
|
6
|
+
name: string;
|
|
7
|
+
packetId?: number;
|
|
8
|
+
}[];
|
|
9
|
+
bandwidth?: number;
|
|
10
|
+
protocol?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function endpointKey(endpoint: MulticastTierEndpoint): string;
|
|
13
|
+
export declare function selectMulticastTier(endpoints: readonly MulticastTierEndpoint[], estimate: number | undefined): MulticastTierEndpoint | undefined;
|
|
14
|
+
//# sourceMappingURL=multicast-tier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multicast-tier.d.ts","sourceRoot":"","sources":["../src/multicast-tier.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,qBAAqB;IACrC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC9C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,qBAAqB,GAAG,MAAM,CAEnE;AAED,wBAAgB,mBAAmB,CAClC,SAAS,EAAE,SAAS,qBAAqB,EAAE,EAC3C,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC1B,qBAAqB,GAAG,SAAS,CAenC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export function endpointKey(endpoint) {
|
|
2
|
+
return `${endpoint.sourceAddress ?? ""}|${endpoint.groupAddress}|${endpoint.port}`;
|
|
3
|
+
}
|
|
4
|
+
export function selectMulticastTier(endpoints, estimate) {
|
|
5
|
+
if (endpoints.length <= 1)
|
|
6
|
+
return endpoints[0];
|
|
7
|
+
const ranked = endpoints
|
|
8
|
+
.filter((endpoint) => typeof endpoint.bandwidth === "number" && endpoint.bandwidth > 0)
|
|
9
|
+
.sort((a, b) => a.bandwidth - b.bandwidth);
|
|
10
|
+
if (ranked.length < 2)
|
|
11
|
+
return endpoints[0];
|
|
12
|
+
if (estimate === undefined)
|
|
13
|
+
return ranked[0];
|
|
14
|
+
const budget = estimate * 0.8;
|
|
15
|
+
let selected;
|
|
16
|
+
for (const endpoint of ranked) {
|
|
17
|
+
if (endpoint.bandwidth <= budget)
|
|
18
|
+
selected = endpoint;
|
|
19
|
+
}
|
|
20
|
+
return selected ?? ranked[0];
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=multicast-tier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multicast-tier.js","sourceRoot":"","sources":["../src/multicast-tier.ts"],"names":[],"mappings":"AASA,MAAM,UAAU,WAAW,CAAC,QAA+B;IAC1D,OAAO,GAAG,QAAQ,CAAC,aAAa,IAAI,EAAE,IAAI,QAAQ,CAAC,YAAY,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC;AACpF,CAAC;AAED,MAAM,UAAU,mBAAmB,CAClC,SAA2C,EAC3C,QAA4B;IAE5B,IAAI,SAAS,CAAC,MAAM,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC;IAE/C,MAAM,MAAM,GAAG,SAAS;SACtB,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,OAAO,QAAQ,CAAC,SAAS,KAAK,QAAQ,IAAI,QAAQ,CAAC,SAAS,GAAG,CAAC,CAAC;SACtF,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAU,GAAG,CAAC,CAAC,SAAU,CAAC,CAAC;IAC9C,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC;IAC3C,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;IAE7C,MAAM,MAAM,GAAG,QAAQ,GAAG,GAAG,CAAC;IAC9B,IAAI,QAA2C,CAAC;IAChD,KAAK,MAAM,QAAQ,IAAI,MAAM,EAAE,CAAC;QAC/B,IAAI,QAAQ,CAAC,SAAU,IAAI,MAAM;YAAE,QAAQ,GAAG,QAAQ,CAAC;IACxD,CAAC;IACD,OAAO,QAAQ,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC;AAC9B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multicast-tier.test.d.ts","sourceRoot":"","sources":["../src/multicast-tier.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { MulticastOrchestrator } from "./multicast-orchestrator.js";
|
|
3
|
+
import { endpointKey, selectMulticastTier } from "./multicast-tier.js";
|
|
4
|
+
const low = {
|
|
5
|
+
groupAddress: "232.1.1.1", port: 5000, tracks: [{ name: "video/720p" }], bandwidth: 2_000_000,
|
|
6
|
+
};
|
|
7
|
+
const high = {
|
|
8
|
+
groupAddress: "232.1.1.2", port: 5000, tracks: [{ name: "video/4k" }], bandwidth: 15_000_000,
|
|
9
|
+
};
|
|
10
|
+
describe("multicast tier selection", () => {
|
|
11
|
+
it("starts conservatively and applies headroom", () => {
|
|
12
|
+
expect(selectMulticastTier([high, low], undefined)).toBe(low);
|
|
13
|
+
expect(selectMulticastTier([low, high], 16_000_000)).toBe(low);
|
|
14
|
+
expect(selectMulticastTier([low, high], 20_000_000)).toBe(high);
|
|
15
|
+
});
|
|
16
|
+
it("switches make-before-break only on a valid target RAP", () => {
|
|
17
|
+
let now = 0;
|
|
18
|
+
const orchestrator = new MulticastOrchestrator({
|
|
19
|
+
endpoints: [low, high], now: () => now, dwellMs: 1000, joinDeadlineMs: 5000,
|
|
20
|
+
});
|
|
21
|
+
expect(orchestrator.start()).toBe(low);
|
|
22
|
+
expect(orchestrator.onGoodput({ bitrate: 20_000_000 })).toBeUndefined();
|
|
23
|
+
now = 1000;
|
|
24
|
+
expect(orchestrator.onGoodput({ bitrate: 20_000_000 })?.kind).toBe("join-target");
|
|
25
|
+
expect(orchestrator.onTierPacket({
|
|
26
|
+
endpointKey: endpointKey(high), keyframe: false, timestamp: 10, hasValidCodecConfig: true,
|
|
27
|
+
})).toBeUndefined();
|
|
28
|
+
expect(orchestrator.onTierPacket({
|
|
29
|
+
endpointKey: endpointKey(high), keyframe: true, timestamp: 11, hasValidCodecConfig: true,
|
|
30
|
+
})?.kind).toBe("commit-swap");
|
|
31
|
+
});
|
|
32
|
+
it("preserves a pinned initial tier", () => {
|
|
33
|
+
let now = 0;
|
|
34
|
+
const orchestrator = new MulticastOrchestrator({
|
|
35
|
+
endpoints: [low, high], initialEndpoint: high, adaptive: false,
|
|
36
|
+
now: () => now, dwellMs: 1000, joinDeadlineMs: 5000,
|
|
37
|
+
});
|
|
38
|
+
expect(orchestrator.start()).toBe(high);
|
|
39
|
+
expect(orchestrator.onGoodput({ bitrate: 1_000_000 })).toBeUndefined();
|
|
40
|
+
now = 10_000;
|
|
41
|
+
expect(orchestrator.onGoodput({ bitrate: 1_000_000 })).toBeUndefined();
|
|
42
|
+
expect(orchestrator.current).toBe(high);
|
|
43
|
+
});
|
|
44
|
+
it("abandons a silent target when polled after the join deadline", () => {
|
|
45
|
+
let now = 0;
|
|
46
|
+
const orchestrator = new MulticastOrchestrator({
|
|
47
|
+
endpoints: [low, high], now: () => now, dwellMs: 1000, joinDeadlineMs: 5000,
|
|
48
|
+
});
|
|
49
|
+
orchestrator.start();
|
|
50
|
+
orchestrator.onGoodput({ bitrate: 20_000_000 });
|
|
51
|
+
now = 1000;
|
|
52
|
+
orchestrator.onGoodput({ bitrate: 20_000_000 });
|
|
53
|
+
now = 6000;
|
|
54
|
+
expect(orchestrator.poll()?.kind).toBe("abandon-join");
|
|
55
|
+
expect(orchestrator.state).toBe("stable");
|
|
56
|
+
});
|
|
57
|
+
it("rejects a target RAP older than the active tier high-water mark", () => {
|
|
58
|
+
let now = 0;
|
|
59
|
+
const orchestrator = new MulticastOrchestrator({
|
|
60
|
+
endpoints: [low, high], now: () => now, dwellMs: 1000, joinDeadlineMs: 5000,
|
|
61
|
+
});
|
|
62
|
+
orchestrator.start();
|
|
63
|
+
orchestrator.onTierPacket({
|
|
64
|
+
endpointKey: endpointKey(low), keyframe: false, timestamp: 10_000, hasValidCodecConfig: true,
|
|
65
|
+
});
|
|
66
|
+
orchestrator.onGoodput({ bitrate: 20_000_000 });
|
|
67
|
+
now = 1000;
|
|
68
|
+
orchestrator.onGoodput({ bitrate: 20_000_000 });
|
|
69
|
+
expect(orchestrator.onTierPacket({
|
|
70
|
+
endpointKey: endpointKey(high), keyframe: true, timestamp: 9_000, hasValidCodecConfig: true,
|
|
71
|
+
})).toBeUndefined();
|
|
72
|
+
expect(orchestrator.state).toBe("joining");
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
//# sourceMappingURL=multicast-tier.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multicast-tier.test.js","sourceRoot":"","sources":["../src/multicast-tier.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { AdaptiveBitrateController } from "./abr-controller.js";
|
|
2
|
+
import { isMulticastExplicitlyDisabled, isUsableMulticastEndpoint, isValidMulticastGroupAddress, multicastEndpointKey, normalizeMulticastEndpoint, readUrlMulticastConfig } from "./endpoint-config.js";
|
|
3
|
+
import { MulticastOrchestrator } from "./multicast-orchestrator.js";
|
|
4
|
+
import { MulticastSubscriptionPool } from "./multicast-subscription-pool.js";
|
|
5
|
+
import { endpointKey, selectMulticastTier } from "./multicast-tier.js";
|
|
6
|
+
import { TransportStallDetector } from "./stall-detector.js";
|
|
7
|
+
import { makeSourceSwitchHandler, wireStarvationDetector } from "./takeover.js";
|
|
8
|
+
import { TransportManager } from "./transport-manager.js";
|
|
9
|
+
import { runUnicastFallback } from "./unicast-fallback.js";
|
|
10
|
+
declare const api: {
|
|
11
|
+
AdaptiveBitrateController: typeof AdaptiveBitrateController;
|
|
12
|
+
MulticastOrchestrator: typeof MulticastOrchestrator;
|
|
13
|
+
MulticastSubscriptionPool: typeof MulticastSubscriptionPool;
|
|
14
|
+
TransportManager: typeof TransportManager;
|
|
15
|
+
TransportStallDetector: typeof TransportStallDetector;
|
|
16
|
+
endpointKey: typeof endpointKey;
|
|
17
|
+
isMulticastExplicitlyDisabled: typeof isMulticastExplicitlyDisabled;
|
|
18
|
+
isUsableMulticastEndpoint: typeof isUsableMulticastEndpoint;
|
|
19
|
+
isValidMulticastGroupAddress: typeof isValidMulticastGroupAddress;
|
|
20
|
+
makeSourceSwitchHandler: typeof makeSourceSwitchHandler;
|
|
21
|
+
multicastEndpointKey: typeof multicastEndpointKey;
|
|
22
|
+
normalizeMulticastEndpoint: typeof normalizeMulticastEndpoint;
|
|
23
|
+
readUrlMulticastConfig: typeof readUrlMulticastConfig;
|
|
24
|
+
runUnicastFallback: typeof runUnicastFallback;
|
|
25
|
+
selectMulticastTier: typeof selectMulticastTier;
|
|
26
|
+
wireStarvationDetector: typeof wireStarvationDetector;
|
|
27
|
+
};
|
|
28
|
+
export default api;
|
|
29
|
+
//# sourceMappingURL=no-modules.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-modules.d.ts","sourceRoot":"","sources":["../src/no-modules.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,EACN,6BAA6B,EAC7B,yBAAyB,EACzB,4BAA4B,EAC5B,oBAAoB,EACpB,0BAA0B,EAC1B,sBAAsB,EACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,EAAE,yBAAyB,EAAE,MAAM,kCAAkC,CAAC;AAC7E,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAChF,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAE3D,QAAA,MAAM,GAAG;;;;;;;;;;;;;;;;;CAiBR,CAAC;AASF,eAAe,GAAG,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { AdaptiveBitrateController } from "./abr-controller.js";
|
|
2
|
+
import { isMulticastExplicitlyDisabled, isUsableMulticastEndpoint, isValidMulticastGroupAddress, multicastEndpointKey, normalizeMulticastEndpoint, readUrlMulticastConfig, } from "./endpoint-config.js";
|
|
3
|
+
import { MulticastOrchestrator } from "./multicast-orchestrator.js";
|
|
4
|
+
import { MulticastSubscriptionPool } from "./multicast-subscription-pool.js";
|
|
5
|
+
import { endpointKey, selectMulticastTier } from "./multicast-tier.js";
|
|
6
|
+
import { TransportStallDetector } from "./stall-detector.js";
|
|
7
|
+
import { makeSourceSwitchHandler, wireStarvationDetector } from "./takeover.js";
|
|
8
|
+
import { TransportManager } from "./transport-manager.js";
|
|
9
|
+
import { runUnicastFallback } from "./unicast-fallback.js";
|
|
10
|
+
const api = {
|
|
11
|
+
AdaptiveBitrateController,
|
|
12
|
+
MulticastOrchestrator,
|
|
13
|
+
MulticastSubscriptionPool,
|
|
14
|
+
TransportManager,
|
|
15
|
+
TransportStallDetector,
|
|
16
|
+
endpointKey,
|
|
17
|
+
isMulticastExplicitlyDisabled,
|
|
18
|
+
isUsableMulticastEndpoint,
|
|
19
|
+
isValidMulticastGroupAddress,
|
|
20
|
+
makeSourceSwitchHandler,
|
|
21
|
+
multicastEndpointKey,
|
|
22
|
+
normalizeMulticastEndpoint,
|
|
23
|
+
readUrlMulticastConfig,
|
|
24
|
+
runUnicastFallback,
|
|
25
|
+
selectMulticastTier,
|
|
26
|
+
wireStarvationDetector,
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(globalThis, "__MmtTransport", {
|
|
29
|
+
configurable: true,
|
|
30
|
+
enumerable: false,
|
|
31
|
+
writable: false,
|
|
32
|
+
value: Object.freeze(api),
|
|
33
|
+
});
|
|
34
|
+
export default api;
|
|
35
|
+
//# sourceMappingURL=no-modules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-modules.js","sourceRoot":"","sources":["../src/no-modules.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,EACN,6BAA6B,EAC7B,yBAAyB,EACzB,4BAA4B,EAC5B,oBAAoB,EACpB,0BAA0B,EAC1B,sBAAsB,GACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,EAAE,yBAAyB,EAAE,MAAM,kCAAkC,CAAC;AAC7E,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAChF,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAE3D,MAAM,GAAG,GAAG;IACX,yBAAyB;IACzB,qBAAqB;IACrB,yBAAyB;IACzB,gBAAgB;IAChB,sBAAsB;IACtB,WAAW;IACX,6BAA6B;IAC7B,yBAAyB;IACzB,4BAA4B;IAC5B,uBAAuB;IACvB,oBAAoB;IACpB,0BAA0B;IAC1B,sBAAsB;IACtB,kBAAkB;IAClB,mBAAmB;IACnB,sBAAsB;CACtB,CAAC;AAEF,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,gBAAgB,EAAE;IACnD,YAAY,EAAE,IAAI;IAClB,UAAU,EAAE,KAAK;IACjB,QAAQ,EAAE,KAAK;IACf,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC;CACzB,CAAC,CAAC;AAEH,eAAe,GAAG,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface FecProgressSnapshot {
|
|
2
|
+
sourceSymbols: number;
|
|
3
|
+
repairSymbols: number;
|
|
4
|
+
blocksComplete: number;
|
|
5
|
+
blocksRecovered: number;
|
|
6
|
+
mediaFrames?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface StallDetectorConfig {
|
|
9
|
+
windowMs: number;
|
|
10
|
+
onStarvation(): void;
|
|
11
|
+
onRecovery(): void;
|
|
12
|
+
now?: () => number;
|
|
13
|
+
}
|
|
14
|
+
export declare class TransportStallDetector {
|
|
15
|
+
#private;
|
|
16
|
+
private readonly config;
|
|
17
|
+
constructor(config: StallDetectorConfig);
|
|
18
|
+
get isStarved(): boolean;
|
|
19
|
+
update(snapshot: FecProgressSnapshot): void;
|
|
20
|
+
reset(): void;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=stall-detector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stall-detector.d.ts","sourceRoot":"","sources":["../src/stall-detector.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IACnC,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,mBAAmB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,IAAI,IAAI,CAAC;IACrB,UAAU,IAAI,IAAI,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACnB;AAED,qBAAa,sBAAsB;;IAOtB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,mBAAmB;IAKxD,IAAI,SAAS,IAAI,OAAO,CAA0B;IAElD,MAAM,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI;IA4B3C,KAAK,IAAI,IAAI;CAKb"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export class TransportStallDetector {
|
|
2
|
+
config;
|
|
3
|
+
#last = null;
|
|
4
|
+
#lastSampleAt = 0;
|
|
5
|
+
#starvedSince = null;
|
|
6
|
+
#latched = false;
|
|
7
|
+
#now;
|
|
8
|
+
constructor(config) {
|
|
9
|
+
this.config = config;
|
|
10
|
+
if (config.windowMs <= 0)
|
|
11
|
+
throw new Error("Stall window must be positive and catalog-derived");
|
|
12
|
+
this.#now = config.now ?? Date.now;
|
|
13
|
+
}
|
|
14
|
+
get isStarved() { return this.#latched; }
|
|
15
|
+
update(snapshot) {
|
|
16
|
+
const previous = this.#last;
|
|
17
|
+
const previousAt = this.#lastSampleAt;
|
|
18
|
+
const now = this.#now();
|
|
19
|
+
this.#last = { ...snapshot };
|
|
20
|
+
this.#lastSampleAt = now;
|
|
21
|
+
if (!previous)
|
|
22
|
+
return;
|
|
23
|
+
const blockDelta = snapshot.blocksComplete - previous.blocksComplete +
|
|
24
|
+
snapshot.blocksRecovered - previous.blocksRecovered;
|
|
25
|
+
const mediaDelta = (snapshot.mediaFrames ?? 0) - (previous.mediaFrames ?? 0);
|
|
26
|
+
if (blockDelta <= 0 && mediaDelta <= 0) {
|
|
27
|
+
this.#starvedSince ??= previousAt;
|
|
28
|
+
if (!this.#latched && now - this.#starvedSince >= this.config.windowMs) {
|
|
29
|
+
this.#latched = true;
|
|
30
|
+
this.config.onStarvation();
|
|
31
|
+
}
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
this.#starvedSince = null;
|
|
35
|
+
if (this.#latched) {
|
|
36
|
+
this.#latched = false;
|
|
37
|
+
this.config.onRecovery();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
reset() {
|
|
41
|
+
this.#last = null;
|
|
42
|
+
this.#starvedSince = null;
|
|
43
|
+
this.#latched = false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=stall-detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stall-detector.js","sourceRoot":"","sources":["../src/stall-detector.ts"],"names":[],"mappings":"AAeA,MAAM,OAAO,sBAAsB;IAOL;IAN7B,KAAK,GAA+B,IAAI,CAAC;IACzC,aAAa,GAAG,CAAC,CAAC;IAClB,aAAa,GAAkB,IAAI,CAAC;IACpC,QAAQ,GAAG,KAAK,CAAC;IACR,IAAI,CAAe;IAE5B,YAA6B,MAA2B;QAA3B,WAAM,GAAN,MAAM,CAAqB;QACvD,IAAI,MAAM,CAAC,QAAQ,IAAI,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;QAC/F,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;IACpC,CAAC;IAED,IAAI,SAAS,KAAc,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;IAElD,MAAM,CAAC,QAA6B;QACnC,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC;QAC5B,MAAM,UAAU,GAAG,IAAI,CAAC,aAAa,CAAC;QACtC,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QACxB,IAAI,CAAC,KAAK,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;QAC7B,IAAI,CAAC,aAAa,GAAG,GAAG,CAAC;QACzB,IAAI,CAAC,QAAQ;YAAE,OAAO;QAEtB,MAAM,UAAU,GAAG,QAAQ,CAAC,cAAc,GAAG,QAAQ,CAAC,cAAc;YACnE,QAAQ,CAAC,eAAe,GAAG,QAAQ,CAAC,eAAe,CAAC;QACrD,MAAM,UAAU,GAAG,CAAC,QAAQ,CAAC,WAAW,IAAI,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,WAAW,IAAI,CAAC,CAAC,CAAC;QAE7E,IAAI,UAAU,IAAI,CAAC,IAAI,UAAU,IAAI,CAAC,EAAE,CAAC;YACxC,IAAI,CAAC,aAAa,KAAK,UAAU,CAAC;YAClC,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,GAAG,GAAG,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;gBACxE,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;gBACrB,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;YAC5B,CAAC;YACD,OAAO;QACR,CAAC;QAED,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;QAC1B,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;YACtB,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;QAC1B,CAAC;IACF,CAAC;IAED,KAAK;QACJ,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;QAC1B,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;IACvB,CAAC;CACD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stall-detector.test.d.ts","sourceRoot":"","sources":["../src/stall-detector.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { TransportStallDetector } from "./stall-detector.js";
|
|
3
|
+
describe("TransportStallDetector", () => {
|
|
4
|
+
it("detects repair-only starvation and recovery", () => {
|
|
5
|
+
let now = 0;
|
|
6
|
+
const onStarvation = vi.fn();
|
|
7
|
+
const onRecovery = vi.fn();
|
|
8
|
+
const detector = new TransportStallDetector({ windowMs: 1000, now: () => now, onStarvation, onRecovery });
|
|
9
|
+
detector.update({ sourceSymbols: 0, repairSymbols: 0, blocksComplete: 0, blocksRecovered: 0 });
|
|
10
|
+
now = 1000;
|
|
11
|
+
detector.update({ sourceSymbols: 0, repairSymbols: 10, blocksComplete: 0, blocksRecovered: 0 });
|
|
12
|
+
expect(onStarvation).toHaveBeenCalledOnce();
|
|
13
|
+
now = 1100;
|
|
14
|
+
detector.update({ sourceSymbols: 1, repairSymbols: 10, blocksComplete: 1, blocksRecovered: 0 });
|
|
15
|
+
expect(onRecovery).toHaveBeenCalledOnce();
|
|
16
|
+
});
|
|
17
|
+
it("detects starvation when all packet counters stop", () => {
|
|
18
|
+
let now = 0;
|
|
19
|
+
const onStarvation = vi.fn();
|
|
20
|
+
const detector = new TransportStallDetector({
|
|
21
|
+
windowMs: 1000,
|
|
22
|
+
now: () => now,
|
|
23
|
+
onStarvation,
|
|
24
|
+
onRecovery: vi.fn(),
|
|
25
|
+
});
|
|
26
|
+
const snapshot = { sourceSymbols: 10, repairSymbols: 2, blocksComplete: 1, blocksRecovered: 0 };
|
|
27
|
+
detector.update(snapshot);
|
|
28
|
+
now = 1000;
|
|
29
|
+
detector.update(snapshot);
|
|
30
|
+
expect(onStarvation).toHaveBeenCalledOnce();
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
//# sourceMappingURL=stall-detector.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stall-detector.test.js","sourceRoot":"","sources":["../src/stall-detector.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAClD,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAE7D,QAAQ,CAAC,wBAAwB,EAAE,GAAG,EAAE;IACvC,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACtD,IAAI,GAAG,GAAG,CAAC,CAAC;QACZ,MAAM,YAAY,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAC7B,MAAM,UAAU,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3B,MAAM,QAAQ,GAAG,IAAI,sBAAsB,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,YAAY,EAAE,UAAU,EAAE,CAAC,CAAC;QAC1G,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE,eAAe,EAAE,CAAC,EAAE,CAAC,CAAC;QAC/F,GAAG,GAAG,IAAI,CAAC;QACX,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,aAAa,EAAE,EAAE,EAAE,cAAc,EAAE,CAAC,EAAE,eAAe,EAAE,CAAC,EAAE,CAAC,CAAC;QAChG,MAAM,CAAC,YAAY,CAAC,CAAC,oBAAoB,EAAE,CAAC;QAC5C,GAAG,GAAG,IAAI,CAAC;QACX,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,aAAa,EAAE,EAAE,EAAE,cAAc,EAAE,CAAC,EAAE,eAAe,EAAE,CAAC,EAAE,CAAC,CAAC;QAChG,MAAM,CAAC,UAAU,CAAC,CAAC,oBAAoB,EAAE,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC3D,IAAI,GAAG,GAAG,CAAC,CAAC;QACZ,MAAM,YAAY,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAC7B,MAAM,QAAQ,GAAG,IAAI,sBAAsB,CAAC;YAC3C,QAAQ,EAAE,IAAI;YACd,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG;YACd,YAAY;YACZ,UAAU,EAAE,EAAE,CAAC,EAAE,EAAE;SACnB,CAAC,CAAC;QACH,MAAM,QAAQ,GAAG,EAAE,aAAa,EAAE,EAAE,EAAE,aAAa,EAAE,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE,eAAe,EAAE,CAAC,EAAE,CAAC;QAChG,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC1B,GAAG,GAAG,IAAI,CAAC;QACX,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC1B,MAAM,CAAC,YAAY,CAAC,CAAC,oBAAoB,EAAE,CAAC;IAC7C,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export interface ActivatableSessions {
|
|
2
|
+
activateMulticast(): void;
|
|
3
|
+
}
|
|
4
|
+
export declare function makeSourceSwitchHandler(options: {
|
|
5
|
+
multicastBridgeAvailable: boolean;
|
|
6
|
+
sessionsReady: Promise<ActivatableSessions | null>;
|
|
7
|
+
onMulticastConfirmed?(): void;
|
|
8
|
+
onAbortTakeover?(): void;
|
|
9
|
+
log?(message: string): void;
|
|
10
|
+
warn?(message: string): void;
|
|
11
|
+
}): (source: string) => Promise<void>;
|
|
12
|
+
export interface LossArcSession {
|
|
13
|
+
lossDetected(): void;
|
|
14
|
+
lossRecovered(): void;
|
|
15
|
+
}
|
|
16
|
+
export interface FecStarvationSnapshot {
|
|
17
|
+
sourceSymbols: number;
|
|
18
|
+
repairSymbols: number;
|
|
19
|
+
blocksComplete: number;
|
|
20
|
+
blocksRecovered: number;
|
|
21
|
+
mediaFrames?: number;
|
|
22
|
+
}
|
|
23
|
+
export interface StarvationDetectorLike {
|
|
24
|
+
update(snapshot: FecStarvationSnapshot): void;
|
|
25
|
+
reset(): void;
|
|
26
|
+
}
|
|
27
|
+
export type StallDetectorLike = StarvationDetectorLike;
|
|
28
|
+
export declare function wireStallDetector(args: {
|
|
29
|
+
session: LossArcSession | undefined;
|
|
30
|
+
windowMs: number;
|
|
31
|
+
makeDetector(config: {
|
|
32
|
+
windowMs: number;
|
|
33
|
+
onStarvation(): void;
|
|
34
|
+
onRecovery(): void;
|
|
35
|
+
}): StallDetectorLike;
|
|
36
|
+
}): StallDetectorLike | null;
|
|
37
|
+
export declare function wireStarvationDetector(args: {
|
|
38
|
+
session: LossArcSession | undefined;
|
|
39
|
+
trackName: string;
|
|
40
|
+
fecInterleaveMs: number;
|
|
41
|
+
fecK: number;
|
|
42
|
+
makeDetector(config: {
|
|
43
|
+
windowMs: number;
|
|
44
|
+
onStarvation(): void;
|
|
45
|
+
onRecovery(): void;
|
|
46
|
+
}): StarvationDetectorLike;
|
|
47
|
+
warn?(message: string): void;
|
|
48
|
+
}): Promise<StarvationDetectorLike | null>;
|
|
49
|
+
//# sourceMappingURL=takeover.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"takeover.d.ts","sourceRoot":"","sources":["../src/takeover.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAAG,iBAAiB,IAAI,IAAI,CAAC;CAAE;AAEnE,wBAAgB,uBAAuB,CAAC,OAAO,EAAE;IAChD,wBAAwB,EAAE,OAAO,CAAC;IAClC,aAAa,EAAE,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAAC;IACnD,oBAAoB,CAAC,IAAI,IAAI,CAAC;IAC9B,eAAe,CAAC,IAAI,IAAI,CAAC;IACzB,GAAG,CAAC,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,IAAI,CAAC,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAmBpC;AAED,MAAM,WAAW,cAAc;IAAG,YAAY,IAAI,IAAI,CAAC;IAAC,aAAa,IAAI,IAAI,CAAC;CAAE;AAChF,MAAM,WAAW,qBAAqB;IACrC,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AACD,MAAM,WAAW,sBAAsB;IAAG,MAAM,CAAC,QAAQ,EAAE,qBAAqB,GAAG,IAAI,CAAC;IAAC,KAAK,IAAI,IAAI,CAAC;CAAE;AACzG,MAAM,MAAM,iBAAiB,GAAG,sBAAsB,CAAC;AAEvD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IACvC,OAAO,EAAE,cAAc,GAAG,SAAS,CAAC;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,YAAY,IAAI,IAAI,CAAC;QAAC,UAAU,IAAI,IAAI,CAAA;KAAE,GAAG,iBAAiB,CAAC;CACxG,GAAG,iBAAiB,GAAG,IAAI,CAO3B;AAED,wBAAsB,sBAAsB,CAAC,IAAI,EAAE;IAClD,OAAO,EAAE,cAAc,GAAG,SAAS,CAAC;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,YAAY,IAAI,IAAI,CAAC;QAAC,UAAU,IAAI,IAAI,CAAA;KAAE,GAAG,sBAAsB,CAAC;IAC7G,IAAI,CAAC,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,GAAG,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CASzC"}
|
package/dist/takeover.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export function makeSourceSwitchHandler(options) {
|
|
2
|
+
const log = options.log ?? console.log;
|
|
3
|
+
const warn = options.warn ?? console.warn;
|
|
4
|
+
return async (source) => {
|
|
5
|
+
if (source !== "multicast")
|
|
6
|
+
return;
|
|
7
|
+
if (!options.multicastBridgeAvailable) {
|
|
8
|
+
warn("[MoqWatch] MediaRouter requested multicast without bridge; keeping MoQ active");
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const sessions = await options.sessionsReady;
|
|
12
|
+
if (!sessions) {
|
|
13
|
+
warn("[MoqWatch] SessionManager unavailable; aborting multicast takeover, staying on MoQ");
|
|
14
|
+
options.onAbortTakeover?.();
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
log("[MoqWatch] MediaRouter -> multicast; activating via SessionManager");
|
|
18
|
+
sessions.activateMulticast();
|
|
19
|
+
options.onMulticastConfirmed?.();
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function wireStallDetector(args) {
|
|
23
|
+
if (!args.session || args.windowMs <= 0)
|
|
24
|
+
return null;
|
|
25
|
+
return args.makeDetector({
|
|
26
|
+
windowMs: args.windowMs,
|
|
27
|
+
onStarvation: () => args.session.lossDetected(),
|
|
28
|
+
onRecovery: () => args.session.lossRecovered(),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
export async function wireStarvationDetector(args) {
|
|
32
|
+
if (!args.session || args.fecInterleaveMs <= 0 || args.fecK <= 0)
|
|
33
|
+
return null;
|
|
34
|
+
const detector = wireStallDetector({
|
|
35
|
+
session: args.session,
|
|
36
|
+
windowMs: Math.ceil(args.fecInterleaveMs * 3),
|
|
37
|
+
makeDetector: args.makeDetector,
|
|
38
|
+
});
|
|
39
|
+
if (!detector)
|
|
40
|
+
args.warn?.(`Unable to create starvation detector for ${args.trackName}`);
|
|
41
|
+
return detector;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=takeover.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"takeover.js","sourceRoot":"","sources":["../src/takeover.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,uBAAuB,CAAC,OAOvC;IACA,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACvC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAC1C,OAAO,KAAK,EAAE,MAAM,EAAE,EAAE;QACvB,IAAI,MAAM,KAAK,WAAW;YAAE,OAAO;QACnC,IAAI,CAAC,OAAO,CAAC,wBAAwB,EAAE,CAAC;YACvC,IAAI,CAAC,+EAA+E,CAAC,CAAC;YACtF,OAAO;QACR,CAAC;QACD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,aAAa,CAAC;QAC7C,IAAI,CAAC,QAAQ,EAAE,CAAC;YACf,IAAI,CAAC,oFAAoF,CAAC,CAAC;YAC3F,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC;YAC5B,OAAO;QACR,CAAC;QACD,GAAG,CAAC,oEAAoE,CAAC,CAAC;QAC1E,QAAQ,CAAC,iBAAiB,EAAE,CAAC;QAC7B,OAAO,CAAC,oBAAoB,EAAE,EAAE,CAAC;IAClC,CAAC,CAAC;AACH,CAAC;AAaD,MAAM,UAAU,iBAAiB,CAAC,IAIjC;IACA,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,QAAQ,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACrD,OAAO,IAAI,CAAC,YAAY,CAAC;QACxB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,YAAY,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,OAAQ,CAAC,YAAY,EAAE;QAChD,UAAU,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,OAAQ,CAAC,aAAa,EAAE;KAC/C,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,IAO5C;IACA,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,eAAe,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC9E,MAAM,QAAQ,GAAG,iBAAiB,CAAC;QAClC,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,GAAG,CAAC,CAAC;QAC7C,YAAY,EAAE,IAAI,CAAC,YAAY;KAC/B,CAAC,CAAC;IACH,IAAI,CAAC,QAAQ;QAAE,IAAI,CAAC,IAAI,EAAE,CAAC,4CAA4C,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;IACzF,OAAO,QAAQ,CAAC;AACjB,CAAC"}
|