An Introduction to the Picture-in-Picture Web API

Avatar of Ayooluwa Isaiah
Ayooluwa Isaiah on (Updated on )

Picture-in-Picture made its first appearance on the web in the Safari browser with the release of macOS Sierra in 2016. It made it possible for a user to pop a video out into a small floating window that stays above all others, so that they can keep watching while doing other things. It’s an idea that came from TV, where, for example, you might want to keep watching your Popular Sporting Event even as you browse the guide or even other channels.

Not long after that, Android 8.0 was released which included picture-in-picture support via native APIs. Chrome for Android was able to play videos in picture-in-picture mode through this API even though its desktop counterpart was unable to do so.

This lead to the drafting of a standard Picture-in-Picture Web API which makes it possible for websites to initiate and control this behavior.

At the time of writing, only Chrome (version 70+) and Edge (version 76+) support this feature. Firefox, Safari, and Opera all use proprietary APIs for their implementations.

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

ChromeFirefoxIEEdgeSafari
70127No7913.1

Mobile / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
123124No14.0-14.4

How to use the Picture-In-Picture API

Let’s start by adding a video to a webpage.

<video controls src="video.mp4"></video>

In Chrome, there will already be a toggle for entering and exiting Picture-in-Picture mode.

Showing a video with the picture-in-picture option in the bottom right of the screen.

To test Firefox’s implementation, you’ll need to enable the media.videocontrols.picture-in-picture.enabled flag in about:config first, then right-click on the video to find the picture-in-picture option.

Showing the Firefox settings that enable picture-in-picture.
Enabling picture-in-picture in Firefox

While this works, in many cases, you want your video controls to be consistent across browsers and you might want control over which videos can be entered into picture-in-picture mode and which ones cannot.

We can replace the default method of entering picture-in-picture mode in the browser with our own method using the Picture-in-Picture Web API. For example, let’s add a button that, when clicked, enables it:

<button id="pipButton" class="hidden" disabled>Enter Picture-in-Picture mode</button>

Then select both the video and the button in JavaScript:

const video = document.getElementById('video');
const pipButton = document.getElementById('pipButton');

The button is hidden and disabled by default because we need to know if the Picture-in-Picture API is supported and enabled in the user’s browser before displaying the it. This is a form of progressive enhancement which helps avoid a broken experience in browsers that do not support the feature.

We can check that the API is supported and enable the button as shown below:

if ('pictureInPictureEnabled' in document) {
  pipButton.classList.remove('hidden')
  pipButton.disabled = false;
}

Entering picture-in-picture mode

Let’s say our JavaScript has determined that the browser has picture-in-picture support enabled. Let’s call requestPictureInPicture() on the video element when the button with #pipButton is clicked. This method returns a promise that places the video in a mini window on the bottom-right side of the screen by default when resolved, although it can be moved around by the user.

if ('pictureInPictureEnabled' in document) {
  pipButton.classList.remove('hidden')
  pipButton.disabled = false;

  pipButton.addEventListener('click', () => {
    video.requestPictureInPicture();
  });
}

We cannot leave the code above as is because requestPictureInPicture() returns a promise and it’s possible for the promise to reject if, for example, the video metadata is not yet loaded or if the disablePictureInPicture attribute is present on the video.

Let’s add a catch block to capture this potential error and let the user know what is going on:

pipButton.addEventListener('click', () => {
  video
    .requestPictureInPicture()
    .catch(error => {
      // Error handling
    });
});

Exiting picture-in-picture mode

The browser helpfully provides a close button on the picture-in-picture window, which enables the window to be close when clicked. However, we can also provide another way to exit picture-in-picture mode as well. For example, we can make clicking our #pipButton close any active picture-in-picture window.

pipButton.addEventListener('click', () => {
  if (document.pictureInPictureElement) {
    document
      .exitPictureInPicture()
      .catch(error => {
      // Error handling
    })
  } else {
    // Request Picture-in-Picture
  }
});

One other situation where you might want to close the picture-in-picture window is when the video enters full-screen mode. Chrome already does this automatically without having to write any code.

Picture-in-Picture events

The browser allows us to detect when a video enters or leaves picture-in-picture mode. Since there are many ways picture-in-picture mode can be entered or exited, it is better to rely on event detection to update media controls.

The events are enterpictureinpicture and leavepictureinpicture which, as their names imply, fire when a video enters or exits picture-in-picture mode, respectively.

In our example, we need to update the #pipButton label depending on whether the video is or is not currently in picture-in-picture mode.

Here’s the code that helps us achieve that:

video.addEventListener('enterpictureinpicture', () => {
  pipButton.textContent = 'Exit Picture-in-Picture mode';
});

video.addEventListener('leavepictureinpicture', () => {
  pipButton.textContent = 'Enter Picture-in-Picture mode';
});

Here’s a demo of that:

Customizing the picture-in-picture window

The browser shows a play/pause button in the picture-in-picture window by default except when the video is playing a MediaStream object (produced by a virtual video source such as a camera, video recording device, screen sharing service, or other hardware sources).

It’s also possible to add controls that go to the previous or next track straight from the picture-in-picture window:

navigator.mediaSession.setActionHandler('previoustrack', () => {
  // Go to previous track
});

navigator.mediaSession.setActionHandler('nexttrack', () => {
  // Go to next track
});

Displaying a webcam feed in a picture-in-picture window

Video meeting web applications could benefit from placing a webcam feed in picture-in-picture mode when a user switches back and forth between the app and other browser tabs or windows.

Here’s an example:

Disabling picture-in-picture on a video

If you do not want a video to pop out in a picture-in-picture window, you can add the disablePictureInPicture attribute to it, like this:

<video disablePictureInPicture controls src="video.mp4"></video>

Wrapping up

That’s pretty much all you need to about the Picture-in-Picture Web API at this time! Currently, the API only supports the <video> element but it is meant to be extensible to other elements as well.

Although browser support is spotty right now, you can still use it as a way to progressively enhance the video experience on your website.

Further reading