twilio-video 2.35.0 → 2.36.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,41 @@ 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.36.0 (September 11, 2026)
6
+ ====================
7
+
8
+ New Features
9
+ ------------
10
+ - Added support for `@twilio/krisp-audio-plugin` 2.x. Version 1.x remains supported, so
11
+ existing applications continue to work without changes.
12
+
13
+ The 2.x plugin expects the raw microphone signal, so disable the browser's own audio
14
+ processing when using it:
15
+
16
+ ```js
17
+ const localAudioTrack = await createLocalAudioTrack({
18
+ noiseCancellationOptions: {
19
+ sdkAssetsPath: 'path/to/hosted/twilio/krisp/audio/plugin/{version}/dist',
20
+ vendor: 'krisp'
21
+ },
22
+ // Required for 2.x only — omit these for 1.x.
23
+ echoCancellation: false,
24
+ noiseSuppression: false,
25
+ autoGainControl: false
26
+ });
27
+ ```
28
+
29
+ Bug Fixes
30
+ ---------
31
+ - Fixed loading of the noise cancellation plugin when the SDK is bundled by tools that
32
+ rewrite dynamic imports. The module path is now passed as an argument to the import
33
+ function instead of being embedded in its body.
34
+
35
+ Changes
36
+ -------
37
+ - Updated `ws` to address a security advisory. This affects the Node.js build only;
38
+ browser builds do not include `ws`.
39
+
5
40
  2.35.0 (April 29, 2026)
6
41
  ====================
7
42
 
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  twilio-video.js
2
2
  ===============
3
3
 
4
- [![NPM](https://img.shields.io/npm/v/twilio-video.svg)](https://www.npmjs.com/package/twilio-video) [![CircleCI](https://dl.circleci.com/status-badge/img/gh/twilio/twilio-video.js/tree/master.svg?style=shield&circle-token=CCIPRJ_7qmZwk292pKYnNqeFyi5x8_a409cfa25489fb617be3c56119e8064cc77ffb80)](https://circleci.com/gh/twilio/twilio-video.js/tree/master)
4
+ [![NPM](https://img.shields.io/npm/v/twilio-video.svg)](https://www.npmjs.com/package/twilio-video) [![CircleCI](https://dl.circleci.com/status-badge/img/gh/twilio/twilio-video.js/tree/master.svg?style=shield)](https://circleci.com/gh/twilio/twilio-video.js/tree/master)
5
5
 
6
6
  twilio-video.js allows you to add real-time voice and video to your web apps.
7
7
 
@@ -73,7 +73,7 @@ Releases of twilio-video.js are hosted on a CDN, and you can include these
73
73
  directly in your web app using a <script> tag.
74
74
 
75
75
  ```html
76
- <script src="//sdk.twilio.com/js/video/releases/2.35.0/twilio-video.min.js"></script>
76
+ <script src="//sdk.twilio.com/js/video/releases/2.36.0/twilio-video.min.js"></script>
77
77
  ```
78
78
 
79
79
  Using this method, twilio-video.js will set a browser global:
@@ -1,4 +1,4 @@
1
- /*! twilio-video.js 2.35.0
1
+ /*! twilio-video.js 2.36.0
2
2
 
3
3
  The following license applies to all parts of this software except as
4
4
  documented below.
@@ -518,6 +518,141 @@ var __twilio_video = (function () {
518
518
 
519
519
  var noisecancellationadapter = {};
520
520
 
521
+ var legacyPluginAdapter = {};
522
+
523
+ /* globals webkitAudioContext, AudioContext */
524
+
525
+ var audiocontext;
526
+ var hasRequiredAudiocontext;
527
+
528
+ function requireAudiocontext () {
529
+ if (hasRequiredAudiocontext) return audiocontext;
530
+ hasRequiredAudiocontext = 1;
531
+ const NativeAudioContext = typeof AudioContext !== 'undefined'
532
+ ? AudioContext
533
+ : typeof webkitAudioContext !== 'undefined'
534
+ ? webkitAudioContext
535
+ : null;
536
+ /**
537
+ * @interface AudioContextFactoryOptions
538
+ * @property {AudioContext} [AudioContext] - The AudioContext constructor
539
+ */
540
+ /**
541
+ * {@link AudioContextFactory} ensures we construct at most one AudioContext
542
+ * at a time, and that it is eventually closed when we no longer need it.
543
+ * @property {AudioContextFactory} AudioContextFactory - The
544
+ * {@link AudioContextFactory} constructor
545
+ */
546
+ class AudioContextFactory {
547
+ /**
548
+ * @param {AudioContextFactoryOptions} [options]
549
+ */
550
+ constructor(options) {
551
+ options = Object.assign({
552
+ AudioContext: NativeAudioContext
553
+ }, options);
554
+ Object.defineProperties(this, {
555
+ _AudioContext: {
556
+ value: options.AudioContext
557
+ },
558
+ _audioContext: {
559
+ value: null,
560
+ writable: true
561
+ },
562
+ _holders: {
563
+ value: new Set()
564
+ },
565
+ AudioContextFactory: {
566
+ enumerable: true,
567
+ value: AudioContextFactory
568
+ }
569
+ });
570
+ }
571
+ /**
572
+ * Each call to {@link AudioContextFactory#getOrCreate} should be paired with a
573
+ * call to {@link AudioContextFactory#release}. Calling this increments an
574
+ * internal reference count.
575
+ * @param {*} holder - The object to hold a reference to the AudioContext
576
+ * @returns {?AudioContext}
577
+ */
578
+ getOrCreate(holder) {
579
+ if (!this._holders.has(holder)) {
580
+ this._holders.add(holder);
581
+ if (this._AudioContext && !this._audioContext) {
582
+ try {
583
+ this._audioContext = new this._AudioContext();
584
+ }
585
+ catch (error) {
586
+ // Do nothing;
587
+ }
588
+ }
589
+ }
590
+ return this._audioContext;
591
+ }
592
+ /**
593
+ * Decrement the internal reference count. If it reaches zero, close and destroy
594
+ * the AudioContext.
595
+ * @param {*} holder - The object that held a reference to the AudioContext
596
+ * @returns {void}
597
+ */
598
+ release(holder) {
599
+ if (this._holders.has(holder)) {
600
+ this._holders.delete(holder);
601
+ if (!this._holders.size && this._audioContext) {
602
+ this._audioContext.close();
603
+ this._audioContext = null;
604
+ }
605
+ }
606
+ }
607
+ }
608
+ audiocontext = new AudioContextFactory();
609
+
610
+ return audiocontext;
611
+ }
612
+
613
+ var hasRequiredLegacyPluginAdapter;
614
+
615
+ function requireLegacyPluginAdapter () {
616
+ if (hasRequiredLegacyPluginAdapter) return legacyPluginAdapter;
617
+ hasRequiredLegacyPluginAdapter = 1;
618
+ Object.defineProperty(legacyPluginAdapter, "__esModule", { value: true });
619
+ legacyPluginAdapter.LegacyPluginAdapter = void 0;
620
+ const AudioContextFactory = requireAudiocontext();
621
+ class LegacyPluginAdapter {
622
+ constructor(legacy) {
623
+ this.legacy = legacy;
624
+ }
625
+ init(options) { return this.legacy.init(options); }
626
+ isInitialized() { return this.legacy.isInitialized(); }
627
+ isConnected() { return this.legacy.isConnected(); }
628
+ isEnabled() { return this.legacy.isEnabled(); }
629
+ connect(input) { return this.legacy.connect(input); }
630
+ disconnect() { this.legacy.disconnect(); }
631
+ enable() { this.legacy.enable(); }
632
+ disable() { this.legacy.disable(); }
633
+ setLogging(enable) { this.legacy.setLogging(enable); }
634
+ getVersion() { return this.legacy.getVersion(); }
635
+ isSupported() {
636
+ const holder = {};
637
+ const ctx = AudioContextFactory.getOrCreate(holder);
638
+ try {
639
+ return !!ctx && this.legacy.isSupported(ctx);
640
+ }
641
+ finally {
642
+ AudioContextFactory.release(holder);
643
+ }
644
+ }
645
+ destroy() {
646
+ return Promise.resolve().then(() => {
647
+ this.legacy.destroy();
648
+ });
649
+ }
650
+ }
651
+ legacyPluginAdapter.LegacyPluginAdapter = LegacyPluginAdapter;
652
+
653
+ return legacyPluginAdapter;
654
+ }
655
+
521
656
  var dynamicimport;
522
657
  var hasRequiredDynamicimport;
523
658
 
@@ -534,14 +669,20 @@ var __twilio_video = (function () {
534
669
  scope.__twilioVideoImportedModules = {
535
670
  // Imported module map.
536
671
  };
672
+ // NOTE(mmalavalli): Calling import() directly can cause build issues in TypeScript and Webpack
673
+ // (and probably other frameworks). So, we create a Function that calls import() in its body.
674
+ // The path arrives only as the `url` argument, so it stays data and never joins the body source.
675
+ // eslint-disable-next-line no-new-func
676
+ const importModule = new Function('url', 'return import(url);');
537
677
  return function dynamicImport(module) {
538
678
  if (module in scope.__twilioVideoImportedModules) {
539
679
  return Promise.resolve(scope.__twilioVideoImportedModules[module]);
540
680
  }
541
- // NOTE(mmalavalli): Calling import() directly can cause build issues in TypeScript and Webpack
542
- // (and probably other frameworks). So, we create a Function that calls import() in its body.
543
- // eslint-disable-next-line no-new-func
544
- return new Function('scope', `return import('${new URL(module, location)}').then(m => scope.__twilioVideoImportedModules['${module}'] = m);`)(scope);
681
+ return importModule(new URL(module, location).toString())
682
+ .then(m => {
683
+ scope.__twilioVideoImportedModules[module] = m;
684
+ return m;
685
+ });
545
686
  };
546
687
  }(globalThis));
547
688
 
@@ -800,7 +941,7 @@ var __twilio_video = (function () {
800
941
  var constants = {exports: {}};
801
942
 
802
943
  var name = "twilio-video";
803
- var version = "2.35.0";
944
+ var version = "2.36.0";
804
945
  var require$$5 = {
805
946
  name: name,
806
947
  version: version};
@@ -1197,42 +1338,45 @@ var __twilio_video = (function () {
1197
1338
  Object.defineProperty(noisecancellationadapter, "__esModule", { value: true });
1198
1339
  noisecancellationadapter.createNoiseCancellationAudioProcessor = createNoiseCancellationAudioProcessor;
1199
1340
  const tslib_1 = require$$0;
1341
+ const legacy_plugin_adapter_1 = requireLegacyPluginAdapter();
1200
1342
  const dynamicImport = requireDynamicimport();
1201
1343
  requireLog();
1202
1344
  const PLUGIN_CONFIG = {
1203
1345
  krisp: {
1204
- supportedVersion: '1.0.0',
1346
+ supportedVersions: ['1.0.0', '2.0.0'],
1205
1347
  pluginFile: 'krispsdk.mjs'
1206
1348
  },
1207
1349
  rnnoise: {
1208
- supportedVersion: '0.6.0',
1350
+ supportedVersions: ['0.6.0'],
1209
1351
  pluginFile: 'rnnoise_sdk.mjs'
1210
1352
  }
1211
1353
  };
1212
- const ensureVersionSupported = ({ supportedVersion, plugin, log }) => {
1213
- if (!plugin.getVersion || !plugin.isSupported) {
1214
- throw new Error('Plugin does not export getVersion/isSupported api. Are you using old version of the plugin ?');
1215
- }
1216
- const pluginVersion = plugin.getVersion();
1217
- log.debug(`Plugin Version = ${pluginVersion}`);
1218
- const supportedVersions = supportedVersion.split('.').map(version => Number(version));
1219
- const pluginVersions = pluginVersion.split('.').map(version => Number(version));
1220
- if (supportedVersions.length !== 3 || pluginVersions.length !== 3) {
1221
- throw new Error(`Unsupported Plugin version format: ${supportedVersion}, ${pluginVersion}`);
1222
- }
1223
- if (supportedVersions[0] !== pluginVersions[0]) {
1224
- throw new Error(`Major version mismatch: [Plugin version ${pluginVersion}], [Supported Version ${supportedVersion}]`);
1225
- }
1226
- if (pluginVersions[1] < supportedVersions[1]) {
1227
- throw new Error(`Minor version mismatch: [Plugin version ${pluginVersion}] < [Supported Version ${supportedVersion}]`);
1354
+ const parseVersion = (version) => {
1355
+ // Strip semver pre-release suffix (e.g. "2.0.0-rc.1") before parsing.
1356
+ const core = version.split('-')[0];
1357
+ const parts = core.split('.').map(v => Number(v));
1358
+ if (parts.length !== 3 || parts.some(Number.isNaN)) {
1359
+ throw new Error(`Unsupported Plugin version format: ${version}`);
1360
+ }
1361
+ return parts;
1362
+ };
1363
+ const ensureVersionCompatible = ({ supportedVersions, pluginVersion }) => {
1364
+ const pluginParts = parseVersion(pluginVersion);
1365
+ const match = supportedVersions
1366
+ .map(parseVersion)
1367
+ .find(parts => parts[0] === pluginParts[0]);
1368
+ if (!match) {
1369
+ throw new Error(`Major version mismatch: [Plugin version ${pluginVersion}], [Supported Versions ${supportedVersions.join(', ')}]`);
1228
1370
  }
1229
- const tempContext = new AudioContext();
1230
- const isSupported = plugin.isSupported(tempContext);
1231
- tempContext.close();
1232
- if (!isSupported) {
1233
- throw new Error('Noise Cancellation plugin is not supported on your browser');
1371
+ if (pluginParts[1] < match[1]) {
1372
+ throw new Error(`Minor version mismatch: [Plugin version ${pluginVersion}] < [Supported Version ${match.join('.')}]`);
1234
1373
  }
1235
1374
  };
1375
+ const adaptPlugin = (maybeLegacyPlugin, major) => {
1376
+ return major === 2
1377
+ ? maybeLegacyPlugin
1378
+ : new legacy_plugin_adapter_1.LegacyPluginAdapter(maybeLegacyPlugin);
1379
+ };
1236
1380
  let audioProcessors = new Map();
1237
1381
  function createNoiseCancellationAudioProcessor(noiseCancellationOptions, log) {
1238
1382
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
@@ -1242,19 +1386,25 @@ var __twilio_video = (function () {
1242
1386
  if (!pluginConfig) {
1243
1387
  throw new Error(`Unsupported NoiseCancellationOptions.vendor: ${noiseCancellationOptions.vendor}`);
1244
1388
  }
1245
- const { supportedVersion, pluginFile } = pluginConfig;
1389
+ const { supportedVersions, pluginFile } = pluginConfig;
1246
1390
  const rootDir = noiseCancellationOptions.sdkAssetsPath;
1247
1391
  const sdkFilePath = `${rootDir}/${pluginFile}`;
1248
1392
  try {
1249
1393
  log.debug('loading noise cancellation sdk: ', sdkFilePath);
1250
1394
  const dynamicModule = yield dynamicImport(sdkFilePath);
1251
1395
  log.debug('Loaded noise cancellation sdk:', dynamicModule);
1252
- const plugin = dynamicModule.default;
1253
- ensureVersionSupported({
1254
- supportedVersion,
1255
- plugin,
1256
- log
1257
- });
1396
+ const maybeLegacyPlugin = dynamicModule.default;
1397
+ if (!maybeLegacyPlugin || typeof maybeLegacyPlugin.getVersion !== 'function') {
1398
+ throw new Error(`Invalid noise cancellation plugin module: ${sdkFilePath}`);
1399
+ }
1400
+ const pluginVersion = maybeLegacyPlugin.getVersion();
1401
+ log.debug('Plugin Version =', pluginVersion);
1402
+ ensureVersionCompatible({ supportedVersions, pluginVersion });
1403
+ const major = parseVersion(pluginVersion)[0];
1404
+ const plugin = adaptPlugin(maybeLegacyPlugin, major);
1405
+ if (!plugin.isSupported()) {
1406
+ throw new Error('Noise Cancellation plugin is not supported on your browser');
1407
+ }
1258
1408
  if (!plugin.isInitialized()) {
1259
1409
  log.debug('initializing noise cancellation sdk: ', rootDir);
1260
1410
  yield plugin.init({ rootDir });
@@ -1323,11 +1473,17 @@ var __twilio_video = (function () {
1323
1473
  * const { connect, createLocalAudioTrack } = require('twilio-video');
1324
1474
  *
1325
1475
  * // Create a LocalAudioTrack with Krisp noise cancellation enabled.
1476
+ * // Supported plugin versions: @twilio/krisp-audio-plugin 1.x or 2.x.
1326
1477
  * const localAudioTrack = await createLocalAudioTrack({
1327
1478
  * noiseCancellationOptions: {
1328
- * sdkAssetsPath: 'path/to/hosted/twilio/krisp/audio/plugin/1.0.0/dist',
1479
+ * sdkAssetsPath: 'path/to/hosted/twilio/krisp/audio/plugin/{version}/dist',
1329
1480
  * vendor: 'krisp'
1330
- * }
1481
+ * },
1482
+ * // Required for @twilio/krisp-audio-plugin 2.x only, so Krisp receives
1483
+ * // the raw microphone signal. Omit these for 1.x.
1484
+ * echoCancellation: false,
1485
+ * noiseSuppression: false,
1486
+ * autoGainControl: false
1331
1487
  * });
1332
1488
  *
1333
1489
  * if (!localAudioTrack.noiseCancellation) {
@@ -1425,7 +1581,7 @@ var __twilio_video = (function () {
1425
1581
  this._processor.disconnect();
1426
1582
  const track = yield reacquire();
1427
1583
  this._sourceTrack = track;
1428
- const processedTrack = yield this._processor.connect(track);
1584
+ const processedTrack = this._processor.connect(track);
1429
1585
  if (processorWasEnabled) {
1430
1586
  this._processor.enable();
1431
1587
  }
@@ -5887,96 +6043,6 @@ var __twilio_video = (function () {
5887
6043
  return detectsilence;
5888
6044
  }
5889
6045
 
5890
- /* globals webkitAudioContext, AudioContext */
5891
-
5892
- var audiocontext;
5893
- var hasRequiredAudiocontext;
5894
-
5895
- function requireAudiocontext () {
5896
- if (hasRequiredAudiocontext) return audiocontext;
5897
- hasRequiredAudiocontext = 1;
5898
- const NativeAudioContext = typeof AudioContext !== 'undefined'
5899
- ? AudioContext
5900
- : typeof webkitAudioContext !== 'undefined'
5901
- ? webkitAudioContext
5902
- : null;
5903
- /**
5904
- * @interface AudioContextFactoryOptions
5905
- * @property {AudioContext} [AudioContext] - The AudioContext constructor
5906
- */
5907
- /**
5908
- * {@link AudioContextFactory} ensures we construct at most one AudioContext
5909
- * at a time, and that it is eventually closed when we no longer need it.
5910
- * @property {AudioContextFactory} AudioContextFactory - The
5911
- * {@link AudioContextFactory} constructor
5912
- */
5913
- class AudioContextFactory {
5914
- /**
5915
- * @param {AudioContextFactoryOptions} [options]
5916
- */
5917
- constructor(options) {
5918
- options = Object.assign({
5919
- AudioContext: NativeAudioContext
5920
- }, options);
5921
- Object.defineProperties(this, {
5922
- _AudioContext: {
5923
- value: options.AudioContext
5924
- },
5925
- _audioContext: {
5926
- value: null,
5927
- writable: true
5928
- },
5929
- _holders: {
5930
- value: new Set()
5931
- },
5932
- AudioContextFactory: {
5933
- enumerable: true,
5934
- value: AudioContextFactory
5935
- }
5936
- });
5937
- }
5938
- /**
5939
- * Each call to {@link AudioContextFactory#getOrCreate} should be paired with a
5940
- * call to {@link AudioContextFactory#release}. Calling this increments an
5941
- * internal reference count.
5942
- * @param {*} holder - The object to hold a reference to the AudioContext
5943
- * @returns {?AudioContext}
5944
- */
5945
- getOrCreate(holder) {
5946
- if (!this._holders.has(holder)) {
5947
- this._holders.add(holder);
5948
- if (this._AudioContext && !this._audioContext) {
5949
- try {
5950
- this._audioContext = new this._AudioContext();
5951
- }
5952
- catch (error) {
5953
- // Do nothing;
5954
- }
5955
- }
5956
- }
5957
- return this._audioContext;
5958
- }
5959
- /**
5960
- * Decrement the internal reference count. If it reaches zero, close and destroy
5961
- * the AudioContext.
5962
- * @param {*} holder - The object that held a reference to the AudioContext
5963
- * @returns {void}
5964
- */
5965
- release(holder) {
5966
- if (this._holders.has(holder)) {
5967
- this._holders.delete(holder);
5968
- if (!this._holders.size && this._audioContext) {
5969
- this._audioContext.close();
5970
- this._audioContext = null;
5971
- }
5972
- }
5973
- }
5974
- }
5975
- audiocontext = new AudioContextFactory();
5976
-
5977
- return audiocontext;
5978
- }
5979
-
5980
6046
  var detectsilentaudio;
5981
6047
  var hasRequiredDetectsilentaudio;
5982
6048
 
@@ -29648,8 +29714,8 @@ MediaStreamTracks and calling getUserMedia again. This is retry \
29648
29714
  * @example
29649
29715
  * var Video = require('twilio-video');
29650
29716
  *
29651
- * // Request the LocalAudioTrack with a custom name
29652
- * // and krisp noise cancellation
29717
+ * // Request the LocalAudioTrack with a custom name and noise cancellation
29718
+ * // from @twilio/krisp-audio-plugin 1.x.
29653
29719
  * Video.createLocalAudioTrack({
29654
29720
  * name: 'microphone',
29655
29721
  * noiseCancellationOptions: {
@@ -29657,6 +29723,22 @@ MediaStreamTracks and calling getUserMedia again. This is retry \
29657
29723
  * sdkAssetsPath: '/twilio-krisp-audio-plugin/1.0.0/dist'
29658
29724
  * }
29659
29725
  * });
29726
+ * @example
29727
+ * var Video = require('twilio-video');
29728
+ *
29729
+ * // Request the LocalAudioTrack with noise cancellation from
29730
+ * // @twilio/krisp-audio-plugin 2.x, which expects the raw microphone signal,
29731
+ * // so the browser's own audio processing is disabled.
29732
+ * Video.createLocalAudioTrack({
29733
+ * name: 'microphone',
29734
+ * noiseCancellationOptions: {
29735
+ * vendor: 'krisp',
29736
+ * sdkAssetsPath: '/twilio-krisp-audio-plugin/2.0.0/dist'
29737
+ * },
29738
+ * echoCancellation: false,
29739
+ * noiseSuppression: false,
29740
+ * autoGainControl: false
29741
+ * });
29660
29742
  */
29661
29743
  function createLocalAudioTrack(options) {
29662
29744
  return createLocalTrack('audio', options);
@@ -29717,7 +29799,7 @@ MediaStreamTracks and calling getUserMedia again. This is retry \
29717
29799
  * LocalAudioTrack continues to capture from the same audio input device even after the default audio input device changes.
29718
29800
  * When the property is not specified, it defaults to "auto".
29719
29801
  * @property {NoiseCancellationOptions} [noiseCancellationOptions] - This optional property enables using 3rd party plugins
29720
- * for noise cancellation.
29802
+ * for noise cancellation. Supports @twilio/krisp-audio-plugin 1.x and 2.x.
29721
29803
  */
29722
29804
  /**
29723
29805
  * Create {@link LocalTrack} options. Apart from the properties listed here, you can