系统拆解直播答题场景的技术需求、架构设计和核心功能实现

一、引言

2018年,直播答题以”百万英雄””冲顶大会”为代表迅速席卷全网。主持人出题,数十万甚至上百万观众同时在线抢答,12道闯关题,全部答对者瓜分奖金。一天之内,这个模式创造了互联网产品爆发速度的新纪录。

但爆发过后,技术债务迅速暴露。复盘当时行业遇到的共性问题:题目与画面不同步、倒计时感知延迟、大规模并发下的消息风暴是三个致命伤。观众看到的题目出现时间不一致,提交答案的服务端被瞬间冲垮,倒计时在弱网下偏差超过2秒,这些体验问题直接摧毁了用户信任。

此后几年,实时音视频技术的持续演进,特别是 SEI(媒体补充增强信息)和超低延迟直播能力的成熟为直播答题提供了根本性的技术解法。题目数据可以嵌入视频帧实现帧级同步,超低延迟直播将端到端延迟压到亚秒级,CDN大规模分发承载百万并发不再是瓶颈。

本文从工程实践角度,系统拆解直播答题场景的技术需求、架构设计和核心功能实现,重点聚焦 SEI 实现题目与画面精准同步、CDN 大规模分发、答案提交的并发峰值处理,以及弹幕消息的高并发优化。

二、场景技术需求拆解

2.1 核心需求

需求项 详细说明 优先级
题目精准同步 所有观众必须在同一时刻看到题目出现。题目晚出现1秒,对百万观众就意味着不公平 必需
倒计时精准同步 10秒倒计时在所有终端严格一致,不能出现”我的倒计时还有2秒,别人显示已结束” 必需
百万级并发拉流 同时在线观众可达百万级别,需要 CDN 大规模分发承载带宽压力 必需
答案提交可靠性 观众提交的答案必须可靠送达服务端,不能因网络抖动导致”我明明答对了系统没收到” 必需
低延迟画面传输 主持人出题、讲解的实时画面不能有明显延迟,否则互动体验丧失 必需
答题结果同步 答题结束后,1-2秒内向所有观众公布正确答案、个人得分及晋级状态 必需
弹幕互动 观众答题期间可以发弹幕聊天,百万并发下的弹幕通道需要不限频 加分
云端录制与回放 整场答题需要录制存档,供回放和审核 加分

2.2 技术难点

  • 题目画面与音频同步:如果题目信息通过独立的信令通道下发,与主持人口播”请看题”可能出现1-3秒偏差。信令通道与视频传输路径不同,延迟差异是结构性的,无法通过调参解决。
  • 惊群效应:每道题截止前300ms,百万观众同时提交答案。这不是普通秒杀的”抢购”,而是所有人在同一毫秒级窗口内触发写操作,服务端瞬间QPS可达普通场景的数千倍。
  • 倒计时的时间权威性:客户端倒计时只是视觉辅助,真正的截止时间必须以服务端时钟为准。弱网下客户端时钟与服务端偏差可能超过1秒,用客户端时间切片会引发公平性争议。
  • SEI丢失风险:SEI 在视频流传输过程中可能因网络丢帧而丢失。不同观众的丢帧率不同,部分观众可能错过题目 SEI,需要冗余策略和兜底通道。

三、RTC 技术选型

3.1 为什么不用 HTTP 轮询

早期直播答题实现中,有人尝试用 HTTP 长轮询下发题目,客户端每500ms轮询一次接口获取题目状态。问题明显:

  • 十万客户端同时轮询 = 自造DDoS
  • 轮询间隔内的时间差导致题目显示不一致(早轮询到的观众先行看到题目)
  • 无法解决题目与视频画面的对齐问题

WebSocket方案解决了轮询效率问题,但依然无法解决时序对齐。WebSocket是独立于视频的数据通道,延迟波动不可控。题目早到就是”提前偷跑”,晚到就是”画面已出但题目没跟上”。

3.2 核心技术指标

指标 目标值 说明
题目同步误差 < 400ms 题目出现时所有端的时间差异
视频端到端延迟 < 1000ms 主持人画面到观众端的延迟(超低延迟直播)
答案消息可靠性 99.99% 答案提交不丢失
CDN并发 100万+ 同时在线拉流
弹幕消息吞吐 20条/秒/人 单用户弹幕发送频率上限
弱网抗丢包 80%丢包不掉线 保障极端弱网场景的基本可用性

3.3 ZEGO Express SDK 能力匹配

即构科技实时音视频 SDK(ZEGO Express SDK) 提供直播答题场景的三项核心能力:

SEI(媒体补充增强信息):将题目数据直接嵌入H.264/H.265视频流的补充增强信息字段,与视频帧在同一个媒体通道传输。拉流端playerRecvSEI回调触发时,对应的视频帧已到达解码器——数据与画面天然对齐。SEI发送限制为4096字节/次、30次/秒,足以覆盖题目JSON的精简传输,且冗余发送策略可降低丢帧导致的漏题风险。

超低延迟直播(600-1000ms):基于ZEGO自研MSDN(海量有序数据网络)的私有协议分发,不同观众间同步误差<400ms。相比传统CDN直播2-5秒的延迟,超低延迟直播将延迟压缩到亚秒级,配合CDN旁路推流实现百万并发分发。

旁路转推CDN:RTC流推送到ZEGO边缘节点后,通过服务端API或客户端接口旁路转推到指定CDN地址。支持同步推多家CDN,实现Multi-CDN容灾。走CDN的观众拉取CDN流,核心互动观众走超低延迟直播,分层分发策略大幅降低带宽成本。

辅助消息能力:Express SDK内置三种消息通道——广播消息(可靠,10次/秒,1024字节)、弹幕消息(不限频,不可靠)、自定义信令(可靠,200条/秒单人/10条/秒广播)。配合即构即时通讯SDK(ZIM SDK)的消息类型(CommandMessage信令/BarrageMessage弹幕/CustomMessage自定义),可灵活组合覆盖答题信令和弹幕互动全场景。

四、系统架构设计

4.1 整体架构

系统分为四层:客户端层、RTC网络层、业务服务层、基础设施层。

┌──────────────────────────────────────────────────────────────┐
│                         客户端层                               │
│  ┌───────────────┐  ┌───────────────┐  ┌──────────────────┐  │
│  │   主持人端      │  │  观众端 Web    │  │  观众端 App       │  │
│  │  (推流+SEI)    │  │  (拉流+答题)   │  │  (拉流+答题)      │  │
│  └───────┬───────┘  └───────┬───────┘  └────────┬─────────┘  │
├──────────┼──────────────────┼───────────────────┼────────────┤
│         │       RTC 网络层(ZEGO Express + MSDN)             │
│         │  ┌───────────────────────────────────┐             │
│         └─►│  SEI嵌入 → 推流 → 混流转码         │            │
│            │  超低延迟分发 → CDN旁路转发          │            │
│            │  消息通道 → 广播/弹幕/自定义信令     │            │
│            └───────────────────────────────────┘             │
├──────────────────────────────────────────────────────────────┤
│                        业务服务层                              │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────┐   │
│  │  答题引擎      │  │  题管理服务    │  │  排行榜服务        │   │
│  │  (计时/判分/   │  │  (题库/出题    │  │  (积分/晋级/       │   │
│  │   淘汰逻辑)    │  │   /统计)      │  │   排名)            │   │
│  └──────────────┘  └──────────────┘  └──────────────────┘   │
│  ┌──────────────┐  ┌──────────────────────────────────┐     │
│  │  用户服务      │  │  数据分析与监控                    │     │
│  │  (登录/鉴权/   │  │  (埋点/漏斗/画像/实时Dashboard)    │     │
│  │   档案)       │  │                                   │     │
│  └──────────────┘  └──────────────────────────────────┘     │
├──────────────────────────────────────────────────────────────┤
│                        基础设施层                              │
│  CDN分发 (Multi-CDN)  │  Redis (缓存/实时排名)                │
│  MQ 削峰 (Kafka/RocketMQ)  │  MySQL/ClickHouse (存储/分析)    │
│  监控告警 (Prometheus + Grafana)  │  星图 (全链路质量监控)     │
└──────────────────────────────────────────────────────────────┘

主持人端:负责音视频采集、编码、推流。管理员在题目管理后台触发出题时,主持人端将题目数据作为SEI嵌入视频流。主持人端可使用OBS等专业推流工具配合Express SDK的自定义推流能力。

观众端:拉流观看直播,解析SEI获取题目数据,通过Express广播消息/ZIM信令提交答案,接收结果广播。Web端为主入口,App端为辅。走超低延迟拉流获得低延迟体验,网络差的观众自动降级到CDN拉流。

RTC网络层:ZEGO MSDN负责任意节点间的实时媒体流和信令传输。主持人的视频流推送到边缘节点后,一路走超低延迟直播分发,一路旁路转推到CDN。SEI数据随视频帧在两条路径中均可传输。

业务服务层:独立的答题业务逻辑,包括题目管理、计时引擎、判分逻辑、排行榜计算。通过服务端API调用ZEGO的CDN转推任务管理接口,实现推流分发策略的动态调整。

4.2 数据流设计

数据类型 传输方式 可靠性 频率 流向
主持人音视频 RTC推流 → 混流 → CDN/超低延迟分发 实时,允许策略性丢帧 持续 主持人 → MSDN → 观众
题目数据 SEI嵌入视频帧 允许冗余(30次/秒,<4096字节) 每题冗余发送3-5次 主持人 → 观众
倒计时同步 Express sendBroadcastMessage 可靠广播 每秒1次 业务服务器 → 观众
观众答案 Express sendBroadcastMessage / ZIMCustomMessage 可靠有序 每题1次/人,峰值100万+ 观众 → 业务服务器
结果公布 Express sendBroadcastMessage 可靠广播 每题1次 业务服务器 → 观众
弹幕聊天 Express sendBarrageMessage / ZIMBarrageMessage 不可靠,允许丢失 不限频(20条/秒/人) 观众 ↔ 观众
CDN转推管理 服务端API (CreateCDNTransferRule) 高可靠 推流开始/结束时 业务服务器 → ZEGO服务端

4.3 消息通道选型策略

直播答题存在三类消息需求,各自对可靠性和频率的要求差异很大,不能统一处理:

消息场景 推荐通道 选型理由
题目同步 Express sendSEI 与视频帧物理绑定,天然同步,WebSocket等带外方案无法替代
倒计时同步 Express sendBroadcastMessage 可靠广播,房间内全员可达,每秒1次频率完全满足
答案提交 Express sendBroadcastMessage / ZIMCustomMessage 可靠有序,保证答案不丢不乱序
结果公布 Express sendBroadcastMessage 可靠广播,低频高可靠
弹幕聊天 Express sendBarrageMessage 或 ZIMBarrageMessage 不限频(20条/秒/人),允许部分丢失
用户状态变更 ZIMCustomMessage 可靠有序,适合状态同步

核心原则:与画面严格同步的信息走SEI媒体通道,需要可靠送达的答题数据走广播消息/自定义信令,高吞吐低价值的弹幕走弹幕消息通道。三者互不干扰,各司其职。

五、核心功能实现

5.1 SEI 实现题目与画面精准同步

SEI(Supplemental Enhancement Information)是H.264/H.265编码标准定义的补充增强信息机制,允许在视频码流中嵌入自定义数据。这些数据与视频帧物理绑定——解码器必须解析完一个完整帧才会输出画面,因此SEI数据到达的时间点就是对应画面即将渲染的时间点。

在直播答题中,SEI的使用方式是将题目JSON嵌入视频流,由观众端的playerRecvSEI回调提取并触发题目渲染。相比WebSocket等带外通道的根本优势在于:SEI与视频帧在同一个比特流中,不存在路径差异导致的时序错位。

冗余策略:SEI可能因网络丢帧而丢失。解决方式是同一题目的SEI数据冗余发送3-5次(间隔100ms),大幅降低单帧丢失导致漏题的概率。观众端通过qid去重,确保同一题目只被渲染一次。

兜底策略:题目数据同时通过广播消息下发。CDN旁路场景中第三方播放器不支持SEI解析,观众端从广播消息获取题目内容,以SEI中的题目时间戳校准显示时机。两条通道互为备份。

推流端:发送带SEI题目数据的视频流

// ===== 主持人推流端 =====
const zg = new ZegoExpressEngine(appID, server);

// 登录房间
await zg.loginRoom(roomID, token, { userID, userName: 'host' });
const localStream = await zg.createZegoStream();

// 推流配置:开启SEI能力
await zg.startPublishingStream(publishStreamID, localStream, {
    roomID,
    isSEIStart: true,   // 必须:开启SEI数据发送
    SEIType: 5           // UserUnregistered类型,保证第三方解码器兼容
});

// 监听推流状态
zg.on('publisherStateUpdate', async (result) => {
    if (result.state === 'PUBLISHING') {
        console.log('推流已就绪,SEI功能可用');
    }
});

/**
 * 发送题目SEI
 * @param {Object} question - 题目数据
 *   { qid, type, title, options, round, totalRounds, startTime }
 *
 * SEI限制:
 * - 单帧数据限制4096字节
 * - 频率不超过30次/秒
 * - 建议同一题目冗余发送3-5次,间隔100ms,降低丢帧风险
 */
function sendQuestionSEI(question) {
    const jsonStr = JSON.stringify(question);
    const encoder = new TextEncoder();
    const seiData = encoder.encode(jsonStr);

    if (seiData.byteLength > 4096) {
        console.error('SEI数据超出4096字节限制');
        return;
    }

    const streamID = 'host_stream_001';

    // 冗余发送5次(间隔100ms),确保至少一次到达
    for (let i = 0; i < 5; i++) {
        setTimeout(() => {
            zg.sendSEI(streamID, seiData);
        }, i * 100);
    }

    console.log(`[SEI] 题目已嵌入视频流: ${question.qid}`);
}

拉流端:解析SEI获取题目数据

// ===== 观众拉流端 =====
const zg = new ZegoExpressEngine(appID, server);
let currentQuestionId = null; // 用于SEI去重

// 在拉流前注册SEI回调(必须在拉流前注册,否则会漏掉首批SEI)
zg.on('playerRecvSEI', (streamID, uintArray) => {
    // SEI数据前4字节为媒体侧信息类型标识
    // 1004 = payload type 5 (UserUnregisteredSEI)
    // 1005 = payload type 243 (UserDataRegisteredSEI)
    let mediaSideInfoType = 0;
    mediaSideInfoType = uintArray[0] << 24;
    mediaSideInfoType |= uintArray[1] << 16;
    mediaSideInfoType |= uintArray[2] << 8;
    mediaSideInfoType |= uintArray[3];

    // 跳过前4字节,提取有效载荷
    const payloadBytes = uintArray.slice(4);

    try {
        const decoder = new TextDecoder();
        const jsonStr = decoder.decode(payloadBytes);
        const question = JSON.parse(jsonStr);

        // SEI去重:相同qid只处理一次
        if (currentQuestionId !== question.qid) {
            currentQuestionId = question.qid;
            handleNewQuestion(question);
        }
    } catch (e) {
        console.error('SEI解析失败:', e);
    }
});

// 登录房间并开始超低延迟拉流
await zg.loginRoom(roomID, token, { userID, userName: 'player' });
const remoteStream = await zg.startPlayingStream('host_stream_001', {
    resourceMode: 2,    // 2 = 超低延迟直播模式(L3)
    isSEIStart: true    // 必须:开启SEI解析
});

/**
 * 处理新题目:渲染题目面板 + 启动本地倒计时
 * 本地倒计时仅为视觉展示,最终截止判定以服务端为准
 */
function handleNewQuestion(data) {
    renderQuestionPanel({
        round: data.round,
        totalRounds: data.totalRounds,
        title: data.title,
        options: data.options
    });
    startLocalCountdown(data.startTime);
}

5.2 倒计时精准同步

倒计时是答题体验的核心触点。各端本地时钟天然存在偏差(用户手机时间未必准确),直接用客户端时间计算倒计时会引发公平性问题。

设计原则:服务端为唯一计时源。服务端按每秒1次的频率通过广播消息下发剩余秒数(基于服务端时钟计算),客户端收到后直接使用该值渲染,同时计算ServerTimeOffset用于自身时钟持续校准。即使某一帧广播丢失,下一帧到达时自动修正。

// ===== 服务端(Node.js 伪代码):倒计时引擎 =====
class AnswerTimerEngine {
    constructor(roomID) {
        this.roomID = roomID;
        this.currentRound = 0;
        this.questionEndTime = 0;
        this.answerWindow = 10000;  // 每题10秒答题窗口
        this.timerInterval = null;
    }

    /** 开始新一题倒计时 */
    startRound(roundNum) {
        this.currentRound = roundNum;
        this.questionEndTime = Date.now() + this.answerWindow + 500; // +500ms容错

        this.timerInterval = setInterval(() => {
            const remaining = Math.max(0, this.questionEndTime - Date.now());
            const seconds = Math.ceil(remaining / 1000);

            // 每秒广播一次倒计时(服务端时钟为权威时间源)
            sendBroadcastToRoom(this.roomID, JSON.stringify({
                type: 'COUNTDOWN',
                round: this.currentRound,
                remainingSeconds: seconds,
                serverTimestamp: Date.now() // 客户端用于时间校准
            }));

            if (remaining <= 0) {
                this.endRound();
            }
        }, 1000);
    }

    endRound() {
        clearInterval(this.timerInterval);
        this.questionEndTime = 0;
        sendBroadcastToRoom(this.roomID, JSON.stringify({
            type: 'ROUND_END',
            round: this.currentRound
        }));
    }
}
// ===== 观众端:接收倒计时并持续校准时钟 =====
let serverTimeOffset = 0; // 客户端 → 服务端的时钟偏差(ms)

zg.on('IMRecvBroadcastMessage', (roomID, chatData) => {
    const msg = JSON.parse(chatData[0].message);

    if (msg.type === 'COUNTDOWN') {
        // 更新时钟偏差(持续校准,抵抗漂移)
        serverTimeOffset = msg.serverTimestamp - Date.now();

        // 直接渲染服务端下发的剩余秒数
        updateCountdownUI(msg.remainingSeconds);

        if (msg.remainingSeconds <= 3) {
            highlightCountdown(true); // 最后3秒强调动画
        }
    }

    if (msg.type === 'ROUND_END') {
        disableAnswerButtons();
        clearCountdownUI();
    }
});

5.3 答案提交与并发峰值处理

直播答题的答案提交是典型的”脉冲式并发峰值”——10秒答题窗口内提交分布不均匀,最后2秒形成陡峭尖峰,倒计时归零瞬间达到顶点。百万观众同时提交意味着秒级内百万量级的写请求。

全链路削峰策略
1. 前端层:倒计时末尾随机延迟提交(在最后200ms内随机分散),削散瞬时尖峰
2. API网关层:只做轻量Token鉴权和格式校验,校验通过即投递MQ并返回”已接收”
3. 消息队列层:Kafka/RocketMQ作为缓冲,消费者异步批量拉取判分
4. 判分引擎:批量消费,以客户端校正后的时间戳判定答题有效性(而非服务端收包时间)

// ===== 观众端:答案提交(含重试和本地缓存兜底) =====
class AnswerSubmitter {
    constructor() {
        this.submitted = false;
        this.maxRetries = 3;
    }

    async submitAnswer(answerIndex) {
        if (this.submitted) return; // 每题只提交一次
        this.submitted = true;

        const payload = JSON.stringify({
            type: 'ANSWER_SUBMIT',
            qid: currentQuestionId,
            answer: answerIndex,
            clientTimestamp: Date.now(),
            serverTimeOffset: serverTimeOffset // 服务端用于校准提交时间
        });

        // 带指数退避的重试(覆盖弱网场景)
        for (let i = 0; i < this.maxRetries; i++) {
            try {
                const result = await zg.sendBroadcastMessage(roomID, payload);
                if (result) {
                    this.showSubmitSuccess();
                    return;
                }
            } catch (error) {
                console.warn(`答案提交失败,第${i + 1}次重试:`, error);
                await new Promise(r => setTimeout(r, 200 * Math.pow(2, i)));
            }
        }

        // 重试耗尽,本地缓存 + 网络恢复后重传
        this.cacheAnswerLocally(payload);
    }

    cacheAnswerLocally(payload) {
        const cached = JSON.parse(localStorage.getItem('quiz_answers') || '[]');
        cached.push({ ...JSON.parse(payload), cachedAt: Date.now() });
        localStorage.setItem('quiz_answers', JSON.stringify(cached));
        window.addEventListener('online', () => this.resubmitCached());
    }
}
// ===== 服务端:判分逻辑 =====
// 架构:客户端 → API Gateway → MQ(Kafka) → 判分引擎(Batch Consumer)
//
// 关键点:
// - API Gateway 仅做格式校验,合法消息直接投MQ即返回"已接收"
// - 判分引擎以 (clientTimestamp + serverTimeOffset) 判定答题有效性
// - 留500ms容错窗口,覆盖时钟漂移和网络传输的边界情况

function checkAnswer(userAnswer, question) {
    // 以客户端校正后的时间为准判定答题窗口
    const correctedTime = userAnswer.clientTimestamp + userAnswer.serverTimeOffset;
    if (correctedTime > question.endTime + 500) {
        return { status: 'expired' };
    }

    const isCorrect = userAnswer.answer === question.correctIndex;
    return {
        status: 'accepted',
        isCorrect,
        score: isCorrect ? calculateScore(correctedTime - question.startTime) : 0
    };
}

5.4 CDN 大规模分发与超低延迟直播

百万观众不能全部进入RTC房间(成本巨大且房间人数有上限),通过CDN分层分发是唯一解。

分层策略:核心互动观众走超低延迟直播(ZEGO私有协议,延迟600-1000ms),纯观看观众走CDN旁路(传统RTMP/FLV分发)。主持人始终只推一路RTC流,由云侧完成转推和分发调度。

// ===== 主持人端:推流 + 启动CDN旁路 =====
// 1. RTC推流(走ZEGO MSDN网络)
await zg.startPublishingStream(publishStreamID, localStream, {
    roomID,
    isSEIStart: true
});

// 2. 动态添加CDN转推地址
const cdnUrl = 'rtmp://cdn-provider.example.com/live/quiz_room_001';
await zg.addPublishCdnUrl(publishStreamID, cdnUrl);
console.log('CDN旁路推流已启动:', cdnUrl);

// 3. 停止时移除CDN转推
// await zg.removePublishCdnUrl(publishStreamID, cdnUrl);

服务端也可以通过API管理转推任务,适用于需要动态调度CDN厂商的场景:

// ===== 服务端:通过API管理CDN转推 =====
// 调用 ZEGO 服务端 API: CreateCDNTransferRule
// GET https://rtc-api.zego.im/?Action=CreateCDNTransferRule&...
// 参数包括 AppId, StreamId, RuleList[TargetUrl, ContentProtect, IsTest]

// 支持同时转推至多家CDN,实现Multi-CDN容灾和成本优化
// 停止转推:DeleteCDNTransferRule
// ===== 观众端:超低延迟拉流 =====
async function startLowLatencyPlay(hostStreamID) {
    try {
        const remoteStream = await zg.startPlayingStream(hostStreamID, {
            resourceMode: 2,        // 2 = 超低延迟直播(L3)
            isSEIStart: true        // 开启SEI解析
        });
        console.log('超低延迟拉流成功,预期延迟600-1000ms');
        return remoteStream;
    } catch (error) {
        console.error('拉流失败:', error);
        // 降级:走CDN拉流
        const stream = await zg.startPlayingStream(hostStreamID, {
            resourceMode: 0         // CDN模式拉流
        });
        return stream;
    }
}

5.5 弹幕消息的高并发处理

百万观众发弹幕的并发量远高于普通直播间。弹幕的特点是”允许丢失、要求不限频”——用户发送弹幕时不能因为服务端压力被卡住,丢失几条弹幕不影响体验。Express SDK的sendBarrageMessage专为此场景设计:不保证可靠,不限频,服务器不做持久化。

// ===== 观众端:发送弹幕 =====
async function sendBarrage(text) {
    // 本地敏感词粗筛(避免明显的违规内容直接发送)
    if (containsSensitiveWord(text)) {
        showToast('弹幕内容不合规');
        return;
    }

    try {
        // sendBarrageMessage:不限频,不保证可靠,适合弹幕场景
        // QPS上限:20次/秒/人,覆盖最快手速
        await zg.sendBarrageMessage(roomID, text);
    } catch (error) {
        // 频率超限或发送失败时静默处理,不阻塞用户操作
        console.log('弹幕发送失败(静默处理)');
    }
}

// 接收弹幕:批量处理 + 客户端渲染限流
zg.on('IMRecvBarrageMessage', (roomID, barrageData) => {
    // barrageData为数组,批量回调减少事件触发频率
    barrageData.forEach(item => {
        addBarrageToWall({
            userID: item.fromUser.userID,
            userName: item.fromUser.userName,
            content: item.message,
            timestamp: item.sendTime
        });
    });
});

// 弹幕墙渲染限流:屏幕最多同时显示30条弹幕
// 超出部分进入队列,按固定间隔出队渲染
// 避免DOM节点爆炸导致页面卡顿
function addBarrageToWall(barrage) {
    barrageQueue.push(barrage);
    if (activeBarrages.length >= 30) {
        // 移除最早的一条弹幕
        const oldest = activeBarrages.shift();
        oldest.el.remove();
    }
    renderNextBarrage();
}

六、关键问题与优化策略

问题 根因分析 优化策略
题目与画面不同步 题目通过独立信令通道下发,与视频传输路径不同,延迟差异1-3秒且不可控 SEI嵌入视频帧实现帧级同步;同一条SEI冗余发送3-5次(间隔100ms)弥补丢帧;qid去重避免重复渲染;广播消息兜底(CDN场景)
答案提交惊群效应 倒计时归零瞬间百万观众同时提交答案,形成数万倍常态的QPS脉冲 全链路削峰:前端在截止前200ms内随机延迟提交 → API网关校验格式后直接投MQ即返回 → 消费者批量拉取异步判分;判分以客户端校正时间戳为准,留500ms容错
百万并发带宽成本 单路高码率流直推所有观众,带宽成本与观众数线性正比 码率阶梯:核心互动走超低延迟直播(600-1000ms),纯观看走CDN(成本降低60%+);启用分层编码SVC,观众端自适应码率;Multi-CDN多路比价
弱网下答案提交失败 弱网环境单次提交超时或失败,此时倒计时仍在走,用户焦虑感强烈 内置3次指数退避重试;失败后本地localStorage缓存,监听online事件异步重传;服务端以校正后的客户端时间戳判定答题有效性,不受重传延迟影响
观众端倒计时不一致 各端本地时钟天然存在偏差,依赖客户端计时会导致不同观众看到不同剩余时间 服务端为唯一计时源,每秒广播剩余秒数;客户端以ServerTimeOffset持续校准本地时钟;即使某帧广播丢失,下一帧自动修正
CDN场景SEI丢失 CDN传输过程中重新封装视频流,或第三方播放器不支持SEI解析,导致题目数据无法通过SEI获取 不依赖SEI作为唯一通道:题目JSON同时通过广播消息下发;CDN观众走广播获取题目内容,以SEI中的题目时间戳为校准基准;两条通道互为备份

七、场景延伸与扩展玩法

7.1 AI 智能出题与难度自适应

答题系统根据实时数据动态调整题目难度。技术链路:题库系统预标注难度等级 → AI引擎根据实时正确率、晋级人数、历史难度曲线动态选择下一题 → 题目数据通过服务端API注入到SEI下发流程。难度自适应策略:连续3题正确率>80%自动升档,<30%自动降档,保证挑战性和观众留存率之间的平衡。

7.2 语音答题模式

观众通过语音说出答案替代手动点击,释放双手。技术链路:Express SDK采集观众音频流 → 云端ASR实时转写 → 文本匹配答案。关键优化点:利用Express SDK的AI降噪预处理音频流以提升ASR准确率;语音采集窗口严格对齐倒计时(startRound时开始录音,ROUND_END时停止);ASR延迟控制在500ms以内以保证答题体验。

7.3 企业定制品牌直播间

为企业客户提供白标答题直播方案。通过Express SDK的云端混流能力,将企业Logo、品牌色、营销引导位叠加到视频画面上。混流输入源包括:主持人画面、题目卡片(静态图片/动态HTML转视频)、品牌素材。混流模板通过服务端StartMixerTask API配置,自定义输入源位置、大小和层级,输出单一答题直播流。配合云端录制,答题回放可二次分发为品牌营销内容。

7.4 答题数据分析与观众画像

每题正确率、答题耗时分布(点击时间散点图)、观众留存曲线(第几题开始加速流失)、地域/设备维度的答对率差异等数据,通过客户端埋点+服务端日志采集到ClickHouse/Elasticsearch,通过Grafana实时Dashboard呈现。数据分析输出两个维度价值:对内优化题库难度曲线和出题策略;对外提供品牌赞助商的观众画像报告(地域、年龄、兴趣偏好)。

7.5 虚拟主持人 + 数字人出题

AI驱动的虚拟主持人替代真人,实现7×24小时无人轮播答题。技术链路:题目脚本通过TTS生成语音 → ZEGO 数字人 API 驱动唇形表情和动作 → 合成画面作为推流源。12道题编排完成后,答题流程完全自动化。虚拟主持人可支持品牌形象定制,进一步降低运营成本和扩展企业直播间差异化体验。

八、总结

  1. 画面与题目的同步是直播答题的技术命门。SEI(媒体补充增强信息)将题目数据嵌入视频帧编码流,与画面物理绑定、帧级同步,这是WebSocket等带外通道无法替代的。冗余发送(3-5次/题)+ qid去重 + 广播消息兜底,三管齐下确保题目不丢、不重、不延迟。
  2. 百万并发必须分层分发。核心互动观众走超低延迟直播(600-1000ms、同步误差<400ms),纯观看观众走CDN旁路承载大规模分发。主持人始终只推一路RTC流,云侧混流和分发自动调度,推流端零负担。
  3. 答案提交的并发峰值需要全链路削峰。前端随机延迟提交避免瞬时集中 → API网关轻量校验后投MQ即返 → 消息队列解耦 → 消费者批量异步判分。以客户端校正时间戳判定答题有效期(500ms容错),不用服务端收包时间,弱网用户的延迟答案也能被公平处理。
  4. 消息通道选型遵循”SEI同步、可靠信令传答案、弹幕不限频”的三角原则。Express SDK内置三种消息通道各司其职:SEI走媒体通道保证画面同步,广播消息可靠有序保证答题数据不丢次序不乱,弹幕消息不限频容忍丢失支撑百万级聊天互动。

技术选型一句话总结:Express SDK的SEI+超低延迟直播+CDN旁路推流解决”看”的问题,Express内置消息通道/ZIM SDK解决”答”的问题,四者组合即可覆盖直播答题从推流到分发、从同步到互动的完整技术链路,无需自建复杂的RTC基础设施。


本文技术实现基于 ZEGO Express Web SDK 2.16.0+,SEI功能需浏览器 Chrome 87+ / Safari 15.4+ / Firefox 117+ 版本。CDN推流和超低延迟直播功能需在 ZEGO 控制台开通后方可使用。

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐