126 lines
3.1 KiB
TypeScript
126 lines
3.1 KiB
TypeScript
import { Image } from 'expo-image'
|
|
import { memo, useEffect, 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
|
|
} & React.ComponentProps<typeof Video>
|
|
|
|
// 默认宽度256半屏宽度
|
|
const VideoBox = ({ url, needWeb = true, width = 256, style, ...videoProps }: Props) => {
|
|
const [urlFinal, setUrlFinal] = useState('')
|
|
|
|
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)
|
|
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])
|
|
|
|
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
|
|
// cachePolicy="memory-disk"
|
|
cachePolicy="disk"
|
|
source={{ uri: urlFinal }}
|
|
style={style as any}
|
|
transition={{ duration: 200, effect: 'cross-dissolve' }}
|
|
/>
|
|
)
|
|
}
|
|
|
|
export default memo(VideoBox)
|