【问题标题】:Playing one of multiple audio tracks in sync with a video与视频同步播放多个音轨之一
【发布时间】:2020-11-14 23:48:29
【问题描述】:

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

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

我尝试使用ffmpeg 分别保存各个音轨并尝试与视频同步播放它们(设置audio.currentTime = video.currentTime 以响应视频上的多个事件,例如playplayingpause , seeked, stalled),在连接到GainNodes 的<audio> 元素中使用Web Audio API 播放两个音轨(切换音轨将增益设置为1 用于您想要的轨道和0对于其余的)。这似乎在 Chrome 中完美运行,但 Firefox 无处不在,即使在同步 currentTime 属性后,实际音频也会延迟一秒或更长时间。

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

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

【问题讨论】:

    标签: javascript html5-video


    【解决方案1】:

    在你的情况下最好的可能是使用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 } );
      } );
    }
    video { max-width: 100%; max-height: 100% }
    <label>Use 300Hz audio track instead of 800Hz <input type="checkbox"></label><br>
    <video controls></video>

    【讨论】:

    • 哇,这听起来正是我需要的!将对此进行调查。视频文件可能非常大,您是否有关于如何在不预先请求完整视频的情况下执行此操作的建议?目前它提供 206 个部分内容,浏览器会在您搜索时自动请求其中的一小部分。
    • 您也可以只传递媒体块,这是 MSE 的主要用途,尽管您必须自己处理分块。网上有很多关于如何做的例子。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多