@opentok/client 2.28.3 → 2.28.4-alpha.10

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.
@@ -597,7 +597,7 @@ declare namespace OT {
597
597
  getAudioVolume(): number;
598
598
  getImgData(): string | null;
599
599
  getStats(callback: (error?: OTError, stats?: SubscriberStats) => void): void;
600
- getRtcStatsReport(): Promise<RTCStatsReport>;
600
+ getRtcStatsReport(): Promise<Map<string, any>>;
601
601
  subscribeToCaptions(value: boolean): Promise<void>;
602
602
  isSubscribedToCaptions(): boolean;
603
603
  isAudioBlocked(): boolean;
@@ -1,11 +1,11 @@
1
1
  /**
2
- * @license OpenTok.js 2.28.3 2898b53
2
+ * @license OpenTok.js 2.28.4 24429b3
3
3
  *
4
4
  * Copyright (c) 2010-2024 TokBox, Inc.
5
5
  * Subject to the applicable Software Development Kit (SDK) License Agreement:
6
6
  * https://www.vonage.com/legal/communications-apis/terms-of-use/
7
7
  *
8
- * Date: Fri, 20 Sep 2024 18:57:18 GMT
8
+ * Date: Thu, 24 Oct 2024 14:39:58 GMT
9
9
  */
10
10
 
11
11
  (function webpackUniversalModuleDefinition(root, factory) {
@@ -5631,8 +5631,8 @@ SDPHelpers.getIceCredentials = sdpLines => {
5631
5631
  * Update ice credentials in one SDP with new credentials.
5632
5632
  * @param {Array<string>} currentSdpLines - An array of SDP lines that need to be updated with new credentials.
5633
5633
  * @param {Object} iceCredentials - The iceCredentials object.
5634
- * @property {string} ufrag - A string representing the ufrag in the ICE credentials.
5635
- * @property {string} pwd - A string representing the password in the ICE credentials.
5634
+ * @property {string} ufrag - A string representing the ufrag line in the ICE credentials, e.g. 'a=ice-ufrag:myUFrag'
5635
+ * @property {string} pwd - A string representing the password line in the ICE credentials, e.g. 'a=ice-pwd:myPwd'
5636
5636
  * @returns {Array<string>} - An array of updated SDP lines with the new credentials.
5637
5637
  */
5638
5638
  SDPHelpers.updateIceCredentials = (currentSdpLines, credentials) => currentSdpLines.map(line => {
@@ -5648,31 +5648,51 @@ SDPHelpers.updateIceCredentials = (currentSdpLines, credentials) => currentSdpLi
5648
5648
  * Add ice credentials in the media section.
5649
5649
  * @param {Array<string>} mediaSection - An array of SDP lines that need to be updated with credentials.
5650
5650
  * @param {Object} iceCredentials - The iceCredentials object.
5651
- * @property {string} ufrag - A string representing the ufrag in the ICE credentials.
5652
- * @property {string} pwd - A string representing the password in the ICE credentials.
5651
+ * @property {string} ufrag - A string representing the ufrag line in the ICE credentials, e.g. 'a=ice-ufrag:myUFrag'
5652
+ * @property {string} pwd - A string representing the password line in the ICE credentials, e.g. 'a=ice-pwd:myPwd'
5653
5653
  * @returns {Array<string>} - An array of updated SDP lines with the new credentials.
5654
5654
  */
5655
5655
  SDPHelpers.addIceCredentialsToSection = (mediaSection, _ref3) => {
5656
5656
  let ufrag = _ref3.ufrag,
5657
5657
  pwd = _ref3.pwd;
5658
5658
  const mediaSectionWithCredentials = [...mediaSection];
5659
+ const midPrefix = 'a=mid:';
5660
+ const midLineIndex = mediaSectionWithCredentials.findIndex(line => line.startsWith(midPrefix));
5661
+ if (midLineIndex >= 0) {
5662
+ mediaSectionWithCredentials.splice(midLineIndex, 0, ufrag, pwd);
5663
+ }
5664
+ return mediaSectionWithCredentials;
5665
+ };
5666
+
5667
+ /**
5668
+ * Add ice candidates in the media section.
5669
+ * @param {Array<string>} mediaSection - An array of SDP lines that need to be updated with candidates.
5670
+ * @param {Array<string>} headers - An array of SDP lines with the headers
5671
+ * @returns {Array<string>} - An array of updated SDP lines with the new candidates.
5672
+ */
5673
+ SDPHelpers.addIceCandidatesToSection = (mediaSection, headers) => {
5674
+ const mediaSectionWithCredentials = [...mediaSection];
5659
5675
  const candidatePrefix = 'a=candidate:';
5660
- const candidateLineIndex = mediaSectionWithCredentials.findIndex(line => line.startsWith(candidatePrefix));
5661
- if (candidateLineIndex >= 0) {
5662
- mediaSectionWithCredentials.splice(candidateLineIndex, 0, ufrag, pwd);
5676
+ const endCandidate = 'a=end-of-candidates';
5677
+ const candidateLines = headers.filter(line => line.startsWith(candidatePrefix) || line === endCandidate);
5678
+ const midPrefix = 'a=mid:';
5679
+ const midLineIndex = mediaSectionWithCredentials.findIndex(line => line.startsWith(midPrefix));
5680
+ if (midLineIndex >= 0 && candidateLines.length) {
5681
+ mediaSectionWithCredentials.splice(midLineIndex, 0, ...candidateLines);
5663
5682
  }
5664
5683
  return mediaSectionWithCredentials;
5665
5684
  };
5666
5685
 
5667
5686
  /**
5668
- * Add ice credentials in the first media section.
5669
- * @param {Array<string>} mediaSections - An array of SDP lines that need to be updated with credentials.
5687
+ * Add ice config (credentials and candidates) in the first media section.
5688
+ * @param {Array<string>} mediaSections - An array of SDP lines that need to be updated with config.
5689
+ * @param {Array<string>} headers - An array of SDP lines with the headers.
5670
5690
  * @param {Object} iceCredentials - The iceCredentials object.
5671
- * @property {string} ufrag - A string representing the ufrag in the ICE credentials.
5672
- * @property {string} pwd - A string representing the password in the ICECredentials.
5673
- * @returns {Array<string>} - An array of updated SDP lines with the new credentials.
5691
+ * @property {string} ufrag - A string representing the ufrag line in the ICE credentials, e.g. 'a=ice-ufrag:myUFrag'
5692
+ * @property {string} pwd - A string representing the password line in the ICE credentials, e.g. 'a=ice-pwd:myPwd'
5693
+ * @returns {Array<string>} - An array of updated SDP lines with the new config.
5674
5694
  */
5675
- SDPHelpers.addIceCredentials = (mediaSections, iceCredentials) => {
5695
+ SDPHelpers.addIceConfig = (mediaSections, headers, iceCredentials) => {
5676
5696
  const mediaSectionsWithCredentials = [...mediaSections];
5677
5697
  const firstSection = mediaSections[0];
5678
5698
  const _SDPHelpers$getIceCre = SDPHelpers.getIceCredentials(firstSection),
@@ -5680,7 +5700,7 @@ SDPHelpers.addIceCredentials = (mediaSections, iceCredentials) => {
5680
5700
  // If there are no credentials in the first media section, we need to add them
5681
5701
  if (!ufrag) {
5682
5702
  const sectionWithIceCredentials = SDPHelpers.addIceCredentialsToSection(firstSection, iceCredentials);
5683
- mediaSectionsWithCredentials[0] = sectionWithIceCredentials;
5703
+ mediaSectionsWithCredentials[0] = SDPHelpers.addIceCandidatesToSection(sectionWithIceCredentials, headers);
5684
5704
  }
5685
5705
  return mediaSectionsWithCredentials;
5686
5706
  };
@@ -5699,6 +5719,11 @@ SDPHelpers.addIceCredentials = (mediaSections, iceCredentials) => {
5699
5719
  * @returns {Array<string>} - An array of updated headers lines.
5700
5720
  */
5701
5721
  SDPHelpers.updateHeadersInPartialSdp = (currentSdp, partialSdp, tracks) => {
5722
+ if (!currentSdp.headers.length) {
5723
+ // If there is no currentSdp, we don't need to update the headers, we just make sure we don't
5724
+ // have more tracks in the bundle than it should.
5725
+ return SDPHelpers.updateBundleLine(partialSdp.headers, tracks);
5726
+ }
5702
5727
  // Update headers with the new bundle line.
5703
5728
  let headers = SDPHelpers.updateBundleLine(currentSdp.headers, tracks);
5704
5729
  // Update headers with the new version.
@@ -8045,7 +8070,7 @@ const logging = (0, _log.default)('StaticConfig');
8045
8070
  */
8046
8071
 
8047
8072
  /** @type builtInConfig */
8048
- const builtInConfig = (0, _cloneDeep.default)({"version":"v2.28.3","buildHash":"2898b53","minimumVersion":{"firefox":52,"chrome":49},"debug":false,"websiteURL":"http://www.tokbox.com","configURL":"https://config.opentok.com","ipWhitelistConfigURL":"","cdnURL":"","loggingURL":"https://hlg.tokbox.com/prod","apiURL":"https://anvil.opentok.com"});
8073
+ const builtInConfig = (0, _cloneDeep.default)({"version":"v2.28.4","buildHash":"24429b3","minimumVersion":{"firefox":52,"chrome":49},"debug":false,"websiteURL":"http://www.tokbox.com","configURL":"https://config.opentok.com","ipWhitelistConfigURL":"","cdnURL":"","loggingURL":"https://hlg.tokbox.com/prod","apiURL":"https://anvil.opentok.com"});
8049
8074
  const whitelistAllowedRuntimeProperties = (0, _pick.default)(['apiURL', 'assetURL', 'cdnURL', 'sessionInfoOverrides', 'loggingURL']);
8050
8075
  const liveConfigMap = {
8051
8076
  apiUrl: 'apiURL',
@@ -18569,6 +18594,9 @@ function PublisherFactory(_ref) {
18569
18594
  } else {
18570
18595
  logging.warn(`Track with invalid kind has ended: ${track.kind}`);
18571
18596
  }
18597
+ logAnalyticsEvent('mediaStopped', 'Event', {
18598
+ kind
18599
+ });
18572
18600
  return;
18573
18601
  }
18574
18602
  if (this.session) {
@@ -32422,7 +32450,11 @@ function PublisherPeerConnectionFactory(deps) {
32422
32450
  if (_cancelWatchAudioAcquisition) {
32423
32451
  _cancelWatchAudioAcquisition();
32424
32452
  }
32425
- _cancelWatchAudioAcquisition = watchAudioAcquisition(callback => _peerConnection.getStats(null, callback), () => this.trigger('audioAcquisitionProblem'));
32453
+ _cancelWatchAudioAcquisition = watchAudioAcquisition(callback => _peerConnection.getStats(null, callback), () => {
32454
+ if (_peerConnection.iceConnectionStateIsConnected()) {
32455
+ this.trigger('audioAcquisitionProblem');
32456
+ }
32457
+ });
32426
32458
  }
32427
32459
  this.trigger('connected');
32428
32460
  }
@@ -32985,6 +33017,7 @@ var _watchSubscriberAudio = _interopRequireDefault(__webpack_require__(683));
32985
33017
  // @todo enable the following disabled rules see OPENTOK-31136 for more info
32986
33018
  /* eslint-disable global-require, no-param-reassign, no-void, no-shadow */
32987
33019
  /* eslint-disable func-names */
33020
+ /* global RTCStatsReport */
32988
33021
 
32989
33022
  function SubscriberPeerConnectionFactory(deps) {
32990
33023
  if (deps === void 0) {
@@ -33180,7 +33213,14 @@ function SubscriberPeerConnectionFactory(deps) {
33180
33213
  };
33181
33214
  this.getRtcStatsReport = function (callback) {
33182
33215
  if (_peerConnection) {
33183
- _peerConnection.getRtcStatsReport(null, callback);
33216
+ _peerConnection.getRtcStatsReport(null, (error, stats) => {
33217
+ if (stats instanceof RTCStatsReport || Object.getPrototypeOf(stats).toString().includes('RTCStatsReport')) {
33218
+ // Convert RTCStatsReport to Map for consistency with SPC
33219
+ callback(error, new Map([...stats]));
33220
+ } else {
33221
+ callback(error, stats);
33222
+ }
33223
+ });
33184
33224
  } else {
33185
33225
  const errorCode = ExceptionCodes.PEER_CONNECTION_NOT_CONNECTED;
33186
33226
  callback(new OTHelpers.Error(OTErrorClass.getTitleByCode(errorCode), Errors.PEER_CONNECTION_NOT_CONNECTED, {
@@ -36506,6 +36546,9 @@ function SubscriberFactory(_ref2) {
36506
36546
  },
36507
36547
  get isSubscribingToVideo() {
36508
36548
  return _properties.subscribeToVideo;
36549
+ },
36550
+ getActiveSourceStreamId() {
36551
+ return _activeSourceStreamId;
36509
36552
  }
36510
36553
  };
36511
36554
  this._onSenderStatsReceived = content => {
@@ -66088,6 +66131,9 @@ function SessionFactory(deps) {
66088
66131
  isE2ee() {
66089
66132
  return _session.sessionInfo.e2ee && _e2eeSecretSet && !!_session.capabilities.supportsE2ee;
66090
66133
  },
66134
+ isSpc() {
66135
+ return _useSinglePeerConnection;
66136
+ },
66091
66137
  isSocketReconnecting() {
66092
66138
  return _isSocketReconnecting;
66093
66139
  },
@@ -92412,8 +92458,8 @@ exports.default = watchAudioAcquisition;
92412
92458
  * Installs a watcher that will calls the given callback if the audio acquisition seems to not
92413
92459
  * work porperly.
92414
92460
  *
92415
- * It waits 3 seconds and poll getStats to find any sign of audio bytes sent. If it can't find any
92416
- * it signals the anomaly by executing the given function.
92461
+ * It runs every 3 seconds and poll getStats to find any sign of audio bytes sent. If it can't find
92462
+ * any it signals the anomaly by executing the given function.
92417
92463
  *
92418
92464
  * @param {function(cb: function(DOMError, Array<RTCStats>))} getStats
92419
92465
  * the function to call to get the stats
@@ -92421,24 +92467,45 @@ exports.default = watchAudioAcquisition;
92421
92467
  * @returns {function} cancel the watch
92422
92468
  */
92423
92469
  function watchAudioAcquisition(getStats, warningCb) {
92424
- // detection of Chrome failure to acquire audio
92425
- // inspired by https://medium.com/the-making-of-appear-in/working-around-webrtc-bugs-d4f6fdb763f
92426
- const to = setTimeout(() => {
92427
- getStats((error, stats) => {
92428
- if (error) {
92429
- return;
92430
- }
92431
- for (let idxStats = 0; idxStats < stats.length; idxStats += 1) {
92432
- const rtcStats = stats[idxStats];
92433
- if (rtcStats.id.indexOf('_send') !== -1 && rtcStats.type === 'ssrc' && (rtcStats.kind === 'audio' || rtcStats.mediaType === 'audio') && parseInt(rtcStats.bytesSent, 10) === 0) {
92434
- // abnormal condition detected
92470
+ let previousBytesSent = 0;
92471
+ let isCallbackNeeded = true;
92472
+ const isAudioStat = rtcStats => rtcStats.type === 'outbound-rtp' && rtcStats.ssrc && (rtcStats.kind === 'audio' || rtcStats.mediaType === 'audio');
92473
+ const isAnomalyDetected = currentBytesSent => currentBytesSent === previousBytesSent && isCallbackNeeded;
92474
+ const isAnomalyResolved = currentBytesSent => currentBytesSent !== previousBytesSent;
92475
+ const handleStats = (error, stats) => {
92476
+ if (error) return;
92477
+ stats.forEach(rtcStats => {
92478
+ if (isAudioStat(rtcStats)) {
92479
+ const statType = typeof rtcStats.bytesSent;
92480
+ let currentBytesSent;
92481
+ // Number.MAX_SAFE_INTEGER is a 16 digit number, but it is only 53 bits. We manually modulo
92482
+ // 15 digits to avoid big number problems.
92483
+ const maxDigit = 15;
92484
+ if (statType === 'number') {
92485
+ currentBytesSent = rtcStats.bytesSent % Math.pow(10, maxDigit);
92486
+ } else if (statType === 'string') {
92487
+ const safeIntegerBytesSent = rtcStats.bytesSent.slice(rtcStats.bytesSent.length - maxDigit);
92488
+ currentBytesSent = parseInt(safeIntegerBytesSent, 10);
92489
+ } else {
92490
+ return;
92491
+ }
92492
+ if (isAnomalyDetected(currentBytesSent)) {
92435
92493
  warningCb();
92494
+ // Execute the callback only once per audio loss
92495
+ isCallbackNeeded = false;
92496
+ } else if (isAnomalyResolved(currentBytesSent)) {
92497
+ // Once resolved, we may need to use the callback in future audio loss
92498
+ isCallbackNeeded = true;
92436
92499
  }
92500
+ previousBytesSent = currentBytesSent;
92437
92501
  }
92438
92502
  });
92503
+ };
92504
+ const audioMonitor = setInterval(() => {
92505
+ getStats(handleStats);
92439
92506
  }, 3000);
92440
92507
  return function cancel() {
92441
- clearTimeout(to);
92508
+ clearInterval(audioMonitor);
92442
92509
  };
92443
92510
  }
92444
92511
 
@@ -92708,17 +92775,18 @@ const collectGetStatsHelper = (peerConnection, tracks, callback) => {
92708
92775
  };
92709
92776
  exports.collectGetStatsHelper = collectGetStatsHelper;
92710
92777
  const collectRtcStatsReportHelper = (peerConnection, tracks, callback) => {
92711
- // getRtcStatsReport uses an Object for the collected stats.
92712
- const merge = function merge(previous, stats) {
92778
+ // getRtcStatsReport uses an RTCStatsReport object that cannot be merged
92779
+ // so we convert it to a Map
92780
+ const mapMerge = function mapMerge(previous, stats) {
92713
92781
  if (previous === void 0) {
92714
- previous = {};
92782
+ previous = new Map();
92715
92783
  }
92716
- return Object.assign({}, previous, stats);
92784
+ return new Map([...previous, ...stats]);
92717
92785
  };
92718
92786
  collectStatsHelper({
92719
92787
  tracks,
92720
92788
  getStats: (track, completion) => peerConnection.getRtcStatsReport(track, completion),
92721
- merge
92789
+ merge: mapMerge
92722
92790
  }, callback);
92723
92791
  };
92724
92792
  exports.collectRtcStatsReportHelper = collectRtcStatsReportHelper;
@@ -104448,6 +104516,7 @@ const createGetFullRemoteSdp = function createGetFullRemoteSdp(deps) {
104448
104516
  const getMediaSectionsForOffer = deps.getMediaSectionsForOffer || _getMediaSectionsForOffer.default;
104449
104517
  const getFullSdpFromPartial = (type, partialSdp, currentSdp) => {
104450
104518
  const isAnswer = type === 'answer';
104519
+ const version = Math.max(partialSdp.version, currentSdp.version || 0);
104451
104520
  const getMediaSections = isAnswer ? getMediaSectionsForAnswer : getMediaSectionsForOffer;
104452
104521
  const mediaSections = getMediaSections(partialSdp, currentSdp);
104453
104522
  const mids = mediaSections.map(_sdp_helpers.default.getSectionMid);
@@ -104462,7 +104531,7 @@ const createGetFullRemoteSdp = function createGetFullRemoteSdp(deps) {
104462
104531
  }
104463
104532
  return {
104464
104533
  headers,
104465
- version: partialSdp.version,
104534
+ version,
104466
104535
  bundle: _sdp_helpers.default.createBundleLine(tracks),
104467
104536
  tracks,
104468
104537
  mediaSections,
@@ -104471,8 +104540,10 @@ const createGetFullRemoteSdp = function createGetFullRemoteSdp(deps) {
104471
104540
  };
104472
104541
  };
104473
104542
  const getFullRemoteSdp = (type, newRemoteSdp, currentRemoteSdp) => {
104474
- if (!isPartialSdp(newRemoteSdp)) {
104475
- // This is a full SDP
104543
+ if (!isPartialSdp(newRemoteSdp) && !currentRemoteSdp.bundle) {
104544
+ // This is a full SDP and it is the first received. When a full SDP comes after several SDP,
104545
+ // we don't return it since we want to merge the cached SDP and this full one. This can
104546
+ // happen, e.g., when SDPs come out of order.
104476
104547
  return newRemoteSdp;
104477
104548
  }
104478
104549
  logging.debug('partial remote sdp: ', newRemoteSdp);
@@ -104483,9 +104554,10 @@ const createGetFullRemoteSdp = function createGetFullRemoteSdp(deps) {
104483
104554
  // This is a partial SDP, however it is the first one we have received (none cached yet), we
104484
104555
  // may need to add ICE credentials to the first enabled section.
104485
104556
  const mediaSections = newRemoteSdp.mediaSections,
104486
- iceCredentials = newRemoteSdp.iceCredentials;
104557
+ iceCredentials = newRemoteSdp.iceCredentials,
104558
+ headers = newRemoteSdp.headers;
104487
104559
  partialSdp = Object.assign({}, newRemoteSdp, {
104488
- mediaSections: _sdp_helpers.default.addIceCredentials(mediaSections, iceCredentials)
104560
+ mediaSections: _sdp_helpers.default.addIceConfig(mediaSections, headers, iceCredentials)
104489
104561
  });
104490
104562
  }
104491
104563
  return getFullSdpFromPartial(type, partialSdp, currentRemoteSdp);