Search code examples
javascriptvideohtml5-canvashtml5-videopicture-in-picture

How to play Multiple Videos in requestPictureInPicture?


requestPictureInPicture is so amazing, but it looks like it only works with 1 video.

How can I get requestPictureInPicture to play multiple videos, so I can watch two videos at the same time?

Basically this only displays one video:

      video
      .requestPictureInPicture()
      .catch(error => {
        console.log(error) // Error handling
      });
      video2
      .requestPictureInPicture()
      .catch(error => {
        console.log(error) // Error handling
      });

https://codepen.io/zecheesy/pen/YzwBJMR

Thoughts: Maybe we could put two videos in a canvas? And have the pictureInPicture play both videos at the same time? https://googlechrome.github.io/samples/picture-in-picture/audio-playlist

I'm not sure if this is possible. Would love your help so much!


Solution

  • Regarding opening two PictureInPitcure windows simultaneously, the specs have a paragraph just for it, where they explain they actually leave it as an implementation detail:

    Operating systems with a Picture-in-Picture API usually restrict Picture-in-Picture mode to only one window. Whether only one window is allowed in Picture-in-Picture mode will be left to the implementation and the platform. However, because of the one Picture-in-Picture window limitation, the specification assumes that a given Document can only have one Picture-in-Picture window.

    What happens when there is a Picture-in-Picture request while a window is already in Picture-in-Picture will be left as an implementation detail: the current Picture-in-Picture window could be closed, the Picture-in-Picture request could be rejected or even two Picture-in-Picture windows could be created. Regardless, the User Agent will have to fire the appropriate events in order to notify the website of the Picture-in-Picture status changes.

    So the best we can say it that you should not expect it to open two windows simultaneously.


    Now, if you really wish, you can indeed draw both videos on a canvas and pass this canvas to a PiP window, after piping its captureStream() to a third <video>, though this require that both videos are served with the proper Access-Control-Allow-Origin headers, and moreover, it requires your browser to actually support the PiP API (current Firefox has a PiP feature which is not the PiP API).

    Here is a proof of concept:

    const vids = document.querySelectorAll( "video" );
    const btn = document.querySelector( "button" );
    
    // wait for both video has their metadata
    Promise.all( [ ...vids ].map( (vid) => {
      return new Promise( (res) => vid.onloadedmetadata = () => res() );
    } ) )
    .then( () => {
      if( !HTMLVideoElement.prototype.requestPictureInPicture ) {
        return console.error( "Your browser doesn't support the PiP API" );
      }
      btn.onclick = async (evt) => {
        const canvas = document.createElement( "canvas" );
        // both videos share the same 16/9 ratio
        // so in this case it's really easy to draw both on the same canvas
        // to make it dynamic would require more maths
        // but I'll let it to the readers
        const height = 720;
        const width = 1280;
        canvas.height = height * 2; // vertical disposition
        canvas.width = width;
        const ctx = canvas.getContext( "2d" );
        
        const video = document.createElement( "video" );
        video.srcObject = canvas.captureStream();
    
        let began = false; // rPiP needs video's metadata
        anim();
        await video.play();
        began = true;
        video.requestPictureInPicture();
        
        function anim() {
          ctx.drawImage( vids[ 0 ], 0, 0, width, height );
          ctx.drawImage( vids[ 1 ], 0, height, width, height  );
          // iff we are still in PiP mode
          if( !began || document.pictureInPictureElement === video ) {
            requestAnimationFrame( anim );
          }
          else {
            // kill the stream
            video.srcObject.getTracks().forEach( track => track.stop() );
          }
        }
      }
    } );
    video { width: 300px }
    <button>enter Picture in Picture</button><br>
    <video crossorigin  muted controls autoplay loop
      src="https://upload.wikimedia.org/wikipedia/commons/2/22/Volcano_Lava_Sample.webm"></video>
    <video crossorigin  muted controls autoplay loop
      src="https://upload.wikimedia.org/wikipedia/commons/a/a4/BBH_gravitational_lensing_of_gw150914.webm"></video>

    And beware, since I did mute the videos fo SO, scrolling in a way the original videos are out of sight will pause them.