expo-duooomi-app/@share/components/Video.tsx

143 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Image, type ImageRef } from 'expo-image'
import { memo, useEffect, useRef, useState } from 'react'
import { type ViewStyle } from 'react-native'
import Video from 'react-native-video'
import { videoUrlCache } from '@/utils/storage'
type Props = {
url?: string
needWeb?: boolean
style?: ViewStyle
width?: number
autoplay?: boolean // 控制动画播放,默认 true
} & React.ComponentProps<typeof Video>
// 默认宽度256半屏宽度
const VideoBox = ({ url, needWeb = true, width = 256, style, autoplay = true, ...videoProps }: Props) => {
const [urlFinal, setUrlFinal] = useState('')
const imageRef = useRef<ImageRef>(null)
const createUrl = (url: string) => {
return `https://modal-dev.bowong.cc/api/custom/video/converter/v2?media_url=${encodeURI(url)}&options=compression_level=3,quality=70,loop=true,resolution=${width}x${width},fps=24`
}
const isImg = (url: any) => {
if (!url) return false
const lowerUrl = url.toLowerCase()
return lowerUrl?.match(/\.(jpg|jpeg|png|gif|webp|bmp|tiff|svg)(\?.*)?$/i)
}
async function resolveRedirect(url: string) {
const res = await fetch(url, {
method: 'GET',
headers: {
Range: 'bytes=0-0',
},
})
return res.url
}
const setRedirectUrl = async (url?: string) => {
const isImg2 = isImg(url)
if (isImg2) {
setUrlFinal(url!)
return
}
try {
// 先尝试从缓存获取
const cachedUrl = await videoUrlCache.get(url!, width)
// console.log('getRedirectUrl cachedUrl-----------', url, cachedUrl, width)
if (cachedUrl) {
setUrlFinal(cachedUrl)
return
}
// 缓存未命中,进行网络请求
const webpUrl = createUrl(url!)
const finalUrl = await resolveRedirect(webpUrl)
// 缓存结果
await videoUrlCache.set(url!, finalUrl, width)
// console.log('setRedirectUrl finalUrl-----------', finalUrl)
setUrlFinal(finalUrl!)
} catch (error) {
console.warn('获取视频URL失败:', error)
// 错误时尝试使用原始URL
setUrlFinal(url!)
}
}
useEffect(() => {
if (!url) return
setRedirectUrl(url!)
// const finalUrl = createUrl(url)
return
}, [url])
// 控制动画播放/停止
useEffect(() => {
if (imageRef.current) {
if (autoplay) {
imageRef.current?.startAnimating()
} else {
imageRef.current?.stopAnimating()
}
}
}, [autoplay])
if (!url) return null
// 本地文件全部用video组件播放
const isLocal = !url.startsWith('http://') && !url.startsWith('https://')
const isImageFile = isImg(url)
if (isLocal) {
if (!isImageFile) {
return (
<Video
key={url ?? 'no-url'}
muted
repeat
controls={false}
paused={false}
poster={url}
resizeMode="cover"
source={{ uri: url }}
style={style as any}
viewType={0}
volume={0}
{...videoProps}
/>
)
}
return (
<Image
cachePolicy="memory-disk"
source={{ uri: url }}
style={style as any}
transition={{ duration: 200, effect: 'cross-dissolve' }}
/>
)
}
return (
// 移除 key 避免组件重建导致闪烁,使用 transition 实现平滑切换
<Image
ref={imageRef}
// 只使用 disk 缓存减少内存占用
cachePolicy="disk"
// 添加 recyclingKey 帮助内存回收
recyclingKey={urlFinal}
source={{ uri: urlFinal }}
style={style as any}
autoplay={autoplay}
transition={{ duration: 200, effect: 'cross-dissolve' }}
/>
)
}
export default memo(VideoBox)