保持两个YouTube视频的同步

38

我在同一个页面上嵌入了两个相同的YouTube视频。我希望它们能够同步,以下是我的要求/注意事项:

  • 两个视频必须同时开始播放
  • 当用户播放/暂停一个视频时,另一个视频也会跟着做同样的操作
    • 这在API方面很容易实现
  • 当一个视频缓冲时,另一个视频会停止等待,并在它们都准备好后开始播放
  • 我只需要从一个视频中获取音频
  • 同步精度不必完全达到毫秒级别,但可靠性很重要
  • 一个视频将用作背景视频
    • 这个视频将被轻微模糊(使用CSS3模糊),所以质量不是非常重要

我尝试使用YouTube JS API监听播放器状态变化并尝试保持两个视频同步,但效果不如我所希望的那么可靠。我会在下面发布部分代码。

其中一个限制是一个视频的大小将比另一个视频大,因此YouTube可能会为其提供更高质量的视频。

我的问题是,针对上述要求,有什么方法可以保持这两个视频同步吗?我曾考虑过是否可以使用画布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;

}

}

确保同步那么多移动部件并使其足够同步以不分散用户的注意力,听起来确实是一个艰巨的任务...愚蠢的问题,为什么? - charlietfl
2
也许是吧,我正在尝试突破界限 :D。其中一个视频将是一个模糊的(CSS3模糊),全屏背景视频,另一个则是屏幕中央的普通视频...这是我正在做的一个小混乱项目。实际上,如果它略微不同步(但可靠),那也不是什么大问题。 - Ben Everard
@AbhishekHingnikar 我正在加载脚本 <script src="http://www.youtube.com/player_api"></script>,所以我猜它是最新版本。 - Ben Everard
也许你可以尝试一些数学函数,比如:获取video1的时间 | 获取video2的时间 | 如果video1的时间 < video2的时间,则为真 | 执行video2 - video1 = A | 暂停video2 "A" 秒钟 | 如果是false,则为video1 > video2 | video1-video2 = B秒 | 暂停video1 "B" 秒钟 | 如果video1 == video2播放视频 | 等待5秒钟回到第一行。 - CMS_95
但是如果两个视频的长度不同,你会怎么做呢?这将导致您的同步出现问题。 - Just code
显示剩余5条评论
1个回答

35

你的项目相当有趣,这就是我决定尝试帮助你的原因。我从未使用过YouTube API,但我尝试了一些编程,这可能是一个好的开始。

所以这是我尝试过的代码,它似乎运行得很好,当然需要一些改进(我没有尝试计算两个播放视频之间的偏移量,但让它们静音显示不匹配并且听起来很合理)

我们开始吧:

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

<!DOCTYPE html>
<html>
    <head>

我们对前景播放器添加了绝对定位,使其覆盖正在播放背景视频的播放器(用于测试)。

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

jQuery 用于淡入/淡出播放器(下面会解释)但您也可以使用基本的 JS。

    <script src="jquery-1.10.2.min.js"></script>
< p > < iframes > (以及视频播放器)将替换这些 < div > 标签。 < /p >
    <div id="player1"></div>    <!-- Background video player -->
    <div id="player2"></div>    <!-- Foreground video player -->

    <script>

该代码异步加载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);

此函数在API代码下载后创建 < iframes>(以及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;

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

  • 隐藏播放器,以便用户看不到接下来的步骤;
  • 静音播放器 (player.mute():Void);
  • 模拟时间轴跳跃开始缓冲 (player.seekTo(seconds:Number, allowSeekAhead:Boolean):Void);
  • 等待状态更改事件与 YT.PlayerState.PLAYING 相等;
  • 暂停视频 (player.pauseVideo():Void);
  • 使用 player.seekTo(seconds:Number, allowSeekAhead:Boolean):Void 倒带视频;
  • 取消静音播放器 (player.unMute():Void);
  • 显示播放器。

您需要预加载两个视频。

        var preloading1 = false;
        var preloading2 = false;
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
        }

背景视频播放器的状态发生变化时,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
            }
        }
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() {

这里是一段行为奇怪的代码。取消下面的注释会使同步效果变得很糟糕,但如果你将其注释掉,你就需要在播放按钮上单击两次但是同步效果会更好。

                       //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>
请注意,这段代码可能不会计算视图次数。
如果您想要没有解释的代码,请在此处查看:http://jsfiddle.net/QtBlueWaffle/r8gvX/1/ 2016年更新 - 实时预览 希望对您有所帮助。

在 jsFiddle 的例子中,视频没有同时开始吗? - Ben Everard
啊,这是一个jQuery问题,我从HTML中移除了jQuery并将其添加到fiddle的脚本中...现在正在测试。 - Ben Everard
好的,但我在 Fiddle 中还无法启动它,我习惯于在本地工作,在本地它运行得非常好。 - ThisIsSparta
1
我尽力了 :) 但是代码并不完美,需要根据您的具体需求进行调整。祝您的项目好运! - ThisIsSparta
好的,我认为这个解决方案可以让我达到我需要的目标...但还需要一些微调,不过你已经提供了很好的基础。非常感谢! - Ben Everard
一个问题:是否可以将毫秒精度传递给player.seekTo(seconds)?我能够播放从“0秒”同步的两个视频,但当它们都从“56秒”开始时,它们会相差几毫秒。 - sports

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接