观众连线(React Native)

最近更新时间:2026-04-28 15:05:03

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

核心场景

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

实现步骤

步骤1:组件集成

请参考 开播与观看(React Native) 集成 AtomicXCore,并完成 LiveCoreView 的接入。

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

观众端实现

作为观众,您的核心任务是发起申请、接收结果主动下麦
1. 发起连麦申请
当用户点击界面上的"申请连麦"按钮时,调用 applyForSeat 方法。
import { useCoGuestState } from 'react-native-tuikit-atomic-x/lib/module/atomic-x/state/CoGuestState';

const liveID = 'xxx'; // 您当前加入直播间的 liveID

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

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

const liveID = 'xxx'; // 您当前加入直播间的 liveID
const { addCoGuestGuestListener, removeCoGuestGuestListener } = useCoGuestState(liveID);
const { openLocalMicrophone, openLocalCamera } = useDeviceState(liveID);

// 在页面初始化时订阅事件
const handleGuestApplicationResponded = (event) => {
if (event.isAccept) {
console.log('上麦申请被同意');
openLocalMicrophone();
openLocalCamera({ isFront: true });
// 在此更新 UI,例如变更申请按钮状态,显示为连麦中
} else {
console.log('上麦申请被拒绝');
// 弹窗提示用户申请被拒绝
}
};

useEffect(() => {
addCoGuestGuestListener('onGuestApplicationResponded', handleGuestApplicationResponded);

return () => {
removeCoGuestGuestListener('onGuestApplicationResponded', handleGuestApplicationResponded);
};
}, []);
3. 主动下麦
当连麦观众想结束互动时,调用 disconnect 方法即可返回普通观众状态。
import { useCoGuestState } from 'react-native-tuikit-atomic-x/lib/module/atomic-x/state/CoGuestState';

const liveID = 'xxx'; // 您当前加入直播间的 liveID
const { disconnect } = useCoGuestState(liveID);

// 用户点击"下麦"按钮
const handleDisconnect = () => {
disconnect({
liveID,
onSuccess: () => { console.log('下麦成功'); },
onError: (error) => { console.log('下麦失败', error); },
});
};
4. (可选) 取消申请
如果观众在主播处理前想撤回申请,可以调用 cancelApplication
import { useCoGuestState } from 'react-native-tuikit-atomic-x/lib/module/atomic-x/state/CoGuestState';

const liveID = 'xxx'; // 您当前加入的直播间的 liveID
const { cancelApplication } = useCoGuestState(liveID);

// 用户在等待时,点击"取消申请"
const handleCancelRequest = () => {
cancelApplication({
liveID,
onSuccess: () => { console.log('取消申请成功'); },
onError: (error) => { console.log('取消申请失败', error); },
});
};

主播端实现

作为主播,您的核心任务是接收申请、展示申请列表处理申请
1. 监听新的连麦申请
通过 CoGuestHostListener 订阅 onGuestApplicationReceived 事件后,您可以在有新观众申请时立即收到通知,并给出提示。
import { useEffect } from 'react';
import { useCoGuestState } from 'react-native-tuikit-atomic-x/lib/module/atomic-x/state/CoGuestState';

const liveID = 'xxx'; // 您当前直播间的 liveID
const { addCoGuestHostListener, removeCoGuestHostListener } = useCoGuestState(liveID);

// 在页面初始化时订阅事件
const handleGuestApplicationReceived = (event) => {
console.log('收到观众的连麦申请', event);
// 在此更新 UI,例如在"申请列表"按钮上显示红点
};

useEffect(() => {
addCoGuestHostListener('onGuestApplicationReceived', handleGuestApplicationReceived);

return () => {
removeCoGuestHostListener('onGuestApplicationReceived', handleGuestApplicationReceived);
};
}, []);
2. 展示申请列表
CoGuestState 会实时维护当前的申请者列表,数据本身是响应式数据,您可以直接在 UI 上使用。
import { useCoGuestState } from 'react-native-tuikit-atomic-x/lib/module/atomic-x/state/CoGuestState';

const liveID = 'xxx'; // 您当前直播间的 liveID
const { applicants } = useCoGuestState(liveID);

// 在这里绘制您的 "申请者列表" UI
<FlatList
data={applicants || []}
keyExtractor={(item) => item.userID}
renderItem={({ item }) => (
<View>
<Text>{item.userName}</Text>
<Text>{item.userID}</Text>
</View>
)}
/>
3. 处理连麦申请
当您在列表中选择一位观众并点击"同意"或"拒绝"时,调用相应的方法。
import { useCoGuestState } from 'react-native-tuikit-atomic-x/lib/module/atomic-x/state/CoGuestState';

const liveID = 'xxx'; // 您当前直播间的 liveID
const { acceptApplication, rejectApplication } = useCoGuestState(liveID);

// 主播点击"同意"按钮,传入申请者的 userID
const handleAccept = (userID) => {
acceptApplication({
liveID,
userID,
onSuccess: () => { console.log('同意上麦申请成功'); },
onError: (error) => { console.log('同意上麦申请失败', error); },
});
};

// 主播点击"拒绝"按钮,传入申请者的 userID
const handleReject = (userID) => {
rejectApplication({
liveID,
userID,
onSuccess: () => { console.log('拒绝上麦申请成功'); },
onError: (error) => { console.log('拒绝上麦申请失败', error); },
});
};

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

主播端实现

1. 向观众发起邀请
当主播在观众列表中选择某人并点击"邀请连麦"时,调用 inviteToSeat 方法。
import { useCoGuestState } from 'react-native-tuikit-atomic-x/lib/module/atomic-x/state/CoGuestState';

const liveID = 'xxx'; // 您当前直播间的 liveID
const { inviteToSeat } = useCoGuestState(liveID);

// 主播选择观众并发起邀请
const handleInviteToSeat = (inviteeID) => {
inviteToSeat({
liveID,
inviteeID,
seatIndex: -1, // 邀请的麦位,传 -1, 随机分配麦位
timeout: 30, // 超时时间
onSuccess: () => { console.log('已向观众发送上麦邀请'); },
onError: (error) => { console.log('发送上麦邀请失败', error); },
});
};
2. 监听观众的回应
通过 CoGuestHostListener 订阅 onHostInvitationResponded 事件。
import { useEffect } from 'react';
import { useCoGuestState } from 'react-native-tuikit-atomic-x/lib/module/atomic-x/state/CoGuestState';

const liveID = 'xxx'; // 您当前直播间的 liveID
const { addCoGuestHostListener, removeCoGuestHostListener } = useCoGuestState(liveID);

// 在页面初始化时订阅事件
const handleHostInvitationResponded = (event) => {
if (event.isAccept) {
console.log('观众接受了您的邀请');
} else {
console.log('观众拒绝了您的邀请');
}
};

useEffect(() => {
addCoGuestHostListener('onHostInvitationResponded', handleHostInvitationResponded);

return () => {
removeCoGuestHostListener('onHostInvitationResponded', handleHostInvitationResponded);
};
}, []);

观众端实现

1. 接收主播的邀请
通过 addCoGuestGuestListener 订阅 onHostInvitationReceived 事件。
import { useEffect } from 'react';
import { useCoGuestState } from 'react-native-tuikit-atomic-x/lib/module/atomic-x/state/CoGuestState';
import { useLiveListState } from 'react-native-tuikit-atomic-x/lib/module/atomic-x/state/LiveListState';

const liveID = 'xxx'; // 您当前加入直播间的 liveID
const { addCoGuestGuestListener, removeCoGuestGuestListener } = useCoGuestState(liveID);
const { currentLive } = useLiveListState();

// 在页面初始化时订阅事件
const handleHostInvitationReceived = (event) => {
console.log('收到主播邀请连麦', event);
const inviterID = currentLive?.liveOwner?.userID || '';
// 在此弹出一个对话框,让用户选择"接受"或"拒绝"
};

useEffect(() => {
addCoGuestGuestListener('onHostInvitationReceived', handleHostInvitationReceived);

return () => {
removeCoGuestGuestListener('onHostInvitationReceived', handleHostInvitationReceived);
};
}, [currentLive]);
2. 响应邀请
当用户在弹出的对话框中做出选择后,调用相应的方法。
import { useCoGuestState } from 'react-native-tuikit-atomic-x/lib/module/atomic-x/state/CoGuestState';
import { useDeviceState } from 'react-native-tuikit-atomic-x/lib/module/atomic-x/state/DeviceState';
import { useLiveListState } from 'react-native-tuikit-atomic-x/lib/module/atomic-x/state/LiveListState';

const liveID = 'xxx'; // 您当前加入直播间的 liveID
const { acceptInvitation, rejectInvitation } = useCoGuestState(liveID);
const { openLocalMicrophone, openLocalCamera } = useDeviceState(liveID);
const { currentLive } = useLiveListState();

// 通过 currentLive 获取 inviterID
const inviterID = currentLive?.liveOwner?.userID || '';

// 用户点击"接受"
const handleAcceptInvitation = () => {
acceptInvitation({
liveID,
inviterID,
onSuccess: () => {
console.log('接受邀请成功');
openLocalMicrophone();
openLocalCamera({ isFront: true });
},
onError: (error) => { console.log('接受邀请失败', error); },
});
};

// 用户点击"拒绝"
const handleRejectInvitation = () => {
rejectInvitation({
liveID,
inviterID,
onSuccess: () => { console.log('拒绝邀请成功'); },
onError: (error) => { console.log('拒绝邀请失败', error); },
});
};

运行效果

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


完善 UI 细节

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

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

实现效果



实现方式

步骤 1:创建自定义 UI 覆盖层组件 (ParticipantOverlay.js)
这个组件是我们的"UI 贴纸",它只负责根据数据渲染 UI,不关心视频。 在您的 components 目录下,创建一个新文件 ParticipantOverlay.js。 将以下代码复制到文件中。
注意:
React Native 中 region 的坐标基于 SDK 返回的 canvas(服务端画布尺寸),需要通过 canvas.w / canvas.h 与屏幕宽度计算缩放比例,不能使用固定的设计稿宽度。
import React, { useMemo } from 'react';
import { View, Text, Image, StyleSheet, Dimensions } from 'react-native';

const DEFAULT_AVATAR = 'https://liteav-test-1252463788.cos.ap-guangzhou.myqcloud.com/voice_room/voice_room_cover1.png';
const { width: SCREEN_WIDTH } = Dimensions.get('window');

// 接收来自父组件的核心数据:seatList 和 canvas
export default function ParticipantOverlay({ seatList, canvas }) {
if (!seatList || seatList.length === 0) return null;

// 根据 canvas 计算缩放比例
const scale = useMemo(() => {
if (!canvas?.w || !canvas?.h) return { scaleX: 1, scaleY: 1 };
const displayWidth = SCREEN_WIDTH;
const displayHeight = SCREEN_WIDTH * (canvas.h / canvas.w);
return {
scaleX: displayWidth / canvas.w,
scaleY: displayHeight / canvas.h,
};
}, [canvas]);

// 计算每个成员 UI 容器的精确位置和大小
const getParticipantStyle = (participant) => {
if (!participant?.region) return {};
return {
position: 'absolute',
left: participant.region.x * scale.scaleX,
top: participant.region.y * scale.scaleY,
width: participant.region.w * scale.scaleX,
height: participant.region.h * scale.scaleY,
};
};

return (
// overlay-container
<View style={styles.overlayContainer} pointerEvents="none">
{/* 遍历 seatList,为每个麦位成员创建独立的 UI 容器 */}
{seatList.map((participant) => {
if (!participant?.userInfo?.userID) return null;
const isCameraOff = participant.userInfo.cameraStatus === 'OFF';

return (
// participant-ui-container
<View key={participant.userInfo.userID} style={getParticipantStyle(participant)}>
{/* 条件渲染:根据摄像头状态显示不同 UI */}
{isCameraOff ? (
// 1. 当摄像头关闭时,显示居中的头像和昵称
<View style={styles.avatarPlaceholder}>
<Image
style={styles.avatarImage}
source={{ uri: participant.userInfo.userAvatar || DEFAULT_AVATAR }}
/>
<Text style={styles.avatarName}>
{participant.userInfo.userName || participant.userInfo.userID}
</Text>
</View>
) : (
// 2. 当摄像头开启时,显示左下角的昵称条
<View style={styles.nicknameBar}>
<Text style={styles.nicknameText}>
{participant.userInfo.userName || participant.userInfo.userID}
</Text>
</View>
)}
</View>
);
})}
</View>
);
}

const styles = StyleSheet.create({
overlayContainer: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
},
avatarPlaceholder: {
flex: 1,
backgroundColor: '#2E323A',
justifyContent: 'center',
alignItems: 'center',
},
avatarImage: {
width: 60,
height: 60,
borderRadius: 30,
},
avatarName: {
marginTop: 8,
fontSize: 13,
color: '#fff',
},
nicknameBar: {
position: 'absolute',
left: 6,
bottom: 6,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
paddingHorizontal: 8,
paddingVertical: 3,
borderRadius: 10,
},
nicknameText: {
color: '#fff',
fontSize: 11,
},
});
步骤 2:在直播间页面中组合所有组件
这是将视频层和 UI 层"叠"在一起的关键步骤。 打开您的直播间页面文件,按照以下结构和样式进行修改。
import React from 'react';
import { StyleSheet, View } from 'react-native';
import { LiveCoreView } from 'react-native-tuikit-atomic-x/lib/module/components/LiveCoreView';
import { useLiveSeatState } from 'react-native-tuikit-atomic-x/lib/module/atomic-x/state/LiveSeatState';
import ParticipantOverlay from '../../components/ParticipantOverlay';

export default function YourAnchorScreen({ route, navigation }) {
const { liveID } = route.params || {};
const { seatList, canvas } = useLiveSeatState(liveID);

return (
// page-container
<View style={styles.pageContainer}>
{/* live-container */}
<View style={styles.liveContainer}>
{/* 底层:视频渲染层 */}
<LiveCoreView
liveID={liveID}
coreViewType="pushView" // 主播端: pushView, 观众端: playView
style={styles.videoLayer}
/>
{/* 上层:自定义 UI 覆盖层 */}
<ParticipantOverlay seatList={seatList} canvas={canvas} />
</View>
{/* 页面的其他 UI,例如底部的操作栏 */}
{/* <View style={styles.bottomControls}>...</View> */}
</View>
);
}

const styles = StyleSheet.create({
pageContainer: {
flex: 1,
backgroundColor: '#000',
},
liveContainer: {
flex: 1,
},
videoLayer: {
flex: 1,
},
});

API 文档

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

常见问题

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

问题描述: 按照"完善 UI 细节"章节添加了 ParticipantOverlay 组件后,显示的昵称条或头像占位图没有精确地覆盖在对应的视频小窗上,存在偏移或尺寸不匹配。
原因分析: UI 的精准对齐依赖于缩放比例的正确计算。ParticipantOverlay 组件内部通过 participant.region.x * scale.scaleX 来计算每个 UI 元素的位置。如果缩放比例不正确,计算出的位置自然会是错误的。
解决方案:
确保 canvas 正确传递:ParticipantOverlay 需要接收 useLiveSeatState(liveID) 返回的 canvas 对象(服务端画布尺寸),组件内部会根据 canvas.w / canvas.h 与屏幕实际宽度自动计算缩放比例:scaleX = 屏幕宽度 / canvas.w。
确保 canvas 数据已就绪:canvas 是在创建或加入直播间后由 SDK 异步返回的响应式数据,组件会在 canvas 更新后自动重新计算布局。

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

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

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

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