代码之家  ›  专栏  ›  技术社区  ›  sheodox

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

  •  0
  • sheodox  · 技术社区  · 4 年前

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

    我试着用 audioTracks 在视频元素上,尽管大多数浏览器都支持它,至少在Firefox和Chrome中,我并不认为它是有效的(在Firefox中,它只显示第一个音轨,而元数据是错误的,在Chrome中,当你静音主音轨时,视频就会暂停,你必须寻找视频才能真正播放继续播放)。

    我试着用 ffmpeg 单独保存单个音频曲目并尝试与视频同步播放(设置 audio.currentTime = video.currentTime play , playing , pause seeked , stalled <audio> 连接到的元素 GainNode 使用 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 )

    有什么好的策略吗?如果我可以避免的话,我不希望每个音轨都有重复的视频文件。

    0 回复  |  直到 4 年前
        1
  •  1
  •   Kaiido NickSlash    4 年前

    在你的情况下最好是使用 Media Source Extension (MSE) API .
    这将允许您在继续播放原始视频的同时只切换音频源。

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