fix: add unmount cancellation guard, simplify keyExtractor, update spec

This commit is contained in:
Developer
2026-03-11 14:48:19 +08:00
parent 287a608e97
commit 4e8beae209
3 changed files with 11 additions and 11 deletions

View File

@@ -99,10 +99,10 @@ export default function HomeScreen() {
<Animated.FlatList <Animated.FlatList
style={styles.listContainer} style={styles.listContainer}
data={rows} data={rows}
keyExtractor={(row: any, index) => keyExtractor={(row: any) =>
row.type === 'big' row.type === 'big'
? `big-${row.item.bvid}` ? `big-${row.item.bvid}`
: `pair-${row.left.bvid}-${row.right?.bvid ?? 'empty'}-${index}` : `pair-${row.left.bvid}-${row.right?.bvid ?? 'empty'}`
} }
contentContainerStyle={{ contentContainerStyle={{
paddingTop: insets.top + NAV_H + 6, paddingTop: insets.top + NAV_H + 6,

View File

@@ -45,7 +45,7 @@ export function BigVideoCard({ item, isVisible, onPress }: Props) {
// Fetch play URL when visible for the first time // Fetch play URL when visible for the first time
useEffect(() => { useEffect(() => {
if (!isVisible || videoUrl) return; if (!isVisible || videoUrl) return;
let cancelled = false;
(async () => { (async () => {
try { try {
// cid may be missing from feed items; fetch detail if needed // cid may be missing from feed items; fetch detail if needed
@@ -54,25 +54,25 @@ export function BigVideoCard({ item, isVisible, onPress }: Props) {
const detail = await getVideoDetail(item.bvid); const detail = await getVideoDetail(item.bvid);
cid = detail.cid ?? detail.pages?.[0]?.cid; cid = detail.cid ?? detail.pages?.[0]?.cid;
} }
if (!cid) return; if (!cid || cancelled) return;
const playData = await getPlayUrl(item.bvid, cid, 16); const playData = await getPlayUrl(item.bvid, cid, 16);
if (cancelled) return;
if (playData.dash) { if (playData.dash) {
setIsDash(true); if (!cancelled) setIsDash(true);
try { try {
const mpdUri = await buildDashMpdUri(playData, 16); const mpdUri = await buildDashMpdUri(playData, 16);
setVideoUrl(mpdUri); if (!cancelled) setVideoUrl(mpdUri);
} catch { } catch {
setVideoUrl(playData.dash.video[0]?.baseUrl); if (!cancelled) setVideoUrl(playData.dash.video[0]?.baseUrl);
} }
} else { } else {
setVideoUrl(playData.durl?.[0]?.url); if (!cancelled) setVideoUrl(playData.durl?.[0]?.url);
} }
} catch (e) { } catch (e) {
console.warn('BigVideoCard: failed to load play URL', e); console.warn('BigVideoCard: failed to load play URL', e);
} }
})(); })();
return () => { cancelled = true; };
// videoUrl intentionally excluded — re-fetch guard prevents redundant fetches after URL is set // videoUrl intentionally excluded — re-fetch guard prevents redundant fetches after URL is set
}, [isVisible, item.bvid]); }, [isVisible, item.bvid]);

View File

@@ -53,7 +53,7 @@ interface BigVideoCardProps {
``` ```
BigVideoCard (TouchableOpacity) BigVideoCard (TouchableOpacity)
├── 封面图 (Image, 16:9 全宽) ├── 封面图 (Image, 16:9 全宽)
├── 视频播放器 (NativeVideoPlayer, position: absolute, isVisible 时加载) ├── 视频播放器 (react-native-video <Video>, position: absolute, isVisible 时加载;直接使用 <Video> 而非 NativeVideoPlayer因为不需要控制条/热力图等全功能组件)
│ └── muted, autoplay, qn=16 │ └── muted, autoplay, qn=16
├── 封面遮罩 (Animated.View, opacity 1→0 淡出) ├── 封面遮罩 (Animated.View, opacity 1→0 淡出)
└── 底部信息栏 └── 底部信息栏