与视频同步播放多个音轨之一

she*_*dox 1 javascript html5-video

我正在尝试在网络浏览器中播放视频,原始视频带有两个或多个音频流,每个音频流都采用不同的语言。我想让用户可以选择切换他们正在收听的音轨。

我尝试audioTracks在 video 元素上使用,但尽管说大多数浏览器都支持它,至少在 Firefox 和 Chrome 中,我不会说它完全有效(在 Firefox 中,它只显示第一个轨道,并且元数据是错误的,在 Chrome 中,一旦您将主音轨静音,视频就会暂停,并且您必须寻找视频才能使其真正继续播放)。

我尝试ffmpeg单独保存各个音轨,并尝试与视频同步播放它们(设置audio.currentTime = video.currentTime响应视频上的多个事件,如playplayingpauseseeked、 ),使用Web Audio API在连接到s 的元素stalled中播放两个音轨(切换音轨可设置您想要的音轨和其余音轨的增益)。这似乎在 Chrome 中完美运行,但 Firefox 到处都是,即使在同步属性后,实际音频也会关闭一秒或更长时间。<audio>GainNode10currentTime

我看到其他人抱怨 MP3 的时序问题,但我使用的是 AAC。在这些情况下,解决方案是不对音频使用可变比特率,但这似乎并没有改善它 ( ffmpeg -i video.mkv -map 0:a:0 -acodec aac -b:a 128k track-0.aac)

有什么好的策略可以做到这一点吗?如果可以避免的话,我宁愿不必为每个音轨都有重复的视频文件。

Kai*_*ido 5

对于您的情况,最好的方法可能是使用Media Source Extension (MSE) API
这将允许您在继续播放原始视频的同时仅切换音频源。
由于我们将用其他音频源替换整个音频 SourceBuffer 的内容,因此不会出现同步问题,对于播放器来说,就像只有一个音频源一样。

(async() => {
  const vid = document.querySelector( "video" );
  const check = document.querySelector( "input" );
  // video track as ArrayBuffer
  const bufvid = await getFileBuffer( "525d5ltprednwh1/test.webm" );
  // audio track one
  const buf300 = await getFileBuffer( "p56kvhwku7pdzd9/beep300hz.webm" );
  // audio track two
  const buf800 = await getFileBuffer( "me3y69ekxyxabhi/beep800hz.webm" );
  
  const source = new MediaSource();
  // load our MediaSource into the video
  vid.src = URL.createObjectURL( source );
  // when the MediaSource becomes open
  await waitForEvent( source, "sourceopen" );

  // append video track
  const vid_buffer = source.addSourceBuffer( "video/webm;codecs=vp8" );
  vid_buffer.appendBuffer( bufvid );

  // append one of the audio tracks
  const aud_buffer =  source.addSourceBuffer( "audio/webm;codecs=opus" );
  aud_buffer.appendBuffer( check.checked ? buf300 : buf800 );
  // wait for both SourceBuffers to be ready
  await Promise.all( [
    waitForEvent( aud_buffer, "updateend" ),
    waitForEvent( vid_buffer, "updateend" )
  ] );
  // Tell the UI the stream is ended (so that 'ended' can fire)
  source.endOfStream();
  
  check.onchange = async (evt) => {
    // remove all the data we had in the Audio track's buffer
    aud_buffer.remove( 0, source.duration );
    // it is async, so we need to wait it's done
    await waitForEvent( aud_buffer, "updateend" );
    // no we append the data of the other track
    aud_buffer.appendBuffer( check.checked ? buf300 : buf800 );
    // also async
    await waitForEvent( aud_buffer, "updateend" );
    // for ended to fire
    source.endOfStream();
  };

})();

// helpers
function getFileBuffer( filename ) {
  return fetch( "https://dl.dropboxusercontent.com/s/" + filename )
    .then( (resp) => resp.arrayBuffer() );
}
function waitForEvent( target, event ) {
  return new Promise( res => {
    target.addEventListener( event, res, { once: true } );
  } );
}
Run Code Online (Sandbox Code Playgroud)
video { max-width: 100%; max-height: 100% }
Run Code Online (Sandbox Code Playgroud)
<label>Use 300Hz audio track instead of 800Hz <input type="checkbox"></label><br>
<video controls></video>
Run Code Online (Sandbox Code Playgroud)