import React, { useState, useEffect, useMemo } from 'react'; import { View, ActivityIndicator, TouchableOpacity, Platform, Image } from 'react-native'; import { useEvent } from 'expo'; import { useVideoPlayer, VideoView, type VideoSource } from 'expo-video'; import { MaterialCommunityIcons } from '@expo/vector-icons'; import * as FileSystem from 'expo-file-system/legacy'; import { Colors } from 'src/theme'; import { CACHED_ATTACHMENTS_DIR } from 'src/constants/constants'; import { API_HOST, APP_VERSION } from 'src/constants'; type RenderMessageVideoProps = { props: any; token: string; currentUserId: number; onLongPress: (currentMessage: any, props: any) => any; }; const MAX_RETRY = 3; const RenderMessageVideo = ({ props, token, currentUserId, onLongPress }: RenderMessageVideoProps) => { const { currentMessage } = props; if (!currentMessage?.video) return null; const leftMessage = currentMessage?.user?._id !== currentUserId; const [videoUri, setVideoUri] = useState(null); const [retryCount, setRetryCount] = useState(0); const [showPoster, setShowPoster] = useState(true); const [hasStarted, setHasStarted] = useState(false); const downloadVideo = async (videoUrl: string) => { if (!videoUrl.startsWith('https')) { setVideoUri(videoUrl); return videoUrl; } try { const dirExist = await FileSystem.getInfoAsync(CACHED_ATTACHMENTS_DIR); if (!dirExist.exists) { await FileSystem.makeDirectoryAsync(CACHED_ATTACHMENTS_DIR, { intermediates: true }); } const filename = currentMessage?.attachment?.filename ?? `video-${currentMessage?._id ?? ''}.mp4`; const videoPath = `${CACHED_ATTACHMENTS_DIR}${filename}`; const videoExists = await FileSystem.getInfoAsync(videoPath); if (videoExists.exists) { if ((videoExists.size ?? 0) < 1024) { await FileSystem.deleteAsync(videoPath, { idempotent: true }); } else { try { await FileSystem.readAsStringAsync(videoPath, { encoding: FileSystem.EncodingType.Base64 }); setVideoUri(videoPath); return videoPath; } catch { await FileSystem.deleteAsync(videoPath, { idempotent: true }); } } } const downloadResult = await FileSystem.downloadAsync(videoUrl, videoPath, { headers: { Nmtoken: token, 'App-Version': APP_VERSION, Platform: Platform.OS } }); setVideoUri(downloadResult.uri); return downloadResult.uri; } catch (error) { console.error('Error downloading video:', error); return null; } }; useEffect(() => { const loadVideo = async () => { if (currentMessage?.video && !currentMessage?.isSending) { setShowPoster(true); await downloadVideo(currentMessage.video); } }; loadVideo(); }, [currentMessage.video, currentMessage.isSending]); const player = useVideoPlayer(null, (p) => { p.loop = false; p.muted = false; p.volume = 1; p.timeUpdateEventInterval = 0.5; }); useEffect(() => { (async () => { if (videoUri) { const source: VideoSource = { uri: videoUri }; await player.replaceAsync(source); } })(); }, [videoUri, player]); const { status, error } = useEvent(player, 'statusChange', { status: player.status }); const { isPlaying } = useEvent(player, 'playingChange', { isPlaying: player.playing }); const isBuffering = status !== 'readyToPlay'; const isVideoLoaded = status === 'readyToPlay'; useEffect(() => { if (status === 'readyToPlay') setShowPoster(false); }, [status]); useEffect(() => { (async () => { if ((status === 'error' || !!error) && retryCount < MAX_RETRY && videoUri) { try { await FileSystem.deleteAsync(videoUri, { idempotent: true }); } catch {} const newUri = await downloadVideo(currentMessage.video); if (newUri) { setRetryCount((c) => c + 1); setShowPoster(true); } } })(); }, [status, error]); const posterUri = useMemo( () => currentMessage?.attachment?.attachment_small_url ? API_HOST + currentMessage.attachment.attachment_small_url : null, [currentMessage] ); const handlePlayPress = async () => { if (isVideoLoaded) { setHasStarted(true); await player.play(); } }; return ( {posterUri && showPoster && ( )} {videoUri ? ( ) : null} {isBuffering && ( )} {!hasStarted && !isBuffering && videoUri && ( onLongPress(currentMessage, props)} > )} ); }; export default RenderMessageVideo;