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.