121 lines
2.8 KiB
TypeScript
121 lines
2.8 KiB
TypeScript
import { Image } from 'expo-image';
|
|
import { useVideoPlayer, VideoView } from 'expo-video';
|
|
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
|
import { StyleSheet, View } from 'react-native';
|
|
import type { VideoPlayerProps } from './types';
|
|
import { useSharedVisibility } from './useSharedVisibility';
|
|
import { getVideoThumbnail, getVideoUrl } from '@/lib/utils/media';
|
|
|
|
const videoViewStyle = {
|
|
width: '100%',
|
|
height: '100%',
|
|
position: 'absolute' as const,
|
|
top: 0,
|
|
left: 0,
|
|
right: 0,
|
|
bottom: 0
|
|
};
|
|
|
|
const originalImageStyle = {
|
|
width: 64,
|
|
height: 64,
|
|
position: 'absolute' as const,
|
|
bottom: 8,
|
|
left: 8,
|
|
borderRadius: 12,
|
|
borderWidth: 1,
|
|
borderColor: "#ffffff",
|
|
zIndex: 99
|
|
};
|
|
|
|
const imageStyle = {
|
|
width: '100%',
|
|
borderWidth: 0
|
|
};
|
|
|
|
function VideoPlayerComponent({
|
|
source,
|
|
originalImage,
|
|
style,
|
|
dwellTime = 500,
|
|
threshold = 0.1
|
|
}: VideoPlayerProps) {
|
|
const [imageAspectRatio, setImageAspectRatio] = useState(1);
|
|
const { ref: viewRef, isVisible, shouldPlay } = useSharedVisibility(threshold, dwellTime);
|
|
|
|
const url = useMemo(() => {
|
|
return source?.uri
|
|
}, [source])
|
|
|
|
const isVideo = useMemo(() => {
|
|
return /\.(mp4|webm|ogg|mov|avi)$/i.test(url || ``)
|
|
}, [url])
|
|
|
|
const thumbnailUrl = useMemo(() => {
|
|
if (!isVideo) return url;
|
|
return getVideoThumbnail(url!, { time: '0s', width: 600, height: 600, fit: 'cover' });
|
|
}, [url, isVideo]);
|
|
|
|
const player = useVideoPlayer(
|
|
isVideo ? { ...source, uri: getVideoUrl(source.uri!) } : null,
|
|
(p) => {
|
|
p.loop = true;
|
|
p.muted = true;
|
|
}
|
|
)
|
|
|
|
useEffect(() => {
|
|
if (!isVideo || !player) return;
|
|
|
|
try {
|
|
shouldPlay ? player.play() : player.pause();
|
|
} catch { }
|
|
}, [shouldPlay, player, isVideo]);
|
|
|
|
const handleImageLoad = useCallback((e: any) => {
|
|
const { width, height } = e.source;
|
|
if (width && height) {
|
|
setImageAspectRatio(width / height);
|
|
}
|
|
}, []);
|
|
|
|
const element = useMemo(() => {
|
|
if (!isVisible) return null;
|
|
if (isVideo && player) {
|
|
return <>
|
|
<VideoView
|
|
player={player}
|
|
contentFit="cover"
|
|
nativeControls={false}
|
|
style={videoViewStyle}
|
|
/>
|
|
{originalImage && <Image source={{ uri: originalImage }} style={originalImageStyle} />}
|
|
</>
|
|
}
|
|
else {
|
|
return <Image
|
|
source={{ uri: thumbnailUrl }}
|
|
contentFit="cover"
|
|
onLoad={handleImageLoad}
|
|
style={[imageStyle, { aspectRatio: imageAspectRatio }]}
|
|
/>
|
|
}
|
|
}, [isVideo, isVisible, imageAspectRatio, player, thumbnailUrl, originalImage, handleImageLoad])
|
|
|
|
return (
|
|
<View ref={viewRef as any} style={[styles.container, style]}>
|
|
{element}
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
width: '100%',
|
|
height: '100%',
|
|
overflow: 'hidden'
|
|
}
|
|
});
|
|
|
|
export default memo(VideoPlayerComponent);
|