直播间列表(React Native)

最近更新时间:2026-04-22 11:09:02

我的收藏
LiveListStateAtomicXCore 中负责管理直播房间列表、创建、加入以及维护房间状态的核心模块。通过 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}>
<ImageBackground
source={{ 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}>
{/* 直播列表 */}
<FlatList
data={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}>
<ImageBackground
source={{ 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) => (
<TouchableOpacity
key={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>

{/* 直播列表 */}
<FlatList
data={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 的变化,一旦发现 metaDataproduct_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 文档

关于 LiveListState 及其相关类的所有公开接口、属性和方法的详细信息,请参阅随 AtomicXCore 框架的官方 API 文档。本文档使用到的相关 Store 如下:
State
功能描述
API 文档
LiveListState
直播间全生命周期管理:创建 / 加入 / 离开 / 销毁房间,查询房间列表,修改直播信息(名称、公告等),监听直播状态(例如被踢出、结束)。

常见问题

使用 updateLiveMetaData 时有哪些需要注意的限制和规则?

为了保证系统的稳定和高效,metaData 的使用遵循以下规则:
权限:只有房主和管理员可以调用 updateLiveMetaData。普通观众没有权限。
数量与大小限制
单个房间最多支持 10 个 key。
每个 key 的长度不超过 50 字节,每个 value 的长度不超过 2KB
单个房间所有 value 的总大小不超过 16KB
冲突解决metaData 的更新机制是“后来者覆盖”。如果多个管理员在短时间内修改同一个 key,最后一次的修改会生效。为避免冲突,业务设计时应避免多人同时修改同一个关键信息。