moqtail 0.7.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/README.md +514 -0
- package/dist/byte_buffer-BOK6VPTF.d.cts +864 -0
- package/dist/byte_buffer-BOK6VPTF.d.ts +864 -0
- package/dist/client/index.cjs +7785 -0
- package/dist/client/index.d.cts +1665 -0
- package/dist/client/index.d.ts +1665 -0
- package/dist/client/index.js +7767 -0
- package/dist/index.cjs +8559 -0
- package/dist/index.d.cts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +8459 -0
- package/dist/model/index.cjs +4759 -0
- package/dist/model/index.d.cts +362 -0
- package/dist/model/index.d.ts +362 -0
- package/dist/model/index.js +4645 -0
- package/dist/util/index.cjs +1799 -0
- package/dist/util/index.d.cts +205 -0
- package/dist/util/index.d.ts +205 -0
- package/dist/util/index.js +1789 -0
- package/dist/version_parameter-CgEPNuUt.d.ts +1660 -0
- package/dist/version_parameter-DCE9_itC.d.cts +1660 -0
- package/package.json +97 -0
|
@@ -0,0 +1,1789 @@
|
|
|
1
|
+
import Heap from 'heap-js';
|
|
2
|
+
|
|
3
|
+
// src/util/telemetry.ts
|
|
4
|
+
var NetworkTelemetry = class {
|
|
5
|
+
events = [];
|
|
6
|
+
windowMs;
|
|
7
|
+
constructor(windowMs = 1e3) {
|
|
8
|
+
this.windowMs = windowMs;
|
|
9
|
+
}
|
|
10
|
+
push({ latency, size }) {
|
|
11
|
+
const now = Date.now();
|
|
12
|
+
this.events.push({ timestamp: now, latency, size });
|
|
13
|
+
}
|
|
14
|
+
clean() {
|
|
15
|
+
const cutoff = Date.now() - this.windowMs;
|
|
16
|
+
while (this.events.length && this.events[0] && this.events[0].timestamp < cutoff) {
|
|
17
|
+
this.events.shift();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
get throughput() {
|
|
21
|
+
this.clean();
|
|
22
|
+
const totalBytes = this.events.reduce((sum, e) => sum + e.size, 0);
|
|
23
|
+
return totalBytes / (this.windowMs / 1e3);
|
|
24
|
+
}
|
|
25
|
+
get latency() {
|
|
26
|
+
this.clean();
|
|
27
|
+
if (!this.events.length) return 0;
|
|
28
|
+
return this.events.reduce((sum, e) => sum + e.latency, 0) / this.events.length;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// src/model/error/error.ts
|
|
33
|
+
var MOQtailError = class extends Error {
|
|
34
|
+
constructor(cause) {
|
|
35
|
+
super(cause);
|
|
36
|
+
this.name = this.constructor.name;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
var NotEnoughBytesError = class extends MOQtailError {
|
|
40
|
+
constructor(context, needed, available) {
|
|
41
|
+
super(`[${context}] not enough bytes: needed ${needed}, available ${available}`);
|
|
42
|
+
this.context = context;
|
|
43
|
+
this.needed = needed;
|
|
44
|
+
this.available = available;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
var CastingError = class extends MOQtailError {
|
|
48
|
+
constructor(context, fromType, toType, details) {
|
|
49
|
+
super(`[${context}] cannot cast from ${fromType} to ${toType}, [${details}]`);
|
|
50
|
+
this.context = context;
|
|
51
|
+
this.fromType = fromType;
|
|
52
|
+
this.toType = toType;
|
|
53
|
+
this.details = details;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
var VarIntOverflowError = class extends MOQtailError {
|
|
57
|
+
constructor(context, value) {
|
|
58
|
+
super(`[${context}] value ${value} too large to encode as varint`);
|
|
59
|
+
this.context = context;
|
|
60
|
+
this.value = value;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
var LengthExceedsMaxError = class extends MOQtailError {
|
|
64
|
+
constructor(context, max, len) {
|
|
65
|
+
super(`[${context}] length ${len} exceeds maximum of ${max}, protocol violation`);
|
|
66
|
+
this.context = context;
|
|
67
|
+
this.max = max;
|
|
68
|
+
this.len = len;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
var KeyValueFormattingError = class extends MOQtailError {
|
|
72
|
+
constructor(context) {
|
|
73
|
+
super(`[${context}] key value formatting error`);
|
|
74
|
+
this.context = context;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
var InvalidTypeError = class extends MOQtailError {
|
|
78
|
+
constructor(context, details) {
|
|
79
|
+
super(`Invalid type: [${context}], [${details}]`);
|
|
80
|
+
this.context = context;
|
|
81
|
+
this.details = details;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
var InvalidUTF8Error = class extends MOQtailError {
|
|
85
|
+
constructor(context, details) {
|
|
86
|
+
super(`Invalid UTF8: [${context}], [${details}]`);
|
|
87
|
+
this.context = context;
|
|
88
|
+
this.details = details;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
var ProtocolViolationError = class extends MOQtailError {
|
|
92
|
+
constructor(context, details) {
|
|
93
|
+
super(`Protocol violation: [${context}], [${details}]`);
|
|
94
|
+
this.context = context;
|
|
95
|
+
this.details = details;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
var TrackNameError = class extends MOQtailError {
|
|
99
|
+
constructor(context, details) {
|
|
100
|
+
super(`Track naming error: [${context}], [${details}]`);
|
|
101
|
+
this.context = context;
|
|
102
|
+
this.details = details;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// src/model/error/constant.ts
|
|
107
|
+
var TerminationCode = /* @__PURE__ */ ((TerminationCode2) => {
|
|
108
|
+
TerminationCode2[TerminationCode2["NO_ERROR"] = 0] = "NO_ERROR";
|
|
109
|
+
TerminationCode2[TerminationCode2["INTERNAL_ERROR"] = 1] = "INTERNAL_ERROR";
|
|
110
|
+
TerminationCode2[TerminationCode2["UNAUTHORIZED"] = 2] = "UNAUTHORIZED";
|
|
111
|
+
TerminationCode2[TerminationCode2["PROTOCOL_VIOLATION"] = 3] = "PROTOCOL_VIOLATION";
|
|
112
|
+
TerminationCode2[TerminationCode2["INVALID_REQUEST_ID"] = 4] = "INVALID_REQUEST_ID";
|
|
113
|
+
TerminationCode2[TerminationCode2["DUPLICATE_TRACK_ALIAS"] = 5] = "DUPLICATE_TRACK_ALIAS";
|
|
114
|
+
TerminationCode2[TerminationCode2["KEY_VALUE_FORMATTING_ERROR"] = 6] = "KEY_VALUE_FORMATTING_ERROR";
|
|
115
|
+
TerminationCode2[TerminationCode2["TOO_MANY_REQUESTS"] = 7] = "TOO_MANY_REQUESTS";
|
|
116
|
+
TerminationCode2[TerminationCode2["INVALID_PATH"] = 8] = "INVALID_PATH";
|
|
117
|
+
TerminationCode2[TerminationCode2["MALFORMED_PATH"] = 9] = "MALFORMED_PATH";
|
|
118
|
+
TerminationCode2[TerminationCode2["GOAWAY_TIMEOUT"] = 16] = "GOAWAY_TIMEOUT";
|
|
119
|
+
TerminationCode2[TerminationCode2["CONTROL_MESSAGE_TIMEOUT"] = 17] = "CONTROL_MESSAGE_TIMEOUT";
|
|
120
|
+
TerminationCode2[TerminationCode2["DATA_STREAM_TIMEOUT"] = 18] = "DATA_STREAM_TIMEOUT";
|
|
121
|
+
TerminationCode2[TerminationCode2["AUTH_TOKEN_CACHE_OVERFLOW"] = 19] = "AUTH_TOKEN_CACHE_OVERFLOW";
|
|
122
|
+
TerminationCode2[TerminationCode2["DUPLICATE_AUTH_TOKEN_ALIAS"] = 20] = "DUPLICATE_AUTH_TOKEN_ALIAS";
|
|
123
|
+
TerminationCode2[TerminationCode2["VERSION_NEGOTIATION_FAILED"] = 21] = "VERSION_NEGOTIATION_FAILED";
|
|
124
|
+
TerminationCode2[TerminationCode2["MALFORMED_AUTH_TOKEN"] = 22] = "MALFORMED_AUTH_TOKEN";
|
|
125
|
+
TerminationCode2[TerminationCode2["UNKNOWN_AUTH_TOKEN_ALIAS"] = 23] = "UNKNOWN_AUTH_TOKEN_ALIAS";
|
|
126
|
+
TerminationCode2[TerminationCode2["EXPIRED_AUTH_TOKEN"] = 24] = "EXPIRED_AUTH_TOKEN";
|
|
127
|
+
TerminationCode2[TerminationCode2["INVALID_AUTHORITY"] = 25] = "INVALID_AUTHORITY";
|
|
128
|
+
TerminationCode2[TerminationCode2["MALFORMED_AUTHORITY"] = 26] = "MALFORMED_AUTHORITY";
|
|
129
|
+
return TerminationCode2;
|
|
130
|
+
})(TerminationCode || {});
|
|
131
|
+
((TerminationCode2) => {
|
|
132
|
+
function tryFrom(code) {
|
|
133
|
+
switch (code) {
|
|
134
|
+
case 0 /* NO_ERROR */:
|
|
135
|
+
return 0 /* NO_ERROR */;
|
|
136
|
+
case 1 /* INTERNAL_ERROR */:
|
|
137
|
+
return 1 /* INTERNAL_ERROR */;
|
|
138
|
+
case 2 /* UNAUTHORIZED */:
|
|
139
|
+
return 2 /* UNAUTHORIZED */;
|
|
140
|
+
case 3 /* PROTOCOL_VIOLATION */:
|
|
141
|
+
return 3 /* PROTOCOL_VIOLATION */;
|
|
142
|
+
case 4 /* INVALID_REQUEST_ID */:
|
|
143
|
+
return 4 /* INVALID_REQUEST_ID */;
|
|
144
|
+
case 5 /* DUPLICATE_TRACK_ALIAS */:
|
|
145
|
+
return 5 /* DUPLICATE_TRACK_ALIAS */;
|
|
146
|
+
case 6 /* KEY_VALUE_FORMATTING_ERROR */:
|
|
147
|
+
return 6 /* KEY_VALUE_FORMATTING_ERROR */;
|
|
148
|
+
case 7 /* TOO_MANY_REQUESTS */:
|
|
149
|
+
return 7 /* TOO_MANY_REQUESTS */;
|
|
150
|
+
case 8 /* INVALID_PATH */:
|
|
151
|
+
return 8 /* INVALID_PATH */;
|
|
152
|
+
case 9 /* MALFORMED_PATH */:
|
|
153
|
+
return 9 /* MALFORMED_PATH */;
|
|
154
|
+
case 16 /* GOAWAY_TIMEOUT */:
|
|
155
|
+
return 16 /* GOAWAY_TIMEOUT */;
|
|
156
|
+
case 17 /* CONTROL_MESSAGE_TIMEOUT */:
|
|
157
|
+
return 17 /* CONTROL_MESSAGE_TIMEOUT */;
|
|
158
|
+
case 18 /* DATA_STREAM_TIMEOUT */:
|
|
159
|
+
return 18 /* DATA_STREAM_TIMEOUT */;
|
|
160
|
+
case 19 /* AUTH_TOKEN_CACHE_OVERFLOW */:
|
|
161
|
+
return 19 /* AUTH_TOKEN_CACHE_OVERFLOW */;
|
|
162
|
+
case 20 /* DUPLICATE_AUTH_TOKEN_ALIAS */:
|
|
163
|
+
return 20 /* DUPLICATE_AUTH_TOKEN_ALIAS */;
|
|
164
|
+
case 21 /* VERSION_NEGOTIATION_FAILED */:
|
|
165
|
+
return 21 /* VERSION_NEGOTIATION_FAILED */;
|
|
166
|
+
default:
|
|
167
|
+
throw new InvalidTypeError("TerminationCode.tryFrom", `Unknown termination code: ${code}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
TerminationCode2.tryFrom = tryFrom;
|
|
171
|
+
})(TerminationCode || (TerminationCode = {}));
|
|
172
|
+
|
|
173
|
+
// src/model/common/location.ts
|
|
174
|
+
var Location = class _Location {
|
|
175
|
+
/**
|
|
176
|
+
* The group index for this location.
|
|
177
|
+
*/
|
|
178
|
+
group;
|
|
179
|
+
/**
|
|
180
|
+
* The object index within the group for this location.
|
|
181
|
+
*/
|
|
182
|
+
object;
|
|
183
|
+
/**
|
|
184
|
+
* Constructs a new Location.
|
|
185
|
+
* @param group - The group index (number or bigint).
|
|
186
|
+
* @param object - The object index (number or bigint).
|
|
187
|
+
*/
|
|
188
|
+
constructor(group, object) {
|
|
189
|
+
this.group = BigInt(group);
|
|
190
|
+
this.object = BigInt(object);
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Serializes this Location to a FrozenByteBuffer.
|
|
194
|
+
* @returns The serialized buffer.
|
|
195
|
+
* @throws CastingError if group or object is negative.
|
|
196
|
+
* @throws VarIntOverflowError if group or object exceeds varint encoding limits.
|
|
197
|
+
*/
|
|
198
|
+
serialize() {
|
|
199
|
+
const buf = new ByteBuffer();
|
|
200
|
+
buf.putVI(this.group);
|
|
201
|
+
buf.putVI(this.object);
|
|
202
|
+
return buf.freeze();
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Deserializes a Location from a buffer.
|
|
206
|
+
* @param buf - The buffer to read from.
|
|
207
|
+
* @returns The deserialized Location.
|
|
208
|
+
* @throws NotEnoughBytesError if buffer does not contain enough bytes.
|
|
209
|
+
*/
|
|
210
|
+
static deserialize(buf) {
|
|
211
|
+
const group = buf.getVI();
|
|
212
|
+
const object = buf.getVI();
|
|
213
|
+
return new _Location(group, object);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Checks if this Location is equal to another.
|
|
217
|
+
* @param other - The other Location to compare.
|
|
218
|
+
* @returns True if both group and object are equal.
|
|
219
|
+
*/
|
|
220
|
+
equals(other) {
|
|
221
|
+
return this.group === other.group && this.object === other.object;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Compares this Location to another for ordering.
|
|
225
|
+
* @param other - The other Location to compare.
|
|
226
|
+
* @returns -1 if this \< other, 1 if this \> other, 0 if equal.
|
|
227
|
+
*/
|
|
228
|
+
compare(other) {
|
|
229
|
+
if (this.group < other.group) return -1;
|
|
230
|
+
if (this.group > other.group) return 1;
|
|
231
|
+
if (this.object < other.object) return -1;
|
|
232
|
+
if (this.object > other.object) return 1;
|
|
233
|
+
return 0;
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// src/model/common/tuple.ts
|
|
238
|
+
var PATH_SEPARATOR = "/";
|
|
239
|
+
var TupleField = class _TupleField {
|
|
240
|
+
/**
|
|
241
|
+
* The raw value of the field as a byte array.
|
|
242
|
+
*/
|
|
243
|
+
constructor(value) {
|
|
244
|
+
this.value = value;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Creates a TupleField from a UTF-8 string.
|
|
248
|
+
* @param str - The string to encode.
|
|
249
|
+
* @returns A new TupleField containing the encoded value.
|
|
250
|
+
*/
|
|
251
|
+
static fromUtf8(str) {
|
|
252
|
+
return new _TupleField(new TextEncoder().encode(str));
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Decodes the field value to a UTF-8 string.
|
|
256
|
+
* @returns The decoded string.
|
|
257
|
+
*/
|
|
258
|
+
toUtf8() {
|
|
259
|
+
return new TextDecoder().decode(this.value);
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Serializes this field to a length-prefixed byte array.
|
|
263
|
+
* @returns The serialized bytes.
|
|
264
|
+
*/
|
|
265
|
+
serialize() {
|
|
266
|
+
const buf = new ByteBuffer();
|
|
267
|
+
buf.putVI(this.value.length);
|
|
268
|
+
buf.putBytes(this.value);
|
|
269
|
+
return buf.toUint8Array();
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Deserializes a TupleField from a buffer.
|
|
273
|
+
* @param buf - The buffer to read from.
|
|
274
|
+
* @returns The deserialized TupleField.
|
|
275
|
+
* @throws CastingError if the length cannot be safely cast to number.
|
|
276
|
+
* @throws NotEnoughBytesError if buffer does not contain enough bytes.
|
|
277
|
+
*/
|
|
278
|
+
static deserialize(buf) {
|
|
279
|
+
const lenBig = buf.getVI();
|
|
280
|
+
const len = Number(lenBig);
|
|
281
|
+
if (BigInt(len) !== lenBig) {
|
|
282
|
+
throw new CastingError("TupleField.deserialize", "bigint", "number", `${lenBig}`);
|
|
283
|
+
}
|
|
284
|
+
const bytes = buf.getBytes(len);
|
|
285
|
+
return new _TupleField(bytes);
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
var Tuple = class _Tuple {
|
|
289
|
+
/**
|
|
290
|
+
* The ordered list of fields in this tuple.
|
|
291
|
+
*/
|
|
292
|
+
constructor(fields = []) {
|
|
293
|
+
this.fields = fields;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Creates a Tuple from a path string, splitting on '/'.
|
|
297
|
+
* @param path - The path string (e.g. '/foo/bar').
|
|
298
|
+
* @returns A Tuple with each segment as a field.
|
|
299
|
+
*/
|
|
300
|
+
static fromUtf8Path(path) {
|
|
301
|
+
const parts = path.split(PATH_SEPARATOR).filter(Boolean);
|
|
302
|
+
const fields = parts.map(TupleField.fromUtf8);
|
|
303
|
+
return new _Tuple(fields);
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Converts the tuple to a path string, joining fields with '/'.
|
|
307
|
+
* @returns The path string (e.g. '/foo/bar').
|
|
308
|
+
*/
|
|
309
|
+
toUtf8Path() {
|
|
310
|
+
return this.fields.map((f) => PATH_SEPARATOR + f.toUtf8()).join("");
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Adds a field to the tuple.
|
|
314
|
+
* @param field - The TupleField to add.
|
|
315
|
+
*/
|
|
316
|
+
add(field) {
|
|
317
|
+
this.fields.push(field);
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Gets the field at the specified index.
|
|
321
|
+
* @param index - The field index.
|
|
322
|
+
* @returns The TupleField at the index.
|
|
323
|
+
* @throws Error if no field exists at the index.
|
|
324
|
+
*/
|
|
325
|
+
get(index) {
|
|
326
|
+
const field = this.fields[index];
|
|
327
|
+
if (!field) throw new Error("Field not found at index");
|
|
328
|
+
return field;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Sets the field at the specified index.
|
|
332
|
+
* @param index - The field index.
|
|
333
|
+
* @param field - The TupleField to set.
|
|
334
|
+
*/
|
|
335
|
+
set(index, field) {
|
|
336
|
+
this.fields[index] = field;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Clears all fields from the tuple.
|
|
340
|
+
*/
|
|
341
|
+
clear() {
|
|
342
|
+
this.fields.length = 0;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Serializes the tuple to a FrozenByteBuffer.
|
|
346
|
+
* @returns The serialized buffer.
|
|
347
|
+
*/
|
|
348
|
+
serialize() {
|
|
349
|
+
const buf = new ByteBuffer();
|
|
350
|
+
buf.putVI(this.fields.length);
|
|
351
|
+
for (const field of this.fields) {
|
|
352
|
+
buf.putBytes(field.serialize());
|
|
353
|
+
}
|
|
354
|
+
return buf.freeze();
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Deserializes a Tuple from a buffer.
|
|
358
|
+
* @param buf - The buffer to read from.
|
|
359
|
+
* @returns The deserialized Tuple.
|
|
360
|
+
* @throws CastingError if the count cannot be safely cast to number.
|
|
361
|
+
* @throws NotEnoughBytesError if buffer does not contain enough bytes.
|
|
362
|
+
*/
|
|
363
|
+
static deserialize(buf) {
|
|
364
|
+
const countBig = buf.getVI();
|
|
365
|
+
const count = Number(countBig);
|
|
366
|
+
if (BigInt(count) !== countBig) {
|
|
367
|
+
throw new CastingError("Tuple.deserialize", "bigint", "number", `${countBig}`);
|
|
368
|
+
}
|
|
369
|
+
const fields = [];
|
|
370
|
+
for (let i = 0; i < count; i++) {
|
|
371
|
+
const field = TupleField.deserialize(buf);
|
|
372
|
+
fields.push(field);
|
|
373
|
+
}
|
|
374
|
+
return new _Tuple(fields);
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Checks if this tuple is equal to another.
|
|
378
|
+
* @param other - The other Tuple to compare.
|
|
379
|
+
* @returns True if all fields are equal.
|
|
380
|
+
*/
|
|
381
|
+
equals(other) {
|
|
382
|
+
if (this.fields.length !== other.fields.length) return false;
|
|
383
|
+
return this.fields.every((f, i) => f.value.toString() === other.fields[i].value.toString());
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
// src/model/common/reason_phrase.ts
|
|
388
|
+
var MAX_REASON_PHRASE_LEN = 1024;
|
|
389
|
+
var ReasonPhrase = class _ReasonPhrase {
|
|
390
|
+
/**
|
|
391
|
+
* The underlying phrase string.
|
|
392
|
+
* @public
|
|
393
|
+
*/
|
|
394
|
+
#phrase;
|
|
395
|
+
/**
|
|
396
|
+
* Constructs a ReasonPhrase, validating UTF-8 encoding and length.
|
|
397
|
+
*
|
|
398
|
+
* @param phrase - The string to use as the reason phrase.
|
|
399
|
+
* @throws {@link InvalidUTF8Error} if encoding fails.
|
|
400
|
+
* @throws {@link LengthExceedsMaxError} if the encoded phrase exceeds {@link MAX_REASON_PHRASE_LEN} bytes.
|
|
401
|
+
* @public
|
|
402
|
+
*/
|
|
403
|
+
constructor(phrase) {
|
|
404
|
+
let encodedPhrase;
|
|
405
|
+
try {
|
|
406
|
+
encodedPhrase = new TextEncoder().encode(phrase);
|
|
407
|
+
} catch (e) {
|
|
408
|
+
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
409
|
+
throw new InvalidUTF8Error("ReasonPhrase.constructor (TextEncoder failed)", errorMessage);
|
|
410
|
+
}
|
|
411
|
+
if (encodedPhrase.length > MAX_REASON_PHRASE_LEN) {
|
|
412
|
+
throw new LengthExceedsMaxError("ReasonPhrase.constructor", MAX_REASON_PHRASE_LEN, encodedPhrase.length);
|
|
413
|
+
}
|
|
414
|
+
this.#phrase = phrase;
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Returns the phrase string.
|
|
418
|
+
* @public
|
|
419
|
+
*/
|
|
420
|
+
get phrase() {
|
|
421
|
+
return this.#phrase;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Serializes the ReasonPhrase into a {@link FrozenByteBuffer} containing:
|
|
425
|
+
* varint(length_of_phrase_bytes) || phrase_bytes
|
|
426
|
+
*
|
|
427
|
+
* @returns The serialized buffer.
|
|
428
|
+
* @public
|
|
429
|
+
*/
|
|
430
|
+
serialize() {
|
|
431
|
+
const buf = new ByteBuffer();
|
|
432
|
+
const phraseBytes = new TextEncoder().encode(this.#phrase);
|
|
433
|
+
buf.putVI(phraseBytes.length);
|
|
434
|
+
buf.putBytes(phraseBytes);
|
|
435
|
+
return buf.freeze();
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Deserializes a ReasonPhrase from the given buffer.
|
|
439
|
+
* Reads varint(length) || utf8‑bytes.
|
|
440
|
+
*
|
|
441
|
+
* @param buf - The buffer to read from.
|
|
442
|
+
* @returns The deserialized ReasonPhrase.
|
|
443
|
+
* @throws :{@link CastingError} if the length cannot be safely cast to a number.
|
|
444
|
+
* @throws :{@link LengthExceedsMaxError} if the length exceeds {@link MAX_REASON_PHRASE_LEN}.
|
|
445
|
+
* @throws :{@link NotEnoughBytesError} if the buffer does not contain enough bytes.
|
|
446
|
+
* @throws :{@link InvalidUTF8Error} if decoding fails.
|
|
447
|
+
* @public
|
|
448
|
+
*/
|
|
449
|
+
static deserialize(buf) {
|
|
450
|
+
const lenBig = buf.getVI();
|
|
451
|
+
let len;
|
|
452
|
+
try {
|
|
453
|
+
len = Number(lenBig);
|
|
454
|
+
if (BigInt(len) !== lenBig) {
|
|
455
|
+
throw new Error(`Value ${lenBig.toString()} cannot be accurately represented as a number.`);
|
|
456
|
+
}
|
|
457
|
+
} catch (e) {
|
|
458
|
+
const errorDetails = e instanceof Error ? e.message : String(e);
|
|
459
|
+
throw new CastingError("ReasonPhrase.deserialize length", "bigint", "number", errorDetails);
|
|
460
|
+
}
|
|
461
|
+
if (len > MAX_REASON_PHRASE_LEN) {
|
|
462
|
+
throw new LengthExceedsMaxError("ReasonPhrase.deserialize", MAX_REASON_PHRASE_LEN, len);
|
|
463
|
+
}
|
|
464
|
+
if (buf.remaining < len) {
|
|
465
|
+
throw new NotEnoughBytesError("ReasonPhrase.deserialize value", len, buf.remaining);
|
|
466
|
+
}
|
|
467
|
+
const phraseBytes = buf.getBytes(len);
|
|
468
|
+
try {
|
|
469
|
+
const phraseStr = new TextDecoder("utf-8", { fatal: true }).decode(phraseBytes);
|
|
470
|
+
return new _ReasonPhrase(phraseStr);
|
|
471
|
+
} catch (e) {
|
|
472
|
+
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
473
|
+
throw new InvalidUTF8Error("ReasonPhrase.deserialize (decode)", errorMessage);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
// src/model/data/constant.ts
|
|
479
|
+
var ObjectDatagramStatusType = /* @__PURE__ */ ((ObjectDatagramStatusType2) => {
|
|
480
|
+
ObjectDatagramStatusType2[ObjectDatagramStatusType2["WithoutExtensions"] = 2] = "WithoutExtensions";
|
|
481
|
+
ObjectDatagramStatusType2[ObjectDatagramStatusType2["WithExtensions"] = 3] = "WithExtensions";
|
|
482
|
+
return ObjectDatagramStatusType2;
|
|
483
|
+
})(ObjectDatagramStatusType || {});
|
|
484
|
+
((ObjectDatagramStatusType2) => {
|
|
485
|
+
function tryFrom(value) {
|
|
486
|
+
const v = typeof value === "bigint" ? Number(value) : value;
|
|
487
|
+
switch (v) {
|
|
488
|
+
case 2:
|
|
489
|
+
return 2 /* WithoutExtensions */;
|
|
490
|
+
case 3:
|
|
491
|
+
return 3 /* WithExtensions */;
|
|
492
|
+
default:
|
|
493
|
+
throw new Error(`Invalid ObjectDatagramStatusType: ${value}`);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
ObjectDatagramStatusType2.tryFrom = tryFrom;
|
|
497
|
+
})(ObjectDatagramStatusType || (ObjectDatagramStatusType = {}));
|
|
498
|
+
var ObjectDatagramType = /* @__PURE__ */ ((ObjectDatagramType2) => {
|
|
499
|
+
ObjectDatagramType2[ObjectDatagramType2["WithoutExtensions"] = 0] = "WithoutExtensions";
|
|
500
|
+
ObjectDatagramType2[ObjectDatagramType2["WithExtensions"] = 1] = "WithExtensions";
|
|
501
|
+
return ObjectDatagramType2;
|
|
502
|
+
})(ObjectDatagramType || {});
|
|
503
|
+
((ObjectDatagramType2) => {
|
|
504
|
+
function tryFrom(value) {
|
|
505
|
+
const v = typeof value === "bigint" ? Number(value) : value;
|
|
506
|
+
switch (v) {
|
|
507
|
+
case 0:
|
|
508
|
+
return 0 /* WithoutExtensions */;
|
|
509
|
+
case 1:
|
|
510
|
+
return 1 /* WithExtensions */;
|
|
511
|
+
default:
|
|
512
|
+
throw new Error(`Invalid ObjectDatagramType: ${value}`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
ObjectDatagramType2.tryFrom = tryFrom;
|
|
516
|
+
})(ObjectDatagramType || (ObjectDatagramType = {}));
|
|
517
|
+
var FetchHeaderType = /* @__PURE__ */ ((FetchHeaderType2) => {
|
|
518
|
+
FetchHeaderType2[FetchHeaderType2["Type0x05"] = 5] = "Type0x05";
|
|
519
|
+
return FetchHeaderType2;
|
|
520
|
+
})(FetchHeaderType || {});
|
|
521
|
+
((FetchHeaderType2) => {
|
|
522
|
+
function tryFrom(value) {
|
|
523
|
+
const v = typeof value === "bigint" ? Number(value) : value;
|
|
524
|
+
switch (v) {
|
|
525
|
+
case 5:
|
|
526
|
+
return 5 /* Type0x05 */;
|
|
527
|
+
default:
|
|
528
|
+
throw new Error(`Invalid FetchHeaderType: ${value}`);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
FetchHeaderType2.tryFrom = tryFrom;
|
|
532
|
+
})(FetchHeaderType || (FetchHeaderType = {}));
|
|
533
|
+
var SubgroupHeaderType = /* @__PURE__ */ ((SubgroupHeaderType2) => {
|
|
534
|
+
SubgroupHeaderType2[SubgroupHeaderType2["Type0x10"] = 16] = "Type0x10";
|
|
535
|
+
SubgroupHeaderType2[SubgroupHeaderType2["Type0x11"] = 17] = "Type0x11";
|
|
536
|
+
SubgroupHeaderType2[SubgroupHeaderType2["Type0x12"] = 18] = "Type0x12";
|
|
537
|
+
SubgroupHeaderType2[SubgroupHeaderType2["Type0x13"] = 19] = "Type0x13";
|
|
538
|
+
SubgroupHeaderType2[SubgroupHeaderType2["Type0x14"] = 20] = "Type0x14";
|
|
539
|
+
SubgroupHeaderType2[SubgroupHeaderType2["Type0x15"] = 21] = "Type0x15";
|
|
540
|
+
SubgroupHeaderType2[SubgroupHeaderType2["Type0x18"] = 24] = "Type0x18";
|
|
541
|
+
SubgroupHeaderType2[SubgroupHeaderType2["Type0x19"] = 25] = "Type0x19";
|
|
542
|
+
SubgroupHeaderType2[SubgroupHeaderType2["Type0x1A"] = 26] = "Type0x1A";
|
|
543
|
+
SubgroupHeaderType2[SubgroupHeaderType2["Type0x1B"] = 27] = "Type0x1B";
|
|
544
|
+
SubgroupHeaderType2[SubgroupHeaderType2["Type0x1C"] = 28] = "Type0x1C";
|
|
545
|
+
SubgroupHeaderType2[SubgroupHeaderType2["Type0x1D"] = 29] = "Type0x1D";
|
|
546
|
+
return SubgroupHeaderType2;
|
|
547
|
+
})(SubgroupHeaderType || {});
|
|
548
|
+
((SubgroupHeaderType2) => {
|
|
549
|
+
function hasExplicitSubgroupId(t) {
|
|
550
|
+
switch (t) {
|
|
551
|
+
case 20 /* Type0x14 */:
|
|
552
|
+
case 21 /* Type0x15 */:
|
|
553
|
+
case 28 /* Type0x1C */:
|
|
554
|
+
case 29 /* Type0x1D */:
|
|
555
|
+
return true;
|
|
556
|
+
default:
|
|
557
|
+
return false;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
SubgroupHeaderType2.hasExplicitSubgroupId = hasExplicitSubgroupId;
|
|
561
|
+
function isSubgroupIdZero(t) {
|
|
562
|
+
switch (t) {
|
|
563
|
+
case 16 /* Type0x10 */:
|
|
564
|
+
case 17 /* Type0x11 */:
|
|
565
|
+
case 24 /* Type0x18 */:
|
|
566
|
+
case 25 /* Type0x19 */:
|
|
567
|
+
return true;
|
|
568
|
+
default:
|
|
569
|
+
return false;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
SubgroupHeaderType2.isSubgroupIdZero = isSubgroupIdZero;
|
|
573
|
+
function hasExtensions(t) {
|
|
574
|
+
switch (t) {
|
|
575
|
+
case 17 /* Type0x11 */:
|
|
576
|
+
case 19 /* Type0x13 */:
|
|
577
|
+
case 21 /* Type0x15 */:
|
|
578
|
+
case 25 /* Type0x19 */:
|
|
579
|
+
case 27 /* Type0x1B */:
|
|
580
|
+
case 29 /* Type0x1D */:
|
|
581
|
+
return true;
|
|
582
|
+
default:
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
SubgroupHeaderType2.hasExtensions = hasExtensions;
|
|
587
|
+
function tryFrom(value) {
|
|
588
|
+
const v = typeof value === "bigint" ? Number(value) : value;
|
|
589
|
+
switch (v) {
|
|
590
|
+
case 16:
|
|
591
|
+
return 16 /* Type0x10 */;
|
|
592
|
+
case 17:
|
|
593
|
+
return 17 /* Type0x11 */;
|
|
594
|
+
case 18:
|
|
595
|
+
return 18 /* Type0x12 */;
|
|
596
|
+
case 19:
|
|
597
|
+
return 19 /* Type0x13 */;
|
|
598
|
+
case 20:
|
|
599
|
+
return 20 /* Type0x14 */;
|
|
600
|
+
case 21:
|
|
601
|
+
return 21 /* Type0x15 */;
|
|
602
|
+
case 24:
|
|
603
|
+
return 24 /* Type0x18 */;
|
|
604
|
+
case 25:
|
|
605
|
+
return 25 /* Type0x19 */;
|
|
606
|
+
case 26:
|
|
607
|
+
return 26 /* Type0x1A */;
|
|
608
|
+
case 27:
|
|
609
|
+
return 27 /* Type0x1B */;
|
|
610
|
+
case 28:
|
|
611
|
+
return 28 /* Type0x1C */;
|
|
612
|
+
case 29:
|
|
613
|
+
return 29 /* Type0x1D */;
|
|
614
|
+
default:
|
|
615
|
+
throw new Error(`Invalid SubgroupHeaderType: ${value}`);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
SubgroupHeaderType2.tryFrom = tryFrom;
|
|
619
|
+
})(SubgroupHeaderType || (SubgroupHeaderType = {}));
|
|
620
|
+
var ObjectForwardingPreference = /* @__PURE__ */ ((ObjectForwardingPreference2) => {
|
|
621
|
+
ObjectForwardingPreference2["Subgroup"] = "Subgroup";
|
|
622
|
+
ObjectForwardingPreference2["Datagram"] = "Datagram";
|
|
623
|
+
return ObjectForwardingPreference2;
|
|
624
|
+
})(ObjectForwardingPreference || {});
|
|
625
|
+
((ObjectForwardingPreference2) => {
|
|
626
|
+
function tryFrom(value) {
|
|
627
|
+
if (value === "Subgroup") return "Subgroup" /* Subgroup */;
|
|
628
|
+
if (value === "Datagram") return "Datagram" /* Datagram */;
|
|
629
|
+
throw new Error(`Invalid ObjectForwardingPreference: ${value}`);
|
|
630
|
+
}
|
|
631
|
+
ObjectForwardingPreference2.tryFrom = tryFrom;
|
|
632
|
+
})(ObjectForwardingPreference || (ObjectForwardingPreference = {}));
|
|
633
|
+
var ObjectStatus = /* @__PURE__ */ ((ObjectStatus2) => {
|
|
634
|
+
ObjectStatus2[ObjectStatus2["Normal"] = 0] = "Normal";
|
|
635
|
+
ObjectStatus2[ObjectStatus2["DoesNotExist"] = 1] = "DoesNotExist";
|
|
636
|
+
ObjectStatus2[ObjectStatus2["EndOfGroup"] = 3] = "EndOfGroup";
|
|
637
|
+
ObjectStatus2[ObjectStatus2["EndOfTrack"] = 4] = "EndOfTrack";
|
|
638
|
+
return ObjectStatus2;
|
|
639
|
+
})(ObjectStatus || {});
|
|
640
|
+
((ObjectStatus2) => {
|
|
641
|
+
function tryFrom(value) {
|
|
642
|
+
const v = typeof value === "bigint" ? Number(value) : value;
|
|
643
|
+
switch (v) {
|
|
644
|
+
case 0:
|
|
645
|
+
return 0 /* Normal */;
|
|
646
|
+
case 1:
|
|
647
|
+
return 1 /* DoesNotExist */;
|
|
648
|
+
case 3:
|
|
649
|
+
return 3 /* EndOfGroup */;
|
|
650
|
+
case 4:
|
|
651
|
+
return 4 /* EndOfTrack */;
|
|
652
|
+
default:
|
|
653
|
+
throw new Error(`Invalid ObjectStatus: ${value}`);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
ObjectStatus2.tryFrom = tryFrom;
|
|
657
|
+
})(ObjectStatus || (ObjectStatus = {}));
|
|
658
|
+
|
|
659
|
+
// src/model/data/fetch_header.ts
|
|
660
|
+
var FetchHeader = class _FetchHeader {
|
|
661
|
+
constructor(type, requestId) {
|
|
662
|
+
this.type = type;
|
|
663
|
+
this.requestId = BigInt(requestId);
|
|
664
|
+
}
|
|
665
|
+
requestId;
|
|
666
|
+
serialize() {
|
|
667
|
+
const buf = new ByteBuffer();
|
|
668
|
+
buf.putVI(this.type);
|
|
669
|
+
buf.putVI(this.requestId);
|
|
670
|
+
return buf.freeze();
|
|
671
|
+
}
|
|
672
|
+
static deserialize(buf) {
|
|
673
|
+
const typeRaw = buf.getVI();
|
|
674
|
+
const type = FetchHeaderType.tryFrom(typeRaw);
|
|
675
|
+
const requestId = buf.getVI();
|
|
676
|
+
return new _FetchHeader(type, requestId);
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
// src/model/data/full_track_name.ts
|
|
681
|
+
var MAX_NAMESPACE_TUPLE_COUNT = 32;
|
|
682
|
+
var MAX_FULL_TRACK_NAME_LENGTH = 4096;
|
|
683
|
+
var FullTrackName = class _FullTrackName {
|
|
684
|
+
constructor(namespace, name) {
|
|
685
|
+
this.namespace = namespace;
|
|
686
|
+
this.name = name;
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Human-readable representation: `\<namespace path joined by '/'\>:\<name as lowercase hex\>`.
|
|
690
|
+
* If the underlying {@link Tuple} exposes `toUtf8Path`, it's used; otherwise the raw fields are joined.
|
|
691
|
+
* This is lossy only in the sense that name bytes are hex encoded; round-tripping requires serialization.
|
|
692
|
+
*/
|
|
693
|
+
toString() {
|
|
694
|
+
const nsStr = this.namespace.toUtf8Path ? this.namespace.toUtf8Path() : Array.from(this.namespace.fields).join("/");
|
|
695
|
+
const nameStr = Array.from(this.name).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
696
|
+
return `${nsStr}:${nameStr}`;
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Construct a validated full track name.
|
|
700
|
+
*
|
|
701
|
+
* Validation steps:
|
|
702
|
+
* 1. Convert namespace string -\> {@link Tuple} (split on '/') if needed.
|
|
703
|
+
* 2. Reject if namespace tuple field count is 0 or \> {@link MAX_NAMESPACE_TUPLE_COUNT}.
|
|
704
|
+
* 3. Encode name string to UTF-8 if needed.
|
|
705
|
+
* 4. Reject if total serialized length (namespace tuple + name bytes) \> {@link MAX_FULL_TRACK_NAME_LENGTH}.
|
|
706
|
+
*
|
|
707
|
+
* @throws :{@link TrackNameError} on any constraint violation.
|
|
708
|
+
* @example
|
|
709
|
+
* ```ts
|
|
710
|
+
* const full = FullTrackName.tryNew('media/video', 'keyframe')
|
|
711
|
+
* console.log(full.toString()) // media/video:6b65796672616d65
|
|
712
|
+
* ```
|
|
713
|
+
*/
|
|
714
|
+
static tryNew(namespace, name) {
|
|
715
|
+
const nsTuple = typeof namespace === "string" ? Tuple.fromUtf8Path(namespace) : namespace;
|
|
716
|
+
const nsCount = nsTuple.fields.length;
|
|
717
|
+
if (nsCount === 0 || nsCount > MAX_NAMESPACE_TUPLE_COUNT) {
|
|
718
|
+
throw new TrackNameError(
|
|
719
|
+
"FullTrackName::tryNew(nsCount)",
|
|
720
|
+
`Namespace cannot be empty or cannot exceed ${MAX_NAMESPACE_TUPLE_COUNT} fields`
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
const nameBytes = typeof name === "string" ? new TextEncoder().encode(name) : name;
|
|
724
|
+
const totalLen = nsTuple.serialize().toUint8Array().length + nameBytes.length;
|
|
725
|
+
if (totalLen > MAX_FULL_TRACK_NAME_LENGTH) {
|
|
726
|
+
throw new TrackNameError(
|
|
727
|
+
"FullTrackName::tryNew(totalLen)",
|
|
728
|
+
`Total length cannot exceed ${MAX_FULL_TRACK_NAME_LENGTH}`
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
return new _FullTrackName(nsTuple, nameBytes);
|
|
732
|
+
}
|
|
733
|
+
/**
|
|
734
|
+
* Serialize to a frozen buffer: tuple (namespace) followed by length‑prefixed name bytes.
|
|
735
|
+
* Consumers needing raw bytes should call `.toUint8Array()` on the returned {@link FrozenByteBuffer}.
|
|
736
|
+
*/
|
|
737
|
+
serialize() {
|
|
738
|
+
const buf = new ByteBuffer();
|
|
739
|
+
buf.putTuple(this.namespace);
|
|
740
|
+
buf.putLengthPrefixedBytes(this.name);
|
|
741
|
+
return buf.freeze();
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Parse a serialized full track name. Performs the same validations as {@link FullTrackName.tryNew}.
|
|
745
|
+
* The provided buffer's read cursor advances accordingly.
|
|
746
|
+
* @throws :{@link TrackNameError} if constraints are violated.
|
|
747
|
+
*/
|
|
748
|
+
static deserialize(buf) {
|
|
749
|
+
const namespace = buf.getTuple();
|
|
750
|
+
const nsCount = namespace.fields.length;
|
|
751
|
+
if (nsCount === 0 || nsCount > MAX_NAMESPACE_TUPLE_COUNT) {
|
|
752
|
+
throw new TrackNameError(
|
|
753
|
+
"FullTrackName::deserialize(nsCount)",
|
|
754
|
+
`Namespace cannot be empty or cannot exceed ${MAX_NAMESPACE_TUPLE_COUNT} fields`
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
const name = buf.getLengthPrefixedBytes();
|
|
758
|
+
const totalLen = namespace.serialize().toUint8Array().length + name.length;
|
|
759
|
+
if (totalLen > MAX_FULL_TRACK_NAME_LENGTH) {
|
|
760
|
+
throw new TrackNameError(
|
|
761
|
+
"FullTrackName::deserialize(totalLen)",
|
|
762
|
+
`Total length cannot exceed ${MAX_FULL_TRACK_NAME_LENGTH}`
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
return new _FullTrackName(namespace, name);
|
|
766
|
+
}
|
|
767
|
+
};
|
|
768
|
+
|
|
769
|
+
// src/model/data/subgroup_header.ts
|
|
770
|
+
var SubgroupHeader = class _SubgroupHeader {
|
|
771
|
+
constructor(type, trackAlias, groupId, subgroupId, publisherPriority) {
|
|
772
|
+
this.type = type;
|
|
773
|
+
this.publisherPriority = publisherPriority;
|
|
774
|
+
this.trackAlias = BigInt(trackAlias);
|
|
775
|
+
this.groupId = BigInt(groupId);
|
|
776
|
+
if (subgroupId !== void 0) {
|
|
777
|
+
this.subgroupId = BigInt(subgroupId);
|
|
778
|
+
} else {
|
|
779
|
+
this.subgroupId = subgroupId;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
subgroupId;
|
|
783
|
+
trackAlias;
|
|
784
|
+
groupId;
|
|
785
|
+
serialize() {
|
|
786
|
+
const buf = new ByteBuffer();
|
|
787
|
+
buf.putVI(this.type);
|
|
788
|
+
buf.putVI(this.trackAlias);
|
|
789
|
+
buf.putVI(this.groupId);
|
|
790
|
+
if (SubgroupHeaderType.hasExplicitSubgroupId(this.type)) {
|
|
791
|
+
if (this.subgroupId === void 0) {
|
|
792
|
+
throw new ProtocolViolationError(
|
|
793
|
+
"SubgroupHeader.serialize",
|
|
794
|
+
"Subgroup_id field is required for this header type"
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
buf.putVI(this.subgroupId);
|
|
798
|
+
}
|
|
799
|
+
buf.putU8(this.publisherPriority);
|
|
800
|
+
return buf.freeze();
|
|
801
|
+
}
|
|
802
|
+
static deserialize(buf) {
|
|
803
|
+
const headerType = SubgroupHeaderType.tryFrom(buf.getNumberVI());
|
|
804
|
+
const trackAlias = buf.getVI();
|
|
805
|
+
const groupId = buf.getVI();
|
|
806
|
+
let subgroupId;
|
|
807
|
+
if (SubgroupHeaderType.hasExplicitSubgroupId(headerType)) {
|
|
808
|
+
subgroupId = buf.getVI();
|
|
809
|
+
} else if (SubgroupHeaderType.isSubgroupIdZero(headerType)) {
|
|
810
|
+
subgroupId = 0n;
|
|
811
|
+
}
|
|
812
|
+
const publisherPriority = buf.getU8();
|
|
813
|
+
return new _SubgroupHeader(headerType, trackAlias, groupId, subgroupId, publisherPriority);
|
|
814
|
+
}
|
|
815
|
+
};
|
|
816
|
+
|
|
817
|
+
// src/model/data/header.ts
|
|
818
|
+
var Header;
|
|
819
|
+
((Header2) => {
|
|
820
|
+
function newFetch(...args) {
|
|
821
|
+
return new FetchHeader(...args);
|
|
822
|
+
}
|
|
823
|
+
Header2.newFetch = newFetch;
|
|
824
|
+
function newSubgroup(...args) {
|
|
825
|
+
return new SubgroupHeader(...args);
|
|
826
|
+
}
|
|
827
|
+
Header2.newSubgroup = newSubgroup;
|
|
828
|
+
function isFetch(header) {
|
|
829
|
+
return header instanceof FetchHeader;
|
|
830
|
+
}
|
|
831
|
+
Header2.isFetch = isFetch;
|
|
832
|
+
function isSubgroup(header) {
|
|
833
|
+
return header instanceof SubgroupHeader;
|
|
834
|
+
}
|
|
835
|
+
Header2.isSubgroup = isSubgroup;
|
|
836
|
+
function serialize(header) {
|
|
837
|
+
return header.serialize();
|
|
838
|
+
}
|
|
839
|
+
Header2.serialize = serialize;
|
|
840
|
+
function deserialize(buf) {
|
|
841
|
+
buf.checkpoint();
|
|
842
|
+
const type = buf.getNumberVI();
|
|
843
|
+
buf.restore();
|
|
844
|
+
switch (type) {
|
|
845
|
+
case 5:
|
|
846
|
+
return FetchHeader.deserialize(buf);
|
|
847
|
+
case 16:
|
|
848
|
+
case 17:
|
|
849
|
+
case 18:
|
|
850
|
+
case 19:
|
|
851
|
+
case 20:
|
|
852
|
+
case 21:
|
|
853
|
+
case 24:
|
|
854
|
+
case 25:
|
|
855
|
+
case 26:
|
|
856
|
+
case 27:
|
|
857
|
+
case 28:
|
|
858
|
+
case 29:
|
|
859
|
+
return SubgroupHeader.deserialize(buf);
|
|
860
|
+
default:
|
|
861
|
+
throw new InvalidTypeError("Header::deserialize(type)", `Unknown header type: ${type}`);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
Header2.deserialize = deserialize;
|
|
865
|
+
})(Header || (Header = {}));
|
|
866
|
+
|
|
867
|
+
// src/model/common/byte_buffer.ts
|
|
868
|
+
var MAX_VARINT_1BYTE = 2n ** 6n - 1n;
|
|
869
|
+
var MAX_VARINT_2BYTE = 2n ** 14n - 1n;
|
|
870
|
+
var MAX_VARINT_4BYTE = 2n ** 30n - 1n;
|
|
871
|
+
var MAX_VARINT_8BYTE = 2n ** 62n - 1n;
|
|
872
|
+
var BaseByteBuffer11 = class {
|
|
873
|
+
buf;
|
|
874
|
+
view;
|
|
875
|
+
_offset = 0;
|
|
876
|
+
_checkpoint = 0;
|
|
877
|
+
constructor(buf) {
|
|
878
|
+
this.buf = buf;
|
|
879
|
+
this.view = new DataView(buf.buffer, buf.byteOffset, buf.length);
|
|
880
|
+
}
|
|
881
|
+
get offset() {
|
|
882
|
+
return this._offset;
|
|
883
|
+
}
|
|
884
|
+
get remaining() {
|
|
885
|
+
return this.length - this._offset;
|
|
886
|
+
}
|
|
887
|
+
/**
|
|
888
|
+
* Save current read position for potential rollback
|
|
889
|
+
*/
|
|
890
|
+
checkpoint() {
|
|
891
|
+
this._checkpoint = this._offset;
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* Restore read position to last checkpoint
|
|
895
|
+
*/
|
|
896
|
+
restore() {
|
|
897
|
+
this._offset = this._checkpoint;
|
|
898
|
+
}
|
|
899
|
+
toUint8Array() {
|
|
900
|
+
return this.buf.slice();
|
|
901
|
+
}
|
|
902
|
+
getU8() {
|
|
903
|
+
if (this.remaining < 1) throw new NotEnoughBytesError("getU8", 1, this.remaining);
|
|
904
|
+
return this.view.getUint8(this._offset++);
|
|
905
|
+
}
|
|
906
|
+
getU16() {
|
|
907
|
+
if (this.remaining < 2) throw new NotEnoughBytesError("getU16", 2, this.remaining);
|
|
908
|
+
const v = this.view.getUint16(this._offset, false);
|
|
909
|
+
this._offset += 2;
|
|
910
|
+
return v;
|
|
911
|
+
}
|
|
912
|
+
getVI() {
|
|
913
|
+
if (this.remaining < 1) throw new NotEnoughBytesError("getVI.first_byte", 1, this.remaining);
|
|
914
|
+
const first = this.getU8();
|
|
915
|
+
const prefix = first >> 6;
|
|
916
|
+
let numBytes;
|
|
917
|
+
switch (prefix) {
|
|
918
|
+
case 0:
|
|
919
|
+
numBytes = 1;
|
|
920
|
+
break;
|
|
921
|
+
case 1:
|
|
922
|
+
numBytes = 2;
|
|
923
|
+
break;
|
|
924
|
+
case 2:
|
|
925
|
+
numBytes = 4;
|
|
926
|
+
break;
|
|
927
|
+
case 3:
|
|
928
|
+
numBytes = 8;
|
|
929
|
+
break;
|
|
930
|
+
default:
|
|
931
|
+
throw new Error("Invalid varint prefix");
|
|
932
|
+
}
|
|
933
|
+
if (numBytes > 1 && this.remaining < numBytes - 1) {
|
|
934
|
+
this._offset--;
|
|
935
|
+
throw new NotEnoughBytesError("getVI.continuation", numBytes, this.remaining + 1);
|
|
936
|
+
}
|
|
937
|
+
let result = BigInt(first & 63);
|
|
938
|
+
if (numBytes > 1) {
|
|
939
|
+
result <<= BigInt((numBytes - 1) * 8);
|
|
940
|
+
for (let i = 1; i < numBytes; i++) {
|
|
941
|
+
const b = BigInt(this.getU8());
|
|
942
|
+
result |= b << BigInt((numBytes - 1 - i) * 8);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
return result;
|
|
946
|
+
}
|
|
947
|
+
getNumberVI() {
|
|
948
|
+
let big = this.getVI();
|
|
949
|
+
if (big > Number.MAX_SAFE_INTEGER)
|
|
950
|
+
throw new CastingError(
|
|
951
|
+
"BaseByteBuffer.getNumberVI()",
|
|
952
|
+
"bigint",
|
|
953
|
+
"number",
|
|
954
|
+
"bigint exceeds Number.MAX_SAFE_INTEGER"
|
|
955
|
+
);
|
|
956
|
+
return Number(big);
|
|
957
|
+
}
|
|
958
|
+
getBytes(len) {
|
|
959
|
+
if (this.remaining < len) throw new NotEnoughBytesError("getBytes", len, this.remaining);
|
|
960
|
+
const slice = this.buf.slice(this._offset, this._offset + len);
|
|
961
|
+
this._offset += len;
|
|
962
|
+
return slice;
|
|
963
|
+
}
|
|
964
|
+
getLengthPrefixedBytes() {
|
|
965
|
+
const len = this.getNumberVI();
|
|
966
|
+
if (this.length < this.offset + len)
|
|
967
|
+
throw new NotEnoughBytesError("BaseByteBuffer.getLengthPrefixedBytes", len, this.length - this.offset);
|
|
968
|
+
return this.getBytes(len);
|
|
969
|
+
}
|
|
970
|
+
getKeyValuePair() {
|
|
971
|
+
return KeyValuePair5.deserialize(this);
|
|
972
|
+
}
|
|
973
|
+
getReasonPhrase() {
|
|
974
|
+
return ReasonPhrase.deserialize(this);
|
|
975
|
+
}
|
|
976
|
+
getLocation() {
|
|
977
|
+
return Location.deserialize(this);
|
|
978
|
+
}
|
|
979
|
+
getTuple() {
|
|
980
|
+
return Tuple.deserialize(this);
|
|
981
|
+
}
|
|
982
|
+
getFullTrackName() {
|
|
983
|
+
return FullTrackName.deserialize(this);
|
|
984
|
+
}
|
|
985
|
+
};
|
|
986
|
+
var ByteBuffer = class extends BaseByteBuffer11 {
|
|
987
|
+
_length = 0;
|
|
988
|
+
constructor(initialSize = 128) {
|
|
989
|
+
super(new Uint8Array(initialSize));
|
|
990
|
+
}
|
|
991
|
+
get length() {
|
|
992
|
+
return this._length;
|
|
993
|
+
}
|
|
994
|
+
/**
|
|
995
|
+
* Clear all data and reset all positions
|
|
996
|
+
*/
|
|
997
|
+
clear() {
|
|
998
|
+
this._length = 0;
|
|
999
|
+
this._offset = 0;
|
|
1000
|
+
this._checkpoint = 0;
|
|
1001
|
+
this.buf = new Uint8Array();
|
|
1002
|
+
this.view = new DataView(this.buf.buffer, this.buf.byteOffset, this.buf.length);
|
|
1003
|
+
}
|
|
1004
|
+
/**
|
|
1005
|
+
* Drop all data before current offset and reset positions
|
|
1006
|
+
* This is the key method for memory management - removes processed data
|
|
1007
|
+
*/
|
|
1008
|
+
commit() {
|
|
1009
|
+
if (this._offset === 0) {
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
if (this._offset >= this._length) {
|
|
1013
|
+
this.clear();
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
this.buf.set(this.buf.subarray(this._offset, this._length), 0);
|
|
1017
|
+
this._length = this._length - this._offset;
|
|
1018
|
+
this._offset = 0;
|
|
1019
|
+
this._checkpoint = 0;
|
|
1020
|
+
this.view = new DataView(this.buf.buffer, this.buf.byteOffset, this.buf.length);
|
|
1021
|
+
}
|
|
1022
|
+
ensureCapacity(add) {
|
|
1023
|
+
const need = this._length + add;
|
|
1024
|
+
if (need <= this.buf.length) return;
|
|
1025
|
+
let newSize = this.buf.length * 2 + 1;
|
|
1026
|
+
while (newSize < need) newSize *= 2;
|
|
1027
|
+
const newBuf = new Uint8Array(newSize);
|
|
1028
|
+
newBuf.set(this.buf.subarray(0, this._length));
|
|
1029
|
+
this.buf = newBuf;
|
|
1030
|
+
this.view = new DataView(this.buf.buffer);
|
|
1031
|
+
}
|
|
1032
|
+
// --------- WRITE OPERATIONS ---------
|
|
1033
|
+
putU8(v) {
|
|
1034
|
+
if (v < 0 || v > 255) {
|
|
1035
|
+
throw new RangeError(`Value ${v} is out of range for a U8 (0-255).`);
|
|
1036
|
+
}
|
|
1037
|
+
this.ensureCapacity(1);
|
|
1038
|
+
this.view.setUint8(this._length++, v);
|
|
1039
|
+
}
|
|
1040
|
+
putU16(v) {
|
|
1041
|
+
if (v < 0 || v > 65535) {
|
|
1042
|
+
throw new RangeError(`Value ${v} is out of range for a U16 (0-65535).`);
|
|
1043
|
+
}
|
|
1044
|
+
this.ensureCapacity(2);
|
|
1045
|
+
this.view.setUint16(this._length, v, false);
|
|
1046
|
+
this._length += 2;
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Write a variable-length integer (QUIC-style varint)
|
|
1050
|
+
* Encoding:
|
|
1051
|
+
* - 2 MSB = 00: 1 byte (6 bits) for values 0-63
|
|
1052
|
+
* - 2 MSB = 01: 2 bytes (14 bits) for values 0-16383
|
|
1053
|
+
* - 2 MSB = 10: 4 bytes (30 bits) for values 0-1073741823
|
|
1054
|
+
* - 2 MSB = 11: 8 bytes (62 bits) for values 0-4611686018427387903
|
|
1055
|
+
*/
|
|
1056
|
+
putVI(v) {
|
|
1057
|
+
const value = typeof v === "number" ? BigInt(v) : v;
|
|
1058
|
+
if (value < 0) {
|
|
1059
|
+
throw new CastingError("putVI", typeof v, "unsigned varint", "negative values are not supported");
|
|
1060
|
+
}
|
|
1061
|
+
if (value <= MAX_VARINT_1BYTE) {
|
|
1062
|
+
this.putU8(Number(value));
|
|
1063
|
+
} else if (value <= MAX_VARINT_2BYTE) {
|
|
1064
|
+
this.ensureCapacity(2);
|
|
1065
|
+
this.view.setUint8(this._length++, Number(value >> 8n | 0b01000000n));
|
|
1066
|
+
this.view.setUint8(this._length++, Number(value & 0xffn));
|
|
1067
|
+
} else if (value <= MAX_VARINT_4BYTE) {
|
|
1068
|
+
this.ensureCapacity(4);
|
|
1069
|
+
this.view.setUint8(this._length++, Number(value >> 24n | 0b10000000n));
|
|
1070
|
+
this.view.setUint8(this._length++, Number(value >> 16n & 0xffn));
|
|
1071
|
+
this.view.setUint8(this._length++, Number(value >> 8n & 0xffn));
|
|
1072
|
+
this.view.setUint8(this._length++, Number(value & 0xffn));
|
|
1073
|
+
} else if (value <= MAX_VARINT_8BYTE) {
|
|
1074
|
+
this.ensureCapacity(8);
|
|
1075
|
+
this.view.setUint8(this._length++, Number(value >> 56n | 0b11000000n));
|
|
1076
|
+
this.view.setUint8(this._length++, Number(value >> 48n & 0xffn));
|
|
1077
|
+
this.view.setUint8(this._length++, Number(value >> 40n & 0xffn));
|
|
1078
|
+
this.view.setUint8(this._length++, Number(value >> 32n & 0xffn));
|
|
1079
|
+
this.view.setUint8(this._length++, Number(value >> 24n & 0xffn));
|
|
1080
|
+
this.view.setUint8(this._length++, Number(value >> 16n & 0xffn));
|
|
1081
|
+
this.view.setUint8(this._length++, Number(value >> 8n & 0xffn));
|
|
1082
|
+
this.view.setUint8(this._length++, Number(value & 0xffn));
|
|
1083
|
+
} else {
|
|
1084
|
+
throw new VarIntOverflowError("putVI", Number(value));
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
putBytes(src) {
|
|
1088
|
+
this.ensureCapacity(src.length);
|
|
1089
|
+
this.buf.set(src, this._length);
|
|
1090
|
+
this._length += src.length;
|
|
1091
|
+
}
|
|
1092
|
+
putLengthPrefixedBytes(src) {
|
|
1093
|
+
this.putVI(src.length);
|
|
1094
|
+
this.putBytes(src);
|
|
1095
|
+
}
|
|
1096
|
+
putKeyValuePair(pair) {
|
|
1097
|
+
const b = pair.serialize().toUint8Array();
|
|
1098
|
+
this.putBytes(b);
|
|
1099
|
+
}
|
|
1100
|
+
putReasonPhrase(reason) {
|
|
1101
|
+
const b = reason.serialize().toUint8Array();
|
|
1102
|
+
this.putBytes(b);
|
|
1103
|
+
}
|
|
1104
|
+
toUint8Array() {
|
|
1105
|
+
return this.buf.slice(0, this._length);
|
|
1106
|
+
}
|
|
1107
|
+
freeze() {
|
|
1108
|
+
const snap = this.buf.slice(0, this._length);
|
|
1109
|
+
return new FrozenByteBuffer4(snap);
|
|
1110
|
+
}
|
|
1111
|
+
putLocation(loc) {
|
|
1112
|
+
this.putVI(loc.group);
|
|
1113
|
+
this.putVI(loc.object);
|
|
1114
|
+
}
|
|
1115
|
+
putTuple(tuple) {
|
|
1116
|
+
const serialized = tuple.serialize();
|
|
1117
|
+
this.putBytes(serialized.toUint8Array());
|
|
1118
|
+
}
|
|
1119
|
+
putFullTrackName(fullTrackName) {
|
|
1120
|
+
const serialized = fullTrackName.serialize();
|
|
1121
|
+
this.putBytes(serialized.toUint8Array());
|
|
1122
|
+
}
|
|
1123
|
+
};
|
|
1124
|
+
var FrozenByteBuffer4 = class extends BaseByteBuffer11 {
|
|
1125
|
+
constructor(buf) {
|
|
1126
|
+
super(buf);
|
|
1127
|
+
}
|
|
1128
|
+
get length() {
|
|
1129
|
+
return this.buf.length;
|
|
1130
|
+
}
|
|
1131
|
+
};
|
|
1132
|
+
|
|
1133
|
+
// src/model/common/pair.ts
|
|
1134
|
+
var MAX_VALUE_LENGTH = 2 ** 16 - 1;
|
|
1135
|
+
var KeyValuePair5 = class _KeyValuePair {
|
|
1136
|
+
/**
|
|
1137
|
+
* The key/type identifier for this pair.
|
|
1138
|
+
* - Even: value is a varint.
|
|
1139
|
+
* - Odd: value is a blob.
|
|
1140
|
+
*/
|
|
1141
|
+
typeValue;
|
|
1142
|
+
/**
|
|
1143
|
+
* The value for this pair.
|
|
1144
|
+
* - If `typeValue` is even: a varint (`bigint`).
|
|
1145
|
+
* - If `typeValue` is odd: a binary blob (`Uint8Array`).
|
|
1146
|
+
*/
|
|
1147
|
+
value;
|
|
1148
|
+
/**
|
|
1149
|
+
* Constructs a new KeyValuePair.
|
|
1150
|
+
* @param typeValue - The key/type identifier.
|
|
1151
|
+
* @param value - The value (varint or blob).
|
|
1152
|
+
* @internal Use static factory methods instead.
|
|
1153
|
+
*/
|
|
1154
|
+
constructor(typeValue, value) {
|
|
1155
|
+
this.typeValue = typeValue;
|
|
1156
|
+
this.value = value;
|
|
1157
|
+
}
|
|
1158
|
+
/**
|
|
1159
|
+
* Creates a new varint KeyValuePair.
|
|
1160
|
+
* @param typeValue - Must be even.
|
|
1161
|
+
* @param value - The varint value.
|
|
1162
|
+
* @returns A KeyValuePair with varint value.
|
|
1163
|
+
* @throws KeyValueFormattingError if typeValue is not even.
|
|
1164
|
+
*/
|
|
1165
|
+
static tryNewVarInt(typeValue, value) {
|
|
1166
|
+
const tv = typeof typeValue === "number" ? BigInt(typeValue) : typeValue;
|
|
1167
|
+
if (tv % 2n !== 0n) {
|
|
1168
|
+
throw new KeyValueFormattingError("KeyValuePair.tryNewVarInt");
|
|
1169
|
+
}
|
|
1170
|
+
const v = typeof value === "number" ? BigInt(value) : value;
|
|
1171
|
+
return new _KeyValuePair(tv, v);
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* Creates a new blob KeyValuePair.
|
|
1175
|
+
* @param typeValue - Must be odd.
|
|
1176
|
+
* @param value - The binary blob value.
|
|
1177
|
+
* @returns A KeyValuePair with blob value.
|
|
1178
|
+
* @throws KeyValueFormattingError if typeValue is not odd.
|
|
1179
|
+
* @throws LengthExceedsMaxError if value length exceeds 65535 bytes.
|
|
1180
|
+
*/
|
|
1181
|
+
static tryNewBytes(typeValue, value) {
|
|
1182
|
+
const tv = typeof typeValue === "number" ? BigInt(typeValue) : typeValue;
|
|
1183
|
+
if (tv % 2n === 0n) {
|
|
1184
|
+
throw new KeyValueFormattingError("KeyValuePair.tryNewBytes");
|
|
1185
|
+
}
|
|
1186
|
+
const len = value.length;
|
|
1187
|
+
if (len > MAX_VALUE_LENGTH) {
|
|
1188
|
+
throw new LengthExceedsMaxError("KeyValuePair.tryNewBytes", MAX_VALUE_LENGTH, len);
|
|
1189
|
+
}
|
|
1190
|
+
return new _KeyValuePair(tv, value);
|
|
1191
|
+
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Serializes this key-value pair to a frozen byte buffer.
|
|
1194
|
+
* @returns The serialized buffer.
|
|
1195
|
+
*/
|
|
1196
|
+
serialize() {
|
|
1197
|
+
const buf = new ByteBuffer();
|
|
1198
|
+
buf.putVI(this.typeValue);
|
|
1199
|
+
if (isVarInt(this)) {
|
|
1200
|
+
buf.putVI(this.value);
|
|
1201
|
+
} else if (isBytes(this)) {
|
|
1202
|
+
buf.putLengthPrefixedBytes(this.value);
|
|
1203
|
+
}
|
|
1204
|
+
return buf.freeze();
|
|
1205
|
+
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Deserializes a KeyValuePair from a buffer.
|
|
1208
|
+
* @param buf - The buffer to read from.
|
|
1209
|
+
* @returns The deserialized KeyValuePair.
|
|
1210
|
+
* @throws LengthExceedsMaxError if blob length exceeds 65535 bytes.
|
|
1211
|
+
* @throws NotEnoughBytesError if buffer does not contain enough bytes.
|
|
1212
|
+
*/
|
|
1213
|
+
static deserialize(buf) {
|
|
1214
|
+
const typeValue = buf.getVI();
|
|
1215
|
+
if (typeValue % 2n === 0n) {
|
|
1216
|
+
const value = buf.getVI();
|
|
1217
|
+
return new _KeyValuePair(typeValue, value);
|
|
1218
|
+
} else {
|
|
1219
|
+
const len = buf.getNumberVI();
|
|
1220
|
+
if (len > MAX_VALUE_LENGTH) {
|
|
1221
|
+
throw new LengthExceedsMaxError("KeyValuePair.deserialize", MAX_VALUE_LENGTH, len);
|
|
1222
|
+
}
|
|
1223
|
+
const value = buf.getBytes(len);
|
|
1224
|
+
return new _KeyValuePair(typeValue, value);
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
/**
|
|
1228
|
+
* Checks if this pair is equal to another.
|
|
1229
|
+
* @param other - The other KeyValuePair.
|
|
1230
|
+
* @returns True if both type and value are equal.
|
|
1231
|
+
*/
|
|
1232
|
+
equals(other) {
|
|
1233
|
+
if (this.typeValue !== other.typeValue) return false;
|
|
1234
|
+
if (isVarInt(this) && isVarInt(other)) {
|
|
1235
|
+
return this.value === other.value;
|
|
1236
|
+
}
|
|
1237
|
+
if (isBytes(this) && isBytes(other)) {
|
|
1238
|
+
const a = this.value;
|
|
1239
|
+
const b = other.value;
|
|
1240
|
+
if (a.length !== b.length) return false;
|
|
1241
|
+
for (let i = 0; i < a.length; i++) {
|
|
1242
|
+
if (a[i] !== b[i]) return false;
|
|
1243
|
+
}
|
|
1244
|
+
return true;
|
|
1245
|
+
}
|
|
1246
|
+
return false;
|
|
1247
|
+
}
|
|
1248
|
+
};
|
|
1249
|
+
function isVarInt(pair) {
|
|
1250
|
+
return pair.typeValue % 2n === 0n;
|
|
1251
|
+
}
|
|
1252
|
+
function isBytes(pair) {
|
|
1253
|
+
return pair.typeValue % 2n !== 0n;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
// src/model/extension_header/capture_time_stamp.ts
|
|
1257
|
+
var CaptureTimestamp = class _CaptureTimestamp {
|
|
1258
|
+
constructor(timestamp) {
|
|
1259
|
+
this.timestamp = timestamp;
|
|
1260
|
+
}
|
|
1261
|
+
static TYPE = 2 /* CaptureTimestamp */;
|
|
1262
|
+
toKeyValuePair() {
|
|
1263
|
+
return KeyValuePair5.tryNewVarInt(_CaptureTimestamp.TYPE, this.timestamp);
|
|
1264
|
+
}
|
|
1265
|
+
static fromKeyValuePair(pair) {
|
|
1266
|
+
const type = Number(pair.typeValue);
|
|
1267
|
+
if (type === _CaptureTimestamp.TYPE && typeof pair.value === "bigint") {
|
|
1268
|
+
return new _CaptureTimestamp(pair.value);
|
|
1269
|
+
}
|
|
1270
|
+
return void 0;
|
|
1271
|
+
}
|
|
1272
|
+
};
|
|
1273
|
+
|
|
1274
|
+
// src/model/extension_header/video_frame_marking.ts
|
|
1275
|
+
var VideoFrameMarking = class _VideoFrameMarking {
|
|
1276
|
+
constructor(value) {
|
|
1277
|
+
this.value = value;
|
|
1278
|
+
}
|
|
1279
|
+
static TYPE = 4 /* VideoFrameMarking */;
|
|
1280
|
+
toKeyValuePair() {
|
|
1281
|
+
return KeyValuePair5.tryNewVarInt(_VideoFrameMarking.TYPE, this.value);
|
|
1282
|
+
}
|
|
1283
|
+
static fromKeyValuePair(pair) {
|
|
1284
|
+
const type = Number(pair.typeValue);
|
|
1285
|
+
if (type === _VideoFrameMarking.TYPE && typeof pair.value === "bigint") {
|
|
1286
|
+
return new _VideoFrameMarking(pair.value);
|
|
1287
|
+
}
|
|
1288
|
+
return void 0;
|
|
1289
|
+
}
|
|
1290
|
+
};
|
|
1291
|
+
|
|
1292
|
+
// src/model/extension_header/audio_level.ts
|
|
1293
|
+
var AudioLevel = class _AudioLevel {
|
|
1294
|
+
constructor(audioLevel) {
|
|
1295
|
+
this.audioLevel = audioLevel;
|
|
1296
|
+
}
|
|
1297
|
+
static TYPE = 6 /* AudioLevel */;
|
|
1298
|
+
toKeyValuePair() {
|
|
1299
|
+
return KeyValuePair5.tryNewVarInt(_AudioLevel.TYPE, this.audioLevel);
|
|
1300
|
+
}
|
|
1301
|
+
static fromKeyValuePair(pair) {
|
|
1302
|
+
const type = Number(pair.typeValue);
|
|
1303
|
+
if (type === _AudioLevel.TYPE && typeof pair.value === "bigint") {
|
|
1304
|
+
return new _AudioLevel(pair.value);
|
|
1305
|
+
}
|
|
1306
|
+
return void 0;
|
|
1307
|
+
}
|
|
1308
|
+
};
|
|
1309
|
+
|
|
1310
|
+
// src/model/extension_header/video_config.ts
|
|
1311
|
+
var VideoConfig = class _VideoConfig {
|
|
1312
|
+
constructor(config) {
|
|
1313
|
+
this.config = config;
|
|
1314
|
+
}
|
|
1315
|
+
static TYPE = 13 /* VideoConfig */;
|
|
1316
|
+
toKeyValuePair() {
|
|
1317
|
+
return KeyValuePair5.tryNewBytes(_VideoConfig.TYPE, this.config);
|
|
1318
|
+
}
|
|
1319
|
+
static fromKeyValuePair(pair) {
|
|
1320
|
+
const type = Number(pair.typeValue);
|
|
1321
|
+
if (type === _VideoConfig.TYPE && pair.value instanceof Uint8Array) {
|
|
1322
|
+
return new _VideoConfig(pair.value);
|
|
1323
|
+
}
|
|
1324
|
+
return void 0;
|
|
1325
|
+
}
|
|
1326
|
+
};
|
|
1327
|
+
|
|
1328
|
+
// src/model/extension_header/extension_header.ts
|
|
1329
|
+
var ExtensionHeader;
|
|
1330
|
+
((ExtensionHeader2) => {
|
|
1331
|
+
function fromKeyValuePair(pair) {
|
|
1332
|
+
return CaptureTimestamp.fromKeyValuePair(pair) || VideoFrameMarking.fromKeyValuePair(pair) || AudioLevel.fromKeyValuePair(pair) || VideoConfig.fromKeyValuePair(pair);
|
|
1333
|
+
}
|
|
1334
|
+
ExtensionHeader2.fromKeyValuePair = fromKeyValuePair;
|
|
1335
|
+
function toKeyValuePair(header) {
|
|
1336
|
+
return header.toKeyValuePair();
|
|
1337
|
+
}
|
|
1338
|
+
ExtensionHeader2.toKeyValuePair = toKeyValuePair;
|
|
1339
|
+
function isCaptureTimestamp(header) {
|
|
1340
|
+
return header instanceof CaptureTimestamp;
|
|
1341
|
+
}
|
|
1342
|
+
ExtensionHeader2.isCaptureTimestamp = isCaptureTimestamp;
|
|
1343
|
+
function isVideoFrameMarking(header) {
|
|
1344
|
+
return header instanceof VideoFrameMarking;
|
|
1345
|
+
}
|
|
1346
|
+
ExtensionHeader2.isVideoFrameMarking = isVideoFrameMarking;
|
|
1347
|
+
function isAudioLevel(header) {
|
|
1348
|
+
return header instanceof AudioLevel;
|
|
1349
|
+
}
|
|
1350
|
+
ExtensionHeader2.isAudioLevel = isAudioLevel;
|
|
1351
|
+
function isVideoConfig(header) {
|
|
1352
|
+
return header instanceof VideoConfig;
|
|
1353
|
+
}
|
|
1354
|
+
ExtensionHeader2.isVideoConfig = isVideoConfig;
|
|
1355
|
+
})(ExtensionHeader || (ExtensionHeader = {}));
|
|
1356
|
+
var ExtensionHeaders2 = class {
|
|
1357
|
+
kvps = [];
|
|
1358
|
+
addCaptureTimestamp(timestamp) {
|
|
1359
|
+
this.kvps.push(new CaptureTimestamp(BigInt(timestamp)).toKeyValuePair());
|
|
1360
|
+
return this;
|
|
1361
|
+
}
|
|
1362
|
+
addVideoFrameMarking(marking) {
|
|
1363
|
+
this.kvps.push(new VideoFrameMarking(BigInt(marking)).toKeyValuePair());
|
|
1364
|
+
return this;
|
|
1365
|
+
}
|
|
1366
|
+
addAudioLevel(audioLevel) {
|
|
1367
|
+
this.kvps.push(new AudioLevel(BigInt(audioLevel)).toKeyValuePair());
|
|
1368
|
+
return this;
|
|
1369
|
+
}
|
|
1370
|
+
addVideoConfig(config) {
|
|
1371
|
+
this.kvps.push(new VideoConfig(config).toKeyValuePair());
|
|
1372
|
+
return this;
|
|
1373
|
+
}
|
|
1374
|
+
addRaw(pair) {
|
|
1375
|
+
this.kvps.push(pair);
|
|
1376
|
+
return this;
|
|
1377
|
+
}
|
|
1378
|
+
build() {
|
|
1379
|
+
return this.kvps;
|
|
1380
|
+
}
|
|
1381
|
+
static fromKeyValuePairs(kvps) {
|
|
1382
|
+
const result = [];
|
|
1383
|
+
for (const kvp of kvps) {
|
|
1384
|
+
const parsed = ExtensionHeader.fromKeyValuePair(kvp);
|
|
1385
|
+
if (parsed) result.push(parsed);
|
|
1386
|
+
}
|
|
1387
|
+
return result;
|
|
1388
|
+
}
|
|
1389
|
+
};
|
|
1390
|
+
|
|
1391
|
+
// src/util/playout_buffer.ts
|
|
1392
|
+
var DEFAULT_TARGET_LATENCY_MS = 100;
|
|
1393
|
+
var DEFAULT_MAX_LATENCY_MS = 1e3;
|
|
1394
|
+
var PlayoutBuffer = class {
|
|
1395
|
+
constructor(objectStream, options) {
|
|
1396
|
+
this.options = options;
|
|
1397
|
+
this.#targetLatencyMs = this.options?.targetLatencyMs ?? DEFAULT_TARGET_LATENCY_MS;
|
|
1398
|
+
this.#maxLatencyMs = this.options?.maxLatencyMs ?? DEFAULT_MAX_LATENCY_MS;
|
|
1399
|
+
this.#clock = this.options?.clock;
|
|
1400
|
+
this.#reader = objectStream.getReader();
|
|
1401
|
+
this.#fillBuffer();
|
|
1402
|
+
this.#serveBuffer();
|
|
1403
|
+
}
|
|
1404
|
+
#reader;
|
|
1405
|
+
#buffer = new Heap((a, b) => {
|
|
1406
|
+
return a.object.location.compare(b.object.location);
|
|
1407
|
+
});
|
|
1408
|
+
#isRunning = true;
|
|
1409
|
+
#targetLatencyMs;
|
|
1410
|
+
#moqObjectCountInBuffer = 0;
|
|
1411
|
+
#moqObjectCountExitingBuffer = 0;
|
|
1412
|
+
#maxLatencyMs;
|
|
1413
|
+
#clock;
|
|
1414
|
+
onObject = null;
|
|
1415
|
+
async hasObjectReady() {
|
|
1416
|
+
const now = this.#getNormalizedTime();
|
|
1417
|
+
const oldest = this.#buffer.peek();
|
|
1418
|
+
return oldest ? now - oldest.createdAt >= this.#targetLatencyMs : false;
|
|
1419
|
+
}
|
|
1420
|
+
getStatus() {
|
|
1421
|
+
const oldest = this.#buffer.peek();
|
|
1422
|
+
const result = {
|
|
1423
|
+
bufferSize: this.#buffer.length,
|
|
1424
|
+
isRunning: this.#isRunning
|
|
1425
|
+
};
|
|
1426
|
+
if (oldest) {
|
|
1427
|
+
result.oldestTimestamp = oldest.createdAt;
|
|
1428
|
+
}
|
|
1429
|
+
return result;
|
|
1430
|
+
}
|
|
1431
|
+
cleanup() {
|
|
1432
|
+
this.#isRunning = false;
|
|
1433
|
+
this.onObject?.(null);
|
|
1434
|
+
}
|
|
1435
|
+
#getNormalizedTime() {
|
|
1436
|
+
if (this.#clock) {
|
|
1437
|
+
return this.#clock.now();
|
|
1438
|
+
}
|
|
1439
|
+
return Date.now();
|
|
1440
|
+
}
|
|
1441
|
+
async #serveBuffer() {
|
|
1442
|
+
while (this.#isRunning) {
|
|
1443
|
+
const now = this.#getNormalizedTime();
|
|
1444
|
+
const oldest = this.#buffer.peek();
|
|
1445
|
+
if (oldest && this.onObject) {
|
|
1446
|
+
const timeUntilReady = this.#targetLatencyMs - (now - oldest.createdAt);
|
|
1447
|
+
if (timeUntilReady <= 0) {
|
|
1448
|
+
const bufferedObj = this.#buffer.pop();
|
|
1449
|
+
this.#moqObjectCountExitingBuffer++;
|
|
1450
|
+
if (this.#moqObjectCountExitingBuffer % 50 === 0) ;
|
|
1451
|
+
this.onObject(bufferedObj.object);
|
|
1452
|
+
if (this.#moqObjectCountExitingBuffer % 50 === 0) ;
|
|
1453
|
+
} else {
|
|
1454
|
+
const sleepTime = Math.min(timeUntilReady, 50);
|
|
1455
|
+
await new Promise((resolve) => setTimeout(resolve, sleepTime));
|
|
1456
|
+
}
|
|
1457
|
+
} else {
|
|
1458
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
async #fillBuffer() {
|
|
1463
|
+
while (this.#isRunning) {
|
|
1464
|
+
try {
|
|
1465
|
+
const { value, done } = await this.#reader.read();
|
|
1466
|
+
if (done) {
|
|
1467
|
+
this.cleanup();
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
this.#evictOnMaxLatency();
|
|
1471
|
+
const bufferedObject = {
|
|
1472
|
+
object: value,
|
|
1473
|
+
createdAt: this.#extractCreatedAt(value)
|
|
1474
|
+
};
|
|
1475
|
+
this.#moqObjectCountInBuffer++;
|
|
1476
|
+
if (this.#moqObjectCountInBuffer % 50 === 0) {
|
|
1477
|
+
}
|
|
1478
|
+
this.#buffer.push(bufferedObject);
|
|
1479
|
+
} catch (error) {
|
|
1480
|
+
this.cleanup();
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
#extractCreatedAt(object) {
|
|
1485
|
+
if (object.extensionHeaders) {
|
|
1486
|
+
const extensionHeaders = ExtensionHeaders2.fromKeyValuePairs(object.extensionHeaders);
|
|
1487
|
+
for (const header of extensionHeaders) {
|
|
1488
|
+
if (ExtensionHeader.isCaptureTimestamp(header)) {
|
|
1489
|
+
return Number(header.timestamp);
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
return this.#getNormalizedTime();
|
|
1494
|
+
}
|
|
1495
|
+
#evictOnMaxLatency() {
|
|
1496
|
+
const now = this.#getNormalizedTime();
|
|
1497
|
+
const oldest = this.#buffer.peek();
|
|
1498
|
+
if (oldest && now - oldest.createdAt > this.#maxLatencyMs) {
|
|
1499
|
+
if (oldest.object.location.object === 0n) {
|
|
1500
|
+
const groupToDrop = oldest.object.location.group;
|
|
1501
|
+
this.#dropGop(groupToDrop);
|
|
1502
|
+
} else {
|
|
1503
|
+
this.#buffer.pop();
|
|
1504
|
+
}
|
|
1505
|
+
this.#evictOnMaxLatency();
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
#dropGop(groupId) {
|
|
1509
|
+
while (this.#buffer.length > 0) {
|
|
1510
|
+
const oldest = this.#buffer.peek();
|
|
1511
|
+
if (oldest && oldest.object.location.group === groupId) {
|
|
1512
|
+
this.#buffer.pop();
|
|
1513
|
+
} else {
|
|
1514
|
+
break;
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
};
|
|
1519
|
+
var DEFAULT_BUFFER_CAPACITY = 50;
|
|
1520
|
+
var DEFAULT_TARGET_LATENCY_MS2 = 500;
|
|
1521
|
+
var DEFAULT_MAX_LATENCY_MS2 = 2e3;
|
|
1522
|
+
var PullPlayoutBuffer = class {
|
|
1523
|
+
constructor(writeStream, options) {
|
|
1524
|
+
this.options = options;
|
|
1525
|
+
this.#bucketCapacity = this.options.bucketCapacity ?? DEFAULT_BUFFER_CAPACITY;
|
|
1526
|
+
this.#targetLatencyMs = this.options.targetLatencyMs ?? DEFAULT_TARGET_LATENCY_MS2;
|
|
1527
|
+
this.#maxLatencyMs = this.options.maxLatencyMs ?? DEFAULT_MAX_LATENCY_MS2;
|
|
1528
|
+
this.#reader = writeStream.getReader();
|
|
1529
|
+
this.#fillBuffer();
|
|
1530
|
+
}
|
|
1531
|
+
#reader;
|
|
1532
|
+
#buffer = new Heap((a, b) => {
|
|
1533
|
+
if (a.location.compare(b.location) <= 0) {
|
|
1534
|
+
if (b.location.compare(a.location) <= 0) {
|
|
1535
|
+
return 0;
|
|
1536
|
+
}
|
|
1537
|
+
return -1;
|
|
1538
|
+
}
|
|
1539
|
+
return 1;
|
|
1540
|
+
});
|
|
1541
|
+
#isRunning = true;
|
|
1542
|
+
#bucketCapacity;
|
|
1543
|
+
#targetLatencyMs;
|
|
1544
|
+
#maxLatencyMs;
|
|
1545
|
+
#lastIncomingTimestamp = 0;
|
|
1546
|
+
#pendingCallback = null;
|
|
1547
|
+
// Pull-based API: Consumer calls this when ready for next object
|
|
1548
|
+
nextObject(callback) {
|
|
1549
|
+
if (this.#buffer.length > 0) {
|
|
1550
|
+
const obj = this.#buffer.pop();
|
|
1551
|
+
callback(obj || null);
|
|
1552
|
+
return;
|
|
1553
|
+
}
|
|
1554
|
+
this.#pendingCallback = callback;
|
|
1555
|
+
}
|
|
1556
|
+
// Check if there's an object ready immediately (non-blocking)
|
|
1557
|
+
hasObjectReady() {
|
|
1558
|
+
return this.#buffer.length > 0;
|
|
1559
|
+
}
|
|
1560
|
+
// Get current buffer status for debugging
|
|
1561
|
+
getStatus() {
|
|
1562
|
+
return {
|
|
1563
|
+
bufferSize: this.#buffer.length,
|
|
1564
|
+
isRunning: this.#isRunning
|
|
1565
|
+
};
|
|
1566
|
+
}
|
|
1567
|
+
// Cleanup method - called when buffer is destroyed
|
|
1568
|
+
cleanup() {
|
|
1569
|
+
this.#isRunning = false;
|
|
1570
|
+
}
|
|
1571
|
+
// Simple background filling - just fills the buffer as objects arrive
|
|
1572
|
+
async #fillBuffer() {
|
|
1573
|
+
while (this.#isRunning) {
|
|
1574
|
+
try {
|
|
1575
|
+
const { value, done } = await this.#reader.read();
|
|
1576
|
+
if (done) {
|
|
1577
|
+
this.#isRunning = false;
|
|
1578
|
+
if (this.#pendingCallback) {
|
|
1579
|
+
const callback = this.#pendingCallback;
|
|
1580
|
+
this.#pendingCallback = null;
|
|
1581
|
+
callback(null);
|
|
1582
|
+
}
|
|
1583
|
+
return;
|
|
1584
|
+
}
|
|
1585
|
+
this.#lastIncomingTimestamp = performance.now();
|
|
1586
|
+
if (this.#buffer.length >= this.#bucketCapacity) {
|
|
1587
|
+
this.#manageBufferOverflow();
|
|
1588
|
+
}
|
|
1589
|
+
this.#buffer.push(value);
|
|
1590
|
+
if (this.#pendingCallback) {
|
|
1591
|
+
const callback = this.#pendingCallback;
|
|
1592
|
+
this.#pendingCallback = null;
|
|
1593
|
+
const obj = this.#buffer.pop();
|
|
1594
|
+
callback(obj || null);
|
|
1595
|
+
}
|
|
1596
|
+
} catch (error) {
|
|
1597
|
+
console.error("Error in fillBuffer:", error);
|
|
1598
|
+
this.#isRunning = false;
|
|
1599
|
+
if (this.#pendingCallback) {
|
|
1600
|
+
const callback = this.#pendingCallback;
|
|
1601
|
+
this.#pendingCallback = null;
|
|
1602
|
+
callback(null);
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
// Manage buffer overflow by dropping oldest GOPs
|
|
1608
|
+
#manageBufferOverflow() {
|
|
1609
|
+
const droppedGops = this.#dropMultipleGopsToTarget();
|
|
1610
|
+
if (droppedGops === 0) {
|
|
1611
|
+
const dropCount = Math.floor(this.#bucketCapacity * 0.2);
|
|
1612
|
+
for (let i = 0; i < dropCount; i++) {
|
|
1613
|
+
this.#buffer.pop();
|
|
1614
|
+
}
|
|
1615
|
+
console.log(`\u{1F5D1}\uFE0F [PULL BUFFER] Buffer overflow: dropped ${dropCount} oldest objects (fallback)`);
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
// Find GOP boundaries based on group IDs
|
|
1619
|
+
#findGopBoundaries(objects) {
|
|
1620
|
+
const gops = [];
|
|
1621
|
+
if (objects.length === 0) return gops;
|
|
1622
|
+
const groupMap = /* @__PURE__ */ new Map();
|
|
1623
|
+
objects.forEach((obj, index) => {
|
|
1624
|
+
const groupId = obj.location.group;
|
|
1625
|
+
if (!groupMap.has(groupId)) {
|
|
1626
|
+
groupMap.set(groupId, []);
|
|
1627
|
+
}
|
|
1628
|
+
groupMap.get(groupId).push(index);
|
|
1629
|
+
});
|
|
1630
|
+
const sortedGroups = Array.from(groupMap.entries()).sort(([a], [b]) => {
|
|
1631
|
+
if (a < b) return -1;
|
|
1632
|
+
if (a > b) return 1;
|
|
1633
|
+
return 0;
|
|
1634
|
+
});
|
|
1635
|
+
sortedGroups.forEach(([, indices]) => {
|
|
1636
|
+
if (indices.length > 0) {
|
|
1637
|
+
gops.push({
|
|
1638
|
+
start: Math.min(...indices),
|
|
1639
|
+
end: Math.max(...indices)
|
|
1640
|
+
});
|
|
1641
|
+
}
|
|
1642
|
+
});
|
|
1643
|
+
console.log(`\u{1F3AC} [PULL BUFFER] Found ${gops.length} GOPs (groups) in buffer`);
|
|
1644
|
+
return gops;
|
|
1645
|
+
}
|
|
1646
|
+
// Drop oldest GOP (complete group)
|
|
1647
|
+
#dropOldestGop() {
|
|
1648
|
+
const allObjects = this.#buffer.toArray();
|
|
1649
|
+
const gops = this.#findGopBoundaries(allObjects);
|
|
1650
|
+
if (gops.length > 0) {
|
|
1651
|
+
const oldestGop = gops[0];
|
|
1652
|
+
if (oldestGop) {
|
|
1653
|
+
const firstObj = allObjects[oldestGop.start];
|
|
1654
|
+
const groupId = firstObj?.location.group;
|
|
1655
|
+
for (let i = oldestGop.start; i <= oldestGop.end; i++) {
|
|
1656
|
+
this.#buffer.pop();
|
|
1657
|
+
}
|
|
1658
|
+
console.log(`\u{1F5D1}\uFE0F [PULL BUFFER] Dropped GOP (Group ${groupId}): ${oldestGop.end - oldestGop.start + 1} objects`);
|
|
1659
|
+
return true;
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
return false;
|
|
1663
|
+
}
|
|
1664
|
+
// Drop multiple GOPs to reach target buffer size
|
|
1665
|
+
#dropMultipleGopsToTarget() {
|
|
1666
|
+
const targetBufferSize = Math.floor(this.#bucketCapacity * 0.7);
|
|
1667
|
+
let droppedGops = 0;
|
|
1668
|
+
while (this.#buffer.length > targetBufferSize && droppedGops < 3) {
|
|
1669
|
+
if (this.#dropOldestGop()) {
|
|
1670
|
+
droppedGops++;
|
|
1671
|
+
} else {
|
|
1672
|
+
break;
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
if (droppedGops > 0) {
|
|
1676
|
+
console.log(
|
|
1677
|
+
`\u{1F5D1}\uFE0F [PULL BUFFER] Dropped ${droppedGops} GOPs to reduce buffer size. New buffer size: ${this.#buffer.length}`
|
|
1678
|
+
);
|
|
1679
|
+
}
|
|
1680
|
+
return droppedGops;
|
|
1681
|
+
}
|
|
1682
|
+
};
|
|
1683
|
+
|
|
1684
|
+
// src/util/clock_normalizer.ts
|
|
1685
|
+
var DEFAULT_SAMPLE_SIZE = 5;
|
|
1686
|
+
var DEFAULT_TIME_SERVER = "https://time.akamai.com/?ms";
|
|
1687
|
+
var ClockNormalizer = class _ClockNormalizer {
|
|
1688
|
+
offset;
|
|
1689
|
+
timeServerUrl;
|
|
1690
|
+
numSamples;
|
|
1691
|
+
constructor(timeServerUrl, offset, numSamples) {
|
|
1692
|
+
this.timeServerUrl = timeServerUrl;
|
|
1693
|
+
this.offset = offset;
|
|
1694
|
+
this.numSamples = numSamples;
|
|
1695
|
+
}
|
|
1696
|
+
static async create(timeServerUrl, numberOfSamples) {
|
|
1697
|
+
const url = timeServerUrl ? timeServerUrl : DEFAULT_TIME_SERVER;
|
|
1698
|
+
const numSamples = numberOfSamples ? numberOfSamples : DEFAULT_SAMPLE_SIZE;
|
|
1699
|
+
const offset = await _ClockNormalizer.calculateSkew(url, numSamples);
|
|
1700
|
+
return new _ClockNormalizer(url, offset, numSamples);
|
|
1701
|
+
}
|
|
1702
|
+
getSkew() {
|
|
1703
|
+
return this.offset;
|
|
1704
|
+
}
|
|
1705
|
+
now() {
|
|
1706
|
+
return Date.now() - this.offset;
|
|
1707
|
+
}
|
|
1708
|
+
async recalibrate() {
|
|
1709
|
+
this.offset = await _ClockNormalizer.calculateSkew(this.timeServerUrl, this.numSamples);
|
|
1710
|
+
return this.offset;
|
|
1711
|
+
}
|
|
1712
|
+
static async calculateSkew(timeServerUrl, numSamples) {
|
|
1713
|
+
const samples = [];
|
|
1714
|
+
for (let i = 0; i < numSamples; i++) {
|
|
1715
|
+
const sample = await _ClockNormalizer.takeSingleSample(timeServerUrl);
|
|
1716
|
+
if (sample !== null) {
|
|
1717
|
+
samples.push(sample);
|
|
1718
|
+
}
|
|
1719
|
+
if (i < numSamples - 1) {
|
|
1720
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
if (samples.length === 0) {
|
|
1724
|
+
throw new Error("Failed to get any valid samples");
|
|
1725
|
+
}
|
|
1726
|
+
const offsets = samples.map((s) => s.offset);
|
|
1727
|
+
const average = offsets.reduce((sum, offset) => sum + offset, 0) / offsets.length;
|
|
1728
|
+
return Math.round(average);
|
|
1729
|
+
}
|
|
1730
|
+
static async takeSingleSample(timeServerUrl) {
|
|
1731
|
+
const separator = timeServerUrl.includes("?") ? "&" : "?";
|
|
1732
|
+
const url = `${timeServerUrl}${separator}_=${Date.now()}`;
|
|
1733
|
+
const t0 = performance.now();
|
|
1734
|
+
const localTimeBeforeRequest = Date.now();
|
|
1735
|
+
const response = await fetch(url, { cache: "no-store" });
|
|
1736
|
+
const serverTimeText = await response.text();
|
|
1737
|
+
const t1 = performance.now();
|
|
1738
|
+
const localTimeAfterRequest = Date.now();
|
|
1739
|
+
const serverTime = parseFloat(serverTimeText);
|
|
1740
|
+
if (isNaN(serverTime)) {
|
|
1741
|
+
return null;
|
|
1742
|
+
}
|
|
1743
|
+
const serverTimeMs = serverTime * 1e3;
|
|
1744
|
+
const rtt = t1 - t0;
|
|
1745
|
+
const oneWayDelay = rtt / 2;
|
|
1746
|
+
const avgLocalTime = (localTimeBeforeRequest + localTimeAfterRequest) / 2;
|
|
1747
|
+
const localTimeAtServer = avgLocalTime - oneWayDelay;
|
|
1748
|
+
const offset = localTimeAtServer - serverTimeMs;
|
|
1749
|
+
return { offset, rtt };
|
|
1750
|
+
}
|
|
1751
|
+
};
|
|
1752
|
+
|
|
1753
|
+
// src/util/get_akamai_offset.ts
|
|
1754
|
+
var AkamaiOffset = class _AkamaiOffset {
|
|
1755
|
+
static _offset = null;
|
|
1756
|
+
static _promise = null;
|
|
1757
|
+
static async getClockSkew() {
|
|
1758
|
+
if (_AkamaiOffset._offset !== null) {
|
|
1759
|
+
return _AkamaiOffset._offset;
|
|
1760
|
+
}
|
|
1761
|
+
if (_AkamaiOffset._promise) {
|
|
1762
|
+
return _AkamaiOffset._promise;
|
|
1763
|
+
}
|
|
1764
|
+
_AkamaiOffset._promise = _AkamaiOffset.ClockSkew().then((offset2) => {
|
|
1765
|
+
_AkamaiOffset._offset = offset2;
|
|
1766
|
+
return offset2;
|
|
1767
|
+
});
|
|
1768
|
+
const offset = await _AkamaiOffset._promise;
|
|
1769
|
+
return Math.round(offset);
|
|
1770
|
+
}
|
|
1771
|
+
static async ClockSkew() {
|
|
1772
|
+
const akamaiUrl = "https://time.akamai.com?ms";
|
|
1773
|
+
performance.clearResourceTimings();
|
|
1774
|
+
const response = await fetch(akamaiUrl);
|
|
1775
|
+
const text = await response.text();
|
|
1776
|
+
const TR = parseFloat(text.trim()) * 1e3;
|
|
1777
|
+
const entry = performance.getEntriesByType("resource").find((e) => e.name.includes("time.akamai.com"));
|
|
1778
|
+
if (!entry) {
|
|
1779
|
+
console.warn("No resource entry found for Akamai time request");
|
|
1780
|
+
return 0;
|
|
1781
|
+
}
|
|
1782
|
+
const T0 = entry.fetchStart + performance.timeOrigin;
|
|
1783
|
+
const T1 = entry.responseStart + performance.timeOrigin;
|
|
1784
|
+
const offset = (T0 + T1) / 2 - TR;
|
|
1785
|
+
return offset;
|
|
1786
|
+
}
|
|
1787
|
+
};
|
|
1788
|
+
|
|
1789
|
+
export { AkamaiOffset, ClockNormalizer, NetworkTelemetry, PlayoutBuffer, PullPlayoutBuffer };
|