Innovative Picture-in-Picture Techniques for the Modern Web

We live in a time where we all try and be as productive as possible. We watch videos at twice the speed, we have several (too many?) tabs open and always try and stay up to date with the latest tech. I mean, that is why you are currently reading this blog post, isn't it?

Today I want to introduce you to the Picture-in-Picture API. It's a fantastic - yet simple - API that allows users to play video content in a little window that is always visible in the corner of their screen.

Toggling Picture-in-Picture

There are two methods necessary to toggle Picture-in-Picture (PiP). requestPictureInPicture on the HTMLVideoElement (your video) and exitPictureInPicture on document.

const video = document.querySelector('video');

video.requestPictureInPicture()
	.then(/* [dont use] requested picture in picture */)
	.catch(/* couldn't enter picture in picture */);

document.exitPictureInPicture()
	.then(/* [dont use] requested to leave picture in picture */)
	.catch(/* couldn't exit picture in picture */);

While this would work (assuming you call these functions on a user interaction such as button click), you do not want to act on those resolving promises. The problem is that those are only triggered by the user clicking that button. If the user closes the Picture in Picture window themselves (as shown in the gif below), the exitPictureInPicture method is never called.

Closing Picture in Picture without using the button

To act on PiP events we have two new events to attach to a HTMLVideoElement. These events are enterpictureinpicture - fired when we actually enter PiP and leavepictureinpicture - fired when we actually leave PiP.

// ...
video.addEventListener('enterpictureinpicture', event => {
	const pipWindow = event.pictureInPictureWindow;
	// User entered Picture in Picture
});

video.addEventListener('leavepictureinpicture', () => {
	// User left Picture in Picture
});

Handling Picture-in-Picture resize

There is an additional event called resize attached to PictureInPictureWindow. The reference of that window is attached to the event argument of enterpictureinpicture as demonstrated above.

This is super useful for saving bandwidth! You can change the source / quality of your video once the user goes PiP. Why would you stream a 4k video to a small PiP window?

// ...
let pipWindow = {};

function onResize() {
	// This function should be throttled
	// It gets triggered often, much like the window.scroll event
	const { width, height } = pipWindow;
	const current = video.currentTime;

	video.src = getVideoSource(width, height);

	video.currentTime = current;
	video.play()
}

video.addEventListener('enterpictureinpicture', event => {
	pipWindow = event.pictureInPictureWindow;
	pipWindow.addEventListener('resize', onResize);
});

video.addEventListener('leavepictureinpicture', () => {
	pipWindow.removeEventListener('resize', onResize);
	pipWindow = {};
})

Picture-in-Picture Feature Detection

At the time of writing, Picture in Picture is supported in Chrome and Safari*. If the browser supports PiP, it will be exposed as pictureInPictureEnabled on the document. In order to trigger the PiP, we also need a valid video to be loaded in our video tag.

// Step one: does the browser support it?
if ('pictureInPictureEnabled' in document) {
	...
	// Step two: is the video ready?
	if (video.readyState !== 0) {
		...
	}
}

Let's say you have a few video elements on your page and you introduced a global Picture in Picture handler. If you want to disable PiP on certain videos it is as easy as adding disablepictureinpicture on the <video> element.

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

Picture in Picture Limitations

Unfortunately the API is not available on all browsers yet so it remains a luxurious feature for people who use a browser that support it.

Another limitation is that subtitles / captions currently are not supported in a PiP window. As Francois Beaufort pointed out to me, there is a plan to address it. Meanwhile, if you wanted you could use captureStream() to draw captions or an image over your PiP window.

I hope this was useful to you!