接入概览(Web)

最近更新时间:2026-05-12 16:44:01

我的收藏
本文档旨在为您提供多人会议无 UI 集成方案的快速接入指南。借助该方案,开发者能够以极低的接入成本,在 Web 端快速落地企业协同、医疗问诊与会诊、专业咨询、在线小班课等支持 2~300 人实时互动的音视频场景。
该方案将复杂的房间管理、成员状态、设备控制及屏幕共享等核心会议能力,高度抽象为易用的响应式数据、精简接口与事件,并内置了丰富的场景化最佳实践。您在享受响应式开发带来极致便利的同时,拥有对业务 UI 与交互 100% 的自定义控制权,彻底告别设备插拔监听、房间状态同步、视频流懒加载、大小流切换等繁杂的底层技术处理。
推荐使用更高效的 AI 集成助手
我们为您提供了全新的 AI 集成方式,只需要描述您的需求,即可自动生成集成代码,大幅提升开发效率。

适用场景

凭借这套极具弹性的无 UI 集成架构,您可以轻松打破标准化会议产品的界面束缚,将高品质的音视频能力无缝内嵌至现有的业务工作流中。不论是企业内部协作,还是深耕垂直行业的在线服务,都能快速打磨出高度贴合自身品牌调性的互动体验:
企业协同
医疗问诊/医疗会诊


专业咨询
在线教育


除上述典型行业外,只要您的业务核心诉求涵盖 2~300 人并发的高质量实时互动、灵活的会中协作以及严密的会议秩序管控,该方案均能为您提供坚实稳定的技术底座,显著缩短产品的上线周期。

底层能力概览

尽管企业协同、在线教育、医疗问诊等业务面向不同的行业受众,但其底层产品形态高度一致。基于这套多人会议无 UI 底座,您可以轻松应对以下核心需求:
高并发实时互动:支持最高 300 人稳定在会,保障低延迟、高流畅的音视频沟通体验。
严密的会控与秩序管理:提供完善的房主/主持人权限体系,灵活管理成员状态。
动态的会中协作:支持中途灵活加入、高质量屏幕共享与业务内容演示。
开发者只需基于同一套集成方案快速接入,即可在统一的能力底座上,自由构建贴合自身行业特性的产品体验。

准备工作

步骤1:开通服务

请参考 开通服务 领取 TUIRoomKit 体验版。

步骤2:环境准备

Node.js: ≥ 18.19.1 (推荐使用官方 LTS 版本)。
Vue: ≥ 3.4.21。
现代浏览器:支持 WebRTC APIs 的现代浏览器。
设备:摄像头、麦克风、扬声器。

步骤3:安装 Atomicx 依赖

npm
pnpm
yarn
npm install tuikit-atomicx-vue3 @tencentcloud/uikit-base-component-vue3
pnpm install tuikit-atomicx-vue3 @tencentcloud/uikit-base-component-vue3
yarn add tuikit-atomicx-vue3 @tencentcloud/uikit-base-component-vue3

搭建基础多人房间

下面我们将通过一个最小可用 Demo,快速完成标准会议场景的基础接入。完成本节后,您将获得一个可运行的 Web 会议页面,支持登录鉴权、创建或加入房间、设备控制、房间画面渲染、屏幕分享以及成员列表展示。

步骤1:配置全局 UIKitProvider

先在应用根组件中配置 UIKitProvider。它主要用于提供全局主题和语言配置,建议在 App.vue 中完成初始化。
<template>
<UIKitProvider theme="light" language="zh-CN">
<div id="app-content" />
</UIKitProvider>
</template>

<script setup lang="ts">
import { UIKitProvider } from '@tencentcloud/uikit-base-component-vue3';
</script>

步骤2:登录与身份鉴权

在项目的 src/components 目录下创建 LoginUserInfo.vue 文件,并拷贝以下代码完成用户登录与退出。
<template>
<div class="login-user-info">
<template v-if="loginUserInfo">
<span>{{ loginUserInfo.userName || loginUserInfo.userId }}</span>
<TUIButton type="primary" @click="handleLogout">退出登录</TUIButton>
</template>

<template v-else>
<TUIInput v-model="sdkAppId" style="width: 180px;" placeholder="请输入 sdkAppId" />
<TUIInput v-model="userId" style="width: 180px;" placeholder="请输入 userId" />
<TUIInput v-model="userName" style="width: 220px;" placeholder="请输入 userName(可选)" />
<TUIInput v-model="userSig" style="width: 420px;" :maxLength="2000" placeholder="请输入 userSig" />
<TUIButton type="primary" @click="handleLogin">登录</TUIButton>
</template>
</div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { TUIInput, TUIButton } from '@tencentcloud/uikit-base-component-vue3';
import { useLoginState } from 'tuikit-atomicx-vue3';

const { loginUserInfo, login, logout, setSelfInfo } = useLoginState();

const sdkAppId = ref('');
const userId = ref('');
const userName = ref('');
const userSig = ref('');

async function handleLogin() {
if (!sdkAppId.value || !userId.value || !userSig.value) {
return;
}
await login({
sdkAppId: Number(sdkAppId.value),
userId: userId.value,
userSig: userSig.value,
});
if (userName.value) {
await setSelfInfo({
userName: userName.value,
avatarUrl: '',
});
}
}

async function handleLogout() {
await logout();
}
</script>

<style scoped>
.login-user-info { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
</style>
为了快速体验,您可以先通过控制台或 UserSig 工具生成测试用 userSig。正式环境请改为由业务服务端签发。

步骤3:创建或加入房间

在项目的 src/components 目录下创建 RoomControl.vue 文件,并拷贝以下代码完成房间生命周期管理。
<template>
<div class="room-control">
<template v-if="!currentRoom">
<TUIInput v-model="roomId" style="max-width: 200px;" placeholder="请输入房间 ID" />
<TUIButton type="primary" @click="handleCreateRoom">创建房间</TUIButton>
<TUIButton type="primary" @click="handleJoinRoom">加入房间</TUIButton>
</template>

<template v-else>
<span>房间 ID{{ currentRoom.roomId }}</span>
<TUIButton type="primary" @click="handleLeaveRoom">离开房间</TUIButton>
<TUIButton v-if="localParticipant?.role === RoomParticipantRole.Owner" type="primary" @click="handleEndRoom">
结束房间
</TUIButton>
</template>
</div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { TUIInput, TUIButton, TUIToast } from '@tencentcloud/uikit-base-component-vue3';
import { useRoomState, useRoomParticipantState, RoomParticipantRole } from 'tuikit-atomicx-vue3/room';

const { currentRoom, createAndJoinRoom, joinRoom, leaveRoom, endRoom } = useRoomState();
const { localParticipant } = useRoomParticipantState();

const roomId = ref('');

async function handleCreateRoom() {
try {
await createAndJoinRoom({ roomId: roomId.value, options: { roomName: '临时房间' } });
TUIToast.success({ message: '创建房间成功' });
} catch {
TUIToast.error({ message: '创建房间失败,请重试' });
}
}

async function handleJoinRoom() {
try {
await joinRoom({ roomId: roomId.value });
TUIToast.success({ message: '加入房间成功' });
} catch {
TUIToast.error({ message: '加入房间失败,请重试' });
}
}

async function handleLeaveRoom() {
await leaveRoom();
}

async function handleEndRoom() {
await endRoom();
}
</script>

<style scoped>
.room-control { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; padding: 16px; background: #fff; border: 1px solid #e5e5e5; border-radius: 8px; flex: 1; min-width: 300px; }
</style>
本示例同时提供“创建房间”和“加入房间”两个入口,方便使用两个测试账号完成联调。

步骤4:媒体设备与屏幕分享

在项目的 src/components 目录下创建 DeviceControl.vue 文件,并拷贝以下代码完成摄像头、麦克风和屏幕分享控制。
<template>
<div class="device-control">
<TUIButton type="primary" @click="toggleCamera">
{{ isLocalCameraOpen ? '关闭摄像头' : '打开摄像头' }}
</TUIButton>
<TUIButton type="primary" @click="toggleMicrophone">
{{ isLocalMicrophoneOpen ? '关闭麦克风' : '打开麦克风' }}
</TUIButton>
<TUIButton type="primary" @click="toggleScreenShare">
{{ isScreenSharing ? '停止共享' : '共享屏幕' }}
</TUIButton>
</div>
</template>

<script setup lang="ts">
import { computed } from 'vue';
import { TUIButton, TUIToast } from '@tencentcloud/uikit-base-component-vue3';
import { DeviceStatus, useDeviceState } from 'tuikit-atomicx-vue3/room';

const {
cameraStatus,
microphoneStatus,
screenStatus,
openLocalCamera,
closeLocalCamera,
openLocalMicrophone,
closeLocalMicrophone,
startScreenShare,
stopScreenShare,
} = useDeviceState();

const isLocalCameraOpen = computed(() => cameraStatus.value === DeviceStatus.On);
const isLocalMicrophoneOpen = computed(() => microphoneStatus.value === DeviceStatus.On);
const isScreenSharing = computed(() => screenStatus.value === DeviceStatus.On);

async function toggleCamera() {
try {
if (isLocalCameraOpen.value) {
await closeLocalCamera();
} else {
await openLocalCamera();
}
} catch {
TUIToast.error({ message: '摄像头操作失败,请检查设备权限' });
}
}

async function toggleMicrophone() {
try {
if (isLocalMicrophoneOpen.value) {
await closeLocalMicrophone();
} else {
await openLocalMicrophone();
}
} catch {
TUIToast.error({ message: '麦克风操作失败,请检查设备权限' });
}
}

async function toggleScreenShare() {
try {
if (isScreenSharing.value) {
await stopScreenShare();
} else {
await startScreenShare({ screenAudio: true });
}
} catch {
TUIToast.error({ message: '屏幕共享失败,请检查浏览器权限' });
}
}
</script>

<style scoped>
.device-control { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; padding: 16px; background: #fff; border: 1px solid #e5e5e5; border-radius: 8px; flex: 1; min-width: 300px; }
</style>

步骤5:视频区域及成员列表

在项目的 src/components 目录下创建 ConferenceRoom.vue 文件,并拷贝以下代码完成会议主区域渲染。
<template>
<div v-if="currentRoom" class="conference-room">
<div class="conference-content">
<div class="room-stage">
<RoomView>
<template #participantViewUI="{ participant, streamType }">
<div
v-if="participant"
:class="['participant-overlay', { 'participant-overlay--camera-off': isCameraOff(participant, streamType) }]"
>
<Avatar
v-if="isCameraOff(participant, streamType)"
class="participant-avatar"
size="xxl"
:src="participant.avatarUrl"
:user-id="participant.userId"
/>

<div v-if="isLocalScreenStream(participant, streamType)" class="screen-share-mask">
正在屏幕分享中
</div>

<div class="participant-tag">
<span>{{ participant.userName || participant.userId }}</span>
<span v-if="participant.microphoneStatus !== DeviceStatus.On && !isScreen(streamType)">麦克风已关闭</span>
<span v-if="isScreen(streamType)">屏幕共享中</span>
</div>
</div>
</template>
</RoomView>
</div>

<aside class="member-panel">
<div class="member-title">
参会者({{ currentRoom.participantCount || participantList.length }}
</div>
<div v-for="participant in participantList" :key="participant.userId" class="member-item">
<div class="member-name">
<span>{{ participant.userName || participant.userId }}</span>
<span v-if="isRoomOwner(participant.userId)" class="member-role-tag">房主</span>
</div>
<div class="member-status">
<span>{{ participant.cameraStatus === DeviceStatus.On ? '开摄像头' : '关摄像头' }}</span>
<span>{{ participant.microphoneStatus === DeviceStatus.On ? '开麦克风' : '关麦克风' }}</span>
</div>
</div>
</aside>
</div>
</div>

<div v-else class="empty-state">
请先创建房间或加入已有房间
</div>
</template>

<script setup lang="ts">
import { watch } from 'vue';
import {
Avatar,
DeviceStatus,
RoomView,
useRoomParticipantState,
useRoomState,
VideoStreamType,
} from 'tuikit-atomicx-vue3/room';

const { currentRoom } = useRoomState();
const { participantList, getParticipantList, localParticipant } = useRoomParticipantState();

watch(
() => currentRoom.value?.roomId,
async (roomId, oldRoomId) => {
if (!oldRoomId && roomId) {
await getParticipantList({ cursor: '' });
}
},
);

function isScreen(streamType: VideoStreamType) {
return streamType === VideoStreamType.Screen;
}

function isCameraOff(participant: { cameraStatus?: DeviceStatus }, streamType: VideoStreamType) {
return !isScreen(streamType) && participant.cameraStatus !== DeviceStatus.On;
}

function isLocalScreenStream(participant: { userId?: string }, streamType: VideoStreamType) {
return isScreen(streamType) && participant.userId === localParticipant.value?.userId;
}

function isRoomOwner(userId?: string) {
return userId === currentRoom.value?.roomOwner.userId;
}
</script>

<style scoped>
.conference-room { display: flex; flex-direction: column; gap: 16px; min-height: 0; }
.conference-content { display: flex; gap: 16px; min-height: 520px; }
.room-stage { flex: 1; background: #fff; border: 1px solid #e5e5e5; border-radius: 8px; overflow: hidden; }
.member-panel { width: 280px; padding: 16px; background: #fff; border: 1px solid #e5e5e5; border-radius: 8px; overflow-y: auto; }
.member-title { margin-bottom: 12px; font-weight: 600; }
.member-item { padding: 10px 0; border-bottom: 1px solid #f5f5f5; }
.member-name { display: flex; align-items: center; gap: 8px; font-size: 14px; color: #222; margin-bottom: 4px; }
.member-role-tag { padding: 2px 6px; border-radius: 10px; background: rgba(0, 82, 217, 0.12); color: #0052d9; font-size: 12px; line-height: 1; }
.member-status { display: flex; gap: 8px; font-size: 12px; color: #888; }
.participant-overlay { position: absolute; inset: 0; }
.participant-overlay--camera-off { background: #f3f4f6; }
.participant-avatar { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }
.screen-share-mask { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; background: rgba(0, 0, 0, 0.45); color: #fff; font-size: 18px; font-weight: 600; }
.participant-tag { position: absolute; left: 12px; bottom: 12px; display: flex; gap: 8px; padding: 6px 10px; border-radius: 14px; background: rgba(0, 0, 0, 0.65); color: #fff; font-size: 12px; }
.empty-state { min-height: 520px; display: flex; align-items: center; justify-content: center; color: #999; background: #fff; border: 1px dashed #d9d9d9; border-radius: 8px; }
</style>
RoomView 负责房间中的布局组织与视频画面渲染,您可以通过 participantViewUI 插槽补充成员名称、设备状态、本地屏幕分享遮罩等自定义 UI。同时,您也可以基于 useRoomParticipantState 获取成员数据,自行渲染成员面板。

步骤6:组装完整页面

完成以上组件后,您可以回到 src/App.vue 中将它们组装为一个完整页面。
<template>
<UIKitProvider theme="light" language="zh-CN">
<div class="app-container">
<header class="app-header">
<h1 class="app-title">Conference Demo</h1>
<LoginUserInfo />
</header>

<main v-if="loginUserInfo" class="app-main">
<div class="control-panel">
<RoomControl />
<DeviceControl />
</div>
<ConferenceRoom />
</main>

<div v-else class="welcome-screen">
<div class="welcome-content">
<h2>欢迎使用多人会议 Demo</h2>
<p>请先完成登录,然后创建房间或加入房间。</p>
</div>
</div>
</div>
</UIKitProvider>
</template>

<script setup lang="ts">
import { UIKitProvider } from '@tencentcloud/uikit-base-component-vue3';
import { useLoginState } from 'tuikit-atomicx-vue3';
import LoginUserInfo from './components/LoginUserInfo.vue';
import DeviceControl from './components/DeviceControl.vue';
import RoomControl from './components/RoomControl.vue';
import ConferenceRoom from './components/ConferenceRoom.vue';

const { loginUserInfo } = useLoginState();
</script>

<style>
* { box-sizing: border-box; }
html, body, #app { width: 100%; height: 100%; margin: 0; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif; background: #f5f5f5; }
.app-container { width: 100%; height: 100%; display: flex; flex-direction: column; }
.app-header { padding: 16px 24px; background: #fff; border-bottom: 1px solid #e5e5e5; display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.app-title { margin: 0; font-size: 20px; font-weight: 600; color: #222; }
.app-main { flex: 1; display: flex; flex-direction: column; gap: 16px; padding: 20px; min-height: 0; width: 100%; max-width: 1400px; margin: 0 auto; }
.control-panel { display: flex; gap: 16px; flex-wrap: wrap; }
.welcome-screen { flex: 1; display: flex; align-items: center; justify-content: center; }
.welcome-content { text-align: center; color: #666; }
</style>
到这里,一个最小可用的标准会议页面就已经组装完成。

步骤7:运行和联调验证

启动开发服务器。
npm
pnpm
yarn
npm run dev
pnpm run dev
yarn run dev

准备两个测试账号

在腾讯云 控制台 获取 sdkAppId 信息并生成两个不同的 userId 和对应的 userSig(例如 user_test_A、user_test_B)。

打开两个浏览器标签页

窗口 A:使用 user_test_A 登录。
窗口 B:使用 user_test_B 登录。


测试流程

在窗口 A 中:输入房间 ID(例如 "test_room_A"),点击创建房间
在窗口 A 中:点击打开摄像头打开麦克风
在窗口 B 中:输入相同的房间 ID("test_room_A"),点击加入房间
在窗口 B 中:点击打开摄像头打开麦克风
验证两个窗口能否看到对方的视频画面并听到声音。

完成以上验证后,您已经成功跑通一个最小可用的标准会议 Demo,可以继续进入专题文档扩展更复杂的业务能力。

下一步

当您完成了基础的多人音视频房间功能后,您可以参考以下功能指南,进一步丰富和扩展您的特色业务能力。

核心功能

多人音视频房间功能
功能介绍
实现指南
房间管理
支持客户端及服务端发起会议,加入会议。
设备管理
支持用户切换麦克风、摄像头和扬声器设备。
视频布局
提供宫格模式和演讲者模式的视频布局。
屏幕分享
支持用户分享屏幕、窗口或浏览器标签页。
成员管理
房主/管理员可进行踢人、转让房主等权限操作。

附加功能

多人音视频房间功能
功能介绍
实现指南
会中聊天
实现参会者间的实时文本消息通信。
会中呼叫
支持房间中用户邀请其他用户加入当前房间。
预定房间
提供房间的提前预约和列表管理功能。
虚拟背景
支持用户设置背景模糊或替换为虚拟背景图。
基础美颜
支持用户设置磨皮、美白、红润的美颜效果。

API 参考

State Hook 名称
核心作用与场景
API 文档
useLoginState
登录鉴权。 管理用户登录生命周期和身份信息。
useDeviceState
设备与网络。 管理麦克风、摄像头、屏幕共享设备,以及网络质量。
useRoomState
房间管理。 管理实时房间的生命周期(创建、加入、结束)和核心信息。
useRoomParticipantState
成员管理。 管理房间内所有参会者信息、角色和权限。
useMessageListState
聊天消息。 管理消息收发和展示。
useMessageInputState
聊天输入。管理消息的发送

常见问题

本地开发时音视频功能正常,但部署到线上环境后无法正常采集用户的摄像头或麦克风设备

原因分析:浏览器出于安全和隐私保护的考虑,对音视频设备(麦克风、摄像头)的采集有着严格限制。只有在安全环境下,采集操作才会被允许。安全环境协议包括:https://、localhost、file:// 等。HTTP 协议被视为不安全,浏览器会默认禁止其访问媒体设备。
解决方案:若您在本地(localhost)测试一切正常,但部署后出现采集失败,请立即检查您的网页是否部署在 HTTP 协议上。您必须使用 HTTPS 协议部署您的网页,并确保具备有效的安全证书。
相关资源:更多关于 URL 域名及协议的限制详情,请参见 URL 域名及协议限制说明

是否支持使用 iframe 集成?

支持。使用 iframe 集成无 UI SDK 时, 需要在 iframe 标签中配置 allow 属性以授予必要的浏览器权限(麦克风、摄像头、屏幕共享、全屏等),示例如下:
// 开启麦克风、摄像头、屏幕分享、全屏权限
<iframe allow="microphone; camera; display-capture; display; fullscreen;">

联系我们

如果您在接入或使用过程中有任何疑问或者建议,欢迎 联系我们 提交反馈。