mediasoup-client 3.24.0 → 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.
- package/lib/Producer.d.ts.map +1 -1
- package/lib/Producer.js +18 -4
- package/lib/Transport.d.ts.map +1 -1
- package/lib/Transport.js +30 -11
- package/lib/handlers/Chrome111.d.ts.map +1 -1
- package/lib/handlers/Chrome111.js +130 -95
- package/lib/handlers/Chrome74.d.ts.map +1 -1
- package/lib/handlers/Chrome74.js +166 -129
- package/lib/handlers/Firefox120.d.ts.map +1 -1
- package/lib/handlers/Firefox120.js +142 -102
- package/lib/handlers/ReactNative106.d.ts.map +1 -1
- package/lib/handlers/ReactNative106.js +183 -146
- package/lib/handlers/Safari12.d.ts.map +1 -1
- package/lib/handlers/Safari12.js +150 -112
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/ortc.d.ts.map +1 -1
- package/lib/ortc.js +9 -5
- package/lib/test/test.js +60 -1
- package/package.json +3 -3
package/lib/handlers/Chrome74.js
CHANGED
|
@@ -243,126 +243,145 @@ class Chrome74 extends enhancedEvents_1.EnhancedEventEmitter {
|
|
|
243
243
|
streams: [this._sendStream],
|
|
244
244
|
sendEncodings: encodings,
|
|
245
245
|
});
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
onRtpSender
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
246
|
+
try {
|
|
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 = Chrome74.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
|
+
if (!this._transportReady) {
|
|
269
|
+
await this.setupTransport({
|
|
270
|
+
localDtlsRole: this._forcedLocalDtlsRole ?? 'client',
|
|
271
|
+
localSdpObject,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
// Special case for VP9 with SVC.
|
|
275
|
+
let hackVp9Svc = false;
|
|
276
|
+
const layers = (0, scalabilityModes_1.parse)((encodings ?? [{}])[0].scalabilityMode);
|
|
277
|
+
let offerMediaObject;
|
|
278
|
+
if (encodings?.length === 1 &&
|
|
279
|
+
layers.spatialLayers > 1 &&
|
|
280
|
+
sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/vp9') {
|
|
281
|
+
logger.debug('send() | enabling legacy simulcast for VP9 SVC');
|
|
282
|
+
hackVp9Svc = true;
|
|
283
|
+
localSdpObject = sdpTransform.parse(offer.sdp);
|
|
284
|
+
offerMediaObject = localSdpObject.media[mediaSectionIdx.idx];
|
|
285
|
+
sdpUnifiedPlanUtils.addLegacySimulcast({
|
|
286
|
+
offerMediaObject,
|
|
287
|
+
numStreams: layers.spatialLayers,
|
|
288
|
+
});
|
|
289
|
+
offer = {
|
|
290
|
+
type: 'offer',
|
|
291
|
+
sdp: sdpTransform.write(localSdpObject),
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
logger.debug('send() | calling pc.setLocalDescription() [offer:%o]', offer);
|
|
295
|
+
await this._pc.setLocalDescription(offer);
|
|
296
|
+
// We can now get the transceiver.mid.
|
|
297
|
+
const localId = transceiver.mid;
|
|
298
|
+
// Set MID.
|
|
299
|
+
sendingRtpParameters.mid = localId;
|
|
300
|
+
localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp);
|
|
283
301
|
offerMediaObject = localSdpObject.media[mediaSectionIdx.idx];
|
|
284
|
-
|
|
302
|
+
// Set RTCP CNAME.
|
|
303
|
+
sendingRtpParameters.rtcp.cname = sdpCommonUtils.getCname({
|
|
285
304
|
offerMediaObject,
|
|
286
|
-
numStreams: layers.spatialLayers,
|
|
287
305
|
});
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
306
|
+
// Set msid.
|
|
307
|
+
sendingRtpParameters.msid = `${streamId ?? this._sendStream.id} ${track.id}`;
|
|
308
|
+
// Set RTP encodings by parsing the SDP offer if no encodings are given.
|
|
309
|
+
if (!encodings) {
|
|
310
|
+
sendingRtpParameters.encodings = sdpUnifiedPlanUtils.getRtpEncodings({
|
|
311
|
+
offerMediaObject,
|
|
312
|
+
codecs: sendingRtpParameters.codecs,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
// Set RTP encodings by parsing the SDP offer and complete them with given
|
|
316
|
+
// one if just a single encoding has been given.
|
|
317
|
+
else if (encodings.length === 1) {
|
|
318
|
+
let newEncodings = sdpUnifiedPlanUtils.getRtpEncodings({
|
|
319
|
+
offerMediaObject,
|
|
320
|
+
codecs: sendingRtpParameters.codecs,
|
|
321
|
+
});
|
|
322
|
+
Object.assign(newEncodings[0], encodings[0]);
|
|
323
|
+
// Hack for VP9 SVC.
|
|
324
|
+
if (hackVp9Svc) {
|
|
325
|
+
newEncodings = [newEncodings[0]];
|
|
326
|
+
}
|
|
327
|
+
sendingRtpParameters.encodings = newEncodings;
|
|
328
|
+
}
|
|
329
|
+
// Otherwise if more than 1 encoding are given use them verbatim.
|
|
330
|
+
else {
|
|
331
|
+
sendingRtpParameters.encodings = encodings;
|
|
332
|
+
}
|
|
333
|
+
// If VP8 or H264 and there is effective simulcast, add scalabilityMode to
|
|
334
|
+
// each encoding.
|
|
335
|
+
if (sendingRtpParameters.encodings.length > 1 &&
|
|
336
|
+
(sendingRtpParameters.codecs[0].mimeType.toLowerCase() ===
|
|
337
|
+
'video/vp8' ||
|
|
338
|
+
sendingRtpParameters.codecs[0].mimeType.toLowerCase() ===
|
|
339
|
+
'video/h264')) {
|
|
340
|
+
for (const encoding of sendingRtpParameters.encodings) {
|
|
341
|
+
if (encoding.scalabilityMode) {
|
|
342
|
+
encoding.scalabilityMode = `L1T${layers.temporalLayers}`;
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
encoding.scalabilityMode = 'L1T3';
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
this._remoteSdp.send({
|
|
318
350
|
offerMediaObject,
|
|
319
|
-
|
|
351
|
+
reuseMid: mediaSectionIdx.reuseMid,
|
|
352
|
+
offerRtpParameters: sendingRtpParameters,
|
|
353
|
+
answerRtpParameters: sendingRemoteRtpParameters,
|
|
354
|
+
codecOptions,
|
|
320
355
|
});
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
356
|
+
const answer = {
|
|
357
|
+
type: 'answer',
|
|
358
|
+
sdp: this._remoteSdp.getSdp(),
|
|
359
|
+
};
|
|
360
|
+
logger.debug('send() | calling pc.setRemoteDescription() [answer:%o]', answer);
|
|
361
|
+
await this._pc.setRemoteDescription(answer);
|
|
362
|
+
// Store in the map.
|
|
363
|
+
this._mapMidTransceiver.set(localId, transceiver);
|
|
364
|
+
return {
|
|
365
|
+
localId,
|
|
366
|
+
rtpParameters: sendingRtpParameters,
|
|
367
|
+
rtpSender: transceiver.sender,
|
|
368
|
+
};
|
|
327
369
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
for (const encoding of sendingRtpParameters.encodings) {
|
|
338
|
-
if (encoding.scalabilityMode) {
|
|
339
|
-
encoding.scalabilityMode = `L1T${layers.temporalLayers}`;
|
|
340
|
-
}
|
|
341
|
-
else {
|
|
342
|
-
encoding.scalabilityMode = 'L1T3';
|
|
370
|
+
catch (error) {
|
|
371
|
+
// Undo the changes done in the PeerConnection, otherwise it gets out of
|
|
372
|
+
// sync with the remote SDP and later calls to send() fail.
|
|
373
|
+
try {
|
|
374
|
+
if (this._pc.signalingState === 'have-local-offer') {
|
|
375
|
+
try {
|
|
376
|
+
await this._pc.setLocalDescription({ type: 'rollback' });
|
|
377
|
+
}
|
|
378
|
+
catch (error2) { }
|
|
343
379
|
}
|
|
380
|
+
transceiver.stop();
|
|
344
381
|
}
|
|
382
|
+
catch (error3) { }
|
|
383
|
+
throw error;
|
|
345
384
|
}
|
|
346
|
-
this._remoteSdp.send({
|
|
347
|
-
offerMediaObject,
|
|
348
|
-
reuseMid: mediaSectionIdx.reuseMid,
|
|
349
|
-
offerRtpParameters: sendingRtpParameters,
|
|
350
|
-
answerRtpParameters: sendingRemoteRtpParameters,
|
|
351
|
-
codecOptions,
|
|
352
|
-
});
|
|
353
|
-
const answer = {
|
|
354
|
-
type: 'answer',
|
|
355
|
-
sdp: this._remoteSdp.getSdp(),
|
|
356
|
-
};
|
|
357
|
-
logger.debug('send() | calling pc.setRemoteDescription() [answer:%o]', answer);
|
|
358
|
-
await this._pc.setRemoteDescription(answer);
|
|
359
|
-
// Store in the map.
|
|
360
|
-
this._mapMidTransceiver.set(localId, transceiver);
|
|
361
|
-
return {
|
|
362
|
-
localId,
|
|
363
|
-
rtpParameters: sendingRtpParameters,
|
|
364
|
-
rtpSender: transceiver.sender,
|
|
365
|
-
};
|
|
366
385
|
}
|
|
367
386
|
async stopSending(localId) {
|
|
368
387
|
this.assertSendDirection();
|
|
@@ -530,25 +549,43 @@ class Chrome74 extends enhancedEvents_1.EnhancedEventEmitter {
|
|
|
530
549
|
// If this is the first DataChannel we need to create the SDP answer with
|
|
531
550
|
// m=application section.
|
|
532
551
|
if (!this._hasDataChannelMediaSection) {
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
552
|
+
try {
|
|
553
|
+
const offer = await this._pc.createOffer();
|
|
554
|
+
const localSdpObject = sdpTransform.parse(offer.sdp);
|
|
555
|
+
const offerMediaObject = localSdpObject.media.find(m => m.type === 'application');
|
|
556
|
+
if (!this._transportReady) {
|
|
557
|
+
await this.setupTransport({
|
|
558
|
+
localDtlsRole: this._forcedLocalDtlsRole ?? 'client',
|
|
559
|
+
localSdpObject,
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
logger.debug('sendDataChannel() | calling pc.setLocalDescription() [offer:%o]', offer);
|
|
563
|
+
await this._pc.setLocalDescription(offer);
|
|
564
|
+
this._remoteSdp.sendSctpAssociation({ offerMediaObject });
|
|
565
|
+
const answer = {
|
|
566
|
+
type: 'answer',
|
|
567
|
+
sdp: this._remoteSdp.getSdp(),
|
|
568
|
+
};
|
|
569
|
+
logger.debug('sendDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer);
|
|
570
|
+
await this._pc.setRemoteDescription(answer);
|
|
571
|
+
this._hasDataChannelMediaSection = true;
|
|
572
|
+
}
|
|
573
|
+
catch (error) {
|
|
574
|
+
// Undo the changes done in the PeerConnection, otherwise it gets out of
|
|
575
|
+
// sync with the remote SDP and later calls to send() and
|
|
576
|
+
// sendDataChannel() fail.
|
|
577
|
+
try {
|
|
578
|
+
if (this._pc.signalingState === 'have-local-offer') {
|
|
579
|
+
try {
|
|
580
|
+
await this._pc.setLocalDescription({ type: 'rollback' });
|
|
581
|
+
}
|
|
582
|
+
catch (error2) { }
|
|
583
|
+
}
|
|
584
|
+
dataChannel.close();
|
|
585
|
+
}
|
|
586
|
+
catch (error3) { }
|
|
587
|
+
throw error;
|
|
541
588
|
}
|
|
542
|
-
logger.debug('sendDataChannel() | calling pc.setLocalDescription() [offer:%o]', offer);
|
|
543
|
-
await this._pc.setLocalDescription(offer);
|
|
544
|
-
this._remoteSdp.sendSctpAssociation({ offerMediaObject });
|
|
545
|
-
const answer = {
|
|
546
|
-
type: 'answer',
|
|
547
|
-
sdp: this._remoteSdp.getSdp(),
|
|
548
|
-
};
|
|
549
|
-
logger.debug('sendDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer);
|
|
550
|
-
await this._pc.setRemoteDescription(answer);
|
|
551
|
-
this._hasDataChannelMediaSection = true;
|
|
552
589
|
}
|
|
553
590
|
const newSctpStreamParameters = {
|
|
554
591
|
streamId: options.id,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Firefox120.d.ts","sourceRoot":"","sources":["../../src/handlers/Firefox120.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAKzD,OAAO,KAAK,EAAE,aAAa,EAAY,MAAM,cAAc,CAAC;AAY5D,OAAO,KAAK,EACX,qBAAqB,EAErB,cAAc,EACd,gBAAgB,EAChB,aAAa,EAGb,kBAAkB,EAClB,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,EACpB,6BAA6B,EAC7B,4BAA4B,EAC5B,gCAAgC,EAChC,+BAA+B,EAC/B,MAAM,oBAAoB,CAAC;AAM5B,qBAAa,UACZ,SAAQ,oBAAoB,CAAC,aAAa,CAC1C,YAAW,gBAAgB;IAG3B,OAAO,CAAC,OAAO,CAAS;IAExB,OAAO,CAAC,UAAU,CAAkB;IAEpC,OAAO,CAAC,0BAA0B,CAAC,CAAS;IAE5C,OAAO,CAAC,UAAU,CAAY;IAE9B,OAAO,CAAC,+BAA+B,CAEV;IAC7B,OAAO,CAAC,oBAAoB,CAAC,CAA6B;IAE1D,OAAO,CAAC,GAAG,CAAoB;IAE/B,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CACxB;IAEX,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;IAEjD,OAAO,CAAC,2BAA2B,CAAS;IAE5C,OAAO,CAAC,qBAAqB,CAAK;IAElC,OAAO,CAAC,eAAe,CAAS;IAEhC;;OAEG;IACH,MAAM,CAAC,aAAa,CAAC,EACpB,mBAAmB,GACnB,EAAE,qBAAqB,GAAG,cAAc;IA+FzC,OAAO,CAAC,MAAM,CAAC,uBAAuB;IAatC,OAAO;IAgEP,IAAI,IAAI,IAAI,MAAM,CAEjB;IAEQ,KAAK,IAAI,IAAI;IAsChB,gBAAgB,CAAC,UAAU,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAO3D,UAAU,CAAC,aAAa,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAyDvD,iBAAiB,IAAI,OAAO,CAAC,cAAc,CAAC;IAM5C,IAAI,CAAC,EACV,KAAK,EACL,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,KAAK,EACL,WAAW,GACX,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"Firefox120.d.ts","sourceRoot":"","sources":["../../src/handlers/Firefox120.ts"],"names":[],"mappings":"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"}
|
|
@@ -266,101 +266,120 @@ class Firefox120 extends enhancedEvents_1.EnhancedEventEmitter {
|
|
|
266
266
|
streams: [this._sendStream],
|
|
267
267
|
sendEncodings: encodings,
|
|
268
268
|
});
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
onRtpSender
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
});
|
|
308
|
-
// Set msid.
|
|
309
|
-
sendingRtpParameters.msid = `${streamId ?? this._sendStream.id} ${track.id}`;
|
|
310
|
-
// Set RTP encodings by parsing the SDP offer if no encodings are given.
|
|
311
|
-
if (!encodings) {
|
|
312
|
-
sendingRtpParameters.encodings = sdpUnifiedPlanUtils.getRtpEncodings({
|
|
269
|
+
try {
|
|
270
|
+
if (this._forcedRtpExtensions) {
|
|
271
|
+
ortcUtils.applyForcedRtpExtensions(transceiver, this._forcedRtpExtensions);
|
|
272
|
+
}
|
|
273
|
+
if (onRtpSender) {
|
|
274
|
+
onRtpSender(transceiver.sender);
|
|
275
|
+
}
|
|
276
|
+
const offer = await this._pc.createOffer();
|
|
277
|
+
let localSdpObject = sdpTransform.parse(offer.sdp);
|
|
278
|
+
if (localSdpObject.extmapAllowMixed) {
|
|
279
|
+
this._remoteSdp.setSessionExtmapAllowMixed();
|
|
280
|
+
}
|
|
281
|
+
const nativeRtpCapabilities = Firefox120.getLocalRtpCapabilities(localSdpObject);
|
|
282
|
+
const sendExtendedRtpCapabilities = this._getSendExtendedRtpCapabilities(nativeRtpCapabilities);
|
|
283
|
+
// Generic sending RTP parameters.
|
|
284
|
+
const sendingRtpParameters = ortc.getSendingRtpParameters(track.kind, sendExtendedRtpCapabilities);
|
|
285
|
+
// This may throw.
|
|
286
|
+
sendingRtpParameters.codecs = ortc.reduceCodecs(sendingRtpParameters.codecs, codec);
|
|
287
|
+
// Generic sending RTP parameters suitable for the SDP remote answer.
|
|
288
|
+
const sendingRemoteRtpParameters = ortc.getSendingRemoteRtpParameters(track.kind, sendExtendedRtpCapabilities);
|
|
289
|
+
// This may throw.
|
|
290
|
+
sendingRemoteRtpParameters.codecs = ortc.reduceCodecs(sendingRemoteRtpParameters.codecs, codec);
|
|
291
|
+
// In Firefox use DTLS role client even if we are the "offerer" since
|
|
292
|
+
// Firefox does not respect ICE-Lite.
|
|
293
|
+
if (!this._transportReady) {
|
|
294
|
+
await this.setupTransport({ localDtlsRole: 'client', localSdpObject });
|
|
295
|
+
}
|
|
296
|
+
const layers = (0, scalabilityModes_1.parse)((encodings ?? [{}])[0].scalabilityMode);
|
|
297
|
+
logger.debug('send() | calling pc.setLocalDescription() [offer:%o]', offer);
|
|
298
|
+
await this._pc.setLocalDescription(offer);
|
|
299
|
+
// We can now get the transceiver.mid.
|
|
300
|
+
const localId = transceiver.mid;
|
|
301
|
+
// Set MID.
|
|
302
|
+
sendingRtpParameters.mid = localId;
|
|
303
|
+
localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp);
|
|
304
|
+
const offerMediaObject = localSdpObject.media[localSdpObject.media.length - 1];
|
|
305
|
+
// Set RTCP CNAME.
|
|
306
|
+
sendingRtpParameters.rtcp.cname = sdpCommonUtils.getCname({
|
|
313
307
|
offerMediaObject,
|
|
314
|
-
codecs: sendingRtpParameters.codecs,
|
|
315
308
|
});
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
309
|
+
// Set msid.
|
|
310
|
+
sendingRtpParameters.msid = `${streamId ?? this._sendStream.id} ${track.id}`;
|
|
311
|
+
// Set RTP encodings by parsing the SDP offer if no encodings are given.
|
|
312
|
+
if (!encodings) {
|
|
313
|
+
sendingRtpParameters.encodings = sdpUnifiedPlanUtils.getRtpEncodings({
|
|
314
|
+
offerMediaObject,
|
|
315
|
+
codecs: sendingRtpParameters.codecs,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
// Set RTP encodings by parsing the SDP offer and complete them with given
|
|
319
|
+
// one if just a single encoding has been given.
|
|
320
|
+
else if (encodings.length === 1) {
|
|
321
|
+
const newEncodings = sdpUnifiedPlanUtils.getRtpEncodings({
|
|
322
|
+
offerMediaObject,
|
|
323
|
+
codecs: sendingRtpParameters.codecs,
|
|
324
|
+
});
|
|
325
|
+
Object.assign(newEncodings[0], encodings[0]);
|
|
326
|
+
sendingRtpParameters.encodings = newEncodings;
|
|
327
|
+
}
|
|
328
|
+
// Otherwise if more than 1 encoding are given use them verbatim.
|
|
329
|
+
else {
|
|
330
|
+
sendingRtpParameters.encodings = encodings;
|
|
331
|
+
}
|
|
332
|
+
// If VP8 or H264 and there is effective simulcast, add scalabilityMode to
|
|
333
|
+
// each encoding.
|
|
334
|
+
if (sendingRtpParameters.encodings.length > 1 &&
|
|
335
|
+
(sendingRtpParameters.codecs[0].mimeType.toLowerCase() ===
|
|
336
|
+
'video/vp8' ||
|
|
337
|
+
sendingRtpParameters.codecs[0].mimeType.toLowerCase() ===
|
|
338
|
+
'video/h264')) {
|
|
339
|
+
for (const encoding of sendingRtpParameters.encodings) {
|
|
340
|
+
if (encoding.scalabilityMode) {
|
|
341
|
+
encoding.scalabilityMode = `L1T${layers.temporalLayers}`;
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
encoding.scalabilityMode = 'L1T3';
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
this._remoteSdp.send({
|
|
321
349
|
offerMediaObject,
|
|
322
|
-
|
|
350
|
+
offerRtpParameters: sendingRtpParameters,
|
|
351
|
+
answerRtpParameters: sendingRemoteRtpParameters,
|
|
352
|
+
codecOptions,
|
|
323
353
|
});
|
|
324
|
-
|
|
325
|
-
|
|
354
|
+
const answer = {
|
|
355
|
+
type: 'answer',
|
|
356
|
+
sdp: this._remoteSdp.getSdp(),
|
|
357
|
+
};
|
|
358
|
+
logger.debug('send() | calling pc.setRemoteDescription() [answer:%o]', answer);
|
|
359
|
+
await this._pc.setRemoteDescription(answer);
|
|
360
|
+
// Store in the map.
|
|
361
|
+
this._mapMidTransceiver.set(localId, transceiver);
|
|
362
|
+
return {
|
|
363
|
+
localId,
|
|
364
|
+
rtpParameters: sendingRtpParameters,
|
|
365
|
+
rtpSender: transceiver.sender,
|
|
366
|
+
};
|
|
326
367
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
for (const encoding of sendingRtpParameters.encodings) {
|
|
337
|
-
if (encoding.scalabilityMode) {
|
|
338
|
-
encoding.scalabilityMode = `L1T${layers.temporalLayers}`;
|
|
339
|
-
}
|
|
340
|
-
else {
|
|
341
|
-
encoding.scalabilityMode = 'L1T3';
|
|
368
|
+
catch (error) {
|
|
369
|
+
// Undo the changes done in the PeerConnection, otherwise it gets out of
|
|
370
|
+
// sync with the remote SDP and later calls to send() fail.
|
|
371
|
+
try {
|
|
372
|
+
if (this._pc.signalingState === 'have-local-offer') {
|
|
373
|
+
try {
|
|
374
|
+
await this._pc.setLocalDescription({ type: 'rollback' });
|
|
375
|
+
}
|
|
376
|
+
catch (error2) { }
|
|
342
377
|
}
|
|
378
|
+
transceiver.stop();
|
|
343
379
|
}
|
|
380
|
+
catch (error3) { }
|
|
381
|
+
throw error;
|
|
344
382
|
}
|
|
345
|
-
this._remoteSdp.send({
|
|
346
|
-
offerMediaObject,
|
|
347
|
-
offerRtpParameters: sendingRtpParameters,
|
|
348
|
-
answerRtpParameters: sendingRemoteRtpParameters,
|
|
349
|
-
codecOptions,
|
|
350
|
-
});
|
|
351
|
-
const answer = {
|
|
352
|
-
type: 'answer',
|
|
353
|
-
sdp: this._remoteSdp.getSdp(),
|
|
354
|
-
};
|
|
355
|
-
logger.debug('send() | calling pc.setRemoteDescription() [answer:%o]', answer);
|
|
356
|
-
await this._pc.setRemoteDescription(answer);
|
|
357
|
-
// Store in the map.
|
|
358
|
-
this._mapMidTransceiver.set(localId, transceiver);
|
|
359
|
-
return {
|
|
360
|
-
localId,
|
|
361
|
-
rtpParameters: sendingRtpParameters,
|
|
362
|
-
rtpSender: transceiver.sender,
|
|
363
|
-
};
|
|
364
383
|
}
|
|
365
384
|
async stopSending(localId) {
|
|
366
385
|
this.assertSendDirection();
|
|
@@ -533,22 +552,43 @@ class Firefox120 extends enhancedEvents_1.EnhancedEventEmitter {
|
|
|
533
552
|
// If this is the first DataChannel we need to create the SDP answer with
|
|
534
553
|
// m=application section.
|
|
535
554
|
if (!this._hasDataChannelMediaSection) {
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
555
|
+
try {
|
|
556
|
+
const offer = await this._pc.createOffer();
|
|
557
|
+
const localSdpObject = sdpTransform.parse(offer.sdp);
|
|
558
|
+
const offerMediaObject = localSdpObject.media.find(m => m.type === 'application');
|
|
559
|
+
if (!this._transportReady) {
|
|
560
|
+
await this.setupTransport({
|
|
561
|
+
localDtlsRole: 'client',
|
|
562
|
+
localSdpObject,
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
logger.debug('sendDataChannel() | calling pc.setLocalDescription() [offer:%o]', offer);
|
|
566
|
+
await this._pc.setLocalDescription(offer);
|
|
567
|
+
this._remoteSdp.sendSctpAssociation({ offerMediaObject });
|
|
568
|
+
const answer = {
|
|
569
|
+
type: 'answer',
|
|
570
|
+
sdp: this._remoteSdp.getSdp(),
|
|
571
|
+
};
|
|
572
|
+
logger.debug('sendDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer);
|
|
573
|
+
await this._pc.setRemoteDescription(answer);
|
|
574
|
+
this._hasDataChannelMediaSection = true;
|
|
575
|
+
}
|
|
576
|
+
catch (error) {
|
|
577
|
+
// Undo the changes done in the PeerConnection, otherwise it gets out of
|
|
578
|
+
// sync with the remote SDP and later calls to send() and
|
|
579
|
+
// sendDataChannel() fail.
|
|
580
|
+
try {
|
|
581
|
+
if (this._pc.signalingState === 'have-local-offer') {
|
|
582
|
+
try {
|
|
583
|
+
await this._pc.setLocalDescription({ type: 'rollback' });
|
|
584
|
+
}
|
|
585
|
+
catch (error2) { }
|
|
586
|
+
}
|
|
587
|
+
dataChannel.close();
|
|
588
|
+
}
|
|
589
|
+
catch (error3) { }
|
|
590
|
+
throw error;
|
|
541
591
|
}
|
|
542
|
-
logger.debug('sendDataChannel() | calling pc.setLocalDescription() [offer:%o]', offer);
|
|
543
|
-
await this._pc.setLocalDescription(offer);
|
|
544
|
-
this._remoteSdp.sendSctpAssociation({ offerMediaObject });
|
|
545
|
-
const answer = {
|
|
546
|
-
type: 'answer',
|
|
547
|
-
sdp: this._remoteSdp.getSdp(),
|
|
548
|
-
};
|
|
549
|
-
logger.debug('sendDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer);
|
|
550
|
-
await this._pc.setRemoteDescription(answer);
|
|
551
|
-
this._hasDataChannelMediaSection = true;
|
|
552
592
|
}
|
|
553
593
|
const newSctpStreamParameters = {
|
|
554
594
|
streamId: options.id,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReactNative106.d.ts","sourceRoot":"","sources":["../../src/handlers/ReactNative106.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAKzD,OAAO,KAAK,EAAE,aAAa,EAAY,MAAM,cAAc,CAAC;AAY5D,OAAO,KAAK,EACX,qBAAqB,EAErB,cAAc,EACd,gBAAgB,EAChB,aAAa,EAGb,kBAAkB,EAClB,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,EACpB,6BAA6B,EAC7B,4BAA4B,EAC5B,gCAAgC,EAChC,+BAA+B,EAC/B,MAAM,oBAAoB,CAAC;AAM5B,qBAAa,cACZ,SAAQ,oBAAoB,CAAC,aAAa,CAC1C,YAAW,gBAAgB;IAG3B,OAAO,CAAC,OAAO,CAAS;IAExB,OAAO,CAAC,UAAU,CAAkB;IAEpC,OAAO,CAAC,0BAA0B,CAAC,CAAS;IAE5C,OAAO,CAAC,UAAU,CAAY;IAE9B,OAAO,CAAC,+BAA+B,CAEV;IAC7B,OAAO,CAAC,oBAAoB,CAAC,CAA6B;IAG1D,OAAO,CAAC,oBAAoB,CAAC,CAAW;IAExC,OAAO,CAAC,GAAG,CAAoB;IAE/B,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CACxB;IAEX,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;IAEjD,OAAO,CAAC,2BAA2B,CAAS;IAE5C,OAAO,CAAC,qBAAqB,CAAK;IAElC,OAAO,CAAC,eAAe,CAAS;IAEhC;;OAEG;IACH,MAAM,CAAC,aAAa,CAAC,EACpB,mBAAmB,GACnB,EAAE,qBAAqB,GAAG,cAAc;IA+DzC,OAAO,CAAC,MAAM,CAAC,uBAAuB;IAgBtC,OAAO;IAqEP,IAAI,IAAI,IAAI,MAAM,CAEjB;IAEQ,KAAK,IAAI,IAAI;IA0ChB,gBAAgB,CAAC,UAAU,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAY3D,UAAU,CAAC,aAAa,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAyDvD,iBAAiB,IAAI,OAAO,CAAC,cAAc,CAAC;IAM5C,IAAI,CAAC,EACV,KAAK,EACL,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,KAAK,EACL,WAAW,GACX,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"ReactNative106.d.ts","sourceRoot":"","sources":["../../src/handlers/ReactNative106.ts"],"names":[],"mappings":"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"}
|