@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
package/dist/clock.js ADDED
@@ -0,0 +1,183 @@
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
+ /** Sync tolerance window in microseconds (66ms = ~2 video frames at 30fps) */
16
+ export const SYNC_TOLERANCE_US = 66000;
17
+ /** Default buffer delay in milliseconds.
18
+ * For live streaming without B-frames, use lower value (100ms).
19
+ * ExoPlayer uses 750ms for broadcast with B-frames, but that's too high for live.
20
+ */
21
+ const DEFAULT_BUFFER_DELAY_MS = 100;
22
+ /** PTS offset for decoder timing (from ExoPlayer MMTExtractor.java:37) */
23
+ export const PTS_OFFSET_US = 266000;
24
+ /**
25
+ * Shared clock for A/V synchronization.
26
+ *
27
+ * Usage:
28
+ * ```typescript
29
+ * const clock = new SharedClock();
30
+ *
31
+ * // In video/audio source, on first decoded frame:
32
+ * clock.setAnchor(frame.timestamp);
33
+ *
34
+ * // Before rendering each frame:
35
+ * const decision = clock.shouldRender(frame.timestamp);
36
+ * if (decision === "render") { ... }
37
+ * else if (decision === "hold") { await sleep(10); retry(); }
38
+ * else { frame.close(); } // discard
39
+ * ```
40
+ */
41
+ export class SharedClock {
42
+ /** Wall clock time when anchor was set (performance.now()) */
43
+ #anchorWallTime;
44
+ /** Media timestamp when anchor was set (in microseconds) */
45
+ #anchorMediaTime;
46
+ /** Buffer delay in milliseconds to account for decode latency */
47
+ #bufferDelayMs;
48
+ /** Track minimum MFU timestamp across all tracks for shared reference */
49
+ #mfuClockAnchor = 0;
50
+ constructor(bufferDelayMs = DEFAULT_BUFFER_DELAY_MS) {
51
+ this.#bufferDelayMs = bufferDelayMs;
52
+ }
53
+ /**
54
+ * Set the anchor on first sample received from ANY track.
55
+ * This should be called once per session, typically on the first keyframe.
56
+ *
57
+ * @param mediaTimestamp - The presentation timestamp of the first sample (microseconds)
58
+ */
59
+ setAnchor(mediaTimestamp) {
60
+ if (this.#anchorWallTime === undefined) {
61
+ this.#anchorWallTime = performance.now();
62
+ this.#anchorMediaTime = mediaTimestamp;
63
+ console.log(`[SharedClock] Anchor set: wall=${this.#anchorWallTime.toFixed(2)}ms, media=${mediaTimestamp}us`);
64
+ }
65
+ // Track minimum MFU timestamp for shared reference
66
+ if (this.#mfuClockAnchor === 0 || mediaTimestamp < this.#mfuClockAnchor) {
67
+ this.#mfuClockAnchor = mediaTimestamp;
68
+ }
69
+ }
70
+ /**
71
+ * Get the current playback position in microseconds.
72
+ *
73
+ * @returns Current position in microseconds, or 0 if anchor not set
74
+ */
75
+ getPositionUs() {
76
+ if (this.#anchorWallTime === undefined || this.#anchorMediaTime === undefined) {
77
+ return 0;
78
+ }
79
+ const elapsedMs = performance.now() - this.#anchorWallTime;
80
+ const elapsedUs = elapsedMs * 1000;
81
+ // Subtract buffer delay and add anchor media time
82
+ return (elapsedUs - this.#bufferDelayMs * 1000 + this.#anchorMediaTime);
83
+ }
84
+ /**
85
+ * Determine whether a frame should be rendered, held, or discarded.
86
+ *
87
+ * - HOLD: Frame is ahead of clock by more than 66ms (wait for clock)
88
+ * - DISCARD: Frame is behind clock by more than 66ms (too late)
89
+ * - RENDER: Within +/-66ms tolerance window
90
+ *
91
+ * @param frameTimestamp - The presentation timestamp of the frame (microseconds)
92
+ * @returns "render", "hold", or "discard"
93
+ */
94
+ shouldRender(frameTimestamp) {
95
+ const positionUs = this.getPositionUs();
96
+ const deltaUs = (frameTimestamp - positionUs);
97
+ if (deltaUs > SYNC_TOLERANCE_US) {
98
+ // Frame is ahead - hold it, wait for clock to catch up
99
+ return "hold";
100
+ }
101
+ else if (deltaUs < -SYNC_TOLERANCE_US) {
102
+ // Frame is late - discard it
103
+ return "discard";
104
+ }
105
+ else {
106
+ // Within tolerance - render
107
+ return "render";
108
+ }
109
+ }
110
+ /**
111
+ * Get how long to wait before a held frame can be rendered.
112
+ *
113
+ * @param frameTimestamp - The presentation timestamp of the frame (microseconds)
114
+ * @returns Wait time in milliseconds, or 0 if frame should be rendered now
115
+ */
116
+ getWaitTimeMs(frameTimestamp) {
117
+ const positionUs = this.getPositionUs();
118
+ const deltaUs = frameTimestamp - positionUs;
119
+ if (deltaUs > 0) {
120
+ return deltaUs / 1000; // Convert us to ms
121
+ }
122
+ return 0;
123
+ }
124
+ /**
125
+ * Get the delta between a frame timestamp and current position.
126
+ * Positive = frame is ahead, negative = frame is behind.
127
+ *
128
+ * @param frameTimestamp - The presentation timestamp of the frame (microseconds)
129
+ * @returns Delta in microseconds
130
+ */
131
+ getDeltaUs(frameTimestamp) {
132
+ return frameTimestamp - this.getPositionUs();
133
+ }
134
+ /**
135
+ * Check if the anchor has been set.
136
+ */
137
+ hasAnchor() {
138
+ return this.#anchorWallTime !== undefined;
139
+ }
140
+ /**
141
+ * Get the MFU clock anchor (minimum PTS across all tracks).
142
+ * Used for A/V sync reference point.
143
+ */
144
+ getMfuClockAnchor() {
145
+ return this.#mfuClockAnchor;
146
+ }
147
+ /**
148
+ * Reset the clock anchor (e.g., on seek or error recovery).
149
+ */
150
+ reset() {
151
+ this.#anchorWallTime = undefined;
152
+ this.#anchorMediaTime = undefined;
153
+ this.#mfuClockAnchor = 0;
154
+ console.log("[SharedClock] Anchor reset");
155
+ }
156
+ /**
157
+ * Get debug info about the current clock state.
158
+ */
159
+ getDebugInfo() {
160
+ return {
161
+ hasAnchor: this.hasAnchor(),
162
+ positionUs: this.getPositionUs(),
163
+ mfuClockAnchor: this.#mfuClockAnchor,
164
+ bufferDelayMs: this.#bufferDelayMs,
165
+ };
166
+ }
167
+ }
168
+ /**
169
+ * Factory function to create a shared clock instance.
170
+ * Use this to ensure a single clock is shared across video and audio sources.
171
+ */
172
+ let globalClock;
173
+ export function getSharedClock(bufferDelayMs) {
174
+ if (!globalClock) {
175
+ globalClock = new SharedClock(bufferDelayMs);
176
+ }
177
+ return globalClock;
178
+ }
179
+ export function resetSharedClock() {
180
+ globalClock?.reset();
181
+ globalClock = undefined;
182
+ }
183
+ //# sourceMappingURL=clock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clock.js","sourceRoot":"","sources":["../src/clock.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * @blockcast/transport - DRIAD Discovery (RFC 8777)
3
+ *
4
+ * DRIAD (DNS Reverse IP AMT Discovery) discovers AMT relays based on the
5
+ * **source address**, NOT the multicast group. The source network operator
6
+ * configures DNS records for their source IPs to advertise which AMT relay(s)
7
+ * can tunnel their traffic.
8
+ *
9
+ * Example: For source 69.25.95.10 sending to group 232.0.0.1:
10
+ * Query: 10.95.25.69.amt.in-addr.arpa (source-based, NOT group-based)
11
+ */
12
+ import type { DRIADRelay } from "./types.js";
13
+ /**
14
+ * DRIAD (DNS Reverse IP AMT Discovery) - RFC 8777
15
+ * Discovers AMT relays based on source address via DNS lookups
16
+ */
17
+ export declare class DRIADDiscovery {
18
+ private cache;
19
+ private cacheExpiry;
20
+ /**
21
+ * Discover AMT relays for a multicast source address (RFC 8777)
22
+ *
23
+ * @param source - The multicast source IP address (NOT the group!)
24
+ * @returns Array of discovered AMT relays that can tunnel traffic from this source
25
+ */
26
+ discoverRelays(source: string): Promise<DRIADRelay[]>;
27
+ /**
28
+ * Query DRIAD proxy service (browser-compatible)
29
+ */
30
+ private queryDRIADProxy;
31
+ /**
32
+ * Query DNS-over-HTTPS for DRIAD relay based on source address (RFC 8777)
33
+ */
34
+ private queryDNSOverHTTPS;
35
+ /**
36
+ * Select best relay based on RTT measurements
37
+ */
38
+ selectBestRelay(relays: DRIADRelay[]): Promise<DRIADRelay | null>;
39
+ private measureRTT;
40
+ /**
41
+ * Clear the relay cache
42
+ */
43
+ clearCache(): void;
44
+ /**
45
+ * Get cached relays for a source address
46
+ */
47
+ getCached(source: string): DRIADRelay[] | undefined;
48
+ }
49
+ export declare const driadDiscovery: DRIADDiscovery;
50
+ //# sourceMappingURL=driad-discovery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"driad-discovery.d.ts","sourceRoot":"","sources":["../src/driad-discovery.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;;GAGG;AACH,qBAAa,cAAc;IAC1B,OAAO,CAAC,KAAK,CAAmC;IAChD,OAAO,CAAC,WAAW,CAAW;IAE9B;;;;;OAKG;IACG,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IA6B3D;;OAEG;YACW,eAAe;IAsC7B;;OAEG;YACW,iBAAiB;IAoC/B;;OAEG;IACG,eAAe,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;YAqBzD,UAAU;IAexB;;OAEG;IACH,UAAU,IAAI,IAAI;IAIlB;;OAEG;IACH,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,EAAE,GAAG,SAAS;CAGnD;AAGD,eAAO,MAAM,cAAc,gBAAuB,CAAC"}
@@ -0,0 +1,170 @@
1
+ /**
2
+ * @blockcast/transport - DRIAD Discovery (RFC 8777)
3
+ *
4
+ * DRIAD (DNS Reverse IP AMT Discovery) discovers AMT relays based on the
5
+ * **source address**, NOT the multicast group. The source network operator
6
+ * configures DNS records for their source IPs to advertise which AMT relay(s)
7
+ * can tunnel their traffic.
8
+ *
9
+ * Example: For source 69.25.95.10 sending to group 232.0.0.1:
10
+ * Query: 10.95.25.69.amt.in-addr.arpa (source-based, NOT group-based)
11
+ */
12
+ /**
13
+ * DRIAD (DNS Reverse IP AMT Discovery) - RFC 8777
14
+ * Discovers AMT relays based on source address via DNS lookups
15
+ */
16
+ export class DRIADDiscovery {
17
+ cache = new Map();
18
+ cacheExpiry = 300_000; // 5 minutes
19
+ /**
20
+ * Discover AMT relays for a multicast source address (RFC 8777)
21
+ *
22
+ * @param source - The multicast source IP address (NOT the group!)
23
+ * @returns Array of discovered AMT relays that can tunnel traffic from this source
24
+ */
25
+ async discoverRelays(source) {
26
+ // Check cache first
27
+ const cached = this.cache.get(source);
28
+ if (cached) {
29
+ return cached;
30
+ }
31
+ try {
32
+ // Convert source to DRIAD format (reversed octets)
33
+ const reversedSource = source.split(".").reverse().join(".");
34
+ const queryDomain = `${reversedSource}.amt.in-addr.arpa`;
35
+ console.log(`[DRIAD] Discovering relays for source ${source} via ${queryDomain}`);
36
+ // In browser, we use a DRIAD proxy service or DNS-over-HTTPS
37
+ const relays = await this.queryDRIADProxy(source);
38
+ if (relays.length > 0) {
39
+ this.cache.set(source, relays);
40
+ setTimeout(() => this.cache.delete(source), this.cacheExpiry);
41
+ }
42
+ return relays;
43
+ }
44
+ catch (error) {
45
+ console.warn(`[DRIAD] Discovery failed for source ${source}:`, error);
46
+ return [];
47
+ }
48
+ }
49
+ /**
50
+ * Query DRIAD proxy service (browser-compatible)
51
+ */
52
+ async queryDRIADProxy(source) {
53
+ // List of known DRIAD proxy endpoints
54
+ const proxyEndpoints = [
55
+ "https://driad.amt.net/api/v1/relays",
56
+ "https://multicast-gateway.cloudflare.com/driad",
57
+ ];
58
+ for (const endpoint of proxyEndpoints) {
59
+ try {
60
+ // Query by source address per RFC 8777
61
+ const response = await fetch(`${endpoint}?source=${encodeURIComponent(source)}`, {
62
+ method: "GET",
63
+ headers: { Accept: "application/json" },
64
+ signal: AbortSignal.timeout(5000),
65
+ });
66
+ if (response.ok) {
67
+ const data = (await response.json());
68
+ return data.relays || [];
69
+ }
70
+ }
71
+ catch {
72
+ // Try next proxy endpoint
73
+ }
74
+ }
75
+ // Fallback: Try DNS-over-HTTPS query
76
+ const dohRelay = await this.queryDNSOverHTTPS(source);
77
+ if (dohRelay) {
78
+ return [dohRelay];
79
+ }
80
+ // Final fallback to well-known AMT relays
81
+ return [
82
+ { host: "amt-relay.m2icast.net", port: 2268, priority: 10, weight: 100, asn: 0 },
83
+ { host: "amt.akamaistream.net", port: 2268, priority: 20, weight: 50, asn: 0 },
84
+ ];
85
+ }
86
+ /**
87
+ * Query DNS-over-HTTPS for DRIAD relay based on source address (RFC 8777)
88
+ */
89
+ async queryDNSOverHTTPS(source) {
90
+ try {
91
+ // Build DRIAD query name from source address (reversed octets)
92
+ const reversedSource = source.split(".").reverse().join(".");
93
+ const queryName = `${reversedSource}.amt.in-addr.arpa`;
94
+ const dohUrl = `https://cloudflare-dns.com/dns-query?name=${queryName}&type=A`;
95
+ const response = await fetch(dohUrl, {
96
+ headers: { Accept: "application/dns-json" },
97
+ signal: AbortSignal.timeout(3000),
98
+ });
99
+ if (!response.ok) {
100
+ return null;
101
+ }
102
+ const result = (await response.json());
103
+ // Extract first A/AAAA record
104
+ if (result.Answer && result.Answer.length > 0) {
105
+ return {
106
+ host: result.Answer[0].data,
107
+ port: 2268,
108
+ priority: 10,
109
+ weight: 100,
110
+ asn: 0,
111
+ };
112
+ }
113
+ return null;
114
+ }
115
+ catch (error) {
116
+ console.warn("[DRIAD] DNS-over-HTTPS query failed:", error);
117
+ return null;
118
+ }
119
+ }
120
+ /**
121
+ * Select best relay based on RTT measurements
122
+ */
123
+ async selectBestRelay(relays) {
124
+ if (relays.length === 0)
125
+ return null;
126
+ // Measure RTT to each relay
127
+ const measured = await Promise.all(relays.map(async (relay) => {
128
+ const rtt = await this.measureRTT(relay.host, relay.port);
129
+ return { ...relay, rtt };
130
+ }));
131
+ // Sort by priority, then by RTT
132
+ measured.sort((a, b) => {
133
+ if (a.priority !== b.priority)
134
+ return a.priority - b.priority;
135
+ return (a.rtt || Infinity) - (b.rtt || Infinity);
136
+ });
137
+ console.log(`[DRIAD] Selected relay: ${measured[0].host} (RTT: ${measured[0].rtt}ms)`);
138
+ return measured[0];
139
+ }
140
+ async measureRTT(host, _port) {
141
+ const start = performance.now();
142
+ try {
143
+ // Use HTTP/HTTPS probe since we can't do UDP from browser
144
+ await fetch(`https://${host}/health`, {
145
+ method: "HEAD",
146
+ signal: AbortSignal.timeout(2000),
147
+ mode: "no-cors",
148
+ });
149
+ return performance.now() - start;
150
+ }
151
+ catch {
152
+ return Infinity;
153
+ }
154
+ }
155
+ /**
156
+ * Clear the relay cache
157
+ */
158
+ clearCache() {
159
+ this.cache.clear();
160
+ }
161
+ /**
162
+ * Get cached relays for a source address
163
+ */
164
+ getCached(source) {
165
+ return this.cache.get(source);
166
+ }
167
+ }
168
+ // Export singleton instance
169
+ export const driadDiscovery = new DRIADDiscovery();
170
+ //# sourceMappingURL=driad-discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"driad-discovery.js","sourceRoot":"","sources":["../src/driad-discovery.ts"],"names":[],"mappings":"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"}