@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.
- package/dist/abr-controller.d.ts +94 -0
- package/dist/abr-controller.d.ts.map +1 -0
- package/dist/abr-controller.js +174 -0
- package/dist/abr-controller.js.map +1 -0
- package/dist/amt-gateway.d.ts +160 -0
- package/dist/amt-gateway.d.ts.map +1 -0
- package/dist/amt-gateway.js +390 -0
- package/dist/amt-gateway.js.map +1 -0
- package/dist/clock.d.ts +104 -0
- package/dist/clock.d.ts.map +1 -0
- package/dist/clock.js +183 -0
- package/dist/clock.js.map +1 -0
- package/dist/driad-discovery.d.ts +50 -0
- package/dist/driad-discovery.d.ts.map +1 -0
- package/dist/driad-discovery.js +170 -0
- package/dist/driad-discovery.js.map +1 -0
- package/dist/fec-client.d.ts +442 -0
- package/dist/fec-client.d.ts.map +1 -0
- package/dist/fec-client.js +784 -0
- package/dist/fec-client.js.map +1 -0
- package/dist/fec-client.test.d.ts +8 -0
- package/dist/fec-client.test.d.ts.map +1 -0
- package/dist/fec-client.test.js +112 -0
- package/dist/fec-client.test.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/transport-manager.d.ts +114 -0
- package/dist/transport-manager.d.ts.map +1 -0
- package/dist/transport-manager.js +396 -0
- package/dist/transport-manager.js.map +1 -0
- package/dist/types.d.ts +356 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +85 -0
- package/dist/types.js.map +1 -0
- package/package.json +60 -0
- package/src/abr-controller.ts +227 -0
- package/src/amt-gateway.ts +511 -0
- package/src/clock.ts +212 -0
- package/src/driad-discovery.ts +193 -0
- package/src/fec-client.test.ts +140 -0
- package/src/fec-client.ts +1097 -0
- package/src/index.ts +122 -0
- package/src/transport-manager.ts +460 -0
- 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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"}
|