@blockcast/mmt-transport 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/abr-controller.d.ts +94 -0
  2. package/dist/abr-controller.d.ts.map +1 -0
  3. package/dist/abr-controller.js +174 -0
  4. package/dist/abr-controller.js.map +1 -0
  5. package/dist/amt-gateway.d.ts +160 -0
  6. package/dist/amt-gateway.d.ts.map +1 -0
  7. package/dist/amt-gateway.js +390 -0
  8. package/dist/amt-gateway.js.map +1 -0
  9. package/dist/clock.d.ts +104 -0
  10. package/dist/clock.d.ts.map +1 -0
  11. package/dist/clock.js +183 -0
  12. package/dist/clock.js.map +1 -0
  13. package/dist/driad-discovery.d.ts +50 -0
  14. package/dist/driad-discovery.d.ts.map +1 -0
  15. package/dist/driad-discovery.js +170 -0
  16. package/dist/driad-discovery.js.map +1 -0
  17. package/dist/fec-client.d.ts +442 -0
  18. package/dist/fec-client.d.ts.map +1 -0
  19. package/dist/fec-client.js +784 -0
  20. package/dist/fec-client.js.map +1 -0
  21. package/dist/fec-client.test.d.ts +8 -0
  22. package/dist/fec-client.test.d.ts.map +1 -0
  23. package/dist/fec-client.test.js +112 -0
  24. package/dist/fec-client.test.js.map +1 -0
  25. package/dist/index.d.ts +34 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +43 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/transport-manager.d.ts +114 -0
  30. package/dist/transport-manager.d.ts.map +1 -0
  31. package/dist/transport-manager.js +396 -0
  32. package/dist/transport-manager.js.map +1 -0
  33. package/dist/types.d.ts +356 -0
  34. package/dist/types.d.ts.map +1 -0
  35. package/dist/types.js +85 -0
  36. package/dist/types.js.map +1 -0
  37. package/package.json +60 -0
  38. package/src/abr-controller.ts +227 -0
  39. package/src/amt-gateway.ts +511 -0
  40. package/src/clock.ts +212 -0
  41. package/src/driad-discovery.ts +193 -0
  42. package/src/fec-client.test.ts +140 -0
  43. package/src/fec-client.ts +1097 -0
  44. package/src/index.ts +122 -0
  45. package/src/transport-manager.ts +460 -0
  46. package/src/types.ts +420 -0
@@ -0,0 +1,390 @@
1
+ /**
2
+ * @blockcast/transport - AMT Gateway
3
+ *
4
+ * Consolidated AMT (Automatic Multicast Tunneling) implementation
5
+ * supporting both Service Worker and Direct Sockets approaches.
6
+ *
7
+ * RFC 7450: Automatic Multicast Tunneling
8
+ */
9
+ import { DRIADDiscovery } from "./driad-discovery.js";
10
+ /** Default AMT port (RFC 7450) */
11
+ const DEFAULT_AMT_PORT = 2268;
12
+ /** AMT message types */
13
+ const AMT_MESSAGE_TYPES = {
14
+ RELAY_DISCOVERY: 1,
15
+ RELAY_ADVERTISEMENT: 2,
16
+ REQUEST: 3,
17
+ MEMBERSHIP_QUERY: 4,
18
+ MEMBERSHIP_UPDATE: 5,
19
+ MULTICAST_DATA: 6,
20
+ TEARDOWN: 7,
21
+ };
22
+ /** AMT gateway states */
23
+ const GATEWAY_STATES = {
24
+ IDLE: "idle",
25
+ DISCOVERING: "discovering",
26
+ REQUESTING: "requesting",
27
+ QUERYING: "querying",
28
+ ACTIVE: "active",
29
+ LEAVING: "leaving",
30
+ CLOSED: "closed",
31
+ };
32
+ /** Check if address is IPv4 */
33
+ function isIPv4(address) {
34
+ return /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(address);
35
+ }
36
+ /**
37
+ * AMT Gateway Manager (Service Worker based)
38
+ *
39
+ * Manages AMT tunnel subscriptions using Service Worker for UDP communication.
40
+ * Handles DRIAD discovery, AMT handshake, and packet decapsulation.
41
+ */
42
+ export class AMTGatewayManager {
43
+ subscriptions = new Map();
44
+ messageHandler;
45
+ pendingPackets = new Map();
46
+ driad = new DRIADDiscovery();
47
+ constructor() {
48
+ // Set up message handler for packets from service worker
49
+ this.messageHandler = (event) => {
50
+ const data = event.data;
51
+ console.log(`[AMT] SW message received:`, data.type);
52
+ if (data.type === "UDP_PACKET") {
53
+ this.handlePacket(data);
54
+ }
55
+ else if (data.type === "SOCKET_ERROR") {
56
+ this.handleSocketError(data);
57
+ }
58
+ else if (data.type === "DEBUG_LOG") {
59
+ console.log(`[AMT SW Debug] ${data.message}`);
60
+ }
61
+ };
62
+ // Register message handler
63
+ if (typeof navigator !== "undefined" && navigator.serviceWorker) {
64
+ navigator.serviceWorker.addEventListener("message", this.messageHandler);
65
+ console.log("[AMT] Registered service worker message handler");
66
+ }
67
+ else {
68
+ console.warn("[AMT] Service worker not available");
69
+ }
70
+ }
71
+ /**
72
+ * Subscribe via AMT tunnel
73
+ */
74
+ async subscribe(config) {
75
+ const { key } = config;
76
+ const keyStr = this.getKeyString(key);
77
+ if (this.subscriptions.has(keyStr)) {
78
+ throw new Error(`Already subscribed to ${keyStr}`);
79
+ }
80
+ try {
81
+ let relayAddress;
82
+ let discoveryMethod;
83
+ const relayPort = config.relayPort || DEFAULT_AMT_PORT;
84
+ if (config.relayAddress) {
85
+ relayAddress = config.relayAddress;
86
+ discoveryMethod = "manual";
87
+ console.log(`[AMT] Using configured relay ${relayAddress}:${relayPort}`);
88
+ }
89
+ else if (key.source) {
90
+ // DRIAD (RFC 8777) discovers relays based on SOURCE address, not group
91
+ console.log(`[AMT] No relay configured, discovering via DRIAD for source ${key.source}...`);
92
+ const relays = await this.driad.discoverRelays(key.source);
93
+ const bestRelay = await this.driad.selectBestRelay(relays);
94
+ if (!bestRelay) {
95
+ throw new Error(`AMT relay discovery failed for source ${key.source}. ` +
96
+ `Please specify a relay address in the subscription configuration.`);
97
+ }
98
+ relayAddress = bestRelay.host;
99
+ discoveryMethod = "driad";
100
+ console.log(`[AMT] Discovered relay via DRIAD: ${relayAddress}:${relayPort}`);
101
+ }
102
+ else {
103
+ throw new Error(`AMT relay discovery requires a source address for SSM (RFC 8777). ` +
104
+ `Please specify either a relay address or source address.`);
105
+ }
106
+ const subscriptionId = keyStr;
107
+ // Initialize packet queue for handshake
108
+ this.pendingPackets.set(subscriptionId, []);
109
+ // Create subscription
110
+ const subscription = {
111
+ key,
112
+ subscriptionId,
113
+ config,
114
+ handshakeComplete: false,
115
+ relay: {
116
+ address: relayAddress,
117
+ port: relayPort,
118
+ discoveryMethod,
119
+ discoveredAt: Date.now(),
120
+ },
121
+ };
122
+ this.subscriptions.set(keyStr, subscription);
123
+ // Note: Actual UDP socket creation and handshake would require
124
+ // Service Worker communication - this is a simplified version
125
+ console.log(`[AMT] Subscription created for ${keyStr} via ${relayAddress}:${relayPort}`);
126
+ return keyStr;
127
+ }
128
+ catch (error) {
129
+ const subscription = this.subscriptions.get(keyStr);
130
+ if (subscription) {
131
+ this.subscriptions.delete(keyStr);
132
+ this.pendingPackets.delete(subscription.subscriptionId);
133
+ }
134
+ throw new Error(`Failed to subscribe via AMT: ${error instanceof Error ? error.message : String(error)}`);
135
+ }
136
+ }
137
+ /**
138
+ * Unsubscribe from AMT tunnel
139
+ */
140
+ async unsubscribe(handle) {
141
+ const subscription = this.subscriptions.get(handle);
142
+ if (!subscription) {
143
+ throw new Error(`Subscription ${handle} not found`);
144
+ }
145
+ try {
146
+ // Stop keep-alive timer
147
+ if (subscription.keepAliveTimer) {
148
+ clearInterval(subscription.keepAliveTimer);
149
+ }
150
+ console.log(`[AMT] Unsubscribed from ${handle}`);
151
+ }
152
+ catch (error) {
153
+ console.error("[AMT] Error during unsubscribe:", error);
154
+ }
155
+ finally {
156
+ this.subscriptions.delete(handle);
157
+ this.pendingPackets.delete(subscription.subscriptionId);
158
+ }
159
+ }
160
+ /**
161
+ * Get list of active subscriptions
162
+ */
163
+ getSubscriptions() {
164
+ return Array.from(this.subscriptions.keys());
165
+ }
166
+ /**
167
+ * Handle packet from service worker
168
+ */
169
+ handlePacket(message) {
170
+ const { subscriptionId, data } = message;
171
+ if (!subscriptionId)
172
+ return;
173
+ const subscription = this.subscriptions.get(subscriptionId);
174
+ if (!subscription) {
175
+ console.warn(`[AMT] Received packet for unknown subscription: ${subscriptionId}`);
176
+ return;
177
+ }
178
+ // During handshake, queue packets
179
+ if (!subscription.handshakeComplete) {
180
+ const queue = this.pendingPackets.get(subscriptionId);
181
+ const packetData = data;
182
+ if (queue) {
183
+ queue.push({
184
+ data: packetData.data,
185
+ remoteAddress: packetData.remoteAddress,
186
+ remotePort: packetData.remotePort,
187
+ });
188
+ }
189
+ return;
190
+ }
191
+ // After handshake, forward to callback
192
+ const packetData = data;
193
+ const metadata = {
194
+ sourceAddress: subscription.relay?.address || "unknown",
195
+ sourcePort: subscription.relay?.port || 0,
196
+ transport: "tunnel",
197
+ timestamp: performance.now(),
198
+ };
199
+ subscription.config.onPacket(packetData.data, metadata);
200
+ }
201
+ /**
202
+ * Handle socket error from service worker
203
+ */
204
+ handleSocketError(message) {
205
+ const { subscriptionId, error } = message;
206
+ if (!subscriptionId)
207
+ return;
208
+ const subscription = this.subscriptions.get(subscriptionId);
209
+ if (!subscription) {
210
+ console.warn(`[AMT] Received error for unknown subscription: ${subscriptionId}`);
211
+ return;
212
+ }
213
+ subscription.config.onError(new Error(error || "Unknown error"));
214
+ }
215
+ /**
216
+ * Cleanup on destroy
217
+ */
218
+ destroy() {
219
+ if (this.messageHandler && typeof navigator !== "undefined" && navigator.serviceWorker) {
220
+ navigator.serviceWorker.removeEventListener("message", this.messageHandler);
221
+ }
222
+ const handles = Array.from(this.subscriptions.keys());
223
+ for (const handle of handles) {
224
+ this.unsubscribe(handle).catch((err) => {
225
+ console.error(`[AMT] Error unsubscribing ${handle}:`, err);
226
+ });
227
+ }
228
+ }
229
+ /**
230
+ * Get key string for HashMap
231
+ */
232
+ getKeyString(key) {
233
+ const source = key.source ? `@${key.source}` : "";
234
+ return `${key.group}:${key.port}${source}`;
235
+ }
236
+ }
237
+ /**
238
+ * AMT Gateway Client (Direct Sockets based)
239
+ *
240
+ * Connects to an AMT relay to receive multicast data via unicast tunnel.
241
+ * Implements the gateway side of RFC 7450.
242
+ * Supports multiple (S,G) groups over a single AMT tunnel.
243
+ */
244
+ export class AMTGateway {
245
+ relayAddress;
246
+ relayPort;
247
+ groups = new Map();
248
+ // Callbacks
249
+ onPacket;
250
+ onStateChange;
251
+ onError;
252
+ onLog;
253
+ // State
254
+ state = GATEWAY_STATES.IDLE;
255
+ nonce = null;
256
+ responseMAC = null;
257
+ relayConfirmedAddress = null;
258
+ lastDataTime = 0;
259
+ // Stats
260
+ stats = {
261
+ packetsReceived: 0,
262
+ bytesReceived: 0,
263
+ queriesReceived: 0,
264
+ updatesSet: 0,
265
+ };
266
+ constructor(options) {
267
+ this.relayAddress = options.relayAddress;
268
+ this.relayPort = options.relayPort || DEFAULT_AMT_PORT;
269
+ this.onPacket = options.onPacket || (() => { });
270
+ this.onStateChange = options.onStateChange || (() => { });
271
+ this.onError = options.onError || ((err) => console.error("AMT Error:", err));
272
+ this.onLog = options.onLog || ((msg) => console.log("AMT:", msg));
273
+ }
274
+ /**
275
+ * Generate random nonce
276
+ */
277
+ generateNonce() {
278
+ const nonce = new Uint8Array(4);
279
+ crypto.getRandomValues(nonce);
280
+ return nonce;
281
+ }
282
+ /**
283
+ * Set gateway state
284
+ */
285
+ setState(newState) {
286
+ const oldState = this.state;
287
+ this.state = newState;
288
+ this.onLog(`State: ${oldState} -> ${newState}`);
289
+ this.onStateChange(newState, oldState);
290
+ }
291
+ /**
292
+ * Join a multicast group
293
+ */
294
+ async joinGroup(groupAddress, groupPort, sourceAddress = null, onPacket = null) {
295
+ const groupKey = `${sourceAddress || "*"},${groupAddress}`;
296
+ if (this.groups.has(groupKey)) {
297
+ this.onLog(`Group already joined: ${groupKey}`);
298
+ return groupKey;
299
+ }
300
+ this.groups.set(groupKey, {
301
+ groupPort,
302
+ onPacket,
303
+ joinedAt: Date.now(),
304
+ });
305
+ this.onLog(`Joined group: ${groupKey} (port ${groupPort})`);
306
+ return groupKey;
307
+ }
308
+ /**
309
+ * Leave a multicast group
310
+ */
311
+ async leaveGroup(groupAddress, sourceAddress = null) {
312
+ const groupKey = `${sourceAddress || "*"},${groupAddress}`;
313
+ if (!this.groups.has(groupKey)) {
314
+ this.onLog(`Group not found: ${groupKey}`);
315
+ return false;
316
+ }
317
+ this.groups.delete(groupKey);
318
+ this.onLog(`Left group: ${groupKey} (${this.groups.size} remaining)`);
319
+ if (this.groups.size === 0) {
320
+ this.onLog("No groups remaining - closing AMT tunnel");
321
+ await this.close();
322
+ }
323
+ return true;
324
+ }
325
+ /**
326
+ * Get list of active groups
327
+ */
328
+ getGroups() {
329
+ return Array.from(this.groups.entries()).map(([key, info]) => {
330
+ const [source, group] = key.split(",");
331
+ return {
332
+ groupKey: key,
333
+ groupAddress: group,
334
+ sourceAddress: source !== "*" ? source : null,
335
+ groupPort: info.groupPort,
336
+ joinedAt: info.joinedAt,
337
+ };
338
+ });
339
+ }
340
+ /**
341
+ * Open connection to AMT relay
342
+ */
343
+ async open() {
344
+ if (this.state !== GATEWAY_STATES.IDLE) {
345
+ throw new Error(`Cannot open in state: ${this.state}`);
346
+ }
347
+ this.setState(GATEWAY_STATES.DISCOVERING);
348
+ this.onLog(`Connecting to AMT relay ${this.relayAddress}:${this.relayPort}`);
349
+ this.onLog(`Active groups: ${this.groups.size}`);
350
+ // Generate session nonce
351
+ this.nonce = this.generateNonce();
352
+ this.onLog(`Generated nonce: ${Array.from(this.nonce).map((b) => b.toString(16).padStart(2, "0")).join("")}`);
353
+ // Note: Actual UDPSocket connection would go here
354
+ // This is a simplified version showing the state machine
355
+ }
356
+ /**
357
+ * Close connection
358
+ */
359
+ async close() {
360
+ if (this.state === GATEWAY_STATES.CLOSED) {
361
+ return;
362
+ }
363
+ this.setState(GATEWAY_STATES.LEAVING);
364
+ // Clear all groups
365
+ this.groups.clear();
366
+ this.setState(GATEWAY_STATES.CLOSED);
367
+ this.onLog("AMT Gateway closed");
368
+ }
369
+ /**
370
+ * Get current statistics
371
+ */
372
+ getStats() {
373
+ return { ...this.stats };
374
+ }
375
+ /**
376
+ * Get current state
377
+ */
378
+ getState() {
379
+ return this.state;
380
+ }
381
+ /**
382
+ * Check if gateway is active and receiving data
383
+ */
384
+ isActive() {
385
+ return this.state === GATEWAY_STATES.ACTIVE;
386
+ }
387
+ }
388
+ // Export singleton manager instance
389
+ export const amtGatewayManager = new AMTGatewayManager();
390
+ //# sourceMappingURL=amt-gateway.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"amt-gateway.js","sourceRoot":"","sources":["../src/amt-gateway.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * @blockcast/transport - SharedClock
3
+ *
4
+ * A/V Synchronization via First Sample Anchor
5
+ *
6
+ * Based on ExoPlayer MMT Plugin implementation:
7
+ * - MMTExtractor.java:46-47 (SystemClockAnchor, MfuClockAnchor)
8
+ * - MMTExtractor.java:625-631 (anchor setting)
9
+ * - SystemClockSynchronizedSimpleDecoderAudioRenderer.java:576 (getPositionUs)
10
+ *
11
+ * This class provides a shared clock anchor across video and audio tracks
12
+ * to ensure A/V synchronization. The anchor is set on the first sample
13
+ * received from ANY track, establishing a common reference point.
14
+ */
15
+ import type { Micro, SyncDecision } from "./types.js";
16
+ /** Sync tolerance window in microseconds (66ms = ~2 video frames at 30fps) */
17
+ export declare const SYNC_TOLERANCE_US: Micro;
18
+ /** PTS offset for decoder timing (from ExoPlayer MMTExtractor.java:37) */
19
+ export declare const PTS_OFFSET_US: Micro;
20
+ /**
21
+ * Shared clock for A/V synchronization.
22
+ *
23
+ * Usage:
24
+ * ```typescript
25
+ * const clock = new SharedClock();
26
+ *
27
+ * // In video/audio source, on first decoded frame:
28
+ * clock.setAnchor(frame.timestamp);
29
+ *
30
+ * // Before rendering each frame:
31
+ * const decision = clock.shouldRender(frame.timestamp);
32
+ * if (decision === "render") { ... }
33
+ * else if (decision === "hold") { await sleep(10); retry(); }
34
+ * else { frame.close(); } // discard
35
+ * ```
36
+ */
37
+ export declare class SharedClock {
38
+ #private;
39
+ constructor(bufferDelayMs?: number);
40
+ /**
41
+ * Set the anchor on first sample received from ANY track.
42
+ * This should be called once per session, typically on the first keyframe.
43
+ *
44
+ * @param mediaTimestamp - The presentation timestamp of the first sample (microseconds)
45
+ */
46
+ setAnchor(mediaTimestamp: Micro): void;
47
+ /**
48
+ * Get the current playback position in microseconds.
49
+ *
50
+ * @returns Current position in microseconds, or 0 if anchor not set
51
+ */
52
+ getPositionUs(): Micro;
53
+ /**
54
+ * Determine whether a frame should be rendered, held, or discarded.
55
+ *
56
+ * - HOLD: Frame is ahead of clock by more than 66ms (wait for clock)
57
+ * - DISCARD: Frame is behind clock by more than 66ms (too late)
58
+ * - RENDER: Within +/-66ms tolerance window
59
+ *
60
+ * @param frameTimestamp - The presentation timestamp of the frame (microseconds)
61
+ * @returns "render", "hold", or "discard"
62
+ */
63
+ shouldRender(frameTimestamp: Micro): SyncDecision;
64
+ /**
65
+ * Get how long to wait before a held frame can be rendered.
66
+ *
67
+ * @param frameTimestamp - The presentation timestamp of the frame (microseconds)
68
+ * @returns Wait time in milliseconds, or 0 if frame should be rendered now
69
+ */
70
+ getWaitTimeMs(frameTimestamp: Micro): number;
71
+ /**
72
+ * Get the delta between a frame timestamp and current position.
73
+ * Positive = frame is ahead, negative = frame is behind.
74
+ *
75
+ * @param frameTimestamp - The presentation timestamp of the frame (microseconds)
76
+ * @returns Delta in microseconds
77
+ */
78
+ getDeltaUs(frameTimestamp: Micro): number;
79
+ /**
80
+ * Check if the anchor has been set.
81
+ */
82
+ hasAnchor(): boolean;
83
+ /**
84
+ * Get the MFU clock anchor (minimum PTS across all tracks).
85
+ * Used for A/V sync reference point.
86
+ */
87
+ getMfuClockAnchor(): Micro;
88
+ /**
89
+ * Reset the clock anchor (e.g., on seek or error recovery).
90
+ */
91
+ reset(): void;
92
+ /**
93
+ * Get debug info about the current clock state.
94
+ */
95
+ getDebugInfo(): {
96
+ hasAnchor: boolean;
97
+ positionUs: Micro;
98
+ mfuClockAnchor: Micro;
99
+ bufferDelayMs: number;
100
+ };
101
+ }
102
+ export declare function getSharedClock(bufferDelayMs?: number): SharedClock;
103
+ export declare function resetSharedClock(): void;
104
+ //# sourceMappingURL=clock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clock.d.ts","sourceRoot":"","sources":["../src/clock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEtD,8EAA8E;AAC9E,eAAO,MAAM,iBAAiB,EAAY,KAAK,CAAC;AAQhD,0EAA0E;AAC1E,eAAO,MAAM,aAAa,EAAa,KAAK,CAAC;AAE7C;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,WAAW;;gBAaX,aAAa,GAAE,MAAgC;IAI3D;;;;;OAKG;IACH,SAAS,CAAC,cAAc,EAAE,KAAK,GAAG,IAAI;IAatC;;;;OAIG;IACH,aAAa,IAAI,KAAK;IAYtB;;;;;;;;;OASG;IACH,YAAY,CAAC,cAAc,EAAE,KAAK,GAAG,YAAY;IAgBjD;;;;;OAKG;IACH,aAAa,CAAC,cAAc,EAAE,KAAK,GAAG,MAAM;IAU5C;;;;;;OAMG;IACH,UAAU,CAAC,cAAc,EAAE,KAAK,GAAG,MAAM;IAIzC;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;;OAGG;IACH,iBAAiB,IAAI,KAAK;IAI1B;;OAEG;IACH,KAAK,IAAI,IAAI;IAOb;;OAEG;IACH,YAAY,IAAI;QACf,SAAS,EAAE,OAAO,CAAC;QACnB,UAAU,EAAE,KAAK,CAAC;QAClB,cAAc,EAAE,KAAK,CAAC;QACtB,aAAa,EAAE,MAAM,CAAC;KACtB;CAQD;AAQD,wBAAgB,cAAc,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,WAAW,CAKlE;AAED,wBAAgB,gBAAgB,IAAI,IAAI,CAGvC"}