Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Keeping two YouTube videos in sync with each other

I've got two identical YouTube videos embedded on the same page. I'd like them to both be in sync, here are my requirements / notes:

  • Both videos must start at the same time
  • When a video is played / paused by the user the other video does the same
    • This is quite easy via the API
  • When one video buffers the other stops to wait, and starts when they are both ready
  • I only need audio from one video
  • Sync accuracy doesn't have to be millisecond perfect, just reliable
  • One video will be used as a background video
    • This video will be slightly blurred (using CSS3 blur), so quality not super essential

I've tried using the YouTube JS API to listen for player state changes and attempt to keep both videos in sync, however it wasn't as reliable as I'd hoped for. I'll post part of the code for this below.

One caveat is that one video will appear larger than the other, so the YouTube might provide a higher quality video for that.

Because I'm using CSS3 blur I can only use recent Webkit browsers, so a solution that works on these alone (and not FF/IE) is not a problem.

My question is this, for the requirements above, is there any way to keep these two videos in sync? I did consider if it was possible to use the canvas API to "redraw" the video, but after researching figured this wasn't going to be possible.

buffering = false;  var buffer_control = function(buffering_video, sibling_video, state) {  switch ( state ) {      case 1: // play          if ( buffering === true ) {              console.error('restarting after buffer');              // pause both videos, we want to make sure they are both at the same time             buffering_video.pauseVideo();             sibling_video.pauseVideo();              // get the current time of the video being buffered...             var current_time = buffering_video.getCurrentTime();              // ... and pull the sibling video back to that time             sibling_video.seekTo(current_time, true);              // finally, play both videos             buffering_video.playVideo();             sibling_video.playVideo();              // unset the buffering flag             buffering = false;          }          break;      case 3: // buffering           console.error('buffering');          // set the buffering flag         buffering = true;          // pause the sibling video         sibling_video.pauseVideo();          break;  }  } 
like image 639
Ben Everard Avatar asked Nov 10 '13 23:11

Ben Everard


People also ask

How do you sync two videos on YouTube?

To sync up video playback, all you need to do is: 1) Find a video you want to watch. Create the party using the 'Start Watching' button. 2) Send the link provided in the extension to all your friends you want to watch with.

Can we sync two YouTube accounts?

You can't merge or link separate YouTube channels or automatically transfer data from one channel to another. But you can manually re-upload your videos to a different channel that you manage.


1 Answers

Your project is kinda interesting, that's why I decided to try to help you. I have never used the youtube API but I have tried some coding and it might be a start for you.

So here is the code I have tried and it seems to work quite well , it certainly needs some improvements ( I haven't tried to calculate the offset between the two played videos but letting them unmute shows the mismatch and it sounds legit)

Here we go :

Let's start with some html basics

<!DOCTYPE html> <html>     <head> 

We add an absolute positionning for the foreground player so it overlays the one playing the background video (for testing)

        <style>             #player2{position:absolute;left:195px;top:100px;}         </style>     </head> <body> 

jQuery is used here to fade in/out the players (you'll see why below) but you can use basic JS

    <script src="jquery-1.10.2.min.js"></script> 

The < iframes> (and video players) will replace these < div> tags.

    <div id="player1"></div>    <!-- Background video player -->     <div id="player2"></div>    <!-- Foreground video player -->      <script> 

This code loads the IFrame Player API code asynchronously.

        var tag = document.createElement('script');          tag.src = "https://www.youtube.com/iframe_api";         var firstScriptTag = document.getElementsByTagName('script')[0];         firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); 

This function creates the < iframes> (and YouTube players) after the API code downloads. Note the callback functions : onPlayer1Ready and onPlayer1StateChange

        var player1;         var player2;         function onYouTubeIframeAPIReady() {             player1 = new YT.Player('player1', {                                   height: '780',                                   width: '1280',                                   videoId: 'M7lc1UVf-VE',                                   playerVars: { 'autoplay': 0, 'controls': 0 },                                   events: {                                         'onReady': onPlayer1Ready,                                         'onStateChange': onPlayer1StateChange                                   }                              });             player2 = new YT.Player('player2', {                                   height: '390',                                   width: '640',                                   videoId: 'M7lc1UVf-VE',                                   events: {                                        'onReady': onPlayer2Ready,                                        'onStateChange': onPlayer2StateChange                                   }                               });         }           var player1Ready = false;         var player2Ready = false; 

So now is the interesting part of the code. The main issue in your project of sync is linked to the fact that videos need to be buffered before launching them. Actually the API doesn't provide any kind of intuitive function to preload the videos (due to bandwidth issues (client and server side). So we have to get a bit tricky.
The steps to preload a video are the following:

  • Hide the player so the next steps aren't visible for the user);
  • Mute the player ( player.mute():Void );
  • Emulate a jump in timeline to start the buffering ( player.seekTo(seconds:Number, allowSeekAhead:Boolean):Void );
  • Wait for a state change event equal to YT.PlayerState.PLAYING;
  • Pause the video ( player.pauseVideo():Void );
  • Rewind the video with player.seekTo(seconds:Number, allowSeekAhead:Boolean):Void ;
  • Unmute the player ( player.unMute():Void );
  • Show the player.

You have to preload your two videos.

        var preloading1 = false;         var preloading2 = false; 

The API will call these functions when the video players are ready.

        function onPlayer1Ready(event)          {             player1Ready = true;             preloading1 = true;       // Flag the player 1 preloading             player1.mute();           // Mute the player 1             $( "#player1" ).hide();   // Hide it             player1.seekTo(1);        // Start the preloading and wait a state change event         }          function onPlayer2Ready(event) {             player2Ready = true;      // The foreground video player is not preloaded here         } 

The API calls this function when the background video player's state changes.

        function onPlayer1StateChange(event)          {             if (event.data == YT.PlayerState.PLAYING ) {                 if(preloading1)                 {                     prompt("Background ready");     // For testing                     player1.pauseVideo();           // Pause the video                     player1.seekTo(0);              // Rewind                     player1.unMute();           // Comment this after test                     $( "#player1" ).show();         // Show the player                     preloading1 = false;                      player2Ready = true;                     preloading2 = true;             // Flag for foreground video preloading                     player2.mute();                     //$( "#player2" ).hide();                     player2.seekTo(1);              // Start buffering and wait the event                 }                 else                     player2.playVideo();            // If not preloading link the 2 players PLAY events             }              else if (event.data == YT.PlayerState.PAUSED ) {                 if(!preloading1)                     player2.pauseVideo();           // If not preloading link the 2 players PAUSE events             }             else if (event.data == YT.PlayerState.BUFFERING ) {                 if(!preloading1)                 {                     player2.pauseVideo();           // If not preloading link the 2 players BUFFERING events                 }             }             else if (event.data == YT.PlayerState.CUED ) {                 if(!preloading1)                     player2.pauseVideo();           // If not preloading link the 2 players CUEING events             }             else if (event.data == YT.PlayerState.ENDED ) {                 player2.stopVideo();                // If not preloading link the 2 players ENDING events             }         } 

The API calls this function when the foreground video player's state changes.

        function onPlayer2StateChange(event) {             if (event.data == YT.PlayerState.PLAYING ) {                 if(preloading2)                 {                     //prompt("Foreground ready");                     player2.pauseVideo();           // Pause the video                     player2.seekTo(0);              // Rewind                     player2.unMute();               // Unmute                     preloading2 = false;                      $( "#player2" ).show(50, function() { 

Here is a part of the code that acts strangely. Uncommenting the line below will make the sync quite bad,but if you comment it, you will have to click twice on the PLAY button BUT the sync will look way better.

                       //player2.playVideo();                     });                 }                 else                     player1.playVideo();             }             else if (event.data == YT.PlayerState.PAUSED ) {                 if(/*!preloading1 &&*/ !preloading2)                     player1.pauseVideo();             }             else if (event.data == YT.PlayerState.BUFFERING ) {                 if(!preloading2)                 {                     player1.pauseVideo();                     //player1.seekTo(... // Correct the offset here                 }             }             else if (event.data == YT.PlayerState.CUED ) {                 if(!preloading2)                     player1.pauseVideo();             }             else if (event.data == YT.PlayerState.ENDED ) {                 player1.stopVideo();             }         }           </script>     </body> </html> 

Note that the views might not be counted with this code.

If you want the code without the explanations you can go here : http://jsfiddle.net/QtBlueWaffle/r8gvX/1/

2016 Update Live Preview

Hope this helps.

like image 98
ThisIsSparta Avatar answered Sep 22 '22 09:09

ThisIsSparta