移动端视频转序列图片播放

项目所用5s的视频,起初使用video标签直接内嵌到页面,看起来很正常。但是在安卓浏览器会弹出小窗播放,不支持视频内嵌。

线上案例: https://mc.163.com/m/brain/

解决方案

将视频转化成序列帧,用JS控制img src的切换,视觉上达到和播放视频一样的效果。这个方法适用于短视频,建议控制在5M以内。最终导出的图片会转化成base64,然后以数组的形式存放在一个JS文件。如果视频太大的话,导出的图片就会多,那么存放base64的JS文件也将会很大,这个要根据具体情况斟酌。

1、使用 Premiere 将视频转化成序列帧

选择合适的尺寸,宽750会比较大,可选600,640。质量选择50左右。输出格式选择JPEG,帧速率选择12-15。5s的视频,选择12帧速率,导出了60张:

2、使用node将导出的图片转化成base64

将导出的图片转化成base64,然后以数组的形式存放在一个JS文件,最终生成这个JS文件。

目录结构

|--img
|--index.js

const fs = require('fs');
const toBase64 = foldername => {
    let fileNmaeArr = fs.readdirSync(foldername);
    let strBase64 = "";
    fileNmaeArr.forEach((item, index, array) => {
        let path = foldername + '/' + item;
        let str = fs.readFileSync(path, {
            encoding: 'base64'
        })
        if (index < array.length - 1) {
            strBase64 += '\"' + str + '\"' + ",";
        } else {
            strBase64 += '\"' + str + '\"';
            let imgs = `var imgList = [${strBase64}]`;
            fs.writeFileSync('img.js', imgs);
            console.log("导出成功!")
        }
    })
}
toBase64('img')

执行 node index.js 导出 img.js

3、JS切换img标签的src,播放序列帧

ImgSequence 调用前,页面要先预加载img.js。


var requestAnimFrame = (function() {
    return (
        window.requestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.oRequestAnimationFrame ||
        window.msRequestAnimationFrame ||
        function(callback) {
            window.setTimeout(callback, 1000 / 60)
        }
    )
})()
var ImgSequence = function() {
    var _class = function(opt) {
        this.container = opt.container;
        this.imgArr = [];
        this.isOver = false;
        this.nowpic = opt.nowpic;
        this.stopPicIndex = 0;
        this.speed = opt.speed || 12;
        this.oldpic = -1;
        this.isLoop = opt.isLoop === false ? false : true;
    }
    _class.prototype = {
        loop: function() {
            if (this.isOver) {
                return;
            }
            var ntime = +new Date;
            var diftime = ntime - this.stime;
            this.nowpic = Math.floor(diftime * this.speed * 0.001) + this.stopPicIndex;
            if (this.nowpic == this.imgArr.length) {
                this.stopPicIndex = 0;
                if (this.isLoop) {
                    this.play();
                }
                return;
            }
            requestAnimFrame(() => {
                if (+new Date - ntime > 100) { 
                    this.stopPicIndex = this.nowpic;
                    this.play();
                } else {
                    this.loop();
                }
            });
            if (this.nowpic == this.oldpic) {
                return;
            }
            this.container.setAttribute("src", "data:image/jpg;base64," + this.imgArr[this.nowpic]);
            this.oldpic = this.nowpic;
        },
        show: function() {
            if (window["imgList"]) {
                this.container.style.display = 'block';
                this.imgArr = window["imgList"];
                this.play();
            }
        },
        play: function() {
            this.nowpic = 0;
            this.oldpic = -1;
            this.stime = +new Date;
            this.stime += this.nowpic * 1000 / this.speed;
            this.container.setAttribute("src", "data:image/jpg;base64," + this.imgArr[this.nowpic + this.stopPicIndex]);
            this.loop();
        },
        onplay: function() {
            this.isOver = false;
            this.play()
        },
        stop: function() {
            this.isOver = true;
            this.stopPicIndex = this.nowpic;
        }
    }
    return {
        init: function(opt) {
            return new _class(opt);
        }
    }
}()

var imgSequence = ImgSequence.init({
    'container': document.getElementById('imgVideo'),//必填
    'speed': 12, //可选,每秒播放多少帧,默认12 ,小于imgList的长度
    'isLoop': true //可选,默认true
})
imgSequence.show() //初始化显示并播放
imgSequence.stop() //暂停
imgSequence.onplay()//继续播放