DEV Community

Cover image for Generate video thumbnails in ReactJS, an open source thumbnail library.
Rajesh Royal
Rajesh Royal

Posted on • Edited on

Generate video thumbnails in ReactJS, an open source thumbnail library.

Have you ever needed where the user uploads the video and has the option to select a thumbnail? if yes keep reading.

Recently I needed to create a service in one of my projects where users will upload a video and can select a thumbnail of that video, and I needed to generate multiple thumbnails from the different timeframes of video. So the user can select one of the thumbnail from the given

something like this [can't upload the original screen]

Video thumbnails generate with vanilla JS. Like YouTube
 
I search for JS libraries which can do this for me but ended up creating my own NPM package.
video-thumbnails-generator
Contributions are more than welcome, I have already added a roadmap in the Readme.md.
 

But first lets see how its working behind the scene.

🔵 Index.js The Driver



// convert image to object part instead of base64 for better performance
// https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL
export const importFileandPreview = (file, revoke) => {
    return new Promise((resolve, reject) => {
        window.URL = window.URL || window.webkitURL;
        let preview = window.URL.createObjectURL(file);
        // remove reference
        if (revoke) {
            window.URL.revokeObjectURL(preview);
        }
        setTimeout(() => {
            resolve(preview);
        }, 100);
    });
}


/**
 *
 * @param videoFile {FIle} // the video file
 * @param numberOfThumbnails {number} //number of thumbnails you want to generate
 * @returns {string[]} // an array of base64 thumbnails images
 *
 * @abstract
 * Idea taken from - https://codepen.io/aertmann/pen/mrVaPx
 * The original functionality of getVideoThumbnail() function is customized as per working code
 * If it didn't work in future then replace it with about links working example
 */
export const generateVideoThumbnails = async (videoFile, numberOfThumbnails) => {

    let thumbnail = [];
    let fractions = [];
    return new Promise(async (resolve, reject) => {
        if (!videoFile.type?.includes("video")) reject("not a valid video file");
        await getVideoDuration(videoFile).then(async (duration) => {
            // divide the video timing into particular timestamps in respective to number of thumbnails
            // ex if time is 10 and numOfthumbnails is 4 then result will be -> 0, 2.5, 5, 7.5 ,10
            // we will use this timestamp to take snapshots
            for (let i = 0; i <= duration; i += duration / numberOfThumbnails) {
                fractions.push(Math.floor(i));
            }
            // the array of promises
            let promiseArray = fractions.map((time) => {
                return getVideoThumbnail(videoFile, time)
            })
            // console.log('promiseArray', promiseArray)
            // console.log('duration', duration)
            // console.log('fractions', fractions)
            await Promise.all(promiseArray).then((res) => {
                res.forEach((res) => {
                    // console.log('res', res.slice(0,8))
                    thumbnail.push(res);
                });
                // console.log('thumbnail', thumbnail)
                resolve(thumbnail);
            }).catch((err) => {
                console.error(err)
            }).finally((res) => {
                console.log(res);
                resolve(thumbnail);
            })
        });
        reject("something went wront");
    });
};

const getVideoThumbnail = (file, videoTimeInSeconds) => {
    return new Promise((resolve, reject) => {
        if (file.type.match("video")) {
            importFileandPreview(file).then((urlOfFIle) => {
                var video = document.createElement("video");
                var timeupdate = function () {
                    if (snapImage()) {
                        video.removeEventListener("timeupdate", timeupdate);
                        video.pause();
                    }
                };
                video.addEventListener("loadeddata", function () {
                    if (snapImage()) {
                        video.removeEventListener("timeupdate", timeupdate);
                    }
                });
                var snapImage = function () {
                    var canvas = document.createElement("canvas");
                    canvas.width = video.videoWidth;
                    canvas.height = video.videoHeight;
                    canvas.getContext("2d").drawImage(video, 0, 0, canvas.width, canvas.height);
                    var image = canvas.toDataURL();
                    var success = image.length > 100000;
                    if (success) {
                        URL.revokeObjectURL(urlOfFIle);
                        resolve(image);
                    }
                    return success;
                };
                video.addEventListener("timeupdate", timeupdate);
                video.preload = "metadata";
                video.src = urlOfFIle;
                // Load video in Safari / IE11
                video.muted = true;
                video.playsInline = true;
                video.currentTime = videoTimeInSeconds;
                video.play();
            });
        } else {
            reject("file not valid");
        }
    });
};

/**
 *
 * @param videoFile {File}
 * @returns {number} the duration of video in seconds
 */
export const getVideoDuration = (videoFile)=> {
    return new Promise((resolve, reject) => {
        if (videoFile) {
            if (videoFile.type.match("video")) {
                importFileandPreview(videoFile).then((url) => {
                    let video = document.createElement("video");
                    video.addEventListener("loadeddata", function () {
                        resolve(video.duration);
                    });
                    video.preload = "metadata";
                    video.src = url;
                    // Load video in Safari / IE11
                    video.muted = true;
                    video.playsInline = true;
                    video.play();
                    //  window.URL.revokeObjectURL(url);
                });
            }
        } else {
            reject(0);
        }
    });
};


Enter fullscreen mode Exit fullscreen mode

🔵 Explanations

importFileandPreview()



/**
* This function will take an File object and will convert it
* into windowObjectURI which look something like this - 
* blob:http://localhost/2d7b2c97-02f3-4e7d-a6c1-d04746c27730
*/
export const importFileandPreview = (file, revoke) => {
    return new Promise((resolve, reject) => {
       //@todo - your logic here
    });
}


Enter fullscreen mode Exit fullscreen mode

getVideoDuration()



/**
 * @abbrivation This function takes a video File object as an 
 * input and returns the duration of that video.
 *
 * @param videoFile {File}
 * @returns {number} the duration of video in seconds
 */
export const getVideoDuration = (videoFile)=> {
    return new Promise((resolve, reject) => {
        if (videoFile) {
           resolve(duration);
        } else {
            reject(0);
        }
    });
};



Enter fullscreen mode Exit fullscreen mode

getVideoThumbnail()



/**
* @abbrivation 
* This function takes a video File Object and the time where we
* need a snapshot of video screen.
* It will return a snapshot of the video at the given time
* in `base64` format.
*
* @param {File} file 
* @param {number} videoTimeInSeconds 
* @returns string // base64Image 
*/
const getVideoThumbnail = (file, videoTimeInSeconds) => {
    return new Promise((resolve, reject) => {
        if (file.type.match("video")) {
            resolve(thumbnail); //base64 image
        } else {
            reject("file not valid");
        }
    });
};



Enter fullscreen mode Exit fullscreen mode

generateVideoThumbnails()



/**
 * This functin will take two input video File and Number
 * And It will generate that many thumbnails.
 *
 * @param videoFile {FIle} // the video file
 * @param numberOfThumbnails {number} //number of thumbnails you want to generate
 * @returns {string[]} // an array of base64 thumbnails images
 *
 * @abstract
 * Idea taken from - https://codepen.io/aertmann/pen/mrVaPx
 * The original functionality of getVideoThumbnail() function is customized as per working code
 * If it didn't work in future then replace it with about links working example
 */
export const generateVideoThumbnails = async (videoFile, numberOfThumbnails) => {

    let thumbnail = [];
    let fractions = [];
    return new Promise(async (resolve, reject) => {
        if (!videoFile.type?.includes("video")) reject("not a valid video file");
            // first we get video duration
            // then we calculate how many thumbnails to generate
            // we cann generateThumbnail() function that many times
            // then we resolve all those promises and return result.
            await Promise.all(promiseArray).then((res) => {
                res.forEach((res) => {
                    thumbnail.push(res);
                });
                resolve(thumbnail);
            }).catch((err) => {
                console.error(err)
            }).finally((res) => {
                resolve(thumbnail);
            })
        });
        reject("something went wrong");
    });
};


Enter fullscreen mode Exit fullscreen mode

See it in action - Live Demo.

Video thumbnails generate with vanilla JS. Like YouTube
 

Hope you enjoyed it reading. Do not forget share of hit that heart icon 😀 see you soon with new material 👋.

Top comments (4)

Collapse
 
muhkyle17 profile image
Mikyle Reyes

Just wanted to ask, is this npm package still being actively maintained?

Collapse
 
emmysteven profile image
Emmy Steven

Yes, the npm package is still being actively maintained, it is constantly being maintained.

Kindly note that this is something you can google out yourself.

Collapse
 
muhkyle17 profile image
Mikyle Reyes

Oh yes that's correct. Thank you so much for your response!