Hey is a decentralized and permissionless social media app built with Lens Protocol 🌿

Improve thumbnail generation (#5676)

authored by yoginth.com and committed by

GitHub 93e79322 eb2f9843

+51 -49
+1 -1
apps/web/src/components/Composer/ChooseThumbnail.tsx
··· 1 1 import ThumbnailsShimmer from "@/components/Shared/Shimmer/ThumbnailsShimmer"; 2 2 import { Spinner } from "@/components/Shared/UI"; 3 - import { generateVideoThumbnails } from "@/helpers/generateVideoThumbnails"; 3 + import generateVideoThumbnails from "@/helpers/generateVideoThumbnails"; 4 4 import getFileFromDataURL from "@/helpers/getFileFromDataURL"; 5 5 import { uploadFileToIPFS } from "@/helpers/uploadToIPFS"; 6 6 import { usePostAttachmentStore } from "@/store/non-persisted/post/usePostAttachmentStore";
+50 -48
apps/web/src/helpers/generateVideoThumbnails.ts
··· 1 - const canvasImageFromVideo = ( 1 + const generateVideoThumbnails = async ( 2 2 file: File, 3 - currentTime: number 4 - ): Promise<string> => { 5 - return new Promise((resolve) => { 6 - const video = document.createElement("video"); 7 - const canvas = document.createElement("canvas"); 8 - video.autoplay = true; 9 - video.muted = true; 10 - video.src = URL.createObjectURL(file); 11 - video.onloadedmetadata = () => { 12 - video.currentTime = currentTime; 13 - }; 14 - video.oncanplay = () => { 15 - setTimeout(() => { 16 - const ctx = canvas.getContext("2d"); 3 + count: number 4 + ): Promise<string[]> => { 5 + if (!file.size) { 6 + return []; 7 + } 8 + 9 + const url = URL.createObjectURL(file); 10 + const video = document.createElement("video"); 11 + const canvas = document.createElement("canvas"); 12 + video.muted = true; 13 + video.src = url; 14 + 15 + try { 16 + await new Promise<void>((resolve, reject) => { 17 + video.onloadeddata = () => { 17 18 canvas.width = video.videoWidth; 18 19 canvas.height = video.videoHeight; 19 - ctx?.drawImage(video, 0, 0, video.videoWidth, video.videoHeight); 20 - return resolve(canvas.toDataURL("image/png")); 21 - }, 100); 20 + resolve(); 21 + }; 22 + video.onerror = () => reject(); 23 + }); 24 + 25 + let queue: Promise<void> = Promise.resolve(); 26 + const seekAndCapture = (time: number): Promise<string> => { 27 + const result = queue.then( 28 + () => 29 + new Promise<string>((resolve) => { 30 + const handleSeeked = () => { 31 + const ctx = canvas.getContext("2d"); 32 + ctx?.drawImage(video, 0, 0, video.videoWidth, video.videoHeight); 33 + resolve(canvas.toDataURL("image/png")); 34 + }; 35 + video.addEventListener("seeked", handleSeeked, { once: true }); 36 + video.currentTime = time; 37 + }) 38 + ); 39 + queue = result.then(() => undefined); 40 + return result; 22 41 }; 23 - }); 42 + 43 + const step = video.duration / count; 44 + return await Promise.all( 45 + Array.from({ length: count }).map((_, i) => seekAndCapture(step * i)) 46 + ); 47 + } catch { 48 + return []; 49 + } finally { 50 + video.remove(); 51 + canvas.remove(); 52 + URL.revokeObjectURL(url); 53 + } 24 54 }; 25 55 26 - export const generateVideoThumbnails = ( 27 - file: File, 28 - count: number 29 - ): Promise<string[]> => { 30 - return new Promise((resolve) => { 31 - try { 32 - if (!file.size) { 33 - return []; 34 - } 35 - // creating video element to get duration 36 - const video = document.createElement("video"); 37 - video.autoplay = true; 38 - video.muted = true; 39 - video.src = URL.createObjectURL(file); 40 - video.onloadeddata = async () => { 41 - const thumbnailArray: string[] = []; 42 - const averageSplitTime = Math.floor(video.duration / count); 43 - for (let i = 0; i < count; i++) { 44 - const currentTime = averageSplitTime * i; 45 - const thumbnail = await canvasImageFromVideo(file, currentTime); 46 - thumbnailArray.push(thumbnail); 47 - } 48 - resolve(thumbnailArray); 49 - }; 50 - } catch { 51 - resolve([]); 52 - } 53 - }); 54 - }; 56 + export default generateVideoThumbnails;