import { Image as ExpoImage, type ImageRef } from 'expo-image' import { memo, useEffect, useRef, useState } from 'react' import { View, type ViewStyle } from 'react-native' import Video from 'react-native-video' import tw from 'twrnc' import { videoUrlCache } from '@/utils/storage' import { mp4ToWebpUrl } from '@/utils/uploadFile' import Img from './Img' type Props = { className?: string url?: string placeholderUrl?: string needWeb?: boolean style?: ViewStyle width?: number autoplay?: boolean // 控制动画播放,默认 true } & React.ComponentProps // videoUrlCache.clear() // ExpoImage.clearDiskCache() // ExpoImage.clearMemoryCache() // 默认宽度256半屏宽度 const VideoBox = ({ className = '', url, placeholderUrl = '', width = 256, style, autoplay = true, ...videoProps }: Props) => { const [urlFinal, setUrlFinal] = useState('') const imageRef = useRef(null) const isImg = (url: any) => { if (!url) return false const lowerUrl = url.toLowerCase() return lowerUrl?.match(/\.(jpg|jpeg|png|gif|webp|bmp|tiff|svg)(\?.*)?$/i) } const setRedirectUrl = async (isCancelled: () => boolean, url?: string) => { const isImg2 = isImg(url) if (isImg2) { if (!isCancelled()) { setUrlFinal(url!) } return } try { // 先尝试从缓存获取 const cachedUrl = await videoUrlCache.get(url!, width) // console.log('getRedirectUrl cachedUrl-----------', url, cachedUrl, width) if (cachedUrl) { // 只有当请求未被取消时才更新状态 if (!isCancelled()) { setUrlFinal(cachedUrl) } return } const webpUrl = await mp4ToWebpUrl({ videoUrl: url!, width, height: width }) // 只有当请求未被取消时才缓存和更新状态 if (!isCancelled()) { // 缓存结果 await videoUrlCache.set(url!, webpUrl, width) // console.log('setRedirectUrl finalUrl-----------', finalUrl) setUrlFinal(webpUrl) } } catch (error) { console.warn('获取视频URL失败:', error) // 只有当请求未被取消时才更新状态 if (!isCancelled()) { // 错误时尝试使用原始URL setUrlFinal(url!) } } } useEffect(() => { // 每次url变化强制刷新 setUrlFinal('') if (!url) { return } // 用于标记当前请求是否已过期 let cancelled = false setTimeout(() => { if (!cancelled) { setRedirectUrl(() => cancelled, url) } }, 0) // 清理函数:当 url 变化或组件卸载时,标记当前请求为已取消 return () => { cancelled = true } }, [url, width]) // 控制动画播放/停止 useEffect(() => { // 添加延迟和安全检查,确保 ref 已就绪且组件未卸载 const timer = setTimeout(() => { try { if (imageRef?.current) { if (autoplay) { imageRef.current?.startAnimating() } else { imageRef.current?.stopAnimating() } } } catch (error) { // 忽略动画控制错误,避免崩溃 console.debug('Animation control failed:', error) } }, 100) return () => clearTimeout(timer) }, [autoplay, urlFinal]) if (!url) return null // 本地文件全部用video组件播放 const isLocal = !url.startsWith('http://') && !url.startsWith('https://') const isImageFile = isImg(url) if (isLocal) { if (!isImageFile) { return (