-
-
Save kharioki/b709894cb0a99c5ef563067a25962ce9 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