renderMessageVideo.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. import React, { useState, useEffect, useRef } from 'react';
  2. import { View, ActivityIndicator, TouchableOpacity } from 'react-native';
  3. import { ResizeMode, Video } from 'expo-av';
  4. import { MaterialCommunityIcons } from '@expo/vector-icons';
  5. import * as FileSystem from 'expo-file-system';
  6. import { Colors } from 'src/theme';
  7. import { CACHED_ATTACHMENTS_DIR } from 'src/constants/constants';
  8. import { API_HOST } from 'src/constants';
  9. const RenderMessageVideo = ({
  10. props,
  11. token,
  12. currentUserId,
  13. onLongPress
  14. }: {
  15. props: any;
  16. token: string;
  17. currentUserId: number;
  18. onLongPress: (currentMessage: any, props: any) => any;
  19. }) => {
  20. const { currentMessage } = props;
  21. if (!currentMessage?.video) return null;
  22. const leftMessage = currentMessage?.user?._id !== currentUserId;
  23. const videoRef = useRef<Video>(null);
  24. const [isPlaying, setIsPlaying] = useState(false);
  25. const [isBuffering, setIsBuffering] = useState(true);
  26. const [videoUri, setVideoUri] = useState<string | null>(null);
  27. const [isVideoLoaded, setIsVideoLoaded] = useState(false);
  28. const [retryCount, setRetryCount] = useState(0);
  29. const MAX_RETRY = 3;
  30. const downloadVideo = async (videoUrl: string) => {
  31. if (!videoUrl.startsWith('https')) {
  32. setVideoUri(videoUrl);
  33. setIsVideoLoaded(true);
  34. return videoUrl;
  35. }
  36. try {
  37. const dirExist = await FileSystem.getInfoAsync(CACHED_ATTACHMENTS_DIR);
  38. if (!dirExist.exists) {
  39. await FileSystem.makeDirectoryAsync(CACHED_ATTACHMENTS_DIR, { intermediates: true });
  40. }
  41. const videoPath = `${CACHED_ATTACHMENTS_DIR}${currentMessage.attachment.filename}`;
  42. const videoExists = await FileSystem.getInfoAsync(videoPath);
  43. if (videoExists.exists) {
  44. if (videoExists.size < 1024) {
  45. await FileSystem.deleteAsync(videoPath, { idempotent: true });
  46. } else {
  47. try {
  48. await FileSystem.readAsStringAsync(videoPath, {
  49. encoding: FileSystem.EncodingType.Base64
  50. });
  51. setVideoUri(videoPath);
  52. setIsVideoLoaded(true);
  53. return videoPath;
  54. } catch (e) {
  55. await FileSystem.deleteAsync(videoPath, { idempotent: true });
  56. }
  57. }
  58. }
  59. const downloadResult = await FileSystem.downloadAsync(videoUrl, videoPath, {
  60. headers: {
  61. Nmtoken: token
  62. }
  63. });
  64. setVideoUri(downloadResult.uri);
  65. setIsVideoLoaded(true);
  66. return downloadResult.uri;
  67. } catch (error) {
  68. console.error('Error downloading video:', error);
  69. return null;
  70. }
  71. };
  72. useEffect(() => {
  73. const loadVideo = async () => {
  74. if (currentMessage?.video && !currentMessage?.isSending) {
  75. await downloadVideo(currentMessage.video);
  76. }
  77. };
  78. loadVideo();
  79. }, [currentMessage.video, currentMessage.isSending]);
  80. const handlePlaybackStatusUpdate = (playbackStatus: any) => {
  81. if (!playbackStatus.isLoaded) {
  82. setIsPlaying(false);
  83. setIsBuffering(false);
  84. return;
  85. }
  86. setIsPlaying(playbackStatus.isPlaying);
  87. setIsBuffering(playbackStatus.isBuffering ?? false);
  88. };
  89. const handlePlayPress = async () => {
  90. if (videoRef.current && isVideoLoaded) {
  91. await videoRef.current.presentFullscreenPlayer();
  92. await videoRef.current.playAsync();
  93. }
  94. };
  95. return (
  96. <View
  97. style={{
  98. width: 200,
  99. height: 200,
  100. padding: 6,
  101. borderRadius: 10
  102. }}
  103. >
  104. {videoUri ? (
  105. <Video
  106. ref={videoRef}
  107. source={{ uri: videoUri }}
  108. style={{ flex: 1, borderRadius: 10 }}
  109. resizeMode={ResizeMode.CONTAIN}
  110. useNativeControls
  111. isMuted={false}
  112. volume={1.0}
  113. shouldCorrectPitch
  114. onPlaybackStatusUpdate={handlePlaybackStatusUpdate}
  115. posterStyle={{ resizeMode: 'cover', width: '100%', height: '100%' }}
  116. usePoster={true}
  117. posterSource={{ uri: API_HOST + currentMessage.attachment.attachment_small_url }}
  118. onError={async () => {
  119. if (retryCount >= MAX_RETRY) {
  120. return;
  121. }
  122. if (videoUri) {
  123. await FileSystem.deleteAsync(videoUri, { idempotent: true });
  124. const newUri = await downloadVideo(currentMessage.video);
  125. if (newUri) {
  126. setVideoUri(newUri);
  127. setRetryCount(retryCount + 1);
  128. }
  129. }
  130. }}
  131. />
  132. ) : null}
  133. {isBuffering && (
  134. <View
  135. style={{
  136. position: 'absolute',
  137. top: 0,
  138. left: 0,
  139. right: 0,
  140. bottom: 0,
  141. alignItems: 'center',
  142. justifyContent: 'center'
  143. }}
  144. >
  145. <ActivityIndicator
  146. size="large"
  147. color={leftMessage ? Colors.DARK_BLUE : Colors.FILL_LIGHT}
  148. />
  149. </View>
  150. )}
  151. {!isPlaying && !isBuffering && videoUri && (
  152. <TouchableOpacity
  153. style={{
  154. position: 'absolute',
  155. top: 0,
  156. left: 0,
  157. right: 0,
  158. bottom: 0,
  159. alignItems: 'center',
  160. justifyContent: 'center'
  161. }}
  162. onPress={handlePlayPress}
  163. onLongPress={() => onLongPress(currentMessage, props)}
  164. >
  165. <View style={{ backgroundColor: 'rgba(15, 63, 79, 0.4)', borderRadius: 50 }}>
  166. <MaterialCommunityIcons name="play" size={60} color={Colors.WHITE} />
  167. </View>
  168. </TouchableOpacity>
  169. )}
  170. </View>
  171. );
  172. };
  173. export default RenderMessageVideo;