Skip to content

Commit 191e6e6

Browse files
VBLOCKS-4212 documentPictureInPicture support (#2093)
* VBLOCKS-4212 documentPictureInPicture support * Fix build and address PR review * Adding tests * Adding changelog
1 parent 7c182b0 commit 191e6e6

File tree

3 files changed

+93
-8
lines changed

3 files changed

+93
-8
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@ The Twilio Programmable Video SDKs use [Semantic Versioning](http://www.semver.o
22

33
**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.
44

5+
2.31.0 (In Progress)
6+
====================
7+
8+
New Features
9+
------------
10+
11+
### Document Picture-in-Picture API Support
12+
13+
Previously, when `Client Track Switch Off Control` was set to `auto`, video tracks were automatically switched off—even if they were visible and rendered in a [Document Picture-in-Picture](https://developer.chrome.com/docs/web-platform/document-picture-in-picture) (PiP) window. This caused issues where users could see the video element, but the track itself was disabled. In this new SDK version, video tracks will remain active and continue to play when displayed in a PiP window.
14+
515
2.30.0 (March 28, 2025)
616
========================
717

lib/media/track/remotevideotrack.js

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -338,15 +338,19 @@ class RemoteVideoTrack extends RemoteMediaVideoTrack {
338338
}
339339
}
340340

341-
function setupDocumentVisibilityTurnOff(removeVideoTrack) {
342-
function onVisibilityChanged() {
343-
maybeUpdateEnabledHint(removeVideoTrack);
341+
function isAttachedToDocumentPip(remoteVideoTrack) {
342+
if (!('documentPictureInPicture' in globalThis)) {
343+
return false;
344344
}
345345

346-
documentVisibilityMonitor.onVisibilityChange(1, onVisibilityChanged);
347-
return () => {
348-
documentVisibilityMonitor.offVisibilityChange(1, onVisibilityChanged);
349-
};
346+
const pipWindow = globalThis.documentPictureInPicture.window;
347+
if (!pipWindow) {
348+
return false;
349+
}
350+
351+
const pipEls = new WeakSet(pipWindow.document.querySelectorAll('video'));
352+
353+
return remoteVideoTrack._getAllAttachedElements().some(el => pipEls.has(el));
350354
}
351355

352356
function maybeUpdateEnabledHint(remoteVideoTrack) {
@@ -358,7 +362,9 @@ function maybeUpdateEnabledHint(remoteVideoTrack) {
358362
const pipWindows = remoteVideoTrack._getAllAttachedElements().filter(el => remoteVideoTrack._elToPipWindows.has(el));
359363

360364
// even when document is invisible we may have track playing in pip window.
361-
const enabled = pipWindows.length > 0 || (document.visibilityState === 'visible' && visibleElements.length > 0);
365+
const enabled = pipWindows.length > 0 ||
366+
isAttachedToDocumentPip(remoteVideoTrack) ||
367+
(document.visibilityState === 'visible' && visibleElements.length > 0);
362368

363369
if (enabled === true) {
364370
remoteVideoTrack._turnOffTimer.clear();
@@ -388,6 +394,17 @@ function maybeUpdateDimensionHint(remoteVideoTrack) {
388394
}
389395
}
390396

397+
function setupDocumentVisibilityTurnOff(removeVideoTrack) {
398+
function onVisibilityChanged() {
399+
maybeUpdateEnabledHint(removeVideoTrack);
400+
}
401+
402+
documentVisibilityMonitor.onVisibilityChange(1, onVisibilityChanged);
403+
return () => {
404+
documentVisibilityMonitor.offVisibilityChange(1, onVisibilityChanged);
405+
};
406+
}
407+
391408
/**
392409
* @typedef {object} VideoContentPreferences
393410
* @property {VideoTrack.Dimensions} [renderDimensions] - Render Dimensions to request for the {@link RemoteVideoTrack}.

test/unit/spec/media/track/remotevideotrack.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,64 @@ describe('RemoteVideoTrack', () => {
414414
});
415415
});
416416
});
417+
418+
describe('Picture-in-Picture', () => {
419+
let el;
420+
let setRenderHintsSpy;
421+
let track;
422+
let originalDocumentPictureInPicture;
423+
let pipEls;
424+
425+
const dispatchVisibilityChangeEvent = async () => {
426+
document.visibilityState = 'visible';
427+
document.dispatchEvent('visibilitychange');
428+
await waitForSometime();
429+
};
430+
431+
beforeEach(() => {
432+
documentVisibilityMonitor.clear();
433+
originalDocumentPictureInPicture = global.documentPictureInPicture;
434+
global.documentPictureInPicture = { window: { document: { querySelectorAll: () => pipEls } } };
435+
global.document = global.document || new Document();
436+
setRenderHintsSpy = sinon.spy();
437+
el = document.createElement('video');
438+
track = makeTrack({ id: 'foo', sid: 'bar', setRenderHint: setRenderHintsSpy, options: { enableDocumentVisibilityTurnOff: true } });
439+
track.attach(el);
440+
pipEls = [el];
441+
});
442+
443+
afterEach(() => {
444+
global.documentPictureInPicture = originalDocumentPictureInPicture;
445+
if (global.document instanceof Document) {
446+
delete global.document;
447+
}
448+
});
449+
450+
it('should call _setRenderHint with enable = true', async () => {
451+
await dispatchVisibilityChangeEvent();
452+
sinon.assert.calledWith(setRenderHintsSpy, { enabled: true });
453+
});
454+
455+
describe('should not call _setRenderHint', () => {
456+
it('when documentPictureInPicture is not supported', async () => {
457+
delete global.documentPictureInPicture;
458+
await dispatchVisibilityChangeEvent();
459+
sinon.assert.notCalled(setRenderHintsSpy);
460+
});
461+
462+
it('when documentPictureInPicture window is not open', async () => {
463+
delete global.documentPictureInPicture.window;
464+
await dispatchVisibilityChangeEvent();
465+
sinon.assert.notCalled(setRenderHintsSpy);
466+
});
467+
468+
it('when els are not on the documentPictureInPicture window', async () => {
469+
pipEls = [];
470+
await dispatchVisibilityChangeEvent();
471+
sinon.assert.notCalled(setRenderHintsSpy);
472+
});
473+
});
474+
});
417475
});
418476

419477

0 commit comments

Comments
 (0)