werift 0.24.4 → 0.25.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 (166) hide show
  1. package/README.md +116 -113
  2. package/lib/common/src/transport.d.ts +22 -2
  3. package/lib/common/src/transport.js +84 -26
  4. package/lib/common/src/transport.js.map +1 -1
  5. package/lib/ice/src/exceptions.d.ts +9 -0
  6. package/lib/ice/src/exceptions.js +23 -1
  7. package/lib/ice/src/exceptions.js.map +1 -1
  8. package/lib/ice/src/ice.d.ts +40 -0
  9. package/lib/ice/src/ice.js +194 -28
  10. package/lib/ice/src/ice.js.map +1 -1
  11. package/lib/ice/src/iceBase.d.ts +26 -0
  12. package/lib/ice/src/iceBase.js.map +1 -1
  13. package/lib/ice/src/stun/const.d.ts +7 -0
  14. package/lib/ice/src/stun/const.js +8 -0
  15. package/lib/ice/src/stun/const.js.map +1 -1
  16. package/lib/ice/src/stun/protocol.js +1 -4
  17. package/lib/ice/src/stun/protocol.js.map +1 -1
  18. package/lib/ice/src/stun/tcpProtocol.d.ts +7 -1
  19. package/lib/ice/src/stun/tcpProtocol.js +65 -14
  20. package/lib/ice/src/stun/tcpProtocol.js.map +1 -1
  21. package/lib/ice/src/stun/transaction.d.ts +12 -1
  22. package/lib/ice/src/stun/transaction.js +37 -4
  23. package/lib/ice/src/stun/transaction.js.map +1 -1
  24. package/lib/ice/src/turn/protocol.d.ts +40 -15
  25. package/lib/ice/src/turn/protocol.js +141 -32
  26. package/lib/ice/src/turn/protocol.js.map +1 -1
  27. package/lib/ice/src/types/model.d.ts +8 -0
  28. package/lib/ice/src/types/model.js.map +1 -1
  29. package/lib/index.mjs +6175 -4073
  30. package/lib/nonstandard/index.mjs +286 -1003
  31. package/lib/polyfill/dom.mjs +23794 -0
  32. package/lib/polyfill/index.mjs +23794 -0
  33. package/lib/rtp/src/extra/container/mp4/container.js +12 -4
  34. package/lib/rtp/src/extra/container/mp4/container.js.map +1 -1
  35. package/lib/rtp/src/extra/processor/mp4.js +8 -3
  36. package/lib/rtp/src/extra/processor/mp4.js.map +1 -1
  37. package/lib/rtp/src/extra/processor/mp4Callback.js +3 -1
  38. package/lib/rtp/src/extra/processor/mp4Callback.js.map +1 -1
  39. package/lib/sctp/src/chunk.d.ts +5 -0
  40. package/lib/sctp/src/chunk.js +16 -11
  41. package/lib/sctp/src/chunk.js.map +1 -1
  42. package/lib/sctp/src/index.d.ts +3 -1
  43. package/lib/sctp/src/index.js +10 -1
  44. package/lib/sctp/src/index.js.map +1 -1
  45. package/lib/sctp/src/sctp.d.ts +12 -3
  46. package/lib/sctp/src/sctp.js +51 -18
  47. package/lib/sctp/src/sctp.js.map +1 -1
  48. package/lib/webrtc/src/const.js +2 -0
  49. package/lib/webrtc/src/const.js.map +1 -1
  50. package/lib/webrtc/src/errors.d.ts +9 -1
  51. package/lib/webrtc/src/errors.js +19 -0
  52. package/lib/webrtc/src/errors.js.map +1 -1
  53. package/lib/webrtc/src/media/codec.d.ts +5 -0
  54. package/lib/webrtc/src/media/codec.js +21 -0
  55. package/lib/webrtc/src/media/codec.js.map +1 -1
  56. package/lib/webrtc/src/media/codecCompatibility.d.ts +23 -0
  57. package/lib/webrtc/src/media/codecCompatibility.js +138 -0
  58. package/lib/webrtc/src/media/codecCompatibility.js.map +1 -0
  59. package/lib/webrtc/src/media/index.d.ts +1 -0
  60. package/lib/webrtc/src/media/index.js +1 -0
  61. package/lib/webrtc/src/media/index.js.map +1 -1
  62. package/lib/webrtc/src/media/router.d.ts +2 -0
  63. package/lib/webrtc/src/media/router.js +23 -3
  64. package/lib/webrtc/src/media/router.js.map +1 -1
  65. package/lib/webrtc/src/media/rtpReceiver.d.ts +12 -0
  66. package/lib/webrtc/src/media/rtpReceiver.js +35 -0
  67. package/lib/webrtc/src/media/rtpReceiver.js.map +1 -1
  68. package/lib/webrtc/src/media/rtpSender.d.ts +65 -4
  69. package/lib/webrtc/src/media/rtpSender.js +264 -35
  70. package/lib/webrtc/src/media/rtpSender.js.map +1 -1
  71. package/lib/webrtc/src/media/rtpTransceiver.d.ts +42 -0
  72. package/lib/webrtc/src/media/rtpTransceiver.js +157 -4
  73. package/lib/webrtc/src/media/rtpTransceiver.js.map +1 -1
  74. package/lib/webrtc/src/media/track.d.ts +29 -1
  75. package/lib/webrtc/src/media/track.js +164 -5
  76. package/lib/webrtc/src/media/track.js.map +1 -1
  77. package/lib/webrtc/src/nonstandard/index.d.ts +0 -1
  78. package/lib/webrtc/src/nonstandard/index.js +0 -1
  79. package/lib/webrtc/src/nonstandard/index.js.map +1 -1
  80. package/lib/webrtc/src/nonstandard/recorder/writer/webm.js +2 -1
  81. package/lib/webrtc/src/nonstandard/recorder/writer/webm.js.map +1 -1
  82. package/lib/webrtc/src/nonstandard/userMedia.d.ts +1 -2
  83. package/lib/webrtc/src/nonstandard/userMedia.js +30 -14
  84. package/lib/webrtc/src/nonstandard/userMedia.js.map +1 -1
  85. package/lib/webrtc/src/peerConnection.d.ts +126 -5
  86. package/lib/webrtc/src/peerConnection.js +609 -136
  87. package/lib/webrtc/src/peerConnection.js.map +1 -1
  88. package/lib/webrtc/src/polyfill/api.d.ts +14 -0
  89. package/lib/webrtc/src/polyfill/api.js +23 -0
  90. package/lib/webrtc/src/polyfill/api.js.map +1 -0
  91. package/lib/webrtc/src/polyfill/browserIdentity.d.ts +11 -0
  92. package/lib/webrtc/src/polyfill/browserIdentity.js +59 -0
  93. package/lib/webrtc/src/polyfill/browserIdentity.js.map +1 -0
  94. package/lib/webrtc/src/polyfill/dom.d.ts +11 -0
  95. package/lib/webrtc/src/polyfill/dom.js +18 -0
  96. package/lib/webrtc/src/polyfill/dom.js.map +1 -0
  97. package/lib/webrtc/src/polyfill/existingMediaDevices.d.ts +2 -0
  98. package/lib/webrtc/src/polyfill/existingMediaDevices.js +22 -0
  99. package/lib/webrtc/src/polyfill/existingMediaDevices.js.map +1 -0
  100. package/lib/webrtc/src/polyfill/globals.d.ts +28 -0
  101. package/lib/webrtc/src/polyfill/globals.js +3 -0
  102. package/lib/webrtc/src/polyfill/globals.js.map +1 -0
  103. package/lib/webrtc/src/polyfill/index.d.ts +2 -0
  104. package/lib/webrtc/src/polyfill/index.js +19 -0
  105. package/lib/webrtc/src/polyfill/index.js.map +1 -0
  106. package/lib/webrtc/src/polyfill/install.d.ts +13 -0
  107. package/lib/webrtc/src/polyfill/install.js +266 -0
  108. package/lib/webrtc/src/polyfill/install.js.map +1 -0
  109. package/lib/webrtc/src/polyfill/mediaDevices.d.ts +38 -0
  110. package/lib/webrtc/src/polyfill/mediaDevices.js +228 -0
  111. package/lib/webrtc/src/polyfill/mediaDevices.js.map +1 -0
  112. package/lib/webrtc/src/polyfill/mediaRegister.d.ts +51 -0
  113. package/lib/webrtc/src/polyfill/mediaRegister.js +14 -0
  114. package/lib/webrtc/src/polyfill/mediaRegister.js.map +1 -0
  115. package/lib/webrtc/src/polyfill/peerConnectionConfig.d.ts +3 -0
  116. package/lib/webrtc/src/polyfill/peerConnectionConfig.js +52 -0
  117. package/lib/webrtc/src/polyfill/peerConnectionConfig.js.map +1 -0
  118. package/lib/webrtc/src/polyfill/registers/callback.d.ts +16 -0
  119. package/lib/webrtc/src/polyfill/registers/callback.js +57 -0
  120. package/lib/webrtc/src/polyfill/registers/callback.js.map +1 -0
  121. package/lib/webrtc/src/polyfill/registers/empty.d.ts +2 -0
  122. package/lib/webrtc/src/polyfill/registers/empty.js +28 -0
  123. package/lib/webrtc/src/polyfill/registers/empty.js.map +1 -0
  124. package/lib/webrtc/src/polyfill/registers/mp4Webm.d.ts +36 -0
  125. package/lib/webrtc/src/polyfill/registers/mp4Webm.js +242 -0
  126. package/lib/webrtc/src/polyfill/registers/mp4Webm.js.map +1 -0
  127. package/lib/webrtc/src/polyfill/registers/rtpRtcp.d.ts +17 -0
  128. package/lib/webrtc/src/polyfill/registers/rtpRtcp.js +143 -0
  129. package/lib/webrtc/src/polyfill/registers/rtpRtcp.js.map +1 -0
  130. package/lib/webrtc/src/polyfill/rtcSessionDescription.d.ts +18 -0
  131. package/lib/webrtc/src/polyfill/rtcSessionDescription.js +36 -0
  132. package/lib/webrtc/src/polyfill/rtcSessionDescription.js.map +1 -0
  133. package/lib/webrtc/src/polyfill/rtpCodec.d.ts +17 -0
  134. package/lib/webrtc/src/polyfill/rtpCodec.js +93 -0
  135. package/lib/webrtc/src/polyfill/rtpCodec.js.map +1 -0
  136. package/lib/webrtc/src/polyfill/selectSettings.d.ts +31 -0
  137. package/lib/webrtc/src/polyfill/selectSettings.js +156 -0
  138. package/lib/webrtc/src/polyfill/selectSettings.js.map +1 -0
  139. package/lib/webrtc/src/polyfill/sourceIo.d.ts +21 -0
  140. package/lib/webrtc/src/polyfill/sourceIo.js +298 -0
  141. package/lib/webrtc/src/polyfill/sourceIo.js.map +1 -0
  142. package/lib/webrtc/src/polyfill/trackStop.d.ts +4 -0
  143. package/lib/webrtc/src/polyfill/trackStop.js +16 -0
  144. package/lib/webrtc/src/polyfill/trackStop.js.map +1 -0
  145. package/lib/webrtc/src/sctpManager.d.ts +2 -1
  146. package/lib/webrtc/src/sctpManager.js +5 -2
  147. package/lib/webrtc/src/sctpManager.js.map +1 -1
  148. package/lib/webrtc/src/sdp.d.ts +1 -1
  149. package/lib/webrtc/src/sdp.js +5 -6
  150. package/lib/webrtc/src/sdp.js.map +1 -1
  151. package/lib/webrtc/src/sdpManager.d.ts +38 -1
  152. package/lib/webrtc/src/sdpManager.js +236 -30
  153. package/lib/webrtc/src/sdpManager.js.map +1 -1
  154. package/lib/webrtc/src/secureTransportManager.d.ts +43 -12
  155. package/lib/webrtc/src/secureTransportManager.js +123 -47
  156. package/lib/webrtc/src/secureTransportManager.js.map +1 -1
  157. package/lib/webrtc/src/transceiverManager.d.ts +91 -6
  158. package/lib/webrtc/src/transceiverManager.js +477 -62
  159. package/lib/webrtc/src/transceiverManager.js.map +1 -1
  160. package/lib/webrtc/src/transport/ice.d.ts +8 -0
  161. package/lib/webrtc/src/transport/ice.js +17 -1
  162. package/lib/webrtc/src/transport/ice.js.map +1 -1
  163. package/lib/webrtc/src/transport/sctp.d.ts +3 -2
  164. package/lib/webrtc/src/transport/sctp.js +8 -2
  165. package/lib/webrtc/src/transport/sctp.js.map +1 -1
  166. package/package.json +13 -3
@@ -26,7 +26,17 @@ import type { Kind } from "../types/domain";
26
26
  import type { RTCRtpCodecParameters, RTCRtpSendParameters } from "./parameters";
27
27
  import { SenderBandwidthEstimator } from "./sender/senderBWE";
28
28
  import { type RTCStats, type RTCStatsReport } from "./stats";
29
- import type { MediaStream, MediaStreamTrack } from "./track";
29
+ import { type MediaStream, type MediaStreamTrack } from "./track";
30
+ export type PendingRtpOptions = {
31
+ /** Queue RTP until DTLS is connected and a codec is set. Default true when this object is passed. */
32
+ enabled?: boolean;
33
+ /** Max queued packets when enabled. Oldest packets are dropped. Default 256. */
34
+ maxLength?: number;
35
+ };
36
+ export type RTCRtpSenderOptions = {
37
+ /** Pending RTP cache. Disabled by default. Pass `true` or `{ maxLength }` to enable. */
38
+ pendingRtp?: boolean | PendingRtpOptions;
39
+ };
30
40
  export declare class RTCRtpSender {
31
41
  trackOrKind: Kind | MediaStreamTrack;
32
42
  readonly type = "sender";
@@ -73,8 +83,16 @@ export declare class RTCRtpSender {
73
83
  private timestamp?;
74
84
  private timestampOffset;
75
85
  private seqOffset;
86
+ private rtpContinuityPending;
87
+ private pendingTimestampStep;
76
88
  private rtpCache;
89
+ private pendingRtp;
90
+ private drainingPendingRtp;
91
+ private readonly pendingRtpEnabled;
92
+ private readonly pendingRtpMaxLength;
77
93
  codec?: RTCRtpCodecParameters;
94
+ private negotiatedCodecs;
95
+ private sendPrimaryCodec?;
78
96
  dtlsTransport: RTCDtlsTransport;
79
97
  private dtlsDisposer;
80
98
  track: MediaStreamTrack | null;
@@ -82,7 +100,7 @@ export declare class RTCRtpSender {
82
100
  stopped: boolean;
83
101
  rtcpRunning: boolean;
84
102
  private rtcpCancel;
85
- constructor(trackOrKind: Kind | MediaStreamTrack);
103
+ constructor(trackOrKind: Kind | MediaStreamTrack, options?: RTCRtpSenderOptions);
86
104
  get transport(): RTCDtlsTransport;
87
105
  get streamId(): string | undefined;
88
106
  set streamId(value: string | undefined);
@@ -90,14 +108,57 @@ export declare class RTCRtpSender {
90
108
  get redDistance(): number;
91
109
  set redDistance(n: number);
92
110
  prepareSend(params: RTCRtpSendParameters): void;
111
+ private canSendRtp;
112
+ private settlePendingRtp;
113
+ private discardPendingRtp;
114
+ private enqueuePendingRtp;
115
+ /**
116
+ * Send queued RTP once DTLS is connected and a codec is set. Used only when
117
+ * pending RTP is enabled: `writeRtp` / `sendRtp` may run before ICE/DTLS
118
+ * completes; dropping those packets would lose the start of a media source.
119
+ * `drainingPendingRtp` blocks re-entry while `dispatchRtp` is awaited;
120
+ * packets enqueued during that wait are drained by the recursive call after
121
+ * the flag is cleared.
122
+ */
123
+ private drainPendingRtp;
93
124
  registerTrack(track: MediaStreamTrack): void;
94
125
  setStreams(streams?: MediaStream[]): void;
95
126
  setSendEncodings(encodings?: Array<Record<string, unknown>>): void;
96
127
  replaceTrack(track: MediaStreamTrack | null): Promise<void>;
97
- stop(): void;
128
+ /**
129
+ * Detach the current track without stopping the sender (`removeTrack`).
130
+ * The same sender can resume sending after `replaceTrack(track)`.
131
+ */
132
+ detachTrack(): void;
133
+ stop({ keepTrack }?: {
134
+ keepTrack?: boolean;
135
+ }): void;
98
136
  runRtcp(): Promise<void>;
99
- replaceRTP({ sequenceNumber, timestamp, }: Pick<RtpHeader, "sequenceNumber" | "timestamp">, discontinuity?: boolean): void;
137
+ /**
138
+ * Send an RTP packet. Pending RTP is disabled by default: the packet is
139
+ * written immediately if DTLS is connected and a codec is set, otherwise
140
+ * dropped.
141
+ *
142
+ * When pending RTP is enabled, the packet is queued until it can be sent.
143
+ * The returned promise then settles for this packet only:
144
+ * - resolve: DTLS send completed, or the packet was dropped by `stop()`,
145
+ * `replaceTrack(null)`, or pending-queue overflow
146
+ * - reject: DTLS send threw while writing this packet
147
+ *
148
+ * Later `sendRtp()` calls stay pending until their own packet is sent or
149
+ * dropped, even if a drain is already in progress.
150
+ */
100
151
  sendRtp(rtp: Buffer | RtpPacket): Promise<void>;
152
+ /**
153
+ * Schedule RTP continuity rewrite for the next packet that is actually sent.
154
+ * The header argument is kept for API compatibility and is not used to compute
155
+ * offsets. `discontinuity` does not change sequence or timestamp mapping.
156
+ * `timestampStep` (default 1) is the only way to choose the timestamp increment
157
+ * at the source-switch boundary.
158
+ */
159
+ replaceRTP(header: Pick<RtpHeader, "sequenceNumber" | "timestamp">, discontinuity?: boolean, timestampStep?: number): void;
160
+ private scheduleRtpContinuity;
161
+ private dispatchRtp;
101
162
  handleRtcpPacket(rtcpPacket: RtcpPacket): void;
102
163
  getParameters(): {
103
164
  encodings: {
@@ -27,15 +27,39 @@ const crypto_2 = require("crypto");
27
27
  const promises_1 = require("timers/promises");
28
28
  const common_1 = require("../imports/common");
29
29
  const __1 = require("..");
30
+ const errors_1 = require("../errors");
30
31
  const rtp_1 = require("../imports/rtp");
31
32
  const utils_1 = require("../utils");
33
+ const codecCompatibility_1 = require("./codecCompatibility");
32
34
  const senderBWE_1 = require("./sender/senderBWE");
33
35
  const stats_1 = require("./stats");
36
+ const track_1 = require("./track");
34
37
  const log = (0, rtp_1.debug)("werift:packages/webrtc/src/media/rtpSender.ts");
35
38
  const RTP_HISTORY_SIZE = 128;
39
+ const DEFAULT_PENDING_RTP_MAX_LENGTH = 256;
36
40
  const RTT_ALPHA = 0.85;
41
+ function resolvePendingRtpOptions(pendingRtp) {
42
+ if (pendingRtp === true) {
43
+ return { enabled: true, maxLength: DEFAULT_PENDING_RTP_MAX_LENGTH };
44
+ }
45
+ if (pendingRtp == undefined || pendingRtp === false) {
46
+ return { enabled: false, maxLength: DEFAULT_PENDING_RTP_MAX_LENGTH };
47
+ }
48
+ const maxLength = typeof pendingRtp.maxLength === "number" &&
49
+ Number.isFinite(pendingRtp.maxLength) &&
50
+ pendingRtp.maxLength >= 1
51
+ ? Math.floor(pendingRtp.maxLength)
52
+ : DEFAULT_PENDING_RTP_MAX_LENGTH;
53
+ return { enabled: pendingRtp.enabled ?? true, maxLength };
54
+ }
55
+ function freezeRtpContinuityOffsets(lastOutputSeq, lastOutputTimestamp, firstInputSeq, firstInputTimestamp, timestampStep) {
56
+ return {
57
+ seqOffset: (0, common_1.uint16Add)((0, common_1.uint16Add)(lastOutputSeq, 1), -firstInputSeq),
58
+ timestampOffset: (0, common_1.uint32Add)((0, common_1.uint32Add)(lastOutputTimestamp, timestampStep), -firstInputTimestamp),
59
+ };
60
+ }
37
61
  class RTCRtpSender {
38
- constructor(trackOrKind) {
62
+ constructor(trackOrKind, options = {}) {
39
63
  Object.defineProperty(this, "trackOrKind", {
40
64
  enumerable: true,
41
65
  configurable: true,
@@ -308,18 +332,66 @@ class RTCRtpSender {
308
332
  writable: true,
309
333
  value: 0
310
334
  });
335
+ Object.defineProperty(this, "rtpContinuityPending", {
336
+ enumerable: true,
337
+ configurable: true,
338
+ writable: true,
339
+ value: false
340
+ });
341
+ Object.defineProperty(this, "pendingTimestampStep", {
342
+ enumerable: true,
343
+ configurable: true,
344
+ writable: true,
345
+ value: 1
346
+ });
311
347
  Object.defineProperty(this, "rtpCache", {
312
348
  enumerable: true,
313
349
  configurable: true,
314
350
  writable: true,
315
351
  value: []
316
352
  });
353
+ Object.defineProperty(this, "pendingRtp", {
354
+ enumerable: true,
355
+ configurable: true,
356
+ writable: true,
357
+ value: []
358
+ });
359
+ Object.defineProperty(this, "drainingPendingRtp", {
360
+ enumerable: true,
361
+ configurable: true,
362
+ writable: true,
363
+ value: false
364
+ });
365
+ Object.defineProperty(this, "pendingRtpEnabled", {
366
+ enumerable: true,
367
+ configurable: true,
368
+ writable: true,
369
+ value: void 0
370
+ });
371
+ Object.defineProperty(this, "pendingRtpMaxLength", {
372
+ enumerable: true,
373
+ configurable: true,
374
+ writable: true,
375
+ value: void 0
376
+ });
317
377
  Object.defineProperty(this, "codec", {
318
378
  enumerable: true,
319
379
  configurable: true,
320
380
  writable: true,
321
381
  value: void 0
322
382
  });
383
+ Object.defineProperty(this, "negotiatedCodecs", {
384
+ enumerable: true,
385
+ configurable: true,
386
+ writable: true,
387
+ value: []
388
+ });
389
+ Object.defineProperty(this, "sendPrimaryCodec", {
390
+ enumerable: true,
391
+ configurable: true,
392
+ writable: true,
393
+ value: void 0
394
+ });
323
395
  Object.defineProperty(this, "dtlsTransport", {
324
396
  enumerable: true,
325
397
  configurable: true,
@@ -362,6 +434,9 @@ class RTCRtpSender {
362
434
  writable: true,
363
435
  value: new AbortController()
364
436
  });
437
+ const pendingRtp = resolvePendingRtpOptions(options.pendingRtp);
438
+ this.pendingRtpEnabled = pendingRtp.enabled;
439
+ this.pendingRtpMaxLength = pendingRtp.maxLength;
365
440
  this.kind =
366
441
  typeof this.trackOrKind === "string"
367
442
  ? this.trackOrKind
@@ -388,6 +463,7 @@ class RTCRtpSender {
388
463
  this.dtlsTransport.onStateChange.subscribe((state) => {
389
464
  if (state === "connected") {
390
465
  this.onReady.execute();
466
+ void this.drainPendingRtp();
391
467
  }
392
468
  }).unSubscribe,
393
469
  ];
@@ -403,41 +479,124 @@ class RTCRtpSender {
403
479
  this.cname = params.rtcp?.cname;
404
480
  this.mid = params.muxId;
405
481
  this.headerExtensions = params.headerExtensions;
406
- this.rtpStreamId = params.rtpStreamId;
482
+ this.rtpStreamId = params.rtpStreamId ?? this.rtpStreamId;
407
483
  this.repairedRtpStreamId = params.repairedRtpStreamId;
408
- this.codec = params.codecs[0];
484
+ this.negotiatedCodecs = [...params.codecs];
485
+ this.codec =
486
+ params.codecs.find((codec) => codec.name.toLowerCase() !== "rtx") ??
487
+ params.codecs[0];
488
+ const redPrimaryPayloadType = this.codec?.name.toLowerCase() === "red"
489
+ ? Number((this.codec.parameters ?? "").split("/")[0])
490
+ : undefined;
491
+ this.sendPrimaryCodec =
492
+ this.codec?.name.toLowerCase() === "red"
493
+ ? params.codecs.find((codec) => !["red", "rtx"].includes(codec.name.toLowerCase()) &&
494
+ codec.payloadType === redPrimaryPayloadType)
495
+ : this.codec;
409
496
  if (this.track) {
410
497
  this.track.codec = this.codec;
411
498
  }
499
+ this.rtxPayloadType = undefined;
500
+ this.redRedundantPayloadType = undefined;
412
501
  params.codecs.forEach((codec) => {
413
502
  const codecParams = (0, __1.codecParametersFromString)(codec.parameters ?? "");
414
503
  if (codec.name.toLowerCase() === "rtx" &&
415
- codecParams["apt"] === this.codec?.payloadType) {
504
+ this.sendPrimaryCodec?.payloadType === codecParams["apt"]) {
416
505
  this.rtxPayloadType = codec.payloadType;
417
506
  }
418
- if (codec.name.toLowerCase() === "red") {
419
- this.redRedundantPayloadType = Number((codec.parameters ?? "").split("/")[0]);
420
- }
421
507
  });
508
+ if (this.codec?.name.toLowerCase() === "red" &&
509
+ this.sendPrimaryCodec != undefined) {
510
+ this.redRedundantPayloadType = this.sendPrimaryCodec.payloadType;
511
+ }
512
+ void this.drainPendingRtp();
513
+ }
514
+ canSendRtp() {
515
+ return (!this.stopped && this.dtlsTransport?.state === "connected" && !!this.codec);
516
+ }
517
+ settlePendingRtp(item, error) {
518
+ if (error == undefined) {
519
+ item.resolve();
520
+ return;
521
+ }
522
+ item.reject(error);
523
+ }
524
+ discardPendingRtp() {
525
+ const dropped = this.pendingRtp.splice(0);
526
+ for (const item of dropped) {
527
+ this.settlePendingRtp(item);
528
+ }
529
+ }
530
+ enqueuePendingRtp(rtp, resolve, reject) {
531
+ const packet = Buffer.isBuffer(rtp)
532
+ ? rtp_1.RtpPacket.deSerialize(rtp)
533
+ : rtp.clone();
534
+ this.pendingRtp.push({ packet, resolve, reject });
535
+ while (this.pendingRtp.length > this.pendingRtpMaxLength) {
536
+ const dropped = this.pendingRtp.shift();
537
+ if (dropped) {
538
+ this.settlePendingRtp(dropped);
539
+ }
540
+ }
541
+ }
542
+ /**
543
+ * Send queued RTP once DTLS is connected and a codec is set. Used only when
544
+ * pending RTP is enabled: `writeRtp` / `sendRtp` may run before ICE/DTLS
545
+ * completes; dropping those packets would lose the start of a media source.
546
+ * `drainingPendingRtp` blocks re-entry while `dispatchRtp` is awaited;
547
+ * packets enqueued during that wait are drained by the recursive call after
548
+ * the flag is cleared.
549
+ */
550
+ async drainPendingRtp() {
551
+ if (this.drainingPendingRtp) {
552
+ return;
553
+ }
554
+ this.drainingPendingRtp = true;
555
+ try {
556
+ while (this.pendingRtp.length > 0 && this.canSendRtp()) {
557
+ const item = this.pendingRtp.shift();
558
+ if (!this.canSendRtp()) {
559
+ this.settlePendingRtp(item);
560
+ continue;
561
+ }
562
+ try {
563
+ await this.dispatchRtp(item.packet);
564
+ this.settlePendingRtp(item);
565
+ }
566
+ catch (error) {
567
+ this.settlePendingRtp(item, error);
568
+ }
569
+ }
570
+ }
571
+ finally {
572
+ this.drainingPendingRtp = false;
573
+ }
574
+ if (this.pendingRtp.length > 0 && this.canSendRtp()) {
575
+ await this.drainPendingRtp();
576
+ }
422
577
  }
423
578
  registerTrack(track) {
424
579
  if (track.stopped)
425
580
  throw new Error("track is ended");
581
+ (0, track_1.captureTrackSourceCodecs)(track);
426
582
  if (this.disposeTrack) {
427
583
  this.disposeTrack();
428
584
  }
429
585
  track.id = this.trackId;
430
- const { unSubscribe } = track.onReceiveRtp.subscribe(async (rtp) => {
586
+ const { unSubscribe: unSubscribeRtp } = track.onReceiveRtp.subscribe(async (rtp) => {
431
587
  await this.sendRtp(rtp);
432
588
  });
589
+ const { unSubscribe: unSubscribeSourceChanged } = track.onSourceChanged.subscribe((header) => {
590
+ this.replaceRTP(header);
591
+ });
433
592
  this.track = track;
434
- this.disposeTrack = unSubscribe;
593
+ this.disposeTrack = () => {
594
+ unSubscribeRtp();
595
+ unSubscribeSourceChanged();
596
+ };
435
597
  if (this.codec) {
436
598
  track.codec = this.codec;
437
599
  }
438
- track.onSourceChanged.subscribe((header) => {
439
- this.replaceRTP(header);
440
- });
441
600
  }
442
601
  setStreams(streams = []) {
443
602
  this.streamIds = [...new Set(streams.map((stream) => stream.id))];
@@ -447,32 +606,57 @@ class RTCRtpSender {
447
606
  encodings.length > 0
448
607
  ? encodings.map((encoding) => ({ ...encoding }))
449
608
  : [{}];
609
+ const rid = this.sendEncodings.find((encoding) => typeof encoding.rid === "string")?.rid;
610
+ if (typeof rid === "string") {
611
+ this.rtpStreamId = rid;
612
+ }
450
613
  }
451
614
  async replaceTrack(track) {
452
615
  if (track === null) {
453
- if (this.disposeTrack) {
454
- this.disposeTrack();
455
- }
456
- this.track = null;
616
+ this.detachTrack();
457
617
  return;
458
618
  }
459
619
  if (track.stopped)
460
620
  throw new Error("track is ended");
621
+ (0, track_1.captureTrackSourceCodecs)(track);
622
+ const sourceCodecs = (0, track_1.getTrackSourceCodecs)(track);
623
+ if (this.sendPrimaryCodec != undefined &&
624
+ sourceCodecs != undefined &&
625
+ !sourceCodecs.some((source) => (0, codecCompatibility_1.isCodecCompatible)(source, this.sendPrimaryCodec))) {
626
+ throw (0, errors_1.createWebRtcDomException)("InvalidModificationError", `Track codec ${sourceCodecs.map((codec) => codec.mimeType).join(", ")} is incompatible with negotiated codecs ${this.negotiatedCodecs.map((codec) => codec.mimeType).join(", ")}`);
627
+ }
461
628
  if (this.sequenceNumber != undefined) {
462
- const header = track.header || (await track.onReceiveRtp.asPromise())[0].header;
463
- this.replaceRTP(header);
629
+ this.scheduleRtpContinuity();
464
630
  }
465
631
  this.registerTrack(track);
466
632
  log("replaceTrack", "ssrc", track.ssrc, "rid", track.rid);
467
633
  }
468
- stop() {
634
+ /**
635
+ * Detach the current track without stopping the sender (`removeTrack`).
636
+ * The same sender can resume sending after `replaceTrack(track)`.
637
+ */
638
+ detachTrack() {
639
+ this.rtpContinuityPending = false;
640
+ this.discardPendingRtp();
641
+ if (this.disposeTrack) {
642
+ this.disposeTrack();
643
+ this.disposeTrack = undefined;
644
+ }
645
+ this.track = null;
646
+ }
647
+ stop({ keepTrack = false } = {}) {
469
648
  this.stopped = true;
649
+ this.rtpContinuityPending = false;
650
+ this.discardPendingRtp();
470
651
  this.rtcpRunning = false;
471
652
  this.rtcpCancel.abort();
472
653
  if (this.disposeTrack) {
473
654
  this.disposeTrack();
655
+ this.disposeTrack = undefined;
656
+ }
657
+ if (!keepTrack) {
658
+ this.track = null;
474
659
  }
475
- this.track = null;
476
660
  }
477
661
  async runRtcp() {
478
662
  if (this.rtcpRunning || this.stopped)
@@ -519,30 +703,75 @@ class RTCRtpSender {
519
703
  }
520
704
  catch (error) { }
521
705
  }
522
- replaceRTP({ sequenceNumber, timestamp, }, discontinuity = false) {
523
- if (this.sequenceNumber != undefined) {
524
- this.seqOffset = (0, common_1.uint16Add)(this.sequenceNumber, -sequenceNumber);
525
- if (discontinuity) {
526
- this.seqOffset = (0, common_1.uint16Add)(this.seqOffset, 2);
527
- }
706
+ /**
707
+ * Send an RTP packet. Pending RTP is disabled by default: the packet is
708
+ * written immediately if DTLS is connected and a codec is set, otherwise
709
+ * dropped.
710
+ *
711
+ * When pending RTP is enabled, the packet is queued until it can be sent.
712
+ * The returned promise then settles for this packet only:
713
+ * - resolve: DTLS send completed, or the packet was dropped by `stop()`,
714
+ * `replaceTrack(null)`, or pending-queue overflow
715
+ * - reject: DTLS send threw while writing this packet
716
+ *
717
+ * Later `sendRtp()` calls stay pending until their own packet is sent or
718
+ * dropped, even if a drain is already in progress.
719
+ */
720
+ async sendRtp(rtp) {
721
+ if (this.stopped) {
722
+ return;
528
723
  }
529
- if (this.timestamp != undefined) {
530
- this.timestampOffset = (0, common_1.uint32Add)(this.timestamp, -timestamp);
531
- if (discontinuity) {
532
- this.timestampOffset = (0, common_1.uint16Add)(this.timestampOffset, 1);
724
+ if (!this.pendingRtpEnabled) {
725
+ if (!this.canSendRtp()) {
726
+ return;
533
727
  }
728
+ await this.dispatchRtp(rtp);
729
+ return;
534
730
  }
731
+ await new Promise((resolve, reject) => {
732
+ this.enqueuePendingRtp(rtp, resolve, reject);
733
+ void this.drainPendingRtp();
734
+ });
735
+ }
736
+ /**
737
+ * Schedule RTP continuity rewrite for the next packet that is actually sent.
738
+ * The header argument is kept for API compatibility and is not used to compute
739
+ * offsets. `discontinuity` does not change sequence or timestamp mapping.
740
+ * `timestampStep` (default 1) is the only way to choose the timestamp increment
741
+ * at the source-switch boundary.
742
+ */
743
+ replaceRTP(header, discontinuity = false, timestampStep = 1) {
744
+ this.scheduleRtpContinuity(timestampStep);
745
+ log("replaceRTP", this.sequenceNumber, header.sequenceNumber, discontinuity, timestampStep);
746
+ }
747
+ scheduleRtpContinuity(timestampStep = 1) {
748
+ this.rtpContinuityPending = true;
749
+ this.pendingTimestampStep = timestampStep;
535
750
  this.rtpCache = [];
536
- log("replaceRTP", this.sequenceNumber, sequenceNumber, this.seqOffset);
537
751
  }
538
- async sendRtp(rtp) {
539
- if (this.dtlsTransport.state !== "connected" || !this.codec) {
752
+ async dispatchRtp(rtp) {
753
+ if (!this.canSendRtp()) {
754
+ return;
755
+ }
756
+ const codec = this.codec;
757
+ if (!codec) {
540
758
  return;
541
759
  }
542
- rtp = Buffer.isBuffer(rtp) ? rtp_1.RtpPacket.deSerialize(rtp) : rtp;
760
+ rtp = Buffer.isBuffer(rtp) ? rtp_1.RtpPacket.deSerialize(rtp) : rtp.clone();
543
761
  const { header, payload } = rtp;
762
+ const inputSequenceNumber = header.sequenceNumber;
763
+ const inputTimestamp = header.timestamp;
764
+ if (this.rtpContinuityPending) {
765
+ if (this.sequenceNumber != undefined && this.timestamp != undefined) {
766
+ const offsets = freezeRtpContinuityOffsets(this.sequenceNumber, this.timestamp, inputSequenceNumber, inputTimestamp, this.pendingTimestampStep);
767
+ this.seqOffset = offsets.seqOffset;
768
+ this.timestampOffset = offsets.timestampOffset;
769
+ }
770
+ this.rtpContinuityPending = false;
771
+ this.pendingTimestampStep = 1;
772
+ }
544
773
  header.ssrc = this.ssrc;
545
- header.payloadType = this.codec.payloadType;
774
+ header.payloadType = codec.payloadType;
546
775
  header.timestamp = (0, common_1.uint32Add)(header.timestamp, this.timestampOffset);
547
776
  header.sequenceNumber = (0, common_1.uint16Add)(header.sequenceNumber, this.seqOffset);
548
777
  this.timestamp = header.timestamp;