观众连线 (uni-app)

最近更新时间:2025-11-18 21:37:22

我的收藏
AtomicXCore 提供了 CoGuestState 模块,专门用于管理观众连麦的完整业务流程。您无需关心复杂的状态同步和信令交互,只需调用几个简单的方法,即可为您的直播添加强大的观众与主播音视频互动功能。

核心场景

CoGuestState 支持以下两种最主流的连麦场景:
观众申请上麦:观众主动发起连麦请求,主播在收到请求后进行同意或拒绝。
主播邀请上麦:主播可以主动向直播间内的任意一位观众发起连麦邀请。

实现步骤

步骤1:组件集成

请参考 开始直播(uni-app) 集成 AtomicXCore,并完成 LiveCoreView 的接入。

步骤2:实现观众申请上麦

观众端实现

作为观众,您的核心任务是发起申请、接收结果主动下麦
1. 发起连麦申请
当用户点击界面上的"申请连麦"按钮时,调用 applyForSeat 方法。
import { useCoGuestState } from "@/uni_modules/tuikit-atomic-x/state/CoGuestState";
const liveID = "xxx" // 您当前加入直播间的 liveID

//通过 liveID 获取 CoGuestState 的实例
const { applyForSeat } = useGuestState(liveID);

// 用户点击“申请连麦”
const handleRequestToConnect = () => {
applyForSeat({
liveID,
seatIndex: -1, // 申请的麦位,申请上麦传 -1, 随机分配麦位
timeout: 30, // 请求超时时间,例如 30s
})
}
2. 监听主播的响应
通过 addCoGuestGuestListener订阅'onGuestApplicationResponded'事件,您可以接收到主播的处理结果。
import { onMounted } from 'vue';
import { useCoGuestState } from "@/uni_modules/tuikit-atomic-x/state/CoGuestState";
import { useDeviceState } from "@/uni_modules/tuikit-atomic-x/state/DeviceState";
const liveID = "xxx" // 您当前加入直播间的 liveID
const { addCoGuestGuestListener } = useCoGuestState(liveID);
const { openLocalMicrophone, openLocalCamera } = useDeviceState(liveID);

// 在页面初始化时订阅事件
onMounted(() => {
addCoGuestGuestListener(liveID, 'onGuestApplicationResponded', handleGuestApplicationResponded)
})
const handleGuestApplicationResponded = {
callback: (event) => {
const res = JSON.parse(event)
if(res.isAccept){
console.log('上麦申请被同意')
// 1.打开麦克风
openLocalMicrophone()
openLocalCamera({isFront: true })
// 2. 在此更新 UI,例如变更申请按钮状态,显示为连麦中
} else {
console.log('上麦申请被拒绝')
// 弹窗提示用户申请被拒绝
}
}
}
3. 主动下麦
当连麦观众想结束互动时,调用 disconnect 方法即可返回普通观众状态。
import { useCoGuestState } from "@/uni_modules/tuikit-atomic-x/state/CoGuestState";
const liveID = "xxx" // 您当前加入直播间的 liveID
const { disconnect } = useCoGuestState(liveID);

// 用户点击“下麦”按钮
const leaveSeat = () => {
disconnect({
liveID,
success: () => {
console.log('下麦成功')
},
fail: (error) => {
console.log('下麦失败', error)
}
})
}
4. (可选) 取消申请
如果观众在主播处理前想撤回申请,可以调用 cancelApplication
import { useCoGuestState } from "@/uni_modules/tuikit-atomic-x/state/CoGuestState";
const liveID = "xxx" // 您当前加入的直播间的 liveID
const { cancelApplication } = useCoGuestState(liveID);

// 用户在等待时,点击“取消申请”
const handelCancelRequest = () => {
cancelApplication({
liveID,
success: () => {
console.log('取消申请成功')
},
fail: (error) => {
console.log('取消申请失败', error)
}
})
}

主播端实现

作为主播,您的核心任务是接收申请、展示申请列表处理申请
1. 监听新的连麦申请
通过 CoGuestHostListener订阅 'onGuestApplicationReceived'事件后,您可以在有新观众申请时立即收到通知,并给出提示。
import { onMounted } from 'vue';
import { useCoGuestState } from "@/uni_modules/tuikit-atomic-x/state/CoGuestState";
const liveID = "xxx" // 您当前直播间的 liveID
const { addCoGuestHostListener } = useCoGuestState(liveID);

// 在页面初始化时订阅事件
onMounted(() => {
addCoGuestHostListener(liveID, 'onGuestApplicationReceived', handleGuestApplicationReceived)
})
const handleGuestApplicationReceived = {
callback: (event) => {
const res = JSON.parse(event)
console.log('收到观众的连麦申请')
// 在此更新 UI,例如在"申请列表"按钮上显示红点
}
}
2. 展示申请列表
CoGuestState 会实时维护当前的申请者列表,数据本身是响应式数据,您可以直接在 UI 上使用。
<template>
// 在这里绘制您的 “申请者列表” UI
<view v-for="audience in applicants" :key="audience?.userID">
<text>{ audience.userName }</text>
<text>{ audience.userID }</text>
</view>
</template>
<script setup>
import { useCoGuestState } from "@/uni_modules/tuikit-atomic-x/state/CoGuestState";
const liveID = "xxxx" // 您当前直播间的 liveID
const { applicants } = useCoGuestState(liveID);
</script>
3. 处理连麦申请
当您在列表中选择一位观众并点击"同意"或"拒绝"时,调用相应的方法。
import { useCoGuestState } from "@/uni_modules/tuikit-atomic-x/state/CoGuestState";
const liveID = "xxx" // 您当前直播间的 liveID
const { acceptApplication, rejectApplication } = useCoGuestState(liveID);

// 主播点击“同意”按钮,传入申请者的 userID
const handleAccept = (userID: string) => {
acceptApplication({
liveID,
userID,
success: () => {
console.log('已同意${userID}的上麦申请')
},
fail: (error) => {
console.log('同意${userID}的上麦申请失败', error)
}

})
}

// 主播点击“拒绝”按钮,传入申请者的 userID
const handleReject = (userID: string) => {
rejectApplication({
liveID,
userID,
success: () => {
console.log('已拒绝${userID}的上麦申请')
},
fail: (error) => {
console.log('拒绝${userID}的上麦申请失败', error)
}
})
}

步骤3:实现主播邀请上麦

主播端实现

1. 向观众发起邀请
当主播在观众列表中选择某人并点击"邀请连麦"时,调用 inviteToSeat 方法。
import { useCoGuestState } from "@/uni_modules/tuikit-atomic-x/state/CoGuestState";
const liveID = "xxx" // 您当前直播间的 liveID
const { inviteToSeat } = useCoGuestState(liveID);

// 主播选择观众并发起邀请
const handleInviteToSeat = (userID: string) => {
inviteToSeat({
liveID,
userID,
seatIndex: -1, // 邀请的麦位,传 -1, 随机分配麦位
timeout: 30, // 超时时间
success: () => {
console.log('已向观众 ${userID} 发送上麦邀请,请等待回应...')
},
fail: (error) => {
console.log('向观众 ${userID} 发送上麦邀请失败', error)
}
})
}
2. 监听观众的回应
通过 CoGuestHostListener 订阅 onHostInvitationResponded 事件。
import { onMounted } from 'vue';
import { useCoGuestState } from "@/uni_modules/tuikit-atomic-x/state/CoGuestState";
const liveID = "xxx" // 您当前直播间的 liveID
const { addCoGuestHostListener } = useCoGuestState(liveID);

// 在页面初始化时订阅事件
onMounted(() => {
addCoGuestHostListener(liveID, 'onHostInvitationResponded', handleHostInvitationResponded)
})
const handleHostInvitationResponded = {
callback: (event) => {
const res = JSON.parse(event)
if(res.isAccept){
console.log('观众 ${res.guestUser.userName} 接受了您的邀请')
} else {
console.log('观众 ${res.guestUser.userName} 拒绝了您的邀请')
}
}
}

观众端实现

1. 接收主播的邀请
通过 addCoGuestGuestListener 订阅 onHostInvitationReceived 事件。
import { onMounted } from 'vue';
import { useCoGuestState } from "@/uni_modules/tuikit-atomic-x/state/CoGuestState";
const liveID = "xxx" // 您当前加入直播间的 liveID
const { addCoGuestGuestListener } = useCoGuestState(liveID);
// 在页面初始化时订阅事件
onMounted(() => {
addCoGuestGuestListener(liveID, 'onHostInvitationReceived', handleHostInvitationReceived)
})
const handleHostInvitationReceived = {
callback: (event) => {
// 在此弹出一个对话框,让用户选择“接受”或“拒绝”
}
}
2. 响应邀请
当用户在弹出的对话框中做出选择后,调用相应的方法。
import { useCoGuestState } from "@/uni_modules/tuikit-atomic-x/state/CoGuestState";
import { useDeviceState } from "@/uni_modules/tuikit-atomic-x/state/DeviceState";
const liveID = "xxx" // 您当前加入的语音聊天室的 liveID
const { acceptInvitation, rejectInvitation } = useCoGuestState(liveID);
const { openLocalMicrophone, openLocalCamera } = useDeviceState(liveID);

const inviterID = "发起邀请的主播ID" // 从 onHostInvitationReceived 事件中获取

// 用户点击“接受”,
const handleAccept = () => {
acceptInvitation({
liveID,
inviterID,
success: () => {
openLocalMicrophone()
openLocalCamera({isFront: true})
},
fail: (error) => {
console.log('接受邀请失败', error)
}
})
}

运行效果

当您集成以上功能实现后,请分别使用两个观众与主播进行连麦操作,观众 A 同时打开摄像头和麦克风,观众 B 只打开麦克风,运行效果如下,您可以参考下一章节 完善 UI 细节 来定制您想要的 UI 逻辑。


完善 UI 细节

您可以通过修改源码的方式,在观众连麦的视频流画面上添加自定义视图,用于显示昵称、头像等信息,或在他们关闭摄像头时提供占位图,以优化视觉体验。

实现视频流画面的昵称显示

实现效果



实现方式

步骤 1:创建自定义 UI 覆盖层组件 (ParticipantOverlay.vue)
这个组件是我们的“UI贴纸”,它只负责根据数据渲染UI,不关心视频。
在您的 components 目录下,创建一个新文件 ParticipantOverlay.vue
将以下代码复制到文件中。
<template>
<view class="overlay-container">
<!-- 遍历 seatList,为每个麦位成员创建独立的UI容器 -->
<view
v-for="participant in seatList"
:key="participant.userInfo.userID"
class="participant-ui-container"
:style="getParticipantStyle(participant)"
>
<!-- 条件渲染:根据摄像头状态显示不同UI -->
<!-- 1. 当摄像头关闭时,显示居中的头像和昵称 -->
<view v-if="participant.userInfo.cameraStatus === 'OFF'" class="avatar-placeholder">
<image class="avatar-image" :src="participant.userInfo.userAvatar || '/static/default-avatar.png'" mode="aspectFill"></image>
<text class="avatar-name">{{ participant.userInfo.userName || participant.userInfo.userID }}</text>
</view>
<!-- 2. 当摄像头开启时,显示左下角的昵称条 -->
<view v-if="participant.userInfo.cameraStatus === 'ON'" class="nickname-bar">
<text class="nickname-text">{{ participant.userInfo.userName || participant.userInfo.userID }}</text>
</view>
</view>
</view>
</template>
<script setup>
import { computed } from 'vue';
// 1. 接收来自父组件的核心数据:seatList 和 scale
const props = defineProps({
seatList: {
type: Array,
default: () => []
},
scale: {
type: Object,
default: () => ({ scaleX: 1, scaleY: 1 })
}
});

// 2. 定义一个方法来计算每个成员UI容器的精确位置和大小
const getParticipantStyle = (participant) => {
if (!participant || !participant.region) return {};
return {
position: 'absolute',
left: `${participant.region.x * props.scale.scaleX}px`,
top: `${participant.region.y * props.scale.scaleY}px`,
width: `${participant.region.w * props.scale.scaleX}px`,
height: `${participant.region.h * props.scale.scaleY}px`,
};
};
</script>

<style scoped>
.overlay-container {
pointer-events: none;
}

.participant-ui-container {
overflow: hidden;
}
.avatar-placeholder {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #2E323A;
}
.avatar-image {
width: 160rpx;
height: 160rpx;
border-radius: 80rpx;
}
.avatar-name {
margin-top: 20rpx;
font-size: 28rpx;
color: #FFFFFF;
}
.nickname-bar {
position: absolute;
left: 10rpx;
bottom: 10rpx;
background-color: rgba(0, 0, 0, 0.5);
padding: 4rpx 16rpx;
border-radius: 20rpx;
}
.nickname-text {
color: #FFFFFF;
font-size: 24rpx;
}
</style>
步骤 2:在直播间页面中组合所有组件
这是将视频层和 UI 层“叠”在一起的关键步骤,同时需要解决 nvue 的布局问题。
打开您的直播间页面文件
按照以下结构和样式进行修改。
<template>
<view class="page-container">
<view class="live-container">
<!-- 底层:视频渲染层 -->
<LiveCoreView
:liveID="xxx" // 对应直播间的 liveID
:viewType="xxx" // 主播端: PUSH_VIEW, 观众端: PLAY_VIEW
class="video-layer"
/>
<!-- 上层:自定义UI覆盖层 -->
<ParticipantOverlay
:seatList="seatList"
:scale="scale"
class="ui-layer"
/>
</view>
<!-- 页面的其他UI,如底部的操作栏 -->
<!-- <view class="bottom-controls">...</view> -->
</view>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import ParticipantOverlay from '@/components/ParticipantOverlay.vue';
import { useLiveSeatState } from '@/uni_modules/tuikit-atomic-x/state/LiveSeatState';
const liveID = 'xxx' // 当前直播间的 liveID
const { seatList } = useLiveSeatState(liveID);
const scale = ref({ scaleX: 1, scaleY: 1 });

onMounted(() => {
// 在此获取并设置正确的 scale 值。
// 这是保证UI对齐的关键。请根据您的项目实际逻辑实现。
// 示例:
const { windowWidth } = uni.getSystemInfoSync();
const designWidth = 750; // rpx 设计稿宽度
scale.value.scaleX = windowWidth / designWidth;
scale.value.scaleY = scale.value.scaleX;
});
</script>

<style scoped>
.page-container {
flex: 1;
background-color: #000;
}

.live-container {
flex: 1;
position: relative;
}
.video-layer,
.ui-layer {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}

.video-layer {
z-index: 1;
}

.ui-layer {
z-index: 2;
}
</style>

API 文档

关于 CoGuestState 及其相关类的所有公开接口、属性和方法的详细信息,请您参阅 AtomicXCore 框架的官方 API 文档。本指南使用到的相关 State 如下:
State
功能描述
API 文档
DeviceState
音视频设备控制:麦克风(开关 / 音量)、摄像头(开关 / 切换 / 画质)、屏幕共享,设备状态实时监听。
CoGuestState
观众连麦管理:连麦申请 / 邀请 / 同意 / 拒绝,连麦成员权限控制(麦克风 / 摄像头),状态同步。
LiveSeatState
麦位信息管理:麦位列表管理、麦序管理。

常见问题

组件设置宽高不生效

问题描述: 将 LiveCoreView 或其父容器的widthheight 设置为 100% 后,组件没有按预期铺满整个屏幕或指定区域,甚至完全不显示。
原因分析: 文档明确指出,LiveCoreView 是原生组件,必须在 nvue 页面中使用。nvue 的布局引擎(Weex)与网页的 CSS 布局规则不同,它不支持百分比宽高,除非其父容器有一个由 flexbox或具体数值计算出的明确尺寸。如果父容器的高度是“自适应”的,那么 height: 100% 就没有参考基准,从而失效。
解决方案:
使用 Flex 布局:从页面的最外层根 <view> 开始,使用 flex: 1 样式,让容器逐层向下“撑满”可用空间。
使用绝对定位:对于需要覆盖的层(例如 LiveCoreView 和自定义的 ParticipantOverlay),将其父容器设置为 position: relativeflex: 1,然后将这些覆盖层设置为 position: absolute 并配合 top: 0; left: 0; right: 0; bottom: 0;,让它们从四个方向贴紧父容器边缘,这是在 nvue 中实现“占满”效果最可靠的方法。

自定义的昵称/头像 UI 与视频画面位置错乱

问题描述: 按照“完善 UI 细节”章节添加了 ParticipantOverlay 组件后,显示的昵称条或头像占位图没有精确地覆盖在对应的视频小窗上,存在偏移或尺寸不匹配。
原因分析: UI 的精准对齐依赖于 scale 这个缩放比例对象。ParticipantOverlay 组件内部通过 participant.region.x * props.scale.scaleX 来计算每个 UI 元素的位置。如果 scale 对象的值不正确或没有传递给组件,计算出的位置自然会是错误的。
解决方案:
确保 scale 值正确计算:必须在父页面中正确计算 scale 值。通常的计算方式是:scaleX = 屏幕实际宽度 / 设计稿宽度。
确保 scale 值正确传递:将计算好的 scale对象传递给 <ParticipantOverlay> 组件。

连麦功能无响应(申请/邀请/接受/拒绝均失败)

问题描述: 调用 applyForSeat, acceptApplication, inviteToSeatCoGuestState 提供的方法后,没有任何效果,也没有收到预期的回调。
原因分析: CoGuestState 的所有功能都与特定的直播间绑定。最常见的原因是初始化 CoGuestState 实例或调用其方法时,传入的 liveID 不正确、为 null 或与当前用户实际所在的直播间不符。
解决方案:
校验 liveID:确保在调用 useCoGuestState(liveID) 和所有相关方法(例如 applyForSeat({ liveID, ... }))时,使用的 liveID 是当前直播间真实且有效的 ID
保持 liveID 一致:在整个页面的生命周期中,所有与 CoGuestState 相关的操作都必须使用同一个 liveID

无法接收到连麦相关的事件通知

问题描述: 观众端收不到主播的邀请(onHostInvitationReceived),或者主播端收不到观众的申请(onGuestApplicationReceived)。
原因分析: 没有正确注册相应的事件监听器。CoGuestState 通过监听器模式来分发事件,如果忘记添加监听器,应用将无法对连麦事件做出反应。
解决方案: 确保在页面初始化时(例如在 onMounted 钩子中),根据角色(主播/观众)调用相应的方法来注册监听器:
主播端:调用 addCoGuestHostListener 来监听 onGuestApplicationReceivedonHostInvitationResponded 等事件。
观众端:调用 addCoGuestGuestListener 来监听 onGuestApplicationRespondedonHostInvitationReceived 等事件。

连麦成功后,对方看不到我的画面或听不到我的声音

问题描述: 作为观众,上麦申请被主播同意后,虽然麦位列表中有我,但我的视频画面没有出现,或者麦克风图标显示为关闭状态。
原因分析: 连麦成功(即进入麦位列表)和推流(即打开摄像头/麦克风)是两个独立的操作。CoGuestState 只负责将用户“搬上”麦位,但不会自动打开用户的媒体设备。
解决方案: 在确认上麦成功的逻辑回调中(例如 onGuestApplicationResponded 回调中 isAccepttrue 时,或调用 acceptInvitationsuccess 回调时),必须手动调用 DeviceState 提供的方法来开启媒体设备。