本文主要介绍如何在 Web 端实现实时合唱的功能,Android 和 iOS 端实现教程参考:在线 K 歌。
在线体验 Demo: https://sdk-web-1252463788.cos.ap-hongkong.myqcloud.com/trtc/webrtc/v5/test/ntp/ktv/index.html#/ktv
- 本 Demo 是使用 Vite 构建的,Demo 使用本地 mp3 歌曲和歌词来模拟曲库的实现。歌曲名:“十年”。
// 安装依赖 npm install // 本地允许 npm run dev // 打包构建 Demo npm run build
- 本方案是无 UI 集成方案,SDK 提供实现实时合唱的核心功能,不提供 UI、曲库、房间管理、麦位控制等业务功能,您需要自行实现。
- 实时合唱场景中,业务上有三种身份:主唱、合唱、观众,主唱发起合唱及向房间定时同步歌曲进度;合唱收到合唱信令后,上麦can而对于 TRTC 来说,主唱和合唱都是主播身份,可以上麦;观众不可以上麦。
- Web 合唱实现流程和 Android 和 iOS 端基本一致,区别在于:
- 官网的 Android 和 iOS Demo,开始合唱信令是通过自定义消息来发送的,同步歌曲进度信令是通过 SEI 消息发送的。
- Web 将在 v5.6.0 版本支持自定义消息收发(预计 2024-5-17 发布),目前版本(v5.5.2-ktv)暂不支持。因此目前是通过 SEI 来收发信令,包括:开始合唱信令、同步歌曲进度信令。
先装 tag: ktv 这个版本,后续测试验收通过后,会发到正式版本。
npm install trtc-sdk-v5@ktv
- 创建两个 trtc 实例,以主播身份进房:一个推麦克风 + 拉合唱麦克风、一个推 BGM + 推 SEI。
import TRTC from 'trtc-sdk-v5'; import { CDNStreaming, PublishMode } from 'trtc-sdk-v5/plugins/cdn-streaming'; const trtc = TRTC.create({ enableChorus: true, plugins: [CDNStreaming] }); const trtcBGM = TRTC.create({ enableSEI: true, enableChorus: true}); trtc.on(TRTC.EVENT.REMOTE_AUDIO_AVAILABLE, event => { // 主唱只拉其他合唱的麦克风 // bgm 是主唱推的背景音乐、robot 是服务端混流回推到 trtc 房间的流。 if (!event.userId.includes('bgm') && !event.userId.includes('robot')) { trtc.muteRemoteAudio(event.userId, false); } }) await trtc.enterRoom({ sdkAppId, roomId, userId, userSig, scene: TRTC.TYPE.SCENE.LIVE, role: TRTC.TYPE.ROLE_ANCHOR, autoReceiveAudio: false, autoReceiveVideo: false, }); await trtcBGM.enterRoom({ sdkAppId, roomId, userId: `${userId}_bgm`, userSig, scene: TRTC.TYPE.SCENE.LIVE, role: TRTC.TYPE.ROLE_ANCHOR, autoReceiveAudio: false, autoReceiveVideo: false, });
- 同步 NTP 时间
// 进房前监听事件,在进房后,SDK 更新 ntp 时间成功后,会抛出此事件。 trtc.on(TRTC.EVENT.NETWORK_TIME_UPDATED, event => { const timestamp = trtc.getNetworkTime(); });
- 上麦打开麦克风
await trtc.startLocalAudio({ option: { echoCancellation: false, // 关闭 3A,合唱场景需引导主播佩戴耳机 noiseSuppression: false, autoGainControl: false, profile: "high", // high 是单声道高音质,high-stereo 是双声道高音质 }, })
- 点歌并获取到歌词信息
- 通过 SEI 发送开启合唱信令(合唱开始时间点 + 歌曲 ID)
function getBlackVideoTrackFromCanvas() { const canvas = document.createElement("canvas"); canvas.width = 64; canvas.height = 64; const ctx = canvas.getContext("2d"); setInterval(() => { ctx.fillStyle = "black"; ctx.fillRect(0, 0, 64, 64); }, 66); return canvas.captureStream(15).getVideoTracks()[0]; } // 推黑帧,用于发 SEI await trtcBGM.startLocalVideo({ option: { videoTrack: getBlackVideoTrackFromCanvas(), profile: { bitrate: 50 }, }, }); const data = { cmd: 'start_chorus', music_id: "", start_play_music_ts: trtc.getNetworkTime() + 3000, // 3s 后开始合唱 } // 播放 BGM setTimeout(() => {}, 3000); // 每隔 1s 发一次开始合唱信令,这样可以可以让合唱者,在合唱开始后,动态加入合唱 setInterval(() => { trtcBGM.sendSEIMessage(new TextEncoder().encode(JSON.stringify(data)).buffer) }, 1000);
- 调用 TRTC API 发起混流回推 TRTC 房间任务。用于给观众拉流,观众只拉混流回推 trtc 房间的流。
trtc.startPlugin("CDNStreaming", { target: { publishMode: PublishMode.PublishMixStreamToRoom, robotUser: { userId: `${userId}_robot`, roomId, }, }, encoding: { audioBitrate: 128, audioChannels: 2 }, mix: { videoLayoutList: [ { fixedVideoUser: { userId: `${userId}_bgm`, roomId, }, width: 64, height: 64, locationX: 0, locationY: 0, fixedVideoStreamType: TRTC.TYPE.STREAM_TYPE_MAIN, zOrder: 1, }, ], }, });
- 合唱开始后,通过 SEI 定时(200ms)发送歌曲进度信息(当前歌曲播放进度时间、歌曲 ID 等)。
- 歌曲进度修正。由于 BGM 播放可能由于各种原因导致播放进度与预期不符,因此需要定时检查 BGM 播放进度,若与预期进度的偏差过大(例如 50ms),则手动调整 BGM 播放进度。
function fixBGMProgress() { const currentTime = 0; // 从业务侧曲库获取当前 BGM 播放进度 const estimatedTime = trtc.getNetworkTime() - startPlayMusicTS; // 预期的 BGM 播放进度 = 当前时间 - 合唱开始时间 const offset = Math.abs(currentTime - estimatedTime); if (estimatedTime >= 0 && offset > 50) { // BGM 播放进度与预期进度偏差超过 50ms,手动调整 BGM 播放进度 } }
- 创建一个 trtc 实例,以主播身份进房:推麦克风 + 接收 SEI + 拉合唱麦克风
const trtc = TRTC.create({ enableChorus: true }); trtc.on(TRTC.EVENT.REMOTE_AUDIO_AVAILABLE, event => { // 只拉其他合唱的麦克风 if (!event.userId.includes('bgm') && !event.userId.includes('robot')) { trtc.muteRemoteAudio(event.userId, false); } }) await trtc.enterRoom({ sdkAppId, roomId, userId, userSig, scene: TRTC.TYPE.SCENE.LIVE, role: TRTC.TYPE.ROLE_ANCHOR, autoReceiveAudio: false, });
- 同步 NTP 时间
- 上麦打开麦克风
- 接收 SEI 消息:
- 收到合唱信令时,则在约定的时间点之前,根据歌曲 ID 预加载资源,在约定的时间点开始在本地播放歌曲,并且用本地播放歌曲的进度同步歌词进度。
- 收到歌曲进度信息则忽略。
- 音频拉流时,只拉其他合唱的麦克风,不拉 BGM 及混流回推 robot 的音频流。
- 歌曲进度修正。由于 BGM 播放可能由于各种原因导致播放进度与预期不符,因此需要定时检查 BGM 播放进度,若与预期进度的偏差过大(例如 50ms),则手动调整 BGM 播放进度。
- 创建一个 trtc 实例,以观众身份进房:接收混流回推 robot 的音频流 及 SEI 消息
- 同步 NTP 时间
- 音频拉流只拉 robot,不拉其他合唱的麦克风及 BGM
- 接收 SEI 消息:
- 收到合唱信令时,忽略,因为观众不能上麦合唱。
- 收到歌曲进度信息,则根据歌曲进度信息,更新业务侧歌词进度。