同步视频和音频(最好不使用 JavaScript)

Posted

技术标签:

【中文标题】同步视频和音频(最好不使用 JavaScript)【英文标题】:Synchronize video and audio (preferably without JavaScript) 【发布时间】:2016-08-19 19:45:29 【问题描述】:

如果我有 html5 videoaudio 元素,有没有一种干净的方法可以让它们保持同步?它们应该像包含音轨的视频文件一样,因此手动推进一个轨道应该将另一个轨道带上。假设这两个轨道的持续时间相同。

我想要一个可以跨浏览器运行的解决方案,但我并不特别关心它是否适用于旧版浏览器。如果可能,最好避免使用 javascript。否则,一个非常简单的 JavaScript 库将是最好的——它只要求同步哪些轨道并处理其余部分。

我已经查看了 mediagroup... 但它看起来只适用于 Safari。我查看了 audioTracks... 但用户必须在 Firefox 中启用该功能。

我查看了Popcorn.js,这是一个似乎为这项任务而设计的 JavaScript 框架……但看起来一年多来没有任何活动。除此之外,我能找到的唯一演示是将文本或幻灯片等内容同步到视频,而不是音频到视频。

【问题讨论】:

是否可以在外部程序中预先将音频添加到您的视频中?这将是同步两者的最可靠方式。 "without JavaScript",我会说这是不可能的,但你基本上是在两个元素上监听缓冲区事件,并停止它们。然后,当加载足够的数据时继续播放。重复。 @Glen Despaux 我也希望避免这种情况...... 您可能对 HTML 自身可以做什么提出了太多要求,那么...... @GlenDespaux 是对的,HTML 处理的是文档的结构而不是其行为。 【参考方案1】:

您可以使用Promise.all()fetch() 检索媒体资源作为BlobURL.createObjectURL() 创建资源的Blob URLcanplaythrough event 和 Array.prototype.every() 检查每个资源是否可以播放;当两个资源都可以播放时,在每个资源上调用.play()

我没有在问题中说清楚。我的意思是这两条轨道 应该保持同步,就像播放带有音频的常规视频文件一样。

一种方法是创建一个时间戳变量,当设置变量大于最小延迟时,利用seeked 事件更新元素的.currentTime,以防止递归调用.currentTime

<!DOCTYPE html>
<html>
<head>
</head>
<body>
  <button>load media</button>
  <br>
  <div id="loading" style="display:none;">loading media...</div>
  <script>
    var mediaBlobs = [];
    var mediaTypes = [];
    var mediaLoaded = [];
    var button = document.querySelector("button");
    var loading = document.getElementById("loading");
    var curr = void 0;
    var loadMedia = () => 
      loading.style.display = "block";
      button.setAttribute("disabled", "disabled");
      return Promise.all([
        // `RETURN` by smiling cynic are licensed under a Creative Commons Attribution 3.0 Unported License
        fetch("https://ia600305.us.archive.org/30/items/return_201605/return.mp3")
      , fetch("http://nickdesaulniers.github.io/netfix/demo/frag_bunny.mp4")
        ])
        .then(responses => responses.map(media => (
          [media.headers.get("Content-Type").split("/")[0]]: media.blob()
        )))
        .then(data => 
          for (var currentMedia = 0; currentMedia < data.length; currentMedia++) 
            (function(i) 
              var type = Object.keys(data[i])[0];
              mediaTypes.push(type);
              console.log(data[i]);
              var mediaElement = document.createElement(type);
              mediaElement.id = type;
              var label = document.createElement("label");
              mediaElement.setAttribute("controls", "controls");
              mediaElement.oncanplaythrough = () => 
                mediaLoaded.push(true);
                if (mediaLoaded.length === data.length 
                   && mediaLoaded.every(Boolean)) 
                    loading.style.display = "none";
                    for (var track = 0; track < mediaTypes.length; track++) 
                      document.getElementById(mediaTypes[track]).play();
                      console.log(document.getElementById(mediaTypes[track]));
                    
                
              
              var seek = (e) => 
                if (!curr || new Date().getTime() - curr > 20) 
                  document.getElementById(
                    mediaTypes.filter(id => id !== e.target.id)[0]
                  ).currentTime = e.target.currentTime;
                  curr = new Date().getTime();
                
              
              mediaElement.onseeked = seek;
              mediaElement.onended = () => 
                for (var track = 0; track < mediaTypes.length; track++) 
                  document.getElementById(mediaTypes[track]).pause()
                
              
              mediaElement.ontimeupdate = (e) => 
                e.target.previousElementSibling
                .innerHTML = `$mediaTypes[i] currentTime: $Math.round(e.target.currentTime)<br>`;
              
              data[i][type].then(blob => 
                mediaBlobs.push(URL.createObjectURL(blob));
                mediaElement.src = mediaBlobs[mediaBlobs.length - 1];
                document.body.appendChild(mediaElement);
                document.body.insertBefore(label, mediaElement);
              )
            (currentMedia));
          
        )
    ;
    button.addEventListener("click", loadMedia);
  </script>
</body>
</html>

plnkrhttp://plnkr.co/edit/r51oh7KsZv8DEYhpRJlL?p=preview

【讨论】:

我认为这里缺少的部分是保持两条轨道同步。它们不应不同步,手动推进一首曲目应将另一首曲目与它同步。 @user1475412 "手动推进一个轨道应该把另一个轨道一起带上。"这个需求描述没有出现在实际问题中? 对不起,我没有在问题中说清楚。我的意思是这两个轨道应该保持同步,就像播放带有音频的普通视频文件一样。我会修改问题。【参考方案2】:

据我所知,这对于纯 HTML 是不可能的。

您可以使用&lt;video&gt;&lt;audio&gt; 元素的currentTime 属性来同步时间。

var video = document.getElementById("your-video");
var audio = document.getElementByid("your-audio");
audio.currentTime = video.currentTime;

如有必要,您还可以使用timeupdate 事件来不断地重新同步视频和音频。

【讨论】:

我觉得这个方法效果不太好...见html5demos.com/two-videos 看那个demo的源码,好像时间只同步了一次。如果使用timeupdate 事件来不断地重新同步它们,它可能会更好。

以上是关于同步视频和音频(最好不使用 JavaScript)的主要内容,如果未能解决你的问题,请参考以下文章

FFmpeg concat 视频和音频不同步

是否可以最好使用javascript混合多个音频文件

使用 AVPlayer 音频播放 AVMutableComposition 会不同步

使用 OpenCV 和 PyAudio 同步音频和视频

MPMoviePlayerController 音频/视频不同步

FFmpeg学习6:视音频同步