JS每隔一秒提取视频缩略图实例页面

回到相关文章 »

效果:

1. 本地视频

2. 网络视频

//注意:需要允许跨域

提供地址:https://imgservices-1252317822.image.myqcloud.com/coco/b12202023/67430f37.6y59kt.mp4

代码:

CSS代码:
.result:not(:empty) {
	border: 1px dashed darkgray;
	padding: 12px 16px;
	margin-top: 24px;
}
HTML代码:
<h4>1. 本地视频</h4>
<input type="file" id="file" accept="video/*">

<h4>2. 网络视频</h4>
<input type="url" id="input" placeholder="在线视频地址"> 

<div id="result" class="result"></div>
JS代码:
const handleGetVideoThumb = function (url, options = {}) {
    if (typeof url != 'string') {
        return;    
    }
    // 默认参数
    const defaults = {
        onLoading: () => {},
        onLoaded: () => {},
        onFinish: (arr) => {}
    };
    
    const params = Object.assign({}, defaults, options);
    
    // 基于视频元素绘制缩略图,而非解码视频
    const video = document.createElement('video');
    // 静音
    video.muted = true;
    
    // 绘制缩略图的canvas画布元素
    const canvas = document.createElement('canvas');
    const context = canvas.getContext('2d', {
        willReadFrequently: true
    });
    
    // 绘制缩略图的标志量
    let isTimeUpdated = false;
    // 几个视频事件
    // 1. 获取视频尺寸
    video.addEventListener('loadedmetadata', () => {
        canvas.width = video.videoWidth;
        canvas.height = video.videoHeight;
        
        // 开始执行绘制
        draw();
    });
    // 2. 触发绘制监控
    video.addEventListener('timeupdate', () => {
        isTimeUpdated = true;
    });
    
    // 获取视频数据
    params.onLoading();
    // 请求视频地址,如果是本地文件,直接执行
    if (/^blob:|base64,/i.test(url)) {
        video.src = url;    
    } else {
        fetch(url).then(res => res.blob()).then(blob => {
            params.onLoaded();
            // 赋予视频
            video.src = URL.createObjectURL(blob);
        });
    }
    
    // 绘制方法
    const draw = () => {
        const arrThumb = [];
        const duration = video.duration;
        let seekTime = 0.1;

        const loop = () => {
            if (isTimeUpdated) {                
                context.clearRect(0, 0, canvas.width, canvas.height);
                context.drawImage(video, 0, 0, canvas.width, canvas.height);

                canvas.toBlob(blob => {
                    arrThumb.push(URL.createObjectURL(blob));

                    seekTime += 1;

                    if (seekTime > duration) {
                        params.onFinish(arrThumb);

                        return;
                    }

                    step();
                }, 'image/jpeg');

                return;
            }
            // 监控状态
            requestAnimationFrame(loop);
        }
        
        // 逐步绘制,因为currentTime修改生效是异步的
        const step = () => {
            isTimeUpdated = false;
            video.currentTime = seekTime;
            
            loop();
        }

        step();
    }
};


const handleThumbUrl = function (arrUrl) {
    result.innerHTML = arrUrl.map(url => `<img src="${url}">`).join('');
};

file.onchange = function (event) {
    const file = event.target.files[0];
    // 开始识别
    const reader = new FileReader();
    reader.onload = function (event) {
        const url = URL.createObjectURL(new Blob([event.target.result]));
        handleGetVideoThumb(url, {
            onFinish: handleThumbUrl
        });
    };
    reader.readAsArrayBuffer(file);
};

input.onchange = function () {
    if (this.matches(':invalid')) {
        new LightTip('请输入合法的URL地址', 2000, 'error');
        
        return;    
    }
    
    const url = this.value.trim();
    
    if (!url) {
        return;    
    }
    
    handleGetVideoThumb(url, {
        onLoading: () => {
            result.innerHTML = '<ui-loading></ui-loading>';
        },
        onFinish: handleThumbUrl
    });
};