视频混流
功能说明
本文介绍如何使用 VideoMixer 插件将不同的输入源混合成一路轨道。

前提条件
TRTC Web SDK 版本 >= 5.12.0。
兼容性
浏览器 | 兼容性 |
桌面端 Chrome | ✅ |
桌面端 Safari | ✅ |
桌面端 Firefox | ✅ |
桌面端 Edge | ✅ |
移动设备 | ❌ |
实现步骤
安装 VideoMixer 插件
import { VideoMixer } from 'trtc-sdk-v5/plugins/video-effect/video-mixer';let trtc = TRTC.create({ plugins: [VideoMixer] });
启动 VideoMixer 插件
await trtc.startPlugin('VideoMixer', {view: 'local_preview_div', // 预览canvasInfo: {width: 1920,height: 1080},camera: [{id: 'camera1',layout: {x: 0,y: 0,width: 640,height: 480,zIndex: 1}}]// ...});
启动插件时,必须设置画布的宽度和高度,其他参数可选。
混入各种输入源
摄像头
您可以添加摄像头输入源。混流插件可以控制摄像头的采集,通过
camera 参数数组传入一个带有唯一 id 的对象来表示用于混流的摄像头采集源。await trtc.updatePlugin('VideoMixer', {camera: [{id: 'camera1',profile: { width: 640, height: 480, frameRate: 15 },layout: {x: 100,y: 0,width: 640,height: 480,zIndex: 1}}]});
如需更新摄像头参数,传入相同的 id 并更新其他参数即可。
await trtc.updatePlugin('VideoMixer', {camera: [{id: 'camera1',layout: {x: 500,y: 500,width: 640,height: 480,zIndex: 1,rotation: 90,mirror: true}}]});
要关闭某个摄像头,只需将该对象从数组中移除。
await trtc.updatePlugin('VideoMixer', { camera: [] });
如需临时隐藏摄像头而不实际关闭,请使用
hidden 参数。其他输入源同样适用。await trtc.updatePlugin('VideoMixer', {camera: [{id: 'camera1',layout: {// ...hidden: true,},},],});
屏幕共享
您可以添加屏幕共享输入源。该插件可以控制屏幕共享采集,通过
screen 参数数组传入一个带有唯一 id 的对象来表示用于混流的屏幕共享采集源。await trtc.updatePlugin('VideoMixer', {screen: [{id: 'screen1',profile: { width: 1920, height: 1080, frameRate: 15 },layout: {x: 0,y: 0,width: 1920,height: 1080,zIndex: 0,}}]});
更新屏幕共享参数:
await trtc.updatePlugin('VideoMixer', {screen: [{id: 'screen1',layout: {x: 100,y: 100,width: 1000,height: 500,zIndex: 2,rotation: 180,mirror: true}}]});
停止屏幕共享,将对象从数组中移除。
await trtc.updatePlugin('VideoMixer', {screen: []});
文字
您可以添加文字输入源。通过
text 参数数组传入一个带有唯一 id 的对象来表示用于混流的文字源。await trtc.updatePlugin('VideoMixer', {text: [{id: 'text1',content: '换行\n测试',font: 'bold 60px SimHei',color: 'red',layout: {x: 200,y: 300,width: 300,height: 150,zIndex: 6,}}]});
注意:
如果文字内容超出布局区域,超出的部分将被裁剪。应用端应根据文字内容调整布局的宽度和高度。
更新文字参数:
await trtc.updatePlugin('VideoMixer', {text: [{id: 'text1',content: 'TRTC🥳',font: 'bold 120px Georgia',color: 'blue',layout: {x: 100,y: 200,width: 600,height: 150,zIndex: 7,mirror: true}}]});
删除文字:
await trtc.updatePlugin('VideoMixer', {text: []});
图片
您可以添加图片输入源。通过
image 参数数组传入一个带有唯一 id 的对象来表示用于混流的图片源。await trtc.updatePlugin('VideoMixer', {image: [{id: 'img1',url: './image.png',layout: {x: 0,y: 500,width: 800,height: 400,zIndex: 4,}}]});
更新图片参数:
await trtc.updatePlugin('VideoMixer', {image: [{id: 'img1',url: './another-img.png',layout: {x: 100,y: 100,width: 600,height: 500,zIndex: 4,fillMode: 'fill'}}]});
删除图片:
await trtc.updatePlugin('VideoMixer', {image: []});
视频
您可以添加视频输入源。通过
video 参数数组传入一个带有唯一 id 的对象来表示用于混流的视频源。await trtc.updatePlugin('VideoMixer', {video: [{id: 'video1',url: './video.mp4',layout: {x: 0,y: 0,width: 1000,height: 500,zIndex: 5,}}]});
更新视频参数:
await trtc.updatePlugin('VideoMixer', {video: [{id: 'video1',url: './another-video.mp4',layout: {x: 100,y: 100,width: 1280,height: 720,zIndex: 5,}}]});
删除视频:
await trtc.updatePlugin('VideoMixer', {video: []});
停止混流
移除所有输入源并停止混流:
await trtc.stopPlugin('VideoMixer');
发布混流
推流不由混流插件控制,但
startPlugin 会返回混流后的视频轨道,可将其作为参数传递给 startLocalVideo 等接口来控制推流。const { track } = await trtc.startPlugin('VideoMixer', {canvasInfo: {width: 1920,height: 1080},// ...});// 开始推流await trtc.startLocalVideo({option: {videoTrack: track,profile: {width: 1920,height: 1080,bitrate: 2000}}});
指定帧率
指定混流画布的帧率:
await trtc.startPlugin('VideoMixer', {canvasInfo: {width: 1920,height: 1080,frameRate: 20},});
注意:
不建议手动设置混流帧率,插件会自动计算最佳帧率——当存在摄像头或屏幕时,使用摄像头和屏幕共享视频中的最大帧率;否则默认为 15fps。手动设置可能导致性能下降。
错误处理
由于混流插件在调用
start/updatePlugin 时接受大量参数,为确保部分输入源的失败不影响其他输入源的正常混流,插件的错误处理策略如下:当部分输入源参数成功应用、部分失败时,接口不会抛出错误,而是将成功和失败信息返回给业务侧。
当所有输入源参数均失败,或存在其他可能中断混流过程的错误时,接口将抛出错误及错误详情。
业务逻辑示例:
try {const { result } = await trtc.updatePlugin('VideoMixer', {camera: [/**/],screen: [/**/],text: [/**/],image: [/**/],video: [/**/],});// 部分失败时的错误处理result.failedDetails.forEach(({id, error}: {id: string, error: any}) => {console.error(`${id} 混流失败`, error);})console.log(result.successOptions) // 成功应用的参数信息} catch (error: any) {// 全部失败时的错误处理console.error(error);error?.data?.failedDetails.forEach(({ id, error }: { id: string, error: any }) => {console.error(`${id} 混流失败`, error);})}
successOptions 详细说明successOptions 返回本次更新后实际应用到混流中的参数。具体含义为:当没有错误发生时,
successOptions 与 options 中传入的参数相同。当部分错误发生时,
successOptions 将包含成功更新的参数,失败的部分保留之前的值。例如,假设混流已有摄像头和屏幕输入源:
await trtc.startPlugin('VideoMixer', {camera: [{/*zIndex = 1*/}],screen: [{/*zIndex = 0*/}]});
现在,如果尝试将摄像头的
zIndex 更新为 0(因 zIndex 重复将失败)并将屏幕的 mirror 更新为 true(将成功):await trtc.updatePlugin('VideoMixer', {camera: [{/*zIndex = 0*/}],screen: [{/*mirror = true*/}]});
在这种情况下,属于部分失败(摄像头更新失败,屏幕更新成功),返回的
successOptions 将如下所示:{camera: [{/*zIndex = 1*/}], // 更新失败,因此仍然应用之前的 zIndexscreen: [{/*mirror = true*/}] // 更新成功,因此应用新参数}
也就是说,
successOptions 告诉业务侧更新后实际应用的参数,使业务侧可以相应地同步 UI 或数据。API 说明
trtc.startPlugin('VideoMixer', options)
用于启动混流插件。
options: VideoMixerOptions
参数名 | 类型 | 必填 | 说明 |
view | string | HTMLElement | null | 可选 | 用于视频预览的 HTMLElement 实例或 id。如果不提供或为 null,则不播放混流视频。 |
canvasInfo | CanvasInfo | 必填 | 设置混流画布的参数。 |
camera | CameraSource[] | 可选 | 控制摄像头输入源的参数。 |
screen | ScreenSource[] | 可选 | 控制屏幕共享输入源的参数。 |
text | TextSource[] | 可选 | 控制文字输入源的参数。 |
image | ImageSource[] | 可选 | 控制图片输入源的参数。 |
video | VideoSource[] | 可选 | 控制视频输入源的参数。 |
onScreenShareStop | (id: string) => {} | 可选 | 屏幕共享停止时的回调。当用户通过浏览器按钮而非 SDK 停止屏幕共享时可能会用到。回调参数是停止采集的屏幕 ID。 |
CanvasInfo
参数名 | 类型 | 必填 | 说明 |
canvasColor | string | CanvasGradient | CanvasPattern | 可选 | |
width | number | 必填 | 混流画布的宽度。 |
height | number | 必填 | 混流画布的高度。 |
frameRate | number | 可选 | 手动指定混流画布帧率。通常情况下,建议让插件自动计算帧率以避免性能损失。 |
CameraSource参数名 | 类型 | 必填 | 说明 |
id | string | 必填 | 输入源的唯一 ID。 |
layout | LayerOption | 必填 | 输入源的布局参数。 |
cameraId | string | 可选 | 指定要采集的摄像头 ID。 |
videoTrack | MediaStreamTrack | 可选 | 用于采集的自定义视频轨道。 |
profile | 可选 | 视频采集参数。默认值: 480p_2。 |
ScreenSource参数名 | 类型 | 必填 | 说明 |
id | string | 必填 | 输入源的唯一 ID。 |
layout | LayerOption | 必填 | 输入源的布局参数。 |
profile | 可选 | 屏幕采集参数。默认值:1080p。 | |
captureElement | HTMLElement | 可选 | 从当前页面采集指定的 HTMLElement。Chrome 104+ 支持 |
preferDisplaySurface | 'current-tab' | 'tab' | 'window' | 'monitor' | 可选 | 控制屏幕共享对话框中屏幕采集选项的优先级。默认为 monitor。如果选择 current-tab,则只显示当前页面。Chrome 94+ 支持。 |
TextSource参数名 | 类型 | 必填 | 说明 |
id | string | 必填 | 输入源的唯一 ID。 |
layout | LayerOption | 必填 | 文字源的布局参数。 |
content | string | 必填 | 要显示的文字内容。 |
color | string | CanvasGradient | CanvasPattern | 可选 | |
font | string | 可选 |
ImageSource参数名 | 类型 | 必填 | 说明 |
id | string | 必填 | 输入源的唯一 ID。 |
layout | LayerOption | 必填 | 图片源的布局参数。 |
url | string | 必填 | 要混流的图片 URL。 |
VideoSource参数名 | 类型 | 必填 | 说明 |
id | string | 必填 | 输入源的唯一 ID。 |
layout | LayerOption | 必填 | 视频源的布局参数。 |
url | string | 必填 | 要混流的视频 URL。 |
LayerOption参数名 | 类型 | 必填 | 说明 |
x | number | 必填 | 相对于画布左上角的水平坐标。可以为负数。 |
y | number | 必填 | 相对于画布左上角的垂直坐标。可以为负数。 |
width | number | 必填 | 输入源的布局宽度。必须大于 0。 |
height | number | 必填 | 输入源的布局高度。必须大于 0。 |
zIndex | number | 必填 | 输入源的图层索引。每个源的图层索引必须唯一。 |
fillMode | 'contain' | 'cover' | 'fill' | 可选 | |
mirror | boolean | 可选 | 是否镜像输入源。 |
rotation | 0 | 90 | 180 | 270 | 可选 | 源的旋转角度(0、90、180 或 270 度)。 |
hidden | boolean | 可选 | 是否在画布上临时隐藏输入源。使用场景:临时隐藏摄像头/屏幕而不实际关闭或销毁资源。 |
返回值:Promise<MixParseResult>
MixParseResult | 类型 | 说明 |
MixParseResult.track | MediaStreamTrack | 混流后的输出视频轨道。 |
MixParseResult.result | object | 包含本次更新参数的成功或失败信息。结构如下: |
参数名 | 类型 | 说明 |
successOptions | VideoMixerOptions | UpdateVideoMixerOptions | 本次更新成功应用的参数。 |
failedDetails | Array<MixFailedDetail> | 本次更新中应用失败的参数详情。 |
MixFailedDetailMixFailedDetail | 类型 | 说明 |
MixFailedDetail.id | string | 更新失败的输入源 ID。 |
MixFailedDetail.error | Error | 更新失败的原因。 |
示例:
// 示例 1:仅使用画布设置启动混流await trtc.startPlugin('VideoMixer', {view: 'local_preview_div',canvasInfo: {canvasColor: 'green',width: 1920,height: 1080},});// 示例 2:使用输入源启动混流并记录失败原因try {const { result } = await trtc.startPlugin('VideoMixer', {view: `local_preview_div`,canvasInfo: {width: 1920,height: 1080,},screen: [{id: 'screen1',profile: { width: 1920, height: 1080, frameRate: 15 },preferDisplaySurface: 'tab',layout: {x: 0,y: 0,width: 1920,height: 1080,zIndex: 0,},},],// ...onScreenShareStop: (id: string) => {console.log(`屏幕共享停止,id: ${id}`);},});result.failedDetails.forEach(({ id, error }: { id: string; error: any }) => {console.error(`${id} 混流失败`, error);});} catch (error: any) {console.error(error);error?.data?.failedDetails.forEach(({ id, error }: { id: string, error: any }) => {console.error(`${id} 混流失败`, error);})}
trtc.updatePlugin('VideoMixer', options)
options: UpdateVideoMixerOptions
UpdateVideoMixerOptions | 类型 | 必填 | 说明 |
view | string | HTMLElement | null | 可选 | 用于视频预览的 HTMLElement 实例或 Id。如果不提供或为 null,则不播放混流视频。 |
canvasInfo | CanvasInfo | 可选 | 设置混流画布的参数。 |
camera | CameraSource[] | 可选 | 控制摄像头输入源的参数。 |
screen | ScreenSource[] | 可选 | 控制屏幕共享输入源的参数。 |
text | TextSource[] | 可选 | 控制文字输入源的参数。 |
image | ImageSource[] | 可选 | 控制图片输入源的参数。 |
video | VideoSource[] | 可选 | 控制视频输入源的参数。 |
返回值
返回值与
startPlugin 相同。示例
// 示例 1:移除所有摄像头await trtc.updatePlugin('VideoMixer', {camera: []});// 示例 2:更新混流参数并记录失败原因try {const { result } = await trtc.updatePlugin('VideoMixer', {camera: [{id: 'camera1',profile: { width: 640, height: 480, frameRate: 30 },layout: {x: 100,y: 0,width: 640,height: 480,zIndex: 1,},},],// ...});result.failedDetails.forEach(({id, error}: {id: string, error: any}) => {console.error(`${id} 混流失败`, error);})} catch (error: any) {console.error(error);error?.data?.failedDetails.forEach(({ id, error }: { id: string, error: any }) => {console.error(`${id} 混流失败`, error);})}
trtc.stopPlugin('VideoMixer')
停止混流插件。
await trtc.stopPlugin('VideoMixer');
常见问题
如何设置分辨率和码率?
分辨率指的是画布的宽度和高度。
码率不由混流插件控制,而是在业务端调用
start/updateLocalVideo 推流时设置。为什么输入源参数以数组形式传递,如何更新数组中的某类输入源?
每类输入源都是全量更新的,以支持批量更新和删除(通过比较更新前后的数组)。
所有输入源参数都是可选的。例如,如果只想更新摄像头,可以在
updatePlugin 中只传递 camera 数组,而不传递其他类型的输入源数组,或者传入时保持原有参数。示例:await trtc.startPlugin('VideoMixer', { // 假设已混入摄像头和屏幕camera: [/* 配置 */],screen: [/* 配置 */]});
现在如果只想更新摄像头而不更新屏幕,有两种方式:
只传递
camera,省略 screen:await trtc.updatePlugin('VideoMixer', { camera: [/* 新配置 */], // 只传 camera 数组});
将旧的
screen 参数与新的 camera 参数一起传入:await trtc.updatePlugin('VideoMixer', {camera: [/* 新配置 */],screen: [/* 配置 */] // 保持 screen 参数不变});
通常推荐第一种方式,因为混流插件需要处理的参数更少。虽然第二种方式也能达到相同效果,但存在冗余的参数解析。
传递数组时,如何只更新其中一个输入源?
例如,要更新某个摄像头(id = 1),只需修改
camera 数组中 id = 1 的元素的参数。其他不需要更新的元素应原样传递原有参数。为什么摄像头和屏幕采集的码率设置被忽略?
摄像头和屏幕采集的码率设置会被忽略。上行码率由业务端在调用推流接口(
start/updateLocalVideo)时设置。为什么旋转角度不是围绕中心点?
混流旋转的工作原理是旋转图片并将图片的左上角保持在 layout 提供的坐标位置。如果需要围绕中心旋转,业务端需要相应调整坐标。
业务端如何检测用户通过浏览器按钮而非 SDK 停止屏幕共享?
startPlugin 参数中提供了 onScreenShareStop 回调。使用示例:await trtc.startPlugin('VideoMixer', {// ...onScreenShareStop: (id: string) => {console.log(`屏幕共享停止,id: ${id}`);}});
实践教程
1. 混流插件的
start/updatePlugin 接口接受大量参数,建议每次更新时尽量减少不必要且未更改的参数传输。例如,频繁更新摄像头坐标时,无需传递采集相关参数(例如 profile、cameraId 等),这些参数只需传递一次。这有助于减少插件底层解析参数的开销。2. 画布分辨率越大、输入源越多,渲染开销就越大。业务端需要根据设备能力确定合适的混流参数。
