mediasoup-client 3.24.1 → 3.24.3

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.
@@ -244,109 +244,128 @@ class Safari12 extends enhancedEvents_1.EnhancedEventEmitter {
244
244
  streams: [this._sendStream],
245
245
  sendEncodings: encodings,
246
246
  });
247
- if (this._forcedRtpExtensions) {
248
- ortcUtils.applyForcedRtpExtensions(transceiver, this._forcedRtpExtensions);
249
- }
250
- if (onRtpSender) {
251
- onRtpSender(transceiver.sender);
252
- }
253
- let offer = await this._pc.createOffer();
254
- let localSdpObject = sdpTransform.parse(offer.sdp);
255
- if (localSdpObject.extmapAllowMixed) {
256
- this._remoteSdp.setSessionExtmapAllowMixed();
257
- }
258
- const nativeRtpCapabilities = Safari12.getLocalRtpCapabilities(localSdpObject);
259
- const sendExtendedRtpCapabilities = this._getSendExtendedRtpCapabilities(nativeRtpCapabilities);
260
- // Generic sending RTP parameters.
261
- const sendingRtpParameters = ortc.getSendingRtpParameters(track.kind, sendExtendedRtpCapabilities);
262
- // This may throw.
263
- sendingRtpParameters.codecs = ortc.reduceCodecs(sendingRtpParameters.codecs, codec);
264
- // Generic sending RTP parameters suitable for the SDP remote answer.
265
- const sendingRemoteRtpParameters = ortc.getSendingRemoteRtpParameters(track.kind, sendExtendedRtpCapabilities);
266
- // This may throw.
267
- sendingRemoteRtpParameters.codecs = ortc.reduceCodecs(sendingRemoteRtpParameters.codecs, codec);
268
- let offerMediaObject;
269
- if (!this._transportReady) {
270
- await this.setupTransport({
271
- localDtlsRole: this._forcedLocalDtlsRole ?? 'client',
272
- localSdpObject,
273
- });
274
- }
275
- const layers = (0, scalabilityModes_1.parse)((encodings ?? [{}])[0].scalabilityMode);
276
- if (encodings && encodings.length > 1) {
277
- logger.debug('send() | enabling legacy simulcast');
278
- localSdpObject = sdpTransform.parse(offer.sdp);
247
+ try {
248
+ if (this._forcedRtpExtensions) {
249
+ ortcUtils.applyForcedRtpExtensions(transceiver, this._forcedRtpExtensions);
250
+ }
251
+ if (onRtpSender) {
252
+ onRtpSender(transceiver.sender);
253
+ }
254
+ let offer = await this._pc.createOffer();
255
+ let localSdpObject = sdpTransform.parse(offer.sdp);
256
+ if (localSdpObject.extmapAllowMixed) {
257
+ this._remoteSdp.setSessionExtmapAllowMixed();
258
+ }
259
+ const nativeRtpCapabilities = Safari12.getLocalRtpCapabilities(localSdpObject);
260
+ const sendExtendedRtpCapabilities = this._getSendExtendedRtpCapabilities(nativeRtpCapabilities);
261
+ // Generic sending RTP parameters.
262
+ const sendingRtpParameters = ortc.getSendingRtpParameters(track.kind, sendExtendedRtpCapabilities);
263
+ // This may throw.
264
+ sendingRtpParameters.codecs = ortc.reduceCodecs(sendingRtpParameters.codecs, codec);
265
+ // Generic sending RTP parameters suitable for the SDP remote answer.
266
+ const sendingRemoteRtpParameters = ortc.getSendingRemoteRtpParameters(track.kind, sendExtendedRtpCapabilities);
267
+ // This may throw.
268
+ sendingRemoteRtpParameters.codecs = ortc.reduceCodecs(sendingRemoteRtpParameters.codecs, codec);
269
+ let offerMediaObject;
270
+ if (!this._transportReady) {
271
+ await this.setupTransport({
272
+ localDtlsRole: this._forcedLocalDtlsRole ?? 'client',
273
+ localSdpObject,
274
+ });
275
+ }
276
+ const layers = (0, scalabilityModes_1.parse)((encodings ?? [{}])[0].scalabilityMode);
277
+ if (encodings && encodings.length > 1) {
278
+ logger.debug('send() | enabling legacy simulcast');
279
+ localSdpObject = sdpTransform.parse(offer.sdp);
280
+ offerMediaObject = localSdpObject.media[mediaSectionIdx.idx];
281
+ sdpUnifiedPlanUtils.addLegacySimulcast({
282
+ offerMediaObject,
283
+ numStreams: encodings.length,
284
+ });
285
+ offer = {
286
+ type: 'offer',
287
+ sdp: sdpTransform.write(localSdpObject),
288
+ };
289
+ }
290
+ logger.debug('send() | calling pc.setLocalDescription() [offer:%o]', offer);
291
+ await this._pc.setLocalDescription(offer);
292
+ // We can now get the transceiver.mid.
293
+ const localId = transceiver.mid;
294
+ // Set MID.
295
+ sendingRtpParameters.mid = localId;
296
+ localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp);
279
297
  offerMediaObject = localSdpObject.media[mediaSectionIdx.idx];
280
- sdpUnifiedPlanUtils.addLegacySimulcast({
298
+ // Set RTCP CNAME.
299
+ sendingRtpParameters.rtcp.cname = sdpCommonUtils.getCname({
281
300
  offerMediaObject,
282
- numStreams: encodings.length,
283
301
  });
284
- offer = {
285
- type: 'offer',
286
- sdp: sdpTransform.write(localSdpObject),
287
- };
288
- }
289
- logger.debug('send() | calling pc.setLocalDescription() [offer:%o]', offer);
290
- await this._pc.setLocalDescription(offer);
291
- // We can now get the transceiver.mid.
292
- const localId = transceiver.mid;
293
- // Set MID.
294
- sendingRtpParameters.mid = localId;
295
- localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp);
296
- offerMediaObject = localSdpObject.media[mediaSectionIdx.idx];
297
- // Set RTCP CNAME.
298
- sendingRtpParameters.rtcp.cname = sdpCommonUtils.getCname({
299
- offerMediaObject,
300
- });
301
- // Set msid.
302
- sendingRtpParameters.msid = `${streamId ?? this._sendStream.id} ${track.id}`;
303
- // Set RTP encodings.
304
- sendingRtpParameters.encodings = sdpUnifiedPlanUtils.getRtpEncodings({
305
- offerMediaObject,
306
- codecs: sendingRtpParameters.codecs,
307
- });
308
- // Complete encodings with given values.
309
- if (encodings) {
310
- for (let idx = 0; idx < sendingRtpParameters.encodings.length; ++idx) {
311
- if (encodings[idx]) {
312
- Object.assign(sendingRtpParameters.encodings[idx], encodings[idx]);
302
+ // Set msid.
303
+ sendingRtpParameters.msid = `${streamId ?? this._sendStream.id} ${track.id}`;
304
+ // Set RTP encodings.
305
+ sendingRtpParameters.encodings = sdpUnifiedPlanUtils.getRtpEncodings({
306
+ offerMediaObject,
307
+ codecs: sendingRtpParameters.codecs,
308
+ });
309
+ // Complete encodings with given values.
310
+ if (encodings) {
311
+ for (let idx = 0; idx < sendingRtpParameters.encodings.length; ++idx) {
312
+ if (encodings[idx]) {
313
+ Object.assign(sendingRtpParameters.encodings[idx], encodings[idx]);
314
+ }
313
315
  }
314
316
  }
315
- }
316
- // If VP8 or H264 and there is effective simulcast, add scalabilityMode to
317
- // each encoding.
318
- if (sendingRtpParameters.encodings.length > 1 &&
319
- (sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/vp8' ||
320
- sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/h264')) {
321
- for (const encoding of sendingRtpParameters.encodings) {
322
- if (encoding.scalabilityMode) {
323
- encoding.scalabilityMode = `L1T${layers.temporalLayers}`;
317
+ // If VP8 or H264 and there is effective simulcast, add scalabilityMode to
318
+ // each encoding.
319
+ if (sendingRtpParameters.encodings.length > 1 &&
320
+ (sendingRtpParameters.codecs[0].mimeType.toLowerCase() ===
321
+ 'video/vp8' ||
322
+ sendingRtpParameters.codecs[0].mimeType.toLowerCase() ===
323
+ 'video/h264')) {
324
+ for (const encoding of sendingRtpParameters.encodings) {
325
+ if (encoding.scalabilityMode) {
326
+ encoding.scalabilityMode = `L1T${layers.temporalLayers}`;
327
+ }
328
+ else {
329
+ encoding.scalabilityMode = 'L1T3';
330
+ }
324
331
  }
325
- else {
326
- encoding.scalabilityMode = 'L1T3';
332
+ }
333
+ this._remoteSdp.send({
334
+ offerMediaObject,
335
+ reuseMid: mediaSectionIdx.reuseMid,
336
+ offerRtpParameters: sendingRtpParameters,
337
+ answerRtpParameters: sendingRemoteRtpParameters,
338
+ codecOptions,
339
+ });
340
+ const answer = {
341
+ type: 'answer',
342
+ sdp: this._remoteSdp.getSdp(),
343
+ };
344
+ logger.debug('send() | calling pc.setRemoteDescription() [answer:%o]', answer);
345
+ await this._pc.setRemoteDescription(answer);
346
+ // Store in the map.
347
+ this._mapMidTransceiver.set(localId, transceiver);
348
+ return {
349
+ localId,
350
+ rtpParameters: sendingRtpParameters,
351
+ rtpSender: transceiver.sender,
352
+ };
353
+ }
354
+ catch (error) {
355
+ // Undo the changes done in the PeerConnection, otherwise it gets out of
356
+ // sync with the remote SDP and later calls to send() fail.
357
+ try {
358
+ if (this._pc.signalingState === 'have-local-offer') {
359
+ try {
360
+ await this._pc.setLocalDescription({ type: 'rollback' });
361
+ }
362
+ catch (error2) { }
327
363
  }
364
+ transceiver.stop();
328
365
  }
366
+ catch (error3) { }
367
+ throw error;
329
368
  }
330
- this._remoteSdp.send({
331
- offerMediaObject,
332
- reuseMid: mediaSectionIdx.reuseMid,
333
- offerRtpParameters: sendingRtpParameters,
334
- answerRtpParameters: sendingRemoteRtpParameters,
335
- codecOptions,
336
- });
337
- const answer = {
338
- type: 'answer',
339
- sdp: this._remoteSdp.getSdp(),
340
- };
341
- logger.debug('send() | calling pc.setRemoteDescription() [answer:%o]', answer);
342
- await this._pc.setRemoteDescription(answer);
343
- // Store in the map.
344
- this._mapMidTransceiver.set(localId, transceiver);
345
- return {
346
- localId,
347
- rtpParameters: sendingRtpParameters,
348
- rtpSender: transceiver.sender,
349
- };
350
369
  }
351
370
  async stopSending(localId) {
352
371
  this.assertSendDirection();
@@ -514,25 +533,43 @@ class Safari12 extends enhancedEvents_1.EnhancedEventEmitter {
514
533
  // If this is the first DataChannel we need to create the SDP answer with
515
534
  // m=application section.
516
535
  if (!this._hasDataChannelMediaSection) {
517
- const offer = await this._pc.createOffer();
518
- const localSdpObject = sdpTransform.parse(offer.sdp);
519
- const offerMediaObject = localSdpObject.media.find(m => m.type === 'application');
520
- if (!this._transportReady) {
521
- await this.setupTransport({
522
- localDtlsRole: this._forcedLocalDtlsRole ?? 'client',
523
- localSdpObject,
524
- });
536
+ try {
537
+ const offer = await this._pc.createOffer();
538
+ const localSdpObject = sdpTransform.parse(offer.sdp);
539
+ const offerMediaObject = localSdpObject.media.find(m => m.type === 'application');
540
+ if (!this._transportReady) {
541
+ await this.setupTransport({
542
+ localDtlsRole: this._forcedLocalDtlsRole ?? 'client',
543
+ localSdpObject,
544
+ });
545
+ }
546
+ logger.debug('sendDataChannel() | calling pc.setLocalDescription() [offer:%o]', offer);
547
+ await this._pc.setLocalDescription(offer);
548
+ this._remoteSdp.sendSctpAssociation({ offerMediaObject });
549
+ const answer = {
550
+ type: 'answer',
551
+ sdp: this._remoteSdp.getSdp(),
552
+ };
553
+ logger.debug('sendDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer);
554
+ await this._pc.setRemoteDescription(answer);
555
+ this._hasDataChannelMediaSection = true;
556
+ }
557
+ catch (error) {
558
+ // Undo the changes done in the PeerConnection, otherwise it gets out of
559
+ // sync with the remote SDP and later calls to send() and
560
+ // sendDataChannel() fail.
561
+ try {
562
+ if (this._pc.signalingState === 'have-local-offer') {
563
+ try {
564
+ await this._pc.setLocalDescription({ type: 'rollback' });
565
+ }
566
+ catch (error2) { }
567
+ }
568
+ dataChannel.close();
569
+ }
570
+ catch (error3) { }
571
+ throw error;
525
572
  }
526
- logger.debug('sendDataChannel() | calling pc.setLocalDescription() [offer:%o]', offer);
527
- await this._pc.setLocalDescription(offer);
528
- this._remoteSdp.sendSctpAssociation({ offerMediaObject });
529
- const answer = {
530
- type: 'answer',
531
- sdp: this._remoteSdp.getSdp(),
532
- };
533
- logger.debug('sendDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer);
534
- await this._pc.setRemoteDescription(answer);
535
- this._hasDataChannelMediaSection = true;
536
573
  }
537
574
  const newSctpStreamParameters = {
538
575
  streamId: options.id,
package/lib/index.d.ts CHANGED
@@ -6,7 +6,7 @@ export * as types from './types';
6
6
  /**
7
7
  * Expose mediasoup-client version.
8
8
  */
9
- export declare const version = "3.24.1";
9
+ export declare const version = "3.24.3";
10
10
  /**
11
11
  * Expose Device class and device detector helpers.
12
12
  */
package/lib/index.js CHANGED
@@ -46,7 +46,7 @@ exports.types = __importStar(require("./types"));
46
46
  /**
47
47
  * Expose mediasoup-client version.
48
48
  */
49
- exports.version = '3.24.1';
49
+ exports.version = '3.24.3';
50
50
  /**
51
51
  * Expose Device class and device detector helpers.
52
52
  */
package/lib/ortc.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ortc.d.ts","sourceRoot":"","sources":["../src/ortc.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACX,eAAe,EACf,SAAS,EACT,kBAAkB,EAElB,aAAa,EACb,kBAAkB,EAKlB,uBAAuB,EAGvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAO7D;;;;GAIG;AACH,wBAAgB,mCAAmC,CAClD,IAAI,EAAE,eAAe,GACnB,IAAI,CA0BN;AAED;;;;GAIG;AACH,wBAAgB,iCAAiC,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAiD7E;AAED;;;;GAIG;AACH,wBAAgB,wCAAwC,CACvD,MAAM,EAAE,oBAAoB,GAC1B,IAAI,CA8DN;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CACzC,SAAS,EAAE,eAAe,EAC1B,UAAU,EAAE,eAAe,EAC3B,sBAAsB,EAAE,OAAO,GAC7B,uBAAuB,CAiJzB;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACrC,uBAAuB,EAAE,uBAAuB,GAC9C,eAAe,CAEjB;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACrC,uBAAuB,EAAE,uBAAuB,GAC9C,eAAe,CAEjB;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACtC,IAAI,EAAE,SAAS,EACf,uBAAuB,EAAE,uBAAuB,GAC9C,aAAa,CA8Df;AAED;;GAEG;AACH,wBAAgB,6BAA6B,CAC5C,IAAI,EAAE,SAAS,EACf,uBAAuB,EAAE,uBAAuB,GAC9C,aAAa,CA+Ff;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC3B,MAAM,EAAE,kBAAkB,EAAE,EAC5B,QAAQ,CAAC,EAAE,kBAAkB,GAC3B,kBAAkB,EAAE,CA+BtB;AAED;;GAEG;AACH,wBAAgB,6BAA6B,CAC5C,kBAAkB,EAAE,aAAa,GAC/B,aAAa,CAoBf;AAED;;GAEG;AACH,wBAAgB,OAAO,CACtB,IAAI,EAAE,SAAS,EACf,eAAe,EAAE,eAAe,GAC9B,OAAO,CAET;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACzB,aAAa,EAAE,aAAa,EAC5B,eAAe,EAAE,eAAe,GAC9B,OAAO,CAaT"}
1
+ {"version":3,"file":"ortc.d.ts","sourceRoot":"","sources":["../src/ortc.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACX,eAAe,EACf,SAAS,EACT,kBAAkB,EAElB,aAAa,EACb,kBAAkB,EAKlB,uBAAuB,EAGvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAO7D;;;;GAIG;AACH,wBAAgB,mCAAmC,CAClD,IAAI,EAAE,eAAe,GACnB,IAAI,CA0BN;AAED;;;;GAIG;AACH,wBAAgB,iCAAiC,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAiD7E;AAED;;;;GAIG;AACH,wBAAgB,wCAAwC,CACvD,MAAM,EAAE,oBAAoB,GAC1B,IAAI,CAsEN;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CACzC,SAAS,EAAE,eAAe,EAC1B,UAAU,EAAE,eAAe,EAC3B,sBAAsB,EAAE,OAAO,GAC7B,uBAAuB,CAiJzB;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACrC,uBAAuB,EAAE,uBAAuB,GAC9C,eAAe,CAEjB;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACrC,uBAAuB,EAAE,uBAAuB,GAC9C,eAAe,CAEjB;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACtC,IAAI,EAAE,SAAS,EACf,uBAAuB,EAAE,uBAAuB,GAC9C,aAAa,CA8Df;AAED;;GAEG;AACH,wBAAgB,6BAA6B,CAC5C,IAAI,EAAE,SAAS,EACf,uBAAuB,EAAE,uBAAuB,GAC9C,aAAa,CA+Ff;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC3B,MAAM,EAAE,kBAAkB,EAAE,EAC5B,QAAQ,CAAC,EAAE,kBAAkB,GAC3B,kBAAkB,EAAE,CA+BtB;AAED;;GAEG;AACH,wBAAgB,6BAA6B,CAC5C,kBAAkB,EAAE,aAAa,GAC/B,aAAa,CAoBf;AAED;;GAEG;AACH,wBAAgB,OAAO,CACtB,IAAI,EAAE,SAAS,EACf,eAAe,EAAE,eAAe,GAC9B,OAAO,CAET;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACzB,aAAa,EAAE,aAAa,EAC5B,eAAe,EAAE,eAAe,GAC9B,OAAO,CAaT"}
package/lib/ortc.js CHANGED
@@ -151,24 +151,28 @@ function validateAndNormalizeSctpStreamParameters(params) {
151
151
  params.ordered = true;
152
152
  }
153
153
  // maxPacketLifeTime is optional.
154
- if (params.maxPacketLifeTime &&
154
+ if (params.maxPacketLifeTime !== undefined &&
155
155
  typeof params.maxPacketLifeTime !== 'number') {
156
156
  throw new TypeError('invalid params.maxPacketLifeTime');
157
157
  }
158
158
  // maxRetransmits is optional.
159
- if (params.maxRetransmits && typeof params.maxRetransmits !== 'number') {
159
+ if (params.maxRetransmits !== undefined &&
160
+ typeof params.maxRetransmits !== 'number') {
160
161
  throw new TypeError('invalid params.maxRetransmits');
161
162
  }
162
- if (params.maxPacketLifeTime && params.maxRetransmits) {
163
+ if (params.maxPacketLifeTime !== undefined &&
164
+ params.maxRetransmits !== undefined) {
163
165
  throw new TypeError('cannot provide both maxPacketLifeTime and maxRetransmits');
164
166
  }
165
167
  if (orderedGiven &&
166
168
  params.ordered &&
167
- (params.maxPacketLifeTime || params.maxRetransmits)) {
169
+ (params.maxPacketLifeTime !== undefined ||
170
+ params.maxRetransmits !== undefined)) {
168
171
  throw new TypeError('cannot be ordered with maxPacketLifeTime or maxRetransmits');
169
172
  }
170
173
  else if (!orderedGiven &&
171
- (params.maxPacketLifeTime || params.maxRetransmits)) {
174
+ (params.maxPacketLifeTime !== undefined ||
175
+ params.maxRetransmits !== undefined)) {
172
176
  params.ordered = false;
173
177
  }
174
178
  // label is optional.
package/lib/test/test.js CHANGED
@@ -761,6 +761,31 @@ test('transport.produceData() succeeds', async () => {
761
761
  expect(dataProducer.label).toBe('FOO');
762
762
  expect(dataProducer.protocol).toBe('BAR');
763
763
  }, 500);
764
+ test('transport.produceData() with maxRetransmits 0 or maxPacketLifeTime 0 creates an unordered DataProducer', async () => {
765
+ const dataProducer1 = await ctx.connectedSendTransport.produceData({
766
+ maxRetransmits: 0,
767
+ });
768
+ expect(dataProducer1.sctpStreamParameters.ordered).toBe(false);
769
+ expect(dataProducer1.sctpStreamParameters.maxRetransmits).toBe(0);
770
+ const dataProducer2 = await ctx.connectedSendTransport.produceData({
771
+ maxPacketLifeTime: 0,
772
+ });
773
+ expect(dataProducer2.sctpStreamParameters.ordered).toBe(false);
774
+ expect(dataProducer2.sctpStreamParameters.maxPacketLifeTime).toBe(0);
775
+ }, 500);
776
+ test('transport.produceData() closes the DataChannel if the "producedata" listener rejects', async () => {
777
+ const handlerSpy = jest.spyOn(ctx.connectedSendTransport.handler, 'sendDataChannel');
778
+ const error = new Error('produceData() failed');
779
+ const listener = (parameters, callback, errback) => {
780
+ errback(error);
781
+ };
782
+ ctx.connectedSendTransport.prependListener('producedata', listener);
783
+ await expect(ctx.connectedSendTransport.produceData()).rejects.toThrow(error);
784
+ const { dataChannel } = await handlerSpy.mock.results[0].value;
785
+ expect(dataChannel.readyState).toBe('closed');
786
+ ctx.connectedSendTransport.off('producedata', listener);
787
+ handlerSpy.mockRestore();
788
+ }, 500);
764
789
  test('transport.produceData() in a receiving Transport rejects with UnsupportedError', async () => {
765
790
  await expect(ctx.recvTransport.produceData({})).rejects.toThrow(errors_1.UnsupportedError);
766
791
  }, 500);
@@ -904,7 +929,7 @@ test('producer.replaceTrack() with a new track succeeds', async () => {
904
929
  const videoProducerPreviousTrack = ctx.videoProducer.track;
905
930
  const newVideoTrack = new fake_mediastreamtrack_1.FakeMediaStreamTrack({ kind: 'video' });
906
931
  await expect(ctx.videoProducer.replaceTrack({ track: newVideoTrack })).resolves.toBe(undefined);
907
- // Previous track must be 'ended' due to stopTracks: true.
932
+ // Previous track must be 'ended' due to stopTracks: true (default value).
908
933
  expect(videoProducerPreviousTrack?.readyState).toBe('ended');
909
934
  expect(ctx.videoProducer.track).not.toBe(videoProducerPreviousTrack);
910
935
  expect(ctx.videoProducer.track).toBe(newVideoTrack);
@@ -949,6 +974,34 @@ test('producer.setMaxSpatialLayer() succeeds', async () => {
949
974
  await expect(ctx.videoProducer.setMaxSpatialLayer(0)).resolves.toBe(undefined);
950
975
  expect(ctx.videoProducer.maxSpatialLayer).toBe(0);
951
976
  }, 500);
977
+ test('producer.replaceTrack() rejects with InvalidStateError and stops the new track if the Producer is closed while replacing', async () => {
978
+ const previousTrack = ctx.videoProducer.track;
979
+ const newTrack = new fake_mediastreamtrack_1.FakeMediaStreamTrack({ kind: 'video' });
980
+ const replacePromise = ctx.videoProducer.replaceTrack({ track: newTrack });
981
+ ctx.videoProducer.close();
982
+ await expect(replacePromise).rejects.toThrow(errors_1.InvalidStateError);
983
+ expect(previousTrack?.readyState).toBe('ended');
984
+ // The new track must be 'ended' due to stopTracks: true (default value).
985
+ expect(newTrack.readyState).toBe('ended');
986
+ expect(ctx.videoProducer.track).toBe(previousTrack);
987
+ }, 500);
988
+ test('producer.replaceTrack() stops the new track if the Transport is closed while replacing', async () => {
989
+ const newTrack = new fake_mediastreamtrack_1.FakeMediaStreamTrack({ kind: 'video' });
990
+ const replacePromise = ctx.videoProducer.replaceTrack({ track: newTrack });
991
+ ctx.connectedSendTransport.close();
992
+ await expect(replacePromise).rejects.toThrow();
993
+ // The new track must be 'ended' due to stopTracks: true (default value).
994
+ expect(newTrack.readyState).toBe('ended');
995
+ }, 500);
996
+ test('producer.setMaxSpatialLayer() rejects and keeps the previous value if the handler fails', async () => {
997
+ const previousMaxSpatialLayer = ctx.videoProducer.maxSpatialLayer;
998
+ const error = new Error('setParameters() failed');
999
+ const handlerSpy = jest.spyOn(ctx.connectedSendTransport.handler, 'setMaxSpatialLayer');
1000
+ handlerSpy.mockRejectedValueOnce(error);
1001
+ await expect(ctx.videoProducer.setMaxSpatialLayer(1)).rejects.toThrow(error);
1002
+ expect(ctx.videoProducer.maxSpatialLayer).toBe(previousMaxSpatialLayer);
1003
+ handlerSpy.mockRestore();
1004
+ }, 500);
952
1005
  test('producer.setMaxSpatialLayer() in an audio Producer rejects with UnsupportedError', async () => {
953
1006
  await expect(ctx.audioProducer.setMaxSpatialLayer(1)).rejects.toThrow(errors_1.UnsupportedError);
954
1007
  expect(ctx.audioProducer.maxSpatialLayer).toBe(undefined);
@@ -1083,6 +1136,12 @@ test('transport.produce() rejects with InvalidStateError if closed', async () =>
1083
1136
  // The track must be 'live' due to stopTracks: false.
1084
1137
  expect(track.readyState).toBe('live');
1085
1138
  }, 500);
1139
+ test('transport.produce() stops the track if the Transport is closed', async () => {
1140
+ const track = new fake_mediastreamtrack_1.FakeMediaStreamTrack({ kind: 'audio' });
1141
+ ctx.connectedSendTransport.close();
1142
+ await expect(ctx.connectedSendTransport.produce({ track })).rejects.toThrow(errors_1.InvalidStateError);
1143
+ expect(track.readyState).toBe('ended');
1144
+ }, 500);
1086
1145
  test('transport.consume() rejects with InvalidStateError if closed', async () => {
1087
1146
  ctx.connectedRecvTransport.close();
1088
1147
  // @ts-expect-error --- On purpose.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mediasoup-client",
3
- "version": "3.24.1",
3
+ "version": "3.24.3",
4
4
  "description": "mediasoup client side TypeScript library",
5
5
  "contributors": [
6
6
  "Iñaki Baz Castillo <ibc@aliax.net> (https://inakibaz.me)",
@@ -96,8 +96,8 @@
96
96
  "knip": "^6.38.0",
97
97
  "open-cli": "^9.0.0",
98
98
  "prettier": "^3.9.9",
99
- "ts-jest": "^29.4.13",
99
+ "ts-jest": "^29.4.14",
100
100
  "typescript": "^6.0.3",
101
- "typescript-eslint": "^8.70.1"
101
+ "typescript-eslint": "^8.71.0"
102
102
  }
103
103
  }