Picture-in-Picture for any Element, not just <video>

Published on Updated on

The Document Picture-in-Picture API makes it possible to open an always-on-top window that can be populated with arbitrary HTML content. It extends the existing Picture-in-Picture API for <video> that only allows an HTML <video> element to be put into a Picture-in-Picture window.

The Picture-in-Picture window in the Document Picture-in-Picture API is similar to a blank same-origin window opened via window.open(), with some differences:

  • The Picture-in-Picture window floats on top of other windows.
  • The Picture-in-Picture window never outlives the opening window.
  • The Picture-in-Picture window cannot open additional windows.
  • The Picture-in-Picture window cannot be navigated.
  • The Picture-in-Picture window position cannot be set by the website.
A Picture-in-Picture window playing Sintel trailer video.
A Picture-in-Picture window created with the Document Picture-in-Picture API (demo).

Current status

StepStatus
1. Create explainerComplete
2. Create initial draft of specificationIn progress
3. Gather feedback & iterate on designIn progress
4. Origin trialStarted
5. LaunchNot started

Try out the API on desktop

During the trial phase you can test the API on desktop by one of two methods.

Local testing

To experiment with the Document Picture-in-Picture API locally, without an origin trial token, enable the chrome://flags/#document-picture-in-picture-api flag.

Register for the origin trial

Starting in Chrome 111, the Document Picture-in-Picture API is available as an origin trial. It is expected to end in Chrome 115 (September 8, 2023). Register here.

Use cases

Custom video player

A website can provide a Picture-in-Picture video experience with the existing Picture-in-Picture API for <video>, however it is very limited. The existing Picture-in-Picture window accepts few inputs, and has limited ability for styling them. With a full Document in Picture-in-Picture, the website can provide custom controls and inputs (for example, captions, playlists, time scrubber, liking and disliking videos) to improve the user's Picture-in-Picture video experience.

Video conferencing

It is common for users to leave the browser tab during a video conferencing session for various reasons (for example, presenting another tab to the call or multitasking) while still wishing to see the call, so it's a prime use case for Picture-in-Picture. Once again, the current experience a video conferencing website can provide via the Picture-in-Picture API for <video> is limited in style and input. With a full Document in Picture-in-Picture, the website can easily combine multiple video streams into a single PiP window without having to rely on canvas hacks and provide custom controls such as sending a message, muting another user, or raising a hand.

Productivity

Research has shown that users need more ways to be productive on the web. Document in Picture-in-Picture gives web apps the flexibility to accomplish more. Whether it's text editing, note-taking, task lists, messaging and chat, or design and development tools, web apps can now keep their content always accessible.

Interface

Properties

documentPictureInPicture.window
Returns the current Picture-in-Picture window if any. Otherwise, returns null.

Methods

documentPictureInPicture.requestWindow(options)

Returns a promise that resolves when a Picture-in-Picture window is opened. The promise rejects if it's called without a user gesture. The options dictionary contains the optional following members:

initialAspectRatio
Sets the initial aspect ratio of the Picture-in-Picture window.
width
Sets the initial width of the Picture-in-Picture window.
height
Sets the initial height of the Picture-in-Picture window.
copyStyleSheets
When true, the CSS style sheets of the originated window are copied and applied to the Picture-in-Picture window. This is a one-time copy. The default value is false.

Events

documentPictureInPicture.onenter
Fired on documentPictureInPicture when a Picture-in-Picture window is opened.

Examples

The following HTML sets up a custom video player and a button element to open the video player in a Picture-in-Picture window.

<div id="playerContainer">
<div id="player">
<video id="video"></video>
</div>
</div>
<button id="pipButton">Open Picture-in-Picture window</button>

Open a Picture-in-Picture window

The following JavaScript calls documentPictureInPicture.requestWindow() when the user clicks the button to open a blank Picture-in-Picture window. The returned promise resolves with a Picture-in-Picture window JavaScript object. The video player is moved to that window using append().

pipButton.addEventListener('click', async () => {
const player = document.querySelector("#player");

// Open a Picture-in-Picture window.
const pipWindow = await documentPictureInPicture.requestWindow();

// Move the player to the Picture-in-Picture window.
pipWindow.document.body.append(player);
});

Set the size of the Picture-in-Picture window

To set an aspect ratio, set the initialAspectRatio option of documentPictureInPicture.requestWindow() to the desired Picture-in-Picture window aspect ratio.

pipButton.addEventListener("click", async () => {
const player = document.querySelector("#player");

// Open a Picture-in-Picture window whose aspect ratio is
// the same as the player's.
const pipWindow = await documentPictureInPicture.requestWindow({
initialAspectRatio: player.clientWidth / player.clientHeight,
});

// Move the player to the Picture-in-Picture window.
pipWindow.document.body.append(player);
});

The width and height options can also be used to set the desired Picture-in-Picture window size. Chrome may clamp those options values if they are too large or too small to fit a user-friendly window size.

// Set player's width and height as the Picture-in-Picture window size.
const pipWindow = await documentPictureInPicture.requestWindow({
width: player.clientWidth,
height: player.clientHeight,
});

Copy style sheets to the Picture-in-Picture window

To copy the CSS style sheets from the originating window set the copyStyleSheets option of documentPictureInPicture.requestWindow() to true. Note that this is a one-time copy.

pipButton.addEventListener("click", async () => {
const player = document.querySelector("#player");

// Open a Picture-in-Picture window with style sheets copied over
// from the initial document so that the player looks the same.
const pipWindow = await documentPictureInPicture.requestWindow({
copyStyleSheets: true,
});

// Move the player to the Picture-in-Picture window.
pipWindow.document.body.append(player);
});

Handle when the Picture-in-Picture window closes

Listen to the window "unload" event to know when the Picture-in-Picture window gets closed (either because the website initiated it or the user manually closed it). The event handler is a good place to get the elements back out of the Picture-in-Picture window as shown below.

pipButton.addEventListener("click", async () => {
const player = document.querySelector("#player");

// Open a Picture-in-Picture window.
const pipWindow = await documentPictureInPicture.requestWindow();

// Move the player to the Picture-in-Picture window.
pipWindow.document.body.append(player);

// Move the player back when the Picture-in-Picture window closes.
pipWindow.addEventListener("unload", (event) => {
const playerContainer = document.querySelector("#playerContainer");
const pipPlayer = event.target.querySelector("#player");
playerContainer.append(pipPlayer);
});
});

Close the Picture-in-Picture window programmatically by using the close() method.

// Close the Picture-in-Picture window programmatically. 
// The "unload" event will fire normally.
pipWindow.close();

Listen to when the website enters Picture-in-Picture

Listen to the "enter" event on documentPictureInPicture to know when a Picture-in-Picture window is opened. The event contains a window object to access the Picture-in-Picture window.

documentPictureInPicture.addEventListener("enter", (event) => {
const pipWindow = event.window;
});

Access elements in the Picture-in-Picture window

Access elements in the Picture-in-Picture window either from the object returned by documentPictureInPicture.requestWindow(), or with documentPictureInPicture.window as shown below.

const pipWindow = documentPictureInPicture.window;
if (pipWindow) {
// Mute video playing in the Picture-in-Picture window.
const pipVideo = pipWindow.document.querySelector("#video");
pipVideo.muted = true;
}

Handle events from the Picture-in-Picture window

Create buttons and controls and respond to user's input events such as "click" as you would do normally in JavaScript.

// Add a "mute" button to the Picture-in-Picture window.
const pipMuteButton = pipWindow.document.createElement("button");
pipMuteButton.textContent = "Mute";
pipMuteButton.addEventListener("click", () => {
const pipVideo = pipWindow.document.querySelector("#video");
pipVideo.muted = true;
});
pipWindow.document.body.append(pipMuteButton);

Feature detection

To check if the Document Picture-in-Picture API is supported, use:

if ('documentPictureInPicture' in window) {
// The Document Picture-in-Picture API is supported.
}

Demos

VideoJS player

You can play with the Document Picture-in-Picture API VideoJS player demo. Be sure to check out the source code.

Pomodoro

Tomodoro, a pomodoro web app, is also taking advantage of the Document Picture-in-Picture API when available (see GitHub pull request).

Screenshot of Tomodoro, a pomodoro web app.
A Picture-in-Picture window in Tomodoro.

Feedback

Developer feedback is really important at this stage, so please file issues on GitHub with suggestions and questions.

Acknowledgements

Hero image by Jakob Owens.

Updated on Improve article

We serve cookies on this site to analyze traffic, remember your preferences, and optimize your experience.