与视频同步播放多个音轨之一
Playing one of multiple audio tracks in sync with a video
我正在尝试在网络浏览器中播放视频,原始视频带有两个或多个音频流,每个都使用不同的语言。我想让用户可以选择切换他们正在收听的音轨。
我尝试在视频元素上使用 audioTracks
,但尽管说它在大多数浏览器中都支持标记,至少在 Firefox 和 Chrome 中我不会说它在工作(在 Firefox 中,它只显示第一首曲目,元数据是错误的,而在 Chrome 中,一旦您将主曲目静音,视频就会暂停,您必须寻找视频才能真正继续播放)。
我尝试使用 ffmpeg
单独保存各个音轨并尝试与视频同步播放它们(设置 audio.currentTime = video.currentTime
以响应视频中的几个事件,例如 play
, playing
, pause
, seeked
, stalled
), 使用 [=25] 在连接到 GainNode
的 <audio>
元素中播放两个音轨=](切换音轨将您想要的音轨的增益设置为 1
,其余音轨的增益设置为 0
)。这似乎在 Chrome 中完美运行,但 Firefox 无处不在,即使在同步 currentTime
属性后,实际音频也会关闭一秒或更长时间。
我看到其他人抱怨与 MP3 计时不符的类似问题,但我使用的是 AAC。在这些情况下的解决方案是不对音频使用可变比特率,但这似乎并没有改善它 (ffmpeg -i video.mkv -map 0:a:0 -acodec aac -b:a 128k track-0.aac
)
这样做有什么好的策略吗?如果可以避免,我宁愿不必为每个音轨创建重复的视频文件。
在你的情况下最好的可能是使用 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>
我正在尝试在网络浏览器中播放视频,原始视频带有两个或多个音频流,每个都使用不同的语言。我想让用户可以选择切换他们正在收听的音轨。
我尝试在视频元素上使用 audioTracks
,但尽管说它在大多数浏览器中都支持标记,至少在 Firefox 和 Chrome 中我不会说它在工作(在 Firefox 中,它只显示第一首曲目,元数据是错误的,而在 Chrome 中,一旦您将主曲目静音,视频就会暂停,您必须寻找视频才能真正继续播放)。
我尝试使用 ffmpeg
单独保存各个音轨并尝试与视频同步播放它们(设置 audio.currentTime = video.currentTime
以响应视频中的几个事件,例如 play
, playing
, pause
, seeked
, stalled
), 使用 [=25] 在连接到 GainNode
的 <audio>
元素中播放两个音轨=](切换音轨将您想要的音轨的增益设置为 1
,其余音轨的增益设置为 0
)。这似乎在 Chrome 中完美运行,但 Firefox 无处不在,即使在同步 currentTime
属性后,实际音频也会关闭一秒或更长时间。
我看到其他人抱怨与 MP3 计时不符的类似问题,但我使用的是 AAC。在这些情况下的解决方案是不对音频使用可变比特率,但这似乎并没有改善它 (ffmpeg -i video.mkv -map 0:a:0 -acodec aac -b:a 128k track-0.aac
)
这样做有什么好的策略吗?如果可以避免,我宁愿不必为每个音轨创建重复的视频文件。
在你的情况下最好的可能是使用 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>