Using the Document Picture-in-Picture API
This guide provides a walkthrough of typical usage of the Document Picture-in-Picture API.
Sample HTML
The following HTML sets up a basic video player.
<div id="container">
<p class="in-pip-message">
Video player is currently in the separate Picture-in-Picture window.
</p>
<div id="player">
<video
src="assets/bigbuckbunny.mp4"
id="video"
controls
width="320"></video>
<div id="credits">
<a href="https://peach.blender.org/download/" target="_blank">
Video by Blender </a
>;
<a href="https://peach.blender.org/about/" target="_blank">
licensed CC-BY 3.0
</a>
</div>
<div id="controlbar">
<p class="no-picture-in-picture">
Document Picture-in-Picture API not available
</p>
<p></p>
</div>
</div>
</div>
Feature detection
To check if the Document Picture-in-Picture API is supported, you can test whether documentPictureInPicture is available on window:
if ("documentPictureInPicture" in window) {
document.querySelector(".no-picture-in-picture").remove();
const togglePipButton = document.createElement("button");
togglePipButton.textContent = "Toggle Picture-in-Picture";
togglePipButton.addEventListener("click", togglePictureInPicture, false);
document.getElementById("controlbar").appendChild(togglePipButton);
}
If it is available, we remove the "Document Picture-in-Picture API not available" message and instead add a <button> element to open the video player in a Document Picture-in-Picture window.
Open a Picture-in-Picture window
The following JavaScript calls window.documentPictureInPicture.requestWindow() to open a blank Picture-in-Picture window. The returned Promise fulfills with a Picture-in-Picture Window object. The video player is moved to that window using Element.append(), and we display the message informing the user that it has been moved.
The width and height options of requestWindow() set the Picture-in-Picture window to the desired size. Browsers may clamp the option values if they are too large or too small to fit a user-friendly window size.
async function togglePictureInPicture() {
if (!!window.documentPictureInPicture.window) {
const pipWindow = await documentPictureInPicture.requestWindow({
width: videoPlayer.clientWidth,
height: videoPlayer.clientHeight,
});
pipWindow.document.body.append(videoPlayer);
inPipMessage.style.display = "block";
}
}
Copy style sheets to the Picture-in-Picture window
To copy all CSS style sheets from the originating window, loop through all style sheets explicitly linked into or embedded in the document (via Document.styleSheets) and append them to the Picture-in-Picture window. Note that this is a one-time copy.
[...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);
}
});
Handle when the Picture-in-Picture window closes
The code for toggling the Picture-in-Picture window closed again when the button is pressed a second time looks like this:
inPipMessage.style.display = "none";
playerContainer.append(videoPlayer);
window.documentPictureInPicture.window.close();
Here we reverse the DOM changes — hiding the message and putting the video player back in the player container in the main app window. We also close the Picture-in-Picture window programmatically using the Window.close() method.
However, you also need to consider the case where the user closes the Picture-in-Picture window by pressing the browser supplied close (X) button on the window itself. You can handle this by detecting when the window closes using the pagehide event:
pipWindow.addEventListener("pagehide", (event) => {
inPipMessage.style.display = "none";
playerContainer.append(videoPlayer);
});
Listen to when the website enters Picture-in-Picture
Listen to the enter event on the DocumentPictureInPicture instance to know when a Picture-in-Picture window is opened.
In our demo, we use the enter event to add a mute toggle button to the Picture-in-Picture window:
documentPictureInPicture.addEventListener("enter", (event) => {
const pipWindow = event.window;
console.log("Video player has entered the pip window");
const pipMuteButton = pipWindow.document.createElement("button");
pipMuteButton.textContent = "Mute";
pipMuteButton.addEventListener("click", () => {
const pipVideo = pipWindow.document.querySelector("#video");
if (!pipVideo.muted) {
pipVideo.muted = true;
pipMuteButton.textContent = "Unmute";
} else {
pipVideo.muted = false;
pipMuteButton.textContent = "Mute";
}
});
pipWindow.document.body.append(pipMuteButton);
});
Access elements and handle events
You can access elements in the Picture-in-Picture window in several different ways:
const pipWindow = window.documentPictureInPicture.window;
if (pipWindow) {
const pipVideo = pipWindow.document.querySelector("#video");
pipVideo.muted = true;
}
Once you've got a reference to the Picture-in-Picture window instance, you can manipulate the DOM (for example creating buttons) and respond to user input events (such as click) as you would do normally in the regular browser window context.