Picture-in-Picture
Picture-in-Picture is a mode to have the player float independently in a window so that the user is free to continue navigating while keeping their content playing.
PiP APIs for Web
There are currently two PiP APIs available.
- The video element PiP API which only works for the
video
element. - The document PiP API which can be applied to any element in the document.
Video Element PiP
This API has the advantage that is widely supported by all major browsers which makes it ideal delivering the same experience across browsers.
The major limitation of the video element PiP API is that is limited to the video element and thus no custom content can be added to the floating player window. This impacts mostly in 2 areas: custom controls
support and external subtitles/captions
support. The first one limits the experience delivered by the application as it can only show built-in player controls with a limited set of styling (CSS
) that can be used. The second could impact accessibility as many external text tracks require to be displayed independently with its own html tags, this causes that those custom tracks cannot be moved to the PiP window.
Video PiP API Usage
A minimal example to use PiP is as follow:
const video = document.getElementById('video');
const togglePictureInPicture = () => {
if (document.pictureInPictureElement) {
document.exitPictureInPicture();
} else if (document.pictureInPictureEnabled) {
video.requestPictureInPicture();
}
}
togglePictureInPicture();
Breaking down the example above we have the following:
video.requestPictureInPicture
makes a request to the browser to move the video element to a PiP mode.document.pictureInPictureEnabled
checks if the current browser supports the PiP API.document.exitPictureInPicture
makes a request to the browser end the PiP mode and return the video to its original position.document.pcitureInPictureElement
checks if there is a video element currently in PiP mode.
Document PiP API
The document PiP API is a more modern API that extends the video element PIP API and brings support to all elements in the document. This allows for greater customization and solves the limitations explained in the video PiP API.
The main downside of this API is that is currently only supported by chromium based browsers such a Google Chrome, Microsoft Edge, Brave, etc. Other mayor vendors like Safari or Firefox currently doesn't support this API which may make it unreliable depending of the target audience.
Other disadvantage is the added complexity. You need to consider move your elements between windows appropriately and handle styling manually which could be error prone.
Document PiP API Usage
A minimal example to use document PiP is as follow:
const video = document.getElementById('video');
const playerContainer = document.getElementById('container');
// Check if Document PiP is supported
if (!("documentPictureInPicture" in window)) {
return;
}
const togglePictureInPicture = async () => {
// Returns null if no pip window is currently open
if (!window.documentPictureInPicture.window) {
// Open a Picture-in-Picture window.
const pipWindow = await window.documentPictureInPicture.requestWindow({
width: video.clientWidth,
height: video.clientHeight + 50,
});
// Add pagehide listener to handle the case of the pip window being closed using the browser X button
pipWindow.addEventListener("pagehide", (event) => {
// Move video back to original position
playerContainer.append(video);
});
// Copy style sheets over from the initial document
// so that the player looks the same.
[...document.styleSheets].forEach((styleSheet) => {
try {
const cssRules = [...styleSheet.cssRules].map((rule) => rule.cssText).join('');
const style = document.createElement('style');
style.textContent = cssRules;
pipWindow.document.head.appendChild(style);
} catch (e) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.type = styleSheet.type;
link.media = styleSheet.media;
link.href = styleSheet.href;
pipWindow.document.head.appendChild(link);
}
})
// Move the player to the Picture-in-Picture window.
pipWindow.document.body.append(video);
} else {
// Move video back to original position
playerContainer.append(video);
// Close PiP Window
window.documentPictureInPicture.window.close();
}
}
// Add listener when a PiP window is created
documentPictureInPicture.addEventListener("enter", (event) => {
const pipWindow = event.window;
// ... Do something
});
Source: https://github.com/chrisdavidmills/dom-examples/blob/main/document-picture-in-picture/main.js
The example above shows how to toggle the PiP mode using the document
API. The key points are:
- API is now asynchronous.
- Check whether the browser supports the new document PiP API (you can use
"documentPictureInPicture" in window
). - Handle carefully moving the
video
or container between windows on PiP open or PiP close. - If you wish to preserve the same styling, you need to copy the stylesheets from the main window.
Picture-in-Picture using Shaka player
Shaka player uses a video element to display content which make it compatible with both PiP APIs.
The Shaka player library also provides a ui library for basic player controls which can be used to validate PiP as it implements the logic required to test this feature.
The following snippet of code shows a minimal example to test the feature.
// Import shaka from the ui version and the css stylesheet
import shaka from 'shaka-player/dist/shaka-player.ui.debug';
import 'shaka-player/dist/controls.css';
const loadVideo = async () => {
// Create a player
const video = document.getElementById('video');
const videoContainer = document.getElementById('video-container');
const player = new shaka.Player();
await player.attach(video);
// Create UI Overlay
const ui = new shaka.ui.Overlay(player, videoContainer, video);
// Configure PiP API type. Default: true
ui.configure({ preferDocumentPictureInPicture: false });
// Load content
await player.load(url);
}
loadVideo();
Results
- Shaka player works fine with both PiP APIs (video and document).
- DRM (widevine or playready) work fine in a PiP window on the tested browsers (Google Chrome and Microsoft Edge) using both PiP APIs. So DRM should not be a mayor concern to use the feature.
- PiP windows are display always on top of other browser windows. In Windows OS the window will be on top of any other program window.
- The
document
API require extra attention in frameworks like react that expect to remove the html elements. This is due to the elements could be moved to the PiP window which will make them unavailable for removal from the main window point of view. This was found while testing back navigation with the PiP window open.
PiP for Elevate Web
For Elevate Web it is decided to use the video
element Picture-in-Picture API. These are the mayor reasons in favor of it:
- More widely supported.
- Doesn't present issues with react rendering.
- Api usage requires less complexity.
Shaka ui controls implementation
The code for the shaka player ui controls is a great place to look for an example of how to implement the different PiP APIs. The code can be found here.