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.
Files changed (64) hide show
  1. package/lib/common/src/binary.d.ts +0 -1
  2. package/lib/common/src/binary.js +1 -2
  3. package/lib/common/src/binary.js.map +1 -1
  4. package/lib/dtls/src/context/cipher.js +5 -2
  5. package/lib/dtls/src/context/cipher.js.map +1 -1
  6. package/lib/dtls/src/handshake/extensions/useSrtp.js +5 -2
  7. package/lib/dtls/src/handshake/extensions/useSrtp.js.map +1 -1
  8. package/lib/ice/src/candidate.js +5 -2
  9. package/lib/ice/src/candidate.js.map +1 -1
  10. package/lib/ice/src/ice.d.ts +2 -2
  11. package/lib/ice/src/ice.js +19 -8
  12. package/lib/ice/src/ice.js.map +1 -1
  13. package/lib/ice/src/stun/attributes.js +5 -2
  14. package/lib/ice/src/stun/attributes.js.map +1 -1
  15. package/lib/rtp/src/rtcp/rr.js +5 -2
  16. package/lib/rtp/src/rtcp/rr.js.map +1 -1
  17. package/lib/rtp/src/rtcp/rtpfb/nack.js +6 -3
  18. package/lib/rtp/src/rtcp/rtpfb/nack.js.map +1 -1
  19. package/lib/rtp/src/rtcp/rtpfb/twcc.js +6 -6
  20. package/lib/rtp/src/rtcp/rtpfb/twcc.js.map +1 -1
  21. package/lib/rtp/src/rtcp/sr.js +5 -2
  22. package/lib/rtp/src/rtcp/sr.js.map +1 -1
  23. package/lib/rtp/src/srtp/cipher/ctr.js +5 -2
  24. package/lib/rtp/src/srtp/cipher/ctr.js.map +1 -1
  25. package/lib/rtp/src/srtp/cipher/gcm.js +6 -3
  26. package/lib/rtp/src/srtp/cipher/gcm.js.map +1 -1
  27. package/lib/sctp/src/param.js +5 -2
  28. package/lib/sctp/src/param.js.map +1 -1
  29. package/lib/sctp/src/sctp.js +5 -5
  30. package/lib/sctp/src/sctp.js.map +1 -1
  31. package/lib/webrtc/src/dataChannel.js +3 -2
  32. package/lib/webrtc/src/dataChannel.js.map +1 -1
  33. package/lib/webrtc/src/media/receiver/nack.js +2 -2
  34. package/lib/webrtc/src/media/receiver/nack.js.map +1 -1
  35. package/lib/webrtc/src/media/rtpReceiver.d.ts +3 -2
  36. package/lib/webrtc/src/media/rtpReceiver.js +4 -2
  37. package/lib/webrtc/src/media/rtpReceiver.js.map +1 -1
  38. package/lib/webrtc/src/media/rtpSender.d.ts +4 -2
  39. package/lib/webrtc/src/media/rtpSender.js +15 -7
  40. package/lib/webrtc/src/media/rtpSender.js.map +1 -1
  41. package/lib/webrtc/src/media/rtpTransceiver.d.ts +7 -6
  42. package/lib/webrtc/src/media/rtpTransceiver.js +10 -3
  43. package/lib/webrtc/src/media/rtpTransceiver.js.map +1 -1
  44. package/lib/webrtc/src/peerConnection.d.ts +10 -10
  45. package/lib/webrtc/src/peerConnection.js +356 -211
  46. package/lib/webrtc/src/peerConnection.js.map +1 -1
  47. package/lib/webrtc/src/sdp.js +5 -2
  48. package/lib/webrtc/src/sdp.js.map +1 -1
  49. package/lib/webrtc/src/transport/dtls.d.ts +3 -2
  50. package/lib/webrtc/src/transport/dtls.js +3 -0
  51. package/lib/webrtc/src/transport/dtls.js.map +1 -1
  52. package/lib/webrtc/src/transport/sctp.d.ts +5 -3
  53. package/lib/webrtc/src/transport/sctp.js +46 -34
  54. package/lib/webrtc/src/transport/sctp.js.map +1 -1
  55. package/package.json +2 -2
  56. package/src/dataChannel.ts +4 -2
  57. package/src/media/receiver/nack.ts +1 -1
  58. package/src/media/rtpReceiver.ts +6 -5
  59. package/src/media/rtpSender.ts +18 -9
  60. package/src/media/rtpTransceiver.ts +17 -6
  61. package/src/peerConnection.ts +431 -253
  62. package/src/sdp.ts +1 -1
  63. package/src/transport/dtls.ts +4 -1
  64. package/src/transport/sctp.ts +53 -33
@@ -24,7 +24,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
25
  exports.defaultPeerConfig = exports.findCodecByMimeType = exports.allocateMid = exports.addTransportDescription = exports.createMediaDescriptionForSctp = exports.createMediaDescriptionForTransceiver = exports.RTCPeerConnection = void 0;
26
26
  const debug_1 = __importDefault(require("debug"));
27
- const lodash_1 = require("lodash");
27
+ const cloneDeep_1 = __importDefault(require("lodash/cloneDeep"));
28
+ const isEqual_1 = __importDefault(require("lodash/isEqual"));
28
29
  const rx_mini_1 = __importDefault(require("rx.mini"));
29
30
  const uuid = __importStar(require("uuid"));
30
31
  const _1 = require(".");
@@ -44,11 +45,11 @@ const sctp_1 = require("./transport/sctp");
44
45
  const utils_1 = require("./utils");
45
46
  const log = (0, debug_1.default)("werift:packages/webrtc/src/peerConnection.ts");
46
47
  class RTCPeerConnection extends helper_1.EventTarget {
47
- constructor({ codecs, headerExtensions, iceServers, iceTransportPolicy, icePortRange, dtls, } = {}) {
48
+ constructor({ codecs, headerExtensions, iceServers, iceTransportPolicy, icePortRange, dtls, bundlePolicy, } = {}) {
48
49
  super();
49
50
  this.cname = uuid.v4();
50
51
  this.masterTransportEstablished = false;
51
- this.configuration = (0, lodash_1.cloneDeep)(exports.defaultPeerConfig);
52
+ this.configuration = (0, cloneDeep_1.default)(exports.defaultPeerConfig);
52
53
  this.connectionState = "new";
53
54
  this.iceConnectionState = "new";
54
55
  this.iceGatheringState = "new";
@@ -61,11 +62,6 @@ class RTCPeerConnection extends helper_1.EventTarget {
61
62
  this.connectionStateChange = new rx_mini_1.default();
62
63
  this.onDataChannel = new rx_mini_1.default();
63
64
  this.onRemoteTransceiverAdded = new rx_mini_1.default();
64
- /**
65
- * should use onRemoteTransceiverAdded
66
- * @deprecated
67
- */
68
- this.onTransceiver = new rx_mini_1.default();
69
65
  this.onTransceiverAdded = new rx_mini_1.default();
70
66
  this.onIceCandidate = new rx_mini_1.default();
71
67
  this.onNegotiationneeded = new rx_mini_1.default();
@@ -148,12 +144,24 @@ class RTCPeerConnection extends helper_1.EventTarget {
148
144
  break;
149
145
  }
150
146
  });
151
- const { iceTransport, dtlsTransport } = this.createTransport([
152
- const_1.SRTP_PROFILE.SRTP_AEAD_AES_128_GCM,
153
- const_1.SRTP_PROFILE.SRTP_AES128_CM_HMAC_SHA1_80,
154
- ]);
155
- this.iceTransport = iceTransport;
156
- this.dtlsTransport = dtlsTransport;
147
+ if (bundlePolicy) {
148
+ this.configuration.bundlePolicy = bundlePolicy;
149
+ }
150
+ }
151
+ get dtlsTransports() {
152
+ const transports = this.transceivers.map((t) => t.dtlsTransport);
153
+ if (this.sctpTransport) {
154
+ transports.push(this.sctpTransport.dtlsTransport);
155
+ }
156
+ return transports.reduce((acc, cur) => {
157
+ if (!acc.map((d) => d.id).includes(cur.id)) {
158
+ acc.push(cur);
159
+ }
160
+ return acc;
161
+ }, []);
162
+ }
163
+ get iceTransports() {
164
+ return this.dtlsTransports.map((d) => d.iceTransport);
157
165
  }
158
166
  get localDescription() {
159
167
  if (!this._localDescription)
@@ -178,9 +186,7 @@ class RTCPeerConnection extends helper_1.EventTarget {
178
186
  return this.transceivers.find((transceiver) => transceiver.mLineIndex === index);
179
187
  }
180
188
  async createOffer() {
181
- if (this.certificates.length === 0) {
182
- await this.dtlsTransport.setupCertificate();
183
- }
189
+ await this.ensureCerts();
184
190
  this.transceivers.forEach((transceiver) => {
185
191
  transceiver.codecs = this.configuration.codecs[transceiver.kind];
186
192
  transceiver.headerExtensions =
@@ -222,11 +228,13 @@ class RTCPeerConnection extends helper_1.EventTarget {
222
228
  !description.media.find((m) => this.sctpTransport.mid === m.rtp.muxId)) {
223
229
  description.media.push(createMediaDescriptionForSctp(this.sctpTransport, allocateMid(this.seenMid)));
224
230
  }
225
- const mids = description.media
226
- .map((m) => m.rtp.muxId)
227
- .filter((v) => v);
228
- const bundle = new sdp_1.GroupDescription("BUNDLE", mids);
229
- description.group.push(bundle);
231
+ if (this.configuration.bundlePolicy !== "disable") {
232
+ const mids = description.media
233
+ .map((m) => m.rtp.muxId)
234
+ .filter((v) => v);
235
+ const bundle = new sdp_1.GroupDescription("BUNDLE", mids);
236
+ description.group.push(bundle);
237
+ }
230
238
  return description.toJSON();
231
239
  }
232
240
  createDataChannel(label, options = {}) {
@@ -252,7 +260,8 @@ class RTCPeerConnection extends helper_1.EventTarget {
252
260
  ordered: settings.ordered,
253
261
  protocol: settings.protocol,
254
262
  });
255
- return new dataChannel_1.RTCDataChannel(this.sctpTransport, parameters);
263
+ const channel = new dataChannel_1.RTCDataChannel(this.sctpTransport, parameters);
264
+ return channel;
256
265
  }
257
266
  removeTrack(sender) {
258
267
  if (this.isClosed)
@@ -277,20 +286,31 @@ class RTCPeerConnection extends helper_1.EventTarget {
277
286
  this.needNegotiation();
278
287
  }
279
288
  createTransport(srtpProfiles = []) {
289
+ const [existing] = this.iceTransports;
290
+ // Gather ICE candidates for only one track. If the remote endpoint is not bundle-aware, negotiate only one media track.
291
+ // https://w3c.github.io/webrtc-pc/#rtcbundlepolicy-enum
292
+ if (this.configuration.bundlePolicy === "max-bundle") {
293
+ if (existing) {
294
+ return this.dtlsTransports[0];
295
+ }
296
+ }
280
297
  const iceGatherer = new ice_1.RTCIceGatherer({
281
298
  ...(0, utils_1.parseIceServers)(this.configuration.iceServers),
282
299
  forceTurn: this.configuration.iceTransportPolicy === "relay",
283
300
  portRange: this.configuration.icePortRange,
284
301
  });
285
- iceGatherer.onGatheringStateChange.subscribe((state) => {
286
- this.updateIceGatheringState(state);
302
+ if (existing) {
303
+ iceGatherer.connection.localUserName = existing.connection.localUserName;
304
+ iceGatherer.connection.localPassword = existing.connection.localPassword;
305
+ }
306
+ iceGatherer.onGatheringStateChange.subscribe(() => {
307
+ this.updateIceGatheringState();
287
308
  });
288
- this.updateIceGatheringState(iceGatherer.gatheringState);
309
+ this.updateIceGatheringState();
289
310
  const iceTransport = new ice_1.RTCIceTransport(iceGatherer);
290
- iceTransport.onStateChange.subscribe((state) => {
291
- this.updateIceConnectionState(state);
311
+ iceTransport.onStateChange.subscribe(() => {
312
+ this.updateIceConnectionState();
292
313
  });
293
- this.updateIceConnectionState(iceTransport.state);
294
314
  iceTransport.iceGather.onIceCandidate = (candidate) => {
295
315
  if (!this.localDescription)
296
316
  return;
@@ -302,7 +322,6 @@ class RTCPeerConnection extends helper_1.EventTarget {
302
322
  }
303
323
  candidate.sdpMLineIndex = 0;
304
324
  candidate.sdpMid = media.rtp.muxId;
305
- // for chrome & firefox & maybe others
306
325
  candidate.foundation = "candidate:" + candidate.foundation;
307
326
  this.onIceCandidate.execute(candidate.toJSON());
308
327
  if (this.onicecandidate)
@@ -310,10 +329,15 @@ class RTCPeerConnection extends helper_1.EventTarget {
310
329
  this.emit("icecandidate", { candidate });
311
330
  };
312
331
  const dtlsTransport = new dtls_1.RTCDtlsTransport(iceTransport, this.router, this.certificates, srtpProfiles);
313
- return { dtlsTransport, iceTransport };
332
+ return dtlsTransport;
314
333
  }
315
334
  createSctpTransport() {
316
- const sctp = new sctp_1.RTCSctpTransport(this.dtlsTransport);
335
+ const dtlsTransport = this.createTransport([
336
+ const_1.SRTP_PROFILE.SRTP_AEAD_AES_128_GCM,
337
+ const_1.SRTP_PROFILE.SRTP_AES128_CM_HMAC_SHA1_80,
338
+ ]);
339
+ const sctp = new sctp_1.RTCSctpTransport();
340
+ sctp.setDtlsTransport(dtlsTransport);
317
341
  sctp.mid = undefined;
318
342
  sctp.onDataChannel.subscribe((channel) => {
319
343
  this.onDataChannel.execute(channel);
@@ -322,6 +346,8 @@ class RTCPeerConnection extends helper_1.EventTarget {
322
346
  this.ondatachannel(event);
323
347
  this.emit("datachannel", event);
324
348
  });
349
+ this.sctpTransport = sctp;
350
+ this.updateIceConnectionState();
325
351
  return sctp;
326
352
  }
327
353
  async setLocalDescription(sessionDescription) {
@@ -339,9 +365,6 @@ class RTCPeerConnection extends helper_1.EventTarget {
339
365
  // # assign MID
340
366
  description.media.forEach((media, i) => {
341
367
  const mid = media.rtp.muxId;
342
- if (!mid) {
343
- throw new Error("mid not exist");
344
- }
345
368
  this.seenMid.add(mid);
346
369
  if (["audio", "video"].includes(media.kind)) {
347
370
  const transceiver = this.getTransceiverByMLineIndex(i);
@@ -353,26 +376,30 @@ class RTCPeerConnection extends helper_1.EventTarget {
353
376
  this.sctpTransport.mid = mid;
354
377
  }
355
378
  });
356
- // # set ICE role
357
- if (description.type === "offer") {
358
- this.iceTransport.connection.iceControlling = true;
359
- }
360
- else {
361
- this.iceTransport.connection.iceControlling = false;
362
- }
363
- // One agent full, one lite: The full agent MUST take the controlling role, and the lite agent MUST take the controlled role
364
- // RFC 8445 S6.1.1
365
- if (this.iceTransport.connection.remoteIsLite) {
366
- this.iceTransport.connection.iceControlling = true;
367
- }
368
- // # set DTLS role for mediasoup
369
- if (description.type === "answer") {
370
- const role = description.media.find((media) => media.dtlsParams)
371
- ?.dtlsParams?.role;
372
- if (role) {
373
- this.dtlsTransport.role = role;
379
+ const setupRole = (dtlsTransport) => {
380
+ const iceTransport = dtlsTransport.iceTransport;
381
+ // # set ICE role
382
+ if (description.type === "offer") {
383
+ iceTransport.connection.iceControlling = true;
374
384
  }
375
- }
385
+ else {
386
+ iceTransport.connection.iceControlling = false;
387
+ }
388
+ // One agent full, one lite: The full agent MUST take the controlling role, and the lite agent MUST take the controlled role
389
+ // RFC 8445 S6.1.1
390
+ if (iceTransport.connection.remoteIsLite) {
391
+ iceTransport.connection.iceControlling = true;
392
+ }
393
+ // # set DTLS role for mediasoup
394
+ if (description.type === "answer") {
395
+ const role = description.media.find((media) => media.dtlsParams)
396
+ ?.dtlsParams?.role;
397
+ if (role) {
398
+ dtlsTransport.role = role;
399
+ }
400
+ }
401
+ };
402
+ this.dtlsTransports.forEach((d) => setupRole(d));
376
403
  // # configure direction
377
404
  this.transceivers.forEach((t) => {
378
405
  if (["answer", "pranswer"].includes(description.type)) {
@@ -383,10 +410,18 @@ class RTCPeerConnection extends helper_1.EventTarget {
383
410
  // for trickle ice
384
411
  this.setLocal(description);
385
412
  // # gather candidates
386
- await this.iceTransport.iceGather.gather();
387
- description.media.map((media) => {
388
- addTransportDescription(media, this.dtlsTransport);
413
+ for (const iceTransport of this.iceTransports) {
414
+ await iceTransport.iceGather.gather();
415
+ }
416
+ description.media
417
+ .filter((m) => ["audio", "video"].includes(m.kind))
418
+ .forEach((m, i) => {
419
+ addTransportDescription(m, this.transceivers[i].dtlsTransport);
389
420
  });
421
+ const sctpMedia = description.media.find((m) => m.kind === "application");
422
+ if (this.sctpTransport && sctpMedia) {
423
+ addTransportDescription(sctpMedia, this.sctpTransport.dtlsTransport);
424
+ }
390
425
  this.setLocal(description);
391
426
  // connect transports
392
427
  if (description.type === "answer") {
@@ -412,33 +447,36 @@ class RTCPeerConnection extends helper_1.EventTarget {
412
447
  }
413
448
  async addIceCandidate(candidateMessage) {
414
449
  const candidate = ice_1.IceCandidate.fromJSON(candidateMessage);
415
- await this.iceTransport.addRemoteCandidate(candidate);
450
+ for (const iceTransport of this.iceTransports) {
451
+ await iceTransport.addRemoteCandidate(candidate);
452
+ }
416
453
  }
417
454
  async connect() {
418
455
  if (this.masterTransportEstablished)
419
456
  return;
420
- const dtlsTransport = this.dtlsTransport;
421
- const iceTransport = dtlsTransport.iceTransport;
422
457
  this.setConnectionState("connecting");
423
- await iceTransport.start().catch((err) => {
424
- log("iceTransport.start failed", err);
425
- throw err;
426
- });
427
- log("ice connected");
428
- await dtlsTransport.start().catch((err) => {
429
- log("dtlsTransport.start failed", err);
430
- throw err;
431
- });
432
- log("dtls connected");
433
- if (this.sctpTransport && this.sctpRemotePort) {
434
- await this.sctpTransport.start(this.sctpRemotePort);
435
- await this.sctpTransport.sctp.stateChanged.connected.asPromise();
436
- }
437
- log("sctp connected");
458
+ await Promise.all(this.dtlsTransports.map(async (dtlsTransport) => {
459
+ const { iceTransport } = dtlsTransport;
460
+ await iceTransport.start().catch((err) => {
461
+ log("iceTransport.start failed", err);
462
+ throw err;
463
+ });
464
+ await dtlsTransport.start().catch((err) => {
465
+ log("dtlsTransport.start failed", err);
466
+ throw err;
467
+ });
468
+ if (this.sctpTransport &&
469
+ this.sctpRemotePort &&
470
+ this.sctpTransport.dtlsTransport.id === dtlsTransport.id) {
471
+ await this.sctpTransport.start(this.sctpRemotePort);
472
+ await this.sctpTransport.sctp.stateChanged.connected.asPromise();
473
+ log("sctp connected");
474
+ }
475
+ }));
438
476
  this.masterTransportEstablished = true;
439
477
  this.setConnectionState("connected");
440
478
  }
441
- localRtp(transceiver) {
479
+ getLocalRtpParams(transceiver) {
442
480
  if (transceiver.mid == undefined)
443
481
  throw new Error("mid not assigned");
444
482
  const rtp = {
@@ -449,12 +487,7 @@ class RTCPeerConnection extends helper_1.EventTarget {
449
487
  };
450
488
  return rtp;
451
489
  }
452
- remoteRtp(remoteDescription, transceiver) {
453
- if (transceiver.mLineIndex == undefined)
454
- throw new Error("mLineIndex not assigned");
455
- const media = remoteDescription.media[transceiver.mLineIndex];
456
- if (!media)
457
- throw new Error("media line not exist");
490
+ getRemoteRtpParams(media, transceiver) {
458
491
  const receiveParameters = {
459
492
  muxId: media.rtp.muxId,
460
493
  rtcp: media.rtp.rtcp,
@@ -483,115 +516,79 @@ class RTCPeerConnection extends helper_1.EventTarget {
483
516
  const remoteSdp = sdp_1.SessionDescription.parse(sessionDescription.sdp);
484
517
  remoteSdp.type = sessionDescription.type;
485
518
  this.validateDescription(remoteSdp, false);
519
+ if (remoteSdp.type === "answer") {
520
+ this.currentRemoteDescription = remoteSdp;
521
+ this.pendingRemoteDescription = undefined;
522
+ }
523
+ else {
524
+ this.pendingRemoteDescription = remoteSdp;
525
+ }
526
+ const bundle = remoteSdp.group.find((g) => g.semantic === "BUNDLE" && this.configuration.bundlePolicy !== "disable");
527
+ let bundleTransport;
486
528
  // # apply description
487
- for (const [i, remoteMedia] of (0, helper_1.enumerate)(remoteSdp.media)) {
529
+ await Promise.all((0, helper_1.enumerate)(remoteSdp.media).map(async ([i, remoteMedia]) => {
530
+ let dtlsTransport;
488
531
  if (["audio", "video"].includes(remoteMedia.kind)) {
489
- const transceiver = this.transceivers.find((t) => t.kind === remoteMedia.kind &&
490
- [undefined, remoteMedia.rtp.muxId].includes(t.mid)) ||
491
- (() => {
492
- // create remote transceiver
493
- const transceiver = this.addTransceiver(remoteMedia.kind, {
494
- direction: "recvonly",
495
- });
496
- this.onRemoteTransceiverAdded.execute(transceiver);
497
- this.onTransceiver.execute(transceiver);
498
- return transceiver;
499
- })();
500
- if (!transceiver.mid) {
501
- transceiver.mid = remoteMedia.rtp.muxId;
502
- transceiver.mLineIndex = i;
503
- }
504
- // # negotiate codecs
505
- transceiver.codecs = remoteMedia.rtp.codecs.filter((remoteCodec) => {
506
- const localCodecs = this.configuration.codecs[remoteMedia.kind] || [];
507
- const existCodec = (0, exports.findCodecByMimeType)(localCodecs, remoteCodec);
508
- if (!existCodec)
509
- return false;
510
- if (existCodec?.name.toLowerCase() === "rtx") {
511
- const params = (0, _1.codecParametersFromString)(existCodec.parameters ?? "");
512
- const pt = params["apt"];
513
- const origin = remoteMedia.rtp.codecs.find((c) => c.payloadType === pt);
514
- if (!origin)
515
- return false;
516
- return !!(0, exports.findCodecByMimeType)(localCodecs, origin);
517
- }
518
- return true;
519
- });
520
- log("negotiated codecs", transceiver.codecs);
521
- if (transceiver.codecs.length === 0) {
522
- throw new Error("negotiate codecs failed.");
523
- }
524
- transceiver.headerExtensions = remoteMedia.rtp.headerExtensions.filter((extension) => (this.configuration.headerExtensions[remoteMedia.kind] || []).find((v) => v.uri === extension.uri));
525
- // # configure direction
526
- const mediaDirection = remoteMedia.direction || "inactive";
527
- const direction = (0, utils_1.reverseDirection)(mediaDirection);
528
- if (["answer", "pranswer"].includes(remoteSdp.type)) {
529
- transceiver.currentDirection = direction;
530
- }
531
- else {
532
- transceiver.offerDirection = direction;
533
- }
534
- const localParams = this.localRtp(transceiver);
535
- transceiver.sender.prepareSend(localParams);
536
- if (["recvonly", "sendrecv"].includes(transceiver.direction)) {
537
- const remotePrams = this.remoteRtp(remoteSdp, transceiver);
538
- // register simulcast receiver
539
- remoteMedia.simulcastParameters.forEach((param) => {
540
- this.router.registerRtpReceiverByRid(transceiver, param, remotePrams);
532
+ let transceiver = this.transceivers.find((t) => t.kind === remoteMedia.kind &&
533
+ [undefined, remoteMedia.rtp.muxId].includes(t.mid));
534
+ if (!transceiver) {
535
+ // create remote transceiver
536
+ transceiver = this.addTransceiver(remoteMedia.kind, {
537
+ direction: "recvonly",
541
538
  });
542
- transceiver.receiver.prepareReceive(remotePrams);
543
- // register ssrc receiver
544
- this.router.registerRtpReceiverBySsrc(transceiver, remotePrams);
539
+ this.onRemoteTransceiverAdded.execute(transceiver);
545
540
  }
546
- if (["sendonly", "sendrecv"].includes(mediaDirection)) {
547
- // assign msid
548
- if (remoteMedia.msid != undefined) {
549
- const [streamId, trackId] = remoteMedia.msid.split(" ");
550
- transceiver.receiver.remoteStreamId = streamId;
551
- transceiver.receiver.remoteTrackId = trackId;
552
- this.fireOnTrack(transceiver.receiver.track, transceiver, new track_1.MediaStream({
553
- id: streamId,
554
- tracks: [transceiver.receiver.track],
555
- }));
541
+ if (bundle) {
542
+ if (!bundleTransport) {
543
+ bundleTransport = transceiver.dtlsTransport;
544
+ }
545
+ else {
546
+ transceiver.setDtlsTransport(bundleTransport);
556
547
  }
557
548
  }
558
- transceiver.receiver.setupTWCC(remoteMedia.ssrc[0]?.ssrc);
549
+ dtlsTransport = transceiver.dtlsTransport;
550
+ this.setRemoteRTP(transceiver, remoteMedia, remoteSdp.type, i);
559
551
  }
560
552
  else if (remoteMedia.kind === "application") {
561
- // # configure sctp
562
- this.sctpRemotePort = remoteMedia.sctpPort;
563
- if (!this.sctpRemotePort) {
564
- throw new Error("sctpRemotePort not exist");
565
- }
566
553
  if (!this.sctpTransport) {
567
554
  this.sctpTransport = this.createSctpTransport();
568
555
  }
569
- this.sctpTransport.setRemotePort(this.sctpRemotePort);
570
- if (!this.sctpTransport.mid) {
571
- this.sctpTransport.mid = remoteMedia.rtp.muxId;
556
+ if (bundle) {
557
+ if (!bundleTransport) {
558
+ bundleTransport = this.sctpTransport.dtlsTransport;
559
+ }
560
+ else {
561
+ this.sctpTransport.setDtlsTransport(bundleTransport);
562
+ }
572
563
  }
564
+ dtlsTransport = this.sctpTransport.dtlsTransport;
565
+ this.setRemoteSCTP(remoteMedia, this.sctpTransport);
573
566
  }
567
+ else {
568
+ throw new Error("invalid media kind");
569
+ }
570
+ const iceTransport = dtlsTransport.iceTransport;
574
571
  if (remoteMedia.iceParams && remoteMedia.dtlsParams) {
575
- this.iceTransport.setRemoteParams(remoteMedia.iceParams);
576
- this.dtlsTransport.setRemoteParams(remoteMedia.dtlsParams);
572
+ iceTransport.setRemoteParams(remoteMedia.iceParams);
573
+ dtlsTransport.setRemoteParams(remoteMedia.dtlsParams);
577
574
  // One agent full, one lite: The full agent MUST take the controlling role, and the lite agent MUST take the controlled role
578
575
  // RFC 8445 S6.1.1
579
576
  if (remoteMedia.iceParams?.iceLite) {
580
- this.iceTransport.connection.iceControlling = true;
577
+ iceTransport.connection.iceControlling = true;
581
578
  }
582
579
  }
583
580
  // # add ICE candidates
584
- remoteMedia.iceCandidates.forEach(this.iceTransport.addRemoteCandidate);
585
- await this.iceTransport.iceGather.gather();
581
+ remoteMedia.iceCandidates.forEach(iceTransport.addRemoteCandidate);
582
+ await iceTransport.iceGather.gather();
586
583
  if (remoteMedia.iceCandidatesComplete) {
587
- await this.iceTransport.addRemoteCandidate(undefined);
584
+ await iceTransport.addRemoteCandidate(undefined);
588
585
  }
589
586
  // # set DTLS role
590
587
  if (remoteSdp.type === "answer" && remoteMedia.dtlsParams?.role) {
591
- this.dtlsTransport.role =
588
+ dtlsTransport.role =
592
589
  remoteMedia.dtlsParams.role === "client" ? "server" : "client";
593
590
  }
594
- }
591
+ }));
595
592
  // connect transports
596
593
  if (remoteSdp.type === "answer") {
597
594
  log("caller start connect");
@@ -606,18 +603,83 @@ class RTCPeerConnection extends helper_1.EventTarget {
606
603
  else if (remoteSdp.type === "answer") {
607
604
  this.setSignalingState("stable");
608
605
  }
609
- if (remoteSdp.type === "answer") {
610
- this.currentRemoteDescription = remoteSdp;
611
- this.pendingRemoteDescription = undefined;
612
- }
613
- else {
614
- this.pendingRemoteDescription = remoteSdp;
615
- }
616
606
  this.negotiationneeded = false;
617
607
  if (this.shouldNegotiationneeded) {
618
608
  this.needNegotiation();
619
609
  }
620
610
  }
611
+ setRemoteRTP(transceiver, remoteMedia, type, mLineIndex) {
612
+ if (!transceiver.mid) {
613
+ transceiver.mid = remoteMedia.rtp.muxId;
614
+ transceiver.mLineIndex = mLineIndex;
615
+ }
616
+ // # negotiate codecs
617
+ transceiver.codecs = remoteMedia.rtp.codecs.filter((remoteCodec) => {
618
+ const localCodecs = this.configuration.codecs[remoteMedia.kind] || [];
619
+ const existCodec = (0, exports.findCodecByMimeType)(localCodecs, remoteCodec);
620
+ if (!existCodec)
621
+ return false;
622
+ if (existCodec?.name.toLowerCase() === "rtx") {
623
+ const params = (0, _1.codecParametersFromString)(existCodec.parameters ?? "");
624
+ const pt = params["apt"];
625
+ const origin = remoteMedia.rtp.codecs.find((c) => c.payloadType === pt);
626
+ if (!origin)
627
+ return false;
628
+ return !!(0, exports.findCodecByMimeType)(localCodecs, origin);
629
+ }
630
+ return true;
631
+ });
632
+ log("negotiated codecs", transceiver.codecs);
633
+ if (transceiver.codecs.length === 0) {
634
+ throw new Error("negotiate codecs failed.");
635
+ }
636
+ transceiver.headerExtensions = remoteMedia.rtp.headerExtensions.filter((extension) => (this.configuration.headerExtensions[remoteMedia.kind] || []).find((v) => v.uri === extension.uri));
637
+ // # configure direction
638
+ const mediaDirection = remoteMedia.direction || "inactive";
639
+ const direction = (0, utils_1.reverseDirection)(mediaDirection);
640
+ if (["answer", "pranswer"].includes(type)) {
641
+ transceiver.currentDirection = direction;
642
+ }
643
+ else {
644
+ transceiver.offerDirection = direction;
645
+ }
646
+ const localParams = this.getLocalRtpParams(transceiver);
647
+ transceiver.sender.prepareSend(localParams);
648
+ if (["recvonly", "sendrecv"].includes(transceiver.direction)) {
649
+ const remotePrams = this.getRemoteRtpParams(remoteMedia, transceiver);
650
+ // register simulcast receiver
651
+ remoteMedia.simulcastParameters.forEach((param) => {
652
+ this.router.registerRtpReceiverByRid(transceiver, param, remotePrams);
653
+ });
654
+ transceiver.receiver.prepareReceive(remotePrams);
655
+ // register ssrc receiver
656
+ this.router.registerRtpReceiverBySsrc(transceiver, remotePrams);
657
+ }
658
+ if (["sendonly", "sendrecv"].includes(mediaDirection)) {
659
+ // assign msid
660
+ if (remoteMedia.msid != undefined) {
661
+ const [streamId, trackId] = remoteMedia.msid.split(" ");
662
+ transceiver.receiver.remoteStreamId = streamId;
663
+ transceiver.receiver.remoteTrackId = trackId;
664
+ this.fireOnTrack(transceiver.receiver.track, transceiver, new track_1.MediaStream({
665
+ id: streamId,
666
+ tracks: [transceiver.receiver.track],
667
+ }));
668
+ }
669
+ }
670
+ transceiver.receiver.setupTWCC(remoteMedia.ssrc[0]?.ssrc);
671
+ }
672
+ setRemoteSCTP(remoteMedia, sctpTransport) {
673
+ // # configure sctp
674
+ this.sctpRemotePort = remoteMedia.sctpPort;
675
+ if (!this.sctpRemotePort) {
676
+ throw new Error("sctpRemotePort not exist");
677
+ }
678
+ sctpTransport.setRemotePort(this.sctpRemotePort);
679
+ if (!sctpTransport.mid) {
680
+ sctpTransport.mid = remoteMedia.rtp.muxId;
681
+ }
682
+ }
621
683
  validateDescription(description, isLocal) {
622
684
  if (isLocal) {
623
685
  if (description.type === "offer") {
@@ -656,7 +718,7 @@ class RTCPeerConnection extends helper_1.EventTarget {
656
718
  throw new Error();
657
719
  const offerMedia = offer.media.map((v) => [v.kind, v.rtp.muxId]);
658
720
  const answerMedia = description.media.map((v) => [v.kind, v.rtp.muxId]);
659
- if (!(0, lodash_1.isEqual)(offerMedia, answerMedia))
721
+ if (!(0, isEqual_1.default)(offerMedia, answerMedia))
660
722
  throw new Error("Media sections in answer do not match offer");
661
723
  }
662
724
  }
@@ -674,13 +736,18 @@ class RTCPeerConnection extends helper_1.EventTarget {
674
736
  addTransceiver(trackOrKind, options = {}) {
675
737
  const kind = typeof trackOrKind === "string" ? trackOrKind : trackOrKind.kind;
676
738
  const direction = options.direction || "sendrecv";
677
- const sender = new rtpSender_1.RTCRtpSender(trackOrKind, this.dtlsTransport);
678
- const receiver = new rtpReceiver_1.RTCRtpReceiver(kind, this.dtlsTransport, sender.ssrc);
679
- const transceiver = new rtpTransceiver_1.RTCRtpTransceiver(kind, receiver, sender, direction, this.dtlsTransport);
739
+ const dtlsTransport = this.createTransport([
740
+ const_1.SRTP_PROFILE.SRTP_AEAD_AES_128_GCM,
741
+ const_1.SRTP_PROFILE.SRTP_AES128_CM_HMAC_SHA1_80,
742
+ ]);
743
+ const sender = new rtpSender_1.RTCRtpSender(trackOrKind);
744
+ const receiver = new rtpReceiver_1.RTCRtpReceiver(kind, sender.ssrc);
745
+ const transceiver = new rtpTransceiver_1.RTCRtpTransceiver(kind, dtlsTransport, receiver, sender, direction);
680
746
  transceiver.options = options;
681
747
  this.router.registerRtpSender(transceiver.sender);
682
748
  this.transceivers.push(transceiver);
683
749
  this.onTransceiverAdded.execute(transceiver);
750
+ this.updateIceConnectionState();
684
751
  this.needNegotiation();
685
752
  return transceiver;
686
753
  }
@@ -735,54 +802,72 @@ class RTCPeerConnection extends helper_1.EventTarget {
735
802
  return transceiver.sender;
736
803
  }
737
804
  }
805
+ async ensureCerts() {
806
+ const ensureCert = async (dtlsTransport) => {
807
+ if (this.certificates.length === 0) {
808
+ const localCertificate = await dtlsTransport.setupCertificate();
809
+ this.certificates.push(localCertificate);
810
+ }
811
+ else {
812
+ dtlsTransport.localCertificate = this.certificates[0];
813
+ }
814
+ };
815
+ for (const dtlsTransport of this.dtlsTransports) {
816
+ await ensureCert(dtlsTransport);
817
+ }
818
+ }
738
819
  async createAnswer() {
739
820
  this.assertNotClosed();
740
- if (!["have-remote-offer", "have-local-pranswer"].includes(this.signalingState) ||
741
- !this.dtlsTransport)
821
+ if (!["have-remote-offer", "have-local-pranswer"].includes(this.signalingState)) {
742
822
  throw new Error("createAnswer failed");
743
- if (this.certificates.length === 0) {
744
- await this.dtlsTransport.setupCertificate();
745
823
  }
824
+ if (!this._remoteDescription) {
825
+ throw new Error("wrong state");
826
+ }
827
+ await this.ensureCerts();
746
828
  const description = new sdp_1.SessionDescription();
747
829
  (0, sdp_1.addSDPHeader)("answer", description);
748
- this._remoteDescription?.media.forEach((remoteM) => {
830
+ this._remoteDescription.media.forEach((remoteMedia) => {
749
831
  let dtlsTransport;
750
832
  let media;
751
- if (["audio", "video"].includes(remoteM.kind)) {
752
- const transceiver = this.getTransceiverByMid(remoteM.rtp.muxId);
833
+ if (["audio", "video"].includes(remoteMedia.kind)) {
834
+ const transceiver = this.getTransceiverByMid(remoteMedia.rtp.muxId);
753
835
  media = createMediaDescriptionForTransceiver(transceiver, this.cname, (0, utils_1.andDirection)(transceiver.direction, transceiver.offerDirection), transceiver.mid);
754
- if (!transceiver.dtlsTransport)
755
- throw new Error();
756
836
  dtlsTransport = transceiver.dtlsTransport;
757
837
  }
758
- else if (remoteM.kind === "application") {
759
- if (!this.sctpTransport || !this.sctpTransport.mid)
760
- throw new Error();
838
+ else if (remoteMedia.kind === "application") {
839
+ if (!this.sctpTransport || !this.sctpTransport.mid) {
840
+ throw new Error("sctpTransport not found");
841
+ }
761
842
  media = createMediaDescriptionForSctp(this.sctpTransport, this.sctpTransport.mid);
762
843
  dtlsTransport = this.sctpTransport.dtlsTransport;
763
844
  }
764
- else
765
- throw new Error();
845
+ else {
846
+ throw new Error("invalid kind");
847
+ }
766
848
  // # determine DTLS role, or preserve the currently configured role
767
- if (!media.dtlsParams)
768
- throw new Error();
849
+ if (!media.dtlsParams) {
850
+ throw new Error("dtlsParams missing");
851
+ }
769
852
  if (dtlsTransport.role === "auto") {
770
853
  media.dtlsParams.role = "client";
771
854
  }
772
855
  else {
773
856
  media.dtlsParams.role = dtlsTransport.role;
774
857
  }
775
- media.simulcastParameters = remoteM.simulcastParameters.map((v) => ({
858
+ media.simulcastParameters = remoteMedia.simulcastParameters.map((v) => ({
776
859
  ...v,
777
860
  direction: (0, utils_1.reverseSimulcastDirection)(v.direction),
778
861
  }));
779
862
  description.media.push(media);
780
863
  });
781
- const bundle = new sdp_1.GroupDescription("BUNDLE", []);
782
- description.media.forEach((media) => {
783
- bundle.items.push(media.rtp.muxId);
784
- });
785
- description.group.push(bundle);
864
+ if (this.configuration.bundlePolicy !== "disable") {
865
+ const bundle = new sdp_1.GroupDescription("BUNDLE", []);
866
+ description.media.forEach((media) => {
867
+ bundle.items.push(media.rtp.muxId);
868
+ });
869
+ description.group.push(bundle);
870
+ }
786
871
  return description.toJSON();
787
872
  }
788
873
  async close() {
@@ -798,26 +883,85 @@ class RTCPeerConnection extends helper_1.EventTarget {
798
883
  if (this.sctpTransport) {
799
884
  await this.sctpTransport.stop();
800
885
  }
801
- await this.dtlsTransport.stop();
802
- await this.iceTransport.stop();
886
+ for (const dtlsTransport of this.dtlsTransports) {
887
+ await dtlsTransport.stop();
888
+ await dtlsTransport.iceTransport.stop();
889
+ }
803
890
  this.dispose();
804
891
  log("peerConnection closed");
805
892
  }
806
893
  assertNotClosed() {
807
- if (this.isClosed)
894
+ if (this.isClosed) {
808
895
  throw new Error("RTCPeerConnection is closed");
896
+ }
809
897
  }
810
- updateIceGatheringState(state) {
811
- log("iceGatheringStateChange", state);
812
- this.iceGatheringState = state;
813
- this.iceGatheringStateChange.execute(state);
814
- this.emit("icegatheringstatechange", state);
898
+ // https://w3c.github.io/webrtc-pc/#dom-rtcicegatheringstate
899
+ updateIceGatheringState() {
900
+ const all = this.iceTransports;
901
+ function allMatch(...state) {
902
+ return (all.filter((check) => state.includes(check.iceGather.gatheringState))
903
+ .length === all.length);
904
+ }
905
+ let newState;
906
+ if (all.length && allMatch("complete")) {
907
+ newState = "complete";
908
+ }
909
+ else if (!all.length || allMatch("new", "complete")) {
910
+ newState = "new";
911
+ }
912
+ else if (all.map((check) => check.iceGather.gatheringState).includes("gathering")) {
913
+ newState = "gathering";
914
+ }
915
+ else {
916
+ newState = "new";
917
+ }
918
+ if (this.iceGatheringState === newState) {
919
+ return;
920
+ }
921
+ log("iceGatheringStateChange", newState);
922
+ this.iceGatheringState = newState;
923
+ this.iceGatheringStateChange.execute(newState);
924
+ this.emit("icegatheringstatechange", newState);
815
925
  }
816
- updateIceConnectionState(state) {
817
- log("iceConnectionStateChange", state);
818
- this.iceConnectionState = state;
819
- this.iceConnectionStateChange.execute(state);
820
- this.emit("iceconnectionstatechange", state);
926
+ // https://w3c.github.io/webrtc-pc/#dom-rtciceconnectionstate
927
+ updateIceConnectionState() {
928
+ const all = this.iceTransports;
929
+ let newState;
930
+ function allMatch(...state) {
931
+ return (all.filter((check) => state.includes(check.state)).length === all.length);
932
+ }
933
+ if (this.connectionState === "closed") {
934
+ newState = "closed";
935
+ }
936
+ else if (allMatch("failed")) {
937
+ newState = "failed";
938
+ }
939
+ else if (allMatch("disconnected")) {
940
+ newState = "disconnected";
941
+ }
942
+ else if (allMatch("new", "closed")) {
943
+ newState = "new";
944
+ }
945
+ else if (allMatch("new", "checking")) {
946
+ newState = "checking";
947
+ }
948
+ else if (allMatch("completed", "closed")) {
949
+ newState = "completed";
950
+ }
951
+ else if (allMatch("connected", "completed", "closed")) {
952
+ newState = "connected";
953
+ }
954
+ else {
955
+ // unreachable?
956
+ newState = "new";
957
+ }
958
+ if (this.iceConnectionState === newState) {
959
+ return;
960
+ }
961
+ log("iceConnectionStateChange", newState);
962
+ this.iceConnectionState = newState;
963
+ this.iceConnectionStateChange.execute(newState);
964
+ this.emit("iceconnectionstatechange", newState);
821
965
  }
822
966
  setSignalingState(state) {
823
967
  log("signalingStateChange", state);
@@ -958,5 +1102,6 @@ exports.defaultPeerConfig = {
958
1102
  iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
959
1103
  icePortRange: undefined,
960
1104
  dtls: {},
1105
+ bundlePolicy: "max-compat",
961
1106
  };
962
1107
  //# sourceMappingURL=peerConnection.js.map