twilio-video 2.26.1 → 2.27.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,13 +2,66 @@ 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.27.0 (March 21, 2023)
6
+ =======================
7
+
8
+ Changes
9
+ -------
10
+
11
+ `VideoTrack.addProcessor` now works on browsers that support `OffscreenCanvas` as well as `HTMLCanvasElement`. When used with
12
+ [@twilio/video-processors v2.0.0](https://github.com/twilio/twilio-video-processors.js/blob/2.0.0/CHANGELOG.md), the Virtual
13
+ Background feature will work on browsers that supports [WebGL2](https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext).
14
+ See [VideoTrack.addProcessor](https://sdk.twilio.com/js/video/releases/2.27.0/docs/VideoTrack.html#addProcessor__anchor) and
15
+ [@twilio/video-processors v2.0.0](https://github.com/twilio/twilio-video-processors.js/blob/2.0.0/CHANGELOG.md) for details.
16
+
17
+ ### Example
18
+
19
+ ```ts
20
+ import { createLocalVideoTrack } from 'twilio-video';
21
+ import { Pipeline, VirtualBackgroundProcessor } from '@twilio/video-processors';
22
+
23
+ const virtualBackgroundProcessor = new VirtualBackgroundProcessor({
24
+ pipeline: Pipeline.WebGL2,
25
+ // ...otherOptions
26
+ });
27
+
28
+ await virtualBackgroundProcessor.loadModel();
29
+
30
+ const videoTrack = await createLocalVideoTrack({
31
+ width: 640,
32
+ height: 480,
33
+ frameRate: 24
34
+ });
35
+
36
+ videoTrack.addProcessor(processor, {
37
+ inputFrameBufferType: 'video',
38
+ outputFrameBufferContextType: 'webgl2',
39
+ });
40
+ ```
41
+
42
+ 2.26.2 (February 21, 2023)
43
+ ==========================
44
+
45
+ Changes
46
+ -------
47
+
48
+ - Starting from version 110, Chrome will no longer support the iSAC audio codec. The SDK will now log a warning to the console
49
+ whenever an audio or a video codec that is specified in `ConnectOptions.preferredAudioCodecs` and `ConnectOptions.preferredVideoCodecs`
50
+ is not supported by the browser. (VIDEO-12494)
51
+
52
+ Bug Fixes
53
+ ---------
54
+
55
+ - Fixed a bug on Chrome versions 112+ where `Room.getStats()` did not reject the returned Promise when an exception was
56
+ raised while accessing WebRTC stats that due to a TypeError caused by trying to read from the now-removed `RTCMediaStreamTrackStats`. (VIDEO-12534)
57
+
5
58
  2.26.1 (January 31, 2023)
6
59
  =========================
7
60
 
8
61
  Bug Fixes
9
62
  ---------
10
63
 
11
- - Fixed a bug that manifests on Chrome versions 112+ where `Room.getStats()` rejects the returned Promise due to a
64
+ - Fixed a bug that manifests on Chrome versions 112+ where `Room.getStats()` raises an unhandled exception due to a
12
65
  TypeError caused by trying to read from the now-removed `RTCMediaStreamTrackStats`. Instead, the SDK now reads from
13
66
  the `RTCMediaSourceStats`. (VIDEO-12411)
14
67
  - Fixed an error in the type definition for the `attach()` method of `AudioTrack` and `VideoTrack`. (VIDEO-12242)
package/COMMON_ISSUES.md CHANGED
@@ -200,10 +200,10 @@ transform: scaleX(-1)
200
200
  </p>
201
201
  </details>
202
202
  <details>
203
- <summary>iOS 15: Audio is lost in a video call after an interruption with a phone call</summary>
203
+ <summary>iOS 16: Local Media Tracks are lost in a video call after an interruption with a phone call</summary>
204
204
  <p>
205
205
 
206
- Due to a regression on Safari on iOS 15, an incoming call causes local and sometimes remote media playback to stop. You can find more details in this [WebKit bug](https://bugs.webkit.org/show_bug.cgi?id=230537).
206
+ Due to a bug on Safari on iOS 16, an incoming call causes local and sometimes remote media playback to stop after the **second** incoming call. You can find more details in this [WebKit bug](https://bugs.webkit.org/show_bug.cgi?id=240651). As a workaround, you can call the [Room.refreshInactiveMedia](https://sdk.twilio.com/js/video/releases/2.26.0/docs/Room.html#refreshInactiveMedia__anchor) method in your application in order to restart the muted local media tracks and resume playback of the paused remote media tracks.
207
207
  </p>
208
208
  </details>
209
209
  <details>
@@ -348,7 +348,7 @@ trouble with twilio-video.js, ensure these are not running.
348
348
  <summary>setSinkId method is not implemented in all browsers</summary>
349
349
  <p>
350
350
 
351
- The `audioElement.setSinkId()` method, which is used to change the audio output device for a given HTML audio element, is only implemented in Desktop Chrome and Desktop Edge. Therefore, it is not possible for users to change their audio output device in other browsers. Users will have to use their operating system settings to change their audio output device instead.
351
+ The `audioElement.setSinkId()` method, which is used to change the audio output device for a given HTML audio element, is only implemented in Desktop Chrome and Desktop Edge. Therefore, it is not possible for users to change their audio output device in other browsers. Users will have to use their operating system settings to change their audio output device instead.
352
352
 
353
353
  More information about this method (including browser compatibility) is available [here](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/setSinkId).
354
354
  </p>
package/README.md CHANGED
@@ -74,7 +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 &lt;script&gt; tag.
75
75
 
76
76
  ```html
77
- <script src="//sdk.twilio.com/js/video/releases/2.26.1/twilio-video.min.js"></script>
77
+ <script src="//sdk.twilio.com/js/video/releases/2.27.0/twilio-video.min.js"></script>
78
78
  ```
79
79
 
80
80
  Using this method, twilio-video.js will set a browser global:
@@ -1,4 +1,4 @@
1
- /*! twilio-video.js 2.26.1
1
+ /*! twilio-video.js 2.27.0
2
2
 
3
3
  The following license applies to all parts of this software except as
4
4
  documented below.
@@ -86,7 +86,7 @@ module.exports = createCancelableRoomPromise;
86
86
  },{"./util/cancelablepromise":124}],2:[function(require,module,exports){
87
87
  'use strict';
88
88
  var MediaStreamTrack = require('./webrtc').MediaStreamTrack;
89
- var _a = require('./webrtc/util'), guessBrowser = _a.guessBrowser, guessBrowserVersion = _a.guessBrowserVersion;
89
+ var _a = require('./webrtc/util'), guessBrowser = _a.guessBrowser, guessBrowserVersion = _a.guessBrowserVersion, isCodecSupported = _a.isCodecSupported;
90
90
  var createCancelableRoomPromise = require('./cancelableroompromise');
91
91
  var EncodingParametersImpl = require('./encodingparameters');
92
92
  var LocalParticipant = require('./localparticipant');
@@ -407,6 +407,11 @@ function connect(token, options) {
407
407
  video: options.preferredVideoCodecs.map(normalizeCodecSettings)
408
408
  };
409
409
  var networkQualityConfiguration = new NetworkQualityConfigurationImpl(isNonArrayObject(options.networkQuality) ? options.networkQuality : {});
410
+ // Log warnings for any unsupported preferred codecs.
411
+ ['audio', 'video'].forEach(function (kind) { return preferredCodecs[kind].forEach(function (_a) {
412
+ var codec = _a.codec;
413
+ return isCodecSupported(codec, kind).then(function (isSupported) { return !isSupported && log.warn("The preferred " + kind + " codec \"" + codec + "\" will be ignored as it is not supported by the browser."); });
414
+ }); });
410
415
  // Create a CancelableRoomPromise<Room> that resolves after these steps:
411
416
  // 1 - Get the LocalTracks.
412
417
  // 2 - Create the LocalParticipant using options.tracks.
@@ -3868,8 +3873,10 @@ function restartWhenInadvertentlyStopped(localMediaTrack) {
3868
3873
  }).catch(function (error) {
3869
3874
  log.warn('Failed to detect silence:', error);
3870
3875
  }).finally(function () {
3871
- // Pause the dummy element again.
3872
- el.pause();
3876
+ // Pause the dummy element again, if there is no processed track.
3877
+ if (!localMediaTrack.processedTrack) {
3878
+ el.pause();
3879
+ }
3873
3880
  });
3874
3881
  }
3875
3882
  function shouldReacquireTrack() {
@@ -4227,8 +4234,8 @@ var LocalVideoTrack = /** @class */ (function (_super) {
4227
4234
  };
4228
4235
  /**
4229
4236
  * Add a {@link VideoProcessor} to allow for custom processing of video frames belonging to a VideoTrack.
4230
- * Only Chrome supports this as of now. Calling this API from a non-supported browser will result in a log warning.
4231
4237
  * @param {VideoProcessor} processor - The {@link VideoProcessor} to use.
4238
+ * @param {AddProcessorOptions} [options] - {@link AddProcessorOptions} to provide.
4232
4239
  * @returns {this}
4233
4240
  * @example
4234
4241
  * class GrayScaleProcessor {
@@ -4428,9 +4435,10 @@ function workaroundSilentLocalVideo(localVideoTrack, doc) {
4428
4435
  }).catch(function (error) {
4429
4436
  log.warn('Failed to detect silence and restart:', error);
4430
4437
  }).finally(function () {
4431
- // If silent frames were not detected, then pause the dummy element again.
4438
+ // If silent frames were not detected, then pause the dummy element again,
4439
+ // if there is no processed track.
4432
4440
  el = localVideoTrack._dummyEl;
4433
- if (el && !el.paused) {
4441
+ if (el && !el.paused && !localVideoTrack.processedTrack) {
4434
4442
  el.pause();
4435
4443
  }
4436
4444
  // Reset the unmute handler.
@@ -6346,8 +6354,8 @@ var RemoteVideoTrack = /** @class */ (function (_super) {
6346
6354
  * Add a {@link VideoProcessor} to allow for custom processing of video frames belonging to a VideoTrack.
6347
6355
  * When a Participant un-publishes and re-publishes a VideoTrack, a new RemoteVideoTrack is created and
6348
6356
  * any VideoProcessors attached to the previous RemoteVideoTrack would have to be re-added again.
6349
- * Only Chrome supports this as of now. Calling this API from a non-supported browser will result in a log warning.
6350
6357
  * @param {VideoProcessor} processor - The {@link VideoProcessor} to use.
6358
+ * @param {AddProcessorOptions} [options] - {@link AddProcessorOptions} to provide.
6351
6359
  * @returns {this}
6352
6360
  * @example
6353
6361
  * class GrayScaleProcessor {
@@ -7014,10 +7022,10 @@ var VideoProcessorEventObserver = /** @class */ (function (_super) {
7014
7022
  if (!this._processorInfo) {
7015
7023
  return {};
7016
7024
  }
7017
- var _a = this._processorInfo, processor = _a.processor, captureHeight = _a.captureHeight, captureWidth = _a.captureWidth, inputFrameRate = _a.inputFrameRate, isRemoteVideoTrack = _a.isRemoteVideoTrack;
7018
- var data = { captureHeight: captureHeight, captureWidth: captureWidth, inputFrameRate: inputFrameRate, isRemoteVideoTrack: isRemoteVideoTrack };
7025
+ var _a = this._processorInfo, processor = _a.processor, captureHeight = _a.captureHeight, captureWidth = _a.captureWidth, inputFrameRate = _a.inputFrameRate, isRemoteVideoTrack = _a.isRemoteVideoTrack, inputFrameBufferType = _a.inputFrameBufferType, outputFrameBufferContextType = _a.outputFrameBufferContextType;
7026
+ var data = { captureHeight: captureHeight, captureWidth: captureWidth, inputFrameRate: inputFrameRate, isRemoteVideoTrack: isRemoteVideoTrack, inputFrameBufferType: inputFrameBufferType, outputFrameBufferContextType: outputFrameBufferContextType };
7019
7027
  data.name = processor._name || 'VideoProcessor';
7020
- ['assetsPath', 'blurFilterRadius', 'fitType', 'isSimdEnabled', 'maskBlurRadius', 'version'].forEach(function (prop) {
7028
+ ['assetsPath', 'blurFilterRadius', 'debounce', 'fitType', 'isSimdEnabled', 'maskBlurRadius', 'pipeline', 'version'].forEach(function (prop) {
7021
7029
  var val = processor["_" + prop];
7022
7030
  if (typeof val !== 'undefined') {
7023
7031
  data[prop] = val;
@@ -7155,6 +7163,10 @@ var VideoTrack = /** @class */ (function (_super) {
7155
7163
  value: null,
7156
7164
  writable: true,
7157
7165
  },
7166
+ _processorOptions: {
7167
+ value: {},
7168
+ writable: true,
7169
+ },
7158
7170
  _unmuteHandler: {
7159
7171
  value: null,
7160
7172
  writable: true
@@ -7246,18 +7258,21 @@ var VideoTrack = /** @class */ (function (_super) {
7246
7258
  var _a = _this.mediaStreamTrack.getSettings(), _b = _a.width, width = _b === void 0 ? 0 : _b, _c = _a.height, height = _c === void 0 ? 0 : _c;
7247
7259
  // Setting the canvas' dimension triggers a redraw.
7248
7260
  // Only set it if it has changed.
7249
- if (_this._inputFrame.width !== width) {
7250
- _this._inputFrame.width = width;
7251
- _this._inputFrame.height = height;
7252
- if (_this._outputFrame) {
7253
- _this._outputFrame.width = width;
7254
- _this._outputFrame.height = height;
7261
+ if (_this._outputFrame && _this._outputFrame.width !== width) {
7262
+ _this._outputFrame.width = width;
7263
+ _this._outputFrame.height = height;
7264
+ }
7265
+ if (_this._inputFrame) {
7266
+ if (_this._inputFrame.width !== width) {
7267
+ _this._inputFrame.width = width;
7268
+ _this._inputFrame.height = height;
7255
7269
  }
7270
+ _this._inputFrame.getContext('2d').drawImage(_this._dummyEl, 0, 0, width, height);
7256
7271
  }
7257
- _this._inputFrame.getContext('2d').drawImage(_this._dummyEl, 0, 0, width, height);
7258
7272
  var result = null;
7259
7273
  try {
7260
- result = _this.processor.processFrame(_this._inputFrame, _this._outputFrame);
7274
+ var input = _this._processorOptions.inputFrameBufferType === 'video' ? _this._dummyEl : _this._inputFrame;
7275
+ result = _this.processor.processFrame(input, _this._outputFrame);
7261
7276
  }
7262
7277
  catch (ex) {
7263
7278
  _this._log.debug('Exception detected after calling processFrame.', ex);
@@ -7265,7 +7280,9 @@ var VideoTrack = /** @class */ (function (_super) {
7265
7280
  ((result instanceof Promise) ? result : Promise.resolve(result))
7266
7281
  .then(function () {
7267
7282
  if (_this._outputFrame) {
7268
- _this.processedTrack.requestFrame();
7283
+ if (typeof _this.processedTrack.requestFrame === 'function') {
7284
+ _this.processedTrack.requestFrame();
7285
+ }
7269
7286
  _this._processorEventObserver.emit('stats');
7270
7287
  }
7271
7288
  })
@@ -7308,8 +7325,9 @@ var VideoTrack = /** @class */ (function (_super) {
7308
7325
  VideoTrack.prototype._restartProcessor = function () {
7309
7326
  var processor = this.processor;
7310
7327
  if (processor) {
7328
+ var processorOptions = Object.assign({}, this._processorOptions);
7311
7329
  this.removeProcessor(processor);
7312
- this.addProcessor(processor);
7330
+ this.addProcessor(processor, processorOptions);
7313
7331
  }
7314
7332
  };
7315
7333
  /**
@@ -7324,8 +7342,8 @@ var VideoTrack = /** @class */ (function (_super) {
7324
7342
  };
7325
7343
  /**
7326
7344
  * Add a {@link VideoProcessor} to allow for custom processing of video frames belonging to a VideoTrack.
7327
- * Only Chrome supports this as of now. Calling this API from a non-supported browser will result in a log warning.
7328
7345
  * @param {VideoProcessor} processor - The {@link VideoProcessor} to use.
7346
+ * @param {AddProcessorOptions} [options] - {@link AddProcessorOptions} to provide.
7329
7347
  * @returns {this}
7330
7348
  * @example
7331
7349
  * class GrayScaleProcessor {
@@ -7343,11 +7361,8 @@ var VideoTrack = /** @class */ (function (_super) {
7343
7361
  * videoTrack.addProcessor(new GrayScaleProcessor(100));
7344
7362
  * });
7345
7363
  */
7346
- VideoTrack.prototype.addProcessor = function (processor) {
7364
+ VideoTrack.prototype.addProcessor = function (processor, options) {
7347
7365
  var _this = this;
7348
- if (typeof OffscreenCanvas !== 'function') {
7349
- return this._log.warn('Adding a VideoProcessor is not supported in this browser.');
7350
- }
7351
7366
  if (!processor || typeof processor.processFrame !== 'function') {
7352
7367
  throw new Error('Received an invalid VideoProcessor from addProcessor.');
7353
7368
  }
@@ -7371,12 +7386,46 @@ var VideoTrack = /** @class */ (function (_super) {
7371
7386
  };
7372
7387
  this.mediaStreamTrack.addEventListener('unmute', this._unmuteHandler);
7373
7388
  }
7374
- var _a = this.mediaStreamTrack.getSettings(), _b = _a.width, width = _b === void 0 ? 0 : _b, _c = _a.height, height = _c === void 0 ? 0 : _c, _d = _a.frameRate, frameRate = _d === void 0 ? DEFAULT_FRAME_RATE : _d;
7375
- this._inputFrame = new OffscreenCanvas(width, height);
7389
+ this._processorOptions = options || {};
7390
+ var _a = this._processorOptions, inputFrameBufferType = _a.inputFrameBufferType, outputFrameBufferContextType = _a.outputFrameBufferContextType;
7391
+ if (typeof OffscreenCanvas === 'undefined' && inputFrameBufferType === 'offscreencanvas') {
7392
+ throw new Error('OffscreenCanvas is not supported by this browser.');
7393
+ }
7394
+ if (inputFrameBufferType && inputFrameBufferType !== 'video' && inputFrameBufferType !== 'canvas' && inputFrameBufferType !== 'offscreencanvas') {
7395
+ throw new Error("Invalid inputFrameBufferType of " + inputFrameBufferType);
7396
+ }
7397
+ if (!inputFrameBufferType) {
7398
+ inputFrameBufferType = typeof OffscreenCanvas === 'undefined' ? 'canvas' : 'offscreencanvas';
7399
+ }
7400
+ var _b = this.mediaStreamTrack.getSettings(), _c = _b.width, width = _c === void 0 ? 0 : _c, _d = _b.height, height = _d === void 0 ? 0 : _d, _e = _b.frameRate, frameRate = _e === void 0 ? DEFAULT_FRAME_RATE : _e;
7401
+ if (inputFrameBufferType === 'offscreencanvas') {
7402
+ this._inputFrame = new OffscreenCanvas(width, height);
7403
+ }
7404
+ if (inputFrameBufferType === 'canvas') {
7405
+ this._inputFrame = document.createElement('canvas');
7406
+ }
7407
+ if (this._inputFrame) {
7408
+ this._inputFrame.width = width;
7409
+ this._inputFrame.height = height;
7410
+ }
7376
7411
  this._outputFrame = document.createElement('canvas');
7377
7412
  this._outputFrame.width = width;
7378
7413
  this._outputFrame.height = height;
7379
- this.processedTrack = this._outputFrame.captureStream(0).getTracks()[0];
7414
+ // NOTE(csantos): Initialize the rendering context for future renders. This also ensures
7415
+ // that the correct type is used and on Firefox, it throws an exception if you try to capture
7416
+ // frames prior calling getContext https://bugzilla.mozilla.org/show_bug.cgi?id=1572422
7417
+ outputFrameBufferContextType = outputFrameBufferContextType || '2d';
7418
+ var ctx = this._outputFrame.getContext(outputFrameBufferContextType);
7419
+ if (!ctx) {
7420
+ throw new Error("Cannot get outputFrameBufferContextType: " + outputFrameBufferContextType + ".");
7421
+ }
7422
+ // NOTE(csantos): Zero FPS means we can control when to render the next frame by calling requestFrame.
7423
+ // Some browsers such as Firefox doesn't support requestFrame so we will use default, which is an undefined value.
7424
+ // This means, the browser will use the highest FPS available.
7425
+ var targetFps = typeof CanvasCaptureMediaStreamTrack !== 'undefined' && CanvasCaptureMediaStreamTrack.prototype &&
7426
+ // eslint-disable-next-line
7427
+ typeof CanvasCaptureMediaStreamTrack.prototype.requestFrame === 'function' ? 0 : undefined;
7428
+ this.processedTrack = this._outputFrame.captureStream(targetFps).getTracks()[0];
7380
7429
  this.processedTrack.enabled = this.mediaStreamTrack.enabled;
7381
7430
  this.processor = processor;
7382
7431
  this._processorEventObserver.emit('add', {
@@ -7384,7 +7433,9 @@ var VideoTrack = /** @class */ (function (_super) {
7384
7433
  captureHeight: height,
7385
7434
  captureWidth: width,
7386
7435
  inputFrameRate: frameRate,
7387
- isRemoteVideoTrack: this.toString().includes('RemoteVideoTrack')
7436
+ isRemoteVideoTrack: this.toString().includes('RemoteVideoTrack'),
7437
+ inputFrameBufferType: inputFrameBufferType,
7438
+ outputFrameBufferContextType: outputFrameBufferContextType
7388
7439
  });
7389
7440
  this._updateElementsMediaStreamTrack();
7390
7441
  this._captureFrames();
@@ -7521,12 +7572,11 @@ var VideoTrack = /** @class */ (function (_super) {
7521
7572
  this._log.debug('Removing VideoProcessor from the VideoTrack', processor);
7522
7573
  clearTimeout(this._captureTimeoutId);
7523
7574
  this.mediaStreamTrack.removeEventListener('unmute', this._unmuteHandler);
7575
+ this._processorOptions = {};
7524
7576
  this._unmuteHandler = null;
7525
7577
  this._isCapturing = false;
7526
7578
  this.processor = null;
7527
7579
  this.processedTrack = null;
7528
- this._inputFrame.getContext('2d').clearRect(0, 0, this._inputFrame.width, this._inputFrame.height);
7529
- this._outputFrame.getContext('2d').clearRect(0, 0, this._outputFrame.width, this._outputFrame.height);
7530
7580
  this._inputFrame = null;
7531
7581
  this._outputFrame = null;
7532
7582
  this._updateElementsMediaStreamTrack();
@@ -7560,7 +7610,7 @@ function dimensionsChanged(track, elem) {
7560
7610
  * Any exception raised (either synchronously or asynchronously) in `processFrame` will result in the frame being dropped.
7561
7611
  * This callback has the following signature:<br/><br/>
7562
7612
  * <code>processFrame(</code><br/>
7563
- * &nbsp;&nbsp;<code>inputFrameBuffer: OffscreenCanvas,</code><br/>
7613
+ * &nbsp;&nbsp;<code>inputFrameBuffer: OffscreenCanvas | HTMLCanvasElement | HTMLVideoElement,</code><br/>
7564
7614
  * &nbsp;&nbsp;<code>outputFrameBuffer: HTMLCanvasElement</code><br/>
7565
7615
  * <code>): Promise&lt;void&gt; | void;</code>
7566
7616
  *
@@ -7576,6 +7626,30 @@ function dimensionsChanged(track, elem) {
7576
7626
  * }
7577
7627
  * }
7578
7628
  */
7629
+ /**
7630
+ * Possible options to provide to {@link LocalVideoTrack#addProcessor} and {@link RemoteVideoTrack#addProcessor}.
7631
+ * @typedef {object} AddProcessorOptions
7632
+ * @property {string} [inputFrameBufferType="offscreencanvas"] - This option allows you to specify what kind of input you want to receive in your
7633
+ * Video Processor. The default is `offscreencanvas` and will fallback to a regular `canvas` if the browser does not support it.
7634
+ * Possible values include the following.
7635
+ * <br/>
7636
+ * <br/>
7637
+ * `offscreencanvas` - Your Video Processor will receive an [OffscreenCanvas](https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas)
7638
+ * which is good for canvas-related processing that can be rendered off screen.
7639
+ * <br/>
7640
+ * <br/>
7641
+ * `canvas` - Your Video Processor will receive an [HTMLCanvasElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement).
7642
+ * This is recommended on browsers that doesn't support `OffscreenCanvas`, or if you need to render the frame on the screen.
7643
+ * <br/>
7644
+ * <br/>
7645
+ * `video` - Your Video Processor will receive an [HTMLVideoElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement).
7646
+ * Use this option if you are processing the frame using WebGL or if you only need to [draw](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage)
7647
+ * the frame directly to your output canvas.
7648
+ * @property {string} [outputFrameBufferContextType="2d"] - The SDK needs the [context type](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext)
7649
+ * that your Video Processor uses in order to properly generate the processed track. For example, if your Video Processor uses WebGL2 (`canvas.getContext('webgl2')`),
7650
+ * you should set `outputFrameBufferContextType` to `webgl2`. Or if you're using Canvas 2D processing (`canvas.getContext('2d')`),
7651
+ * you should set `outputFrameBufferContextType` to `2d`.
7652
+ */
7579
7653
  /**
7580
7654
  * The {@link VideoTrack}'s dimensions changed.
7581
7655
  * @param {VideoTrack} track - The {@link VideoTrack} whose dimensions changed
@@ -26761,14 +26835,14 @@ function getTracks(peerConnection, kind, localOrRemote) {
26761
26835
  function getTrackStats(peerConnection, track, options) {
26762
26836
  if (options === void 0) { options = {}; }
26763
26837
  if (typeof options.testForChrome !== 'undefined' || isChrome) {
26764
- return chromeOrSafariGetTrackStats(peerConnection, track);
26838
+ return chromeOrSafariGetTrackStats(peerConnection, track, options);
26765
26839
  }
26766
26840
  if (typeof options.testForFirefox !== 'undefined' || isFirefox) {
26767
- return firefoxGetTrackStats(peerConnection, track, options.isRemote);
26841
+ return firefoxGetTrackStats(peerConnection, track, options);
26768
26842
  }
26769
26843
  if (typeof options.testForSafari !== 'undefined' || isSafari) {
26770
26844
  if (typeof options.testForSafari !== 'undefined' || getSdpFormat() === 'unified') {
26771
- return chromeOrSafariGetTrackStats(peerConnection, track);
26845
+ return chromeOrSafariGetTrackStats(peerConnection, track, options);
26772
26846
  }
26773
26847
  // NOTE(syerrapragada): getStats() is not supported on
26774
26848
  // Safari versions where plan-b is the SDP format
@@ -26784,33 +26858,31 @@ function getTrackStats(peerConnection, track, options) {
26784
26858
  * Get the standardized statistics for a particular MediaStreamTrack in Chrome or Safari.
26785
26859
  * @param {RTCPeerConnection} peerConnection
26786
26860
  * @param {MediaStreamTrack} track
26861
+ * @param {object} options - Used for testing
26787
26862
  * @returns {Promise.<Array<StandardizedTrackStatsReport>>}
26788
26863
  */
26789
- function chromeOrSafariGetTrackStats(peerConnection, track) {
26790
- return new Promise(function (resolve, reject) {
26791
- if (chromeMajorVersion && chromeMajorVersion < 67) {
26864
+ function chromeOrSafariGetTrackStats(peerConnection, track, options) {
26865
+ if (chromeMajorVersion && chromeMajorVersion < 67) {
26866
+ return new Promise(function (resolve, reject) {
26792
26867
  peerConnection.getStats(function (response) {
26793
26868
  resolve([standardizeChromeLegacyStats(response, track)]);
26794
26869
  }, null, reject);
26795
- return;
26796
- }
26797
- peerConnection.getStats(track).then(function (response) {
26798
- resolve(standardizeChromeOrSafariStats(response));
26799
- }, reject);
26870
+ });
26871
+ }
26872
+ return peerConnection.getStats(track).then(function (response) {
26873
+ return standardizeChromeOrSafariStats(response, options);
26800
26874
  });
26801
26875
  }
26802
26876
  /**
26803
26877
  * Get the standardized statistics for a particular MediaStreamTrack in Firefox.
26804
26878
  * @param {RTCPeerConnection} peerConnection
26805
26879
  * @param {MediaStreamTrack} track
26806
- * @param {boolean} isRemote
26880
+ * @param {object} options
26807
26881
  * @returns {Promise.<Array<StandardizedTrackStatsReport>>}
26808
26882
  */
26809
- function firefoxGetTrackStats(peerConnection, track, isRemote) {
26810
- return new Promise(function (resolve, reject) {
26811
- peerConnection.getStats(track).then(function (response) {
26812
- resolve([standardizeFirefoxStats(response, isRemote)]);
26813
- }, reject);
26883
+ function firefoxGetTrackStats(peerConnection, track, options) {
26884
+ return peerConnection.getStats(track).then(function (response) {
26885
+ return [standardizeFirefoxStats(response, options)];
26814
26886
  });
26815
26887
  }
26816
26888
  /**
@@ -26885,9 +26957,14 @@ function standardizeChromeLegacyStats(response, track) {
26885
26957
  /**
26886
26958
  * Standardize the MediaStreamTrack's statistics in Chrome or Safari.
26887
26959
  * @param {RTCStatsResponse} response
26960
+ * @param {object} options - Used for testing
26888
26961
  * @returns {Array<StandardizedTrackStatsReport>}
26889
26962
  */
26890
- function standardizeChromeOrSafariStats(response) {
26963
+ function standardizeChromeOrSafariStats(response, _a) {
26964
+ var _b = _a.simulateExceptionWhileStandardizingStats, simulateExceptionWhileStandardizingStats = _b === void 0 ? false : _b;
26965
+ if (simulateExceptionWhileStandardizingStats) {
26966
+ throw new Error('Error while gathering stats');
26967
+ }
26891
26968
  var inbound = null;
26892
26969
  // NOTE(mpatwardhan): We should expect more than one "outbound-rtp" stats for a
26893
26970
  // VP8 simulcast MediaStreamTrack.
@@ -27054,17 +27131,21 @@ function standardizeChromeOrSafariStats(response) {
27054
27131
  /**
27055
27132
  * Standardize the MediaStreamTrack's statistics in Firefox.
27056
27133
  * @param {RTCStatsReport} response
27057
- * @param {boolean} isRemote
27134
+ * @param {object} options - Used for testing
27058
27135
  * @returns {StandardizedTrackStatsReport}
27059
27136
  */
27060
- function standardizeFirefoxStats(response, isRemote) {
27137
+ function standardizeFirefoxStats(response, _a) {
27138
+ if (response === void 0) { response = new Map(); }
27139
+ var isRemote = _a.isRemote, _b = _a.simulateExceptionWhileStandardizingStats, simulateExceptionWhileStandardizingStats = _b === void 0 ? false : _b;
27140
+ if (simulateExceptionWhileStandardizingStats) {
27141
+ throw new Error('Error while gathering stats');
27142
+ }
27061
27143
  // NOTE(mroberts): If getStats is called on a closed RTCPeerConnection,
27062
27144
  // Firefox returns undefined instead of an RTCStatsReport. We workaround this
27063
27145
  // here. See the following bug for more details:
27064
27146
  //
27065
27147
  // https://bugzilla.mozilla.org/show_bug.cgi?id=1377225
27066
27148
  //
27067
- if (response === void 0) { response = new Map(); }
27068
27149
  var inbound = null;
27069
27150
  var outbound = null;
27070
27151
  // NOTE(mmalavalli): Starting from Firefox 63, RTC{Inbound, Outbound}RTPStreamStats.isRemote
@@ -29093,18 +29174,79 @@ function support() {
29093
29174
  && typeof navigator.mediaDevices.getUserMedia === 'function'
29094
29175
  && typeof RTCPeerConnection === 'function';
29095
29176
  }
29177
+ /**
29178
+ * Create a Set of supported codecs for a certain kind of media.
29179
+ * @param {'audio'|'video'} kind
29180
+ * @returns {Promise<Set<AudioCodec|VideoCodec>>}
29181
+ */
29182
+ function createSupportedCodecsSet(kind) {
29183
+ if (typeof RTCRtpSender !== 'undefined'
29184
+ && typeof RTCRtpSender.getCapabilities === 'function') {
29185
+ return Promise.resolve(new Set(RTCRtpSender
29186
+ .getCapabilities(kind)
29187
+ .codecs
29188
+ .map(function (_a) {
29189
+ var mimeType = _a.mimeType;
29190
+ return mimeType.split('/')[1].toLowerCase();
29191
+ })));
29192
+ }
29193
+ if (typeof RTCPeerConnection === 'undefined'
29194
+ || typeof RTCPeerConnection.prototype === 'undefined'
29195
+ || typeof RTCPeerConnection.prototype.addTransceiver !== 'function'
29196
+ || typeof RTCPeerConnection.prototype.close !== 'function'
29197
+ || typeof RTCPeerConnection.prototype.createOffer !== 'function') {
29198
+ return Promise.resolve(new Set());
29199
+ }
29200
+ var pc = new RTCPeerConnection();
29201
+ pc.addTransceiver(kind);
29202
+ return pc.createOffer().then(function (_a) {
29203
+ var sdp = _a.sdp;
29204
+ pc.close();
29205
+ return new Set((sdp.match(/^a=rtpmap:.+$/gm) || [])
29206
+ .map(function (line) { return line.match(/^a=rtpmap:.+ ([^/]+)/)[1].toLowerCase(); }));
29207
+ }, function () {
29208
+ pc.close();
29209
+ return new Set();
29210
+ });
29211
+ }
29212
+ // NOTE(mmalavalli): Cache the supported audio and video codecs here.
29213
+ var supportedCodecs = new Map();
29214
+ /**
29215
+ * Check whether a given codec for a certain kind of media is supported.
29216
+ * @param {AudioCodec|VideoCodec} codec
29217
+ * @param {'audio'|'video'} kind
29218
+ * @returns {Promise<boolean>}
29219
+ */
29220
+ function isCodecSupported(codec, kind) {
29221
+ var codecs = supportedCodecs.get(kind);
29222
+ if (codecs) {
29223
+ return Promise.resolve(codecs.has(codec.toLowerCase()));
29224
+ }
29225
+ return createSupportedCodecsSet(kind).then(function (codecs) {
29226
+ supportedCodecs.set(kind, codecs);
29227
+ return codecs.has(codec.toLowerCase());
29228
+ });
29229
+ }
29230
+ /**
29231
+ * Clear cached supported codecs (unit tests only).
29232
+ */
29233
+ function clearCachedSupportedCodecs() {
29234
+ supportedCodecs.clear();
29235
+ }
29096
29236
  /**
29097
29237
  * @typedef {object} Deferred
29098
29238
  * @property {Promise} promise
29099
29239
  * @property {function} reject
29100
29240
  * @property {function} resolve
29101
29241
  */
29242
+ exports.clearCachedSupportedCodecs = clearCachedSupportedCodecs;
29102
29243
  exports.defer = defer;
29103
29244
  exports.delegateMethods = delegateMethods;
29104
29245
  exports.difference = difference;
29105
29246
  exports.flatMap = flatMap;
29106
29247
  exports.guessBrowser = guessBrowser;
29107
29248
  exports.guessBrowserVersion = guessBrowserVersion;
29249
+ exports.isCodecSupported = isCodecSupported;
29108
29250
  exports.isIOSChrome = isIOSChrome;
29109
29251
  exports.interceptEvent = interceptEvent;
29110
29252
  exports.legacyPromise = legacyPromise;
@@ -30010,7 +30152,7 @@ module.exports={
30010
30152
  "name": "twilio-video",
30011
30153
  "title": "Twilio Video",
30012
30154
  "description": "Twilio Video JavaScript Library",
30013
- "version": "2.26.1",
30155
+ "version": "2.27.0",
30014
30156
  "homepage": "https://twilio.com",
30015
30157
  "author": "Mark Andrus Roberts <mroberts@twilio.com>",
30016
30158
  "contributors": [