Skip to content

Instantly share code, notes, and snippets.

@hirbod
Created November 28, 2023 17:10
Show Gist options
  • Save hirbod/0996926a1848b3481bee99006563d853 to your computer and use it in GitHub Desktop.
Save hirbod/0996926a1848b3481bee99006563d853 to your computer and use it in GitHub Desktop.
expo-video-thumbnails for iOS, Android and Web
import React, { useState, useEffect, useCallback } from "react";
import { Platform } from "react-native";
import * as VideoThumbnails from "expo-video-thumbnails";
import { Image } from "@showtime-xyz/universal.image";
import Spinner from "@showtime-xyz/universal.spinner";
import { View } from "@showtime-xyz/universal.view";
interface VideoThumbnailProps {
videoUri: string;
timeFrame?: number;
}
const VideoThumbnail: React.FC<VideoThumbnailProps> = ({
videoUri,
timeFrame = 0,
}) => {
const [thumbnailUri, setThumbnailUri] = useState<string | null>(null);
const generateThumbnailForWeb = useCallback(
async (uri: string, time: number): Promise<string> => {
return new Promise((resolve, reject) => {
const video = document.createElement("video");
video.src = uri;
video.crossOrigin = "anonymous";
video.addEventListener("loadeddata", () => {
try {
video.currentTime = time;
} catch (e) {
reject(e);
}
});
video.addEventListener("seeked", () => {
try {
const canvas = document.createElement("canvas");
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
const ctx = canvas.getContext("2d");
if (ctx) {
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
const imageUrl = canvas.toDataURL();
resolve(imageUrl);
} else {
reject(new Error("Failed to get canvas context"));
}
} catch (e) {
reject(e);
}
});
video.load();
});
},
[]
);
const generateThumbnail = useCallback(async () => {
if (Platform.OS === "web") {
try {
const thumbnailUrl = await generateThumbnailForWeb(videoUri, timeFrame);
setThumbnailUri(thumbnailUrl);
} catch (e) {
console.warn("Error generating thumbnail for web:", e);
}
} else {
try {
const { uri } = await VideoThumbnails.getThumbnailAsync(videoUri, {
time: timeFrame * 1000,
});
setThumbnailUri(uri);
} catch (e) {
console.warn("Error generating thumbnail for mobile:", e);
}
}
}, [generateThumbnailForWeb, timeFrame, videoUri]);
useEffect(() => {
generateThumbnail();
}, [videoUri, timeFrame, generateThumbnail]);
return (
<View
tw="overflow-hidden rounded-2xl border-2 border-gray-500 bg-black dark:border-gray-800"
style={{
aspectRatio: 11 / 16,
}}
>
{thumbnailUri ? (
<Image
source={thumbnailUri}
alt="Video Thumbnail"
height={200}
width={200}
contentFit="contain"
style={{
aspectRatio: 11 / 16,
backgroundColor: "black",
overflow: "hidden",
}}
/>
) : (
<Spinner />
)}
</View>
);
};
export default VideoThumbnail;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment