This commit is contained in:
Developer
2026-03-17 18:00:00 +08:00
parent 2e47871689
commit 714d28358f
5 changed files with 19 additions and 21 deletions

View File

@@ -62,7 +62,6 @@ const LIVE_AREAS = [
{ id: 9, name: "虚拟主播" }, { id: 9, name: "虚拟主播" },
{ id: 10, name: "生活" }, { id: 10, name: "生活" },
{ id: 11, name: "知识" }, { id: 11, name: "知识" },
{ id: 13, name: "赛事" },
]; ];
export default function HomeScreen() { export default function HomeScreen() {

View File

@@ -30,6 +30,7 @@ export default function LiveDetailScreen() {
const isLive = room?.live_status === 1; const isLive = room?.live_status === 1;
const hlsUrl = stream?.hlsUrl ?? ""; const hlsUrl = stream?.hlsUrl ?? "";
const flvUrl = stream?.flvUrl ?? "";
const qualities = stream?.qualities ?? []; const qualities = stream?.qualities ?? [];
const currentQn = stream?.qn ?? 0; const currentQn = stream?.qn ?? 0;
@@ -51,6 +52,7 @@ export default function LiveDetailScreen() {
{/* Player */} {/* Player */}
<LivePlayer <LivePlayer
hlsUrl={hlsUrl} hlsUrl={hlsUrl}
flvUrl={flvUrl}
isLive={isLive} isLive={isLive}
qualities={qualities} qualities={qualities}
currentQn={currentQn} currentQn={currentQn}

View File

@@ -13,6 +13,7 @@ import { Ionicons } from '@expo/vector-icons';
interface Props { interface Props {
hlsUrl: string; hlsUrl: string;
flvUrl?: string;
isLive: boolean; isLive: boolean;
qualities?: { qn: number; desc: string }[]; qualities?: { qn: number; desc: string }[];
currentQn?: number; currentQn?: number;
@@ -27,7 +28,7 @@ const HEADERS = {
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
}; };
export function LivePlayer({ hlsUrl, isLive, qualities = [], currentQn = 0, onQualityChange }: Props) { export function LivePlayer({ hlsUrl, flvUrl, isLive, qualities = [], currentQn = 0, onQualityChange }: Props) {
const { width: SCREEN_W, height: SCREEN_H } = useWindowDimensions(); const { width: SCREEN_W, height: SCREEN_H } = useWindowDimensions();
const VIDEO_H = SCREEN_W * 0.5625; const VIDEO_H = SCREEN_W * 0.5625;
@@ -179,12 +180,6 @@ function NativeLivePlayer({
{showControls && ( {showControls && (
<> <>
{/* LIVE badge top-left */}
<View style={styles.liveBadge} pointerEvents="none">
<View style={styles.liveDot} />
<Text style={styles.liveText}>live</Text>
</View>
{/* Center play/pause */} {/* Center play/pause */}
<TouchableOpacity <TouchableOpacity
style={styles.centerBtn} style={styles.centerBtn}

View File

@@ -123,6 +123,8 @@ export function useLiveDanmaku(roomId: number): DanmakuItem[] {
const info = await getLiveDanmakuInfo(roomId); const info = await getLiveDanmakuInfo(roomId);
token = info.token; token = info.token;
host = info.host; host = info.host;
} catch (e) { } catch (e) {
console.warn('[danmaku] getDanmuInfo failed:', e); console.warn('[danmaku] getDanmuInfo failed:', e);
} }
@@ -144,19 +146,18 @@ export function useLiveDanmaku(roomId: number): DanmakuItem[] {
wsRef.current = ws; wsRef.current = ws;
ws.onopen = () => { ws.onopen = () => {
const authBody = JSON.stringify({ const authBody = JSON.stringify({
uid: 0, uid: 0,
roomid: roomId, roomid: roomId,
protover: 2, protover: 2,
platform: 'h5', // must match platform param used in getConf API call platform: 'h5',
type: 2, type: 2,
key: token, key: token,
}); });
const authPkt = buildPacket(authBody, 7); const authPkt = buildPacket(authBody, 7);
const hdr = Array.from(authPkt.slice(0, 16))
.map(b => b.toString(16).padStart(2, '0')).join(' ');
// Send as ArrayBuffer (more reliable than Uint8Array in some RN/Hermes versions)
const t0 = Date.now();
ws.send(authPkt.buffer as ArrayBuffer); ws.send(authPkt.buffer as ArrayBuffer);
heartbeatRef.current = setInterval(() => { heartbeatRef.current = setInterval(() => {
@@ -171,10 +172,8 @@ export function useLiveDanmaku(roomId: number): DanmakuItem[] {
if (items.length > 0) setDanmakus(prev => [...prev, ...items]); if (items.length > 0) setDanmakus(prev => [...prev, ...items]);
}; };
ws.onerror = (e: Event) => { ws.onerror = () => {};
const ws2 = (e as any).currentTarget as WebSocket; ws.onclose = () => {
};
ws.onclose = (e: CloseEvent) => {
if (heartbeatRef.current) clearInterval(heartbeatRef.current); if (heartbeatRef.current) clearInterval(heartbeatRef.current);
}; };
} }

View File

@@ -261,7 +261,7 @@ export async function getLiveAnchorInfo(roomId: number): Promise<LiveAnchorInfo>
export async function getLiveStreamUrl(roomId: number, qn = 10000): Promise<LiveStreamInfo> { export async function getLiveStreamUrl(roomId: number, qn = 10000): Promise<LiveStreamInfo> {
try { try {
const res = await api.get(`${LIVE_BASE}/xlive/web-room/v2/index/getRoomPlayInfo`, { const res = await api.get(`${LIVE_BASE}/xlive/web-room/v2/index/getRoomPlayInfo`, {
params: { room_id: roomId, protocol: '0,1', format: '0,1,2', codec: '0', qn }, params: { room_id: roomId, protocol: '0,1', format: '0,1,2', codec: '0', qn, platform: 'android' },
}); });
const playurl = res.data?.data?.playurl_info?.playurl; const playurl = res.data?.data?.playurl_info?.playurl;
const streams: any[] = playurl?.stream ?? []; const streams: any[] = playurl?.stream ?? [];
@@ -338,11 +338,14 @@ export async function searchVideos(keyword: string, page = 1): Promise<VideoItem
} }
export async function getLiveDanmakuInfo(roomId: number): Promise<{ token: string; host: string }> { export async function getLiveDanmakuInfo(roomId: number): Promise<{ token: string; host: string }> {
const res = await api.get(`${LIVE_BASE}/room/v1/Danmu/getConf`, { const { imgKey, subKey } = await getWbiKeys();
params: { room_id: roomId, platform: 'h5' }, const signed = signWbi({ id: roomId, type: 0 }, imgKey, subKey);
const res = await api.get(`${LIVE_BASE}/xlive/web-room/v1/index/getDanmuInfo`, {
params: signed,
}); });
const data = res.data.data; const data = res.data.data;
const hostInfo = data?.host_server_list?.[0]; const hostList: any[] = data?.host_list ?? [];
const hostInfo = hostList.find(h => h.wss_port === 443) ?? hostList[0];
const host = hostInfo const host = hostInfo
? `wss://${hostInfo.host}:${hostInfo.wss_port}/sub` ? `wss://${hostInfo.host}:${hostInfo.wss_port}/sub`
: 'wss://broadcastlv.chat.bilibili.com/sub'; : 'wss://broadcastlv.chat.bilibili.com/sub';