LiveListState 是 AtomicXCore 中负责管理直播房间列表、创建、加入以及维护房间状态的核心模块。通过 LiveListState,您可以为您的应用构建完整的直播生命周期管理。
核心功能
直播列表拉取:获取当前所有公开的直播间列表,支持分页加载。
直播生命周期管理:提供从创建、开播、加入、离开到结束直播的全套流程接口。
直播信息更新:主播可以随时更新直播间的公开信息,例如封面、公告等。
实时事件监听:监听直播结束、用户被踢出等关键事件。
自定义业务数据:强大的自定义元数据(
Metadata)能力,允许您在房间内存储和同步任何业务相关的信息,例如直播状态、音乐信息、自定义角色等 。实现步骤
步骤1:组件集成
语聊房:请参考 开播与收听 集成
AtomicXCore,完成接入。步骤2:实现观众从直播列表进入直播间
创建一个展示直播列表的页面。当用户点击某个卡片时,获取该直播间的
liveID,并跳转到观众观看页面。import React, { useEffect } from 'react';import {StyleSheet, Text, View, FlatList, TouchableOpacity,Dimensions, ImageBackground,} from 'react-native';import { useLiveListState } from 'react-native-tuikit-atomic-x/lib/module/atomic-x/state/LiveListState';const defaultCover = 'https://liteav-test-1252463788.cos.ap-guangzhou.myqcloud.com/voice_room/voice_room_cover1.png';const { width: SCREEN_WIDTH } = Dimensions.get('window');const CARD_GAP = 12;const CARD_PADDING = 16;const CARD_WIDTH = (SCREEN_WIDTH - CARD_PADDING * 2 - CARD_GAP) / 2;const CARD_HEIGHT = CARD_WIDTH * 1.3;// 直播卡片组件function LiveCard({ item, onPress }) {return (<TouchableOpacity style={styles.card} activeOpacity={0.75} onPress={onPress}><ImageBackgroundsource={{ uri: item.coverURL || defaultCover }}style={styles.cardBg}resizeMode="cover"><View style={styles.viewerBadge}><Text style={styles.viewerBadgeText}>{item.totalViewerCount || 0}人看过</Text></View><View style={styles.bottomInfo}><Text style={styles.liveName} numberOfLines={1}>{item.liveName}</Text><Text style={styles.ownerName} numberOfLines={1}>{item.liveOwner?.userName}</Text></View></ImageBackground></TouchableOpacity>);}export default function LiveListScreen({ navigation }) {// 状态管理const { liveList, liveListCursor, fetchLiveList, joinLive } = useLiveListState();// 初始化useEffect(() => {fetchLiveList({ cursor: liveListCursor, count: 20 });}, []);// 加入直播const handleJoinLive = (live) => {joinLive({liveID: live.liveID,onSuccess: () => {navigation.navigate('Audience', { liveID: live.liveID }); // 跳转至您的观众页面},onError: (error) => {console.log('进入语聊房失败', error);},});};return (<View style={styles.container}>{/* 直播列表 */}<FlatListdata={liveList || []}keyExtractor={(item) => item.liveID}numColumns={2}columnWrapperStyle={styles.row}contentContainerStyle={styles.listContent}renderItem={({ item }) => (<LiveCard item={item} onPress={() => handleJoinLive(item)} />)}/></View>);}const styles = StyleSheet.create({container: {flex: 1,backgroundColor: '#fff',},listContent: {paddingHorizontal: CARD_PADDING,paddingTop: 12,},row: {justifyContent: 'space-between',marginBottom: CARD_GAP,},card: {width: CARD_WIDTH,height: CARD_HEIGHT,borderRadius: 12,overflow: 'hidden',},cardBg: {flex: 1,justifyContent: 'space-between',},viewerBadge: {alignSelf: 'flex-start',backgroundColor: 'rgba(0, 0, 0, 0.45)',borderRadius: 10,paddingHorizontal: 8,paddingVertical: 3,margin: 8,},viewerBadgeText: {fontSize: 11,color: '#fff',},bottomInfo: {padding: 8,paddingTop: 20,backgroundColor: 'rgba(0, 0, 0, 0.35)',},liveName: {fontSize: 14,fontWeight: '600',color: '#fff',},ownerName: {fontSize: 12,color: 'rgba(255, 255, 255, 0.8)',marginTop: 2,},});
LiveInfo 参数说明参数名 | 类型 | 描述 |
liveID | string | 直播间的唯一标识符 |
liveName | string | 直播间的标题 |
coverURL | string | 直播间的封面图片地址 |
liveOwner | LiveUserInfo | 房主的个人信息 |
totalViewerCount | number | 直播间的总观看人数 |
categoryList | [number] | 直播间的分类标签列表 |
notice | string | 直播间的公告信息 |
metaData | [string: string] | 开发者自定义的元数据,用于实现复杂的业务场景 |
功能进阶
场景一:实现直播列表的分类展示
在 App 的直播广场页,顶部设有“热门”、“音乐”、“游戏”等分类标签。用户点击不同的标签后,下方的直播列表会动态筛选,只展示对应分类的直播间,从而帮助用户快速发现感兴趣的内容。

实现方式
核心是利用
LiveInfo 模型中的 categoryList 属性。当主播开播设置分类后,fetchLiveList 返回的 LiveInfo 对象中就会包含这些分类信息。您的 App 在获取到完整的直播列表后,只需在客户端根据用户选择的分类,对这个列表进行一次简单的筛选,然后刷新 UI 即可。代码示例
以下示例展示了如何在直播列表页中增加根据一个
categoryList 来处理数据和筛选逻辑的 UI。import React, { useEffect, useState, useMemo } from 'react';import {StyleSheet, Text, View, FlatList, TouchableOpacity,Dimensions, ImageBackground, ScrollView,} from 'react-native';import { useLiveListState } from 'react-native-tuikit-atomic-x/lib/module/atomic-x/state/LiveListState';const defaultCover = 'https://liteav-test-1252463788.cos.ap-guangzhou.myqcloud.com/voice_room/voice_room_cover1.png';const { width: SCREEN_WIDTH } = Dimensions.get('window');const CARD_GAP = 12;const CARD_PADDING = 16;const CARD_WIDTH = (SCREEN_WIDTH - CARD_PADDING * 2 - CARD_GAP) / 2;const CARD_HEIGHT = CARD_WIDTH * 1.3;// 分类列表const categories = [{ id: 0, name: '全部' },{ id: 1, name: '游戏' },{ id: 2, name: '音乐' },{ id: 3, name: '聊天' },{ id: 4, name: '体育' },{ id: 5, name: '娱乐' },];// 分类名称映射const categoryMap = {1: '游戏',2: '音乐',3: '聊天',4: '体育',5: '娱乐',};// 获取分类名称const getCategoryName = (categoryId) => {return categoryMap[categoryId] || '其他';};// 直播卡片组件function LiveCard({ item, onPress }) {const firstCategory = item.categoryList?.[0];return (<TouchableOpacity style={styles.card} activeOpacity={0.75} onPress={onPress}><ImageBackgroundsource={{ uri: item.coverURL || defaultCover }}style={styles.cardBg}resizeMode="cover"><View style={styles.viewerBadge}><Text style={styles.viewerBadgeText}>{item.totalViewerCount || 0}人看过</Text></View><View style={styles.bottomInfo}>{firstCategory ? (<View style={styles.categoryBadge}><Text style={styles.categoryBadgeText}>{getCategoryName(firstCategory)}</Text></View>) : null}<Text style={styles.liveName} numberOfLines={1}>{item.liveName}</Text><Text style={styles.ownerName} numberOfLines={1}>{item.liveOwner?.userName}</Text></View></ImageBackground></TouchableOpacity>);}export default function LiveListScreen({ navigation }) {// 状态管理const { liveList, fetchLiveList, joinLive } = useLiveListState();// 当前选中的分类,0 代表全部const [selectedCategory, setSelectedCategory] = useState(0);// 初始化useEffect(() => {fetchLiveList({ cursor: '', count: 20 });}, []);// 根据选中分类过滤列表const filteredList = useMemo(() => {const list = liveList || [];if (selectedCategory === 0) {return list;}return list.filter((live) => {const categoryList = live.categoryList || [];return categoryList.includes(selectedCategory);});}, [liveList, selectedCategory]);// 加入直播const handleJoinLive = (live) => {joinLive({liveID: live.liveID,onSuccess: () => {navigation.navigate('Audience', { liveID: live.liveID }); // 跳转至您的目标页面},onError: (error) => {console.log('进入语聊房失败', error);},});};return (<View style={styles.container}>{/* 分类标签栏 */}<View style={styles.categoryTabsWrapper}><ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.categoryTabs}>{categories.map((cat) => (<TouchableOpacitykey={cat.id}style={[styles.tab, selectedCategory === cat.id && styles.tabActive]}activeOpacity={0.7}onPress={() => setSelectedCategory(cat.id)}><Text style={[styles.tabText, selectedCategory === cat.id && styles.tabTextActive]}>{cat.name}</Text></TouchableOpacity>))}</ScrollView></View>{/* 直播列表 */}<FlatListdata={filteredList}keyExtractor={(item) => item.liveID}numColumns={2}columnWrapperStyle={styles.row}contentContainerStyle={styles.listContent}renderItem={({ item }) => (<LiveCard item={item} onPress={() => handleJoinLive(item)} />)}/></View>);}const styles = StyleSheet.create({container: {flex: 1,backgroundColor: '#fff',},categoryTabsWrapper: {backgroundColor: '#f9f9f9',borderBottomWidth: StyleSheet.hairlineWidth,borderBottomColor: '#e8e8e8',},categoryTabs: {flexDirection: 'row',paddingHorizontal: 16,paddingVertical: 10,},tab: {paddingHorizontal: 14,paddingVertical: 6,marginRight: 10,backgroundColor: '#fff',borderWidth: 1,borderColor: '#e0e0e0',borderRadius: 16,},tabActive: {backgroundColor: '#0468FC',borderColor: '#0468FC',},tabText: {fontSize: 13,color: '#666',},tabTextActive: {color: '#fff',},listContent: {paddingHorizontal: CARD_PADDING,paddingTop: 12,},row: {justifyContent: 'space-between',marginBottom: CARD_GAP,},card: {width: CARD_WIDTH,height: CARD_HEIGHT,borderRadius: 12,overflow: 'hidden',},cardBg: {flex: 1,justifyContent: 'space-between',},viewerBadge: {alignSelf: 'flex-start',backgroundColor: 'rgba(0, 0, 0, 0.45)',borderRadius: 10,paddingHorizontal: 8,paddingVertical: 3,margin: 8,},viewerBadgeText: {fontSize: 11,color: '#fff',},categoryBadge: {alignSelf: 'flex-start',backgroundColor: 'rgba(4, 104, 252, 0.9)',borderRadius: 3,paddingHorizontal: 6,paddingVertical: 2,marginBottom: 4,},categoryBadgeText: {fontSize: 10,color: '#fff',fontWeight: '600',},bottomInfo: {padding: 8,paddingTop: 20,backgroundColor: 'rgba(0, 0, 0, 0.35)',},liveName: {fontSize: 14,fontWeight: '600',color: '#fff',},ownerName: {fontSize: 12,color: 'rgba(255, 255, 255, 0.8)',marginTop: 2,},});
场景二:实现电商直播的商品信息展示
在电商直播中,主播正在讲解一款商品。此时,所有观众的直播画面下方都会弹出一张商品卡片,实时展示该商品的图片、名称和价格。当主播切换讲解下一件商品时,所有观众端的卡片都会自动、同步更新。

实现方式
主播端将当前商品的结构化信息(建议使用 JSON 格式)通过
updateLiveMetaData 接口设置到一个自定义的 key(例如 "product_info")中。AtomicXCore 会将这个变更实时同步给所有观众。观众端只需监听 currentLive 的变化,一旦发现 metaData 的 product_info 的值更新,就解析其中的 JSON 数据并刷新商品卡片 UI。代码示例
import React, { useEffect, useState } from 'react';import { useLiveListState } from 'react-native-tuikit-atomic-x/lib/module/atomic-x/state/LiveListState';// 主播端示例组件function AnchorProductPanel() {const { updateLiveMetaData } = useLiveListState();// 主播端:推送商品信息给所有观众const updateProduct = (product) => {updateLiveMetaData({metaData: {product_id: product.id,product_name: product.name,product_price: product.price,product_image_url: product.image_url,},onSuccess: () => {console.log(`商品 ${product.name} 信息推送成功`);},onError: (error) => {console.log('商品信息推送失败', error);},});};return null; // 替换为您的主播端 UI}// 观众端示例组件function AudienceProductCard() {const { currentLive } = useLiveListState();const [product, setProduct] = useState(null);// 观众端:监听 currentLive,解析商品信息useEffect(() => {if (!currentLive) return;const metaData = currentLive.metaData;if (metaData?.product_id) {const newProduct = {id: metaData.product_id,name: metaData.product_name,price: metaData.product_price,imageUrl: metaData.product.image_url,};setProduct(newProduct);// 在此根据 newProduct 里面的字段来渲染您的商品 UI}}, [currentLive]);return null; // 替换为您的观众端商品卡片 UI}
API 文档
State | 功能描述 | API 文档 |
LiveListState | 直播间全生命周期管理:创建 / 加入 / 离开 / 销毁房间,查询房间列表,修改直播信息(名称、公告等),监听直播状态(例如被踢出、结束)。 |
常见问题
使用 updateLiveMetaData 时有哪些需要注意的限制和规则?
为了保证系统的稳定和高效,
metaData 的使用遵循以下规则:权限:只有房主和管理员可以调用
updateLiveMetaData。普通观众没有权限。数量与大小限制:
单个房间最多支持 10 个 key。
每个 key 的长度不超过 50 字节,每个 value 的长度不超过 2KB。
单个房间所有 value 的总大小不超过 16KB。
冲突解决:
metaData 的更新机制是“后来者覆盖”。如果多个管理员在短时间内修改同一个 key,最后一次的修改会生效。为避免冲突,业务设计时应避免多人同时修改同一个关键信息。