werift 0.23.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/README.md +46 -0
  2. package/lib/common/src/transport.d.ts +22 -9
  3. package/lib/common/src/transport.js +152 -25
  4. package/lib/common/src/transport.js.map +1 -1
  5. package/lib/ice/src/candidate.d.ts +16 -1
  6. package/lib/ice/src/candidate.js +99 -15
  7. package/lib/ice/src/candidate.js.map +1 -1
  8. package/lib/ice/src/ice.d.ts +2 -0
  9. package/lib/ice/src/ice.js +171 -41
  10. package/lib/ice/src/ice.js.map +1 -1
  11. package/lib/ice/src/iceBase.d.ts +23 -2
  12. package/lib/ice/src/iceBase.js +63 -1
  13. package/lib/ice/src/iceBase.js.map +1 -1
  14. package/lib/ice/src/index.d.ts +2 -0
  15. package/lib/ice/src/index.js +2 -0
  16. package/lib/ice/src/index.js.map +1 -1
  17. package/lib/ice/src/stun/const.d.ts +1 -26
  18. package/lib/ice/src/stun/const.js +15 -54
  19. package/lib/ice/src/stun/const.js.map +1 -1
  20. package/lib/ice/src/stun/message.d.ts +1 -25
  21. package/lib/ice/src/stun/message.js +15 -149
  22. package/lib/ice/src/stun/message.js.map +1 -1
  23. package/lib/ice/src/stun/protocol.d.ts +1 -1
  24. package/lib/ice/src/stun/protocol.js +2 -2
  25. package/lib/ice/src/stun/protocol.js.map +1 -1
  26. package/lib/ice/src/stun/tcpFrame.d.ts +5 -0
  27. package/lib/ice/src/stun/tcpFrame.js +26 -0
  28. package/lib/ice/src/stun/tcpFrame.js.map +1 -0
  29. package/lib/ice/src/stun/tcpProtocol.d.ts +51 -0
  30. package/lib/ice/src/stun/tcpProtocol.js +326 -0
  31. package/lib/ice/src/stun/tcpProtocol.js.map +1 -0
  32. package/lib/ice/src/stun/transaction.d.ts +2 -1
  33. package/lib/ice/src/stun/transaction.js +9 -3
  34. package/lib/ice/src/stun/transaction.js.map +1 -1
  35. package/lib/ice/src/turn/frame.d.ts +1 -0
  36. package/lib/ice/src/turn/frame.js +18 -0
  37. package/lib/ice/src/turn/frame.js.map +1 -0
  38. package/lib/ice/src/turn/protocol.d.ts +9 -7
  39. package/lib/ice/src/turn/protocol.js +32 -44
  40. package/lib/ice/src/turn/protocol.js.map +1 -1
  41. package/lib/ice/src/types/model.d.ts +1 -1
  42. package/lib/ice/src/types/model.js.map +1 -1
  43. package/lib/{ice → ice-server}/src/stun/attributes.d.ts +12 -5
  44. package/lib/{ice → ice-server}/src/stun/attributes.js +60 -19
  45. package/lib/ice-server/src/stun/attributes.js.map +1 -0
  46. package/lib/ice-server/src/stun/const.d.ts +28 -0
  47. package/lib/ice-server/src/stun/const.js +88 -0
  48. package/lib/ice-server/src/stun/const.js.map +1 -0
  49. package/lib/ice-server/src/stun/message.d.ts +37 -0
  50. package/lib/ice-server/src/stun/message.js +190 -0
  51. package/lib/ice-server/src/stun/message.js.map +1 -0
  52. package/lib/ice-server/src/turn/auth.d.ts +1 -0
  53. package/lib/ice-server/src/turn/auth.js +10 -0
  54. package/lib/ice-server/src/turn/auth.js.map +1 -0
  55. package/lib/ice-server/src/turn/frame.d.ts +13 -0
  56. package/lib/ice-server/src/turn/frame.js +69 -0
  57. package/lib/ice-server/src/turn/frame.js.map +1 -0
  58. package/lib/index.mjs +2688 -855
  59. package/lib/nonstandard/index.mjs +1753 -403
  60. package/lib/rtp/src/extra/container/mp4/container.d.ts +9 -6
  61. package/lib/rtp/src/extra/container/mp4/container.js +258 -173
  62. package/lib/rtp/src/extra/container/mp4/container.js.map +1 -1
  63. package/lib/rtp/src/extra/container/mp4/h264.d.ts +1 -0
  64. package/lib/rtp/src/extra/container/mp4/h264.js +13 -0
  65. package/lib/rtp/src/extra/container/mp4/h264.js.map +1 -1
  66. package/lib/rtp/src/extra/processor/mp4.d.ts +11 -7
  67. package/lib/rtp/src/extra/processor/mp4.js +115 -60
  68. package/lib/rtp/src/extra/processor/mp4.js.map +1 -1
  69. package/lib/rtp/src/extra/processor/mp4Callback.d.ts +2 -3
  70. package/lib/rtp/src/extra/processor/mp4Callback.js +5 -3
  71. package/lib/rtp/src/extra/processor/mp4Callback.js.map +1 -1
  72. package/lib/webrtc/src/dataChannel.d.ts +3 -2
  73. package/lib/webrtc/src/dataChannel.js +9 -2
  74. package/lib/webrtc/src/dataChannel.js.map +1 -1
  75. package/lib/webrtc/src/errors.d.ts +3 -0
  76. package/lib/webrtc/src/errors.js +11 -0
  77. package/lib/webrtc/src/errors.js.map +1 -0
  78. package/lib/webrtc/src/helper.d.ts +5 -1
  79. package/lib/webrtc/src/helper.js +33 -1
  80. package/lib/webrtc/src/helper.js.map +1 -1
  81. package/lib/webrtc/src/media/receiver/statistics.d.ts +4 -1
  82. package/lib/webrtc/src/media/receiver/statistics.js +22 -0
  83. package/lib/webrtc/src/media/receiver/statistics.js.map +1 -1
  84. package/lib/webrtc/src/media/rtpReceiver.d.ts +15 -2
  85. package/lib/webrtc/src/media/rtpReceiver.js +135 -20
  86. package/lib/webrtc/src/media/rtpReceiver.js.map +1 -1
  87. package/lib/webrtc/src/media/rtpSender.d.ts +32 -7
  88. package/lib/webrtc/src/media/rtpSender.js +177 -28
  89. package/lib/webrtc/src/media/rtpSender.js.map +1 -1
  90. package/lib/webrtc/src/media/rtpTransceiver.d.ts +13 -4
  91. package/lib/webrtc/src/media/rtpTransceiver.js +26 -5
  92. package/lib/webrtc/src/media/rtpTransceiver.js.map +1 -1
  93. package/lib/webrtc/src/media/stats.d.ts +15 -1
  94. package/lib/webrtc/src/media/stats.js +55 -1
  95. package/lib/webrtc/src/media/stats.js.map +1 -1
  96. package/lib/webrtc/src/media/track.d.ts +2 -1
  97. package/lib/webrtc/src/media/track.js +7 -1
  98. package/lib/webrtc/src/media/track.js.map +1 -1
  99. package/lib/webrtc/src/nonstandard/dummyMedia.d.ts +19 -0
  100. package/lib/webrtc/src/nonstandard/dummyMedia.js +157 -0
  101. package/lib/webrtc/src/nonstandard/dummyMedia.js.map +1 -0
  102. package/lib/webrtc/src/nonstandard/navigator.d.ts +18 -8
  103. package/lib/webrtc/src/nonstandard/navigator.js +96 -32
  104. package/lib/webrtc/src/nonstandard/navigator.js.map +1 -1
  105. package/lib/webrtc/src/nonstandard/userMedia/packetizer.d.ts +16 -0
  106. package/lib/webrtc/src/nonstandard/userMedia/packetizer.js +360 -0
  107. package/lib/webrtc/src/nonstandard/userMedia/packetizer.js.map +1 -0
  108. package/lib/webrtc/src/nonstandard/userMedia.d.ts +71 -31
  109. package/lib/webrtc/src/nonstandard/userMedia.js +454 -108
  110. package/lib/webrtc/src/nonstandard/userMedia.js.map +1 -1
  111. package/lib/webrtc/src/peerConnection.d.ts +183 -50
  112. package/lib/webrtc/src/peerConnection.js +458 -109
  113. package/lib/webrtc/src/peerConnection.js.map +1 -1
  114. package/lib/webrtc/src/sctpManager.d.ts +1 -1
  115. package/lib/webrtc/src/sctpManager.js +27 -7
  116. package/lib/webrtc/src/sctpManager.js.map +1 -1
  117. package/lib/webrtc/src/sdp.d.ts +10 -7
  118. package/lib/webrtc/src/sdp.js +39 -9
  119. package/lib/webrtc/src/sdp.js.map +1 -1
  120. package/lib/webrtc/src/sdpManager.d.ts +4 -3
  121. package/lib/webrtc/src/sdpManager.js +50 -27
  122. package/lib/webrtc/src/sdpManager.js.map +1 -1
  123. package/lib/webrtc/src/secureTransportManager.d.ts +13 -4
  124. package/lib/webrtc/src/secureTransportManager.js +100 -22
  125. package/lib/webrtc/src/secureTransportManager.js.map +1 -1
  126. package/lib/webrtc/src/transceiverManager.d.ts +5 -4
  127. package/lib/webrtc/src/transceiverManager.js +61 -62
  128. package/lib/webrtc/src/transceiverManager.js.map +1 -1
  129. package/lib/webrtc/src/transport/dtls.d.ts +15 -4
  130. package/lib/webrtc/src/transport/dtls.js +91 -23
  131. package/lib/webrtc/src/transport/dtls.js.map +1 -1
  132. package/lib/webrtc/src/transport/ice.d.ts +21 -3
  133. package/lib/webrtc/src/transport/ice.js +159 -30
  134. package/lib/webrtc/src/transport/ice.js.map +1 -1
  135. package/lib/webrtc/src/transport/sctp.d.ts +1 -0
  136. package/lib/webrtc/src/transport/sctp.js +9 -6
  137. package/lib/webrtc/src/transport/sctp.js.map +1 -1
  138. package/lib/webrtc/src/utils.d.ts +12 -4
  139. package/lib/webrtc/src/utils.js +118 -16
  140. package/lib/webrtc/src/utils.js.map +1 -1
  141. package/package.json +7 -4
  142. package/lib/ice/src/stun/attributes.js.map +0 -1
  143. package/lib/rtp/src/extra/container/mp4/mp4box.d.ts +0 -5
  144. package/lib/rtp/src/extra/container/mp4/mp4box.js +0 -32
  145. package/lib/rtp/src/extra/container/mp4/mp4box.js.map +0 -1
@@ -1,3 +1,13 @@
1
+ // ../rtp/src/extra/container/mp4/container.ts
2
+ import {
3
+ EncodedAudioPacketSource,
4
+ EncodedPacket,
5
+ EncodedVideoPacketSource,
6
+ Mp4OutputFormat,
7
+ NullTarget,
8
+ Output
9
+ } from "mediabunny";
10
+
1
11
  // ../common/src/binary.ts
2
12
  import { randomBytes } from "crypto";
3
13
  import { jspack } from "@shinyoshiaki/jspack";
@@ -464,7 +474,7 @@ var WeriftError = class extends Error {
464
474
  var debug = Debug.debug;
465
475
 
466
476
  // ../common/src/event.ts
467
- var Event = class {
477
+ var Event2 = class {
468
478
  event = {
469
479
  stack: [],
470
480
  promiseStack: [],
@@ -570,7 +580,7 @@ var Event = class {
570
580
  error
571
581
  );
572
582
  };
573
- watch = (cb, timeLimit) => new Promise((resolve, reject) => {
583
+ watch = (cb, timeLimit) => new Promise((resolve2, reject) => {
574
584
  const timeout = timeLimit && setTimeout(() => {
575
585
  reject("Event watch timeout");
576
586
  }, timeLimit);
@@ -579,22 +589,22 @@ var Event = class {
579
589
  if (done) {
580
590
  if (timeout) clearTimeout(timeout);
581
591
  unSubscribe();
582
- resolve(args);
592
+ resolve2(args);
583
593
  }
584
594
  });
585
595
  });
586
- asPromise = (timeLimit) => new Promise((resolve, reject) => {
596
+ asPromise = (timeLimit) => new Promise((resolve2, reject) => {
587
597
  const timeout = timeLimit && setTimeout(() => {
588
598
  reject("Event asPromise timeout");
589
599
  }, timeLimit);
590
600
  this.once(
591
601
  (...args) => {
592
602
  if (timeout) clearTimeout(timeout);
593
- resolve(args);
603
+ resolve2(args);
594
604
  },
595
605
  () => {
596
606
  if (timeout) clearTimeout(timeout);
597
- resolve([]);
607
+ resolve2([]);
598
608
  },
599
609
  (err5) => {
600
610
  if (timeout) clearTimeout(timeout);
@@ -630,7 +640,8 @@ import {
630
640
  createSocket as createSocket2
631
641
  } from "dgram";
632
642
  import * as net from "node:net";
633
- import { connect } from "node:net";
643
+ import * as tls from "node:tls";
644
+ import { connect as connect2 } from "node:net";
634
645
  var log = debug("werift-ice:packages/ice/src/transport.ts");
635
646
  var UdpTransport = class _UdpTransport {
636
647
  constructor(socketType, options = {}) {
@@ -717,11 +728,89 @@ var UdpTransport = class _UdpTransport {
717
728
  });
718
729
  };
719
730
  var TcpTransport = class _TcpTransport {
731
+ type = "tcp";
732
+ stream;
720
733
  constructor(addr) {
721
- this.addr = addr;
734
+ this.stream = new StreamTransport(
735
+ "tcp",
736
+ () => connect2({ port: addr[1], host: addr[0] })
737
+ );
738
+ }
739
+ static async init(addr) {
740
+ const transport = new _TcpTransport(addr);
741
+ await transport.init();
742
+ return transport;
743
+ }
744
+ async init() {
745
+ await this.stream.waitForConnect();
746
+ }
747
+ get address() {
748
+ return this.stream.address;
749
+ }
750
+ get closed() {
751
+ return this.stream.closed;
752
+ }
753
+ get onData() {
754
+ return this.stream.onData;
755
+ }
756
+ set onData(handler) {
757
+ this.stream.onData = handler;
758
+ }
759
+ send = async (data, addr) => {
760
+ await this.stream.send(data, addr);
761
+ };
762
+ close = async () => {
763
+ await this.stream.close();
764
+ };
765
+ };
766
+ var TlsTransport = class _TlsTransport {
767
+ type = "tls";
768
+ stream;
769
+ constructor(addr, options = {}) {
770
+ this.stream = new StreamTransport(
771
+ "tls",
772
+ () => tls.connect({
773
+ ...options,
774
+ host: addr[0],
775
+ port: addr[1]
776
+ })
777
+ );
778
+ }
779
+ static async init(addr, options = {}) {
780
+ const transport = new _TlsTransport(addr, options);
781
+ await transport.init();
782
+ return transport;
783
+ }
784
+ async init() {
785
+ await this.stream.waitForConnect();
786
+ }
787
+ get address() {
788
+ return this.stream.address;
789
+ }
790
+ get closed() {
791
+ return this.stream.closed;
792
+ }
793
+ get onData() {
794
+ return this.stream.onData;
795
+ }
796
+ set onData(handler) {
797
+ this.stream.onData = handler;
798
+ }
799
+ send = async (data, addr) => {
800
+ await this.stream.send(data, addr);
801
+ };
802
+ close = async () => {
803
+ await this.stream.close();
804
+ };
805
+ };
806
+ var StreamTransport = class {
807
+ constructor(type, createClient, connectEvent = type === "tls" ? "secureConnect" : "connect") {
808
+ this.createClient = createClient;
809
+ this.connectEvent = connectEvent;
810
+ this.type = type;
722
811
  this.connect();
723
812
  }
724
- type = "tcp";
813
+ type;
725
814
  connecting;
726
815
  client;
727
816
  onData = () => {
@@ -734,90 +823,88 @@ var TcpTransport = class _TcpTransport {
734
823
  if (this.client) {
735
824
  this.client.destroy();
736
825
  }
826
+ const client = this.createClient();
827
+ this.client = client;
737
828
  this.connecting = new Promise((r, f) => {
738
- try {
739
- this.client = connect({ port: this.addr[1], host: this.addr[0] }, r);
740
- } catch (error) {
829
+ const onConnect = () => {
830
+ client.off("error", onConnectError);
831
+ r();
832
+ };
833
+ const onConnectError = (error) => {
834
+ client.off(this.connectEvent, onConnect);
741
835
  f(error);
742
- }
836
+ };
837
+ client.once(this.connectEvent, onConnect);
838
+ client.once("error", onConnectError);
743
839
  });
744
- this.client.on("data", (data) => {
840
+ client.on("data", (data) => {
745
841
  const addr = [
746
842
  this.client.remoteAddress,
747
843
  this.client.remotePort
748
844
  ];
749
845
  this.onData(data, addr);
750
846
  });
751
- this.client.on("end", () => {
847
+ client.on("end", () => {
752
848
  this.connect();
753
849
  });
754
- this.client.on("error", (error) => {
755
- console.log("error", error);
850
+ client.on("error", (error) => {
851
+ log(`${this.type} transport error`, error);
756
852
  });
757
853
  }
758
- async init() {
854
+ async waitForConnect() {
759
855
  await this.connecting;
760
856
  }
761
- static async init(addr) {
762
- const transport = new _TcpTransport(addr);
763
- await transport.init();
764
- return transport;
765
- }
766
857
  get address() {
767
858
  return {};
768
859
  }
769
860
  send = async (data, addr) => {
861
+ void addr;
770
862
  await this.connecting;
771
- this.client.write(data, (err5) => {
772
- if (err5) {
773
- console.log("err", err5);
774
- }
863
+ await new Promise((resolve2, reject) => {
864
+ this.client.write(data, (err5) => {
865
+ if (err5) {
866
+ reject(err5);
867
+ return;
868
+ }
869
+ resolve2();
870
+ });
775
871
  });
776
872
  };
777
873
  close = async () => {
778
874
  this.closed = true;
779
- this.client.destroy();
875
+ this.client?.destroy();
780
876
  };
781
877
  };
782
878
 
783
- // ../rtp/src/extra/container/mp4/mp4box.ts
784
- import {
785
- BoxParser,
786
- ISOFile,
787
- Log,
788
- createFile,
789
- DataStream
790
- } from "mp4box";
791
- import {
792
- BoxParser as BoxParser2
793
- } from "mp4box";
794
- BoxParser2.dOpsBox.prototype.write = function(stream) {
795
- this.size = 11;
796
- this.writeHeader(stream);
797
- stream.writeUint8(0);
798
- stream.writeUint8(this.OutputChannelCount);
799
- stream.writeUint16(this.PreSkip);
800
- stream.writeUint32(this.InputSampleRate);
801
- stream.writeUint16(0);
802
- stream.writeUint8(0);
803
- };
804
-
805
879
  // ../rtp/src/extra/container/mp4/container.ts
806
880
  var Mp4Container = class {
807
881
  constructor(props) {
808
882
  this.props = props;
809
- this.#mp4 = new ISOFile();
810
- this.#mp4.init();
811
883
  }
812
- #mp4;
813
884
  #audioFrame;
885
+ #audioMeta;
886
+ #audioSource;
887
+ #currentFragment;
888
+ #ftyp;
889
+ #initializationEmitted = false;
890
+ #lastAudioDuration = 0;
891
+ #lastMoof;
892
+ #lastMoofTimestamp = 0;
893
+ #lastVideoDuration = 0;
894
+ #moov;
895
+ #onDataOperation = Promise.resolve();
896
+ #operation = Promise.resolve();
897
+ #output;
898
+ #startPromise;
899
+ #stopPromise;
900
+ #stopped = false;
814
901
  #videoFrame;
815
- // 1 frame buffer
902
+ #videoMeta;
903
+ #videoSource;
816
904
  audioTrack;
905
+ frameBuffer = [];
906
+ onData = new Event2();
817
907
  videoTrack;
818
- #audioSegment = 0;
819
- #videoSegment = 0;
820
- onData = new Event();
821
908
  get tracksReady() {
822
909
  let ready = true;
823
910
  if (this.props.track.audio && !this.audioTrack) {
@@ -836,145 +923,259 @@ var Mp4Container = class {
836
923
  }
837
924
  }
838
925
  #init(frame) {
839
- let codec = frame.codec.substring(0, 4);
840
- if (codec == "opus") {
841
- codec = "Opus";
842
- }
843
- const options = {
844
- type: codec,
845
- timescale: 1e6
846
- };
847
- if (isVideoConfig(frame)) {
848
- options.width = frame.codedWidth;
849
- options.height = frame.codedHeight;
850
- } else {
851
- options.channel_count = frame.numberOfChannels;
852
- options.samplerate = frame.sampleRate;
853
- options.hdlr = "soun";
854
- }
855
- if (!frame.description) throw new Error("missing frame description");
856
- const desc = frame.description;
857
- if (codec === "avc1") {
858
- options.avcDecoderConfigRecord = desc;
859
- } else if (codec === "hev1") {
860
- options.hevcDecoderConfigRecord = desc;
861
- } else if (codec === "Opus") {
862
- const dops = new BoxParser.dOpsBox();
863
- dops.parse(new DataStream(desc, 8, DataStream.LITTLE_ENDIAN));
864
- options.description = dops;
865
- } else {
866
- throw new Error(`unsupported codec: ${codec}`);
867
- }
868
- const track = this.#mp4.addTrack(options);
869
- if (track == void 0) {
870
- throw new Error("failed to initialize MP4 track");
871
- }
872
926
  if (frame.track === "audio") {
873
- this.audioTrack = track;
927
+ if (isVideoConfig(frame)) {
928
+ throw new Error("audio track requires an audio decoder config");
929
+ }
930
+ if (frame.codec !== "opus") {
931
+ throw new Error(`unsupported codec: ${frame.codec}`);
932
+ }
933
+ this.#audioMeta = {
934
+ decoderConfig: {
935
+ codec: "opus",
936
+ description: frame.description != void 0 ? new Uint8Array(frame.description) : void 0,
937
+ numberOfChannels: frame.numberOfChannels,
938
+ sampleRate: frame.sampleRate
939
+ }
940
+ };
941
+ this.audioTrack = 1;
874
942
  } else {
875
- this.videoTrack = track;
943
+ if (!isVideoConfig(frame)) {
944
+ throw new Error("video track requires a video decoder config");
945
+ }
946
+ if (!frame.codec.startsWith("avc1")) {
947
+ throw new Error(`unsupported codec: ${frame.codec}`);
948
+ }
949
+ if (frame.codedWidth == void 0 || frame.codedHeight == void 0) {
950
+ throw new Error("missing coded video dimensions");
951
+ }
952
+ this.#videoMeta = {
953
+ decoderConfig: {
954
+ codec: frame.codec,
955
+ codedHeight: frame.codedHeight,
956
+ codedWidth: frame.codedWidth,
957
+ description: frame.description != void 0 ? new Uint8Array(frame.description) : void 0,
958
+ displayAspectHeight: frame.displayAspectHeight,
959
+ displayAspectWidth: frame.displayAspectWidth
960
+ }
961
+ };
962
+ this.videoTrack = this.props.track.audio ? 2 : 1;
876
963
  }
877
964
  if (!this.tracksReady) {
878
965
  return;
879
966
  }
880
- const buffer2 = ISOFile.writeInitializationSegment(
881
- this.#mp4.ftyp,
882
- this.#mp4.moov,
883
- 0,
884
- 0
885
- );
886
- const data = new Uint8Array(buffer2);
887
- const res = {
888
- type: "init",
889
- timestamp: 0,
890
- duration: 0,
891
- data,
892
- kind: frame.track
893
- };
894
- this.onData.execute(res);
967
+ void this.#enqueueOperation(async () => {
968
+ await this.#ensureOutputStarted();
969
+ await this.#drainFrameBuffer();
970
+ });
895
971
  }
896
- frameBuffer = [];
897
972
  #enqueue(frame) {
898
973
  this.frameBuffer.push(frame);
899
974
  if (!this.tracksReady) {
900
975
  return;
901
976
  }
902
- for (const frame2 of this.frameBuffer) {
903
- this._enqueue(frame2);
977
+ void this.#enqueueOperation(async () => {
978
+ await this.#ensureOutputStarted();
979
+ await this.#drainFrameBuffer();
980
+ });
981
+ }
982
+ stop() {
983
+ if (this.#stopPromise) {
984
+ return this.#stopPromise;
904
985
  }
986
+ this.#stopped = true;
987
+ this.#stopPromise = this.#enqueueOperation(async () => {
988
+ if (!this.tracksReady) {
989
+ this.frameBuffer = [];
990
+ return;
991
+ }
992
+ await this.#ensureOutputStarted();
993
+ await this.#drainFrameBuffer();
994
+ await this.#flushPendingFrame("audio");
995
+ await this.#flushPendingFrame("video");
996
+ this.#audioSource?.close();
997
+ this.#videoSource?.close();
998
+ if (this.#output) {
999
+ await this.#output.finalize();
1000
+ }
1001
+ await this.#flushOnData();
1002
+ });
1003
+ return this.#stopPromise;
1004
+ }
1005
+ async #drainFrameBuffer() {
1006
+ const frames = this.frameBuffer;
905
1007
  this.frameBuffer = [];
1008
+ for (const frame of frames) {
1009
+ await this.#processFrame(frame);
1010
+ }
906
1011
  }
907
- _enqueue(frame) {
908
- const track = frame.track === "audio" ? this.audioTrack : this.videoTrack;
909
- if (!track) {
910
- throw new Error("track missing");
1012
+ async #ensureOutputStarted() {
1013
+ if (this.#startPromise) {
1014
+ await this.#startPromise;
1015
+ return;
911
1016
  }
912
- if (frame.track === "video") {
913
- if (frame.type == "key") {
914
- this.#videoSegment += 1;
915
- } else if (this.#videoSegment == 0) {
916
- throw new Error("must start with keyframe");
917
- }
918
- } else {
919
- this.#audioSegment += 1;
1017
+ if (!this.tracksReady) {
1018
+ return;
920
1019
  }
921
- if (frame.track === "video") {
922
- if (!this.#videoFrame) {
923
- this.#videoFrame = frame;
924
- return;
925
- }
1020
+ const output = new Output({
1021
+ format: new Mp4OutputFormat({
1022
+ fastStart: "fragmented",
1023
+ minimumFragmentDuration: 0,
1024
+ onFtyp: (data) => {
1025
+ this.#ftyp = copyBytes(data);
1026
+ },
1027
+ onMdat: (data) => {
1028
+ if (!this.#lastMoof) {
1029
+ throw new Error("moof missing before mdat");
1030
+ }
1031
+ const fragment = this.#currentFragment;
1032
+ const timestamp = fragment ? fragment.timestamp : Math.round(this.#lastMoofTimestamp * 1e6);
1033
+ const duration = fragment?.duration ?? 0;
1034
+ const kind = fragment?.kind ?? this.#defaultKind();
1035
+ const type = fragment?.type ?? "key";
1036
+ const segment = concatBytes(this.#lastMoof, data);
1037
+ this.#currentFragment = void 0;
1038
+ this.#lastMoof = void 0;
1039
+ void this.#emitData({
1040
+ type,
1041
+ timestamp,
1042
+ duration,
1043
+ data: segment,
1044
+ kind
1045
+ });
1046
+ },
1047
+ onMoof: (data, _position, timestamp) => {
1048
+ this.#lastMoof = copyBytes(data);
1049
+ this.#lastMoofTimestamp = timestamp;
1050
+ },
1051
+ onMoov: (data) => {
1052
+ this.#moov = copyBytes(data);
1053
+ this.#emitInitializationSegment();
1054
+ }
1055
+ }),
1056
+ target: new NullTarget()
1057
+ });
1058
+ if (this.#audioMeta) {
1059
+ this.#audioSource = new EncodedAudioPacketSource("opus");
1060
+ output.addAudioTrack(this.#audioSource);
1061
+ }
1062
+ if (this.#videoMeta) {
1063
+ this.#videoSource = new EncodedVideoPacketSource("avc");
1064
+ output.addVideoTrack(this.#videoSource);
1065
+ }
1066
+ this.#output = output;
1067
+ this.#startPromise = output.start();
1068
+ await this.#startPromise;
1069
+ }
1070
+ #emitInitializationSegment() {
1071
+ if (this.#initializationEmitted || !this.#ftyp || !this.#moov) {
1072
+ return;
1073
+ }
1074
+ this.#initializationEmitted = true;
1075
+ void this.#emitData({
1076
+ type: "init",
1077
+ timestamp: 0,
1078
+ duration: 0,
1079
+ data: concatBytes(this.#ftyp, this.#moov),
1080
+ kind: this.#defaultKind()
1081
+ });
1082
+ }
1083
+ #emitData(data) {
1084
+ const next = this.#onDataOperation.then(() => this.onData.execute(data));
1085
+ this.#onDataOperation = next;
1086
+ return next;
1087
+ }
1088
+ async #flushOnData() {
1089
+ await this.#onDataOperation;
1090
+ }
1091
+ #enqueueOperation(operation) {
1092
+ const next = this.#operation.then(operation);
1093
+ this.#operation = next;
1094
+ return next;
1095
+ }
1096
+ #defaultKind() {
1097
+ return this.props.track.video ? "video" : "audio";
1098
+ }
1099
+ async #flushPendingFrame(track) {
1100
+ const buffered = track === "audio" ? this.#audioFrame : this.#videoFrame;
1101
+ if (!buffered) {
1102
+ return;
1103
+ }
1104
+ const duration = track === "audio" ? this.#lastAudioDuration : this.#lastVideoDuration;
1105
+ await this.#writePacket(buffered, duration);
1106
+ if (track === "audio") {
1107
+ this.#audioFrame = void 0;
926
1108
  } else {
927
- if (!this.#audioFrame) {
1109
+ this.#videoFrame = void 0;
1110
+ }
1111
+ }
1112
+ async #processFrame(frame) {
1113
+ const buffered = frame.track === "audio" ? this.#audioFrame : this.#videoFrame;
1114
+ if (!buffered) {
1115
+ if (frame.track === "audio") {
928
1116
  this.#audioFrame = frame;
929
- return;
1117
+ } else {
1118
+ this.#videoFrame = frame;
930
1119
  }
1120
+ return;
931
1121
  }
932
- const bufferFrame = frame.track === "video" ? this.#videoFrame : this.#audioFrame;
933
- if (!bufferFrame) {
934
- throw new Error("bufferFrame missing");
935
- }
936
- const duration = frame.timestamp - bufferFrame.timestamp;
937
- const buffer2 = new ArrayBuffer(bufferFrame.byteLength);
938
- bufferFrame.copyTo(buffer2);
939
- this.#mp4.addSample(track, buffer2, {
940
- duration,
941
- dts: bufferFrame.timestamp,
942
- cts: bufferFrame.timestamp,
943
- is_sync: bufferFrame.type == "key"
944
- });
945
- const stream = new DataStream(void 0, 0, DataStream.BIG_ENDIAN);
946
- for (; ; ) {
947
- const moof = this.#mp4.moofs.shift();
948
- const mdat = this.#mp4.mdats.shift();
949
- if (!moof && !mdat) break;
950
- if (!moof) throw new Error("moof missing");
951
- if (!mdat) throw new Error("mdat missing");
952
- moof.write(stream);
953
- mdat.write(stream);
954
- }
955
- const data = new Uint8Array(stream.buffer);
956
- if (frame.track === "video") {
1122
+ const duration = Math.max(frame.timestamp - buffered.timestamp, 0);
1123
+ await this.#writePacket(buffered, duration);
1124
+ if (frame.track === "audio") {
1125
+ this.#audioFrame = frame;
1126
+ this.#lastAudioDuration = duration;
1127
+ } else {
957
1128
  this.#videoFrame = frame;
1129
+ this.#lastVideoDuration = duration;
1130
+ }
1131
+ }
1132
+ async #writePacket(frame, duration) {
1133
+ const data = new Uint8Array(frame.byteLength);
1134
+ frame.copyTo(data.buffer);
1135
+ const packet = new EncodedPacket(
1136
+ data,
1137
+ frame.type,
1138
+ frame.timestamp / 1e6,
1139
+ duration / 1e6
1140
+ );
1141
+ if (frame.track === "audio") {
1142
+ if (!this.#audioSource || !this.#audioMeta) {
1143
+ throw new Error("audio track missing");
1144
+ }
1145
+ await this.#audioSource.add(packet, this.#audioMeta);
958
1146
  } else {
959
- this.#audioFrame = frame;
1147
+ if (!this.#videoSource || !this.#videoMeta) {
1148
+ throw new Error("video track missing");
1149
+ }
1150
+ await this.#videoSource.add(packet, this.#videoMeta);
960
1151
  }
961
- const res = {
962
- type: bufferFrame.type,
963
- timestamp: bufferFrame.timestamp,
964
- kind: frame.track,
965
- duration,
966
- data
967
- };
968
- this.onData.execute(res);
1152
+ this.#rememberFragmentPacket(
1153
+ frame.track,
1154
+ frame.type,
1155
+ frame.timestamp,
1156
+ duration
1157
+ );
969
1158
  }
970
- /* TODO flush the last frame
971
- #flush(controller: TransformStreamDefaultController<Chunk>) {
972
- if (this.#frame) {
973
- // TODO guess the duration
974
- this.#enqueue(this.#frame, 0, controller)
975
- }
1159
+ #rememberFragmentPacket(kind, type, timestamp, duration) {
1160
+ const end = timestamp + duration;
1161
+ if (!this.#currentFragment) {
1162
+ this.#currentFragment = {
1163
+ kind,
1164
+ type,
1165
+ timestamp,
1166
+ duration
1167
+ };
1168
+ return;
1169
+ }
1170
+ if (kind === "video" && type === "key") {
1171
+ this.#currentFragment.type = "key";
1172
+ }
1173
+ const fragmentEnd = Math.max(
1174
+ this.#currentFragment.timestamp + this.#currentFragment.duration,
1175
+ end
1176
+ );
1177
+ this.#currentFragment.duration = fragmentEnd - this.#currentFragment.timestamp;
976
1178
  }
977
- */
978
1179
  };
979
1180
  function isDecoderConfig(frame) {
980
1181
  return frame.codec !== void 0;
@@ -983,6 +1184,19 @@ function isVideoConfig(frame) {
983
1184
  return frame.codedWidth !== void 0;
984
1185
  }
985
1186
  var mp4SupportedCodecs = ["avc1", "opus"];
1187
+ function concatBytes(...parts) {
1188
+ const size = parts.reduce((sum, part) => sum + part.byteLength, 0);
1189
+ const combined = new Uint8Array(size);
1190
+ let offset = 0;
1191
+ for (const part of parts) {
1192
+ combined.set(part, offset);
1193
+ offset += part.byteLength;
1194
+ }
1195
+ return combined;
1196
+ }
1197
+ function copyBytes(data) {
1198
+ return new Uint8Array(data);
1199
+ }
986
1200
 
987
1201
  // ../rtp/src/extra/container/mp4/exp-golomb.ts
988
1202
  var ExpGolomb = class {
@@ -1521,6 +1735,18 @@ function annexb2avcc(data) {
1521
1735
  );
1522
1736
  return avcc.getData();
1523
1737
  }
1738
+ function annexb2avcSample(data) {
1739
+ const annexbParser = new H264AnnexBParser(data);
1740
+ const nalUnits = [];
1741
+ let naluPayload = null;
1742
+ while ((naluPayload = annexbParser.readNextNaluPayload()) != null) {
1743
+ nalUnits.push(Buffer.from(new H264NaluAVC1(naluPayload).data));
1744
+ }
1745
+ if (nalUnits.length === 0) {
1746
+ throw new Error("annexb sample does not contain nal units");
1747
+ }
1748
+ return Buffer.concat(nalUnits);
1749
+ }
1524
1750
 
1525
1751
  // ../rtp/src/extra/container/ogg/parser.ts
1526
1752
  var OggParser = class {
@@ -5202,7 +5428,7 @@ var DepacketizeBase = class {
5202
5428
  frameBroken = false;
5203
5429
  keyframeReceived = false;
5204
5430
  count = 0;
5205
- onNeedKeyFrame = new Event();
5431
+ onNeedKeyFrame = new Event2();
5206
5432
  internalStats = {};
5207
5433
  toJSON() {
5208
5434
  return {
@@ -5562,17 +5788,17 @@ var JitterBufferBase = class {
5562
5788
  this.rtpBuffer[rtp.header.sequenceNumber] = rtp;
5563
5789
  }
5564
5790
  resolveBuffer(seqNumFrom) {
5565
- const resolve = [];
5791
+ const resolve2 = [];
5566
5792
  for (let index = seqNumFrom; ; index = uint16Add(index, 1)) {
5567
5793
  const rtp = this.rtpBuffer[index];
5568
5794
  if (rtp) {
5569
- resolve.push(rtp);
5795
+ resolve2.push(rtp);
5570
5796
  delete this.rtpBuffer[index];
5571
5797
  } else {
5572
5798
  break;
5573
5799
  }
5574
5800
  }
5575
- return resolve;
5801
+ return resolve2;
5576
5802
  }
5577
5803
  sortAndClearBuffer(rtpBuffer) {
5578
5804
  const buffer2 = [];
@@ -5871,93 +6097,124 @@ var MP4Base = class {
5871
6097
  video: !!this.tracks.find((t) => t.kind === "video")
5872
6098
  }
5873
6099
  });
5874
- this.container.onData.subscribe((data) => {
5875
- this.output(data);
6100
+ this.container.onData.subscribe(async (data) => {
6101
+ await this.output(data);
5876
6102
  });
5877
6103
  }
6104
+ audioStopped = false;
5878
6105
  internalStats = {};
5879
6106
  container;
5880
6107
  stopped = false;
5881
- onStopped = new Event();
6108
+ onStopped = new Event2();
6109
+ videoStopped = false;
5882
6110
  toJSON() {
5883
6111
  return {
5884
6112
  ...this.internalStats
5885
6113
  };
5886
6114
  }
5887
- processAudioInput = ({ frame }) => {
5888
- const track = this.tracks.find((t) => t.kind === "audio");
5889
- if (frame) {
5890
- if (!this.container.audioTrack) {
6115
+ processVideoInput = ({ eol, frame }) => {
6116
+ if (this.stopped) {
6117
+ return;
6118
+ }
6119
+ if (!frame) {
6120
+ if (eol) {
6121
+ this.videoStopped = true;
6122
+ if (!this.tracks.some((track2) => track2.kind === "audio") || this.audioStopped) {
6123
+ void this.stop();
6124
+ }
6125
+ }
6126
+ return;
6127
+ }
6128
+ const track = this.tracks.find((t) => t.kind === "video");
6129
+ this.videoStopped = false;
6130
+ if (!this.container.videoTrack) {
6131
+ if (frame.isKeyframe) {
6132
+ const avcc = annexb2avcc(frame.data);
6133
+ const sample = annexb2avcSample(frame.data);
6134
+ const [displayAspectWidth, displayAspectHeight] = computeRatio(
6135
+ track.width,
6136
+ track.height
6137
+ );
5891
6138
  this.container.write({
5892
- codec: track.codec,
5893
- description: buffer2ArrayBuffer(
5894
- OpusRtpPayload.createCodecPrivate()
5895
- ),
5896
- numberOfChannels: 2,
5897
- sampleRate: track.clockRate,
5898
- track: "audio"
6139
+ codec: avccToCodecString(avcc),
6140
+ codedWidth: track.width,
6141
+ codedHeight: track.height,
6142
+ description: toArrayBuffer(Buffer.from(avcc)),
6143
+ displayAspectWidth,
6144
+ displayAspectHeight,
6145
+ track: "video"
5899
6146
  });
5900
- } else {
5901
6147
  this.container.write({
5902
- byteLength: frame.data.length,
6148
+ byteLength: sample.length,
5903
6149
  duration: null,
5904
6150
  timestamp: frame.time * 1e3,
5905
6151
  type: "key",
5906
6152
  copyTo: (destination) => {
5907
- frame.data.copy(destination);
6153
+ new Uint8Array(destination).set(sample);
5908
6154
  },
5909
- track: "audio"
6155
+ track: "video"
5910
6156
  });
5911
6157
  }
6158
+ } else {
6159
+ const sample = annexb2avcSample(frame.data);
6160
+ this.container.write({
6161
+ byteLength: sample.length,
6162
+ duration: null,
6163
+ timestamp: frame.time * 1e3,
6164
+ type: frame.isKeyframe ? "key" : "delta",
6165
+ copyTo: (destination) => {
6166
+ new Uint8Array(destination).set(sample);
6167
+ },
6168
+ track: "video"
6169
+ });
5912
6170
  }
5913
6171
  };
5914
- processVideoInput = ({ frame }) => {
5915
- const track = this.tracks.find((t) => t.kind === "video");
5916
- if (frame) {
5917
- if (!this.container.videoTrack) {
5918
- if (frame.isKeyframe) {
5919
- const avcc = annexb2avcc(frame.data);
5920
- const [displayAspectWidth, displayAspectHeight] = computeRatio(
5921
- track.width,
5922
- track.height
5923
- );
5924
- this.container.write({
5925
- codec: track.codec,
5926
- codedWidth: track.width,
5927
- codedHeight: track.height,
5928
- description: avcc.buffer,
5929
- displayAspectWidth,
5930
- displayAspectHeight,
5931
- track: "video"
5932
- });
5933
- this.container.write({
5934
- byteLength: frame.data.length,
5935
- duration: null,
5936
- timestamp: frame.time * 1e3,
5937
- type: "key",
5938
- copyTo: (destination) => {
5939
- frame.data.copy(destination);
5940
- },
5941
- track: "video"
5942
- });
6172
+ processAudioInput = ({ eol, frame }) => {
6173
+ if (this.stopped) {
6174
+ return;
6175
+ }
6176
+ if (!frame) {
6177
+ if (eol) {
6178
+ this.audioStopped = true;
6179
+ if (!this.tracks.some((track2) => track2.kind === "video") || this.videoStopped) {
6180
+ void this.stop();
5943
6181
  }
5944
- } else {
5945
- this.container.write({
5946
- byteLength: frame.data.length,
5947
- duration: null,
5948
- timestamp: frame.time * 1e3,
5949
- type: frame.isKeyframe ? "key" : "delta",
5950
- copyTo: (destination) => {
5951
- frame.data.copy(destination);
5952
- },
5953
- track: "video"
5954
- });
5955
6182
  }
6183
+ return;
6184
+ }
6185
+ const track = this.tracks.find((t) => t.kind === "audio");
6186
+ this.audioStopped = false;
6187
+ if (!this.container.audioTrack) {
6188
+ this.container.write({
6189
+ codec: track.codec,
6190
+ description: toArrayBuffer(OpusRtpPayload.createCodecPrivate()),
6191
+ numberOfChannels: 2,
6192
+ sampleRate: track.clockRate,
6193
+ track: "audio"
6194
+ });
6195
+ } else {
6196
+ this.container.write({
6197
+ byteLength: frame.data.length,
6198
+ duration: null,
6199
+ timestamp: frame.time * 1e3,
6200
+ type: "key",
6201
+ copyTo: (destination) => {
6202
+ new Uint8Array(destination).set(frame.data);
6203
+ },
6204
+ track: "audio"
6205
+ });
5956
6206
  }
5957
6207
  };
5958
6208
  start() {
5959
6209
  }
5960
- stop() {
6210
+ async stop() {
6211
+ if (this.stopped) {
6212
+ return;
6213
+ }
6214
+ this.stopped = true;
6215
+ await this.container.stop();
6216
+ await this.output({ eol: true });
6217
+ await this.onStopped.execute();
5961
6218
  }
5962
6219
  };
5963
6220
  function computeRatio(a, b) {
@@ -5972,6 +6229,21 @@ function computeRatio(a, b) {
5972
6229
  const divisor = gcd(a, b);
5973
6230
  return [a / divisor, b / divisor];
5974
6231
  }
6232
+ function avccToCodecString(avcc) {
6233
+ if (avcc.byteLength < 4) {
6234
+ throw new Error("invalid avcc decoder configuration record");
6235
+ }
6236
+ return `avc1.${toHex(avcc[1])}${toHex(avcc[2])}${toHex(avcc[3])}`;
6237
+ }
6238
+ function toHex(value) {
6239
+ return value.toString(16).padStart(2, "0");
6240
+ }
6241
+ function toArrayBuffer(buffer2) {
6242
+ return buffer2.buffer.slice(
6243
+ buffer2.byteOffset,
6244
+ buffer2.byteOffset + buffer2.byteLength
6245
+ );
6246
+ }
5975
6247
 
5976
6248
  // ../rtp/src/extra/processor/mp4Callback.ts
5977
6249
  import { appendFile } from "fs/promises";
@@ -6001,14 +6273,16 @@ var MP4Callback = class extends MP4Base {
6001
6273
  this.processVideoInput(input);
6002
6274
  };
6003
6275
  destroy = () => {
6004
- this.cb = void 0;
6005
- this.queue.cancel();
6276
+ void this.stop().finally(() => {
6277
+ this.cb = void 0;
6278
+ this.queue.cancel();
6279
+ });
6006
6280
  };
6007
6281
  static saveToFileSystem = (path2) => {
6008
6282
  const queue = new PromiseQueue();
6009
6283
  return async (value) => {
6010
6284
  await queue.push(async () => {
6011
- if (value.data) {
6285
+ if ("data" in value) {
6012
6286
  await appendFile(path2, value.data);
6013
6287
  } else if (value.eol) {
6014
6288
  }
@@ -6171,8 +6445,8 @@ var NackHandlerBase = class {
6171
6445
  _lost = {};
6172
6446
  clearNackInterval;
6173
6447
  internalStats = {};
6174
- onNackSent = new Event();
6175
- onPacketLost = new Event();
6448
+ onNackSent = new Event2();
6449
+ onPacketLost = new Event2();
6176
6450
  mediaSourceSsrc;
6177
6451
  retryCount = 10;
6178
6452
  stopped = false;
@@ -6318,7 +6592,7 @@ var WebmBase = class {
6318
6592
  stopped = false;
6319
6593
  videoKeyframeReceived = false;
6320
6594
  internalStats = {};
6321
- onStopped = new Event();
6595
+ onStopped = new Event2();
6322
6596
  toJSON() {
6323
6597
  return {
6324
6598
  ...this.internalStats,
@@ -6711,7 +6985,7 @@ var NtpTimeCallback = class extends SimpleProcessorCallbackBase(NtpTimeBase) {
6711
6985
  var RtcpSourceCallback = class {
6712
6986
  cb;
6713
6987
  destructor;
6714
- onStopped = new Event();
6988
+ onStopped = new Event2();
6715
6989
  toJSON() {
6716
6990
  return {};
6717
6991
  }
@@ -6749,7 +7023,7 @@ var RtpSourceCallback = class {
6749
7023
  }
6750
7024
  cb;
6751
7025
  destructor;
6752
- onStopped = new Event();
7026
+ onStopped = new Event2();
6753
7027
  stats = {};
6754
7028
  buffer = [];
6755
7029
  bufferFulfilled = false;
@@ -6985,9 +7259,7 @@ var WebmStream = class extends WebmBase {
6985
7259
  };
6986
7260
 
6987
7261
  // src/nonstandard/navigator.ts
6988
- import { randomBytes as randomBytes5 } from "crypto";
6989
7262
  import { createSocket as createSocket3 } from "dgram";
6990
- import { jspack as jspack8 } from "@shinyoshiaki/jspack";
6991
7263
 
6992
7264
  // ../dtls/src/context/cipher.ts
6993
7265
  import { createSign, randomBytes as randomBytes3, webcrypto } from "crypto";
@@ -8679,12 +8951,12 @@ handlers2[20 /* finished_20 */] = ({ dtls }) => (message) => {
8679
8951
  // ../dtls/src/server.ts
8680
8952
  var log19 = debug("werift-dtls : packages/dtls/src/server.ts : log");
8681
8953
 
8682
- // ../ice/src/stun/const.ts
8954
+ // ../ice-server/src/stun/const.ts
8683
8955
  var COOKIE = 554869826;
8684
8956
  var IPV4_PROTOCOL = 1;
8685
8957
  var IPV6_PROTOCOL = 2;
8686
8958
 
8687
- // ../ice/src/stun/attributes.ts
8959
+ // ../ice-server/src/stun/attributes.ts
8688
8960
  import * as Int64 from "int64-buffer";
8689
8961
  import nodeIp from "ip";
8690
8962
  function packAddress(value) {
@@ -8697,26 +8969,31 @@ function packAddress(value) {
8697
8969
  return Buffer.concat([buffer2, nodeIp.toBuffer(address)]);
8698
8970
  }
8699
8971
  function unpackErrorCode(data) {
8700
- if (data.length < 4) throw new Error("STUN error code is less than 4 bytes");
8972
+ if (data.length < 4) {
8973
+ throw new Error("STUN error code is less than 4 bytes");
8974
+ }
8701
8975
  const codeHigh = data.readUInt8(2);
8702
8976
  const codeLow = data.readUInt8(3);
8703
8977
  const reason = data.slice(4).toString("utf8");
8704
8978
  return [codeHigh * 100 + codeLow, reason];
8705
8979
  }
8706
8980
  function unpackAddress(data) {
8707
- if (data.length < 4)
8981
+ if (data.length < 4) {
8708
8982
  throw new Error("STUN address length is less than 4 bytes");
8983
+ }
8709
8984
  const protocol = data.readUInt8(1);
8710
8985
  const port = data.readUInt16BE(2);
8711
8986
  const address = data.slice(4);
8712
8987
  switch (protocol) {
8713
8988
  case IPV4_PROTOCOL:
8714
- if (address.length != 4)
8715
- throw new Error(`STUN address has invalid length for IPv4`);
8989
+ if (address.length !== 4) {
8990
+ throw new Error("STUN address has invalid length for IPv4");
8991
+ }
8716
8992
  return [nodeIp.toString(address), port];
8717
8993
  case IPV6_PROTOCOL:
8718
- if (address.length != 16)
8994
+ if (address.length !== 16) {
8719
8995
  throw new Error("STUN address has invalid length for IPv6");
8996
+ }
8720
8997
  return [nodeIp.toString(address), port];
8721
8998
  default:
8722
8999
  throw new Error("STUN address has unknown protocol");
@@ -8744,8 +9021,25 @@ function packErrorCode(value) {
8744
9021
  buffer2.writeUInt16BE(0, 0);
8745
9022
  buffer2.writeUInt8(Math.floor(value[0] / 100), 2);
8746
9023
  buffer2.writeUInt8(value[0] % 100, 3);
8747
- const encode20 = Buffer.from(value[1], "utf8");
8748
- return Buffer.concat([buffer2, encode20]);
9024
+ const encoded = Buffer.from(value[1], "utf8");
9025
+ return Buffer.concat([buffer2, encoded]);
9026
+ }
9027
+ function packUnknownAttributes(value) {
9028
+ const buffer2 = Buffer.alloc(value.length * 2);
9029
+ value.forEach((attributeType, index) => {
9030
+ buffer2.writeUInt16BE(attributeType, index * 2);
9031
+ });
9032
+ return buffer2;
9033
+ }
9034
+ function unpackUnknownAttributes(data) {
9035
+ if (data.length % 2 !== 0) {
9036
+ throw new Error("UNKNOWN-ATTRIBUTES must have an even length");
9037
+ }
9038
+ const attributes = [];
9039
+ for (let offset = 0; offset < data.length; offset += 2) {
9040
+ attributes.push(data.readUInt16BE(offset));
9041
+ }
9042
+ return attributes;
8749
9043
  }
8750
9044
  function packXorAddress(value, transactionId) {
8751
9045
  return xorAddress(packAddress(value), transactionId);
@@ -8771,10 +9065,16 @@ var unpackUnsigned64 = (data) => {
8771
9065
  };
8772
9066
  var packString = (value) => Buffer.from(value, "utf8");
8773
9067
  var unpackString = (data) => data.toString("utf8");
9068
+ var packSoftware = (value) => {
9069
+ if ([...value].length >= 128) {
9070
+ throw new Error("SOFTWARE must be shorter than 128 characters");
9071
+ }
9072
+ return packString(value);
9073
+ };
8774
9074
  var packBytes = (value) => value;
8775
9075
  var unpackBytes = (data) => data;
8776
- var packNone = (value) => Buffer.from([]);
8777
- var unpackNone = (data) => null;
9076
+ var packNone = () => Buffer.alloc(0);
9077
+ var unpackNone = () => null;
8778
9078
  var ATTRIBUTES = [
8779
9079
  [1, "MAPPED-ADDRESS", packAddress, unpackAddress],
8780
9080
  [3, "CHANGE-REQUEST", packUnsigned, unpackUnsigned],
@@ -8783,6 +9083,12 @@ var ATTRIBUTES = [
8783
9083
  [6, "USERNAME", packString, unpackString],
8784
9084
  [8, "MESSAGE-INTEGRITY", packBytes, unpackBytes],
8785
9085
  [9, "ERROR-CODE", packErrorCode, unpackErrorCode],
9086
+ [
9087
+ 10,
9088
+ "UNKNOWN-ATTRIBUTES",
9089
+ packUnknownAttributes,
9090
+ unpackUnknownAttributes
9091
+ ],
8786
9092
  [12, "CHANNEL-NUMBER", packUnsignedShort, unpackUnsignedShort],
8787
9093
  [13, "LIFETIME", packUnsigned, unpackUnsigned],
8788
9094
  [18, "XOR-PEER-ADDRESS", packXorAddress, unpackXorAddress],
@@ -8790,11 +9096,20 @@ var ATTRIBUTES = [
8790
9096
  [20, "REALM", packString, unpackString],
8791
9097
  [21, "NONCE", packBytes, unpackBytes],
8792
9098
  [22, "XOR-RELAYED-ADDRESS", packXorAddress, unpackXorAddress],
9099
+ [23, "REQUESTED-ADDRESS-FAMILY", packUnsigned, unpackUnsigned],
9100
+ [24, "EVEN-PORT", packBytes, unpackBytes],
8793
9101
  [25, "REQUESTED-TRANSPORT", packUnsigned, unpackUnsigned],
9102
+ [28, "MESSAGE-INTEGRITY-SHA256", packBytes, unpackBytes],
9103
+ [29, "PASSWORD-ALGORITHM", packBytes, unpackBytes],
9104
+ [30, "USERHASH", packBytes, unpackBytes],
8794
9105
  [32, "XOR-MAPPED-ADDRESS", packXorAddress, unpackXorAddress],
9106
+ [34, "RESERVATION-TOKEN", packBytes, unpackBytes],
8795
9107
  [36, "PRIORITY", packUnsigned, unpackUnsigned],
8796
9108
  [37, "USE-CANDIDATE", packNone, unpackNone],
8797
- [32802, "SOFTWARE", packString, unpackString],
9109
+ [32770, "PASSWORD-ALGORITHMS", packBytes, unpackBytes],
9110
+ [32771, "ALTERNATE-DOMAIN", packString, unpackString],
9111
+ [32802, "SOFTWARE", packSoftware, unpackString],
9112
+ [32803, "ALTERNATE-SERVER", packAddress, unpackAddress],
8798
9113
  [32808, "FINGERPRINT", packUnsigned, unpackUnsigned],
8799
9114
  [32809, "ICE-CONTROLLED", packUnsigned64, unpackUnsigned64],
8800
9115
  [32810, "ICE-CONTROLLING", packUnsigned64, unpackUnsigned64],
@@ -8819,12 +9134,14 @@ var ATTRIBUTES_BY_NAME = ATTRIBUTES.reduce(
8819
9134
  // ../ice/src/stun/transaction.ts
8820
9135
  var log20 = debug("werift-ice:packages/ice/src/stun/transaction.ts");
8821
9136
 
9137
+ // ../ice/src/stun/tcpProtocol.ts
9138
+ var log21 = debug("werift-ice:packages/ice/src/stun/tcpProtocol.ts");
9139
+
8822
9140
  // ../ice/src/stun/protocol.ts
8823
- var log21 = debug("werift-ice : packages/ice/src/stun/protocol.ts");
9141
+ var log22 = debug("werift-ice : packages/ice/src/stun/protocol.ts");
8824
9142
 
8825
9143
  // ../ice/src/turn/protocol.ts
8826
- import { jspack as jspack3 } from "@shinyoshiaki/jspack";
8827
- var log22 = debug("werift-ice:packages/ice/src/turn/protocol.ts");
9144
+ var log23 = debug("werift-ice:packages/ice/src/turn/protocol.ts");
8828
9145
 
8829
9146
  // ../ice/src/ice.ts
8830
9147
  import * as Int642 from "int64-buffer";
@@ -8834,28 +9151,58 @@ import isEqual from "fast-deep-equal";
8834
9151
  import mdns from "multicast-dns";
8835
9152
 
8836
9153
  // ../ice/src/iceBase.ts
8837
- var log23 = debug("werift-ice : packages/ice/src/ice.ts : log");
9154
+ var log24 = debug("werift-ice : packages/ice/src/ice.ts : log");
8838
9155
 
8839
9156
  // ../ice/src/utils.ts
8840
9157
  import nodeIp2 from "ip";
8841
9158
  var logger = debug("werift-ice : packages/ice/src/utils.ts");
8842
9159
 
8843
9160
  // ../ice/src/ice.ts
8844
- var log24 = debug("werift-ice : packages/ice/src/ice.ts : log");
9161
+ var log25 = debug("werift-ice : packages/ice/src/ice.ts : log");
8845
9162
 
8846
9163
  // src/helper.ts
8847
9164
  import { EventEmitter } from "node:events";
9165
+ function divide(from, split) {
9166
+ const arr = from.split(split);
9167
+ return [arr[0], arr.slice(1).join(split)];
9168
+ }
8848
9169
  var EventTarget2 = class extends EventEmitter {
8849
- addEventListener = (type, listener) => {
9170
+ emit(type, ...args) {
9171
+ if (typeof type !== "string") {
9172
+ return super.emit(type, ...args);
9173
+ }
9174
+ if (args.length === 0) {
9175
+ return super.emit(type, new Event(type));
9176
+ }
9177
+ const [event, ...rest] = args;
9178
+ if (event && typeof event === "object" && !("type" in event)) {
9179
+ try {
9180
+ Object.defineProperty(event, "type", {
9181
+ configurable: true,
9182
+ enumerable: true,
9183
+ value: type
9184
+ });
9185
+ } catch {
9186
+ return super.emit(type, { type, ...event }, ...rest);
9187
+ }
9188
+ }
9189
+ return super.emit(type, event, ...rest);
9190
+ }
9191
+ addEventListener = (type, listener, options) => {
9192
+ if (typeof options === "object" && options?.once) {
9193
+ this.once(type, listener);
9194
+ return;
9195
+ }
8850
9196
  this.addListener(type, listener);
8851
9197
  };
8852
9198
  removeEventListener = (type, listener) => {
8853
9199
  this.removeListener(type, listener);
8854
9200
  };
9201
+ dispatchEvent = (event) => this.emit(event.type, event);
8855
9202
  };
8856
9203
 
8857
9204
  // src/dataChannel.ts
8858
- var log25 = debug("werift:packages/webrtc/src/dataChannel.ts");
9205
+ var log26 = debug("werift:packages/webrtc/src/dataChannel.ts");
8859
9206
 
8860
9207
  // src/media/extension/rtcpFeedback.ts
8861
9208
  var useNACK = () => ({ type: "nack" });
@@ -8958,6 +9305,15 @@ var DTLS_SETUP_ROLE = Object.keys(
8958
9305
  acc[key] = cur;
8959
9306
  return acc;
8960
9307
  }, {});
9308
+ var FMTP_INT_PARAMETERS = [
9309
+ "apt",
9310
+ "max-fr",
9311
+ "max-fs",
9312
+ "maxplaybackrate",
9313
+ "minptime",
9314
+ "stereo",
9315
+ "useinbandfec"
9316
+ ];
8961
9317
  var SRTP_PROFILE = {
8962
9318
  SRTP_AES128_CM_HMAC_SHA1_80: ProtectionProfileAes128CmHmacSha1_80,
8963
9319
  SRTP_AEAD_AES_128_GCM: ProtectionProfileAeadAes128Gcm
@@ -8980,9 +9336,9 @@ var MediaStreamTrack = class extends EventTarget2 {
8980
9336
  codec;
8981
9337
  /**todo impl */
8982
9338
  enabled = true;
8983
- onReceiveRtp = new Event();
8984
- onReceiveRtcp = new Event();
8985
- onSourceChanged = new Event();
9339
+ onReceiveRtp = new Event2();
9340
+ onReceiveRtcp = new Event2();
9341
+ onSourceChanged = new Event2();
8986
9342
  stopped = false;
8987
9343
  muted = true;
8988
9344
  constructor(props) {
@@ -9015,7 +9371,7 @@ var MediaStreamTrack = class extends EventTarget2 {
9015
9371
  var MediaStream = class {
9016
9372
  id;
9017
9373
  tracks = [];
9018
- constructor(props) {
9374
+ constructor(props = {}) {
9019
9375
  if (Array.isArray(props)) {
9020
9376
  this.tracks = props;
9021
9377
  } else {
@@ -9027,6 +9383,12 @@ var MediaStream = class {
9027
9383
  track.streamId = this.id;
9028
9384
  this.tracks.push(track);
9029
9385
  }
9386
+ removeTrack(track) {
9387
+ this.tracks = this.tracks.filter((currentTrack) => currentTrack !== track);
9388
+ if (track.streamId === this.id) {
9389
+ track.streamId = void 0;
9390
+ }
9391
+ }
9030
9392
  getTracks() {
9031
9393
  return this.tracks;
9032
9394
  }
@@ -9039,33 +9401,33 @@ var MediaStream = class {
9039
9401
  };
9040
9402
 
9041
9403
  // src/utils.ts
9042
- var log26 = debug("werift:packages/webrtc/src/utils.ts");
9404
+ var log27 = debug("werift:packages/webrtc/src/utils.ts");
9043
9405
  var milliTime = Date.now;
9044
9406
  var startupTimestampInMicroseconds = BigInt(Date.now()) * 1000n - process.hrtime.bigint() / 1000n;
9045
9407
  var createSelfSignedCertificate = CipherContext.createSelfSignedCertificateWithKey;
9046
9408
 
9047
9409
  // src/media/receiver/nack.ts
9048
- var log27 = debug("werift:packages/webrtc/src/media/receiver/nack.ts");
9410
+ var log28 = debug("werift:packages/webrtc/src/media/receiver/nack.ts");
9049
9411
  var LOST_SIZE2 = 30 * 5;
9050
9412
 
9051
9413
  // src/media/receiver/receiverTwcc.ts
9052
- var log28 = debug("werift:packages/webrtc/media/receiver/receiverTwcc");
9414
+ var log29 = debug("werift:packages/webrtc/media/receiver/receiverTwcc");
9053
9415
 
9054
9416
  // src/sdp.ts
9055
9417
  import * as Int643 from "int64-buffer";
9056
9418
 
9057
9419
  // src/transport/dtls.ts
9058
9420
  import { Certificate as Certificate3, PrivateKey as PrivateKey2 } from "@fidm/x509";
9059
- var log29 = debug("werift:packages/webrtc/src/transport/dtls.ts");
9421
+ var log30 = debug("werift:packages/webrtc/src/transport/dtls.ts");
9060
9422
 
9061
9423
  // src/transport/ice.ts
9062
- var log30 = debug("werift:packages/webrtc/src/transport/ice.ts");
9424
+ var log31 = debug("werift:packages/webrtc/src/transport/ice.ts");
9063
9425
 
9064
9426
  // src/transport/sctp.ts
9065
- import { jspack as jspack6 } from "@shinyoshiaki/jspack";
9427
+ import { jspack as jspack5 } from "@shinyoshiaki/jspack";
9066
9428
 
9067
9429
  // ../sctp/src/sctp.ts
9068
- import { jspack as jspack5 } from "@shinyoshiaki/jspack";
9430
+ import { jspack as jspack4 } from "@shinyoshiaki/jspack";
9069
9431
 
9070
9432
  // ../sctp/src/chunk.ts
9071
9433
  var Chunk = class _Chunk {
@@ -9455,33 +9817,54 @@ function decodeParams(body) {
9455
9817
  }
9456
9818
 
9457
9819
  // ../sctp/src/param.ts
9458
- import { jspack as jspack4 } from "@shinyoshiaki/jspack";
9820
+ import { jspack as jspack3 } from "@shinyoshiaki/jspack";
9459
9821
 
9460
9822
  // ../sctp/src/sctp.ts
9461
- var log31 = debug("werift/sctp/sctp");
9823
+ var log32 = debug("werift/sctp/sctp");
9462
9824
  var SCTP_RTO_ALPHA = 1 / 8;
9463
9825
  var SCTP_RTO_BETA = 1 / 4;
9464
9826
  var SCTP_TSN_MODULO = 2 ** 32;
9465
9827
 
9466
9828
  // src/transport/sctp.ts
9467
- var log32 = debug("werift:packages/webrtc/src/transport/sctp.ts");
9829
+ var log33 = debug("werift:packages/webrtc/src/transport/sctp.ts");
9468
9830
  var DEFAULT_MAX_MESSAGE_SIZE = 65536;
9469
9831
 
9832
+ // src/sdp.ts
9833
+ function codecParametersFromString(str) {
9834
+ const parameters = {};
9835
+ str.split(";").forEach((param) => {
9836
+ if (param.includes("=")) {
9837
+ const [k, v] = divide(param, "=");
9838
+ if (FMTP_INT_PARAMETERS.includes(k)) {
9839
+ parameters[k] = Number(v);
9840
+ } else {
9841
+ parameters[k] = v;
9842
+ }
9843
+ } else if (param.includes(":")) {
9844
+ const [k, v] = param.split(":");
9845
+ parameters[k] = Number.isNaN(Number(v)) ? v : Number(v);
9846
+ } else {
9847
+ parameters[param] = void 0;
9848
+ }
9849
+ });
9850
+ return parameters;
9851
+ }
9852
+
9470
9853
  // src/media/rtpReceiver.ts
9471
- var log33 = debug("werift:packages/webrtc/src/media/rtpReceiver.ts");
9854
+ var log34 = debug("werift:packages/webrtc/src/media/rtpReceiver.ts");
9472
9855
 
9473
9856
  // src/media/router.ts
9474
- var log34 = debug("werift:packages/webrtc/src/media/router.ts");
9857
+ var log35 = debug("werift:packages/webrtc/src/media/router.ts");
9475
9858
 
9476
9859
  // src/media/rtpSender.ts
9477
- import { jspack as jspack7 } from "@shinyoshiaki/jspack";
9478
- var log35 = debug("werift:packages/webrtc/src/media/rtpSender.ts");
9860
+ import { jspack as jspack6 } from "@shinyoshiaki/jspack";
9861
+ var log36 = debug("werift:packages/webrtc/src/media/rtpSender.ts");
9479
9862
 
9480
9863
  // src/sctpManager.ts
9481
- var log36 = debug("werift:packages/webrtc/src/transport/sctpManager.ts");
9864
+ var log37 = debug("werift:packages/webrtc/src/transport/sctpManager.ts");
9482
9865
 
9483
9866
  // src/secureTransportManager.ts
9484
- var log37 = debug(
9867
+ var log38 = debug(
9485
9868
  "werift:packages/webrtc/src/transport/secureTransportManager.ts"
9486
9869
  );
9487
9870
  var srtpProfiles = [
@@ -9491,7 +9874,7 @@ var srtpProfiles = [
9491
9874
  ];
9492
9875
 
9493
9876
  // src/peerConnection.ts
9494
- var log38 = debug("werift:packages/webrtc/src/peerConnection.ts");
9877
+ var log39 = debug("werift:packages/webrtc/src/peerConnection.ts");
9495
9878
  function generateDefaultPeerConfig() {
9496
9879
  return {
9497
9880
  codecs: {
@@ -9503,18 +9886,25 @@ function generateDefaultPeerConfig() {
9503
9886
  video: []
9504
9887
  },
9505
9888
  iceTransportPolicy: "all",
9889
+ iceLite: false,
9506
9890
  iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
9507
9891
  icePortRange: void 0,
9508
9892
  iceInterfaceAddresses: void 0,
9509
9893
  iceAdditionalHostAddresses: void 0,
9510
9894
  iceUseIpv4: true,
9511
9895
  iceUseIpv6: true,
9896
+ iceUseTcp: false,
9897
+ turnTransport: void 0,
9898
+ turnTlsOptions: void 0,
9512
9899
  iceFilterStunResponse: void 0,
9513
9900
  iceFilterCandidatePair: void 0,
9514
9901
  icePasswordPrefix: void 0,
9515
9902
  iceUseLinkLocalAddress: void 0,
9516
9903
  dtls: {},
9517
9904
  bundlePolicy: "max-compat",
9905
+ rtcpMuxPolicy: "require",
9906
+ iceCandidatePoolSize: 0,
9907
+ certificates: [],
9518
9908
  debug: {},
9519
9909
  midSuffix: false,
9520
9910
  forceTurnTCP: false,
@@ -9524,7 +9914,129 @@ function generateDefaultPeerConfig() {
9524
9914
  var defaultPeerConfig = generateDefaultPeerConfig();
9525
9915
 
9526
9916
  // src/transceiverManager.ts
9527
- var log39 = debug("werift:packages/webrtc/src/media/rtpTransceiverManager.ts");
9917
+ var log40 = debug("werift:packages/webrtc/src/media/rtpTransceiverManager.ts");
9918
+
9919
+ // src/nonstandard/dummyMedia.ts
9920
+ var AUDIO_PACKET_MS = 20;
9921
+ var AUDIO_TIMESTAMP_STEP = 960;
9922
+ var AUDIO_PAYLOAD = Buffer.from([248, 255, 254]);
9923
+ var DEFAULT_VIDEO_FPS = 30;
9924
+ var VIDEO_TIMESTAMP_STEP = 3e3;
9925
+ var DUMMY_SSRC_BASE = 439041024;
9926
+ var DUMMY_SEQUENCE_BASE = 16384;
9927
+ var DUMMY_SEQUENCE_STEP = 256;
9928
+ var nextDummySourceId = 0;
9929
+ var VP8_KEYFRAME = Buffer.from([
9930
+ 16,
9931
+ 16,
9932
+ 0,
9933
+ 0,
9934
+ 157,
9935
+ 1,
9936
+ 42,
9937
+ 2,
9938
+ 0,
9939
+ 2,
9940
+ 0
9941
+ ]);
9942
+ var VP8_DELTA_FRAME = Buffer.from([16, 17, 0, 0]);
9943
+ function createDummyAudioTrack(options = {}) {
9944
+ const track = new MediaStreamTrack({ kind: "audio" });
9945
+ const source = new ScheduledRtpSource({
9946
+ track,
9947
+ intervalMs: options.packetIntervalMs ?? AUDIO_PACKET_MS,
9948
+ buildPacket: ({ sequenceNumber, timestamp, ssrc }) => new RtpPacket(
9949
+ new RtpHeader({
9950
+ version: 2,
9951
+ payloadType: 111,
9952
+ sequenceNumber,
9953
+ timestamp,
9954
+ ssrc,
9955
+ marker: true
9956
+ }),
9957
+ Buffer.from(AUDIO_PAYLOAD)
9958
+ ),
9959
+ nextTimestamp: (timestamp) => timestamp + AUDIO_TIMESTAMP_STEP
9960
+ });
9961
+ source.start();
9962
+ return { track, source };
9963
+ }
9964
+ function createDummyVideoTrack(options = {}) {
9965
+ const fps = options.fps ?? DEFAULT_VIDEO_FPS;
9966
+ const keyframeIntervalFrames = options.keyframeIntervalFrames ?? fps;
9967
+ const intervalMs = 1e3 / fps;
9968
+ const track = new MediaStreamTrack({ kind: "video" });
9969
+ const source = new ScheduledRtpSource({
9970
+ track,
9971
+ intervalMs,
9972
+ buildPacket: ({ sequenceNumber, timestamp, ssrc, iteration }) => {
9973
+ const isKeyframe = iteration % keyframeIntervalFrames === 0;
9974
+ return new RtpPacket(
9975
+ new RtpHeader({
9976
+ version: 2,
9977
+ payloadType: 96,
9978
+ sequenceNumber,
9979
+ timestamp,
9980
+ ssrc,
9981
+ marker: true
9982
+ }),
9983
+ Buffer.from(isKeyframe ? VP8_KEYFRAME : VP8_DELTA_FRAME)
9984
+ );
9985
+ },
9986
+ nextTimestamp: (timestamp) => timestamp + VIDEO_TIMESTAMP_STEP
9987
+ });
9988
+ source.start();
9989
+ return { track, source };
9990
+ }
9991
+ var ScheduledRtpSource = class {
9992
+ constructor(props) {
9993
+ this.props = props;
9994
+ }
9995
+ sourceId = nextDummySourceId++;
9996
+ ssrc = DUMMY_SSRC_BASE + this.sourceId >>> 0;
9997
+ sequenceNumber = DUMMY_SEQUENCE_BASE + this.sourceId * DUMMY_SEQUENCE_STEP & 65535;
9998
+ timestamp = 0;
9999
+ iteration = 0;
10000
+ nextTickAt;
10001
+ timer;
10002
+ stopped = false;
10003
+ start() {
10004
+ this.nextTickAt = performance.now();
10005
+ this.schedule();
10006
+ }
10007
+ stop() {
10008
+ this.stopped = true;
10009
+ if (this.timer) {
10010
+ clearTimeout(this.timer);
10011
+ this.timer = void 0;
10012
+ }
10013
+ }
10014
+ schedule() {
10015
+ if (this.stopped || this.props.track.stopped) {
10016
+ this.stop();
10017
+ return;
10018
+ }
10019
+ this.nextTickAt = (this.nextTickAt ?? performance.now()) + this.props.intervalMs;
10020
+ const delay = Math.max(0, this.nextTickAt - performance.now());
10021
+ this.timer = setTimeout(() => {
10022
+ if (this.stopped || this.props.track.stopped) {
10023
+ this.stop();
10024
+ return;
10025
+ }
10026
+ const packet = this.props.buildPacket({
10027
+ sequenceNumber: this.sequenceNumber,
10028
+ timestamp: this.timestamp,
10029
+ ssrc: this.ssrc,
10030
+ iteration: this.iteration
10031
+ });
10032
+ this.props.track.writeRtp(packet);
10033
+ this.sequenceNumber = this.sequenceNumber + 1 & 65535;
10034
+ this.timestamp = this.props.nextTimestamp(this.timestamp) >>> 0;
10035
+ this.iteration += 1;
10036
+ this.schedule();
10037
+ }, delay);
10038
+ }
10039
+ };
9528
10040
 
9529
10041
  // src/nonstandard/navigator.ts
9530
10042
  var Navigator = class {
@@ -9534,7 +10046,7 @@ var Navigator = class {
9534
10046
  }
9535
10047
  };
9536
10048
  var MediaDevices = class extends EventTarget {
9537
- constructor(props) {
10049
+ constructor(props = {}) {
9538
10050
  super();
9539
10051
  this.props = props;
9540
10052
  this.video = props.video;
@@ -9542,31 +10054,16 @@ var MediaDevices = class extends EventTarget {
9542
10054
  }
9543
10055
  video;
9544
10056
  audio;
10057
+ activeSourceStops = /* @__PURE__ */ new Map();
9545
10058
  getUserMedia = async (constraints) => {
9546
- const video = constraints.video ? new MediaStreamTrack({ kind: "video" }) : void 0;
9547
- if (video) {
9548
- this.video?.onReceiveRtp.subscribe((rtp) => {
9549
- const cloned = rtp.clone();
9550
- cloned.header.ssrc = jspack8.Unpack("!L", randomBytes5(4))[0];
9551
- video.onReceiveRtp.execute(cloned);
9552
- });
9553
- }
9554
- const audio = constraints.audio ? new MediaStreamTrack({ kind: "audio" }) : void 0;
9555
- if (audio) {
9556
- this.audio?.onReceiveRtp.subscribe((rtp) => {
9557
- const cloned = rtp.clone();
9558
- cloned.header.ssrc = jspack8.Unpack("!L", randomBytes5(4))[0];
9559
- audio.onReceiveRtp.execute(cloned);
9560
- });
9561
- }
9562
- if (constraints.video && constraints.audio) {
9563
- return new MediaStream([video, audio]);
9564
- } else if (constraints.audio) {
9565
- return new MediaStream([audio]);
9566
- } else if (constraints.video) {
9567
- return new MediaStream([video]);
10059
+ const tracks = [
10060
+ ...constraints.audio ? [this.createTrack("audio", constraints.audio)] : [],
10061
+ ...constraints.video ? [this.createTrack("video", constraints.video)] : []
10062
+ ];
10063
+ if (tracks.length === 0) {
10064
+ throw new Error("At least one audio or video track is required");
9568
10065
  }
9569
- throw new Error("Not implemented");
10066
+ return new MediaStream(tracks);
9570
10067
  };
9571
10068
  getDisplayMedia = this.getUserMedia;
9572
10069
  getUdpMedia = ({
@@ -9588,8 +10085,83 @@ var MediaDevices = class extends EventTarget {
9588
10085
  };
9589
10086
  return { track, disposer };
9590
10087
  };
10088
+ cleanup() {
10089
+ for (const stop of this.activeSourceStops.values()) {
10090
+ stop();
10091
+ }
10092
+ this.activeSourceStops.clear();
10093
+ }
10094
+ createTrack(kind, constraints) {
10095
+ const existingTrack = kind === "audio" ? this.audio : this.video;
10096
+ if (existingTrack) {
10097
+ return this.createClonedTrack(kind, existingTrack);
10098
+ }
10099
+ if (this.props.dummyMedia?.enabled) {
10100
+ return this.createDummyTrack(kind, constraints);
10101
+ }
10102
+ throw new Error(`No ${kind} source configured for getUserMedia`);
10103
+ }
10104
+ createClonedTrack(kind, sourceTrack) {
10105
+ const track = new MediaStreamTrack({ kind });
10106
+ const { unSubscribe } = sourceTrack.onReceiveRtp.subscribe((rtp) => {
10107
+ track.onReceiveRtp.execute(rtp.clone());
10108
+ });
10109
+ this.attachTrackStop(track, () => {
10110
+ unSubscribe();
10111
+ });
10112
+ return track;
10113
+ }
10114
+ createDummyTrack(kind, constraints) {
10115
+ const dummy = kind === "audio" ? createDummyAudioTrack(this.props.dummyMedia?.audio) : createDummyVideoTrack({
10116
+ ...this.props.dummyMedia?.video,
10117
+ ...resolveVideoOptions(constraints)
10118
+ });
10119
+ this.attachTrackStop(dummy.track, () => {
10120
+ dummy.source.stop();
10121
+ });
10122
+ return dummy.track;
10123
+ }
10124
+ attachTrackStop(track, stop) {
10125
+ let disposed = false;
10126
+ const originalStop = track.stop;
10127
+ const dispose = () => {
10128
+ if (disposed) {
10129
+ return;
10130
+ }
10131
+ disposed = true;
10132
+ stop();
10133
+ this.activeSourceStops.delete(track);
10134
+ };
10135
+ track.stop = () => {
10136
+ dispose();
10137
+ originalStop();
10138
+ };
10139
+ this.activeSourceStops.set(track, dispose);
10140
+ }
9591
10141
  };
9592
10142
  var navigator = new Navigator();
10143
+ function resolveVideoOptions(constraints) {
10144
+ if (typeof constraints === "boolean") {
10145
+ return {};
10146
+ }
10147
+ const fps = resolveConstrainNumber(constraints.frameRate);
10148
+ if (!fps) {
10149
+ return {};
10150
+ }
10151
+ return {
10152
+ fps,
10153
+ keyframeIntervalFrames: Math.max(1, Math.round(fps))
10154
+ };
10155
+ }
10156
+ function resolveConstrainNumber(value) {
10157
+ if (typeof value === "number") {
10158
+ return value;
10159
+ }
10160
+ if (!value || typeof value !== "object") {
10161
+ return;
10162
+ }
10163
+ return value.exact ?? value.ideal ?? value.max ?? value.min;
10164
+ }
9593
10165
 
9594
10166
  // src/nonstandard/recorder/writer/webm.ts
9595
10167
  import { unlink } from "fs/promises";
@@ -9609,7 +10181,7 @@ var MediaWriter = class {
9609
10181
  var sourcePath = "packages/webrtc/src/nonstandard/recorder/writer/webm.ts";
9610
10182
  var WebmFactory = class extends MediaWriter {
9611
10183
  rtpSources = [];
9612
- onEol = new Event();
10184
+ onEol = new Event2();
9613
10185
  ended = false;
9614
10186
  unSubscribers = new EventDisposer();
9615
10187
  async start(tracks) {
@@ -9771,7 +10343,7 @@ var MediaRecorder = class {
9771
10343
  ext;
9772
10344
  tracks = [];
9773
10345
  started = false;
9774
- onError = new Event();
10346
+ onError = new Event2();
9775
10347
  async addTrack(track) {
9776
10348
  this.tracks.push(track);
9777
10349
  await this.start();
@@ -9788,113 +10360,891 @@ var MediaRecorder = class {
9788
10360
  };
9789
10361
 
9790
10362
  // src/nonstandard/userMedia.ts
9791
- import { exec } from "child_process";
9792
10363
  import { randomUUID as randomUUID6 } from "crypto";
9793
- import { createSocket as createSocket4 } from "dgram";
9794
- import { setImmediate as setImmediate2 } from "timers/promises";
9795
- var getUserMedia = async ({
9796
- path: path2,
9797
- loop,
9798
- width,
9799
- height
9800
- }) => {
9801
- const audioPort = await randomPort();
9802
- const videoPort = await randomPort();
9803
- if (path2.endsWith(".mp4")) {
9804
- return new MediaPlayerMp4({
9805
- audioPort,
9806
- videoPort,
9807
- path: path2,
9808
- loop,
9809
- width,
9810
- height
10364
+ import { homedir } from "os";
10365
+ import { isAbsolute, resolve } from "path";
10366
+ import { Readable } from "stream";
10367
+ import { readFile } from "fs/promises";
10368
+ import {
10369
+ setImmediate as onNextTurn,
10370
+ setTimeout as wait
10371
+ } from "timers/promises";
10372
+ import {
10373
+ BufferSource,
10374
+ EncodedPacketSink,
10375
+ Input,
10376
+ MP4,
10377
+ WEBM
10378
+ } from "mediabunny";
10379
+
10380
+ // src/nonstandard/userMedia/packetizer.ts
10381
+ var DEFAULT_MAX_RTP_PAYLOAD_SIZE = 1200;
10382
+ var supportedSourceCodecs = [
10383
+ "avc",
10384
+ "vp8",
10385
+ "vp9",
10386
+ "av1",
10387
+ "opus"
10388
+ ];
10389
+ function toSupportedMimeType(codec) {
10390
+ switch (codec) {
10391
+ case "avc":
10392
+ return "video/h264";
10393
+ case "vp8":
10394
+ return "video/vp8";
10395
+ case "vp9":
10396
+ return "video/vp9";
10397
+ case "av1":
10398
+ return "video/av1x";
10399
+ case "opus":
10400
+ return "audio/opus";
10401
+ }
10402
+ }
10403
+ function createPacketizer({
10404
+ codec,
10405
+ sourceCodec,
10406
+ decoderDescription
10407
+ }) {
10408
+ switch (sourceCodec) {
10409
+ case "avc":
10410
+ return new H264Packetizer(codec, decoderDescription ?? null);
10411
+ case "vp8":
10412
+ return new Vp8Packetizer(codec);
10413
+ case "vp9":
10414
+ return new Vp9Packetizer(codec);
10415
+ case "av1":
10416
+ return new Av1Packetizer(codec);
10417
+ case "opus":
10418
+ return new OpusPacketizer(codec);
10419
+ }
10420
+ }
10421
+ var BasePacketizer = class {
10422
+ constructor(codec, maxPayloadSize = DEFAULT_MAX_RTP_PAYLOAD_SIZE) {
10423
+ this.codec = codec;
10424
+ this.maxPayloadSize = maxPayloadSize;
10425
+ }
10426
+ sequenceNumber = random16();
10427
+ buildPacket(payload, timestamp, marker) {
10428
+ const packet = new RtpPacket(
10429
+ new RtpHeader({
10430
+ payloadType: this.codec.payloadType ?? 96,
10431
+ sequenceNumber: this.sequenceNumber,
10432
+ timestamp,
10433
+ marker
10434
+ }),
10435
+ payload
10436
+ );
10437
+ this.sequenceNumber = this.sequenceNumber + 1 & 65535;
10438
+ return packet;
10439
+ }
10440
+ };
10441
+ var OpusPacketizer = class extends BasePacketizer {
10442
+ packetize(packet, rtpTimestamp) {
10443
+ return [this.buildPacket(toBuffer(packet.data), rtpTimestamp, true)];
10444
+ }
10445
+ };
10446
+ var Vp8Packetizer = class extends BasePacketizer {
10447
+ packetize(packet, rtpTimestamp) {
10448
+ const frame = toBuffer(packet.data);
10449
+ const chunkSize = this.maxPayloadSize - 1;
10450
+ if (chunkSize <= 0) {
10451
+ throw new Error("invalid VP8 RTP payload size");
10452
+ }
10453
+ const packets = [];
10454
+ for (let offset = 0; offset < frame.length; offset += chunkSize) {
10455
+ const chunk = frame.subarray(
10456
+ offset,
10457
+ Math.min(frame.length, offset + chunkSize)
10458
+ );
10459
+ const descriptor = Buffer.from([offset === 0 ? 16 : 0]);
10460
+ packets.push(
10461
+ this.buildPacket(
10462
+ Buffer.concat([descriptor, chunk]),
10463
+ rtpTimestamp,
10464
+ offset + chunk.length >= frame.length
10465
+ )
10466
+ );
10467
+ }
10468
+ return packets;
10469
+ }
10470
+ };
10471
+ var Vp9Packetizer = class extends BasePacketizer {
10472
+ packetize(packet, rtpTimestamp) {
10473
+ const frame = toBuffer(packet.data);
10474
+ const chunkSize = this.maxPayloadSize - 1;
10475
+ if (chunkSize <= 0) {
10476
+ throw new Error("invalid VP9 RTP payload size");
10477
+ }
10478
+ const packets = [];
10479
+ for (let offset = 0; offset < frame.length; offset += chunkSize) {
10480
+ const chunk = frame.subarray(
10481
+ offset,
10482
+ Math.min(frame.length, offset + chunkSize)
10483
+ );
10484
+ const descriptor = new BitWriter(8).set(1, 0, 0).set(1, 1, packet.type === "delta" ? 1 : 0).set(1, 2, 0).set(1, 3, 0).set(1, 4, offset === 0 ? 1 : 0).set(1, 5, offset + chunk.length >= frame.length ? 1 : 0).set(1, 6, 0).set(1, 7, 0).buffer;
10485
+ packets.push(
10486
+ this.buildPacket(
10487
+ Buffer.concat([descriptor, chunk]),
10488
+ rtpTimestamp,
10489
+ offset + chunk.length >= frame.length
10490
+ )
10491
+ );
10492
+ }
10493
+ return packets;
10494
+ }
10495
+ };
10496
+ var Av1Packetizer = class extends BasePacketizer {
10497
+ packetize(packet, rtpTimestamp) {
10498
+ const obus = splitAv1Obus(toBuffer(packet.data));
10499
+ const packets = [];
10500
+ const fragmentSize = this.maxPayloadSize - 1;
10501
+ if (fragmentSize <= 0) {
10502
+ throw new Error("invalid AV1 RTP payload size");
10503
+ }
10504
+ let firstPacketInFrame = true;
10505
+ const startsNewCodedVideoSequence = packet.type === "key";
10506
+ for (const obu of obus) {
10507
+ if (obu.length <= fragmentSize) {
10508
+ const aggregationHeader = createAv1AggregationHeader({
10509
+ startsWithFragment: false,
10510
+ endsWithFragment: false,
10511
+ startsNewCodedVideoSequence: firstPacketInFrame && startsNewCodedVideoSequence
10512
+ });
10513
+ packets.push(
10514
+ this.buildPacket(
10515
+ Buffer.concat([aggregationHeader, obu]),
10516
+ rtpTimestamp,
10517
+ false
10518
+ )
10519
+ );
10520
+ firstPacketInFrame = false;
10521
+ continue;
10522
+ }
10523
+ for (let offset = 0; offset < obu.length; offset += fragmentSize) {
10524
+ const chunk = obu.subarray(
10525
+ offset,
10526
+ Math.min(obu.length, offset + fragmentSize)
10527
+ );
10528
+ const aggregationHeader = createAv1AggregationHeader({
10529
+ startsWithFragment: offset > 0,
10530
+ endsWithFragment: offset + chunk.length < obu.length,
10531
+ startsNewCodedVideoSequence: firstPacketInFrame && startsNewCodedVideoSequence
10532
+ });
10533
+ packets.push(
10534
+ this.buildPacket(
10535
+ Buffer.concat([aggregationHeader, chunk]),
10536
+ rtpTimestamp,
10537
+ false
10538
+ )
10539
+ );
10540
+ firstPacketInFrame = false;
10541
+ }
10542
+ }
10543
+ if (packets.length === 0) {
10544
+ throw new Error("AV1 sample did not contain any OBU data");
10545
+ }
10546
+ packets.at(-1).header.marker = true;
10547
+ return packets;
10548
+ }
10549
+ };
10550
+ var H264Packetizer = class extends BasePacketizer {
10551
+ naluLengthSize;
10552
+ parameterSets;
10553
+ constructor(codec, decoderDescription) {
10554
+ super(codec);
10555
+ const packetizationMode = getH264PacketizationMode(codec.parameters);
10556
+ if (packetizationMode !== 1) {
10557
+ throw new Error(
10558
+ `Unsupported H264 packetization-mode=${packetizationMode}. File playback requires packetization-mode=1.`
10559
+ );
10560
+ }
10561
+ const avcConfig = parseAvcDecoderConfiguration(decoderDescription);
10562
+ this.naluLengthSize = avcConfig?.naluLengthSize ?? 4;
10563
+ this.parameterSets = avcConfig ? [...avcConfig.sps.map(Buffer.from), ...avcConfig.pps.map(Buffer.from)] : [];
10564
+ }
10565
+ packetize(packet, rtpTimestamp) {
10566
+ let nalUnits = splitH264Sample(toBuffer(packet.data), this.naluLengthSize);
10567
+ if (packet.type === "key" && this.parameterSets.length > 0 && !containsH264ParameterSets(nalUnits)) {
10568
+ nalUnits = [...this.parameterSets, ...nalUnits];
10569
+ }
10570
+ const packets = [];
10571
+ nalUnits.forEach((nalUnit, index) => {
10572
+ const marker = index === nalUnits.length - 1;
10573
+ if (nalUnit.length <= this.maxPayloadSize) {
10574
+ packets.push(this.buildPacket(nalUnit, rtpTimestamp, marker));
10575
+ return;
10576
+ }
10577
+ const nalHeader = nalUnit[0];
10578
+ const fragmentPayload = nalUnit.subarray(1);
10579
+ const fragmentSize = this.maxPayloadSize - 2;
10580
+ if (fragmentSize <= 0) {
10581
+ throw new Error("invalid H264 RTP payload size");
10582
+ }
10583
+ const fuIndicator = nalHeader & 224 | 28;
10584
+ const nalType = nalHeader & 31;
10585
+ for (let offset = 0; offset < fragmentPayload.length; offset += fragmentSize) {
10586
+ const chunk = fragmentPayload.subarray(
10587
+ offset,
10588
+ Math.min(fragmentPayload.length, offset + fragmentSize)
10589
+ );
10590
+ const fuHeader = Buffer.from([
10591
+ (offset === 0 ? 128 : 0) | (offset + chunk.length >= fragmentPayload.length ? 64 : 0) | nalType
10592
+ ]);
10593
+ packets.push(
10594
+ this.buildPacket(
10595
+ Buffer.concat([Buffer.from([fuIndicator]), fuHeader, chunk]),
10596
+ rtpTimestamp,
10597
+ marker && offset + chunk.length >= fragmentPayload.length
10598
+ )
10599
+ );
10600
+ }
9811
10601
  });
9812
- } else {
9813
- return new MediaPlayerWebm({
9814
- audioPort,
9815
- videoPort,
9816
- path: path2,
9817
- loop,
9818
- width,
9819
- height
10602
+ return packets;
10603
+ }
10604
+ };
10605
+ function createAv1AggregationHeader({
10606
+ startsWithFragment,
10607
+ endsWithFragment,
10608
+ startsNewCodedVideoSequence
10609
+ }) {
10610
+ return new BitWriter(8).set(1, 0, startsWithFragment ? 1 : 0).set(1, 1, endsWithFragment ? 1 : 0).set(2, 2, 1).set(1, 4, startsNewCodedVideoSequence ? 1 : 0).set(3, 5, 0).buffer;
10611
+ }
10612
+ function parseAvcDecoderConfiguration(decoderDescription) {
10613
+ if (!decoderDescription) {
10614
+ return null;
10615
+ }
10616
+ const description = toDescriptionBuffer(decoderDescription);
10617
+ if (description.length < 7) {
10618
+ throw new Error("invalid H264 decoder configuration");
10619
+ }
10620
+ const naluLengthSize = (description[4] & 3) + 1;
10621
+ const spsCount = description[5] & 31;
10622
+ let offset = 6;
10623
+ const sps = [];
10624
+ for (let index = 0; index < spsCount; index++) {
10625
+ const length = description.readUInt16BE(offset);
10626
+ offset += 2;
10627
+ sps.push(description.subarray(offset, offset + length));
10628
+ offset += length;
10629
+ }
10630
+ const ppsCount = description[offset] ?? 0;
10631
+ offset++;
10632
+ const pps = [];
10633
+ for (let index = 0; index < ppsCount; index++) {
10634
+ const length = description.readUInt16BE(offset);
10635
+ offset += 2;
10636
+ pps.push(description.subarray(offset, offset + length));
10637
+ offset += length;
10638
+ }
10639
+ return { naluLengthSize, sps, pps };
10640
+ }
10641
+ function splitH264Sample(sample, naluLengthSize) {
10642
+ if (looksLikeAnnexB(sample)) {
10643
+ const nalUnits2 = [];
10644
+ const parser = new H264AnnexBParser(sample);
10645
+ let payload = parser.readNextNaluPayload();
10646
+ while (payload) {
10647
+ nalUnits2.push(Buffer.from(payload.data));
10648
+ payload = parser.readNextNaluPayload();
10649
+ }
10650
+ return nalUnits2;
10651
+ }
10652
+ const nalUnits = [];
10653
+ let offset = 0;
10654
+ while (offset < sample.length) {
10655
+ const length = readUintLength(sample, offset, naluLengthSize);
10656
+ offset += naluLengthSize;
10657
+ const end = offset + length;
10658
+ if (end > sample.length) {
10659
+ throw new Error("invalid H264 sample length");
10660
+ }
10661
+ nalUnits.push(sample.subarray(offset, end));
10662
+ offset = end;
10663
+ }
10664
+ return nalUnits;
10665
+ }
10666
+ function splitAv1Obus(sample) {
10667
+ const obus = [];
10668
+ let offset = 0;
10669
+ while (offset < sample.length) {
10670
+ const start = offset;
10671
+ const header = sample[offset];
10672
+ offset += 1;
10673
+ if (header == void 0) {
10674
+ break;
10675
+ }
10676
+ const extensionFlag = getBit(header, 5);
10677
+ const hasSizeField = getBit(header, 6);
10678
+ if (extensionFlag) {
10679
+ offset += 1;
10680
+ }
10681
+ if (!hasSizeField) {
10682
+ obus.push(sample.subarray(start));
10683
+ break;
10684
+ }
10685
+ const [obuSize, leb128Length] = leb128decode(sample.subarray(offset));
10686
+ offset += leb128Length;
10687
+ const end = offset + obuSize;
10688
+ if (end > sample.length) {
10689
+ throw new Error("invalid AV1 OBU size");
10690
+ }
10691
+ obus.push(sample.subarray(start, end));
10692
+ offset = end;
10693
+ }
10694
+ return obus;
10695
+ }
10696
+ function containsH264ParameterSets(nalUnits) {
10697
+ return nalUnits.some((nalUnit) => {
10698
+ const nalType = nalUnit[0] & 31;
10699
+ return nalType === 7 || nalType === 8;
10700
+ });
10701
+ }
10702
+ function looksLikeAnnexB(sample) {
10703
+ return sample.length >= 4 && sample[0] === 0 && sample[1] === 0 && (sample[2] === 0 && sample[3] === 1 || sample[2] === 1);
10704
+ }
10705
+ function readUintLength(buffer2, offset, length) {
10706
+ switch (length) {
10707
+ case 1:
10708
+ return buffer2.readUInt8(offset);
10709
+ case 2:
10710
+ return buffer2.readUInt16BE(offset);
10711
+ case 3:
10712
+ return buffer2.readUIntBE(offset, 3);
10713
+ case 4:
10714
+ return buffer2.readUInt32BE(offset);
10715
+ default:
10716
+ throw new Error(`unsupported H264 NALU length size: ${length}`);
10717
+ }
10718
+ }
10719
+ function getH264PacketizationMode(parameters) {
10720
+ if (!parameters) {
10721
+ return 1;
10722
+ }
10723
+ const packetizationMode = parameters.split(";").map((part) => part.trim()).find((part) => part.startsWith("packetization-mode="));
10724
+ if (!packetizationMode) {
10725
+ return 1;
10726
+ }
10727
+ return Number(packetizationMode.split("=")[1] ?? 1);
10728
+ }
10729
+ function toDescriptionBuffer(decoderDescription) {
10730
+ if (ArrayBuffer.isView(decoderDescription)) {
10731
+ return Buffer.from(
10732
+ decoderDescription.buffer,
10733
+ decoderDescription.byteOffset,
10734
+ decoderDescription.byteLength
10735
+ );
10736
+ }
10737
+ return Buffer.from(decoderDescription);
10738
+ }
10739
+ function toBuffer(data) {
10740
+ return Buffer.from(data.buffer, data.byteOffset, data.byteLength);
10741
+ }
10742
+
10743
+ // src/nonstandard/userMedia.ts
10744
+ var getUserMedia = async (options) => {
10745
+ assertSupportedOptions(options);
10746
+ const replayableSource = await createReplayableSource(options);
10747
+ const input = await createInput(replayableSource);
10748
+ try {
10749
+ if (!await input.canRead()) {
10750
+ throw new Error("Only MP4 and WebM file playback is supported");
10751
+ }
10752
+ const streamId = randomUUID6().toString();
10753
+ const playbackTracks = await selectPlaybackTracks(input, streamId);
10754
+ if (playbackTracks.length === 0) {
10755
+ throw new Error(
10756
+ "The media source does not contain playable audio or video tracks"
10757
+ );
10758
+ }
10759
+ const mediaStartTimestamp = await input.getFirstTimestamp(
10760
+ playbackTracks.map(({ inputTrack }) => inputTrack)
10761
+ );
10762
+ return new MediaPlayer({
10763
+ source: replayableSource,
10764
+ loop: options.loop ?? false,
10765
+ streamId,
10766
+ session: createPlaybackSession({
10767
+ input,
10768
+ mediaStartTimestamp,
10769
+ playbackTracks
10770
+ })
9820
10771
  });
10772
+ } catch (error) {
10773
+ input.dispose();
10774
+ throw error;
9821
10775
  }
9822
10776
  };
9823
10777
  var MediaPlayer = class {
9824
- constructor(props) {
9825
- this.props = props;
9826
- this.setupTrack(props.audioPort, this.audio);
9827
- this.setupTrack(props.videoPort, this.video);
9828
- }
9829
- streamId = randomUUID6().toString();
9830
- audio = new MediaStreamTrack({ kind: "audio", streamId: this.streamId });
9831
- video = new MediaStreamTrack({ kind: "video", streamId: this.streamId });
9832
- process;
10778
+ audio;
10779
+ video;
10780
+ onError = new Event2();
10781
+ source;
10782
+ loop;
10783
+ streamId;
10784
+ session;
10785
+ running;
10786
+ abortController;
10787
+ hasStartedPlayback = false;
9833
10788
  stopped = false;
9834
- setupTrack = (port, track) => {
9835
- let payloadType = 0;
9836
- const socket = createSocket4("udp4");
9837
- socket.bind(port);
9838
- socket.on("message", async (buf) => {
9839
- const rtp = RtpPacket.deSerialize(buf);
9840
- if (!payloadType) {
9841
- payloadType = rtp.header.payloadType;
9842
- }
9843
- if (payloadType !== rtp.header.payloadType) {
9844
- payloadType = rtp.header.payloadType;
9845
- track.onSourceChanged.execute(rtp.header);
9846
- }
9847
- track.writeRtp(buf);
10789
+ constructor({
10790
+ source,
10791
+ loop,
10792
+ streamId,
10793
+ session
10794
+ }) {
10795
+ this.source = source;
10796
+ this.loop = loop;
10797
+ this.streamId = streamId;
10798
+ this.session = session;
10799
+ this.audio = session.playbackTracks.find(
10800
+ ({ track }) => track.kind === "audio"
10801
+ )?.track;
10802
+ this.video = session.playbackTracks.find(
10803
+ ({ track }) => track.kind === "video"
10804
+ )?.track;
10805
+ }
10806
+ async start() {
10807
+ if (this.stopped) {
10808
+ throw new Error("The media source has already been stopped");
10809
+ }
10810
+ if (this.running) {
10811
+ return;
10812
+ }
10813
+ const abortController = new AbortController();
10814
+ this.abortController = abortController;
10815
+ let startupPending = true;
10816
+ let session;
10817
+ const playback = (async () => {
10818
+ session = await this.getOrCreateSession();
10819
+ if (abortController.signal.aborted || this.stopped) {
10820
+ return;
10821
+ }
10822
+ session.runners.forEach((runner) => runner.assertReady());
10823
+ const initialSourceChanged = this.hasStartedPlayback;
10824
+ this.hasStartedPlayback = true;
10825
+ await this.run(session, abortController, initialSourceChanged);
10826
+ })();
10827
+ const observedPlayback = playback.catch((error) => {
10828
+ if (abortController.signal.aborted) {
10829
+ return;
10830
+ }
10831
+ this.onError.execute(toError(error));
10832
+ if (startupPending) {
10833
+ throw error;
10834
+ }
9848
10835
  });
9849
- };
10836
+ this.running = observedPlayback.finally(() => {
10837
+ if (this.abortController === abortController) {
10838
+ this.abortController = void 0;
10839
+ }
10840
+ this.running = void 0;
10841
+ this.disposeSession(session);
10842
+ });
10843
+ try {
10844
+ await Promise.race([this.running, onNextTurn()]);
10845
+ } finally {
10846
+ startupPending = false;
10847
+ }
10848
+ }
9850
10849
  stop() {
10850
+ if (this.stopped) {
10851
+ return;
10852
+ }
9851
10853
  this.stopped = true;
9852
- this.process.kill("SIGINT");
10854
+ this.abortController?.abort();
10855
+ if (!this.running) {
10856
+ this.disposeSession();
10857
+ }
10858
+ }
10859
+ async run(session, abortController, initialSourceChanged) {
10860
+ let sourceChanged = initialSourceChanged;
10861
+ do {
10862
+ const startedAt = performance.now();
10863
+ await Promise.all(
10864
+ session.runners.map(
10865
+ (runner) => runner.play({
10866
+ startedAt,
10867
+ signal: abortController.signal,
10868
+ sourceChanged
10869
+ })
10870
+ )
10871
+ );
10872
+ if (!this.loop || abortController.signal.aborted) {
10873
+ break;
10874
+ }
10875
+ sourceChanged = true;
10876
+ } while (!abortController.signal.aborted);
9853
10877
  }
9854
- };
9855
- var MediaPlayerMp4 = class extends MediaPlayer {
9856
- async start() {
9857
- let payloadType = 96;
9858
- const run = async () => {
9859
- if (payloadType > 100) payloadType = 96;
9860
- let cmd = "";
9861
- if (this.props.width && this.props.height) {
9862
- cmd = `gst-launch-1.0 filesrc location= ${this.props.path} ! decodebin ! videoscale ! video/x-raw,width=${this.props.width},height=${this.props.height} ! x264enc ! h264parse ! rtph264pay config-interval=10 pt=${payloadType++} ! udpsink host=127.0.0.1 port=${this.props.videoPort}`;
9863
- } else {
9864
- cmd = `gst-launch-1.0 filesrc location= ${this.props.path} ! qtdemux name=d ! queue ! h264parse ! rtph264pay config-interval=10 pt=${payloadType++} ! udpsink host=127.0.0.1 port=${this.props.videoPort} d. ! queue ! aacparse ! avdec_aac ! audioresample ! audioconvert ! opusenc ! rtpopuspay pt=${payloadType++} ! udpsink host=127.0.0.1 port=${this.props.audioPort}`;
9865
- }
9866
- console.log(cmd);
9867
- this.process = exec(cmd);
9868
- this.process.on("error", (e) => console.error("gst error", e));
9869
- if (this.props.loop) {
9870
- await new Promise((r) => this.process.on("close", r));
9871
- if (!this.stopped) {
9872
- run();
9873
- }
10878
+ async getOrCreateSession() {
10879
+ if (this.session) {
10880
+ return this.session;
10881
+ }
10882
+ const input = await createInput(this.source);
10883
+ try {
10884
+ if (!await input.canRead()) {
10885
+ throw new Error("Only MP4 and WebM file playback is supported");
9874
10886
  }
9875
- };
9876
- await setImmediate2();
9877
- run();
10887
+ const playbackTracks = await selectPlaybackTracks(
10888
+ input,
10889
+ this.streamId,
10890
+ buildExistingTrackMap(this.audio, this.video)
10891
+ );
10892
+ const mediaStartTimestamp = await input.getFirstTimestamp(
10893
+ playbackTracks.map(({ inputTrack }) => inputTrack)
10894
+ );
10895
+ const session = createPlaybackSession({
10896
+ input,
10897
+ mediaStartTimestamp,
10898
+ playbackTracks
10899
+ });
10900
+ this.session = session;
10901
+ return session;
10902
+ } catch (error) {
10903
+ input.dispose();
10904
+ throw error;
10905
+ }
10906
+ }
10907
+ disposeSession(session = this.session) {
10908
+ if (!session) {
10909
+ return;
10910
+ }
10911
+ session.input.dispose();
10912
+ if (this.session === session) {
10913
+ this.session = void 0;
10914
+ }
9878
10915
  }
9879
10916
  };
9880
- var MediaPlayerWebm = class extends MediaPlayer {
9881
- async start() {
9882
- let payloadType = 96;
9883
- const run = async () => {
9884
- if (payloadType > 100) payloadType = 96;
9885
- const cmd = `gst-launch-1.0 filesrc location=${this.props.path} ! matroskademux name=d d.video_0 ! queue ! rtpvp8pay pt=${payloadType++} ! udpsink host=127.0.0.1 port=${this.props.videoPort} d.audio_0 ! queue ! rtpopuspay pt=${payloadType++} ! udpsink host=127.0.0.1 port=${this.props.audioPort}`;
9886
- this.process = exec(cmd);
9887
- if (this.props.loop) {
9888
- await new Promise((r) => this.process.on("close", r));
9889
- if (!this.stopped) {
9890
- run();
10917
+ var TrackPlaybackRunner = class {
10918
+ constructor(props) {
10919
+ this.props = props;
10920
+ }
10921
+ async play({
10922
+ startedAt,
10923
+ signal,
10924
+ sourceChanged
10925
+ }) {
10926
+ const codec = this.requireNegotiatedCodec();
10927
+ const packetizer = createPacketizer({
10928
+ codec,
10929
+ sourceCodec: this.props.sourceCodec,
10930
+ decoderDescription: this.props.decoderDescription
10931
+ });
10932
+ const packetSink = new EncodedPacketSink(this.props.inputTrack);
10933
+ let sourceChangeNotified = false;
10934
+ try {
10935
+ for await (const packet of packetSink.packets(void 0, void 0, {
10936
+ verifyKeyPackets: this.props.track.kind === "video"
10937
+ })) {
10938
+ signal.throwIfAborted?.();
10939
+ const dueAtMs = (packet.timestamp - this.props.mediaStartTimestamp) * 1e3;
10940
+ const remainingMs = dueAtMs - (performance.now() - startedAt);
10941
+ if (remainingMs > 0) {
10942
+ await wait(remainingMs, void 0, { signal });
10943
+ }
10944
+ const rtpTimestamp = toRtpTimestamp(
10945
+ packet.timestamp,
10946
+ this.props.mediaStartTimestamp,
10947
+ codec.clockRate
10948
+ );
10949
+ const packets = packetizer.packetize(packet, rtpTimestamp);
10950
+ if (packets.length === 0) {
10951
+ continue;
10952
+ }
10953
+ if (sourceChanged && !sourceChangeNotified) {
10954
+ this.props.track.onSourceChanged.execute(packets[0].header);
10955
+ sourceChangeNotified = true;
10956
+ }
10957
+ for (const rtpPacket of packets) {
10958
+ this.props.track.writeRtp(rtpPacket);
9891
10959
  }
9892
10960
  }
9893
- };
9894
- await setImmediate2();
9895
- run();
10961
+ } catch (error) {
10962
+ if (signal.aborted || isAbortError(error)) {
10963
+ return;
10964
+ }
10965
+ throw error;
10966
+ }
9896
10967
  }
9897
- };
10968
+ assertReady() {
10969
+ const codec = this.requireNegotiatedCodec();
10970
+ createPacketizer({
10971
+ codec,
10972
+ sourceCodec: this.props.sourceCodec,
10973
+ decoderDescription: this.props.decoderDescription
10974
+ });
10975
+ }
10976
+ requireNegotiatedCodec() {
10977
+ const codec = this.props.track.codec;
10978
+ if (!codec) {
10979
+ throw new Error(
10980
+ `Cannot start ${this.props.track.kind} playback before the track is attached to an RTCRtpSender`
10981
+ );
10982
+ }
10983
+ const expectedMimeType = toSupportedMimeType(this.props.sourceCodec);
10984
+ const negotiatedMimeType = normalizeMimeType(codec.mimeType);
10985
+ if (negotiatedMimeType !== expectedMimeType) {
10986
+ throw new Error(
10987
+ `Input ${this.props.track.kind} codec ${expectedMimeType} does not match negotiated codec ${codec.mimeType}`
10988
+ );
10989
+ }
10990
+ const expectedClockRate = this.props.track.kind === "audio" ? 48e3 : 9e4;
10991
+ if (codec.clockRate !== expectedClockRate) {
10992
+ throw new Error(
10993
+ `Input ${this.props.track.kind} clock rate ${expectedClockRate} does not match negotiated clock rate ${codec.clockRate}`
10994
+ );
10995
+ }
10996
+ assertCodecParameters({
10997
+ trackKind: this.props.track.kind === "audio" ? "audio" : "video",
10998
+ codec,
10999
+ sourceCodec: this.props.sourceCodec,
11000
+ decoderDescription: this.props.decoderDescription,
11001
+ sourceChannels: this.props.sourceChannels
11002
+ });
11003
+ return codec;
11004
+ }
11005
+ };
11006
+ async function selectPlaybackTracks(input, streamId, existingTracks = {}) {
11007
+ const primaryVideoTrack = await input.getPrimaryVideoTrack();
11008
+ const primaryAudioTrack = primaryVideoTrack ? await primaryVideoTrack.getPrimaryPairableAudioTrack() ?? await input.getPrimaryAudioTrack() : await input.getPrimaryAudioTrack();
11009
+ const playbackTracks = [];
11010
+ if (primaryAudioTrack) {
11011
+ playbackTracks.push(
11012
+ await createPlaybackTrack(
11013
+ primaryAudioTrack,
11014
+ streamId,
11015
+ existingTracks.audio
11016
+ )
11017
+ );
11018
+ }
11019
+ if (primaryVideoTrack) {
11020
+ playbackTracks.push(
11021
+ await createPlaybackTrack(
11022
+ primaryVideoTrack,
11023
+ streamId,
11024
+ existingTracks.video
11025
+ )
11026
+ );
11027
+ }
11028
+ if (!primaryVideoTrack && !primaryAudioTrack) {
11029
+ const fallbackAudioTrack = await input.getPrimaryAudioTrack();
11030
+ if (fallbackAudioTrack) {
11031
+ playbackTracks.push(
11032
+ await createPlaybackTrack(
11033
+ fallbackAudioTrack,
11034
+ streamId,
11035
+ existingTracks.audio
11036
+ )
11037
+ );
11038
+ }
11039
+ }
11040
+ return playbackTracks;
11041
+ }
11042
+ async function createPlaybackTrack(inputTrack, streamId, track) {
11043
+ const sourceCodec = await requireSupportedCodec(inputTrack);
11044
+ const decoderConfig = await inputTrack.getDecoderConfig();
11045
+ const kind = inputTrack.isAudioTrack() ? "audio" : "video";
11046
+ return {
11047
+ inputTrack,
11048
+ sourceCodec,
11049
+ decoderDescription: decoderConfig && "description" in decoderConfig ? decoderConfig.description ?? null : null,
11050
+ sourceChannels: decoderConfig && "numberOfChannels" in decoderConfig ? decoderConfig.numberOfChannels : void 0,
11051
+ track: track ?? new MediaStreamTrack({
11052
+ kind,
11053
+ streamId
11054
+ })
11055
+ };
11056
+ }
11057
+ async function requireSupportedCodec(inputTrack) {
11058
+ const codec = await inputTrack.getCodec();
11059
+ if (codec && supportedSourceCodecs.includes(codec)) {
11060
+ return codec;
11061
+ }
11062
+ const codecLabel = codec ?? await inputTrack.getCodecParameterString() ?? String(await inputTrack.getInternalCodecId() ?? "unknown");
11063
+ throw new Error(
11064
+ `Unsupported ${inputTrack.type} codec "${codecLabel}". File playback supports H264, VP8, VP9, AV1, and Opus only.`
11065
+ );
11066
+ }
11067
+ async function createReplayableSource(options) {
11068
+ if (typeof options.path === "string") {
11069
+ return { path: options.path };
11070
+ }
11071
+ const buffer2 = options.buffer != void 0 ? toBuffer2(options.buffer) : await readStreamToBuffer(options.stream);
11072
+ return { buffer: buffer2 };
11073
+ }
11074
+ async function createInput(options) {
11075
+ if (typeof options.path === "string") {
11076
+ const buffer2 = await readFile(resolveUserMediaPath(options.path));
11077
+ return new Input({
11078
+ source: new BufferSource(buffer2),
11079
+ formats: [MP4, WEBM]
11080
+ });
11081
+ }
11082
+ return new Input({
11083
+ source: new BufferSource(options.buffer),
11084
+ formats: [MP4, WEBM]
11085
+ });
11086
+ }
11087
+ function resolveUserMediaPath(path2) {
11088
+ if (path2 === "~") {
11089
+ return homedir();
11090
+ }
11091
+ if (path2.startsWith("~/")) {
11092
+ return resolve(homedir(), path2.slice(2));
11093
+ }
11094
+ return isAbsolute(path2) ? path2 : resolve(path2);
11095
+ }
11096
+ async function readStreamToBuffer(stream) {
11097
+ const chunks = [];
11098
+ if (stream instanceof Readable) {
11099
+ for await (const chunk of stream) {
11100
+ chunks.push(toBuffer2(chunk));
11101
+ }
11102
+ return Buffer.concat(chunks);
11103
+ }
11104
+ const reader = stream.getReader();
11105
+ try {
11106
+ for (; ; ) {
11107
+ const { done, value } = await reader.read();
11108
+ if (done) {
11109
+ break;
11110
+ }
11111
+ if (value) {
11112
+ chunks.push(
11113
+ Buffer.from(value.buffer, value.byteOffset, value.byteLength)
11114
+ );
11115
+ }
11116
+ }
11117
+ } finally {
11118
+ reader.releaseLock();
11119
+ }
11120
+ return Buffer.concat(chunks);
11121
+ }
11122
+ function toBuffer2(value) {
11123
+ if (Buffer.isBuffer(value)) {
11124
+ return value;
11125
+ }
11126
+ if (typeof value === "string") {
11127
+ return Buffer.from(value);
11128
+ }
11129
+ if (ArrayBuffer.isView(value)) {
11130
+ return Buffer.from(value.buffer, value.byteOffset, value.byteLength);
11131
+ }
11132
+ return Buffer.from(new Uint8Array(value));
11133
+ }
11134
+ function normalizeMimeType(mimeType) {
11135
+ return mimeType.toLowerCase();
11136
+ }
11137
+ function assertSupportedOptions(options) {
11138
+ const legacyOptions = options;
11139
+ if (legacyOptions.width != void 0 || legacyOptions.height != void 0) {
11140
+ throw new Error(
11141
+ "getUserMedia({ width, height }) is no longer supported for file playback. Resize or re-encode the source before calling getUserMedia()."
11142
+ );
11143
+ }
11144
+ }
11145
+ function createPlaybackSession({
11146
+ input,
11147
+ mediaStartTimestamp,
11148
+ playbackTracks
11149
+ }) {
11150
+ return {
11151
+ input,
11152
+ mediaStartTimestamp,
11153
+ playbackTracks,
11154
+ runners: playbackTracks.map(
11155
+ (playbackTrack) => new TrackPlaybackRunner({
11156
+ mediaStartTimestamp,
11157
+ ...playbackTrack
11158
+ })
11159
+ )
11160
+ };
11161
+ }
11162
+ function buildExistingTrackMap(audio, video) {
11163
+ return {
11164
+ ...audio ? { audio } : {},
11165
+ ...video ? { video } : {}
11166
+ };
11167
+ }
11168
+ function assertCodecParameters({
11169
+ trackKind,
11170
+ codec,
11171
+ sourceCodec,
11172
+ decoderDescription,
11173
+ sourceChannels
11174
+ }) {
11175
+ if (sourceCodec === "opus" && sourceChannels && codec.channels != void 0) {
11176
+ if (codec.channels !== sourceChannels) {
11177
+ throw new Error(
11178
+ `Input ${trackKind} channels ${sourceChannels} do not match negotiated channels ${codec.channels}`
11179
+ );
11180
+ }
11181
+ return;
11182
+ }
11183
+ if (sourceCodec !== "avc") {
11184
+ return;
11185
+ }
11186
+ const negotiatedParameters = codecParametersFromString(
11187
+ codec.parameters ?? ""
11188
+ );
11189
+ const negotiatedPacketizationMode = Number(
11190
+ negotiatedParameters["packetization-mode"] ?? 1
11191
+ );
11192
+ if (negotiatedPacketizationMode !== 1) {
11193
+ throw new Error(
11194
+ `Input ${trackKind} packetization-mode 1 does not match negotiated packetization-mode ${negotiatedPacketizationMode}`
11195
+ );
11196
+ }
11197
+ const sourceProfileLevelId = getH264ProfileLevelId(decoderDescription);
11198
+ const negotiatedProfileLevelId = normalizeOptionalHexParameter(
11199
+ negotiatedParameters["profile-level-id"]
11200
+ );
11201
+ if (!sourceProfileLevelId) {
11202
+ return;
11203
+ }
11204
+ if (!negotiatedProfileLevelId) {
11205
+ throw new Error(
11206
+ `Input ${trackKind} profile-level-id ${sourceProfileLevelId} does not match negotiated profile-level-id <missing>`
11207
+ );
11208
+ }
11209
+ if (sourceProfileLevelId !== negotiatedProfileLevelId) {
11210
+ throw new Error(
11211
+ `Input ${trackKind} profile-level-id ${sourceProfileLevelId} does not match negotiated profile-level-id ${negotiatedProfileLevelId}`
11212
+ );
11213
+ }
11214
+ }
11215
+ function getH264ProfileLevelId(decoderDescription) {
11216
+ if (!decoderDescription) {
11217
+ return void 0;
11218
+ }
11219
+ const description = toDescriptionBuffer2(decoderDescription);
11220
+ if (description.length < 4) {
11221
+ throw new Error("invalid H264 decoder configuration");
11222
+ }
11223
+ return description.subarray(1, 4).toString("hex");
11224
+ }
11225
+ function normalizeOptionalHexParameter(value) {
11226
+ return typeof value === "string" ? value.toLowerCase() : void 0;
11227
+ }
11228
+ function toRtpTimestamp(packetTimestamp, mediaStartTimestamp, clockRate) {
11229
+ const relativeSeconds = Math.max(0, packetTimestamp - mediaStartTimestamp);
11230
+ return Math.round(relativeSeconds * clockRate) >>> 0;
11231
+ }
11232
+ function isAbortError(error) {
11233
+ return error instanceof Error && (error.name === "AbortError" || error.message === "The operation was aborted");
11234
+ }
11235
+ function toError(error) {
11236
+ return error instanceof Error ? error : new Error(String(error));
11237
+ }
11238
+ function toDescriptionBuffer2(decoderDescription) {
11239
+ if (ArrayBuffer.isView(decoderDescription)) {
11240
+ return Buffer.from(
11241
+ decoderDescription.buffer,
11242
+ decoderDescription.byteOffset,
11243
+ decoderDescription.byteLength
11244
+ );
11245
+ }
11246
+ return Buffer.from(decoderDescription);
11247
+ }
9898
11248
  export {
9899
11249
  AV1Obu,
9900
11250
  AV1RtpPayload,
@@ -9910,7 +11260,7 @@ export {
9910
11260
  DtxCallback,
9911
11261
  DurationPosition,
9912
11262
  Element,
9913
- Event,
11263
+ Event2 as Event,
9914
11264
  EventDisposer,
9915
11265
  ExtensionProfiles,
9916
11266
  GenericNack,
@@ -9931,8 +11281,6 @@ export {
9931
11281
  Max32Uint,
9932
11282
  MaxSinged16Int,
9933
11283
  MediaDevices,
9934
- MediaPlayerMp4,
9935
- MediaPlayerWebm,
9936
11284
  MediaRecorder,
9937
11285
  Mp4Container,
9938
11286
  MuteCallback,
@@ -9988,6 +11336,7 @@ export {
9988
11336
  SrtpSession,
9989
11337
  StatusVectorChunk,
9990
11338
  TcpTransport,
11339
+ TlsTransport,
9991
11340
  TransportWideCC,
9992
11341
  UNKNOWN_SIZE,
9993
11342
  UdpTransport,
@@ -9999,6 +11348,7 @@ export {
9999
11348
  WebmCallback,
10000
11349
  WebmStream,
10001
11350
  WeriftError,
11351
+ annexb2avcSample,
10002
11352
  annexb2avcc,
10003
11353
  buffer2ArrayBuffer,
10004
11354
  bufferArrayXor,