Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | 7x 7x 7x 7x 3x 3x 3x 3x 3x 3x 1x 1x 1x 1x 1x 1x 2x 2x 3x 3x 3x 7x 7x 7x | /**
* VideoPreview - 视频预览组件
*
* 用于在发帖编辑器和论坛中显示视频预览
* 支持:
* - 视频第一帧缩略图
* - 播放按钮覆盖层
* - 加载状态
*/
import React, { useState, useEffect } from 'react';
import { Image, ActivityIndicator, StyleSheet, View } from 'react-native';
import { Play } from '@tamagui/lucide-icons';
import { Stack, Text } from 'tamagui';
import { generateVideoThumbnail } from '@/src/utils/videoThumbnail';
interface VideoPreviewProps {
videoUri: string;
width: number | string;
height: number | string;
showPlayButton?: boolean;
onPress?: () => void;
}
export function VideoPreview({
videoUri,
width,
height,
showPlayButton = true,
onPress,
}: VideoPreviewProps) {
const [thumbnail, setThumbnail] = useState<string | null>(null);
const [isGenerating, setIsGenerating] = useState(true);
const [error, setError] = useState(false);
useEffect(() => {
let mounted = true;
const loadThumbnail = async () => {
try {
setIsGenerating(true);
setError(false);
const thumb = await generateVideoThumbnail(videoUri);
Eif (mounted) {
Iif (thumb) {
setThumbnail(thumb);
} else {
setError(true);
}
}
} catch (err) {
console.error('加载视频缩略图失败:', err);
Eif (mounted) {
setError(true);
}
} finally {
Eif (mounted) {
setIsGenerating(false);
}
}
};
loadThumbnail();
return () => {
mounted = false;
};
}, [videoUri]);
const numericWidth = typeof width === 'number' ? width : undefined;
const numericHeight = typeof height === 'number' ? height : undefined;
return (
<Stack
width={numericWidth}
height={numericHeight}
style={typeof width === 'string' ? { width, height } : undefined}
backgroundColor="#1a1a1a"
justifyContent="center"
alignItems="center"
position="relative"
>
{isGenerating ? (
// 加载中
<Stack alignItems="center" gap="$2">
<ActivityIndicator size="small" color="#FFFFFF" />
<Text color="#FFFFFF" fontSize={12} opacity={0.8}>
生成预览...
</Text>
</Stack>
) : error || !thumbnail ? (
// 加载失败 - 显示默认的视频图标
<Stack alignItems="center" gap="$2">
<Play size={48} color="#FFFFFF" opacity={0.6} />
<Text color="#FFFFFF" fontSize={12} opacity={0.6}>
视频预览
</Text>
</Stack>
) : (
// 显示缩略图
<>
<Image
source={{ uri: thumbnail }}
style={[
StyleSheet.absoluteFill,
{ width: numericWidth || '100%', height: numericHeight || '100%' },
]}
resizeMode="cover"
/>
{showPlayButton && (
<Stack
position="absolute"
width={64}
height={64}
borderRadius={32}
backgroundColor="rgba(0, 0, 0, 0.6)"
alignItems="center"
justifyContent="center"
>
<Play size={28} color="#FFFFFF" fill="#FFFFFF" />
</Stack>
)}
</>
)}
</Stack>
);
}
|