twilio-video 2.32.0 → 2.32.1
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 +9 -0
- package/README.md +1 -1
- package/dist/twilio-video.js +295 -18
- package/dist/twilio-video.min.js +20 -20
- package/es5/media/track/localvideotrack.js +127 -5
- package/es5/media/track/localvideotrack.js.map +1 -1
- package/es5/media/track/remotevideotrack.js +4 -2
- package/es5/media/track/remotevideotrack.js.map +1 -1
- package/es5/media/track/videotrack.js +104 -1
- package/es5/media/track/videotrack.js.map +1 -1
- package/es5/util/sdp/index.js +18 -3
- package/es5/util/sdp/index.js.map +1 -1
- package/es5/util/sdp/issue8329.js +37 -0
- package/es5/util/sdp/issue8329.js.map +1 -1
- package/lib/media/track/localvideotrack.js +143 -6
- package/lib/media/track/remotevideotrack.js +6 -2
- package/lib/media/track/videotrack.js +122 -1
- package/lib/util/sdp/index.js +17 -3
- package/lib/util/sdp/issue8329.js +43 -0
- package/package.json +4 -6
- package/src/xmlhttprequest.js +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@ 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.32.1 (August 1, 2025)
|
|
6
|
+
====================
|
|
7
|
+
|
|
8
|
+
Bug Fixes
|
|
9
|
+
---------
|
|
10
|
+
- Fixed an issue where video tracks would freeze when the video element was offscreen in a document Picture-in-Picture window.
|
|
11
|
+
- Fixed an issue where `LocalVideoTrack.restart()` would fail on some devices when a video processor was active.
|
|
12
|
+
- Fixed an issue where SDP munging would generate duplicated payload types, causing SDP negotiation failures with Chrome 137+.
|
|
13
|
+
|
|
5
14
|
2.32.0 (July 23, 2025)
|
|
6
15
|
====================
|
|
7
16
|
|
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 <script> tag.
|
|
75
75
|
|
|
76
76
|
```html
|
|
77
|
-
<script src="//sdk.twilio.com/js/video/releases/2.32.
|
|
77
|
+
<script src="//sdk.twilio.com/js/video/releases/2.32.1/twilio-video.min.js"></script>
|
|
78
78
|
```
|
|
79
79
|
|
|
80
80
|
Using this method, twilio-video.js will set a browser global:
|
package/dist/twilio-video.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! twilio-video.js 2.32.
|
|
1
|
+
/*! twilio-video.js 2.32.1
|
|
2
2
|
|
|
3
3
|
The following license applies to all parts of this software except as
|
|
4
4
|
documented below.
|
|
@@ -4338,6 +4338,10 @@ var LocalVideoTrack = /** @class */ (function (_super) {
|
|
|
4338
4338
|
_workaroundSilentLocalVideoCleanup: {
|
|
4339
4339
|
value: null,
|
|
4340
4340
|
writable: true
|
|
4341
|
+
},
|
|
4342
|
+
_pipIntersectionObserver: {
|
|
4343
|
+
value: null,
|
|
4344
|
+
writable: true
|
|
4341
4345
|
}
|
|
4342
4346
|
});
|
|
4343
4347
|
// NOTE(mmalavalli): In iOS Safari, we work around a bug where local video
|
|
@@ -4351,6 +4355,96 @@ var LocalVideoTrack = /** @class */ (function (_super) {
|
|
|
4351
4355
|
LocalVideoTrack.prototype.toString = function () {
|
|
4352
4356
|
return "[LocalVideoTrack #" + this._instanceId + ": " + this.id + "]";
|
|
4353
4357
|
};
|
|
4358
|
+
/**
|
|
4359
|
+
* Handle document PiP enter event - set up intersection observer for PiP-specific visibility tracking
|
|
4360
|
+
*
|
|
4361
|
+
* NOTE(lrivas): This is only created if we are in document PiP mode and should be considered deprecated
|
|
4362
|
+
* if Chrome's native PiP fixes the issue in the future.
|
|
4363
|
+
* @private
|
|
4364
|
+
*/
|
|
4365
|
+
LocalVideoTrack.prototype._onDocumentPipEnter = function () {
|
|
4366
|
+
this._log.debug('LocalVideoTrack: Setting up PiP intersection observer');
|
|
4367
|
+
this._setupPipIntersectionObserver();
|
|
4368
|
+
};
|
|
4369
|
+
/**
|
|
4370
|
+
* Handle document PiP exit event - clean up intersection observer
|
|
4371
|
+
* NOTE(lrivas): This is only created if we are in document PiP mode and should be considered deprecated
|
|
4372
|
+
* if Chrome's native PiP fixes the issue in the future.
|
|
4373
|
+
* @private
|
|
4374
|
+
*/
|
|
4375
|
+
LocalVideoTrack.prototype._onDocumentPipExit = function () {
|
|
4376
|
+
this._log.debug('LocalVideoTrack: Cleaning up PiP intersection observer');
|
|
4377
|
+
this._cleanupPipIntersectionObserver();
|
|
4378
|
+
};
|
|
4379
|
+
/**
|
|
4380
|
+
* Set up an IntersectionObserver to track visibility of elements attached in document PiP.
|
|
4381
|
+
* This ensures that videos in PiP play when visible.
|
|
4382
|
+
*
|
|
4383
|
+
* NOTE(lrivas): This is only created if we are in document PiP mode and should be considered deprecated
|
|
4384
|
+
* if Chrome's native PiP fixes the issue in the future.
|
|
4385
|
+
* @private
|
|
4386
|
+
*/
|
|
4387
|
+
LocalVideoTrack.prototype._setupPipIntersectionObserver = function () {
|
|
4388
|
+
var _this = this;
|
|
4389
|
+
if (!this._pipIntersectionObserver && this._documentPipWindow) {
|
|
4390
|
+
this._log.debug('LocalVideoTrack: Creating PiP intersection observer');
|
|
4391
|
+
this._pipIntersectionObserver = new IntersectionObserver(function (entries) {
|
|
4392
|
+
var hasVisibleElements = entries.some(function (entry) { return entry.isIntersecting; });
|
|
4393
|
+
if (hasVisibleElements) {
|
|
4394
|
+
_this._log.debug('LocalVideoTrack: Element visible in PiP, ensuring videos are playing');
|
|
4395
|
+
VideoTrack._ensureDocumentPipVideosPlaying(_this);
|
|
4396
|
+
}
|
|
4397
|
+
}, { threshold: 0.25 });
|
|
4398
|
+
this._attachments.forEach(function (el) {
|
|
4399
|
+
_this._log.debug('LocalVideoTrack: Observing element for PiP visibility');
|
|
4400
|
+
_this._pipIntersectionObserver.observe(el);
|
|
4401
|
+
});
|
|
4402
|
+
}
|
|
4403
|
+
};
|
|
4404
|
+
/**
|
|
4405
|
+
* Clean up PiP-specific intersection observer
|
|
4406
|
+
*
|
|
4407
|
+
* NOTE(lrivas): This is only created if we are in document PiP mode and should be considered deprecated
|
|
4408
|
+
* if Chrome's native PiP fixes the issue in the future.
|
|
4409
|
+
* @private
|
|
4410
|
+
*/
|
|
4411
|
+
LocalVideoTrack.prototype._cleanupPipIntersectionObserver = function () {
|
|
4412
|
+
if (this._pipIntersectionObserver) {
|
|
4413
|
+
this._log.debug('LocalVideoTrack: Disconnecting PiP intersection observer');
|
|
4414
|
+
this._pipIntersectionObserver.disconnect();
|
|
4415
|
+
this._pipIntersectionObserver = null;
|
|
4416
|
+
}
|
|
4417
|
+
};
|
|
4418
|
+
/**
|
|
4419
|
+
* Attach the {@link LocalVideoTrack} to an HTMLMediaElement.
|
|
4420
|
+
* @returns {HTMLMediaElement} mediaElement
|
|
4421
|
+
*/
|
|
4422
|
+
LocalVideoTrack.prototype.attach = function () {
|
|
4423
|
+
var result = _super.prototype.attach.apply(this, arguments);
|
|
4424
|
+
// If we're in document PiP, new elements should be observed for visibility
|
|
4425
|
+
if (this._pipIntersectionObserver) {
|
|
4426
|
+
this._log.debug('LocalVideoTrack: Observing newly attached element for PiP visibility');
|
|
4427
|
+
this._pipIntersectionObserver.observe(result);
|
|
4428
|
+
}
|
|
4429
|
+
return result;
|
|
4430
|
+
};
|
|
4431
|
+
/**
|
|
4432
|
+
* Detach the {@link LocalVideoTrack} from HTMLMediaElement(s).
|
|
4433
|
+
* @returns {HTMLMediaElement|Array<HTMLMediaElement>} mediaElement(s)
|
|
4434
|
+
*/
|
|
4435
|
+
LocalVideoTrack.prototype.detach = function () {
|
|
4436
|
+
var _this = this;
|
|
4437
|
+
var result = _super.prototype.detach.apply(this, arguments);
|
|
4438
|
+
var elements = Array.isArray(result) ? result : [result];
|
|
4439
|
+
// Stop observing detached elements if we're in document PiP
|
|
4440
|
+
if (this._pipIntersectionObserver) {
|
|
4441
|
+
elements.forEach(function (el) {
|
|
4442
|
+
_this._log.debug('LocalVideoTrack: Unobserving detached element from PiP visibility');
|
|
4443
|
+
_this._pipIntersectionObserver.unobserve(el);
|
|
4444
|
+
});
|
|
4445
|
+
}
|
|
4446
|
+
return result;
|
|
4447
|
+
};
|
|
4354
4448
|
/**
|
|
4355
4449
|
* @private
|
|
4356
4450
|
*/
|
|
@@ -4364,6 +4458,8 @@ var LocalVideoTrack = /** @class */ (function (_super) {
|
|
|
4364
4458
|
return _super.prototype._end.apply(this, arguments);
|
|
4365
4459
|
};
|
|
4366
4460
|
/**
|
|
4461
|
+
* @param {boolean} useProcessed - Whether to use the processed track or the unprocessed track.
|
|
4462
|
+
* @returns {Promise<void>}
|
|
4367
4463
|
* @private
|
|
4368
4464
|
*/
|
|
4369
4465
|
LocalVideoTrack.prototype._setSenderMediaStreamTrack = function (useProcessed) {
|
|
@@ -4487,12 +4583,18 @@ var LocalVideoTrack = /** @class */ (function (_super) {
|
|
|
4487
4583
|
* the MediaStreamTrack directly, please do so in the "started" event handler. Also,
|
|
4488
4584
|
* the {@link LocalVideoTrack}'s ID is no longer guaranteed to be the same as the
|
|
4489
4585
|
* underlying MediaStreamTrack's ID.
|
|
4586
|
+
*
|
|
4587
|
+
* If a {@link VideoProcessor} is applied to this track, it will be automatically
|
|
4588
|
+
* restarted after the new MediaStreamTrack is created to ensure proper processing
|
|
4589
|
+
* continues with the new video source.
|
|
4590
|
+
*
|
|
4490
4591
|
* @param {MediaTrackConstraints} [constraints] - The optional <a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints" target="_blank">MediaTrackConstraints</a>
|
|
4491
4592
|
* for restarting the {@link LocalVideoTrack}; If not specified, then the current MediaTrackConstraints
|
|
4492
4593
|
* will be used; If <code>{}</code> (empty object) is specified, then the default MediaTrackConstraints
|
|
4493
4594
|
* will be used
|
|
4494
|
-
* @returns {Promise<void>}
|
|
4495
|
-
*
|
|
4595
|
+
* @returns {Promise<void>} Resolves when the restart is complete, including any processor restart operations.
|
|
4596
|
+
* Rejects with a TypeError if the {@link LocalVideoTrack} was not created
|
|
4597
|
+
* using one of <code>createLocalVideoTrack</code>, <code>createLocalTracks</code> or <code>connect</code>;
|
|
4496
4598
|
* Also rejects with the <a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia#Exceptions" target="_blank">DOMException</a>
|
|
4497
4599
|
* raised by <code>getUserMedia</code> when it fails
|
|
4498
4600
|
* @fires LocalVideoTrack#stopped
|
|
@@ -4520,9 +4622,7 @@ var LocalVideoTrack = /** @class */ (function (_super) {
|
|
|
4520
4622
|
}
|
|
4521
4623
|
var promise = _super.prototype.restart.apply(this, arguments);
|
|
4522
4624
|
if (this.processor) {
|
|
4523
|
-
promise.then(function () {
|
|
4524
|
-
_this._restartProcessor();
|
|
4525
|
-
});
|
|
4625
|
+
promise = promise.then(function () { return _this._restartProcessor(); });
|
|
4526
4626
|
}
|
|
4527
4627
|
if (this._workaroundSilentLocalVideo) {
|
|
4528
4628
|
promise.finally(function () {
|
|
@@ -4531,6 +4631,26 @@ var LocalVideoTrack = /** @class */ (function (_super) {
|
|
|
4531
4631
|
}
|
|
4532
4632
|
return promise;
|
|
4533
4633
|
};
|
|
4634
|
+
/**
|
|
4635
|
+
* Restarts the video processor by removing and re-adding it with the same options,
|
|
4636
|
+
* then updates the sender to use the newly processed MediaStreamTrack.
|
|
4637
|
+
*
|
|
4638
|
+
* This operations are done synchronously to ensure the processor is restarted before the
|
|
4639
|
+
* sender is updated to avoid race conditions.
|
|
4640
|
+
*
|
|
4641
|
+
* @returns {Promise<void>}
|
|
4642
|
+
* @private
|
|
4643
|
+
*/
|
|
4644
|
+
LocalVideoTrack.prototype._restartProcessor = function () {
|
|
4645
|
+
var processor = this.processor;
|
|
4646
|
+
var processorOptions = Object.assign({}, this._processorOptions);
|
|
4647
|
+
// Local video track's removeProcessor and addProcessor call setSenderMediaStreamTrack async,
|
|
4648
|
+
// but we need to wait for that operation to finish for a successful restart.
|
|
4649
|
+
// So we call the parent's removeProcessor and addProcessor, then setSenderMediaStreamTrack right after.
|
|
4650
|
+
_super.prototype.removeProcessor.apply(this, [processor]);
|
|
4651
|
+
_super.prototype.addProcessor.apply(this, [processor, processorOptions]);
|
|
4652
|
+
return this._setSenderMediaStreamTrack(true);
|
|
4653
|
+
};
|
|
4534
4654
|
/**
|
|
4535
4655
|
* Calls stop on the underlying MediaStreamTrack. If you choose to stop a
|
|
4536
4656
|
* {@link LocalVideoTrack}, you should unpublish it after stopping.
|
|
@@ -4542,6 +4662,8 @@ var LocalVideoTrack = /** @class */ (function (_super) {
|
|
|
4542
4662
|
this._workaroundSilentLocalVideoCleanup();
|
|
4543
4663
|
this._workaroundSilentLocalVideoCleanup = null;
|
|
4544
4664
|
}
|
|
4665
|
+
// Clean up PiP intersection observer when stopping
|
|
4666
|
+
this._cleanupPipIntersectionObserver();
|
|
4545
4667
|
return _super.prototype.stop.apply(this, arguments);
|
|
4546
4668
|
};
|
|
4547
4669
|
return LocalVideoTrack;
|
|
@@ -6313,7 +6435,7 @@ var documentVisibilityMonitor = require('../../util/documentvisibilitymonitor.js
|
|
|
6313
6435
|
var NullObserver = require('../../util/nullobserver.js').NullObserver;
|
|
6314
6436
|
var Timeout = require('../../util/timeout');
|
|
6315
6437
|
var RemoteMediaVideoTrack = mixinRemoteMediaTrack(VideoTrack);
|
|
6316
|
-
var
|
|
6438
|
+
var TRACK_TURN_OFF_DELAY_MS = 50;
|
|
6317
6439
|
/**
|
|
6318
6440
|
* A {@link RemoteVideoTrack} represents a {@link VideoTrack} published to a
|
|
6319
6441
|
* {@link Room} by a {@link RemoteParticipant}.
|
|
@@ -6380,7 +6502,7 @@ var RemoteVideoTrack = /** @class */ (function (_super) {
|
|
|
6380
6502
|
_turnOffTimer: {
|
|
6381
6503
|
value: new Timeout(function () {
|
|
6382
6504
|
_this._setRenderHint({ enabled: false });
|
|
6383
|
-
},
|
|
6505
|
+
}, TRACK_TURN_OFF_DELAY_MS, false),
|
|
6384
6506
|
},
|
|
6385
6507
|
_resizeObserver: {
|
|
6386
6508
|
value: new options.ResizeObserver(function (entries) {
|
|
@@ -6646,6 +6768,8 @@ function maybeUpdateEnabledHint(remoteVideoTrack) {
|
|
|
6646
6768
|
if (enabled === true) {
|
|
6647
6769
|
remoteVideoTrack._turnOffTimer.clear();
|
|
6648
6770
|
remoteVideoTrack._setRenderHint({ enabled: true });
|
|
6771
|
+
// Check if videos in document PiP are actually playing
|
|
6772
|
+
VideoTrack._ensureDocumentPipVideosPlaying(remoteVideoTrack);
|
|
6649
6773
|
}
|
|
6650
6774
|
else if (!remoteVideoTrack._turnOffTimer.isSet) {
|
|
6651
6775
|
// set the track to be turned off after some delay.
|
|
@@ -7398,6 +7522,18 @@ var VideoTrack = /** @class */ (function (_super) {
|
|
|
7398
7522
|
enumerable: true,
|
|
7399
7523
|
value: null,
|
|
7400
7524
|
writable: true
|
|
7525
|
+
},
|
|
7526
|
+
_documentPipWindow: {
|
|
7527
|
+
value: null,
|
|
7528
|
+
writable: true
|
|
7529
|
+
},
|
|
7530
|
+
_documentPipEnterListener: {
|
|
7531
|
+
value: null,
|
|
7532
|
+
writable: true
|
|
7533
|
+
},
|
|
7534
|
+
_documentPipExitListener: {
|
|
7535
|
+
value: null,
|
|
7536
|
+
writable: true
|
|
7401
7537
|
}
|
|
7402
7538
|
});
|
|
7403
7539
|
_this._processorEventObserver = new (options.VideoProcessorEventObserver || VideoProcessorEventObserver)(_this._log);
|
|
@@ -7720,6 +7856,10 @@ var VideoTrack = /** @class */ (function (_super) {
|
|
|
7720
7856
|
*/
|
|
7721
7857
|
VideoTrack.prototype.attach = function () {
|
|
7722
7858
|
var result = _super.prototype.attach.apply(this, arguments);
|
|
7859
|
+
// Set up document PiP listener on first attach
|
|
7860
|
+
if (this._attachments.size === 1) {
|
|
7861
|
+
setupDocumentPipListener(this);
|
|
7862
|
+
}
|
|
7723
7863
|
if (this.processor) {
|
|
7724
7864
|
this._captureFrames();
|
|
7725
7865
|
}
|
|
@@ -7749,7 +7889,12 @@ var VideoTrack = /** @class */ (function (_super) {
|
|
|
7749
7889
|
* videoTrack.detach('#my-video-element').remove();
|
|
7750
7890
|
*/
|
|
7751
7891
|
VideoTrack.prototype.detach = function () {
|
|
7752
|
-
|
|
7892
|
+
var result = _super.prototype.detach.apply(this, arguments);
|
|
7893
|
+
// Clean up document PiP listener when no attachments remain
|
|
7894
|
+
if (this._attachments.size === 0) {
|
|
7895
|
+
cleanupDocumentPipListener(this);
|
|
7896
|
+
}
|
|
7897
|
+
return result;
|
|
7753
7898
|
};
|
|
7754
7899
|
/**
|
|
7755
7900
|
* Remove the previously added {@link VideoProcessor} using `addProcessor` API.
|
|
@@ -7805,6 +7950,87 @@ function dimensionsChanged(track, elem) {
|
|
|
7805
7950
|
return track.dimensions.width !== elem.videoWidth
|
|
7806
7951
|
|| track.dimensions.height !== elem.videoHeight;
|
|
7807
7952
|
}
|
|
7953
|
+
/**
|
|
7954
|
+
* Set up document PiP listener for a VideoTrack.
|
|
7955
|
+
*
|
|
7956
|
+
* The received VideoTrack can define `_onDocumentPipEnter` and `_onDocumentPipExit`
|
|
7957
|
+
* to handle PiP-specific setup and cleanup.
|
|
7958
|
+
* @param {VideoTrack} videoTrack - The VideoTrack to set up the listener for
|
|
7959
|
+
* @private
|
|
7960
|
+
*/
|
|
7961
|
+
function setupDocumentPipListener(videoTrack) {
|
|
7962
|
+
if (!videoTrack._documentPipEnterListener && 'documentPictureInPicture' in globalThis &&
|
|
7963
|
+
globalThis.documentPictureInPicture &&
|
|
7964
|
+
typeof globalThis.documentPictureInPicture.addEventListener === 'function') {
|
|
7965
|
+
videoTrack._documentPipEnterListener = function (event) {
|
|
7966
|
+
videoTrack._log.debug('document pip entered');
|
|
7967
|
+
videoTrack._documentPipWindow = event.window;
|
|
7968
|
+
// Set up exit listener on the PiP window
|
|
7969
|
+
videoTrack._documentPipExitListener = function () {
|
|
7970
|
+
videoTrack._log.debug('document pip exited');
|
|
7971
|
+
videoTrack._documentPipWindow = null;
|
|
7972
|
+
if (typeof videoTrack._onDocumentPipExit === 'function') {
|
|
7973
|
+
videoTrack._onDocumentPipExit();
|
|
7974
|
+
}
|
|
7975
|
+
};
|
|
7976
|
+
// Listen for window close on the PiP window
|
|
7977
|
+
if (event.window && typeof event.window.addEventListener === 'function') {
|
|
7978
|
+
event.window.addEventListener('pagehide', videoTrack._documentPipExitListener);
|
|
7979
|
+
}
|
|
7980
|
+
// Immediate fix for any paused videos
|
|
7981
|
+
ensureDocumentPipVideosPlaying(videoTrack);
|
|
7982
|
+
if (typeof videoTrack._onDocumentPipEnter === 'function') {
|
|
7983
|
+
videoTrack._onDocumentPipEnter(event);
|
|
7984
|
+
}
|
|
7985
|
+
};
|
|
7986
|
+
globalThis.documentPictureInPicture.addEventListener('enter', videoTrack._documentPipEnterListener);
|
|
7987
|
+
}
|
|
7988
|
+
}
|
|
7989
|
+
/**
|
|
7990
|
+
* Clean up document PiP listener for VideoTrack
|
|
7991
|
+
* @private
|
|
7992
|
+
*/
|
|
7993
|
+
function cleanupDocumentPipListener(videoTrack) {
|
|
7994
|
+
if (videoTrack._documentPipEnterListener) {
|
|
7995
|
+
if (globalThis.documentPictureInPicture && typeof globalThis.documentPictureInPicture.removeEventListener === 'function') {
|
|
7996
|
+
globalThis.documentPictureInPicture.removeEventListener('enter', videoTrack._documentPipEnterListener);
|
|
7997
|
+
}
|
|
7998
|
+
// Clean up exit listener from PiP window if it exists
|
|
7999
|
+
if (videoTrack._documentPipWindow && videoTrack._documentPipExitListener &&
|
|
8000
|
+
typeof videoTrack._documentPipWindow.removeEventListener === 'function') {
|
|
8001
|
+
videoTrack._documentPipWindow.removeEventListener('pagehide', videoTrack._documentPipExitListener);
|
|
8002
|
+
}
|
|
8003
|
+
videoTrack._documentPipEnterListener = null;
|
|
8004
|
+
videoTrack._documentPipExitListener = null;
|
|
8005
|
+
videoTrack._documentPipWindow = null;
|
|
8006
|
+
}
|
|
8007
|
+
}
|
|
8008
|
+
/**
|
|
8009
|
+
* This is a workaround to ensure that videos rendered in a document PIP continue playing after being enabled
|
|
8010
|
+
* by the Intersection Observer. It appears to be a bug in Chrome, and we should revisit this issue once the
|
|
8011
|
+
* Document Picture-in-Picture feature is more reliably supported.
|
|
8012
|
+
* @private
|
|
8013
|
+
*/
|
|
8014
|
+
function ensureDocumentPipVideosPlaying(videoTrack) {
|
|
8015
|
+
if (!videoTrack._documentPipWindow) {
|
|
8016
|
+
return;
|
|
8017
|
+
}
|
|
8018
|
+
try {
|
|
8019
|
+
var pipEls_1 = new WeakSet(videoTrack._documentPipWindow.document.querySelectorAll('video'));
|
|
8020
|
+
videoTrack._attachments.forEach(function (el) {
|
|
8021
|
+
if (pipEls_1.has(el) && el.paused) {
|
|
8022
|
+
el.play().then(function () {
|
|
8023
|
+
videoTrack._log.debug('Successfully played inadvertently paused video element in document PiP window');
|
|
8024
|
+
}).catch(function (error) {
|
|
8025
|
+
videoTrack._log.debug('Failed to play inadvertently paused video element in document PiP window', error);
|
|
8026
|
+
});
|
|
8027
|
+
}
|
|
8028
|
+
});
|
|
8029
|
+
}
|
|
8030
|
+
catch (error) {
|
|
8031
|
+
videoTrack._log.debug('Error checking document PiP video playback:', error);
|
|
8032
|
+
}
|
|
8033
|
+
}
|
|
7808
8034
|
/**
|
|
7809
8035
|
* A {@link VideoTrack}'s width and height.
|
|
7810
8036
|
* @typedef {object} VideoTrack.Dimensions
|
|
@@ -7892,6 +8118,7 @@ function dimensionsChanged(track, elem) {
|
|
|
7892
8118
|
* @param {VideoTrack} track - The {@link VideoTrack} that started
|
|
7893
8119
|
* @event VideoTrack#started
|
|
7894
8120
|
*/
|
|
8121
|
+
VideoTrack._ensureDocumentPipVideosPlaying = ensureDocumentPipVideosPlaying;
|
|
7895
8122
|
module.exports = VideoTrack;
|
|
7896
8123
|
|
|
7897
8124
|
},{"../../util/constants":127,"../../webrtc/util":172,"./capturevideoframes":15,"./mediatrack":29,"./videoprocessoreventobserver":43}],45:[function(require,module,exports){
|
|
@@ -23995,6 +24222,11 @@ var __read = (this && this.__read) || function (o, n) {
|
|
|
23995
24222
|
}
|
|
23996
24223
|
return ar;
|
|
23997
24224
|
};
|
|
24225
|
+
var __spreadArray = (this && this.__spreadArray) || function (to, from) {
|
|
24226
|
+
for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
|
|
24227
|
+
to[j] = from[i];
|
|
24228
|
+
return to;
|
|
24229
|
+
};
|
|
23998
24230
|
var _a = require('../'), difference = _a.difference, flatMap = _a.flatMap;
|
|
23999
24231
|
var setSimulcastInMediaSection = require('./simulcast');
|
|
24000
24232
|
var ptToFixedBitrateAudioCodecName = {
|
|
@@ -24040,6 +24272,11 @@ function createMidToMediaSectionMap(sdp) {
|
|
|
24040
24272
|
*/
|
|
24041
24273
|
function createPtToCodecName(mediaSection) {
|
|
24042
24274
|
return getPayloadTypesInMediaSection(mediaSection).reduce(function (ptToCodecName, pt) {
|
|
24275
|
+
// NOTE(lrivas): Ignore repeated PTs to prevent RFC non‑compliant SDP generation.
|
|
24276
|
+
// See, https://github.com/twilio/twilio-video.js/issues/2122.
|
|
24277
|
+
if (ptToCodecName.has(pt)) {
|
|
24278
|
+
return ptToCodecName;
|
|
24279
|
+
}
|
|
24043
24280
|
var rtpmapPattern = new RegExp("a=rtpmap:" + pt + " ([^/]+)");
|
|
24044
24281
|
var matches = mediaSection.match(rtpmapPattern);
|
|
24045
24282
|
var codecName = matches
|
|
@@ -24261,10 +24498,13 @@ function filterCodecsInMediaSection(section, peerMidsToMediaSections, codecsToRe
|
|
|
24261
24498
|
// Payload Type if present.
|
|
24262
24499
|
var rtxPts = codecsToPts.get('rtx') || [];
|
|
24263
24500
|
// In "a=fmtp:<rtxPt> apt=<apt>", extract the codec PT <apt> associated with rtxPt.
|
|
24264
|
-
|
|
24501
|
+
var additionalRtxPts = rtxPts.filter(function (rtxPt) {
|
|
24265
24502
|
var fmtpAttrs = getFmtpAttributesForPt(rtxPt, section);
|
|
24266
24503
|
return fmtpAttrs && pts.includes(fmtpAttrs.apt);
|
|
24267
|
-
})
|
|
24504
|
+
});
|
|
24505
|
+
// NOTE(lrivas): Retain rtx Payload Types that reference a codec already in `pts`.
|
|
24506
|
+
// Using an intermediate Set to prevent duplicates from being reintroduced after concatenation.
|
|
24507
|
+
pts = Array.from(new Set(__spreadArray(__spreadArray([], __read(pts)), __read(additionalRtxPts))));
|
|
24268
24508
|
// Filter out the below mentioned attribute lines in the m= section that do not
|
|
24269
24509
|
// belong to one of the Payload Types that are to be retained.
|
|
24270
24510
|
// 1. "a=rtpmap:<pt> <codec>"
|
|
@@ -24277,7 +24517,9 @@ function filterCodecsInMediaSection(section, peerMidsToMediaSections, codecsToRe
|
|
|
24277
24517
|
});
|
|
24278
24518
|
// Filter the list of Payload Types in the first line of the m= section.
|
|
24279
24519
|
var orderedPts = getPayloadTypesInMediaSection(section).filter(function (pt) { return pts.includes(pt); });
|
|
24280
|
-
|
|
24520
|
+
// Ensure only unique Payload Types are retained.
|
|
24521
|
+
var uniquePayloadTypes = Array.from(new Set(orderedPts));
|
|
24522
|
+
return setPayloadTypesInMediaSection(uniquePayloadTypes, lines.join('\r\n'));
|
|
24281
24523
|
}
|
|
24282
24524
|
/**
|
|
24283
24525
|
* Filter local codecs based on the remote SDP.
|
|
@@ -24561,6 +24803,7 @@ function sdpWorkaround(sdp) {
|
|
|
24561
24803
|
function mediaSectionWorkaround(mediaSection) {
|
|
24562
24804
|
var ptToCodecName = createPtToCodecName(mediaSection);
|
|
24563
24805
|
mediaSection = deleteDuplicateRtxPts(mediaSection, ptToCodecName);
|
|
24806
|
+
mediaSection = resolvePayloadTypeConflicts(mediaSection);
|
|
24564
24807
|
var codecNameToPts = createCodecNameToPts(ptToCodecName);
|
|
24565
24808
|
var rtxPts = codecNameToPts.get('rtx') || new Set();
|
|
24566
24809
|
var invalidRtxPts = new Set();
|
|
@@ -24588,6 +24831,42 @@ function mediaSectionWorkaround(mediaSection) {
|
|
|
24588
24831
|
});
|
|
24589
24832
|
return mediaSection;
|
|
24590
24833
|
}
|
|
24834
|
+
/**
|
|
24835
|
+
* Resolve payload type conflicts where same PT maps to different codecs
|
|
24836
|
+
* @param {string} mediaSection
|
|
24837
|
+
* @returns {string} resolvedMediaSection
|
|
24838
|
+
*/
|
|
24839
|
+
function resolvePayloadTypeConflicts(mediaSection) {
|
|
24840
|
+
var lines = mediaSection.split('\r\n');
|
|
24841
|
+
var seenPts = new Map(); // PT -> first codec seen
|
|
24842
|
+
var nextAvailablePt = 96; // Start from 96, which is the first dynamic PT. See https://datatracker.ietf.org/doc/html/rfc3551#section-3
|
|
24843
|
+
for (var i = 0; i < lines.length; i++) {
|
|
24844
|
+
var rtpmapMatch = lines[i].match(/^a=rtpmap:(\d+) ([^/]+)/);
|
|
24845
|
+
if (rtpmapMatch) {
|
|
24846
|
+
var pt = parseInt(rtpmapMatch[1], 10);
|
|
24847
|
+
var codec = rtpmapMatch[2].toLowerCase();
|
|
24848
|
+
if (seenPts.has(pt) && seenPts.get(pt) !== codec) {
|
|
24849
|
+
// Conflict detected - reassign this PT
|
|
24850
|
+
while (seenPts.has(nextAvailablePt) || nextAvailablePt === pt) {
|
|
24851
|
+
nextAvailablePt++;
|
|
24852
|
+
}
|
|
24853
|
+
var newPt = nextAvailablePt;
|
|
24854
|
+
seenPts.set(newPt, codec);
|
|
24855
|
+
lines[i] = lines[i].replace("a=rtpmap:" + pt, "a=rtpmap:" + newPt);
|
|
24856
|
+
for (var j = i + 1; j < lines.length; j++) {
|
|
24857
|
+
// eslint-disable-next-line max-depth
|
|
24858
|
+
if (lines[j].match(new RegExp("^a=(fmtp|rtcp-fb):" + pt + "\\b"))) {
|
|
24859
|
+
lines[j] = lines[j].replace(new RegExp("^a=(fmtp|rtcp-fb):" + pt + "\\b"), "a=$1:" + newPt);
|
|
24860
|
+
}
|
|
24861
|
+
}
|
|
24862
|
+
}
|
|
24863
|
+
else {
|
|
24864
|
+
seenPts.set(pt, codec);
|
|
24865
|
+
}
|
|
24866
|
+
}
|
|
24867
|
+
}
|
|
24868
|
+
return lines.join('\r\n');
|
|
24869
|
+
}
|
|
24591
24870
|
/**
|
|
24592
24871
|
* @param {string} mediaSection
|
|
24593
24872
|
* @param {Map<PT, Codec>} ptToCodecName
|
|
@@ -30942,7 +31221,7 @@ module.exports={
|
|
|
30942
31221
|
"name": "twilio-video",
|
|
30943
31222
|
"title": "Twilio Video",
|
|
30944
31223
|
"description": "Twilio Video JavaScript Library",
|
|
30945
|
-
"version": "2.32.
|
|
31224
|
+
"version": "2.32.1",
|
|
30946
31225
|
"homepage": "https://twilio.com",
|
|
30947
31226
|
"author": "Mark Andrus Roberts <mroberts@twilio.com>",
|
|
30948
31227
|
"contributors": [
|
|
@@ -31032,7 +31311,7 @@ module.exports={
|
|
|
31032
31311
|
"webrtc-adapter": "^7.7.1"
|
|
31033
31312
|
},
|
|
31034
31313
|
"engines": {
|
|
31035
|
-
"node": ">=
|
|
31314
|
+
"node": ">=22"
|
|
31036
31315
|
},
|
|
31037
31316
|
"license": "BSD-3-Clause",
|
|
31038
31317
|
"main": "./es5/index.js",
|
|
@@ -31092,12 +31371,10 @@ module.exports={
|
|
|
31092
31371
|
"dependencies": {
|
|
31093
31372
|
"events": "^3.3.0",
|
|
31094
31373
|
"util": "^0.12.4",
|
|
31095
|
-
"ws": "^7.4.6"
|
|
31096
|
-
"xmlhttprequest": "^1.8.0"
|
|
31374
|
+
"ws": "^7.4.6"
|
|
31097
31375
|
},
|
|
31098
31376
|
"browser": {
|
|
31099
|
-
"ws": "./src/ws.js"
|
|
31100
|
-
"xmlhttprequest": "./src/xmlhttprequest.js"
|
|
31377
|
+
"ws": "./src/ws.js"
|
|
31101
31378
|
}
|
|
31102
31379
|
}
|
|
31103
31380
|
|