111 lines
2.8 KiB
TypeScript
111 lines
2.8 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'
|
||
|
||
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
|
||
}
|
||
|
||
// 自动从 URL 提取稳定的缓存键
|
||
const extractCacheKey = (url: string): string => {
|
||
try {
|
||
const urlObj = new URL(url)
|
||
// 使用路径部分作为缓存键,忽略查询参数(token、签名等)
|
||
return urlObj.pathname
|
||
} catch {
|
||
// URL 解析失败时使用原始 URL
|
||
return url
|
||
}
|
||
}
|
||
|
||
const setRedirectUrl = async (url?: string) => {
|
||
const isImg2 = isImg(url)
|
||
if (isImg2) {
|
||
setUrlFinal(url!)
|
||
return
|
||
}
|
||
|
||
const webpUrl = createUrl(url!)
|
||
const finalUrl = await resolveRedirect(webpUrl)
|
||
// console.log('setRedirectUrl finalUrl-----------', finalUrl)
|
||
setUrlFinal(finalUrl!)
|
||
}
|
||
|
||
useEffect(() => {
|
||
if (!url) return
|
||
setRedirectUrl(url!)
|
||
// const finalUrl = createUrl(url)
|
||
// console.log('finalUrl-----------', finalUrl)
|
||
|
||
// setUrlFinal(finalUrl)
|
||
return
|
||
}, [url])
|
||
|
||
// console.log('urlFinal--------- ', urlFinal, url)
|
||
|
||
if (!url) return null
|
||
|
||
// 本地文件全部用video组件播放
|
||
const isLocal = !url.startsWith('http://') && !url.startsWith('https://')
|
||
const isImageFile = isImg(url)
|
||
|
||
if (isLocal && !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 (
|
||
// 移除 key 避免组件重建导致闪烁,使用 transition 实现平滑切换
|
||
<Image
|
||
cachePolicy="memory-disk"
|
||
source={{ uri: urlFinal, cacheKey: extractCacheKey(urlFinal) }}
|
||
style={style as any}
|
||
transition={{ duration: 200, effect: 'cross-dissolve' }}
|
||
/>
|
||
)
|
||
}
|
||
|
||
export default memo(VideoBox)
|