mediasource 通过 sourcebuffer 动态追加媒体片段实现音视频同步播放,拼接成功关键在于编码参数统一、时间戳连续、初始化段完整、容器格式合规,并需正确创建双轨 sourcebuffer、设置 timestampoffset 对齐轨道及处理错误状态。

MediaSource 接口本身不直接“拼接”音视频轨道,而是通过动态追加 SourceBuffer 中的媒体片段(如 MP4 或 WebM 的初始化段 + 媒体段),实现音视频流的分时、分轨写入与同步播放。真正的“轨道拼接”依赖于你提供的媒体数据是否符合时间线连续、编码参数一致、且音视频时间戳(PTS/DTS)对齐。
确保音视频轨道可拼接的前提条件
拼接成功与否,80% 取决于输入数据质量:
-
统一编码参数:所有视频段需同编解码器(如 H.264)、分辨率、帧率;音频段需同编码器(如 AAC)、采样率、声道数。混用会导致
SourceBuffer.appendBuffer()报错或解码失败。 -
时间戳严格连续:每个媒体段(尤其是后续段)的起始 PTS 必须等于前一段的结束 PTS(+ 微小容差,如 1ms)。可用 FFmpeg 检查:
ffprobe -v quiet -show_entries packet=pts_time,duration_time -of csv input.mp4 -
包含完整初始化段(init segment):每个
SourceBuffer(视频/音频各一个)必须先 append 一次 init segment(含 moov box),之后才能追加媒体段(moof+mdat)。不能共用 init segment —— 视频和音频 init 段必须分别提供。 - 使用 ISO BMFF(MP4)或 WebM 容器:MediaSource 仅支持这些格式的流式解析。AVI、MKV(非 WebM 子集)等不支持。
创建双轨 SourceBuffer 并按序写入
关键在于为 video 和 audio 各建一个 SourceBuffer,并保证它们 append 的时间范围不重叠、无缝衔接:
const mediaSource = new MediaSource();
videoElement.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', () => {
// 创建视频轨 buffer(注意 MIME type 中 codec 参数必须匹配实际数据)
const videoBuffer = mediaSource.addSourceBuffer('video/mp4; codecs="avc1.42E01E"');
// 创建音频轨 buffer
const audioBuffer = mediaSource.addSourceBuffer('audio/mp4; codecs="mp4a.40.2"');
// ✅ 正确顺序:先写 init 段(各自独立)
videoBuffer.appendBuffer(videoInitSegment);
audioBuffer.appendBuffer(audioInitSegment);
// ✅ 再按时间顺序交替/并行写媒体段(确保时间戳对齐)
videoBuffer.appendBuffer(videoSegment1); // PTS: 0–5s
audioBuffer.appendBuffer(audioSegment1); // PTS: 0–5s
videoBuffer.appendBuffer(videoSegment2); // PTS: 5–10s
audioBuffer.appendBuffer(audioSegment2); // PTS: 5–10s
});
处理时间戳偏移与轨道对齐
若原始音视频段起始时间不一致(如音频比视频早 200ms),不能靠 JS 等待来“对齐”,而应在 append 前调整其内部时间戳:
- 使用
SourceBuffer.timestampOffset可整体平移该轨所有 append 数据的时间轴(单位:秒)。例如:audioBuffer.timestampOffset = -0.2;表示将后续 append 的音频 PTS 全部提前 200ms。 - 该值必须在 append 前设置,且仅对之后 append 的数据生效;已 append 的数据不受影响。
- 更可靠的做法是在生成媒体段时就修正时间戳(如用 FFmpeg 的
-itsoffset或 mp4box.js 调整 moof 中的 baseMediaDecodeTime)。
错误捕获与状态管理
拼接过中常见问题需主动监听:
-
监听
sourcebuffer.error:append 失败时触发,此时应调用sourceBuffer.abort()清理状态,再重试或降级。 -
检查
mediaSource.readyState:若变为"ended",说明已调用mediaSource.endOfStream(),不能再 append;若为"closed",需重建整个 MediaSource。 -
避免 overlap:append 前检查
sourceBuffer.buffered,确保新段起始时间 ≥ 当前 buffered 最大 end 时间,否则会触发QuotaExceededError或静音/卡顿。
不复杂但容易忽略——真正决定能否流畅拼接的,不是代码逻辑,而是你喂给 SourceBuffer 的那些二进制片段是否“讲规矩”。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











