You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用ts-ebml为实时视频Blob块生成可跳转时间戳?

问题分析与解决方案

你的核心问题在于每个视频片段都被单独注入了完整的EBML元数据,导致后端拼接后的文件存在多个重复的元数据头,播放器只能识别第一个头对应的内容(前3秒),后续内容因结构错误无法正常解析,进而出现卡顿和无法跳转的情况。

直播流的片段是连续的,只需要在第一个片段中注入可seek的元数据,后续片段仅发送纯媒体内容即可。以下是具体修改方案:

1. 重构TsEBMLEngine类,维护跨片段状态

将解码器和阅读器改为实例属性,跟踪是否为第一个片段,避免重复生成元数据:

import videojs from "video.js/dist/video";
import { Buffer } from "buffer";
window.Buffer = Buffer;
import { Decoder, tools, Reader } from "ts-ebml";

class TsEBMLEngine {
    constructor() {
        this.chunkDecoder = new Decoder();
        this.chunkReader = new Reader();
        this.chunkReader.logging = false;
        this.chunkReader.drop_default_duration = false;
        this.isFirstChunk = true;
        this.metadataSize = 0;
    }

    convertStream = (data) => {
        return data.arrayBuffer().then((buffer) => {
            // 解码当前片段的EBML元素并累积到阅读器
            const elms = this.chunkDecoder.decode(buffer);
            elms.forEach((elm) => {
                this.chunkReader.read(elm);
            });

            if (this.isFirstChunk) {
                // 第一个片段:生成可seek的元数据,返回带修正头的完整Blob
                this.chunkReader.stop();
                const refinedMetadataBuf = tools.makeMetadataSeekable(
                    this.chunkReader.metadatas,
                    this.chunkReader.duration,
                    this.chunkReader.cues
                );
                this.metadataSize = this.chunkReader.metadataSize;
                const body = buffer.slice(this.metadataSize);
                const convertedData = new Blob([refinedMetadataBuf, body], { type: data.type });
                this.isFirstChunk = false;
                return convertedData;
            } else {
                // 后续片段:仅返回媒体内容(跳过重复的元数据部分)
                const body = buffer.slice(this.metadataSize);
                return new Blob([body], { type: data.type });
            }
        });
    }

    // 重置状态,用于新的录制会话
    reset() {
        this.chunkDecoder = new Decoder();
        this.chunkReader = new Reader();
        this.chunkReader.logging = false;
        this.chunkReader.drop_default_duration = false;
        this.isFirstChunk = true;
        this.metadataSize = 0;
    }
}

videojs.TsEBMLEngine = TsEBMLEngine;
export default TsEBMLEngine;

2. 修改timestampHandler与录制流程

确保在录制开始时重置引擎状态,并正确处理每个片段:

// 组件初始化时实例化引擎
this.converter = new TsEBMLEngine();

// 录制开始事件中重置状态(比如监听videojs-record的recordStart事件)
handleRecordStart() {
    this.converter.reset();
}

timestampHandler(player) {
    const { length: recordedDataLength } = player.recordedData;
    if (recordedDataLength === 0) return;

    const latestChunk = player.recordedData[recordedDataLength - 1];
    this.converter.convertStream(latestChunk).then((blob) => {
        blob.arrayBuffer().then(async (buffer) => {
            const bytes = new Uint8Array(buffer);
            let binary = '';
            for (let i = 0; i < bytes.length; i++) {
                binary += String.fromCharCode(bytes[i]);
            }
            await this.$backend.videoDataSendToServer({ bytes: window.btoa(binary), id: this.videoId });
        }).catch(error => {
            console.error('Blob转换失败:', error);
        });
    }).catch(error => {
        console.error('流处理失败:', error);
    });
}

关键注意事项

  • 后端写入逻辑:必须确保后端是追加写入到同一个文件,而非覆盖或创建新文件。
  • 元数据截断验证:如果videojs-record返回的后续片段仍包含重复元数据,需确认metadataSize的截取长度是否准确。
  • 录制收尾优化:直播结束后,可对最终文件做一次元数据修正(更新总时长和cue点),提升回放体验(非实时播放场景可选)。

内容的提问来源于stack exchange,提问作者Joseph Bermudez

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 11:45:28