移动端视频兼容性处理-图片序列帧技术

839 阅读2分钟

需求背景:移动端全屏视频做背景

先简要说说video在移动端的一些坑(详细的可参考其他大神的文章,这里不赘述)

  • ios非静音模式下不能自动播放
  • 一些内核浏览器会出现全屏浮层
  • 显示浏览器自带控制条

那么如何完美的解决这一系列问题呢?

思考:video自身的问题我们无法解决,那么就考虑用其他方式代替video。

第一次尝试方案:切割成多个gif图,按顺序播放

此方案的问题在于,我们不能保证每个gif的时长相同且永久不变,也无法监听到gif播放完成的动作,使用顺序轮询的方式加载难以控制gif之间的衔接。

第二次尝试方案:图片序列帧技术

什么是图片序列帧技术?说白了就是把视频通过工具转成一堆连续的图片,把这些图片顺序加载,视觉呈现出来就是一个视频的样子。假如一秒8帧,就每125ms更新一次图片资源。

此处推荐一个视频转图片的在线工具

www.img2go.com/

看到有网友用dom创建和销毁的方式去更新图片资源,而且1秒24帧的频率播放。然而dom的创建和销毁过程太耗cpu了,不可取,我使用的是一个img标签,通过修改src的方式更新资源。加之,如果不是对视频质量有太高要求的,可以适当放缓加载频率。

需要注意的点:
  • 图片资源先全部预加载之后再开始操作
  • 页面销毁时清除定时器
  • 初始时图片一个默认src占位

意外惊喜

图片序列帧技术除了可以解决video在移动端的兼容性问题之外,也可以做到其他稍复杂一点的交互。 比如:当视频播放到某一个时刻,换帧、显示文字、显示一些可点击button等一些列交互,主要就是通过判断图片加载的index。

上代码

先预加载图片
/**
 * 图片预加载
 *
 * @param  {[]} imgs 图片数组
 * @param  {function} callback 图片全部加载完的回调
 *
 */
export const preLoad = (imgs = [], callback) => {
  const arr = [];
  for (let i = 0; i <= imgs.length; i++) {
    arr[i] = new Image();
    arr[i].src = imgs[i];
    arr[i].onload = () => {
      if (i === imgs.length - 1) {
        console.log('全部加载完成');
        typeof callback === 'function' && callback();
      }
    };
  }
};
开始实现
import styles from './video.module.scss';
import React, { useRef, useEffect } from 'react';
import ScrollTip from '@/components/m-scroll-tip';
import { preLoad } from '@/stat/utils';


const BannerVideo = () => {
  const imgInstance = useRef(null);
  const video_timer = useRef(null);

  const imgList = () => {//定义图片数组
    const arr = [];
    for (let i = 0; i <= 100; i++) {
      arr.push(`https://xx_${i}.jpg`);
    }
    return arr;
  };
  const play = () => {
    let count = 0;
    const imgs = imgList();
    (function a() {
      if (count > 100) { count = 0; }//所有图片播放完后从0开始重播
      if (imgInstance?.current) {
        imgInstance.current.src = imgs[count];//更新图片src
        count++;
      }
      video_timer.current = setTimeout(a, 125);//每125ms执行一次
    })();
  };

  useEffect(() => {
    const imgs = imgList();
    preLoad(imgs, play);//图片预加载
    return () => {
      clearTimeout(video_timer.current);//页面销毁清除定时器
    };
  }, []);

  return (
    <div className={styles.box}>
      <img className={styles['video-img']} ref={imgInstance} src="https://xx_00.jpg" />
    </div>
  );
};

export default BannerVideo;