Feedback

视频混流

功能说明

本文介绍如何使用 VideoMixer 插件将不同的输入源混合成一路轨道。
TRTC VideoMixer 工作流
TRTC 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*/}], // 更新失败,因此仍然应用之前的 zIndex
screen: [{/*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
可选
混流画布的背景颜色。请参考 canvas fillStyle 属性。
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
可选
文字颜色。请参考 canvas fillStyle 属性。
font
string
可选
文字的字体。请参考 canvas font 属性。
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'
可选
填充布局的模式,摄像头默认为 cover,其他源默认为 contain。更多详情请参见 CSS object-fit 属性。
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>
本次更新中应用失败的参数详情。
MixFailedDetail
MixFailedDetail
类型
说明
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. 画布分辨率越大、输入源越多,渲染开销就越大。业务端需要根据设备能力确定合适的混流参数。