moqtail 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{client/index.cjs → client.cjs} +25 -14
- package/dist/{client/index.d.cts → client.d.cts} +8 -2
- package/dist/{client/index.d.ts → client.d.ts} +8 -2
- package/dist/{client/index.js → client.js} +25 -14
- package/dist/index.cjs +239 -316
- package/dist/index.d.cts +4 -5
- package/dist/index.d.ts +4 -5
- package/dist/index.js +239 -311
- package/dist/{model/index.cjs → model.cjs} +214 -0
- package/dist/{model/index.d.ts → model.d.cts} +53 -4
- package/dist/{model/index.d.cts → model.d.ts} +53 -4
- package/dist/{model/index.js → model.js} +214 -1
- package/dist/util.cjs +102 -0
- package/dist/util.d.cts +57 -0
- package/dist/util.d.ts +57 -0
- package/dist/util.js +99 -0
- package/dist/{version_parameter-BpmuiMQj.d.cts → version_parameter-DXBOBueW.d.cts} +986 -2
- package/dist/{version_parameter-f75NkWiO.d.ts → version_parameter-DXBOBueW.d.ts} +986 -2
- package/package.json +20 -23
- package/dist/byte_buffer-BM4uNj4n.d.cts +0 -987
- package/dist/byte_buffer-BM4uNj4n.d.ts +0 -987
- package/dist/util/index.cjs +0 -1804
- package/dist/util/index.d.cts +0 -164
- package/dist/util/index.d.ts +0 -164
- package/dist/util/index.js +0 -1795
package/dist/index.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import Heap from 'heap-js';
|
|
2
|
-
|
|
3
1
|
var __defProp = Object.defineProperty;
|
|
4
2
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
3
|
var __esm = (fn, res) => function __init() {
|
|
@@ -4534,6 +4532,219 @@ init_byte_buffer();
|
|
|
4534
4532
|
init_error();
|
|
4535
4533
|
init_constant2();
|
|
4536
4534
|
|
|
4535
|
+
// src/model/catalog/cmsf.ts
|
|
4536
|
+
function isString(v) {
|
|
4537
|
+
return typeof v === "string";
|
|
4538
|
+
}
|
|
4539
|
+
function isNumber(v) {
|
|
4540
|
+
return typeof v === "number";
|
|
4541
|
+
}
|
|
4542
|
+
function isStringArray(v) {
|
|
4543
|
+
return Array.isArray(v) && v.every((item) => isString(item));
|
|
4544
|
+
}
|
|
4545
|
+
function isValidPackaging(v) {
|
|
4546
|
+
return v === "cmaf" || v === "chunk-per-object" || v === "timeline";
|
|
4547
|
+
}
|
|
4548
|
+
function isValidRole(v) {
|
|
4549
|
+
return v === "video" || v === "audio" || v === "timeline";
|
|
4550
|
+
}
|
|
4551
|
+
function isValidBase64(str) {
|
|
4552
|
+
try {
|
|
4553
|
+
return btoa(atob(str)) === str;
|
|
4554
|
+
} catch {
|
|
4555
|
+
return false;
|
|
4556
|
+
}
|
|
4557
|
+
}
|
|
4558
|
+
function validateTrack(track) {
|
|
4559
|
+
if (typeof track !== "object" || track === null) {
|
|
4560
|
+
throw new Error("Track must be an object");
|
|
4561
|
+
}
|
|
4562
|
+
const t = track;
|
|
4563
|
+
if (!isString(t["name"])) {
|
|
4564
|
+
throw new Error("Track.name must be a string");
|
|
4565
|
+
}
|
|
4566
|
+
if (!isValidPackaging(t["packaging"])) {
|
|
4567
|
+
throw new Error("Track.packaging must be 'cmaf', 'chunk-per-object', or 'timeline'");
|
|
4568
|
+
}
|
|
4569
|
+
if (!isValidRole(t["role"])) {
|
|
4570
|
+
throw new Error("Track.role must be 'video', 'audio', or 'timeline'");
|
|
4571
|
+
}
|
|
4572
|
+
const name = t["name"];
|
|
4573
|
+
const packaging = t["packaging"];
|
|
4574
|
+
const role = t["role"];
|
|
4575
|
+
let codec;
|
|
4576
|
+
if (t["codec"] !== void 0) {
|
|
4577
|
+
if (!isString(t["codec"])) {
|
|
4578
|
+
throw new Error("Track.codec must be a string");
|
|
4579
|
+
}
|
|
4580
|
+
codec = t["codec"];
|
|
4581
|
+
}
|
|
4582
|
+
let depends;
|
|
4583
|
+
if (t["depends"] !== void 0) {
|
|
4584
|
+
if (!isStringArray(t["depends"])) {
|
|
4585
|
+
throw new Error("Track.depends must be an array of strings");
|
|
4586
|
+
}
|
|
4587
|
+
depends = t["depends"];
|
|
4588
|
+
}
|
|
4589
|
+
let mimeType;
|
|
4590
|
+
if (t["mimeType"] !== void 0) {
|
|
4591
|
+
if (!isString(t["mimeType"])) {
|
|
4592
|
+
throw new Error("Track.mimeType must be a string");
|
|
4593
|
+
}
|
|
4594
|
+
mimeType = t["mimeType"];
|
|
4595
|
+
}
|
|
4596
|
+
let width;
|
|
4597
|
+
if (t["width"] !== void 0) {
|
|
4598
|
+
if (!isNumber(t["width"])) {
|
|
4599
|
+
throw new Error("Track.width must be a number");
|
|
4600
|
+
}
|
|
4601
|
+
width = t["width"];
|
|
4602
|
+
}
|
|
4603
|
+
let height;
|
|
4604
|
+
if (t["height"] !== void 0) {
|
|
4605
|
+
if (!isNumber(t["height"])) {
|
|
4606
|
+
throw new Error("Track.height must be a number");
|
|
4607
|
+
}
|
|
4608
|
+
height = t["height"];
|
|
4609
|
+
}
|
|
4610
|
+
let timescale;
|
|
4611
|
+
if (t["timescale"] !== void 0) {
|
|
4612
|
+
if (!isNumber(t["timescale"])) {
|
|
4613
|
+
throw new Error("Track.timescale must be a number");
|
|
4614
|
+
}
|
|
4615
|
+
timescale = t["timescale"];
|
|
4616
|
+
}
|
|
4617
|
+
let bitrate;
|
|
4618
|
+
if (t["bitrate"] !== void 0) {
|
|
4619
|
+
if (!isNumber(t["bitrate"])) {
|
|
4620
|
+
throw new Error("Track.bitrate must be a number");
|
|
4621
|
+
}
|
|
4622
|
+
bitrate = t["bitrate"];
|
|
4623
|
+
}
|
|
4624
|
+
let initData;
|
|
4625
|
+
if (t["initData"] !== void 0) {
|
|
4626
|
+
if (!isString(t["initData"])) {
|
|
4627
|
+
throw new Error("Track.initData must be a string");
|
|
4628
|
+
}
|
|
4629
|
+
if (!isValidBase64(t["initData"])) {
|
|
4630
|
+
throw new Error("Track.initData must be a valid base64 string");
|
|
4631
|
+
}
|
|
4632
|
+
initData = t["initData"];
|
|
4633
|
+
}
|
|
4634
|
+
if (packaging === "cmaf" && (!codec || codec.length === 0)) {
|
|
4635
|
+
throw new Error("codec is required when packaging is 'cmaf'");
|
|
4636
|
+
}
|
|
4637
|
+
if (packaging === "timeline" && mimeType !== "text/csv") {
|
|
4638
|
+
throw new Error("mimeType must be 'text/csv' when packaging is 'timeline'");
|
|
4639
|
+
}
|
|
4640
|
+
if (packaging === "timeline" && (!depends || depends.length === 0)) {
|
|
4641
|
+
throw new Error("At least one dependee must be present when packaging is 'timeline'");
|
|
4642
|
+
}
|
|
4643
|
+
const result = {
|
|
4644
|
+
name,
|
|
4645
|
+
packaging,
|
|
4646
|
+
role
|
|
4647
|
+
};
|
|
4648
|
+
if (codec !== void 0) result.codec = codec;
|
|
4649
|
+
if (depends !== void 0) result.depends = depends;
|
|
4650
|
+
if (mimeType !== void 0) result.mimeType = mimeType;
|
|
4651
|
+
if (width !== void 0) result.width = width;
|
|
4652
|
+
if (height !== void 0) result.height = height;
|
|
4653
|
+
if (timescale !== void 0) result.timescale = timescale;
|
|
4654
|
+
if (bitrate !== void 0) result.bitrate = bitrate;
|
|
4655
|
+
if (initData !== void 0) result.initData = initData;
|
|
4656
|
+
return result;
|
|
4657
|
+
}
|
|
4658
|
+
function validateCMSF(obj) {
|
|
4659
|
+
if (typeof obj !== "object" || obj === null) {
|
|
4660
|
+
throw new Error("CMSF must be an object");
|
|
4661
|
+
}
|
|
4662
|
+
const o = obj;
|
|
4663
|
+
if (!isNumber(o["version"])) {
|
|
4664
|
+
throw new Error("CMSF.version must be a number");
|
|
4665
|
+
}
|
|
4666
|
+
if (!Array.isArray(o["tracks"])) {
|
|
4667
|
+
throw new Error("CMSF.tracks must be an array");
|
|
4668
|
+
}
|
|
4669
|
+
const version = o["version"];
|
|
4670
|
+
const tracksArray = o["tracks"];
|
|
4671
|
+
const tracks = tracksArray.map(validateTrack);
|
|
4672
|
+
return {
|
|
4673
|
+
version,
|
|
4674
|
+
tracks
|
|
4675
|
+
};
|
|
4676
|
+
}
|
|
4677
|
+
var CMSFCatalog = class _CMSFCatalog {
|
|
4678
|
+
#catalog;
|
|
4679
|
+
constructor(catalog) {
|
|
4680
|
+
this.#catalog = catalog;
|
|
4681
|
+
}
|
|
4682
|
+
static from(payload) {
|
|
4683
|
+
const decoder = new TextDecoder();
|
|
4684
|
+
const json = decoder.decode(payload);
|
|
4685
|
+
const catalog = validateCMSF(JSON.parse(json));
|
|
4686
|
+
return new _CMSFCatalog(catalog);
|
|
4687
|
+
}
|
|
4688
|
+
getByTrackName(trackName) {
|
|
4689
|
+
return this.#catalog.tracks.find((track) => track.name === this.extractTrackName(trackName));
|
|
4690
|
+
}
|
|
4691
|
+
getTracks(role) {
|
|
4692
|
+
if (role) return this.#catalog.tracks.filter((track) => track.role === role);
|
|
4693
|
+
return this.#catalog.tracks;
|
|
4694
|
+
}
|
|
4695
|
+
getVideo(index = 0) {
|
|
4696
|
+
const videos = this.#catalog.tracks.filter((track) => track.role === "video");
|
|
4697
|
+
if (index < 0 || index >= videos.length) return void 0;
|
|
4698
|
+
return videos[index];
|
|
4699
|
+
}
|
|
4700
|
+
getAudio(index = 0) {
|
|
4701
|
+
const audios = this.#catalog.tracks.filter((track) => track.role === "audio");
|
|
4702
|
+
if (index < 0 || index >= audios.length) return void 0;
|
|
4703
|
+
return audios[index];
|
|
4704
|
+
}
|
|
4705
|
+
getTimeline(index = 0) {
|
|
4706
|
+
const timelines = this.#catalog.tracks.filter((track) => track.role === "timeline");
|
|
4707
|
+
if (index < 0 || index >= timelines.length) return void 0;
|
|
4708
|
+
return timelines[index];
|
|
4709
|
+
}
|
|
4710
|
+
getCodecString(trackName) {
|
|
4711
|
+
const track = this.#catalog.tracks.find((track2) => track2.name === this.extractTrackName(trackName));
|
|
4712
|
+
if (!track) return void 0;
|
|
4713
|
+
if (!track.codec) throw new Error(`Track ${trackName} does not have a codec`);
|
|
4714
|
+
return track.codec;
|
|
4715
|
+
}
|
|
4716
|
+
getRole(trackName) {
|
|
4717
|
+
const track = this.#catalog.tracks.find((track2) => track2.name === this.extractTrackName(trackName));
|
|
4718
|
+
if (!track) return void 0;
|
|
4719
|
+
return track.role;
|
|
4720
|
+
}
|
|
4721
|
+
getPackaging(trackName) {
|
|
4722
|
+
const track = this.#catalog.tracks.find((track2) => track2.name === this.extractTrackName(trackName));
|
|
4723
|
+
if (!track) return void 0;
|
|
4724
|
+
return track.packaging;
|
|
4725
|
+
}
|
|
4726
|
+
isCMAF(trackName) {
|
|
4727
|
+
const packaging = this.getPackaging(trackName);
|
|
4728
|
+
return packaging === "cmaf" || packaging === "chunk-per-object";
|
|
4729
|
+
}
|
|
4730
|
+
getTimescale(trackName) {
|
|
4731
|
+
const track = this.#catalog.tracks.find((track2) => track2.name === this.extractTrackName(trackName));
|
|
4732
|
+
return track?.timescale;
|
|
4733
|
+
}
|
|
4734
|
+
getInitData(trackName) {
|
|
4735
|
+
const track = this.#catalog.tracks.find((track2) => track2.name === this.extractTrackName(trackName));
|
|
4736
|
+
if (!track) return void 0;
|
|
4737
|
+
if (!track.initData) throw new Error(`Track ${trackName} does not have initData`);
|
|
4738
|
+
return new Uint8Array(
|
|
4739
|
+
atob(track.initData).split("").map((c) => c.charCodeAt(0))
|
|
4740
|
+
);
|
|
4741
|
+
}
|
|
4742
|
+
extractTrackName(input) {
|
|
4743
|
+
const parts = input.split("/");
|
|
4744
|
+
return parts[parts.length - 1];
|
|
4745
|
+
}
|
|
4746
|
+
};
|
|
4747
|
+
|
|
4537
4748
|
// src/model/control/switch.ts
|
|
4538
4749
|
init_byte_buffer();
|
|
4539
4750
|
init_pair();
|
|
@@ -6171,13 +6382,13 @@ var handlerSubscribeNamespaceOk = async (_client, _msg) => {
|
|
|
6171
6382
|
};
|
|
6172
6383
|
|
|
6173
6384
|
// src/client/handler/publish_done.ts
|
|
6174
|
-
init_error2();
|
|
6175
6385
|
var handlerPublishDone = async (client, msg) => {
|
|
6386
|
+
if (client.onPeerPublishDone) {
|
|
6387
|
+
client.onPeerPublishDone(msg);
|
|
6388
|
+
}
|
|
6176
6389
|
const request = client.requests.get(msg.requestId);
|
|
6177
6390
|
if (request instanceof SubscribeRequest) {
|
|
6178
6391
|
request.expectedStreams = msg.streamCount;
|
|
6179
|
-
} else {
|
|
6180
|
-
throw new ProtocolViolationError("handlerPublishDone", "No publish request was found with the given request id");
|
|
6181
6392
|
}
|
|
6182
6393
|
};
|
|
6183
6394
|
|
|
@@ -6267,17 +6478,6 @@ var handlerPublish = async (client, msg) => {
|
|
|
6267
6478
|
if (client.onPeerPublish) {
|
|
6268
6479
|
client.onPeerPublish(msg, stream);
|
|
6269
6480
|
}
|
|
6270
|
-
const publishOk = new PublishOk(
|
|
6271
|
-
msg.requestId,
|
|
6272
|
-
1,
|
|
6273
|
-
255,
|
|
6274
|
-
1 /* Ascending */,
|
|
6275
|
-
2 /* LatestObject */,
|
|
6276
|
-
void 0,
|
|
6277
|
-
void 0,
|
|
6278
|
-
[]
|
|
6279
|
-
);
|
|
6280
|
-
await client.controlStream.send(publishOk);
|
|
6281
6481
|
};
|
|
6282
6482
|
|
|
6283
6483
|
// src/client/publication/publish.ts
|
|
@@ -6799,6 +6999,8 @@ var MOQtailClient = class _MOQtailClient {
|
|
|
6799
6999
|
onDatagramSent;
|
|
6800
7000
|
/** Fired when an inbound PUBLISH control message is received. */
|
|
6801
7001
|
onPeerPublish;
|
|
7002
|
+
/** Fired when an inbound PUBLISH_DONE control message is received. */
|
|
7003
|
+
onPeerPublishDone;
|
|
6802
7004
|
/** Fired when an inbound SUBSCRIBE_NAMESPACE control message is received. */
|
|
6803
7005
|
onPeerSubscribeNamespace;
|
|
6804
7006
|
/** Datagram writer for sending datagrams. */
|
|
@@ -7880,6 +8082,26 @@ var MOQtailClient = class _MOQtailClient {
|
|
|
7880
8082
|
throw error;
|
|
7881
8083
|
}
|
|
7882
8084
|
}
|
|
8085
|
+
/**
|
|
8086
|
+
* Signals the end of a published track to the peer/relay.
|
|
8087
|
+
* * @param publishRequestId - The original requestId used when `publish()` was called.
|
|
8088
|
+
*/
|
|
8089
|
+
async publishDone(publishRequestId, statusCode = 0, reasonPhrase = "Track Ended") {
|
|
8090
|
+
this.#ensureActive();
|
|
8091
|
+
try {
|
|
8092
|
+
if (typeof publishRequestId === "number") publishRequestId = BigInt(publishRequestId);
|
|
8093
|
+
const msg = new PublishDone(publishRequestId, statusCode, 0n, new ReasonPhrase(reasonPhrase));
|
|
8094
|
+
await this.controlStream.send(msg);
|
|
8095
|
+
this.requests.delete(publishRequestId);
|
|
8096
|
+
this.requestIdMap.removeMappingByRequestId(publishRequestId);
|
|
8097
|
+
this.subscriptionAliasMap.delete(publishRequestId);
|
|
8098
|
+
} catch (error) {
|
|
8099
|
+
await this.disconnect(
|
|
8100
|
+
new InternalError("MOQtailClient.publishDone", error instanceof Error ? error.message : String(error))
|
|
8101
|
+
);
|
|
8102
|
+
throw error;
|
|
8103
|
+
}
|
|
8104
|
+
}
|
|
7883
8105
|
/**
|
|
7884
8106
|
* Registers an incoming PUBLISH announcement as a valid data receiver.
|
|
7885
8107
|
* This prepares the client to ingest pushed data streams matching the published alias.
|
|
@@ -8466,300 +8688,6 @@ var NetworkTelemetry = class {
|
|
|
8466
8688
|
}
|
|
8467
8689
|
};
|
|
8468
8690
|
|
|
8469
|
-
// src/util/playout_buffer.ts
|
|
8470
|
-
init_extension_header2();
|
|
8471
|
-
var DEFAULT_TARGET_LATENCY_MS = 100;
|
|
8472
|
-
var DEFAULT_MAX_LATENCY_MS = 1e3;
|
|
8473
|
-
var PlayoutBuffer = class {
|
|
8474
|
-
constructor(objectStream, options) {
|
|
8475
|
-
this.options = options;
|
|
8476
|
-
this.#targetLatencyMs = this.options?.targetLatencyMs ?? DEFAULT_TARGET_LATENCY_MS;
|
|
8477
|
-
this.#maxLatencyMs = this.options?.maxLatencyMs ?? DEFAULT_MAX_LATENCY_MS;
|
|
8478
|
-
this.#clock = this.options?.clock;
|
|
8479
|
-
this.#reader = objectStream.getReader();
|
|
8480
|
-
this.#fillBuffer();
|
|
8481
|
-
this.#serveBuffer();
|
|
8482
|
-
}
|
|
8483
|
-
#reader;
|
|
8484
|
-
#buffer = new Heap((a, b) => {
|
|
8485
|
-
return a.object.location.compare(b.object.location);
|
|
8486
|
-
});
|
|
8487
|
-
#isRunning = true;
|
|
8488
|
-
#targetLatencyMs;
|
|
8489
|
-
#moqObjectCountInBuffer = 0;
|
|
8490
|
-
#moqObjectCountExitingBuffer = 0;
|
|
8491
|
-
#maxLatencyMs;
|
|
8492
|
-
#clock;
|
|
8493
|
-
onObject = null;
|
|
8494
|
-
async hasObjectReady() {
|
|
8495
|
-
const now = this.#getNormalizedTime();
|
|
8496
|
-
const oldest = this.#buffer.peek();
|
|
8497
|
-
return oldest ? now - oldest.createdAt >= this.#targetLatencyMs : false;
|
|
8498
|
-
}
|
|
8499
|
-
getStatus() {
|
|
8500
|
-
const oldest = this.#buffer.peek();
|
|
8501
|
-
const result = {
|
|
8502
|
-
bufferSize: this.#buffer.length,
|
|
8503
|
-
isRunning: this.#isRunning
|
|
8504
|
-
};
|
|
8505
|
-
if (oldest) {
|
|
8506
|
-
result.oldestTimestamp = oldest.createdAt;
|
|
8507
|
-
}
|
|
8508
|
-
return result;
|
|
8509
|
-
}
|
|
8510
|
-
cleanup() {
|
|
8511
|
-
this.#isRunning = false;
|
|
8512
|
-
this.onObject?.(null);
|
|
8513
|
-
}
|
|
8514
|
-
#getNormalizedTime() {
|
|
8515
|
-
if (this.#clock) {
|
|
8516
|
-
return this.#clock.now();
|
|
8517
|
-
}
|
|
8518
|
-
return Date.now();
|
|
8519
|
-
}
|
|
8520
|
-
async #serveBuffer() {
|
|
8521
|
-
while (this.#isRunning) {
|
|
8522
|
-
const now = this.#getNormalizedTime();
|
|
8523
|
-
const oldest = this.#buffer.peek();
|
|
8524
|
-
if (oldest && this.onObject) {
|
|
8525
|
-
const timeUntilReady = this.#targetLatencyMs - (now - oldest.createdAt);
|
|
8526
|
-
if (timeUntilReady <= 0) {
|
|
8527
|
-
const bufferedObj = this.#buffer.pop();
|
|
8528
|
-
this.#moqObjectCountExitingBuffer++;
|
|
8529
|
-
if (this.#moqObjectCountExitingBuffer % 50 === 0) ;
|
|
8530
|
-
this.onObject(bufferedObj.object);
|
|
8531
|
-
if (this.#moqObjectCountExitingBuffer % 50 === 0) ;
|
|
8532
|
-
} else {
|
|
8533
|
-
const sleepTime = Math.min(timeUntilReady, 50);
|
|
8534
|
-
await new Promise((resolve) => setTimeout(resolve, sleepTime));
|
|
8535
|
-
}
|
|
8536
|
-
} else {
|
|
8537
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
8538
|
-
}
|
|
8539
|
-
}
|
|
8540
|
-
}
|
|
8541
|
-
async #fillBuffer() {
|
|
8542
|
-
while (this.#isRunning) {
|
|
8543
|
-
try {
|
|
8544
|
-
const { value, done } = await this.#reader.read();
|
|
8545
|
-
if (done) {
|
|
8546
|
-
this.cleanup();
|
|
8547
|
-
return;
|
|
8548
|
-
}
|
|
8549
|
-
this.#evictOnMaxLatency();
|
|
8550
|
-
const bufferedObject = {
|
|
8551
|
-
object: value,
|
|
8552
|
-
createdAt: this.#extractCreatedAt(value)
|
|
8553
|
-
};
|
|
8554
|
-
this.#moqObjectCountInBuffer++;
|
|
8555
|
-
if (this.#moqObjectCountInBuffer % 50 === 0) {
|
|
8556
|
-
}
|
|
8557
|
-
this.#buffer.push(bufferedObject);
|
|
8558
|
-
} catch (error) {
|
|
8559
|
-
this.cleanup();
|
|
8560
|
-
}
|
|
8561
|
-
}
|
|
8562
|
-
}
|
|
8563
|
-
#extractCreatedAt(object) {
|
|
8564
|
-
if (object.extensionHeaders) {
|
|
8565
|
-
const extensionHeaders = ExtensionHeaders.fromKeyValuePairs(object.extensionHeaders);
|
|
8566
|
-
for (const header of extensionHeaders) {
|
|
8567
|
-
if (ExtensionHeader.isCaptureTimestamp(header)) {
|
|
8568
|
-
return Number(header.timestamp);
|
|
8569
|
-
}
|
|
8570
|
-
}
|
|
8571
|
-
}
|
|
8572
|
-
return this.#getNormalizedTime();
|
|
8573
|
-
}
|
|
8574
|
-
#evictOnMaxLatency() {
|
|
8575
|
-
const now = this.#getNormalizedTime();
|
|
8576
|
-
const oldest = this.#buffer.peek();
|
|
8577
|
-
if (oldest && now - oldest.createdAt > this.#maxLatencyMs) {
|
|
8578
|
-
if (oldest.object.location.object === 0n) {
|
|
8579
|
-
const groupToDrop = oldest.object.location.group;
|
|
8580
|
-
this.#dropGop(groupToDrop);
|
|
8581
|
-
} else {
|
|
8582
|
-
this.#buffer.pop();
|
|
8583
|
-
}
|
|
8584
|
-
this.#evictOnMaxLatency();
|
|
8585
|
-
}
|
|
8586
|
-
}
|
|
8587
|
-
#dropGop(groupId) {
|
|
8588
|
-
while (this.#buffer.length > 0) {
|
|
8589
|
-
const oldest = this.#buffer.peek();
|
|
8590
|
-
if (oldest && oldest.object.location.group === groupId) {
|
|
8591
|
-
this.#buffer.pop();
|
|
8592
|
-
} else {
|
|
8593
|
-
break;
|
|
8594
|
-
}
|
|
8595
|
-
}
|
|
8596
|
-
}
|
|
8597
|
-
};
|
|
8598
|
-
var DEFAULT_BUFFER_CAPACITY = 50;
|
|
8599
|
-
var DEFAULT_TARGET_LATENCY_MS2 = 500;
|
|
8600
|
-
var DEFAULT_MAX_LATENCY_MS2 = 2e3;
|
|
8601
|
-
var PullPlayoutBuffer = class {
|
|
8602
|
-
constructor(writeStream, options) {
|
|
8603
|
-
this.options = options;
|
|
8604
|
-
this.#bucketCapacity = this.options.bucketCapacity ?? DEFAULT_BUFFER_CAPACITY;
|
|
8605
|
-
this.#targetLatencyMs = this.options.targetLatencyMs ?? DEFAULT_TARGET_LATENCY_MS2;
|
|
8606
|
-
this.#maxLatencyMs = this.options.maxLatencyMs ?? DEFAULT_MAX_LATENCY_MS2;
|
|
8607
|
-
this.#reader = writeStream.getReader();
|
|
8608
|
-
this.#fillBuffer();
|
|
8609
|
-
}
|
|
8610
|
-
#reader;
|
|
8611
|
-
#buffer = new Heap((a, b) => {
|
|
8612
|
-
if (a.location.compare(b.location) <= 0) {
|
|
8613
|
-
if (b.location.compare(a.location) <= 0) {
|
|
8614
|
-
return 0;
|
|
8615
|
-
}
|
|
8616
|
-
return -1;
|
|
8617
|
-
}
|
|
8618
|
-
return 1;
|
|
8619
|
-
});
|
|
8620
|
-
#isRunning = true;
|
|
8621
|
-
#bucketCapacity;
|
|
8622
|
-
#targetLatencyMs;
|
|
8623
|
-
#maxLatencyMs;
|
|
8624
|
-
#lastIncomingTimestamp = 0;
|
|
8625
|
-
#pendingCallback = null;
|
|
8626
|
-
// Pull-based API: Consumer calls this when ready for next object
|
|
8627
|
-
nextObject(callback) {
|
|
8628
|
-
if (this.#buffer.length > 0) {
|
|
8629
|
-
const obj = this.#buffer.pop();
|
|
8630
|
-
callback(obj || null);
|
|
8631
|
-
return;
|
|
8632
|
-
}
|
|
8633
|
-
this.#pendingCallback = callback;
|
|
8634
|
-
}
|
|
8635
|
-
// Check if there's an object ready immediately (non-blocking)
|
|
8636
|
-
hasObjectReady() {
|
|
8637
|
-
return this.#buffer.length > 0;
|
|
8638
|
-
}
|
|
8639
|
-
// Get current buffer status for debugging
|
|
8640
|
-
getStatus() {
|
|
8641
|
-
return {
|
|
8642
|
-
bufferSize: this.#buffer.length,
|
|
8643
|
-
isRunning: this.#isRunning
|
|
8644
|
-
};
|
|
8645
|
-
}
|
|
8646
|
-
// Cleanup method - called when buffer is destroyed
|
|
8647
|
-
cleanup() {
|
|
8648
|
-
this.#isRunning = false;
|
|
8649
|
-
}
|
|
8650
|
-
// Simple background filling - just fills the buffer as objects arrive
|
|
8651
|
-
async #fillBuffer() {
|
|
8652
|
-
while (this.#isRunning) {
|
|
8653
|
-
try {
|
|
8654
|
-
const { value, done } = await this.#reader.read();
|
|
8655
|
-
if (done) {
|
|
8656
|
-
this.#isRunning = false;
|
|
8657
|
-
if (this.#pendingCallback) {
|
|
8658
|
-
const callback = this.#pendingCallback;
|
|
8659
|
-
this.#pendingCallback = null;
|
|
8660
|
-
callback(null);
|
|
8661
|
-
}
|
|
8662
|
-
return;
|
|
8663
|
-
}
|
|
8664
|
-
this.#lastIncomingTimestamp = performance.now();
|
|
8665
|
-
if (this.#buffer.length >= this.#bucketCapacity) {
|
|
8666
|
-
this.#manageBufferOverflow();
|
|
8667
|
-
}
|
|
8668
|
-
this.#buffer.push(value);
|
|
8669
|
-
if (this.#pendingCallback) {
|
|
8670
|
-
const callback = this.#pendingCallback;
|
|
8671
|
-
this.#pendingCallback = null;
|
|
8672
|
-
const obj = this.#buffer.pop();
|
|
8673
|
-
callback(obj || null);
|
|
8674
|
-
}
|
|
8675
|
-
} catch (error) {
|
|
8676
|
-
console.error("Error in fillBuffer:", error);
|
|
8677
|
-
this.#isRunning = false;
|
|
8678
|
-
if (this.#pendingCallback) {
|
|
8679
|
-
const callback = this.#pendingCallback;
|
|
8680
|
-
this.#pendingCallback = null;
|
|
8681
|
-
callback(null);
|
|
8682
|
-
}
|
|
8683
|
-
}
|
|
8684
|
-
}
|
|
8685
|
-
}
|
|
8686
|
-
// Manage buffer overflow by dropping oldest GOPs
|
|
8687
|
-
#manageBufferOverflow() {
|
|
8688
|
-
const droppedGops = this.#dropMultipleGopsToTarget();
|
|
8689
|
-
if (droppedGops === 0) {
|
|
8690
|
-
const dropCount = Math.floor(this.#bucketCapacity * 0.2);
|
|
8691
|
-
for (let i = 0; i < dropCount; i++) {
|
|
8692
|
-
this.#buffer.pop();
|
|
8693
|
-
}
|
|
8694
|
-
console.log(`\u{1F5D1}\uFE0F [PULL BUFFER] Buffer overflow: dropped ${dropCount} oldest objects (fallback)`);
|
|
8695
|
-
}
|
|
8696
|
-
}
|
|
8697
|
-
// Find GOP boundaries based on group IDs
|
|
8698
|
-
#findGopBoundaries(objects) {
|
|
8699
|
-
const gops = [];
|
|
8700
|
-
if (objects.length === 0) return gops;
|
|
8701
|
-
const groupMap = /* @__PURE__ */ new Map();
|
|
8702
|
-
objects.forEach((obj, index) => {
|
|
8703
|
-
const groupId = obj.location.group;
|
|
8704
|
-
if (!groupMap.has(groupId)) {
|
|
8705
|
-
groupMap.set(groupId, []);
|
|
8706
|
-
}
|
|
8707
|
-
groupMap.get(groupId).push(index);
|
|
8708
|
-
});
|
|
8709
|
-
const sortedGroups = Array.from(groupMap.entries()).sort(([a], [b]) => {
|
|
8710
|
-
if (a < b) return -1;
|
|
8711
|
-
if (a > b) return 1;
|
|
8712
|
-
return 0;
|
|
8713
|
-
});
|
|
8714
|
-
sortedGroups.forEach(([, indices]) => {
|
|
8715
|
-
if (indices.length > 0) {
|
|
8716
|
-
gops.push({
|
|
8717
|
-
start: Math.min(...indices),
|
|
8718
|
-
end: Math.max(...indices)
|
|
8719
|
-
});
|
|
8720
|
-
}
|
|
8721
|
-
});
|
|
8722
|
-
console.log(`\u{1F3AC} [PULL BUFFER] Found ${gops.length} GOPs (groups) in buffer`);
|
|
8723
|
-
return gops;
|
|
8724
|
-
}
|
|
8725
|
-
// Drop oldest GOP (complete group)
|
|
8726
|
-
#dropOldestGop() {
|
|
8727
|
-
const allObjects = this.#buffer.toArray();
|
|
8728
|
-
const gops = this.#findGopBoundaries(allObjects);
|
|
8729
|
-
if (gops.length > 0) {
|
|
8730
|
-
const oldestGop = gops[0];
|
|
8731
|
-
if (oldestGop) {
|
|
8732
|
-
const firstObj = allObjects[oldestGop.start];
|
|
8733
|
-
const groupId = firstObj?.location.group;
|
|
8734
|
-
for (let i = oldestGop.start; i <= oldestGop.end; i++) {
|
|
8735
|
-
this.#buffer.pop();
|
|
8736
|
-
}
|
|
8737
|
-
console.log(`\u{1F5D1}\uFE0F [PULL BUFFER] Dropped GOP (Group ${groupId}): ${oldestGop.end - oldestGop.start + 1} objects`);
|
|
8738
|
-
return true;
|
|
8739
|
-
}
|
|
8740
|
-
}
|
|
8741
|
-
return false;
|
|
8742
|
-
}
|
|
8743
|
-
// Drop multiple GOPs to reach target buffer size
|
|
8744
|
-
#dropMultipleGopsToTarget() {
|
|
8745
|
-
const targetBufferSize = Math.floor(this.#bucketCapacity * 0.7);
|
|
8746
|
-
let droppedGops = 0;
|
|
8747
|
-
while (this.#buffer.length > targetBufferSize && droppedGops < 3) {
|
|
8748
|
-
if (this.#dropOldestGop()) {
|
|
8749
|
-
droppedGops++;
|
|
8750
|
-
} else {
|
|
8751
|
-
break;
|
|
8752
|
-
}
|
|
8753
|
-
}
|
|
8754
|
-
if (droppedGops > 0) {
|
|
8755
|
-
console.log(
|
|
8756
|
-
`\u{1F5D1}\uFE0F [PULL BUFFER] Dropped ${droppedGops} GOPs to reduce buffer size. New buffer size: ${this.#buffer.length}`
|
|
8757
|
-
);
|
|
8758
|
-
}
|
|
8759
|
-
return droppedGops;
|
|
8760
|
-
}
|
|
8761
|
-
};
|
|
8762
|
-
|
|
8763
8691
|
// src/util/clock_normalizer.ts
|
|
8764
8692
|
var DEFAULT_SAMPLE_SIZE = 5;
|
|
8765
8693
|
var DEFAULT_TIME_SERVER = "https://time.akamai.com/?ms";
|
|
@@ -8829,4 +8757,4 @@ var ClockNormalizer = class _ClockNormalizer {
|
|
|
8829
8757
|
}
|
|
8830
8758
|
};
|
|
8831
8759
|
|
|
8832
|
-
export { AudioLevel, AuthorizationToken, BaseByteBuffer12 as BaseByteBuffer, ByteBuffer, CaptureTimestamp, CastingError, ClientSetup, ClockNormalizer, CommonType, ControlMessage, ControlMessageType, ControlStream, DRAFT_14, DatagramObject, DatagramStatus, DeliveryTimeout, ExtensionHeader, ExtensionHeaders, Fetch, FetchCancel, FetchError, FetchErrorCode, FetchHeader, FetchHeaderType, FetchObject, FetchOk, FetchPublication, FetchRequest, FetchType, FilterType, FrozenByteBuffer5 as FrozenByteBuffer, FullTrackName, GoAway, GroupOrder, Header, HybridTrackSource, InternalError, InvalidTypeError, InvalidUTF8Error, KeyValueFormattingError, KeyValuePair, LOCHeaderExtensionId, LengthExceedsMaxError, LiveTrackSource, Location, MAX_FULL_TRACK_NAME_LENGTH, MAX_NAMESPACE_TUPLE_COUNT, MAX_REASON_PHRASE_LEN, MOQtailClient, MOQtailError, MaxAuthTokenCacheSize, MaxCacheDuration, MaxRequestId, MaxRequestId2 as MaxRequestIdParameter, MemoryObjectCache, MoqtObject, NetworkTelemetry, NotEnoughBytesError, ObjectDatagramStatusType, ObjectDatagramType, ObjectForwardingPreference, ObjectStatus, Path,
|
|
8760
|
+
export { AudioLevel, AuthorizationToken, BaseByteBuffer12 as BaseByteBuffer, ByteBuffer, CMSFCatalog, CaptureTimestamp, CastingError, ClientSetup, ClockNormalizer, CommonType, ControlMessage, ControlMessageType, ControlStream, DRAFT_14, DatagramObject, DatagramStatus, DeliveryTimeout, ExtensionHeader, ExtensionHeaders, Fetch, FetchCancel, FetchError, FetchErrorCode, FetchHeader, FetchHeaderType, FetchObject, FetchOk, FetchPublication, FetchRequest, FetchType, FilterType, FrozenByteBuffer5 as FrozenByteBuffer, FullTrackName, GoAway, GroupOrder, Header, HybridTrackSource, InternalError, InvalidTypeError, InvalidUTF8Error, KeyValueFormattingError, KeyValuePair, LOCHeaderExtensionId, LengthExceedsMaxError, LiveTrackSource, Location, MAX_FULL_TRACK_NAME_LENGTH, MAX_NAMESPACE_TUPLE_COUNT, MAX_REASON_PHRASE_LEN, MOQtailClient, MOQtailError, MaxAuthTokenCacheSize, MaxCacheDuration, MaxRequestId, MaxRequestId2 as MaxRequestIdParameter, MemoryObjectCache, MoqtObject, NetworkTelemetry, NotEnoughBytesError, ObjectDatagramStatusType, ObjectDatagramType, ObjectForwardingPreference, ObjectStatus, Path, ProtocolViolationError, Publish, PublishDone, PublishDoneStatusCode, PublishError, PublishErrorCode, PublishNamespace, PublishNamespaceCancel, PublishNamespaceDone, PublishNamespaceError, PublishNamespaceErrorCode, PublishNamespaceOk, PublishNamespaceRequest, PublishOk, PublishPublication, PublishRequest, ReasonPhrase, RecvDatagramStream, RecvStream, RequestIdError, RequestIdMap, RequestsBlocked, RingBufferObjectCache, SendDatagramStream, SendStream, ServerSetup, SetupParameter, SetupParameterType, SetupParameters, StaticTrackSource, SubgroupHeader, SubgroupHeaderType, SubgroupObject, Subscribe, SubscribeError, SubscribeErrorCode, SubscribeNamespace, SubscribeNamespaceError, SubscribeNamespaceErrorCode, SubscribeNamespaceOk, SubscribeNamespaceRequest, SubscribeOk, SubscribePublication, SubscribeRequest, SubscribeUpdate, Switch, TerminationCode, TerminationError, TimeoutError, TokenAliasType, TrackNameError, TrackStatus, TrackStatusCode, TrackStatusError, TrackStatusOk, Tuple, TupleField, Unsubscribe, UnsubscribeNamespace, VarIntOverflowError, VersionSpecificParameter, VersionSpecificParameterType, VersionSpecificParameters, VideoConfig, VideoFrameMarking, commonTypeFromNumber, controlMessageTypeFromBigInt, fetchErrorCodeFromBigInt, fetchTypeFromBigInt, filterTypeFromBigInt, groupOrderFromNumber, isBytes, isVarInt, locHeaderExtensionIdFromNumber, publishDoneStatusCodeFromBigInt, publishErrorCodeFromBigInt, publishNamespaceErrorCodeFromBigInt, setupParameterTypeFromNumber, subscribeErrorCodeFromBigInt, subscribeNamespaceErrorCodeFromBigInt, tokenAliasTypeFromNumber, trackStatusCodeFromBigInt, versionSpecificParameterTypeFromNumber };
|