twilio-video 2.24.3 → 2.25.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/CHANGELOG.md CHANGED
@@ -2,6 +2,67 @@ The Twilio Programmable Video SDKs use [Semantic Versioning](http://www.semver.o
2
2
 
3
3
  **Version 1.x reached End of Life on September 8th, 2021.** See the changelog entry [here](https://www.twilio.com/changelog/end-of-life-complete-for-unsupported-versions-of-the-programmable-video-sdk). Support for the 1.x version ended on December 4th, 2020.
4
4
 
5
+ 2.25.0 (November 14, 2022)
6
+ ==========================
7
+
8
+ New Features
9
+ ------------
10
+
11
+ ### Auto-switch default audio input devices
12
+
13
+ This release adds a new feature that preserves audio continuity in situations where end-users change the default audio input device.
14
+ A LocalAudioTrack is said to be capturing audio from the default audio input device if:
15
+
16
+ - it was created using the MediaTrackConstraints `{ audio: true }`, or
17
+ - it was created using the MediaTrackConstraints `{ audio: { deviceId: 'foo' } }`, and "foo" is not available, or
18
+ - it was created using the MediaTrackConstraints `{ audio: { deviceId: { ideal: 'foo' } } }` and "foo" is not available
19
+
20
+ In previous versions of the SDK, if the default device changed (ex: a bluetooth headset is connected to a mac or windows laptop),
21
+ the LocalAudioTrack continued to capture audio from the old default device (ex: the laptop microphone). Now, a LocalAudioTrack
22
+ will switch automatically from the old default audio input device to the new default audio input device (ex: from the laptop microphone to the headset microphone).
23
+ This feature is controlled by a new [CreateLocalAudioTrackOptions](https://sdk.twilio.com/js/video/releases/2.25.0/docs/global.html#CreateLocalAudioTrackOptions)
24
+ property `defaultDeviceCaptureMode`, which defaults to `auto` (new behavior) or can be set to `manual` (old behavior).
25
+
26
+ The application can decide to capture audio from a specific audio input device by creating a LocalAudioTrack:
27
+
28
+ - using the MediaTrackConstraints `{ audio: { deviceId: 'foo' } }`, and "foo" is available, or
29
+ - using the MediaTrackConstraints `{ audio: { deviceId: { ideal: 'foo' } } }` and "foo" is available, or
30
+ - using the MediaTrackConstraints `{ audio: { deviceId: { exact: 'foo' } } }` and "foo" is available
31
+
32
+ In this case, the LocalAudioTrack DOES NOT switch to another audio input device if the current audio input device is no
33
+ longer available. See below for the behavior of this property based on how the LocalAudioTrack is created. (VIDEO-11701)
34
+
35
+ ```js
36
+ const { connect, createLocalAudioTrack, createLocalTracks } = require('twilio-video');
37
+
38
+ // Auto-switch default audio input devices: option 1
39
+ const audioTrack = await createLocalAudioTrack();
40
+
41
+ // Auto-switch default audio input devices: option 2
42
+ const audioTrack1 = await createLocalAudioTrack({ defaultDeviceCaptureMode: 'auto' });
43
+
44
+ // Auto-switch default audio input devices: option 3
45
+ const [audioTrack3] = await createLocalTracks({ audio: true });
46
+
47
+ // Auto-switch default audio input devices: option 4
48
+ const [audioTrack4] = await createLocalTracks({ audio: { defaultDeviceCaptureMode: 'auto' } });
49
+
50
+ // Auto-switch default audio input devices: option 5
51
+ const room1 = await connect({ audio: true });
52
+
53
+ // Auto-switch default audio input devices: option 6
54
+ const room2 = await connect({ audio: { defaultDeviceCaptureMode: 'auto' } });
55
+
56
+ // Disable auto-switch default audio input devices
57
+ const room = await createLocalAudioTrack({ defaultDeviceCaptureMode: 'manual' });
58
+ ```
59
+
60
+ **Limitations**
61
+
62
+ - This feature is not enabled on iOS as it is natively supported.
63
+ - Due to this [WebKit bug](https://bugs.webkit.org/show_bug.cgi?id=232835), MacOS Safari Participants may lose their local audio after switching between default audio input devices two-three times.
64
+ - This feature is not supported on Android Chrome, as it does not support the [MediaDevices.ondevicechange](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/devicechange_event#browser_compatibility) event.
65
+
5
66
  2.24.3 (October 10, 2022)
6
67
  =========================
7
68
 
package/README.md CHANGED
@@ -74,8 +74,7 @@ Releases of twilio-video.js are hosted on a CDN, and you can include these
74
74
  directly in your web app using a <script> tag.
75
75
 
76
76
  ```html
77
- <script src="//sdk.twilio.com/js/video/releases/2.24.3/twilio-video.min.js"></script>
78
-
77
+ <script src="//sdk.twilio.com/js/video/releases/2.25.0/twilio-video.min.js"></script>
79
78
  ```
80
79
 
81
80
  Using this method, twilio-video.js will set a browser global:
@@ -1,4 +1,4 @@
1
- /*! twilio-video.js 2.24.3
1
+ /*! twilio-video.js 2.25.0
2
2
 
3
3
  The following license applies to all parts of this software except as
4
4
  documented below.
@@ -1116,8 +1116,16 @@ var NoiseCancellationVendor = {
1116
1116
  * @property {boolean} [workaroundWebKitBug180748=false] - setting this
1117
1117
  * attempts to workaround WebKit Bug 180748, where, in Safari, getUserMedia may return a silent audio
1118
1118
  * MediaStreamTrack.
1119
- * @property {NoiseCancellationOptions} [noiseCancellationOptions] - This property enables using 3rd party plugins
1120
- * for noise cancellation. This is a beta feature.
1119
+ * @property {DefaultDeviceCaptureMode} [defaultDeviceCaptureMode="auto"] - This optional property only applies if the
1120
+ * {@link LocalAudioTrack} is capturing from the default audio input device connected to a desktop or laptop. When the
1121
+ * property is set to "auto", the LocalAudioTrack restarts whenever the default audio input device changes, in order to
1122
+ * capture audio from the new default audio input device. For example, when a bluetooth audio headset is connected to a
1123
+ * Macbook, the LocalAudioTrack will start capturing audio from the headset microphone. When the headset is disconnected,
1124
+ * the LocalAudioTrack will start capturing audio from the Macbook microphone. When the property is set to "manual", the
1125
+ * LocalAudioTrack continues to capture from the same audio input device even after the default audio input device changes.
1126
+ * When the property is not specified, it defaults to "auto".
1127
+ * @property {NoiseCancellationOptions} [noiseCancellationOptions] - This optional property enables using 3rd party plugins
1128
+ * for noise cancellation.
1121
1129
  */
1122
1130
  /**
1123
1131
  * Create {@link LocalTrack} options. Apart from the properties listed here, you can
@@ -1218,7 +1226,7 @@ var buildLogLevels = require('./util').buildLogLevels;
1218
1226
  var _a = require('./webrtc'), getUserMedia = _a.getUserMedia, MediaStreamTrack = _a.MediaStreamTrack;
1219
1227
  var _b = require('./media/track/es5'), LocalAudioTrack = _b.LocalAudioTrack, LocalDataTrack = _b.LocalDataTrack, LocalVideoTrack = _b.LocalVideoTrack;
1220
1228
  var Log = require('./util/log');
1221
- var _c = require('./util/constants'), DEFAULT_LOG_LEVEL = _c.DEFAULT_LOG_LEVEL, DEFAULT_LOGGER_NAME = _c.DEFAULT_LOGGER_NAME;
1229
+ var _c = require('./util/constants'), DEFAULT_LOG_LEVEL = _c.DEFAULT_LOG_LEVEL, DEFAULT_LOGGER_NAME = _c.DEFAULT_LOGGER_NAME, INVALID_VALUE = _c.typeErrors.INVALID_VALUE;
1222
1230
  var workaround180748 = require('./webaudio/workaround180748');
1223
1231
  // This is used to make out which createLocalTracks() call a particular Log
1224
1232
  // statement belongs to. Each call to createLocalTracks() increments this
@@ -1317,19 +1325,31 @@ function createLocalTracks(options) {
1317
1325
  extraLocalTrackOptions = {
1318
1326
  audio: typeof fullOptions.audio === 'object' && fullOptions.audio.name
1319
1327
  ? { name: fullOptions.audio.name }
1320
- : {},
1328
+ : { defaultDeviceCaptureMode: 'auto' },
1321
1329
  video: typeof fullOptions.video === 'object' && fullOptions.video.name
1322
1330
  ? { name: fullOptions.video.name }
1323
1331
  : {}
1324
1332
  };
1325
1333
  extraLocalTrackOptions.audio.isCreatedByCreateLocalTracks = true;
1326
1334
  extraLocalTrackOptions.video.isCreatedByCreateLocalTracks = true;
1327
- if (typeof fullOptions.audio === 'object' && typeof fullOptions.audio.workaroundWebKitBug1208516 === 'boolean') {
1328
- extraLocalTrackOptions.audio.workaroundWebKitBug1208516 = fullOptions.audio.workaroundWebKitBug1208516;
1329
- }
1330
- if (typeof fullOptions.audio === 'object' && 'noiseCancellationOptions' in fullOptions.audio) {
1331
- noiseCancellationOptions = fullOptions.audio.noiseCancellationOptions;
1332
- delete fullOptions.audio.noiseCancellationOptions;
1335
+ if (typeof fullOptions.audio === 'object') {
1336
+ if (typeof fullOptions.audio.workaroundWebKitBug1208516 === 'boolean') {
1337
+ extraLocalTrackOptions.audio.workaroundWebKitBug1208516 = fullOptions.audio.workaroundWebKitBug1208516;
1338
+ }
1339
+ if ('noiseCancellationOptions' in fullOptions.audio) {
1340
+ noiseCancellationOptions = fullOptions.audio.noiseCancellationOptions;
1341
+ delete fullOptions.audio.noiseCancellationOptions;
1342
+ }
1343
+ if (!('defaultDeviceCaptureMode' in fullOptions.audio)) {
1344
+ extraLocalTrackOptions.audio.defaultDeviceCaptureMode = 'auto';
1345
+ }
1346
+ else if (['auto', 'manual'].every(function (mode) { return mode !== fullOptions.audio.defaultDeviceCaptureMode; })) {
1347
+ // eslint-disable-next-line new-cap
1348
+ throw INVALID_VALUE('CreateLocalAudioTrackOptions.defaultDeviceCaptureMode', ['auto', 'manual']);
1349
+ }
1350
+ else {
1351
+ extraLocalTrackOptions.audio.defaultDeviceCaptureMode = fullOptions.audio.defaultDeviceCaptureMode;
1352
+ }
1333
1353
  }
1334
1354
  if (typeof fullOptions.video === 'object' && typeof fullOptions.video.workaroundWebKitBug1208516 === 'boolean') {
1335
1355
  extraLocalTrackOptions.video.workaroundWebKitBug1208516 = fullOptions.video.workaroundWebKitBug1208516;
@@ -2969,6 +2989,7 @@ var __extends = (this && this.__extends) || (function () {
2969
2989
  d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
2970
2990
  };
2971
2991
  })();
2992
+ var isIOS = require('../../util/browserdetection').isIOS;
2972
2993
  var AudioTrack = require('./audiotrack');
2973
2994
  var mixinLocalMediaTrack = require('./localmediatrack');
2974
2995
  var LocalMediaAudioTrack = mixinLocalMediaTrack(AudioTrack);
@@ -3001,13 +3022,61 @@ var LocalAudioTrack = /** @class */ (function (_super) {
3001
3022
  var _this = this;
3002
3023
  var noiseCancellation = (options === null || options === void 0 ? void 0 : options.noiseCancellation) || null;
3003
3024
  _this = _super.call(this, mediaStreamTrack, options) || this;
3025
+ var log = _this._log;
3026
+ var _a = mediaStreamTrack.label, defaultDeviceLabel = _a === void 0 ? '' : _a;
3027
+ var _b = mediaStreamTrack.getSettings(), _c = _b.deviceId, defaultDeviceId = _c === void 0 ? '' : _c, _d = _b.groupId, defaultGroupId = _d === void 0 ? '' : _d;
3004
3028
  Object.defineProperties(_this, {
3029
+ _currentDefaultDeviceInfo: {
3030
+ value: { deviceId: defaultDeviceId, groupId: defaultGroupId, label: defaultDeviceLabel },
3031
+ writable: true
3032
+ },
3033
+ _defaultDeviceCaptureMode: {
3034
+ value: !isIOS()
3035
+ && _this._isCreatedByCreateLocalTracks
3036
+ && typeof navigator === 'object'
3037
+ && typeof navigator.mediaDevices === 'object'
3038
+ && typeof navigator.mediaDevices.addEventListener === 'function'
3039
+ && typeof navigator.mediaDevices.enumerateDevices === 'function'
3040
+ ? (options === null || options === void 0 ? void 0 : options.defaultDeviceCaptureMode) || 'auto'
3041
+ : 'manual'
3042
+ },
3043
+ _onDeviceChange: {
3044
+ value: function () {
3045
+ navigator.mediaDevices.enumerateDevices().then(function (deviceInfos) {
3046
+ // NOTE(mmalavalli): In Chrome, when the default device changes, and we restart the LocalAudioTrack with
3047
+ // device ID "default", it will not switch to the new default device unless all LocalAudioTracks capturing
3048
+ // from the old default device are stopped. So, we restart the LocalAudioTrack with the actual device ID of
3049
+ // the new default device instead.
3050
+ var defaultDeviceInfo = deviceInfos.find(function (_a) {
3051
+ var deviceId = _a.deviceId, kind = _a.kind;
3052
+ return kind === 'audioinput' && deviceId !== 'default';
3053
+ });
3054
+ if (defaultDeviceInfo && ['deviceId', 'groupId'].some(function (prop) {
3055
+ return defaultDeviceInfo[prop] !== _this._currentDefaultDeviceInfo[prop];
3056
+ })) {
3057
+ log.info('Default device changed, restarting the LocalAudioTrack');
3058
+ log.debug("Old default device: \"" + _this._currentDefaultDeviceInfo.deviceId + "\" => \"" + _this._currentDefaultDeviceInfo.label + "\"");
3059
+ log.debug("New default device: \"" + defaultDeviceInfo.deviceId + "\" => \"" + defaultDeviceInfo.label + "\"");
3060
+ _this._currentDefaultDeviceInfo = defaultDeviceInfo;
3061
+ _this._restartDefaultDevice().catch(function (error) { return log.warn("Failed to restart: " + error.message); });
3062
+ }
3063
+ }, function (error) {
3064
+ log.warn("Failed to run enumerateDevices(): " + error.message);
3065
+ });
3066
+ }
3067
+ },
3068
+ _restartOnDefaultDeviceChangeCleanup: {
3069
+ value: null,
3070
+ writable: true
3071
+ },
3005
3072
  noiseCancellation: {
3006
3073
  enumerable: true,
3007
3074
  value: noiseCancellation,
3008
3075
  writable: false
3009
3076
  },
3010
3077
  });
3078
+ log.debug('defaultDeviceCaptureMode:', _this._defaultDeviceCaptureMode);
3079
+ _this._maybeRestartOnDefaultDeviceChange();
3011
3080
  return _this;
3012
3081
  }
3013
3082
  LocalAudioTrack.prototype.toString = function () {
@@ -3024,6 +3093,51 @@ var LocalAudioTrack = /** @class */ (function (_super) {
3024
3093
  LocalAudioTrack.prototype._end = function () {
3025
3094
  return _super.prototype._end.apply(this, arguments);
3026
3095
  };
3096
+ /**
3097
+ * @private
3098
+ */
3099
+ LocalAudioTrack.prototype._maybeRestartOnDefaultDeviceChange = function () {
3100
+ var _this = this;
3101
+ var _a = this, constraints = _a._constraints, defaultDeviceCaptureMode = _a._defaultDeviceCaptureMode, log = _a._log;
3102
+ var mediaStreamTrack = this.noiseCancellation ? this.noiseCancellation.sourceTrack : this.mediaStreamTrack;
3103
+ var deviceId = mediaStreamTrack.getSettings().deviceId;
3104
+ var isNotEqualToCapturedDeviceIdOrEqualToDefault = function (requestedDeviceId) {
3105
+ return requestedDeviceId !== deviceId || requestedDeviceId === 'default';
3106
+ };
3107
+ var isCapturingFromDefaultDevice = (function checkIfCapturingFromDefaultDevice(deviceIdConstraint) {
3108
+ if (deviceIdConstraint === void 0) { deviceIdConstraint = {}; }
3109
+ if (typeof deviceIdConstraint === 'string') {
3110
+ return isNotEqualToCapturedDeviceIdOrEqualToDefault(deviceIdConstraint);
3111
+ }
3112
+ else if (Array.isArray(deviceIdConstraint)) {
3113
+ return deviceIdConstraint.every(isNotEqualToCapturedDeviceIdOrEqualToDefault);
3114
+ }
3115
+ else if (deviceIdConstraint.exact) {
3116
+ return checkIfCapturingFromDefaultDevice(deviceIdConstraint.exact);
3117
+ }
3118
+ else if (deviceIdConstraint.ideal) {
3119
+ return checkIfCapturingFromDefaultDevice(deviceIdConstraint.ideal);
3120
+ }
3121
+ return true;
3122
+ }(constraints.deviceId));
3123
+ if (defaultDeviceCaptureMode === 'auto' && isCapturingFromDefaultDevice) {
3124
+ if (!this._restartOnDefaultDeviceChangeCleanup) {
3125
+ log.info('LocalAudioTrack will be restarted if the default device changes');
3126
+ navigator.mediaDevices.addEventListener('devicechange', this._onDeviceChange);
3127
+ this._restartOnDefaultDeviceChangeCleanup = function () {
3128
+ log.info('Cleaning up the listener to restart the LocalAudioTrack if the default device changes');
3129
+ navigator.mediaDevices.removeEventListener('devicechange', _this._onDeviceChange);
3130
+ _this._restartOnDefaultDeviceChangeCleanup = null;
3131
+ };
3132
+ }
3133
+ }
3134
+ else {
3135
+ log.info('LocalAudioTrack will NOT be restarted if the default device changes');
3136
+ if (this._restartOnDefaultDeviceChangeCleanup) {
3137
+ this._restartOnDefaultDeviceChangeCleanup();
3138
+ }
3139
+ }
3140
+ };
3027
3141
  /**
3028
3142
  * @private
3029
3143
  */
@@ -3037,6 +3151,21 @@ var LocalAudioTrack = /** @class */ (function (_super) {
3037
3151
  }
3038
3152
  return _super.prototype._reacquireTrack.call(this, constraints);
3039
3153
  };
3154
+ /**
3155
+ * @private
3156
+ */
3157
+ LocalAudioTrack.prototype._restartDefaultDevice = function () {
3158
+ var _this = this;
3159
+ var constraints = Object.assign({}, this._constraints);
3160
+ var restartConstraints = Object.assign({}, constraints, { deviceId: this._currentDefaultDeviceInfo.deviceId });
3161
+ return this.restart(restartConstraints).then(function () {
3162
+ // NOTE(mmalavalli): Since we used the new default device's ID while restarting the LocalAudioTrack,
3163
+ // we reset the constraints to the original constraints so that the default device detection logic in
3164
+ // _maybeRestartOnDefaultDeviceChange() still works.
3165
+ _this._constraints = constraints;
3166
+ _this._maybeRestartOnDefaultDeviceChange();
3167
+ });
3168
+ };
3040
3169
  /**
3041
3170
  * Disable the {@link LocalAudioTrack}. This is effectively "mute".
3042
3171
  * @returns {this}
@@ -3108,6 +3237,9 @@ var LocalAudioTrack = /** @class */ (function (_super) {
3108
3237
  if (this.noiseCancellation) {
3109
3238
  this.noiseCancellation.stop();
3110
3239
  }
3240
+ if (this._restartOnDefaultDeviceChangeCleanup) {
3241
+ this._restartOnDefaultDeviceChangeCleanup();
3242
+ }
3111
3243
  return _super.prototype.stop.apply(this, arguments);
3112
3244
  };
3113
3245
  return LocalAudioTrack;
@@ -3138,7 +3270,7 @@ var LocalAudioTrack = /** @class */ (function (_super) {
3138
3270
  */
3139
3271
  module.exports = LocalAudioTrack;
3140
3272
 
3141
- },{"./audiotrack":13,"./localmediatrack":23}],20:[function(require,module,exports){
3273
+ },{"../../util/browserdetection":122,"./audiotrack":13,"./localmediatrack":23}],20:[function(require,module,exports){
3142
3274
  'use strict';
3143
3275
  var __extends = (this && this.__extends) || (function () {
3144
3276
  var extendStatics = function (d, b) {
@@ -3387,8 +3519,8 @@ var __extends = (this && this.__extends) || (function () {
3387
3519
  };
3388
3520
  })();
3389
3521
  var getUserMedia = require('../../webrtc').getUserMedia;
3390
- var _a = require('../../webrtc/util'), guessBrowser = _a.guessBrowser, isIOSChrome = _a.isIOSChrome;
3391
- var _b = require('../../util'), capitalize = _b.capitalize, defer = _b.defer, isUserMediaTrack = _b.isUserMediaTrack, waitForSometime = _b.waitForSometime, waitForEvent = _b.waitForEvent;
3522
+ var isIOS = require('../../util/browserdetection').isIOS;
3523
+ var _a = require('../../util'), capitalize = _a.capitalize, defer = _a.defer, isUserMediaTrack = _a.isUserMediaTrack, waitForSometime = _a.waitForSometime, waitForEvent = _a.waitForEvent;
3392
3524
  var ILLEGAL_INVOKE = require('../../util/constants').typeErrors.ILLEGAL_INVOKE;
3393
3525
  var detectSilentAudio = require('../../util/detectsilentaudio');
3394
3526
  var detectSilentVideo = require('../../util/detectsilentvideo');
@@ -3414,10 +3546,7 @@ function mixinLocalMediaTrack(AudioOrVideoTrack) {
3414
3546
  */
3415
3547
  function LocalMediaTrack(mediaStreamTrack, options) {
3416
3548
  var _this = this;
3417
- // NOTE(mpatwardhan): by default workaround for WebKitBug1208516 will be enabled on Safari browsers
3418
- // although the bug is seen mainly on iOS devices, we do not have a reliable way to tell iOS from MacOs
3419
- // userAgent on iOS pretends its macOs if Safari is set to request desktop pages.
3420
- var workaroundWebKitBug1208516 = (guessBrowser() === 'safari' || isIOSChrome())
3549
+ var workaroundWebKitBug1208516 = isIOS()
3421
3550
  && isUserMediaTrack(mediaStreamTrack)
3422
3551
  && typeof document === 'object'
3423
3552
  && typeof document.addEventListener === 'function'
@@ -3726,7 +3855,7 @@ function restartWhenInadvertentlyStopped(localMediaTrack) {
3726
3855
  }
3727
3856
  module.exports = mixinLocalMediaTrack;
3728
3857
 
3729
- },{"../../util":131,"../../util/constants":124,"../../util/detectsilentaudio":125,"../../util/detectsilentvideo":126,"../../util/documentvisibilitymonitor.js":127,"../../util/localmediarestartdeferreds":134,"../../webaudio/workaround180748":154,"../../webrtc":157,"../../webrtc/util":170,"./sender":38}],24:[function(require,module,exports){
3858
+ },{"../../util":131,"../../util/browserdetection":122,"../../util/constants":124,"../../util/detectsilentaudio":125,"../../util/detectsilentvideo":126,"../../util/documentvisibilitymonitor.js":127,"../../util/localmediarestartdeferreds":134,"../../webaudio/workaround180748":154,"../../webrtc":157,"./sender":38}],24:[function(require,module,exports){
3730
3859
  /* eslint new-cap:0 */
3731
3860
  'use strict';
3732
3861
  var __extends = (this && this.__extends) || (function () {
@@ -3909,7 +4038,7 @@ var __extends = (this && this.__extends) || (function () {
3909
4038
  d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
3910
4039
  };
3911
4040
  })();
3912
- var _a = require('../../webrtc/util'), guessBrowser = _a.guessBrowser, isIOSChrome = _a.isIOSChrome;
4041
+ var isIOS = require('../../util/browserdetection').isIOS;
3913
4042
  var detectSilentVideo = require('../../util/detectsilentvideo');
3914
4043
  var mixinLocalMediaTrack = require('./localmediatrack');
3915
4044
  var VideoTrack = require('./videotrack');
@@ -3940,7 +4069,7 @@ var LocalVideoTrack = /** @class */ (function (_super) {
3940
4069
  function LocalVideoTrack(mediaStreamTrack, options) {
3941
4070
  var _this = this;
3942
4071
  options = Object.assign({
3943
- workaroundSilentLocalVideo: (guessBrowser() === 'safari' || isIOSChrome())
4072
+ workaroundSilentLocalVideo: isIOS()
3944
4073
  && isUserMediaTrack(mediaStreamTrack)
3945
4074
  && typeof document !== 'undefined'
3946
4075
  && typeof document.createElement === 'function'
@@ -4239,7 +4368,7 @@ function workaroundSilentLocalVideo(localVideoTrack, doc) {
4239
4368
  */
4240
4369
  module.exports = LocalVideoTrack;
4241
4370
 
4242
- },{"../../util":131,"../../util/detectsilentvideo":126,"../../webrtc/util":170,"./localmediatrack":23,"./videotrack":42}],26:[function(require,module,exports){
4371
+ },{"../../util":131,"../../util/browserdetection":122,"../../util/detectsilentvideo":126,"./localmediatrack":23,"./videotrack":42}],26:[function(require,module,exports){
4243
4372
  'use strict';
4244
4373
  var __extends = (this && this.__extends) || (function () {
4245
4374
  var extendStatics = function (d, b) {
@@ -4302,9 +4431,9 @@ var __extends = (this && this.__extends) || (function () {
4302
4431
  d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
4303
4432
  };
4304
4433
  })();
4305
- var _a = require('../../webrtc/util'), guessBrowser = _a.guessBrowser, isIOSChrome = _a.isIOSChrome;
4434
+ var isIOS = require('../../util/browserdetection').isIOS;
4306
4435
  var MediaStream = require('../../webrtc').MediaStream;
4307
- var _b = require('../../util'), waitForEvent = _b.waitForEvent, waitForSometime = _b.waitForSometime;
4436
+ var _a = require('../../util'), waitForEvent = _a.waitForEvent, waitForSometime = _a.waitForSometime;
4308
4437
  var localMediaRestartDeferreds = require('../../util/localmediarestartdeferreds');
4309
4438
  var Track = require('./');
4310
4439
  /**
@@ -4334,7 +4463,7 @@ var MediaTrack = /** @class */ (function (_super) {
4334
4463
  function MediaTrack(mediaTrackTransceiver, options) {
4335
4464
  var _this = this;
4336
4465
  options = Object.assign({
4337
- playPausedElementsIfNotBackgrounded: (guessBrowser() === 'safari' || isIOSChrome())
4466
+ playPausedElementsIfNotBackgrounded: isIOS()
4338
4467
  && typeof document === 'object'
4339
4468
  && typeof document.addEventListener === 'function'
4340
4469
  && typeof document.visibilityState === 'string'
@@ -4649,7 +4778,7 @@ function shimMediaElement(el, onUnintentionallyPaused) {
4649
4778
  }
4650
4779
  module.exports = MediaTrack;
4651
4780
 
4652
- },{"../../util":131,"../../util/localmediarestartdeferreds":134,"../../webrtc":157,"../../webrtc/util":170,"./":18}],28:[function(require,module,exports){
4781
+ },{"../../util":131,"../../util/browserdetection":122,"../../util/localmediarestartdeferreds":134,"../../webrtc":157,"./":18}],28:[function(require,module,exports){
4653
4782
  'use strict';
4654
4783
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
4655
4784
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
@@ -5383,7 +5512,7 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from) {
5383
5512
  return to;
5384
5513
  };
5385
5514
  var _a = require('../../util/constants'), E = _a.typeErrors, trackPriority = _a.trackPriority;
5386
- var _b = require('../../webrtc/util'), guessBrowser = _b.guessBrowser, isIOSChrome = _b.isIOSChrome;
5515
+ var isIOS = require('../../util/browserdetection').isIOS;
5387
5516
  var documentVisibilityMonitor = require('../../util/documentvisibilitymonitor.js');
5388
5517
  function mixinRemoteMediaTrack(AudioOrVideoTrack) {
5389
5518
  /**
@@ -5417,7 +5546,7 @@ function mixinRemoteMediaTrack(AudioOrVideoTrack) {
5417
5546
  // NOTE(mpatwardhan): WebKit bug: 212780 sometimes causes the audio/video elements to stay paused when safari
5418
5547
  // regains foreground. To workaround it, when safari gains foreground - we will play any elements that were
5419
5548
  // playing before safari lost foreground.
5420
- workaroundWebKitBug212780: (guessBrowser() === 'safari' || isIOSChrome())
5549
+ workaroundWebKitBug212780: isIOS()
5421
5550
  && typeof document === 'object'
5422
5551
  && typeof document.addEventListener === 'function'
5423
5552
  && typeof document.visibilityState === 'string'
@@ -5630,7 +5759,7 @@ function playIfPausedWhileInBackground(remoteMediaTrack) {
5630
5759
  */
5631
5760
  module.exports = mixinRemoteMediaTrack;
5632
5761
 
5633
- },{"../../util/constants":124,"../../util/documentvisibilitymonitor.js":127,"../../webrtc/util":170}],35:[function(require,module,exports){
5762
+ },{"../../util/browserdetection":122,"../../util/constants":124,"../../util/documentvisibilitymonitor.js":127}],35:[function(require,module,exports){
5634
5763
  'use strict';
5635
5764
  var __extends = (this && this.__extends) || (function () {
5636
5765
  var extendStatics = function (d, b) {
@@ -20661,36 +20790,37 @@ var __read = (this && this.__read) || function (o, n) {
20661
20790
  function isAndroid() {
20662
20791
  return /Android/.test(navigator.userAgent);
20663
20792
  }
20664
- /**
20665
- * Check whether the current browser is an iOS device.
20666
- * @returns {boolean}
20667
- */
20668
- function isIOS() {
20669
- return /iPad|iPhone|iPod/.test(navigator.userAgent);
20670
- }
20671
20793
  /**
20672
20794
  * Detects whether or not a device is an Apple touch screen device.
20673
20795
  * @returns {boolean}
20674
20796
  */
20675
20797
  function hasTouchScreen() {
20676
- return navigator && navigator.maxTouchPoints &&
20677
- navigator.maxTouchPoints > 2;
20798
+ return !!(navigator && navigator.maxTouchPoints && navigator.maxTouchPoints > 2);
20678
20799
  }
20679
20800
  /**
20680
- * Detects whether or not a device is an iPad
20801
+ * Detects whether or not a device is an iPad.
20681
20802
  * @returns {boolean}
20682
20803
  */
20683
20804
  function isIpad() {
20684
- return hasTouchScreen() && window.screen.width >= 744 &&
20685
- (/Macintosh/i.test(navigator.userAgent) || /iPad/.test(navigator.userAgent) || /iPad/.test(navigator.platform));
20805
+ return hasTouchScreen() && window.screen.width >= 744 && (/Macintosh/i.test(navigator.userAgent)
20806
+ || /iPad/.test(navigator.userAgent)
20807
+ || /iPad/.test(navigator.platform));
20686
20808
  }
20687
20809
  /**
20688
- * Detects whether or not a device is an iPhone
20810
+ * Detects whether or not a device is an iPhone.
20689
20811
  * @returns {boolean}
20690
20812
  */
20691
20813
  function isIphone() {
20692
- return hasTouchScreen() && window.screen.width <= 476 &&
20693
- (/Macintosh/i.test(navigator.userAgent) || /iPhone/.test(navigator.userAgent) || /iPhone/.test(navigator.platform));
20814
+ return hasTouchScreen() && window.screen.width <= 476 && (/Macintosh/i.test(navigator.userAgent)
20815
+ || /iPhone/.test(navigator.userAgent)
20816
+ || /iPhone/.test(navigator.platform));
20817
+ }
20818
+ /**
20819
+ * Check whether the current device is an iOS device.
20820
+ * @returns {boolean}
20821
+ */
20822
+ function isIOS() {
20823
+ return isIpad() || isIphone();
20694
20824
  }
20695
20825
  /**
20696
20826
  * Check whether the current browser is a mobile browser
@@ -29532,7 +29662,7 @@ module.exports={
29532
29662
  "name": "twilio-video",
29533
29663
  "title": "Twilio Video",
29534
29664
  "description": "Twilio Video JavaScript Library",
29535
- "version": "2.24.3",
29665
+ "version": "2.25.0",
29536
29666
  "homepage": "https://twilio.com",
29537
29667
  "author": "Mark Andrus Roberts <mroberts@twilio.com>",
29538
29668
  "contributors": [
@@ -29633,7 +29763,7 @@ module.exports={
29633
29763
  "test:serversiderender": "mocha ./test/serversiderender/index.js",
29634
29764
  "test:integration:adapter": "node ./scripts/karma.js karma/integration.adapter.conf.js",
29635
29765
  "test:integration": "npm run build:es5 && node ./scripts/karma.js karma/integration.conf.js",
29636
- "test:umd:install": "npm install puppeteer@5.5.0",
29766
+ "test:umd:install": "npm install puppeteer@19.2.2",
29637
29767
  "test:umd": "mocha ./test/umd/index.js",
29638
29768
  "test:crossbrowser:build:clean": "rimraf ./test/crossbrowser/lib ./test/crossbrowser/src/browser/index.js",
29639
29769
  "test:crossbrowser:build:lint": "cd ./test/crossbrowser && tslint --project tsconfig.json",