保持两个YouTube视频彼此同步

时间:2021-12-19 12:05:20

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:

我在同一页面上嵌入了两个相同的YouTube视频。我希望他们两者同步,这是我的要求/说明:

  • 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
    • 这很容易通过API

  • 当用户播放/暂停视频时,其他视频也是如此。通过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
    • 这个视频会有些模糊(使用CSS3模糊),因此质量不是超级必需品

  • 一个视频将用作背景视频此视频将略微模糊(使用CSS3模糊),因此质量不是超级必要的

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.

我已经尝试使用YouTube JS API来监听播放器状态更改并尝试保持两个视频同步,但它并不像我希望的那样可靠。我将在下面发布部分代码。

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

需要注意的是,一个视频看起来比另一个视频大,因此YouTube可能会提供更高质量的视频。

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.

因为我使用CSS3模糊,所以我只能使用最近的Webkit浏览器,所以单独使用这些(而不是FF / IE)的解决方案不是问题。

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.

我的问题是,对于上述要求,有没有办法让这两个视频保持同步?我确实考虑过是否可以使用canvas API来“重绘”视频,但经过研究发现,这是不可能的。

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;

}

}

1 个解决方案

#1


29  

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.

你的项目很有意思,这就是我决定尝试帮助你的原因。我从来没有使用过youtube API,但我尝试了一些编码,这可能是一个开始。

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

让我们从一些HTML基础知识开始

<!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

这里使用jQuery淡入/淡出玩家(你会在下面看到原因)但你可以使用基本的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.

此代码异步加载IFrame Player API代码。

        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

下载API代码后,此功能会创建 (和YouTube播放器)。请注意回调函数:onPlayer1Ready和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:

所以现在是代码中有趣的部分。同步项目中的主要问题与视频在启动之前需要缓冲的事实有关。实际上,API没有提供任何直观的功能来预加载视频(由于带宽问题(客户端和服务器端)。所以我们必须有点棘手。预加载视频的步骤如下:

  • Hide the player so the next steps aren't visible for the user);
  • 隐藏播放器,以便用户看不到后续步骤);

  • Mute the player ( player.mute():Void );
  • 将播放器静音(player.mute():Void);

  • Emulate a jump in timeline to start the buffering ( player.seekTo(seconds:Number, allowSeekAhead:Boolean):Void );
  • 模拟时间轴中的跳转以开始缓冲(player.seekTo(seconds:Number,allowSeekAhead:Boolean):Void);

  • Wait for a state change event equal to YT.PlayerState.PLAYING;
  • 等待状态更改事件等于YT.PlayerState.PLAYING;

  • Pause the video ( player.pauseVideo():Void );
  • 暂停视频(player.pauseVideo():Void);

  • Rewind the video with player.seekTo(seconds:Number, allowSeekAhead:Boolean):Void ;
  • 使用player.seekTo(秒:Number,allowSeekAhead:Boolean)回放视频:Void;

  • Unmute the player ( player.unMute():Void );
  • 取消静音播放器(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.

当视频播放器准备就绪时,API将调用这些功能。

        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.

当后台视频播放器的状态发生变化时,API会调用此函数。

        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.

当前景视频播放器的状态发生变化时,API会调用此函数。

        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.

这是代码的一部分,行为奇怪。取消注释下面的行将使同步非常糟糕,但如果你评论它,你将不得不在PLAY按钮上点击两次但是同步看起来会更好。

                       //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/

如果你想要没有解释的代码,你可以去这里:http://jsfiddle.net/QtBlueWaffle/r8gvX/1/

2016 Update Live Preview

2016年更新实时预览

Hope this helps.

希望这可以帮助。

#1


29  

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.

你的项目很有意思,这就是我决定尝试帮助你的原因。我从来没有使用过youtube API,但我尝试了一些编码,这可能是一个开始。

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

让我们从一些HTML基础知识开始

<!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

这里使用jQuery淡入/淡出玩家(你会在下面看到原因)但你可以使用基本的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.

此代码异步加载IFrame Player API代码。

        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

下载API代码后,此功能会创建 (和YouTube播放器)。请注意回调函数:onPlayer1Ready和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:

所以现在是代码中有趣的部分。同步项目中的主要问题与视频在启动之前需要缓冲的事实有关。实际上,API没有提供任何直观的功能来预加载视频(由于带宽问题(客户端和服务器端)。所以我们必须有点棘手。预加载视频的步骤如下:

  • Hide the player so the next steps aren't visible for the user);
  • 隐藏播放器,以便用户看不到后续步骤);

  • Mute the player ( player.mute():Void );
  • 将播放器静音(player.mute():Void);

  • Emulate a jump in timeline to start the buffering ( player.seekTo(seconds:Number, allowSeekAhead:Boolean):Void );
  • 模拟时间轴中的跳转以开始缓冲(player.seekTo(seconds:Number,allowSeekAhead:Boolean):Void);

  • Wait for a state change event equal to YT.PlayerState.PLAYING;
  • 等待状态更改事件等于YT.PlayerState.PLAYING;

  • Pause the video ( player.pauseVideo():Void );
  • 暂停视频(player.pauseVideo():Void);

  • Rewind the video with player.seekTo(seconds:Number, allowSeekAhead:Boolean):Void ;
  • 使用player.seekTo(秒:Number,allowSeekAhead:Boolean)回放视频:Void;

  • Unmute the player ( player.unMute():Void );
  • 取消静音播放器(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.

当视频播放器准备就绪时,API将调用这些功能。

        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.

当后台视频播放器的状态发生变化时,API会调用此函数。

        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.

当前景视频播放器的状态发生变化时,API会调用此函数。

        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.

这是代码的一部分,行为奇怪。取消注释下面的行将使同步非常糟糕,但如果你评论它,你将不得不在PLAY按钮上点击两次但是同步看起来会更好。

                       //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/

如果你想要没有解释的代码,你可以去这里:http://jsfiddle.net/QtBlueWaffle/r8gvX/1/

2016 Update Live Preview

2016年更新实时预览

Hope this helps.

希望这可以帮助。