🛰 Enable PiP for Youtube Video Embeds
Tags: javascript, webdev, tutorial, pip
Published: 2021-07-26 12:53:49


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 (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 iframe ready to be embedded and shared on your site. Tim Berners-Lee would be proud.
![Happy Tim Lee](https://news.mit.edu/sites/default/files/styles/news_article__image_gallery/public/images/201704/tim-berners-lee-mit-turing-award-00_0.jpg?itok=Fxx5jecT)
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 runs dry. But don't fret, for I will be your guide on this ill-fated journey.

Where to begin? Well, we already have Youtube video in an iframe. Let's just plop that on a page. 
![utube embed](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pl0y4ky6qu9koo4hdjac.PNG)
 
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 let's open up the inspector tool. See if you can find that pip button hiding somewhere in the HTML. Here's a hint 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dvab2ds0sl6dhlepl41d.png)
 
And since the button has a css-class-selector we should easily be able to add a CSS rule to our page to show it.

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

But this doesn't work (I tried) for a couple of reasons. I'll discuss why later. For now, let's take a second look at our iframe element and pay close attention to its "allow" attribute. 

    <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 boy, 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 developer documentation. Don't worry we only need to check out the stuff related to picture-in-picture. And after thorough examination, 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 pip (picture-in-picture). Let's set it up and give it a go.
![plyr](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rxzs0zb411xdr0qxxpgk.png)
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, open pip on programmatically and make our own button.
![plan](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3627bc21banrb3detetu.png) 
 I've compiled a list of 3 StackOverflow posts that seem like promising steps towards our goal. 
 1. [Youtube iframe picture-in-picture mode](https://stackoverflow.com/a/62761448/8723748)
 2. [Post message to Youtube iframe](https://stackoverflow.com/a/54646877/8723748)
 3. [Show Youtube video source in  html5 video tag](https://stackoverflow.com/a/64894396/8723748)

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 same-origin policies. But we're just gonna ignore that for now. 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c0benjxb5ai3u0dhsng2.PNG)
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 once the user logs in, steal all the user's bank information.
![bankthief](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dxadbjj4s63xgq3lwlbt.png)
 
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 naive 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](https://stackoverflow.com/a/64894396/8723748) 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. Here's what the url looks like: 

    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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t6l3aamdavj5ccjsyk55.png)
 
The author of the StackOverflow [post](https://stackoverflow.com/a/64894396/8723748) 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 sending our user 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'll try again some other day.

Ima end this post here. I find it befitting that my first technical blog teaches absolutely nothing. Or at least accomplishes nothing. Join me next time, where we will explore retrieving the title & author of a Youtube video client-side (TL;DR: no need to sign up for a Google API key and setup a server, just call the undocumented `YT.player.getVideoData` method).