werift 0.14.5 → 0.15.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.
- package/lib/common/src/binary.d.ts +0 -1
- package/lib/common/src/binary.js +1 -2
- package/lib/common/src/binary.js.map +1 -1
- package/lib/dtls/src/context/cipher.js +5 -2
- package/lib/dtls/src/context/cipher.js.map +1 -1
- package/lib/dtls/src/handshake/extensions/useSrtp.js +5 -2
- package/lib/dtls/src/handshake/extensions/useSrtp.js.map +1 -1
- package/lib/ice/src/candidate.js +5 -2
- package/lib/ice/src/candidate.js.map +1 -1
- package/lib/ice/src/ice.d.ts +2 -2
- package/lib/ice/src/ice.js +19 -8
- package/lib/ice/src/ice.js.map +1 -1
- package/lib/ice/src/stun/attributes.js +5 -2
- package/lib/ice/src/stun/attributes.js.map +1 -1
- package/lib/rtp/src/rtcp/rr.js +5 -2
- package/lib/rtp/src/rtcp/rr.js.map +1 -1
- package/lib/rtp/src/rtcp/rtpfb/nack.js +6 -3
- package/lib/rtp/src/rtcp/rtpfb/nack.js.map +1 -1
- package/lib/rtp/src/rtcp/rtpfb/twcc.js +6 -6
- package/lib/rtp/src/rtcp/rtpfb/twcc.js.map +1 -1
- package/lib/rtp/src/rtcp/sr.js +5 -2
- package/lib/rtp/src/rtcp/sr.js.map +1 -1
- package/lib/rtp/src/srtp/cipher/ctr.js +5 -2
- package/lib/rtp/src/srtp/cipher/ctr.js.map +1 -1
- package/lib/rtp/src/srtp/cipher/gcm.js +6 -3
- package/lib/rtp/src/srtp/cipher/gcm.js.map +1 -1
- package/lib/sctp/src/param.js +5 -2
- package/lib/sctp/src/param.js.map +1 -1
- package/lib/sctp/src/sctp.js +5 -5
- package/lib/sctp/src/sctp.js.map +1 -1
- package/lib/webrtc/src/dataChannel.js +3 -2
- package/lib/webrtc/src/dataChannel.js.map +1 -1
- package/lib/webrtc/src/media/receiver/nack.js +2 -2
- package/lib/webrtc/src/media/receiver/nack.js.map +1 -1
- package/lib/webrtc/src/media/rtpReceiver.d.ts +3 -2
- package/lib/webrtc/src/media/rtpReceiver.js +4 -2
- package/lib/webrtc/src/media/rtpReceiver.js.map +1 -1
- package/lib/webrtc/src/media/rtpSender.d.ts +4 -2
- package/lib/webrtc/src/media/rtpSender.js +15 -7
- package/lib/webrtc/src/media/rtpSender.js.map +1 -1
- package/lib/webrtc/src/media/rtpTransceiver.d.ts +7 -6
- package/lib/webrtc/src/media/rtpTransceiver.js +10 -3
- package/lib/webrtc/src/media/rtpTransceiver.js.map +1 -1
- package/lib/webrtc/src/peerConnection.d.ts +10 -10
- package/lib/webrtc/src/peerConnection.js +356 -211
- package/lib/webrtc/src/peerConnection.js.map +1 -1
- package/lib/webrtc/src/sdp.js +5 -2
- package/lib/webrtc/src/sdp.js.map +1 -1
- package/lib/webrtc/src/transport/dtls.d.ts +3 -2
- package/lib/webrtc/src/transport/dtls.js +3 -0
- package/lib/webrtc/src/transport/dtls.js.map +1 -1
- package/lib/webrtc/src/transport/sctp.d.ts +5 -3
- package/lib/webrtc/src/transport/sctp.js +46 -34
- package/lib/webrtc/src/transport/sctp.js.map +1 -1
- package/package.json +2 -2
- package/src/dataChannel.ts +4 -2
- package/src/media/receiver/nack.ts +1 -1
- package/src/media/rtpReceiver.ts +6 -5
- package/src/media/rtpSender.ts +18 -9
- package/src/media/rtpTransceiver.ts +17 -6
- package/src/peerConnection.ts +431 -253
- package/src/sdp.ts +1 -1
- package/src/transport/dtls.ts +4 -1
- package/src/transport/sctp.ts +53 -33
package/src/peerConnection.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import debug from "debug";
|
|
2
|
-
import
|
|
2
|
+
import cloneDeep from "lodash/cloneDeep";
|
|
3
|
+
import isEqual from "lodash/isEqual";
|
|
3
4
|
import Event from "rx.mini";
|
|
4
5
|
import * as uuid from "uuid";
|
|
5
6
|
|
|
@@ -61,8 +62,6 @@ const log = debug("werift:packages/webrtc/src/peerConnection.ts");
|
|
|
61
62
|
|
|
62
63
|
export class RTCPeerConnection extends EventTarget {
|
|
63
64
|
readonly cname = uuid.v4();
|
|
64
|
-
iceTransport: RTCIceTransport;
|
|
65
|
-
dtlsTransport: RTCDtlsTransport;
|
|
66
65
|
sctpTransport?: RTCSctpTransport;
|
|
67
66
|
masterTransportEstablished = false;
|
|
68
67
|
configuration: Required<PeerConfig> =
|
|
@@ -73,17 +72,13 @@ export class RTCPeerConnection extends EventTarget {
|
|
|
73
72
|
signalingState: RTCSignalingState = "stable";
|
|
74
73
|
negotiationneeded = false;
|
|
75
74
|
readonly transceivers: RTCRtpTransceiver[] = [];
|
|
75
|
+
|
|
76
76
|
readonly iceGatheringStateChange = new Event<[IceGathererState]>();
|
|
77
77
|
readonly iceConnectionStateChange = new Event<[RTCIceConnectionState]>();
|
|
78
78
|
readonly signalingStateChange = new Event<[RTCSignalingState]>();
|
|
79
79
|
readonly connectionStateChange = new Event<[ConnectionState]>();
|
|
80
80
|
readonly onDataChannel = new Event<[RTCDataChannel]>();
|
|
81
81
|
readonly onRemoteTransceiverAdded = new Event<[RTCRtpTransceiver]>();
|
|
82
|
-
/**
|
|
83
|
-
* should use onRemoteTransceiverAdded
|
|
84
|
-
* @deprecated
|
|
85
|
-
*/
|
|
86
|
-
readonly onTransceiver = new Event<[RTCRtpTransceiver]>();
|
|
87
82
|
readonly onTransceiverAdded = new Event<[RTCRtpTransceiver]>();
|
|
88
83
|
readonly onIceCandidate = new Event<[RTCIceCandidate]>();
|
|
89
84
|
readonly onNegotiationneeded = new Event<[]>();
|
|
@@ -106,6 +101,22 @@ export class RTCPeerConnection extends EventTarget {
|
|
|
106
101
|
private isClosed = false;
|
|
107
102
|
private shouldNegotiationneeded = false;
|
|
108
103
|
|
|
104
|
+
get dtlsTransports() {
|
|
105
|
+
const transports = this.transceivers.map((t) => t.dtlsTransport);
|
|
106
|
+
if (this.sctpTransport) {
|
|
107
|
+
transports.push(this.sctpTransport.dtlsTransport);
|
|
108
|
+
}
|
|
109
|
+
return transports.reduce((acc: RTCDtlsTransport[], cur) => {
|
|
110
|
+
if (!acc.map((d) => d.id).includes(cur.id)) {
|
|
111
|
+
acc.push(cur);
|
|
112
|
+
}
|
|
113
|
+
return acc;
|
|
114
|
+
}, []);
|
|
115
|
+
}
|
|
116
|
+
get iceTransports() {
|
|
117
|
+
return this.dtlsTransports.map((d) => d.iceTransport);
|
|
118
|
+
}
|
|
119
|
+
|
|
109
120
|
constructor({
|
|
110
121
|
codecs,
|
|
111
122
|
headerExtensions,
|
|
@@ -113,6 +124,7 @@ export class RTCPeerConnection extends EventTarget {
|
|
|
113
124
|
iceTransportPolicy,
|
|
114
125
|
icePortRange,
|
|
115
126
|
dtls,
|
|
127
|
+
bundlePolicy,
|
|
116
128
|
}: Partial<PeerConfig> = {}) {
|
|
117
129
|
super();
|
|
118
130
|
|
|
@@ -179,13 +191,9 @@ export class RTCPeerConnection extends EventTarget {
|
|
|
179
191
|
break;
|
|
180
192
|
}
|
|
181
193
|
});
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
SRTP_PROFILE.SRTP_AES128_CM_HMAC_SHA1_80,
|
|
186
|
-
]);
|
|
187
|
-
this.iceTransport = iceTransport;
|
|
188
|
-
this.dtlsTransport = dtlsTransport;
|
|
194
|
+
if (bundlePolicy) {
|
|
195
|
+
this.configuration.bundlePolicy = bundlePolicy;
|
|
196
|
+
}
|
|
189
197
|
}
|
|
190
198
|
|
|
191
199
|
get localDescription() {
|
|
@@ -217,9 +225,7 @@ export class RTCPeerConnection extends EventTarget {
|
|
|
217
225
|
}
|
|
218
226
|
|
|
219
227
|
async createOffer() {
|
|
220
|
-
|
|
221
|
-
await this.dtlsTransport.setupCertificate();
|
|
222
|
-
}
|
|
228
|
+
await this.ensureCerts();
|
|
223
229
|
|
|
224
230
|
this.transceivers.forEach((transceiver) => {
|
|
225
231
|
transceiver.codecs = this.configuration.codecs[transceiver.kind];
|
|
@@ -291,11 +297,13 @@ export class RTCPeerConnection extends EventTarget {
|
|
|
291
297
|
);
|
|
292
298
|
}
|
|
293
299
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
300
|
+
if (this.configuration.bundlePolicy !== "disable") {
|
|
301
|
+
const mids = description.media
|
|
302
|
+
.map((m) => m.rtp.muxId)
|
|
303
|
+
.filter((v) => v) as string[];
|
|
304
|
+
const bundle = new GroupDescription("BUNDLE", mids);
|
|
305
|
+
description.group.push(bundle);
|
|
306
|
+
}
|
|
299
307
|
|
|
300
308
|
return description.toJSON();
|
|
301
309
|
}
|
|
@@ -337,7 +345,8 @@ export class RTCPeerConnection extends EventTarget {
|
|
|
337
345
|
protocol: settings.protocol,
|
|
338
346
|
});
|
|
339
347
|
|
|
340
|
-
|
|
348
|
+
const channel = new RTCDataChannel(this.sctpTransport, parameters);
|
|
349
|
+
return channel;
|
|
341
350
|
}
|
|
342
351
|
|
|
343
352
|
removeTrack(sender: RTCRtpSender) {
|
|
@@ -380,20 +389,33 @@ export class RTCPeerConnection extends EventTarget {
|
|
|
380
389
|
};
|
|
381
390
|
|
|
382
391
|
private createTransport(srtpProfiles: Profile[] = []) {
|
|
392
|
+
const [existing] = this.iceTransports;
|
|
393
|
+
|
|
394
|
+
// Gather ICE candidates for only one track. If the remote endpoint is not bundle-aware, negotiate only one media track.
|
|
395
|
+
// https://w3c.github.io/webrtc-pc/#rtcbundlepolicy-enum
|
|
396
|
+
if (this.configuration.bundlePolicy === "max-bundle") {
|
|
397
|
+
if (existing) {
|
|
398
|
+
return this.dtlsTransports[0];
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
383
402
|
const iceGatherer = new RTCIceGatherer({
|
|
384
403
|
...parseIceServers(this.configuration.iceServers),
|
|
385
404
|
forceTurn: this.configuration.iceTransportPolicy === "relay",
|
|
386
405
|
portRange: this.configuration.icePortRange,
|
|
387
406
|
});
|
|
388
|
-
|
|
389
|
-
|
|
407
|
+
if (existing) {
|
|
408
|
+
iceGatherer.connection.localUserName = existing.connection.localUserName;
|
|
409
|
+
iceGatherer.connection.localPassword = existing.connection.localPassword;
|
|
410
|
+
}
|
|
411
|
+
iceGatherer.onGatheringStateChange.subscribe(() => {
|
|
412
|
+
this.updateIceGatheringState();
|
|
390
413
|
});
|
|
391
|
-
this.updateIceGatheringState(
|
|
414
|
+
this.updateIceGatheringState();
|
|
392
415
|
const iceTransport = new RTCIceTransport(iceGatherer);
|
|
393
|
-
iceTransport.onStateChange.subscribe((
|
|
394
|
-
this.updateIceConnectionState(
|
|
416
|
+
iceTransport.onStateChange.subscribe(() => {
|
|
417
|
+
this.updateIceConnectionState();
|
|
395
418
|
});
|
|
396
|
-
this.updateIceConnectionState(iceTransport.state);
|
|
397
419
|
|
|
398
420
|
iceTransport.iceGather.onIceCandidate = (candidate) => {
|
|
399
421
|
if (!this.localDescription) return;
|
|
@@ -405,7 +427,6 @@ export class RTCPeerConnection extends EventTarget {
|
|
|
405
427
|
}
|
|
406
428
|
candidate.sdpMLineIndex = 0;
|
|
407
429
|
candidate.sdpMid = media.rtp.muxId;
|
|
408
|
-
// for chrome & firefox & maybe others
|
|
409
430
|
candidate.foundation = "candidate:" + candidate.foundation;
|
|
410
431
|
|
|
411
432
|
this.onIceCandidate.execute(candidate.toJSON());
|
|
@@ -421,11 +442,16 @@ export class RTCPeerConnection extends EventTarget {
|
|
|
421
442
|
srtpProfiles
|
|
422
443
|
);
|
|
423
444
|
|
|
424
|
-
return
|
|
445
|
+
return dtlsTransport;
|
|
425
446
|
}
|
|
426
447
|
|
|
427
448
|
private createSctpTransport() {
|
|
428
|
-
const
|
|
449
|
+
const dtlsTransport = this.createTransport([
|
|
450
|
+
SRTP_PROFILE.SRTP_AEAD_AES_128_GCM, // prefer
|
|
451
|
+
SRTP_PROFILE.SRTP_AES128_CM_HMAC_SHA1_80,
|
|
452
|
+
]);
|
|
453
|
+
const sctp = new RTCSctpTransport();
|
|
454
|
+
sctp.setDtlsTransport(dtlsTransport);
|
|
429
455
|
sctp.mid = undefined;
|
|
430
456
|
|
|
431
457
|
sctp.onDataChannel.subscribe((channel) => {
|
|
@@ -436,6 +462,9 @@ export class RTCPeerConnection extends EventTarget {
|
|
|
436
462
|
this.emit("datachannel", event);
|
|
437
463
|
});
|
|
438
464
|
|
|
465
|
+
this.sctpTransport = sctp;
|
|
466
|
+
this.updateIceConnectionState();
|
|
467
|
+
|
|
439
468
|
return sctp;
|
|
440
469
|
}
|
|
441
470
|
|
|
@@ -457,10 +486,7 @@ export class RTCPeerConnection extends EventTarget {
|
|
|
457
486
|
|
|
458
487
|
// # assign MID
|
|
459
488
|
description.media.forEach((media, i) => {
|
|
460
|
-
const mid = media.rtp.muxId
|
|
461
|
-
if (!mid) {
|
|
462
|
-
throw new Error("mid not exist");
|
|
463
|
-
}
|
|
489
|
+
const mid = media.rtp.muxId!;
|
|
464
490
|
this.seenMid.add(mid);
|
|
465
491
|
if (["audio", "video"].includes(media.kind)) {
|
|
466
492
|
const transceiver = this.getTransceiverByMLineIndex(i);
|
|
@@ -473,26 +499,31 @@ export class RTCPeerConnection extends EventTarget {
|
|
|
473
499
|
}
|
|
474
500
|
});
|
|
475
501
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
this.iceTransport.connection.iceControlling = true;
|
|
479
|
-
} else {
|
|
480
|
-
this.iceTransport.connection.iceControlling = false;
|
|
481
|
-
}
|
|
482
|
-
// One agent full, one lite: The full agent MUST take the controlling role, and the lite agent MUST take the controlled role
|
|
483
|
-
// RFC 8445 S6.1.1
|
|
484
|
-
if (this.iceTransport.connection.remoteIsLite) {
|
|
485
|
-
this.iceTransport.connection.iceControlling = true;
|
|
486
|
-
}
|
|
502
|
+
const setupRole = (dtlsTransport: RTCDtlsTransport) => {
|
|
503
|
+
const iceTransport = dtlsTransport.iceTransport;
|
|
487
504
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
505
|
+
// # set ICE role
|
|
506
|
+
if (description.type === "offer") {
|
|
507
|
+
iceTransport.connection.iceControlling = true;
|
|
508
|
+
} else {
|
|
509
|
+
iceTransport.connection.iceControlling = false;
|
|
510
|
+
}
|
|
511
|
+
// One agent full, one lite: The full agent MUST take the controlling role, and the lite agent MUST take the controlled role
|
|
512
|
+
// RFC 8445 S6.1.1
|
|
513
|
+
if (iceTransport.connection.remoteIsLite) {
|
|
514
|
+
iceTransport.connection.iceControlling = true;
|
|
494
515
|
}
|
|
495
|
-
|
|
516
|
+
|
|
517
|
+
// # set DTLS role for mediasoup
|
|
518
|
+
if (description.type === "answer") {
|
|
519
|
+
const role = description.media.find((media) => media.dtlsParams)
|
|
520
|
+
?.dtlsParams?.role;
|
|
521
|
+
if (role) {
|
|
522
|
+
dtlsTransport.role = role;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
this.dtlsTransports.forEach((d) => setupRole(d));
|
|
496
527
|
|
|
497
528
|
// # configure direction
|
|
498
529
|
this.transceivers.forEach((t) => {
|
|
@@ -506,10 +537,18 @@ export class RTCPeerConnection extends EventTarget {
|
|
|
506
537
|
this.setLocal(description);
|
|
507
538
|
|
|
508
539
|
// # gather candidates
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
540
|
+
for (const iceTransport of this.iceTransports) {
|
|
541
|
+
await iceTransport.iceGather.gather();
|
|
542
|
+
}
|
|
543
|
+
description.media
|
|
544
|
+
.filter((m) => ["audio", "video"].includes(m.kind))
|
|
545
|
+
.forEach((m, i) => {
|
|
546
|
+
addTransportDescription(m, this.transceivers[i].dtlsTransport);
|
|
547
|
+
});
|
|
548
|
+
const sctpMedia = description.media.find((m) => m.kind === "application");
|
|
549
|
+
if (this.sctpTransport && sctpMedia) {
|
|
550
|
+
addTransportDescription(sctpMedia, this.sctpTransport.dtlsTransport);
|
|
551
|
+
}
|
|
513
552
|
|
|
514
553
|
this.setLocal(description);
|
|
515
554
|
|
|
@@ -540,39 +579,44 @@ export class RTCPeerConnection extends EventTarget {
|
|
|
540
579
|
|
|
541
580
|
async addIceCandidate(candidateMessage: RTCIceCandidate) {
|
|
542
581
|
const candidate = IceCandidate.fromJSON(candidateMessage);
|
|
543
|
-
|
|
582
|
+
for (const iceTransport of this.iceTransports) {
|
|
583
|
+
await iceTransport.addRemoteCandidate(candidate);
|
|
584
|
+
}
|
|
544
585
|
}
|
|
545
586
|
|
|
546
587
|
private async connect() {
|
|
547
588
|
if (this.masterTransportEstablished) return;
|
|
548
589
|
|
|
549
|
-
const dtlsTransport = this.dtlsTransport;
|
|
550
|
-
const iceTransport = dtlsTransport.iceTransport;
|
|
551
|
-
|
|
552
590
|
this.setConnectionState("connecting");
|
|
553
591
|
|
|
554
|
-
await
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
592
|
+
await Promise.all(
|
|
593
|
+
this.dtlsTransports.map(async (dtlsTransport) => {
|
|
594
|
+
const { iceTransport } = dtlsTransport;
|
|
595
|
+
await iceTransport.start().catch((err) => {
|
|
596
|
+
log("iceTransport.start failed", err);
|
|
597
|
+
throw err;
|
|
598
|
+
});
|
|
599
|
+
await dtlsTransport.start().catch((err) => {
|
|
600
|
+
log("dtlsTransport.start failed", err);
|
|
601
|
+
throw err;
|
|
602
|
+
});
|
|
603
|
+
if (
|
|
604
|
+
this.sctpTransport &&
|
|
605
|
+
this.sctpRemotePort &&
|
|
606
|
+
this.sctpTransport.dtlsTransport.id === dtlsTransport.id
|
|
607
|
+
) {
|
|
608
|
+
await this.sctpTransport.start(this.sctpRemotePort);
|
|
609
|
+
await this.sctpTransport.sctp.stateChanged.connected.asPromise();
|
|
610
|
+
log("sctp connected");
|
|
611
|
+
}
|
|
612
|
+
})
|
|
613
|
+
);
|
|
570
614
|
|
|
571
615
|
this.masterTransportEstablished = true;
|
|
572
616
|
this.setConnectionState("connected");
|
|
573
617
|
}
|
|
574
618
|
|
|
575
|
-
private
|
|
619
|
+
private getLocalRtpParams(transceiver: RTCRtpTransceiver): RTCRtpParameters {
|
|
576
620
|
if (transceiver.mid == undefined) throw new Error("mid not assigned");
|
|
577
621
|
|
|
578
622
|
const rtp: RTCRtpParameters = {
|
|
@@ -584,15 +628,10 @@ export class RTCPeerConnection extends EventTarget {
|
|
|
584
628
|
return rtp;
|
|
585
629
|
}
|
|
586
630
|
|
|
587
|
-
private
|
|
588
|
-
|
|
631
|
+
private getRemoteRtpParams(
|
|
632
|
+
media: MediaDescription,
|
|
589
633
|
transceiver: RTCRtpTransceiver
|
|
590
634
|
): RTCRtpReceiveParameters {
|
|
591
|
-
if (transceiver.mLineIndex == undefined)
|
|
592
|
-
throw new Error("mLineIndex not assigned");
|
|
593
|
-
const media = remoteDescription.media[transceiver.mLineIndex];
|
|
594
|
-
if (!media) throw new Error("media line not exist");
|
|
595
|
-
|
|
596
635
|
const receiveParameters: RTCRtpReceiveParameters = {
|
|
597
636
|
muxId: media.rtp.muxId,
|
|
598
637
|
rtcp: media.rtp.rtcp,
|
|
@@ -632,155 +671,98 @@ export class RTCPeerConnection extends EventTarget {
|
|
|
632
671
|
remoteSdp.type = sessionDescription.type;
|
|
633
672
|
this.validateDescription(remoteSdp, false);
|
|
634
673
|
|
|
674
|
+
if (remoteSdp.type === "answer") {
|
|
675
|
+
this.currentRemoteDescription = remoteSdp;
|
|
676
|
+
this.pendingRemoteDescription = undefined;
|
|
677
|
+
} else {
|
|
678
|
+
this.pendingRemoteDescription = remoteSdp;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
const bundle = remoteSdp.group.find(
|
|
682
|
+
(g) =>
|
|
683
|
+
g.semantic === "BUNDLE" && this.configuration.bundlePolicy !== "disable"
|
|
684
|
+
);
|
|
685
|
+
let bundleTransport: RTCDtlsTransport | undefined;
|
|
686
|
+
|
|
635
687
|
// # apply description
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
688
|
+
await Promise.all(
|
|
689
|
+
enumerate(remoteSdp.media).map(async ([i, remoteMedia]) => {
|
|
690
|
+
let dtlsTransport: RTCDtlsTransport | undefined;
|
|
691
|
+
|
|
692
|
+
if (["audio", "video"].includes(remoteMedia.kind)) {
|
|
693
|
+
let transceiver = this.transceivers.find(
|
|
640
694
|
(t) =>
|
|
641
695
|
t.kind === remoteMedia.kind &&
|
|
642
696
|
[undefined, remoteMedia.rtp.muxId].includes(t.mid)
|
|
643
|
-
)
|
|
644
|
-
(
|
|
697
|
+
);
|
|
698
|
+
if (!transceiver) {
|
|
645
699
|
// create remote transceiver
|
|
646
|
-
|
|
700
|
+
transceiver = this.addTransceiver(remoteMedia.kind, {
|
|
647
701
|
direction: "recvonly",
|
|
648
702
|
});
|
|
649
|
-
|
|
650
703
|
this.onRemoteTransceiverAdded.execute(transceiver);
|
|
651
|
-
|
|
704
|
+
}
|
|
652
705
|
|
|
653
|
-
|
|
654
|
-
|
|
706
|
+
if (bundle) {
|
|
707
|
+
if (!bundleTransport) {
|
|
708
|
+
bundleTransport = transceiver.dtlsTransport;
|
|
709
|
+
} else {
|
|
710
|
+
transceiver.setDtlsTransport(bundleTransport);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
655
713
|
|
|
656
|
-
|
|
657
|
-
transceiver.mid = remoteMedia.rtp.muxId;
|
|
658
|
-
transceiver.mLineIndex = i;
|
|
659
|
-
}
|
|
714
|
+
dtlsTransport = transceiver.dtlsTransport;
|
|
660
715
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
const existCodec = findCodecByMimeType(localCodecs, remoteCodec);
|
|
666
|
-
if (!existCodec) return false;
|
|
667
|
-
|
|
668
|
-
if (existCodec?.name.toLowerCase() === "rtx") {
|
|
669
|
-
const params = codecParametersFromString(
|
|
670
|
-
existCodec.parameters ?? ""
|
|
671
|
-
);
|
|
672
|
-
const pt = params["apt"];
|
|
673
|
-
const origin = remoteMedia.rtp.codecs.find(
|
|
674
|
-
(c) => c.payloadType === pt
|
|
675
|
-
);
|
|
676
|
-
if (!origin) return false;
|
|
677
|
-
return !!findCodecByMimeType(localCodecs, origin);
|
|
716
|
+
this.setRemoteRTP(transceiver, remoteMedia, remoteSdp.type, i);
|
|
717
|
+
} else if (remoteMedia.kind === "application") {
|
|
718
|
+
if (!this.sctpTransport) {
|
|
719
|
+
this.sctpTransport = this.createSctpTransport();
|
|
678
720
|
}
|
|
679
721
|
|
|
680
|
-
|
|
681
|
-
|
|
722
|
+
if (bundle) {
|
|
723
|
+
if (!bundleTransport) {
|
|
724
|
+
bundleTransport = this.sctpTransport.dtlsTransport;
|
|
725
|
+
} else {
|
|
726
|
+
this.sctpTransport.setDtlsTransport(bundleTransport);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
682
729
|
|
|
683
|
-
|
|
684
|
-
if (transceiver.codecs.length === 0) {
|
|
685
|
-
throw new Error("negotiate codecs failed.");
|
|
686
|
-
}
|
|
687
|
-
transceiver.headerExtensions = remoteMedia.rtp.headerExtensions.filter(
|
|
688
|
-
(extension) =>
|
|
689
|
-
(
|
|
690
|
-
this.configuration.headerExtensions[
|
|
691
|
-
remoteMedia.kind as "video" | "audio"
|
|
692
|
-
] || []
|
|
693
|
-
).find((v) => v.uri === extension.uri)
|
|
694
|
-
);
|
|
730
|
+
dtlsTransport = this.sctpTransport.dtlsTransport;
|
|
695
731
|
|
|
696
|
-
|
|
697
|
-
const mediaDirection = remoteMedia.direction || "inactive";
|
|
698
|
-
const direction = reverseDirection(mediaDirection);
|
|
699
|
-
if (["answer", "pranswer"].includes(remoteSdp.type)) {
|
|
700
|
-
transceiver.currentDirection = direction;
|
|
732
|
+
this.setRemoteSCTP(remoteMedia, this.sctpTransport);
|
|
701
733
|
} else {
|
|
702
|
-
|
|
734
|
+
throw new Error("invalid media kind");
|
|
703
735
|
}
|
|
704
736
|
|
|
705
|
-
const
|
|
706
|
-
transceiver.sender.prepareSend(localParams);
|
|
707
|
-
|
|
708
|
-
if (["recvonly", "sendrecv"].includes(transceiver.direction)) {
|
|
709
|
-
const remotePrams = this.remoteRtp(remoteSdp, transceiver);
|
|
737
|
+
const iceTransport = dtlsTransport.iceTransport;
|
|
710
738
|
|
|
711
|
-
|
|
712
|
-
remoteMedia.
|
|
713
|
-
|
|
714
|
-
transceiver,
|
|
715
|
-
param,
|
|
716
|
-
remotePrams
|
|
717
|
-
);
|
|
718
|
-
});
|
|
739
|
+
if (remoteMedia.iceParams && remoteMedia.dtlsParams) {
|
|
740
|
+
iceTransport.setRemoteParams(remoteMedia.iceParams);
|
|
741
|
+
dtlsTransport.setRemoteParams(remoteMedia.dtlsParams);
|
|
719
742
|
|
|
720
|
-
|
|
721
|
-
//
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
if (["sendonly", "sendrecv"].includes(mediaDirection)) {
|
|
725
|
-
// assign msid
|
|
726
|
-
if (remoteMedia.msid != undefined) {
|
|
727
|
-
const [streamId, trackId] = remoteMedia.msid.split(" ");
|
|
728
|
-
transceiver.receiver.remoteStreamId = streamId;
|
|
729
|
-
transceiver.receiver.remoteTrackId = trackId;
|
|
730
|
-
|
|
731
|
-
this.fireOnTrack(
|
|
732
|
-
transceiver.receiver.track,
|
|
733
|
-
transceiver,
|
|
734
|
-
new MediaStream({
|
|
735
|
-
id: streamId,
|
|
736
|
-
tracks: [transceiver.receiver.track],
|
|
737
|
-
})
|
|
738
|
-
);
|
|
743
|
+
// One agent full, one lite: The full agent MUST take the controlling role, and the lite agent MUST take the controlled role
|
|
744
|
+
// RFC 8445 S6.1.1
|
|
745
|
+
if (remoteMedia.iceParams?.iceLite) {
|
|
746
|
+
iceTransport.connection.iceControlling = true;
|
|
739
747
|
}
|
|
740
748
|
}
|
|
741
749
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
// # configure sctp
|
|
745
|
-
this.sctpRemotePort = remoteMedia.sctpPort;
|
|
746
|
-
if (!this.sctpRemotePort) {
|
|
747
|
-
throw new Error("sctpRemotePort not exist");
|
|
748
|
-
}
|
|
749
|
-
if (!this.sctpTransport) {
|
|
750
|
-
this.sctpTransport = this.createSctpTransport();
|
|
751
|
-
}
|
|
752
|
-
this.sctpTransport.setRemotePort(this.sctpRemotePort);
|
|
753
|
-
if (!this.sctpTransport.mid) {
|
|
754
|
-
this.sctpTransport.mid = remoteMedia.rtp.muxId;
|
|
755
|
-
}
|
|
756
|
-
}
|
|
750
|
+
// # add ICE candidates
|
|
751
|
+
remoteMedia.iceCandidates.forEach(iceTransport.addRemoteCandidate);
|
|
757
752
|
|
|
758
|
-
|
|
759
|
-
this.iceTransport.setRemoteParams(remoteMedia.iceParams);
|
|
760
|
-
this.dtlsTransport.setRemoteParams(remoteMedia.dtlsParams);
|
|
753
|
+
await iceTransport.iceGather.gather();
|
|
761
754
|
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
if (remoteMedia.iceParams?.iceLite) {
|
|
765
|
-
this.iceTransport.connection.iceControlling = true;
|
|
755
|
+
if (remoteMedia.iceCandidatesComplete) {
|
|
756
|
+
await iceTransport.addRemoteCandidate(undefined);
|
|
766
757
|
}
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
// # add ICE candidates
|
|
770
|
-
remoteMedia.iceCandidates.forEach(this.iceTransport.addRemoteCandidate);
|
|
771
|
-
|
|
772
|
-
await this.iceTransport.iceGather.gather();
|
|
773
758
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
remoteMedia.dtlsParams.role === "client" ? "server" : "client";
|
|
782
|
-
}
|
|
783
|
-
}
|
|
759
|
+
// # set DTLS role
|
|
760
|
+
if (remoteSdp.type === "answer" && remoteMedia.dtlsParams?.role) {
|
|
761
|
+
dtlsTransport.role =
|
|
762
|
+
remoteMedia.dtlsParams.role === "client" ? "server" : "client";
|
|
763
|
+
}
|
|
764
|
+
})
|
|
765
|
+
);
|
|
784
766
|
|
|
785
767
|
// connect transports
|
|
786
768
|
if (remoteSdp.type === "answer") {
|
|
@@ -797,19 +779,115 @@ export class RTCPeerConnection extends EventTarget {
|
|
|
797
779
|
this.setSignalingState("stable");
|
|
798
780
|
}
|
|
799
781
|
|
|
800
|
-
if (remoteSdp.type === "answer") {
|
|
801
|
-
this.currentRemoteDescription = remoteSdp;
|
|
802
|
-
this.pendingRemoteDescription = undefined;
|
|
803
|
-
} else {
|
|
804
|
-
this.pendingRemoteDescription = remoteSdp;
|
|
805
|
-
}
|
|
806
|
-
|
|
807
782
|
this.negotiationneeded = false;
|
|
808
783
|
if (this.shouldNegotiationneeded) {
|
|
809
784
|
this.needNegotiation();
|
|
810
785
|
}
|
|
811
786
|
}
|
|
812
787
|
|
|
788
|
+
private setRemoteRTP(
|
|
789
|
+
transceiver: RTCRtpTransceiver,
|
|
790
|
+
remoteMedia: MediaDescription,
|
|
791
|
+
type: "offer" | "answer",
|
|
792
|
+
mLineIndex: number
|
|
793
|
+
) {
|
|
794
|
+
if (!transceiver.mid) {
|
|
795
|
+
transceiver.mid = remoteMedia.rtp.muxId;
|
|
796
|
+
transceiver.mLineIndex = mLineIndex;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// # negotiate codecs
|
|
800
|
+
transceiver.codecs = remoteMedia.rtp.codecs.filter((remoteCodec) => {
|
|
801
|
+
const localCodecs = this.configuration.codecs[remoteMedia.kind] || [];
|
|
802
|
+
|
|
803
|
+
const existCodec = findCodecByMimeType(localCodecs, remoteCodec);
|
|
804
|
+
if (!existCodec) return false;
|
|
805
|
+
|
|
806
|
+
if (existCodec?.name.toLowerCase() === "rtx") {
|
|
807
|
+
const params = codecParametersFromString(existCodec.parameters ?? "");
|
|
808
|
+
const pt = params["apt"];
|
|
809
|
+
const origin = remoteMedia.rtp.codecs.find((c) => c.payloadType === pt);
|
|
810
|
+
if (!origin) return false;
|
|
811
|
+
return !!findCodecByMimeType(localCodecs, origin);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
return true;
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
log("negotiated codecs", transceiver.codecs);
|
|
818
|
+
if (transceiver.codecs.length === 0) {
|
|
819
|
+
throw new Error("negotiate codecs failed.");
|
|
820
|
+
}
|
|
821
|
+
transceiver.headerExtensions = remoteMedia.rtp.headerExtensions.filter(
|
|
822
|
+
(extension) =>
|
|
823
|
+
(
|
|
824
|
+
this.configuration.headerExtensions[
|
|
825
|
+
remoteMedia.kind as "video" | "audio"
|
|
826
|
+
] || []
|
|
827
|
+
).find((v) => v.uri === extension.uri)
|
|
828
|
+
);
|
|
829
|
+
|
|
830
|
+
// # configure direction
|
|
831
|
+
const mediaDirection = remoteMedia.direction || "inactive";
|
|
832
|
+
const direction = reverseDirection(mediaDirection);
|
|
833
|
+
if (["answer", "pranswer"].includes(type)) {
|
|
834
|
+
transceiver.currentDirection = direction;
|
|
835
|
+
} else {
|
|
836
|
+
transceiver.offerDirection = direction;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
const localParams = this.getLocalRtpParams(transceiver);
|
|
840
|
+
transceiver.sender.prepareSend(localParams);
|
|
841
|
+
|
|
842
|
+
if (["recvonly", "sendrecv"].includes(transceiver.direction)) {
|
|
843
|
+
const remotePrams = this.getRemoteRtpParams(remoteMedia, transceiver);
|
|
844
|
+
|
|
845
|
+
// register simulcast receiver
|
|
846
|
+
remoteMedia.simulcastParameters.forEach((param) => {
|
|
847
|
+
this.router.registerRtpReceiverByRid(transceiver, param, remotePrams);
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
transceiver.receiver.prepareReceive(remotePrams);
|
|
851
|
+
// register ssrc receiver
|
|
852
|
+
this.router.registerRtpReceiverBySsrc(transceiver, remotePrams);
|
|
853
|
+
}
|
|
854
|
+
if (["sendonly", "sendrecv"].includes(mediaDirection)) {
|
|
855
|
+
// assign msid
|
|
856
|
+
if (remoteMedia.msid != undefined) {
|
|
857
|
+
const [streamId, trackId] = remoteMedia.msid.split(" ");
|
|
858
|
+
transceiver.receiver.remoteStreamId = streamId;
|
|
859
|
+
transceiver.receiver.remoteTrackId = trackId;
|
|
860
|
+
|
|
861
|
+
this.fireOnTrack(
|
|
862
|
+
transceiver.receiver.track,
|
|
863
|
+
transceiver,
|
|
864
|
+
new MediaStream({
|
|
865
|
+
id: streamId,
|
|
866
|
+
tracks: [transceiver.receiver.track],
|
|
867
|
+
})
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
transceiver.receiver.setupTWCC(remoteMedia.ssrc[0]?.ssrc);
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
private setRemoteSCTP(
|
|
876
|
+
remoteMedia: MediaDescription,
|
|
877
|
+
sctpTransport: RTCSctpTransport
|
|
878
|
+
) {
|
|
879
|
+
// # configure sctp
|
|
880
|
+
this.sctpRemotePort = remoteMedia.sctpPort;
|
|
881
|
+
if (!this.sctpRemotePort) {
|
|
882
|
+
throw new Error("sctpRemotePort not exist");
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
sctpTransport.setRemotePort(this.sctpRemotePort);
|
|
886
|
+
if (!sctpTransport.mid) {
|
|
887
|
+
sctpTransport.mid = remoteMedia.rtp.muxId;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
813
891
|
private validateDescription(
|
|
814
892
|
description: SessionDescription,
|
|
815
893
|
isLocal: boolean
|
|
@@ -888,14 +966,19 @@ export class RTCPeerConnection extends EventTarget {
|
|
|
888
966
|
|
|
889
967
|
const direction = options.direction || "sendrecv";
|
|
890
968
|
|
|
891
|
-
const
|
|
892
|
-
|
|
969
|
+
const dtlsTransport = this.createTransport([
|
|
970
|
+
SRTP_PROFILE.SRTP_AEAD_AES_128_GCM, // prefer
|
|
971
|
+
SRTP_PROFILE.SRTP_AES128_CM_HMAC_SHA1_80,
|
|
972
|
+
]);
|
|
973
|
+
|
|
974
|
+
const sender = new RTCRtpSender(trackOrKind);
|
|
975
|
+
const receiver = new RTCRtpReceiver(kind, sender.ssrc);
|
|
893
976
|
const transceiver = new RTCRtpTransceiver(
|
|
894
977
|
kind,
|
|
978
|
+
dtlsTransport,
|
|
895
979
|
receiver,
|
|
896
980
|
sender,
|
|
897
|
-
direction
|
|
898
|
-
this.dtlsTransport
|
|
981
|
+
direction
|
|
899
982
|
);
|
|
900
983
|
transceiver.options = options;
|
|
901
984
|
this.router.registerRtpSender(transceiver.sender);
|
|
@@ -903,6 +986,7 @@ export class RTCPeerConnection extends EventTarget {
|
|
|
903
986
|
this.transceivers.push(transceiver);
|
|
904
987
|
this.onTransceiverAdded.execute(transceiver);
|
|
905
988
|
|
|
989
|
+
this.updateIceConnectionState();
|
|
906
990
|
this.needNegotiation();
|
|
907
991
|
|
|
908
992
|
return transceiver;
|
|
@@ -971,66 +1055,91 @@ export class RTCPeerConnection extends EventTarget {
|
|
|
971
1055
|
}
|
|
972
1056
|
}
|
|
973
1057
|
|
|
1058
|
+
private async ensureCerts() {
|
|
1059
|
+
const ensureCert = async (dtlsTransport: RTCDtlsTransport) => {
|
|
1060
|
+
if (this.certificates.length === 0) {
|
|
1061
|
+
const localCertificate = await dtlsTransport.setupCertificate();
|
|
1062
|
+
this.certificates.push(localCertificate);
|
|
1063
|
+
} else {
|
|
1064
|
+
dtlsTransport.localCertificate = this.certificates[0];
|
|
1065
|
+
}
|
|
1066
|
+
};
|
|
1067
|
+
|
|
1068
|
+
for (const dtlsTransport of this.dtlsTransports) {
|
|
1069
|
+
await ensureCert(dtlsTransport);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
|
|
974
1073
|
async createAnswer() {
|
|
975
1074
|
this.assertNotClosed();
|
|
976
1075
|
if (
|
|
977
1076
|
!["have-remote-offer", "have-local-pranswer"].includes(
|
|
978
1077
|
this.signalingState
|
|
979
|
-
)
|
|
980
|
-
|
|
981
|
-
)
|
|
1078
|
+
)
|
|
1079
|
+
) {
|
|
982
1080
|
throw new Error("createAnswer failed");
|
|
983
|
-
|
|
984
|
-
if (this.certificates.length === 0) {
|
|
985
|
-
await this.dtlsTransport.setupCertificate();
|
|
986
1081
|
}
|
|
1082
|
+
if (!this._remoteDescription) {
|
|
1083
|
+
throw new Error("wrong state");
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
await this.ensureCerts();
|
|
987
1087
|
|
|
988
1088
|
const description = new SessionDescription();
|
|
989
1089
|
addSDPHeader("answer", description);
|
|
990
1090
|
|
|
991
|
-
this._remoteDescription
|
|
1091
|
+
this._remoteDescription.media.forEach((remoteMedia) => {
|
|
992
1092
|
let dtlsTransport!: RTCDtlsTransport;
|
|
993
1093
|
let media: MediaDescription;
|
|
994
1094
|
|
|
995
|
-
if (["audio", "video"].includes(
|
|
996
|
-
const transceiver = this.getTransceiverByMid(
|
|
1095
|
+
if (["audio", "video"].includes(remoteMedia.kind)) {
|
|
1096
|
+
const transceiver = this.getTransceiverByMid(remoteMedia.rtp.muxId!)!;
|
|
997
1097
|
media = createMediaDescriptionForTransceiver(
|
|
998
1098
|
transceiver,
|
|
999
1099
|
this.cname,
|
|
1000
1100
|
andDirection(transceiver.direction, transceiver.offerDirection),
|
|
1001
1101
|
transceiver.mid!
|
|
1002
1102
|
);
|
|
1003
|
-
if (!transceiver.dtlsTransport) throw new Error();
|
|
1004
1103
|
dtlsTransport = transceiver.dtlsTransport;
|
|
1005
|
-
} else if (
|
|
1006
|
-
if (!this.sctpTransport || !this.sctpTransport.mid)
|
|
1104
|
+
} else if (remoteMedia.kind === "application") {
|
|
1105
|
+
if (!this.sctpTransport || !this.sctpTransport.mid) {
|
|
1106
|
+
throw new Error("sctpTransport not found");
|
|
1107
|
+
}
|
|
1007
1108
|
media = createMediaDescriptionForSctp(
|
|
1008
1109
|
this.sctpTransport,
|
|
1009
1110
|
this.sctpTransport.mid
|
|
1010
1111
|
);
|
|
1011
1112
|
|
|
1012
1113
|
dtlsTransport = this.sctpTransport.dtlsTransport;
|
|
1013
|
-
} else
|
|
1114
|
+
} else {
|
|
1115
|
+
throw new Error("invalid kind");
|
|
1116
|
+
}
|
|
1014
1117
|
|
|
1015
1118
|
// # determine DTLS role, or preserve the currently configured role
|
|
1016
|
-
if (!media.dtlsParams)
|
|
1119
|
+
if (!media.dtlsParams) {
|
|
1120
|
+
throw new Error("dtlsParams missing");
|
|
1121
|
+
}
|
|
1017
1122
|
if (dtlsTransport.role === "auto") {
|
|
1018
1123
|
media.dtlsParams.role = "client";
|
|
1019
1124
|
} else {
|
|
1020
1125
|
media.dtlsParams.role = dtlsTransport.role;
|
|
1021
1126
|
}
|
|
1022
|
-
|
|
1127
|
+
|
|
1128
|
+
media.simulcastParameters = remoteMedia.simulcastParameters.map((v) => ({
|
|
1023
1129
|
...v,
|
|
1024
1130
|
direction: reverseSimulcastDirection(v.direction),
|
|
1025
1131
|
}));
|
|
1132
|
+
|
|
1026
1133
|
description.media.push(media);
|
|
1027
1134
|
});
|
|
1028
1135
|
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1136
|
+
if (this.configuration.bundlePolicy !== "disable") {
|
|
1137
|
+
const bundle = new GroupDescription("BUNDLE", []);
|
|
1138
|
+
description.media.forEach((media) => {
|
|
1139
|
+
bundle.items.push(media.rtp.muxId!);
|
|
1140
|
+
});
|
|
1141
|
+
description.group.push(bundle);
|
|
1142
|
+
}
|
|
1034
1143
|
|
|
1035
1144
|
return description.toJSON();
|
|
1036
1145
|
}
|
|
@@ -1050,29 +1159,94 @@ export class RTCPeerConnection extends EventTarget {
|
|
|
1050
1159
|
if (this.sctpTransport) {
|
|
1051
1160
|
await this.sctpTransport.stop();
|
|
1052
1161
|
}
|
|
1053
|
-
|
|
1054
|
-
|
|
1162
|
+
for (const dtlsTransport of this.dtlsTransports) {
|
|
1163
|
+
await dtlsTransport.stop();
|
|
1164
|
+
await dtlsTransport.iceTransport.stop();
|
|
1165
|
+
}
|
|
1055
1166
|
|
|
1056
1167
|
this.dispose();
|
|
1057
1168
|
log("peerConnection closed");
|
|
1058
1169
|
}
|
|
1059
1170
|
|
|
1060
1171
|
private assertNotClosed() {
|
|
1061
|
-
if (this.isClosed)
|
|
1172
|
+
if (this.isClosed) {
|
|
1173
|
+
throw new Error("RTCPeerConnection is closed");
|
|
1174
|
+
}
|
|
1062
1175
|
}
|
|
1063
1176
|
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1177
|
+
// https://w3c.github.io/webrtc-pc/#dom-rtcicegatheringstate
|
|
1178
|
+
private updateIceGatheringState() {
|
|
1179
|
+
const all = this.iceTransports;
|
|
1180
|
+
|
|
1181
|
+
function allMatch(...state: IceGathererState[]) {
|
|
1182
|
+
return (
|
|
1183
|
+
all.filter((check) => state.includes(check.iceGather.gatheringState))
|
|
1184
|
+
.length === all.length
|
|
1185
|
+
);
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
let newState: IceGathererState;
|
|
1189
|
+
|
|
1190
|
+
if (all.length && allMatch("complete")) {
|
|
1191
|
+
newState = "complete";
|
|
1192
|
+
} else if (!all.length || allMatch("new", "complete")) {
|
|
1193
|
+
newState = "new";
|
|
1194
|
+
} else if (
|
|
1195
|
+
all.map((check) => check.iceGather.gatheringState).includes("gathering")
|
|
1196
|
+
) {
|
|
1197
|
+
newState = "gathering";
|
|
1198
|
+
} else {
|
|
1199
|
+
newState = "new";
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
if (this.iceGatheringState === newState) {
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
log("iceGatheringStateChange", newState);
|
|
1207
|
+
this.iceGatheringState = newState;
|
|
1208
|
+
this.iceGatheringStateChange.execute(newState);
|
|
1209
|
+
this.emit("icegatheringstatechange", newState);
|
|
1069
1210
|
}
|
|
1070
1211
|
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1212
|
+
// https://w3c.github.io/webrtc-pc/#dom-rtciceconnectionstate
|
|
1213
|
+
private updateIceConnectionState() {
|
|
1214
|
+
const all = this.iceTransports;
|
|
1215
|
+
let newState: RTCIceConnectionState;
|
|
1216
|
+
|
|
1217
|
+
function allMatch(...state: RTCIceConnectionState[]) {
|
|
1218
|
+
return (
|
|
1219
|
+
all.filter((check) => state.includes(check.state)).length === all.length
|
|
1220
|
+
);
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
if (this.connectionState === "closed") {
|
|
1224
|
+
newState = "closed";
|
|
1225
|
+
} else if (allMatch("failed")) {
|
|
1226
|
+
newState = "failed";
|
|
1227
|
+
} else if (allMatch("disconnected")) {
|
|
1228
|
+
newState = "disconnected";
|
|
1229
|
+
} else if (allMatch("new", "closed")) {
|
|
1230
|
+
newState = "new";
|
|
1231
|
+
} else if (allMatch("new", "checking")) {
|
|
1232
|
+
newState = "checking";
|
|
1233
|
+
} else if (allMatch("completed", "closed")) {
|
|
1234
|
+
newState = "completed";
|
|
1235
|
+
} else if (allMatch("connected", "completed", "closed")) {
|
|
1236
|
+
newState = "connected";
|
|
1237
|
+
} else {
|
|
1238
|
+
// unreachable?
|
|
1239
|
+
newState = "new";
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
if (this.iceConnectionState === newState) {
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
log("iceConnectionStateChange", newState);
|
|
1247
|
+
this.iceConnectionState = newState;
|
|
1248
|
+
this.iceConnectionStateChange.execute(newState);
|
|
1249
|
+
this.emit("iceconnectionstatechange", newState);
|
|
1076
1250
|
}
|
|
1077
1251
|
|
|
1078
1252
|
private setSignalingState(state: RTCSignalingState) {
|
|
@@ -1209,6 +1383,8 @@ export function allocateMid(mids: Set<string>) {
|
|
|
1209
1383
|
return mid;
|
|
1210
1384
|
}
|
|
1211
1385
|
|
|
1386
|
+
export type BundlePolicy = "max-compat" | "max-bundle" | "disable";
|
|
1387
|
+
|
|
1212
1388
|
export interface PeerConfig {
|
|
1213
1389
|
codecs: Partial<{
|
|
1214
1390
|
audio: RTCRtpCodecParameters[];
|
|
@@ -1225,6 +1401,7 @@ export interface PeerConfig {
|
|
|
1225
1401
|
dtls: Partial<{
|
|
1226
1402
|
keys: DtlsKeys;
|
|
1227
1403
|
}>;
|
|
1404
|
+
bundlePolicy: BundlePolicy;
|
|
1228
1405
|
}
|
|
1229
1406
|
|
|
1230
1407
|
export const findCodecByMimeType = (
|
|
@@ -1276,6 +1453,7 @@ export const defaultPeerConfig: PeerConfig = {
|
|
|
1276
1453
|
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
|
|
1277
1454
|
icePortRange: undefined,
|
|
1278
1455
|
dtls: {},
|
|
1456
|
+
bundlePolicy: "max-compat",
|
|
1279
1457
|
};
|
|
1280
1458
|
|
|
1281
1459
|
export interface RTCTrackEvent {
|