fix: BigVideoCard stale closure, visibility guard, mediaContainer positioning

This commit is contained in:
Developer
2026-03-11 14:32:40 +08:00
parent dfa8fc6dc8
commit d09c1d7346

View File

@@ -2,7 +2,7 @@
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import { import {
View, Text, Image, TouchableOpacity, StyleSheet, View, Text, Image, TouchableOpacity, StyleSheet,
Dimensions, Animated, useWindowDimensions, Animated,
} from 'react-native'; } from 'react-native';
import Video from 'react-native-video'; import Video from 'react-native-video';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
@@ -12,9 +12,6 @@ import { proxyImageUrl } from '../utils/imageUrl';
import { formatCount, formatDuration } from '../utils/format'; import { formatCount, formatDuration } from '../utils/format';
import type { VideoItem } from '../services/types'; import type { VideoItem } from '../services/types';
const { width: SCREEN_W } = Dimensions.get('window');
const THUMB_H = SCREEN_W * 0.5625; // 16:9
const HEADERS = { const HEADERS = {
Referer: 'https://www.bilibili.com', Referer: 'https://www.bilibili.com',
'User-Agent': 'User-Agent':
@@ -28,11 +25,22 @@ interface Props {
} }
export function BigVideoCard({ item, isVisible, onPress }: Props) { export function BigVideoCard({ item, isVisible, onPress }: Props) {
const { width: SCREEN_W } = useWindowDimensions();
const THUMB_H = SCREEN_W * 0.5625;
const [videoUrl, setVideoUrl] = useState<string | undefined>(); const [videoUrl, setVideoUrl] = useState<string | undefined>();
const [isDash, setIsDash] = useState(false); const [isDash, setIsDash] = useState(false);
const [paused, setPaused] = useState(true); const [paused, setPaused] = useState(true);
const thumbOpacity = useRef(new Animated.Value(1)).current; const thumbOpacity = useRef(new Animated.Value(1)).current;
// Reset video state when the item changes
useEffect(() => {
setVideoUrl(undefined);
setIsDash(false);
setPaused(true);
thumbOpacity.setValue(1);
}, [item.bvid]);
// 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;
@@ -64,7 +72,7 @@ export function BigVideoCard({ item, isVisible, onPress }: Props) {
console.warn('BigVideoCard: failed to load play URL', e); console.warn('BigVideoCard: failed to load play URL', e);
} }
})(); })();
}, [isVisible]); }, [isVisible, item.bvid]);
// Pause/resume when visibility changes // Pause/resume when visibility changes
useEffect(() => { useEffect(() => {
@@ -77,6 +85,7 @@ export function BigVideoCard({ item, isVisible, onPress }: Props) {
}, [isVisible, videoUrl]); }, [isVisible, videoUrl]);
const handleVideoReady = () => { const handleVideoReady = () => {
if (!isVisible) return;
setPaused(false); setPaused(false);
Animated.timing(thumbOpacity, { Animated.timing(thumbOpacity, {
toValue: 0, toValue: 0,
@@ -88,12 +97,12 @@ export function BigVideoCard({ item, isVisible, onPress }: Props) {
return ( return (
<TouchableOpacity style={styles.card} onPress={onPress} activeOpacity={0.9}> <TouchableOpacity style={styles.card} onPress={onPress} activeOpacity={0.9}>
{/* Media area */} {/* Media area */}
<View style={styles.mediaContainer}> <View style={{ width: SCREEN_W - 8, height: THUMB_H, position: 'relative' }}>
{/* Thumbnail */} {/* Thumbnail */}
<Animated.View style={[StyleSheet.absoluteFill, { opacity: thumbOpacity }]}> <Animated.View style={[StyleSheet.absoluteFill, { opacity: thumbOpacity }]}>
<Image <Image
source={{ uri: proxyImageUrl(item.pic) }} source={{ uri: proxyImageUrl(item.pic) }}
style={styles.thumb} style={{ width: SCREEN_W - 8, height: THUMB_H }}
resizeMode="cover" resizeMode="cover"
/> />
</Animated.View> </Animated.View>
@@ -143,14 +152,6 @@ const styles = StyleSheet.create({
borderRadius: 6, borderRadius: 6,
overflow: 'hidden', overflow: 'hidden',
}, },
mediaContainer: {
width: SCREEN_W - 8,
height: THUMB_H,
},
thumb: {
width: SCREEN_W - 8,
height: THUMB_H,
},
durationBadge: { durationBadge: {
position: 'absolute', position: 'absolute',
bottom: 4, bottom: 4,