Skip to content

Commit

Permalink
Prevent unnecessary re-renders for VideoAttachment
Browse files Browse the repository at this point in the history
  • Loading branch information
fjsj committed Jan 16, 2025
1 parent b2d9457 commit dcbe297
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 47 deletions.
90 changes: 46 additions & 44 deletions components/ChatMessageBubble.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Image } from "expo-image";
import { useVideoPlayer } from "expo-video";
import { VideoView } from "expo-video";
import { CirclePlay, FileDown, UserRound } from "lucide-react-native";
import { useCallback, useRef, useState } from "react";
import { memo, useCallback, useRef, useState } from "react";
import { Pressable, StyleSheet, View } from "react-native";
import { Alert } from "react-native";

Expand All @@ -14,7 +14,7 @@ import { Text } from "@/components/ui/text";
import type { ChatMessage } from "@/models/chat";
import type { AttachmentWithUrl } from "@/types/attachment";
import { formatTime } from "@/utils/datetime";
import { mediaKey, shareFile } from "@/utils/media";
import { isMediaExpired, mediaKey, shareFile } from "@/utils/media";

interface ChatMessageBubbleProps {
message: ChatMessage;
Expand All @@ -28,50 +28,53 @@ const mediaStyles = StyleSheet.create({
},
});

function VideoAttachment({ uri }: { uri: string }) {
const player = useVideoPlayer(uri, (player) => {
player.loop = true;
player.bufferOptions = {
minBufferForPlayback: 0,
preferredForwardBufferDuration: 5,
maxBufferBytes: 0,
prioritizeTimeOverSizeThreshold: false,
waitsToMinimizeStalling: true,
};
});
const videoRef = useRef<VideoView>(null);
const [isFullscreen, setIsFullscreen] = useState(false);
const VideoAttachment = memo(
({ uri }: { uri: string }) => {
const player = useVideoPlayer(uri, (player) => {
player.loop = true;
player.bufferOptions = {
// Reduce buffer for performance:
minBufferForPlayback: 0,
preferredForwardBufferDuration: 5,
};
});
const videoRef = useRef<VideoView>(null);
const [isFullscreen, setIsFullscreen] = useState(false);

const handlePlayPress = useCallback(() => {
if (!player) return;
player.play();
videoRef.current?.enterFullscreen();
setIsFullscreen(true);
}, [player]);
const handlePlayPress = useCallback(() => {
if (!player) return;
player.play();
videoRef.current?.enterFullscreen();
setIsFullscreen(true);
}, [player]);

const handleExitFullscreen = useCallback(() => {
setIsFullscreen(false);
}, []);
const handleExitFullscreen = useCallback(() => {
player?.pause();
setIsFullscreen(false);
}, [player]);

return (
<View className="relative">
<VideoView
ref={videoRef}
key={mediaKey(uri)}
style={mediaStyles.media}
player={player}
nativeControls={isFullscreen}
onFullscreenExit={handleExitFullscreen}
/>
<Pressable
onPress={handlePlayPress}
className="absolute inset-0 items-center justify-center bg-black/50"
>
<Icon as={CirclePlay} size="xl" className="text-typography-0" />
</Pressable>
</View>
);
}
return (
<View className="relative">
<VideoView
ref={videoRef}
style={mediaStyles.media}
player={player}
nativeControls={isFullscreen}
onFullscreenExit={handleExitFullscreen}
/>
<Pressable
onPress={handlePlayPress}
className="absolute inset-0 items-center justify-center bg-black/50"
>
<Icon as={CirclePlay} size="xl" className="text-typography-0" />
</Pressable>
</View>
);
},
(oldProps: { uri: string }, newProps: { uri: string }) =>
mediaKey(oldProps.uri) === mediaKey(newProps.uri) && !isMediaExpired(oldProps.uri),
);
VideoAttachment.displayName = "VideoAttachment";

function FileAttachment({ attachment }: { attachment: AttachmentWithUrl }) {
const [isDownloading, setIsDownloading] = useState(false);
Expand Down Expand Up @@ -129,7 +132,6 @@ export function ChatMessageBubble({ message, avatarURL }: ChatMessageBubbleProps
{hasImage ? (
<Image
style={mediaStyles.media}
key={mediaKey(message.attachment.url)}
source={message.attachment.url}
contentFit="contain"
alt={`Attachment ${message.attachment.title}`}
Expand Down
18 changes: 15 additions & 3 deletions utils/media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,28 @@ import * as Sharing from "expo-sharing";

import type { AttachmentWithUrl } from "@/types/attachment";

/**
* Check AWS media URL isn't expired
* @param url - The media URL
* @returns True if the media URL is expired, false otherwise
*/
export function isMediaExpired(url: string): boolean {
const urlObj = new URL(url);
const expires = urlObj.searchParams.get("Expires");
if (!expires) {
return false;
}
return new Date(parseInt(expires, 10) * 1000) < new Date();
}

/**
* Removes AWS secret parameters from a media URL
* @param attachment - The attachment with the URL
* @returns The media URL without AWS secret parameters
*/
export function mediaKey(url: string): string {
const urlObj = new URL(url);
urlObj.searchParams.delete("AWSAccessKeyId");
urlObj.searchParams.delete("Signature");
urlObj.searchParams.delete("Expires");
urlObj.search = "";
return urlObj.toString();
}

Expand Down

0 comments on commit dcbe297

Please sign in to comment.