Implement Picture-in-Picture on the Web

January 09, 2019  |   3 min read

One of my favourite things to do on the web is to implement and test out new and upcoming features. Today we are going to implement Picture-in-Picture.

What is Picture-in-Picture?

According to W3C Picture-in-Picture Spec-

The specification intends to provide APIs to allow websites to create a floating video window always on top of other windows so that users may continue consuming media while they interact with other content sites, or applications on their device.

While in the PiP(Picture-in-Picture) mode the video is contained in a separate mini window that is always on top of other windows. This window stays visible even when the user agent is not visible.


How to implement Picture-in-Picture?

HTML -

<video id="videoElement" controls="true" src="demo.mp4"></video>

<!-- button will be used to toggle the PiP mode -->
<button id="togglePipButton">Toggle Picture-in-Picture Mode!</button> 

JavaScript -

1. Call requestPictureInPicture() on click of togglePipButton button element.

requestPictureInPicture() returns a promise. When the promise resolves, the browser will shrink the video into a mini window that the user can move around and position over other windows.

let video = document.getElementById('videoElement');
let togglePipButton = document.getElementById('togglePipButton');

togglePipButton.addEventListener('click', async function (event) {
    togglePipButton.disabled = true; //disable toggle button while the event occurs
    try {
        // If there is no element in Picture-in-Picture yet, request for it
        if (video !== document.pictureInPictureElement) {
            await video.requestPictureInPicture();
        }
        // If Picture-in-Picture already exists, exit the mode
        else {
            await document.exitPictureInPicture();
        }

    } catch (error) {
        console.log(`Oh Horror! ${error}`);
    } finally {
        togglePipButton.disabled = false; //enable toggle button after the event
    }
});

2. Check for Picture-in-Picture event changes

let video = document.getElementById('videoElement');
video.addEventListener('enterpictureinpicture', function (event) {
    console.log('Entered PiP');
    pipWindow = event.pictureInPictureWindow;
    console.log(`Window size -  \n Width: ${pipWindow.width} \n Height: ${pipWindow.height}`); // get the width and height of PiP window
});

video.addEventListener('leavepictureinpicture', function (event) {
    console.log('Left PiP');
    togglePipButton.disabled = false;
});

We can even update video size based on Picture-in-Picture window size changes by adding a resize event handler. This will be helpful for serving the right quality of video based on the window the user is viewing it on.

3. Always check for feature support

if ('pictureInPictureEnabled' in document) {
    showPipButton();

    /* loadedmetadata event occurs when meta data for the specified 
    video has been loaded.Meta data might consists of: duration, dimensions etc. */
    video.addEventListener('loadedmetadata', showPipButton);

    /* emptied event is fired when the media has become empty, 
    e.g. media has already been loaded or partially loaded.*/
    video.addEventListener('emptied', showPipButton);
} else {
    console.log('The Picture-in-Picture Web API is not available.');
    togglePipButton.hidden = true;
}

// Enable/disable toggle button depending on whether PiP availability.
function showPipButton() {
    togglePipButton.disabled = (video.readyState === 0) || !document.pictureInPictureEnabled || video.disablePictureInPicture;
}
// video.readyState === 0 means Video metadata have not been loaded yet 
// !document.pictureInPictureEnabled means if Pip is not available
// video.disablePictureInPicture means disablePictureInPicture attribute is present in the video which will result in requestPictureInPicture() rejected promise.

That’s it! Your web app is now ready to use Picture-in-Picture!


Bonus! Made a quick demo for this, check it out!

Currently the API supports only on the HTMLVideoElement but it states to be extensible in the future. Check the caniuse stats for browser support. Since this is a progressive enhancement, so regardless of the support, you can use it today on your existing apps! Yay!


References

  1. W3C Picture-in-Picture Spec
  2. Building Modern Web Media Experiences(Chrome Dev Summit 2018)