@ahmedrowaihi/mediabunny-server 1.65.2-beta.415 → 1.65.2-beta.534

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.
@@ -50,11 +50,12 @@ var MediabunnyServer = (() => {
50
50
  NetworkSource: () => NetworkSource,
51
51
  RistSource: () => RistSource,
52
52
  RtmpSource: () => RtmpSource,
53
+ SrtTarget: () => SrtTarget,
53
54
  _serverOptions: () => _serverOptions,
54
55
  registerMediabunnyServer: () => registerMediabunnyServer,
55
56
  toAvFrame: () => toAvFrame
56
57
  });
57
- var import_mediabunny11 = __require("mediabunny");
58
+ var import_mediabunny12 = __require("mediabunny");
58
59
  var NodeAv8 = __toESM(__require("node-av"));
59
60
 
60
61
  // packages/server/src/video-decoder.ts
@@ -5524,10 +5525,206 @@ var MediabunnyServer = (() => {
5524
5525
  }
5525
5526
  };
5526
5527
 
5528
+ // packages/server/src/srt-target.ts
5529
+ var import_mediabunny11 = __require("mediabunny");
5530
+ var import_node_av4 = __require("node-av");
5531
+ var import_constants3 = __require("node-av/constants");
5532
+ var PAYLOAD_SIZE = 1316;
5533
+ var DEFAULT_MAX_QUEUE_BYTES = 2 * 1024 * 1024;
5534
+ var DEFAULT_LINGER_SECONDS = 5;
5535
+ var PBKEYLEN_BY_BITS = { 128: 16, 192: 24, 256: 32 };
5536
+ var SrtSink = class {
5537
+ constructor(url, maxQueueBytes) {
5538
+ this.url = url;
5539
+ this.maxQueueBytes = maxQueueBytes;
5540
+ this.opening = null;
5541
+ this.pumping = null;
5542
+ this.failure = null;
5543
+ this.closed = false;
5544
+ this.queue = { chunks: [], bytes: 0 };
5545
+ this.partial = new Uint8Array(PAYLOAD_SIZE);
5546
+ this.partialLength = 0;
5547
+ this.droppedBytes = 0;
5548
+ this.droppedDatagrams = 0;
5549
+ this.sentBytes = 0;
5550
+ this.writable = new WritableStream({
5551
+ write: (chunk) => this.enqueue(chunk),
5552
+ close: () => this.finish(),
5553
+ abort: () => this.finish(true)
5554
+ });
5555
+ }
5556
+ /** @internal */
5557
+ async open() {
5558
+ const context = new import_node_av4.IOContext();
5559
+ const ret = await context.open2(this.url, import_constants3.AVIO_FLAG_WRITE);
5560
+ if (ret < 0) {
5561
+ throw new Error(`SrtTarget: could not open ${this.url} (AVERROR ${ret}).`);
5562
+ }
5563
+ this.io = context;
5564
+ }
5565
+ /**
5566
+ * Takes the bytes and returns, rather than awaiting the socket: a target that pushed back here would
5567
+ * stall the muxer for as long as the receiver was behind, which for a live feed is unbounded.
5568
+ */
5569
+ enqueue(chunk) {
5570
+ if (this.failure) {
5571
+ throw this.failure;
5572
+ }
5573
+ if (this.queue.bytes + chunk.byteLength > this.maxQueueBytes) {
5574
+ this.droppedBytes += chunk.byteLength;
5575
+ this.droppedDatagrams += Math.ceil(chunk.byteLength / PAYLOAD_SIZE);
5576
+ return;
5577
+ }
5578
+ this.queue.chunks.push(chunk);
5579
+ this.queue.bytes += chunk.byteLength;
5580
+ this.pumping ??= this.pump().finally(() => {
5581
+ this.pumping = null;
5582
+ });
5583
+ }
5584
+ /** @internal */
5585
+ async writeDatagram(bytes) {
5586
+ const io = this.io;
5587
+ await io.write(Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength));
5588
+ if (io.error < 0) {
5589
+ throw new Error(`SrtTarget: the peer stopped accepting data (AVERROR ${io.error}).`);
5590
+ }
5591
+ this.sentBytes += bytes.byteLength;
5592
+ }
5593
+ /** @internal */
5594
+ async pump() {
5595
+ try {
5596
+ if (!this.io) {
5597
+ this.opening ??= this.open();
5598
+ await this.opening;
5599
+ }
5600
+ while (this.queue.chunks.length > 0) {
5601
+ const chunk = this.queue.chunks.shift();
5602
+ this.queue.bytes -= chunk.byteLength;
5603
+ let offset = 0;
5604
+ while (offset < chunk.byteLength) {
5605
+ const take = Math.min(PAYLOAD_SIZE - this.partialLength, chunk.byteLength - offset);
5606
+ this.partial.set(chunk.subarray(offset, offset + take), this.partialLength);
5607
+ this.partialLength += take;
5608
+ offset += take;
5609
+ if (this.partialLength === PAYLOAD_SIZE) {
5610
+ this.partialLength = 0;
5611
+ await this.writeDatagram(this.partial);
5612
+ }
5613
+ }
5614
+ }
5615
+ } catch (error) {
5616
+ this.failure ??= error instanceof Error ? error : new Error(String(error));
5617
+ this.queue = { chunks: [], bytes: 0 };
5618
+ }
5619
+ }
5620
+ /** Drains what is queued, then closes. Awaited by the output's `finalize()`. */
5621
+ async finish(aborting = false) {
5622
+ if (this.closed) {
5623
+ return;
5624
+ }
5625
+ this.closed = true;
5626
+ await this.pumping?.catch(() => {
5627
+ });
5628
+ if (!aborting && !this.failure && this.io && this.partialLength > 0) {
5629
+ await this.writeDatagram(this.partial.subarray(0, this.partialLength)).catch((error) => {
5630
+ this.failure ??= error instanceof Error ? error : new Error(String(error));
5631
+ });
5632
+ }
5633
+ if (this.io) {
5634
+ await this.io.flush().catch(() => {
5635
+ });
5636
+ await this.io.closep().catch(() => {
5637
+ });
5638
+ }
5639
+ if (this.failure && !aborting) {
5640
+ throw this.failure;
5641
+ }
5642
+ }
5643
+ };
5644
+ var srtUrl = (rawUrl, options) => {
5645
+ let url;
5646
+ try {
5647
+ url = new URL(rawUrl);
5648
+ } catch {
5649
+ throw new TypeError(`SrtTarget: ${JSON.stringify(rawUrl)} is not a URL.`);
5650
+ }
5651
+ if (url.protocol !== "srt:") {
5652
+ throw new TypeError(`SrtTarget: expected an srt:// URL, got ${JSON.stringify(url.protocol)}.`);
5653
+ }
5654
+ const connectTimeout = options.connectTimeout ?? 30;
5655
+ const micros = (seconds) => String(Math.max(1, Math.round(seconds * 1e6)));
5656
+ if (!url.searchParams.has("transtype")) {
5657
+ url.searchParams.set("transtype", "live");
5658
+ }
5659
+ if (!url.searchParams.has("payload_size")) {
5660
+ url.searchParams.set("payload_size", String(PAYLOAD_SIZE));
5661
+ }
5662
+ if (!url.searchParams.has("mode")) {
5663
+ url.searchParams.set("mode", "caller");
5664
+ }
5665
+ if (!url.searchParams.has("linger")) {
5666
+ url.searchParams.set("linger", String(DEFAULT_LINGER_SECONDS));
5667
+ }
5668
+ if (options.passphrase !== void 0) {
5669
+ url.searchParams.set("passphrase", options.passphrase);
5670
+ url.searchParams.set("pbkeylen", String(PBKEYLEN_BY_BITS[options.keyLength ?? 128]));
5671
+ }
5672
+ if (options.latency !== void 0) {
5673
+ url.searchParams.set("latency", micros(options.latency / 1e3));
5674
+ }
5675
+ if (url.searchParams.get("mode") === "listener") {
5676
+ url.searchParams.set("listen_timeout", micros(connectTimeout));
5677
+ }
5678
+ url.searchParams.set("timeout", micros(connectTimeout));
5679
+ return url.toString();
5680
+ };
5681
+ var SrtTarget = class extends import_mediabunny11.AppendOnlyStreamTarget {
5682
+ constructor(url, options = {}) {
5683
+ if (typeof url !== "string") {
5684
+ throw new TypeError("url must be a string.");
5685
+ }
5686
+ if (!options || typeof options !== "object") {
5687
+ throw new TypeError("options must be an object.");
5688
+ }
5689
+ if (options.connectTimeout !== void 0 && (!Number.isFinite(options.connectTimeout) || options.connectTimeout <= 0)) {
5690
+ throw new TypeError("options.connectTimeout, when provided, must be a positive number.");
5691
+ }
5692
+ if (options.passphrase !== void 0 && (typeof options.passphrase !== "string" || options.passphrase.length < 10 || options.passphrase.length > 79)) {
5693
+ throw new TypeError("options.passphrase, when provided, must be a string of 10 to 79 characters.");
5694
+ }
5695
+ if (options.keyLength !== void 0 && PBKEYLEN_BY_BITS[options.keyLength] === void 0) {
5696
+ throw new TypeError("options.keyLength, when provided, must be 128, 192 or 256.");
5697
+ }
5698
+ if (options.latency !== void 0 && (!Number.isFinite(options.latency) || options.latency <= 0)) {
5699
+ throw new TypeError("options.latency, when provided, must be a positive number of milliseconds.");
5700
+ }
5701
+ if (options.maxQueueBytes !== void 0 && (!Number.isInteger(options.maxQueueBytes) || options.maxQueueBytes < PAYLOAD_SIZE)) {
5702
+ throw new TypeError(
5703
+ `options.maxQueueBytes, when provided, must be an integer of at least ${PAYLOAD_SIZE}.`
5704
+ );
5705
+ }
5706
+ const sink = new SrtSink(srtUrl(url, options), options.maxQueueBytes ?? DEFAULT_MAX_QUEUE_BYTES);
5707
+ super(sink.writable);
5708
+ this.sink = sink;
5709
+ }
5710
+ /** Bytes handed to the socket so far. */
5711
+ get sentBytes() {
5712
+ return this.sink.sentBytes;
5713
+ }
5714
+ /** Bytes dropped because the receiver was not keeping up. Stays 0 for a receiver that keeps up. */
5715
+ get droppedBytes() {
5716
+ return this.sink.droppedBytes;
5717
+ }
5718
+ /** Datagrams' worth of dropped bytes, which is the unit a receiver sees missing. */
5719
+ get droppedDatagrams() {
5720
+ return this.sink.droppedDatagrams;
5721
+ }
5722
+ };
5723
+
5527
5724
  // packages/server/src/index.ts
5528
5725
  var SERVER_LOADED_SYMBOL = Symbol.for("@mediabunny/server loaded");
5529
5726
  if (globalThis[SERVER_LOADED_SYMBOL]) {
5530
- import_mediabunny11.Logging._error(
5727
+ import_mediabunny12.Logging._error(
5531
5728
  "[WARNING]\n@mediabunny/server was loaded twice. This will likely cause the package not to work correctly. Check if multiple dependencies are importing different versions of @mediabunny/server, or if something is being bundled incorrectly."
5532
5729
  );
5533
5730
  }
@@ -5549,21 +5746,21 @@ var MediabunnyServer = (() => {
5549
5746
  registered2 = true;
5550
5747
  _serverOptions = options;
5551
5748
  NodeAv8.Log.setLevel(NodeAv8.AV_LOG_ERROR);
5552
- (0, import_mediabunny11.registerDecoder)(NodeAvVideoDecoder);
5749
+ (0, import_mediabunny12.registerDecoder)(NodeAvVideoDecoder);
5553
5750
  registerProresDecoder();
5554
- (0, import_mediabunny11.registerEncoder)(NodeAvVideoEncoder);
5555
- (0, import_mediabunny11.registerDecoder)(NodeAvAudioDecoder);
5556
- (0, import_mediabunny11.registerEncoder)(NodeAvAudioEncoder);
5557
- (0, import_mediabunny11.registerVideoSampleTransformer)(transformVideoSample);
5751
+ (0, import_mediabunny12.registerEncoder)(NodeAvVideoEncoder);
5752
+ (0, import_mediabunny12.registerDecoder)(NodeAvAudioDecoder);
5753
+ (0, import_mediabunny12.registerEncoder)(NodeAvAudioEncoder);
5754
+ (0, import_mediabunny12.registerVideoSampleTransformer)(transformVideoSample);
5558
5755
  };
5559
5756
  var toAvFrame = async (sample, frame) => {
5560
- if (!(sample instanceof import_mediabunny11.VideoSample) && !(sample instanceof import_mediabunny11.AudioSample)) {
5757
+ if (!(sample instanceof import_mediabunny12.VideoSample) && !(sample instanceof import_mediabunny12.AudioSample)) {
5561
5758
  throw new TypeError("sample must be a VideoSample or an AudioSample.");
5562
5759
  }
5563
5760
  if (!(frame instanceof NodeAv8.Frame)) {
5564
5761
  throw new TypeError("frame must be a NodeAv.Frame.");
5565
5762
  }
5566
- if (sample instanceof import_mediabunny11.VideoSample) {
5763
+ if (sample instanceof import_mediabunny12.VideoSample) {
5567
5764
  if (sample._data instanceof AvFrameVideoSampleResource) {
5568
5765
  frame.unref();
5569
5766
  frame.ref(sample._data.frame);