🛰 Enable PiP for Youtube Video Embeds

TL;DR: It's impossible. Give up now.

So you want to host Youtube videos on your site, ya? Well you're in luck. The web was invented for just this type of thing (kinda, not really). And Youtube makes it quite simple. All you need to do is hit that share button under a video, select embed, copy the Html code, and voila; you have a Youtube video embedded in an iframe (mini-webpage), ready to be shared on your site. Tim Berners-Lee would be proud.
Happy Tim Lee
But wait, what's that? You want to enable playback in a floating window so that your users can continue using your app while watching videos? Well my friend this is where your luck begins running dry. Don't fret too much though, for I will be your guide on this ill-fated journey.

Where to begin? Well, we already have Youtube video embedded in an iframe. Let's just plop that on a page.
utube embed

Unfortunately, there's no pip button here in the video controls. I happen to know that if you right-click on the video you will get a context menu with the option to open the video in a picture-in-picture window. But you can't count on the user to discover that they need to right-click the video and select pip. Heck, we can barely count on them to find a pip button if it were right in front of their face.

Right-click the video again, but this time open up the inspector tool. See if you can maybe find that pip button hiding somewhere in the Html. Here's a tip, it's hiding next to the fullscreen button. If we toggle the display:none inline-style on the button we can get it to showup!
display

And since the button has a class selector, we should easily be able to add a CSS rule to our page to show it.

.ytp-pip-button { display: inline; }

However this doesn't work (I tried) for a couple of reasons that we will discuss later. For now lets take a look at our iframe element and pay special attention to its "allow" attribtue.

<iframe allow="picture-in-picture; accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope" src="https://www.youtube.com/embed/3vBwRfQbXkg" title="YouTube video player" frameborder="0" width="560" height="315" allowfullscreen></iframe>

The attribute contains the words picture in picture. And picture in picture is exactly what we are trying to accomplish. So what is the problem here? Perhaps we should take a look at Youtube's embed documentation. Don't worry, we only need to check out the stuff related to picture in picture. And after a thorough examination of the docs, we find zero references to picture in picture (I encourage you to take a second look). It's now time to head to Google for more options.

One option we have is a popular library called Plyr that supports Youtube video embeds. They also support picture in picture. Let's set it up and give it a go.
plyr
That didn't seem to work. I don't think we can count on this library to handle Youtube pip playback for us. Nor does there seem to be any other library on the web that can. But we won't be easily deterred. If no one else has done it, then we will just need to blaze the trail ourselves.

Here is the game plan. We know the pip button is hiding in the Youtube iframe embed next to the fullscreen button. If we could just get the button to show up somehow. Or maybe we can bypass the button altogether and open pip on command, in which case we could make our own button.
plan
I've compiled a list of 3 StackOverflow posts that seem like promising steps towards our goal.

The first one talks about a right-click option to select pip, which we already know about. It also mentions something about it being impossible to trigger pip programmatically from outside the iframe embed due to, uh, same-origin policies. But we're gonna just ignore that for a second. Lastly mentioned is that Html video elements come with a video.requestPictureInPicture() method. Although we aren't working directly with a video element, our iframe element has a video element inside of it. If we can somehow get a hold of it and...
cross-origin err
Alright, what have we learned? Who cares, the point is that we won't be running any functions inside this pretty iframe of ours. Which is a shame because... wait a second, is it even our iframe? Technically it's Youtube's since we can't execute any code inside of it. So really we just invited Youtube to come and have a nice little seat on our webpage.

Perhaps there's some way we can style this thing. We tried to get the pip button to display using CSS but that didn't work. Actually we tried setting the CSS of our webpage, not the iframe. And since the iframe is a "separate" page we will need to set its CSS... somehow. We can't inject javascript to run code in the iframe, but surely CSS isn't a problem? I mean what're the most some style changes can do. Someone on Google has to have a solution for styling cross-domain iframes.

Gonna save some time here and just say the plan of injecting CSS is a BIG no-go. Same cross-domain scripting issue. To put it concretely, in order to access data between window frames, the frames need to have the same domain name, port, and protocol. It would be a field day for hackers otherwise. For instance, a site could coerce its users to log in to their bank in an iframe, and then once logged in, steal all the user's bank information from the frame.
bankthief

Here's a wild idea. The iframe isn't really ours, ya? Because we link it to youtube.com, ya? Once it's linked we can only send it messages with window.postMesssage. And messages require Youtube or the iframe to have message listeners to receive and execute our commands. And since Youtube doesn't care about any commands not laid out in their API docs we have no hope of opening pip this way. But what if.. what if we add our own message listener to the iframe before relinquishing ownership to Youtube. This way we can send a command to the iframe to find the video element and call requestPictureInPicture(). Alternatively, the message handler could just unhide the pip button, not really picky here.

I think you know how this ends up. The browser is not fooled by our naïve attempt at code injection. If we were really desperate, rather than trying to inject code into the iframe we could try and programmatically right-click the video, open the context menu, and select picture in picture for the user. But I'm sure there's a host of security issues preventing that too (sure because I tried).

Fear not, we haven't run out of options yet. The 3rd StackOverflow post in the list I compiled mentions loading Youtube videos directly into a video element tag. It does so by sending Google a request for a video's stream URL. The url looks like this:

https://r4---sn-4g5ednls.googlevideo.com/videoplayback?expire=1627319351&ei=15...

We can then set the src for our video element to this stream URL and call requestPictureInPicture().
yt2html

The author of the StackOverflow post wrote a package to retrieve the stream URL. But the package makes requests to a reverse proxy server (yt2html5.com) to retrieve the stream URL, which can't trust. It could suddenly start directing our users to pornographic streams for all we know. So we will need to request the stream URL ourselves (using a server we setup). Alternatively, we could ask Google nicely for the raw video data and stream the video ourselves.

And it is at this point we say fuck Youtube and pip. Maybe we try again some other day.

Ima end this post off here. It is befitting for my first technical blog to teach absolutely nothing. Or at least accomplish nothing. Join me next time, where we will explore the difficulties of retrieving the title/author of a Youtube video on the client-side (TL;DR: no need to sign up for a Google API key and setup a reverse proxy server, you can just call the undocumented YT.player.getVideoData method).

20