Created
November 28, 2023 17:10
-
-
Save hirbod/0996926a1848b3481bee99006563d853 to your computer and use it in GitHub Desktop.
expo-video-thumbnails for iOS, Android and Web
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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