本篇文档将指导您使用 Atomicx 的核心视图组件
RoomView 实现实时音视频房间的视频画面渲染。通过配置布局模板和利用组件插槽,您可以快速实现视频布局的切换与个性化定制。RoomView 是 Atomicx 中负责视频画面渲染的主容器组件。它会自动处理视频流的订阅、渲染和释放,您只需关注其布局配置和 UI 增强。
核心能力
RoomView 作为多人音视频房间的核心视图容器,内置了多项企业级音视频处理能力:智能性能管理:支持视口内视频流懒加载,并能根据流区域尺寸,远端流数量自动进行高低清流切换,显著降低系统能耗与带宽占用。
精细化排序策略:系统根据角色(主持人优先)与媒体状态(音视频开启优先)自动调整画面顺序。
交互式演讲者模式:支持双击锁定主画面,并在侧边栏或顶部栏布局中支持收起非核心区域,聚焦演示内容。
沉浸式视觉设计:所有视频窗口强制保持
16:9 的标准比例,并配合交互显示的工具栏,提供无干扰的多人音视频房间体验。可定制范围
RoomView 仅聚焦于底层视频流的播放,而将视图层的所有“表现权”交给了开发者。使用 RoomView 您可以自由定义:布局切换:根据业务场景(例如演讲模式或讨论模式)实时变换布局。
视频挂件 UI:包含昵称标签、音量动态波形、角色徽章等。
前提条件
用户已经通过 useLoginState 完成登录鉴权,请参考 接入概览。
用户已经通过 useRoomState 进入房间,参考 房间管理。
已在
App.vue 中配置全局 UIKitProvider,具体请参考 配置 App.vue。实现视频布局功能
步骤1:引入并渲染基础布局
在您的会议页面(例如 RoomLayoutView.vue)中引入
RoomView 及布局枚举 RoomLayoutTemplate。<template><div class="room-container"><RoomView /></div></template><script setup lang="ts">import { ref } from 'vue';import { RoomView } from 'tuikit-atomicx-vue3/room';</script><style lang="scss">.room-container {width: 100%;height: 100%;}</style>
注意:
RoomView 会自动占满父容器的高度和宽度,请确保其父容器具备明确的尺寸。步骤2:切换布局模式
您可以根据业务逻辑(例如点击切换按钮)动态更新
RoomView 属性 layoutTemplate 的值。属性名 | 类型 | 默认值 | 说明 |
layoutTemplate | RoomLayoutTemplate | RoomLayoutTemplate.GridLayout | 视频流布局 |
RoomLayoutTemplate 可选值解析:布局模式 | 枚举值 | 描述 |
宫格布局 | RoomLayoutTemplate.GridLayout | 默认布局。所有参会者画面均匀排列,参会者超出 9 人时支持翻页。 |
侧边栏布局 | RoomLayoutTemplate.SidebarLayout | 主画面突出显示,其余参会者画面以侧边栏的形式排列,适用于演讲、报告等场景。 |
顶部栏布局 | RoomLayoutTemplate.CinemaLayout | 主画面突出显示,其余参会者画面以顶部栏的形式排列,适用于演讲、报告等场景。 |
<template><!-- 添加布局切换按钮 --><div class="layout-switcher"><button @click="switchLayout(RoomLayoutTemplate.GridLayout)">宫格</button><button @click="switchLayout(RoomLayoutTemplate.SidebarLayout)">侧边栏</button><button @click="switchLayout(RoomLayoutTemplate.CinemaLayout)">顶部栏</button></div><div class="room-container"><RoomView :layoutTemplate="currentLayout" /></div></template><script setup lang="ts">import { ref } from 'vue';import { RoomView, RoomLayoutTemplate } from 'tuikit-atomicx-vue3/room';// 默认使用宫格布局const currentLayout = ref(RoomLayoutTemplate.GridLayout);// 切换布局const switchLayout = (layout: RoomLayoutTemplate) => {currentLayout.value = layout;};</script><style lang="scss">.room-container {width: 100%;height: 100%;}</style>
步骤3:实现视频流挂件信息
您可以通过使用
participantViewUI 插槽,为视频画面定制 UI 显示内容(例如设置昵称,添加自定义图标)。
插槽参数 | 类型 | 说明 |
participant | RoomParticipant | 当前视频流对应的参会者对象,包含用户的 userId、userName、role 等实时状态信息。 |
streamType | VideoStreamType | 区分当前是摄像头流(Camera)还是屏幕分享流(Screen) |
准备视觉资源
为了提供完整的交互反馈,建议您提前准备以下图标:
麦克风状态图标:区分开启、静音及动态音量条。
身份标识图标:用于区分房主(Owner)、管理员(Admin)与普通成员。
网络信号图标:展示实时的上/下行网络质量。
特别说明:
我们在示例项目的 @tencentcloud/uikit-base-component-vue3 npm 包中提供了免版权的图标,您可以直接引入使用。
实现代码示例
1. 新增文件
ParticipantViewUI.vue 实现单个视频流挂件层 UI 渲染逻辑。<template><div class="stream-cover-container"><div class="corner-user-info-container"><divv-if="showMasterIcon || showAdminIcon":class="{ 'master-icon': showMasterIcon, 'admin-icon': showAdminIcon }"><IconUser /></div><div v-if="!isScreenStream" :class="['audio-icon-container']"><div class="audio-level-container"><div class="audio-level" :style="audioLevelStyle" /></div><IconMicOff v-if="!isMicrophoneOn" class="audio-icon" size="20" /><IconMicOn v-else class="audio-icon" size="20" /></div><span class="user-name" :title="displayName">{{ displayName }}</span></div></div></template><script setup lang="ts">import { computed, defineProps } from 'vue';import {IconUser,IconMicOff,IconMicOn,} from '@tencentcloud/uikit-base-component-vue3';import { RoomParticipantRole, DeviceStatus, VideoStreamType, useRoomParticipantState } from 'tuikit-atomicx-vue3/room';import type { RoomParticipant } from 'tuikit-atomicx-vue3/room';interface Props {participant: RoomParticipant;streamType: VideoStreamType;}const props = defineProps<Props>();const { speakingUsers } = useRoomParticipantState();const speakingAudioVolume = computed(() => speakingUsers.value.get(props.participant.userId) || 0);const audioLevelStyle = computed(() => {if (props.participant.microphoneStatus === DeviceStatus.Off || !speakingAudioVolume.value) {return '';}return `height: ${speakingAudioVolume.value * 4}%`;});const isMicrophoneOn = computed(() => props.participant.microphoneStatus === DeviceStatus.On);const displayName = computed(() => props.participant.nameCard || props.participant.userName || props.participant.userId);const showMasterIcon = computed(() => {const { role } = props.participant;return role === RoomParticipantRole.Owner && props.streamType === VideoStreamType.Camera;});const showAdminIcon = computed(() => {const { role } = props.participant;return (role === RoomParticipantRole.Admin && props.streamType === VideoStreamType.Camera);});</script><style lang="scss" scoped>.stream-cover-container { position: absolute; top: 0; left: 0; width: 100%; height: 100%; border-radius: 12px; pointer-events: none; .corner-user-info-container { position: absolute; bottom: 8px; left: 8px; display: flex; align-content: center; align-items: center; box-sizing: border-box; min-width: 118px; max-width: calc(100% - 24px); padding-right: 10px; height: 32px; overflow: hidden; font-size: 14px; color: var(--uikit-color-white-1); border-radius: 16px; background-color: var(--uikit-color-black-5); .master-icon, .admin-icon { display: flex; flex-shrink: 0; align-items: center; justify-content: center; width: 32px; height: 32px; margin-left: 0; border-radius: 50%; background-color: var(--button-color-primary-default); } .admin-icon { background-color: var(--text-color-warning); } .audio-icon-container { margin-left: 4px; position: relative; width: 20px; height: 20px; flex-shrink: 0; min-width: 20px; &:first-child { margin-left: 8px; } .audio-level-container { position: absolute; top: 2px; left: 6px; display: flex; flex-flow: column-reverse wrap; justify-content: space-between; width: 8px; height: 12px; overflow: hidden; border-radius: 4px; .audio-level { width: 100%; background-color: var(--text-color-success); transition: height 0.2s; } } .audio-icon { position: absolute; top: 0; left: 0; } } .user-name { margin-left: 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; } .screen-icon { flex-shrink: 0; min-width: 0; color: var(--uikit-color-white-1); margin-left: 4px; margin-right: 2px; } .screen-info { margin-left: 4px; font-size: 12px; color: var(--uikit-color-white-1); flex-shrink: 0; min-width: 0; } }}</style>
2. 配置
RoomView 插槽,在页面中渲染视频流挂件内容。<template><div class="room-container"><RoomView :layout-template="currentLayout"><template #participantViewUI="{ participant, streamType }"><ParticipantViewUI :participant="participant" :stream-type="streamType" /></template></RoomView></div></template><script setup lang="ts">import { ref } from 'vue';import { RoomView, RoomLayoutTemplate } from 'tuikit-atomicx-vue3/room';// 引入新增的 ParticipantViewUI.vue 文件import ParticipantViewUI from './ParticipantViewUI.vue';// 默认使用宫格布局const currentLayout = ref(RoomLayoutTemplate.GridLayout);</script>
API 文档
State/Component | 功能描述 | API 文档 |
useRoomParticipantState | 包含房间内用户数据,用户管理接口。 |
常见问题
RoomView 是否包含屏幕分享流的渲染?
RoomView 内置远端屏幕分享流的渲染,对于本地屏幕共享您需要通过 participantViewUI 插槽完成本地屏幕分享的占位 UI。