openrtc 0.1.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 (122) hide show
  1. package/README.md +106 -0
  2. package/dist/OpenRTC.d.ts +71 -0
  3. package/dist/OpenRTC.js +130 -0
  4. package/dist/api/MediaTransport.d.ts +7 -0
  5. package/dist/api/MediaTransport.js +262 -0
  6. package/dist/api/PlutoPeerConnection.d.ts +50 -0
  7. package/dist/api/PlutoPeerConnection.js +434 -0
  8. package/dist/api/PlutoWebSocket.d.ts +24 -0
  9. package/dist/api/PlutoWebSocket.js +113 -0
  10. package/dist/api/PlutoWebTransport.d.ts +29 -0
  11. package/dist/api/PlutoWebTransport.js +105 -0
  12. package/dist/auth/host.d.ts +73 -0
  13. package/dist/auth/host.js +513 -0
  14. package/dist/auth/index.d.ts +1 -0
  15. package/dist/auth/index.js +1 -0
  16. package/dist/auth/internal.d.ts +1 -0
  17. package/dist/auth/internal.js +1 -0
  18. package/dist/compat/index.d.ts +20 -0
  19. package/dist/compat/index.js +20 -0
  20. package/dist/core/Client.d.ts +352 -0
  21. package/dist/core/Client.js +3689 -0
  22. package/dist/core/Connection.d.ts +67 -0
  23. package/dist/core/Connection.js +404 -0
  24. package/dist/core/IEngineBridge.d.ts +18 -0
  25. package/dist/core/IEngineBridge.js +1 -0
  26. package/dist/core/ISignalingTransport.d.ts +13 -0
  27. package/dist/core/ISignalingTransport.js +1 -0
  28. package/dist/core/PeerLifecycleManager.d.ts +43 -0
  29. package/dist/core/PeerLifecycleManager.js +330 -0
  30. package/dist/core/PeerProjectionStore.d.ts +43 -0
  31. package/dist/core/PeerProjectionStore.js +327 -0
  32. package/dist/core/PlutoRTC.d.ts +13 -0
  33. package/dist/core/PlutoRTC.js +26 -0
  34. package/dist/core/Room.d.ts +47 -0
  35. package/dist/core/Room.js +376 -0
  36. package/dist/core/Signaling.d.ts +39 -0
  37. package/dist/core/Signaling.js +147 -0
  38. package/dist/core/bridges/BridgeCodecs.d.ts +19 -0
  39. package/dist/core/bridges/BridgeCodecs.js +317 -0
  40. package/dist/core/bridges/BrowserFirestoreRooms.d.ts +36 -0
  41. package/dist/core/bridges/BrowserFirestoreRooms.js +169 -0
  42. package/dist/core/bridges/BrowserFirestoreSignaling.d.ts +111 -0
  43. package/dist/core/bridges/BrowserFirestoreSignaling.js +1010 -0
  44. package/dist/core/bridges/NativeIpcBridge.d.ts +156 -0
  45. package/dist/core/bridges/NativeIpcBridge.js +1472 -0
  46. package/dist/core/bridges/RtdbPresence.d.ts +122 -0
  47. package/dist/core/bridges/RtdbPresence.js +201 -0
  48. package/dist/core/bridges/TauriBridge.d.ts +8 -0
  49. package/dist/core/bridges/TauriBridge.js +7 -0
  50. package/dist/core/bridges/WasmBridge.d.ts +233 -0
  51. package/dist/core/bridges/WasmBridge.js +1811 -0
  52. package/dist/core/config.d.ts +31 -0
  53. package/dist/core/config.js +138 -0
  54. package/dist/core/transports/ITransport.d.ts +12 -0
  55. package/dist/core/transports/ITransport.js +1 -0
  56. package/dist/core/transports/LocalDaemonBridge.d.ts +41 -0
  57. package/dist/core/transports/LocalDaemonBridge.js +227 -0
  58. package/dist/core/transports/TauriSignaling.d.ts +28 -0
  59. package/dist/core/transports/TauriSignaling.js +373 -0
  60. package/dist/core/transports/TransportFactory.d.ts +9 -0
  61. package/dist/core/transports/TransportFactory.js +59 -0
  62. package/dist/core/transports/WasmSignaling.d.ts +78 -0
  63. package/dist/core/transports/WasmSignaling.js +522 -0
  64. package/dist/core/transports/WebRTCTransport.d.ts +23 -0
  65. package/dist/core/transports/WebRTCTransport.js +132 -0
  66. package/dist/core/trust.d.ts +23 -0
  67. package/dist/core/trust.js +99 -0
  68. package/dist/core/types.d.ts +559 -0
  69. package/dist/core/types.js +19 -0
  70. package/dist/index.d.ts +3 -0
  71. package/dist/index.js +3 -0
  72. package/dist/moq/MoQMediaTransport.d.ts +6 -0
  73. package/dist/moq/MoQMediaTransport.js +106 -0
  74. package/dist/moq/MoQPublisher.d.ts +22 -0
  75. package/dist/moq/MoQPublisher.js +179 -0
  76. package/dist/moq/MoQSession.d.ts +35 -0
  77. package/dist/moq/MoQSession.js +166 -0
  78. package/dist/moq/MoQSubscriber.d.ts +19 -0
  79. package/dist/moq/MoQSubscriber.js +235 -0
  80. package/dist/moq/common.d.ts +47 -0
  81. package/dist/moq/common.js +304 -0
  82. package/dist/moq/messages.d.ts +105 -0
  83. package/dist/moq/messages.js +367 -0
  84. package/dist/presets/plutoHosted.d.ts +13 -0
  85. package/dist/presets/plutoHosted.js +18 -0
  86. package/dist/runtime/DelegatingRuntimeAdapter.d.ts +68 -0
  87. package/dist/runtime/DelegatingRuntimeAdapter.js +250 -0
  88. package/dist/runtime/IpcRuntimeAdapter.d.ts +9 -0
  89. package/dist/runtime/IpcRuntimeAdapter.js +13 -0
  90. package/dist/runtime/RuntimeClient.d.ts +257 -0
  91. package/dist/runtime/RuntimeClient.js +1484 -0
  92. package/dist/runtime/TauriRuntimeAdapter.d.ts +1 -0
  93. package/dist/runtime/TauriRuntimeAdapter.js +1 -0
  94. package/dist/runtime/WasmRuntimeAdapter.d.ts +6 -0
  95. package/dist/runtime/WasmRuntimeAdapter.js +11 -0
  96. package/dist/runtime/factories.d.ts +24 -0
  97. package/dist/runtime/factories.js +66 -0
  98. package/dist/runtime/index.d.ts +6 -0
  99. package/dist/runtime/index.js +5 -0
  100. package/dist/runtime/ipc.d.ts +8 -0
  101. package/dist/runtime/ipc.js +65 -0
  102. package/dist/runtime/ipcContract.d.ts +46 -0
  103. package/dist/runtime/ipcContract.js +1 -0
  104. package/dist/runtime/tauri.d.ts +16 -0
  105. package/dist/runtime/tauri.js +22 -0
  106. package/dist/runtime/types.d.ts +110 -0
  107. package/dist/runtime/types.js +1 -0
  108. package/dist/transports/TauriTransport.d.ts +17 -0
  109. package/dist/transports/TauriTransport.js +49 -0
  110. package/dist/transports/WebTransport.d.ts +23 -0
  111. package/dist/transports/WebTransport.js +71 -0
  112. package/dist/utils/debug.d.ts +4 -0
  113. package/dist/utils/debug.js +33 -0
  114. package/dist/utils/runtime.d.ts +1 -0
  115. package/dist/utils/runtime.js +10 -0
  116. package/package.json +97 -0
  117. package/wasm/README.md +64 -0
  118. package/wasm/openrtc.d.ts +284 -0
  119. package/wasm/openrtc.js +2132 -0
  120. package/wasm/openrtc_bg.wasm +0 -0
  121. package/wasm/openrtc_bg.wasm.d.ts +110 -0
  122. package/wasm/package.json +16 -0
@@ -0,0 +1,235 @@
1
+ import { StreamBuffer } from './common';
2
+ import { MsgType } from './messages';
3
+ export class MoQSubscriber {
4
+ constructor(session, onObject) {
5
+ this.aliasMap = new Map();
6
+ this.nextAlias = 0;
7
+ this.nextSubscribeId = 1;
8
+ // Track pending subscriptions for retry on 404
9
+ this.pendingSubscriptions = new Map();
10
+ // Buffered delivery state per track alias
11
+ this.deliveryState = new Map();
12
+ // Map subscribeId -> trackAlias for active subscriptions
13
+ this.activeSubscriptions = new Map();
14
+ this.session = session;
15
+ this.onObject = onObject;
16
+ }
17
+ async subscribe(namespace, name) {
18
+ // Send SUBSCRIBE
19
+ const trackAlias = this.nextAlias++;
20
+ const subscribeId = this.nextSubscribeId++;
21
+ this.aliasMap.set(trackAlias, { namespace, name });
22
+ console.log(`[MoQ] Subscribing to ${namespace}/${name} with Alias ${trackAlias} ID ${subscribeId}`);
23
+ const msg = {
24
+ type: MsgType.SUBSCRIBE,
25
+ subscribeId: subscribeId,
26
+ trackAlias: trackAlias,
27
+ trackNamespace: namespace,
28
+ trackName: name
29
+ };
30
+ this.pendingSubscriptions.set(subscribeId, { namespace, name, trackAlias });
31
+ this.activeSubscriptions.set(subscribeId, trackAlias);
32
+ await this.session.sendControlMessage(msg);
33
+ }
34
+ onSubscribeOk(subscribeId) {
35
+ this.pendingSubscriptions.delete(subscribeId);
36
+ }
37
+ async onSubscribeError(subscribeId, code) {
38
+ const pending = this.pendingSubscriptions.get(subscribeId);
39
+ this.pendingSubscriptions.delete(subscribeId);
40
+ if (!pending)
41
+ return;
42
+ if (code === 404) {
43
+ // Peer hasn't announced yet — retry after delay
44
+ console.log(`[MoQ] Will retry subscription to ${pending.namespace}/${pending.name} in 2s...`);
45
+ await new Promise(r => setTimeout(r, 2000));
46
+ // Re-subscribe with a NEW subscribeId but SAME trackAlias
47
+ const newId = this.nextSubscribeId++;
48
+ const msg = {
49
+ type: MsgType.SUBSCRIBE,
50
+ subscribeId: newId,
51
+ trackAlias: pending.trackAlias,
52
+ trackNamespace: pending.namespace,
53
+ trackName: pending.name
54
+ };
55
+ this.pendingSubscriptions.set(newId, pending);
56
+ console.log(`[MoQ] Retrying subscription to ${pending.namespace}/${pending.name} with ID ${newId}`);
57
+ await this.session.sendControlMessage(msg);
58
+ }
59
+ }
60
+ onSubscribeDone(subscribeId, code, reason) {
61
+ const alias = this.activeSubscriptions.get(subscribeId);
62
+ if (alias !== undefined) {
63
+ console.warn(`[MoQ] Subscription ${subscribeId} (alias ${alias}) ended: ${code} ${reason}`);
64
+ this.activeSubscriptions.delete(subscribeId);
65
+ this.aliasMap.delete(alias);
66
+ this.deliveryState.delete(alias);
67
+ }
68
+ else {
69
+ console.warn(`[MoQ] Subscription ${subscribeId} ended (unknown alias): ${code} ${reason}`);
70
+ }
71
+ }
72
+ // Wake the delivery loop for a track alias so it can check for new frames
73
+ wakeDelivery(trackAlias) {
74
+ const state = this.deliveryState.get(trackAlias);
75
+ if (state?.notifyResolve) {
76
+ const resolve = state.notifyResolve;
77
+ state.notifyResolve = null;
78
+ resolve();
79
+ }
80
+ }
81
+ // Delivery loop: drains groups in sequence order, one per track alias.
82
+ // Matches the reference Consumer.next() pattern — oldest group first,
83
+ // advance only when a group is fully consumed.
84
+ async runDeliveryLoop(trackAlias) {
85
+ const state = this.deliveryState.get(trackAlias);
86
+ if (!state)
87
+ return;
88
+ const info = this.aliasMap.get(trackAlias);
89
+ const ns = info ? info.namespace : 'unknown';
90
+ const name = info ? info.name : 'unknown';
91
+ while (true) {
92
+ const group = state.groups.get(state.nextDeliverGroupId);
93
+ if (group) {
94
+ // Deliver all currently buffered frames from this group
95
+ while (group.frames.length > 0) {
96
+ const frame = group.frames.shift();
97
+ this.onObject(ns, name, frame.payload, { groupId: frame.groupId, objectId: frame.objectId });
98
+ }
99
+ if (group.done) {
100
+ // Group fully consumed and reader finished — advance to next
101
+ state.groups.delete(state.nextDeliverGroupId);
102
+ state.nextDeliverGroupId++;
103
+ continue; // Immediately check next group
104
+ }
105
+ }
106
+ else {
107
+ // Check if we're behind — a newer group exists but our current one never arrived.
108
+ // Skip forward to avoid blocking forever (handles gaps from relay).
109
+ let minFutureGroup = null;
110
+ for (const gid of state.groups.keys()) {
111
+ if (gid > state.nextDeliverGroupId) {
112
+ if (minFutureGroup === null || gid < minFutureGroup) {
113
+ minFutureGroup = gid;
114
+ }
115
+ }
116
+ }
117
+ // Only skip if there are at least 2 newer groups buffered
118
+ // (the missing group is clearly not coming)
119
+ if (minFutureGroup !== null && minFutureGroup > state.nextDeliverGroupId + 1) {
120
+ console.warn(`[MoQ] Skipping missing group ${state.nextDeliverGroupId}, jumping to ${minFutureGroup}`);
121
+ state.nextDeliverGroupId = minFutureGroup;
122
+ continue;
123
+ }
124
+ }
125
+ // Nothing to deliver right now — wait for a notification
126
+ await new Promise(resolve => {
127
+ state.notifyResolve = resolve;
128
+ });
129
+ }
130
+ }
131
+ // Process incoming UniStream — each group reads concurrently into a buffer
132
+ async handleIncomingUniStream(stream) {
133
+ const reader = stream.getReader();
134
+ const sb = new StreamBuffer();
135
+ // These are set during header parsing and used in the finally block
136
+ let trackAlias = -1;
137
+ let groupId = -1;
138
+ let groupBuffer = null;
139
+ try {
140
+ // Read Stream Header
141
+ while (true) {
142
+ const { done, value } = await reader.read();
143
+ if (done)
144
+ return;
145
+ if (value)
146
+ sb.append(value);
147
+ try {
148
+ sb.checkpoint();
149
+ const type = Number(sb.readVarInt());
150
+ const subscribeId = Number(sb.readVarInt()); // Draft-07: Subscribe ID
151
+ trackAlias = Number(sb.readVarInt());
152
+ groupId = Number(sb.readVarInt());
153
+ sb.readVarInt(); // Subgroup ID
154
+ sb.readUint8(); // Priority (8-bit)
155
+ sb.commit();
156
+ break;
157
+ }
158
+ catch (e) {
159
+ if (e.message === 'EOF') {
160
+ sb.rewind();
161
+ continue; // Read more
162
+ }
163
+ throw e;
164
+ }
165
+ }
166
+ // Get or create delivery state for this track alias
167
+ let state = this.deliveryState.get(trackAlias);
168
+ if (!state) {
169
+ state = {
170
+ groups: new Map(),
171
+ nextDeliverGroupId: groupId,
172
+ notifyResolve: null,
173
+ loopRunning: false,
174
+ };
175
+ this.deliveryState.set(trackAlias, state);
176
+ }
177
+ // Skip truly stale groups (already delivered past this point)
178
+ if (groupId < state.nextDeliverGroupId) {
179
+ reader.cancel().catch(() => { });
180
+ return;
181
+ }
182
+ // Create buffer for this group
183
+ groupBuffer = { frames: [], done: false };
184
+ state.groups.set(groupId, groupBuffer);
185
+ // Start delivery loop if not already running
186
+ if (!state.loopRunning) {
187
+ state.loopRunning = true;
188
+ this.runDeliveryLoop(trackAlias);
189
+ }
190
+ // Read Objects from this group into buffer (runs concurrently with other groups)
191
+ while (true) {
192
+ if (!sb.has(1)) {
193
+ const { done, value } = await reader.read();
194
+ if (done)
195
+ break;
196
+ if (value)
197
+ sb.append(value);
198
+ }
199
+ try {
200
+ sb.checkpoint();
201
+ const objectId = Number(sb.readVarInt());
202
+ const length = Number(sb.readVarInt());
203
+ const payload = sb.readBytes(length);
204
+ sb.commit();
205
+ // Buffer the frame
206
+ groupBuffer.frames.push({ payload, groupId, objectId });
207
+ // Wake delivery loop to check for new frames
208
+ this.wakeDelivery(trackAlias);
209
+ }
210
+ catch (e) {
211
+ if (e.message === 'EOF') {
212
+ sb.rewind();
213
+ const { done, value } = await reader.read();
214
+ if (done)
215
+ break;
216
+ if (value)
217
+ sb.append(value);
218
+ continue;
219
+ }
220
+ throw e;
221
+ }
222
+ }
223
+ }
224
+ catch (e) {
225
+ console.error("Subscriber Stream Error", e);
226
+ }
227
+ finally {
228
+ // Mark this group as done so the delivery loop can advance past it
229
+ if (groupBuffer) {
230
+ groupBuffer.done = true;
231
+ this.wakeDelivery(trackAlias);
232
+ }
233
+ }
234
+ }
235
+ }
@@ -0,0 +1,47 @@
1
+ export declare class BufferReader {
2
+ private view;
3
+ private offset;
4
+ constructor(buffer: Uint8Array);
5
+ get eof(): boolean;
6
+ remaining(): number;
7
+ readUint8(): number;
8
+ readVarInt(): number;
9
+ readVarIntBig(): bigint;
10
+ readString(): string;
11
+ readBytes(len: number): Uint8Array;
12
+ }
13
+ export declare class StreamBuffer {
14
+ private buffer;
15
+ private offset;
16
+ constructor();
17
+ append(chunk: Uint8Array): void;
18
+ peekVarInt(): {
19
+ val: number | bigint;
20
+ len: number;
21
+ } | null;
22
+ advance(bytes: number): void;
23
+ has(bytes: number): boolean;
24
+ readBytes(len: number): Uint8Array;
25
+ readVarInt(): number | bigint;
26
+ readUint8(): number;
27
+ private checkpoints;
28
+ checkpoint(): void;
29
+ commit(): void;
30
+ rewind(): void;
31
+ readString(): string;
32
+ }
33
+ export declare class BufferWriter {
34
+ private chunks;
35
+ private length;
36
+ writeUint8(val: number): void;
37
+ writeVarInt(val: number | bigint): void;
38
+ writeString(str: string): void;
39
+ writeBytes(bytes: Uint8Array): void;
40
+ toUint8Array(): Uint8Array;
41
+ }
42
+ export declare class Mutex {
43
+ private mutex;
44
+ lock(): Promise<() => void>;
45
+ dispatch<T>(fn: (() => T | PromiseLike<T>)): Promise<T>;
46
+ }
47
+ export declare const DEFAULT_MOQ_RELAY_URL = "https://relay.cloudflare.mediaoverquic.com:443/moq";
@@ -0,0 +1,304 @@
1
+ export class BufferReader {
2
+ constructor(buffer) {
3
+ this.offset = 0;
4
+ this.view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
5
+ }
6
+ get eof() {
7
+ return this.offset >= this.view.byteLength;
8
+ }
9
+ remaining() {
10
+ return this.view.byteLength - this.offset;
11
+ }
12
+ readUint8() {
13
+ if (this.remaining() < 1)
14
+ throw new Error("EOF");
15
+ const val = this.view.getUint8(this.offset);
16
+ this.offset += 1;
17
+ return val;
18
+ }
19
+ readVarInt() {
20
+ const first = this.readUint8();
21
+ const prefix = first >> 6;
22
+ const b1 = first & 0x3f;
23
+ if (prefix === 0) {
24
+ return b1;
25
+ }
26
+ else if (prefix === 1) {
27
+ if (this.remaining() < 1)
28
+ throw new Error("EOF");
29
+ const b2 = this.readUint8();
30
+ return (b1 << 8) | b2;
31
+ }
32
+ else if (prefix === 2) {
33
+ if (this.remaining() < 3)
34
+ throw new Error("EOF");
35
+ const b2 = this.readUint8();
36
+ const b3 = this.readUint8();
37
+ const b4 = this.readUint8();
38
+ return (b1 << 24) | (b2 << 16) | (b3 << 8) | b4;
39
+ }
40
+ else {
41
+ // Priority 3: 8 bytes (62 bits)
42
+ // JS numbers are doubles, safe integer is 2^53.
43
+ // 62 bits might overflow JS number. For now we read as BigInt if needed or clamp?
44
+ // Let's use BigInt implementation for 8 bytes to be safe, then cast to Number.
45
+ if (this.remaining() < 7)
46
+ throw new Error("EOF");
47
+ // We already read byte 1 (b1). We need 7 more.
48
+ // But DataView.getBigUint64 expects 8 bytes.
49
+ // Let's manually reconstruct.
50
+ // b1 is the top 6 bits (technically top 6 bits of usage).
51
+ // This is getting messy. Let's peek generic Logic.
52
+ // Re-implement simpler:
53
+ // Backtrack 1 byte
54
+ this.offset--;
55
+ return Number(this.readVarIntBig());
56
+ }
57
+ }
58
+ readVarIntBig() {
59
+ if (this.remaining() < 1)
60
+ throw new Error("EOF");
61
+ const first = this.view.getUint8(this.offset);
62
+ const prefix = first >> 6;
63
+ if (prefix === 0) {
64
+ this.offset++;
65
+ return BigInt(first & 0x3f);
66
+ }
67
+ else if (prefix === 1) {
68
+ if (this.remaining() < 2)
69
+ throw new Error("EOF");
70
+ const val = this.view.getUint16(this.offset) & 0x3fff;
71
+ this.offset += 2;
72
+ return BigInt(val);
73
+ }
74
+ else if (prefix === 2) {
75
+ if (this.remaining() < 4)
76
+ throw new Error("EOF");
77
+ const val = this.view.getUint32(this.offset) & 0x3fffffff;
78
+ this.offset += 4;
79
+ return BigInt(val);
80
+ }
81
+ else {
82
+ if (this.remaining() < 8)
83
+ throw new Error("EOF");
84
+ const val = this.view.getBigUint64(this.offset) & BigInt("0x3fffffffffffffff");
85
+ this.offset += 8;
86
+ return val;
87
+ }
88
+ }
89
+ readString() {
90
+ const len = this.readVarInt();
91
+ if (this.remaining() < len)
92
+ throw new Error("EOF");
93
+ const bytes = new Uint8Array(this.view.buffer, this.view.byteOffset + this.offset, len);
94
+ this.offset += len;
95
+ return new TextDecoder().decode(bytes);
96
+ }
97
+ readBytes(len) {
98
+ if (this.remaining() < len)
99
+ throw new Error("EOF");
100
+ const bytes = new Uint8Array(this.view.buffer, this.view.byteOffset + this.offset, len);
101
+ this.offset += len;
102
+ // Should we copy? If not, it references original buffer. usage safety.
103
+ // Let's maintain reference for speed.
104
+ return bytes;
105
+ }
106
+ }
107
+ export class StreamBuffer {
108
+ constructor() {
109
+ this.offset = 0;
110
+ this.checkpoints = [];
111
+ this.buffer = new Uint8Array(0);
112
+ }
113
+ append(chunk) {
114
+ const newBuf = new Uint8Array(this.buffer.length + chunk.length);
115
+ newBuf.set(this.buffer);
116
+ newBuf.set(chunk, this.buffer.length);
117
+ this.buffer = newBuf;
118
+ }
119
+ // Try to read a VarInt without advancing if fails
120
+ peekVarInt() {
121
+ if (this.buffer.length - this.offset < 1)
122
+ return null;
123
+ const first = this.buffer[this.offset];
124
+ const prefix = first >> 6;
125
+ const b1 = first & 0x3f;
126
+ if (prefix === 0) {
127
+ return { val: b1, len: 1 };
128
+ }
129
+ else if (prefix === 1) {
130
+ if (this.buffer.length - this.offset < 2)
131
+ return null;
132
+ const view = new DataView(this.buffer.buffer, this.buffer.byteOffset, this.buffer.byteLength);
133
+ const val = view.getUint16(this.offset) & 0x3fff;
134
+ return { val, len: 2 };
135
+ }
136
+ else if (prefix === 2) {
137
+ if (this.buffer.length - this.offset < 4)
138
+ return null;
139
+ const view = new DataView(this.buffer.buffer, this.buffer.byteOffset, this.buffer.byteLength);
140
+ const val = view.getUint32(this.offset) & 0x3fffffff;
141
+ return { val, len: 4 };
142
+ }
143
+ else {
144
+ if (this.buffer.length - this.offset < 8)
145
+ return null;
146
+ const view = new DataView(this.buffer.buffer, this.buffer.byteOffset, this.buffer.byteLength);
147
+ const val = view.getBigUint64(this.offset) & BigInt("0x3fffffffffffffff");
148
+ return { val, len: 8 };
149
+ }
150
+ }
151
+ // Commit a read operation (advance offset)
152
+ advance(bytes) {
153
+ this.offset += bytes;
154
+ // Optimization: slice buffer if offset gets too large?
155
+ if (this.offset > 1024 && this.offset > this.buffer.length / 2) {
156
+ this.buffer = this.buffer.slice(this.offset);
157
+ this.offset = 0;
158
+ }
159
+ }
160
+ // Check if we have N bytes available from current offset
161
+ has(bytes) {
162
+ return (this.buffer.length - this.offset) >= bytes;
163
+ }
164
+ readBytes(len) {
165
+ if (!this.has(len))
166
+ throw new Error("EOF");
167
+ const res = this.buffer.slice(this.offset, this.offset + len);
168
+ this.advance(len);
169
+ return res;
170
+ }
171
+ readVarInt() {
172
+ const res = this.peekVarInt();
173
+ if (!res)
174
+ throw new Error("EOF");
175
+ this.advance(res.len);
176
+ return res.val;
177
+ }
178
+ readUint8() {
179
+ if (!this.has(1))
180
+ throw new Error("EOF");
181
+ const val = this.buffer[this.offset];
182
+ this.advance(1);
183
+ return val;
184
+ }
185
+ checkpoint() {
186
+ this.checkpoints.push(this.offset);
187
+ }
188
+ commit() {
189
+ this.checkpoints.pop();
190
+ // If no more checkpoints, we could compact buffer logic here if desired
191
+ }
192
+ rewind() {
193
+ const cp = this.checkpoints.pop();
194
+ if (cp !== undefined) {
195
+ this.offset = cp;
196
+ }
197
+ else {
198
+ throw new Error("No checkpoint to rewind to");
199
+ }
200
+ }
201
+ readString() {
202
+ // Read Length (VarInt)
203
+ const lenRes = this.peekVarInt();
204
+ if (!lenRes)
205
+ throw new Error("EOF");
206
+ const strLen = Number(lenRes.val);
207
+ const totalLen = lenRes.len + strLen;
208
+ if (!this.has(totalLen))
209
+ throw new Error("EOF");
210
+ this.advance(lenRes.len); // Parse Len
211
+ const bytes = this.readBytes(strLen);
212
+ return new TextDecoder().decode(bytes);
213
+ }
214
+ }
215
+ export class BufferWriter {
216
+ constructor() {
217
+ this.chunks = [];
218
+ this.length = 0;
219
+ }
220
+ writeUint8(val) {
221
+ const buf = new Uint8Array(1);
222
+ buf[0] = val;
223
+ this.chunks.push(buf);
224
+ this.length += 1;
225
+ }
226
+ writeVarInt(val) {
227
+ const v = BigInt(val);
228
+ if (v < 64n) { // 6 bits, 1 byte
229
+ this.writeUint8(Number(v));
230
+ }
231
+ else if (v < 16384n) { // 14 bits, 2 bytes
232
+ const buf = new Uint8Array(2);
233
+ const view = new DataView(buf.buffer);
234
+ view.setUint16(0, Number(v) | 0x4000);
235
+ this.chunks.push(buf);
236
+ this.length += 2;
237
+ }
238
+ else if (v < 1073741824n) { // 30 bits, 4 bytes
239
+ const buf = new Uint8Array(4);
240
+ const view = new DataView(buf.buffer);
241
+ view.setUint32(0, Number(v) | 0x80000000);
242
+ this.chunks.push(buf);
243
+ this.length += 4;
244
+ }
245
+ else { // 62 bits, 8 bytes
246
+ const buf = new Uint8Array(8);
247
+ const view = new DataView(buf.buffer);
248
+ // 0xc0 = 1100 0000
249
+ // We rely on BigInt bitwise or
250
+ // But setBigUint64 takes bigint.
251
+ // 0xc0 << 56 is outside number range.
252
+ // BigInt literal
253
+ const magic = 0xc000000000000000n;
254
+ view.setBigUint64(0, v | magic);
255
+ this.chunks.push(buf);
256
+ this.length += 8;
257
+ }
258
+ }
259
+ writeString(str) {
260
+ const bytes = new TextEncoder().encode(str);
261
+ this.writeVarInt(bytes.length);
262
+ this.chunks.push(bytes);
263
+ this.length += bytes.length;
264
+ }
265
+ writeBytes(bytes) {
266
+ this.chunks.push(bytes);
267
+ this.length += bytes.byteLength;
268
+ }
269
+ toUint8Array() {
270
+ const res = new Uint8Array(this.length);
271
+ let offset = 0;
272
+ for (const chunk of this.chunks) {
273
+ res.set(chunk, offset);
274
+ offset += chunk.length;
275
+ }
276
+ return res;
277
+ }
278
+ }
279
+ export class Mutex {
280
+ constructor() {
281
+ this.mutex = Promise.resolve();
282
+ }
283
+ lock() {
284
+ let unlock = () => { };
285
+ const next = new Promise(resolve => {
286
+ unlock = resolve;
287
+ });
288
+ // The caller waits for the previous operation to complete
289
+ const permission = this.mutex.then(() => unlock);
290
+ // Update the mutex to wait for the new operation
291
+ this.mutex = this.mutex.then(() => next);
292
+ return permission;
293
+ }
294
+ async dispatch(fn) {
295
+ const unlock = await this.lock();
296
+ try {
297
+ return await Promise.resolve(fn());
298
+ }
299
+ finally {
300
+ unlock();
301
+ }
302
+ }
303
+ }
304
+ export const DEFAULT_MOQ_RELAY_URL = "https://relay.cloudflare.mediaoverquic.com:443/moq";
@@ -0,0 +1,105 @@
1
+ import { StreamBuffer } from './common';
2
+ export declare enum MsgType {
3
+ SUBSCRIBE = 3,
4
+ SUBSCRIBE_OK = 4,
5
+ SUBSCRIBE_ERROR = 5,
6
+ ANNOUNCE = 6,
7
+ ANNOUNCE_OK = 7,
8
+ ANNOUNCE_ERROR = 8,
9
+ UNANNOUNCE = 9,
10
+ UNSUBSCRIBE = 10,
11
+ SUBSCRIBE_DONE = 11,
12
+ ANNOUNCE_CANCEL = 12,
13
+ TRACK_STATUS_REQUEST = 13,
14
+ TRACK_STATUS = 14,
15
+ GOAWAY = 16,
16
+ MAX_SUBSCRIBE_ID = 21,
17
+ CLIENT_SETUP = 64,
18
+ SERVER_SETUP = 65
19
+ }
20
+ export declare const SETUP_ROLE_PARAMETER_KEY = 0;
21
+ export declare const SETUP_PATH_PARAMETER_KEY = 1;
22
+ export interface MoQMessage {
23
+ type: MsgType;
24
+ }
25
+ export interface MsgClientSetup extends MoQMessage {
26
+ type: MsgType.CLIENT_SETUP;
27
+ supportedVersions: number[];
28
+ role: number;
29
+ path?: string;
30
+ }
31
+ export interface MsgServerSetup extends MoQMessage {
32
+ type: MsgType.SERVER_SETUP;
33
+ selectedVersion: number;
34
+ role: number;
35
+ }
36
+ export interface MsgSubscribe extends MoQMessage {
37
+ type: MsgType.SUBSCRIBE;
38
+ subscribeId: number;
39
+ trackAlias: number;
40
+ trackNamespace: string;
41
+ trackName: string;
42
+ subscriberPriority?: number;
43
+ groupOrder?: number;
44
+ startGroup?: number;
45
+ startObject?: number;
46
+ endGroup?: number;
47
+ endObject?: number;
48
+ }
49
+ export interface MsgSubscribeOk extends MoQMessage {
50
+ type: MsgType.SUBSCRIBE_OK;
51
+ subscribeId: number;
52
+ expires: number;
53
+ }
54
+ export interface MsgSubscribeError extends MoQMessage {
55
+ type: MsgType.SUBSCRIBE_ERROR;
56
+ subscribeId: number;
57
+ code: number;
58
+ reason: string;
59
+ }
60
+ export interface MsgAnnounce extends MoQMessage {
61
+ type: MsgType.ANNOUNCE;
62
+ trackNamespace: string;
63
+ }
64
+ export interface MsgAnnounceOk extends MoQMessage {
65
+ type: MsgType.ANNOUNCE_OK;
66
+ trackNamespace: string;
67
+ }
68
+ export interface MsgAnnounceError extends MoQMessage {
69
+ type: MsgType.ANNOUNCE_ERROR;
70
+ trackNamespace: string;
71
+ code: number;
72
+ reason: string;
73
+ }
74
+ export interface MsgMaxSubscribeId extends MoQMessage {
75
+ type: MsgType.MAX_SUBSCRIBE_ID;
76
+ subscribeId: number;
77
+ }
78
+ export interface MsgUnsubscribe extends MoQMessage {
79
+ type: MsgType.UNSUBSCRIBE;
80
+ subscribeId: number;
81
+ }
82
+ export interface MsgSubscribeDone extends MoQMessage {
83
+ type: MsgType.SUBSCRIBE_DONE;
84
+ subscribeId: number;
85
+ code: number;
86
+ reason: string;
87
+ finalGroup?: number;
88
+ finalObject?: number;
89
+ }
90
+ export interface MsgObjectHeader {
91
+ subscribeId: number;
92
+ trackAlias: number;
93
+ groupId: number;
94
+ objectId: number;
95
+ priority: number;
96
+ }
97
+ export declare class MoQSerializer {
98
+ /**
99
+ * Serialize a MoQ control message with proper framing:
100
+ * Type (varint) + Length (varint) + Payload (bytes)
101
+ * Per Draft-07: https://www.ietf.org/archive/id/draft-ietf-moq-transport-07.html
102
+ */
103
+ static serialize(msg: MoQMessage): Uint8Array;
104
+ static deserialize(sb: StreamBuffer): MoQMessage | null;
105
+ }