又要做动画?什么!美术给我200M的序列帧?

612 阅读7分钟

前言

动画的需求他虽迟但到,不过没关系之前我已经经历过一次了。不就是移动端进入首页全屏的自动播放动画嘛?

哥们有经验!上序列帧就完活辣!

于是我们贴心的动画师十分配合得将动画文件丢给了我

好家伙,这个大小,我难道加一个loading页让用户睡一觉再来吗? 这可是万万不能接受的。

然后我又在动画的文件夹里面发现了这个

MP4 不错,虽然20兆也很大,不过这也算是减小了百分之九十的大小了迈出了非常大的一步啊。

但是在我之前的文章里面说过关于用视频做移动端动画的问题

(美术:你要什么格式的动画?我?我也不知道我要什么啊)

那我们也只能想办法逐一解决问题了。

用canvas绘制视频

之前的文章里面提到了用 在小米浏览器会出现进度条,或者其它安卓手机也会出现进度条的情况。那么很简单的思路就是我不用标签不就得了吗!

于是和之前的思路类似,我们直接上万能的canvas

 <template>
  <div class="animation-container">
    <canvas ref="canvas" class="canvas"></canvas>
  </div>
</template>   

和绘制序列帧的文章相同思路,先加载视频然后往canvas 上绘制就行了。

   const canvas = ref<HTMLCanvasElement | null>(null);
   let video: HTMLVideoElement | null = null;
   let drawAnimationFrameId = 0; // 用于记录画布绘制的动画帧请求ID
 
   
    // 加载视频函数
    const loadVideo = () => {
      return new Promise<void>((resolve, reject) => {
        if (!canvas.value) return reject(new Error('Canvas element not found,000000000000'));
        video = document.createElement('video');
        video.src = 'https://yourvideo.information.com/transition.mp4';
        video.crossOrigin = "anonymous";
        video.preload = 'auto';
        video.autoplay = false; // 预加载时不自动播放
        video.muted = true;

        video.addEventListener('loadedmetadata', () => {
          const videoWidth = video.videoWidth;
          const videoHeight = video.videoHeight;
          const dpr = window.devicePixelRatio || 1;
          canvas.value.width = videoWidth * dpr;
          canvas.value.height = videoHeight * dpr;

          const ctx = canvas.value.getContext('2d');
          if (!ctx) return reject(new Error('Could not get 2D context for canvas'));
          if (video) {
        // 绘制视频第一帧到canvas上,模拟预加载绘制
            ctx.drawImage(video, 0, 0, canvas.value.width, canvas.value.height);
            video.addEventListener('canplaythrough', () => {
              console.log('video canplay');
              resolve();
            });
       }
    });
    video.addEventListener('error', (error) => {
      console.log('video error', error);
      reject(error);
    });
  });
}
    
//播放视频方法
const startCanvasVideo = () => {
      if (!canvas.value) return;
      video.autoplay = true;
      video.addEventListener('loadedmetadata', () => {
        if (!video) return;
        const videoWidth = video.videoWidth;
        const videoHeight = video.videoHeight;

        const dpr = window.devicePixelRatio || 1;
        if (canvas.value) {
          canvas.value.width = videoWidth * dpr;
          canvas.value.height = videoHeight * dpr;
        }

        if (!canvas.value) return;
        const ctx = canvas.value.getContext('2d');
        if (!ctx) return;

        const draw = () => {
          if (!video || video.paused || video.ended) {
            // 视频停止播放时,取消绘制动画帧请求,停止绘制
            if (drawAnimationFrameId) {
              cancelAnimationFrame(drawAnimationFrameId);
            }
            return;
          }
      if (canvas.value) {
        ctx.drawImage(video, 0, 0, canvas.value.width, canvas.value.height);
      }
      drawAnimationFrameId = requestAnimationFrame(draw);
    };

    video.addEventListener('canplay', () => {
      draw();
    });

    video.addEventListener('ended', () => {
      // 视频播放结束,重新播放视频
      if (video) {
        video.currentTime = 0;
        video.play();
      }
    });

    video.play();
    canvas.value.style.display = 'block';
  });
} 

在生命周期函数里面调用开始视频方法就然后大家可以根据自己的需求在比如视频播放一次结束的时候做结束动画,或者通知父组件等操作。

很简单就实现了用canvas绘制视频的需求,在chrome模拟器里发现效果也是没得说啊!

然后借用拿起安卓手机一看,进度条是没了。但是问题是他不播啊!这视频不管静音不静音他都不播啊!不播就没法画,回忆一下自己上次为什么不用canvas画视频想起来了,

那这时候我只能又双叒叕考虑一下刚刚被我抛弃的序列帧了吗?

插播一下

将本来的序列帧图片用格式工厂从png转为webp,可以让整个大小压缩到20M左右,如果可以接受画质的下降,还可以做更多压缩。

JSMpeg.js

为了解决移动端视频自动播放的问题,调研到了这个方案方案。

JSMpeg 是一个用于在网页上播放 MPEG 流的 JavaScript 库,它可以在不依赖浏览器原生视频播放器的情况下,直接在 HTML5 的<canvas>元素上播放 MPEG 视频流,以下是关于它的详细介绍:

  1. 基本原理:JSMpeg 通过解析 MPEG 视频流数据,并将其转换为适合在<canvas>上绘制的帧数据。它利用 JavaScript 的特性,逐帧处理视频数据,并将这些帧绘制到<canvas>上,从而实现视频的播放效果。与传统的依赖浏览器原生<video>标签播放视频的方式不同,JSMpeg 完全通过 JavaScript 代码来控制视频的播放过程,这为开发者提供了更大的灵活性和控制权。

  2. 特点

    • 兼容性好:由于它不依赖浏览器的原生视频播放功能,因此在一些对视频格式支持有限的浏览器中,也能够实现视频播放。可以在较老的浏览器版本或者一些特殊的浏览器环境中使用,拓宽了视频播放的应用场景。
    • 可定制性高:开发者可以根据自己的需求,对视频的播放过程进行深入定制。例如,可以实现自定义的播放控制条、添加视频滤镜效果、与其他网页元素进行更紧密的交互等。这种高度的可定制性使得 JSMpeg 非常适合用于开发一些具有独特交互性的网页应用,如在线教育平台中的视频播放器、互动式视频广告等。
    • 性能较好:通过直接在<canvas>上绘制视频帧,JSMpeg 能够在一定程度上优化视频播放的性能。它可以根据设备的性能和网络状况,动态调整视频的播放帧率和分辨率,以确保视频播放的流畅性。在一些对性能要求较高的场景中,如实时视频监控系统中,JSMpeg 的性能优势就显得尤为突出。
  3. 应用场景

    • 实时视频流:在一些需要实时显示视频流的应用中,如网络摄像头监控、视频会议等,JSMpeg 可以实现低延迟的视频播放。它能够快速地接收和处理视频流数据,并及时将视频帧绘制到<canvas>上,让用户能够实时看到视频画面。
    • 互动式视频应用:由于其高度的可定制性,JSMpeg 非常适合用于开发互动式视频应用。例如,可以在视频播放过程中添加互动元素,如点击视频中的某个物体弹出相关信息、通过拖动视频画面来切换视角等,增强用户与视频之间的互动体验。
    • 游戏中的视频播放:在一些基于网页的游戏中,可能需要播放视频来展示游戏剧情、过场动画等。JSMpeg 可以在游戏中无缝地集成视频播放功能,并且可以根据游戏的需求对视频播放进行定制,如控制视频的播放速度、暂停和恢复播放等。
  4. 局限性

    • 编码限制:JSMpeg 主要支持 MPEG 格式的视频流,对于其他常见的视频格式,如 MP4、AVI 等,需要先进行格式转换才能使用。这在一定程度上限制了它的应用范围,特别是在一些对视频格式兼容性要求较高的场景中。
    • 资源消耗:由于 JSMpeg 需要通过 JavaScript 代码来解析和处理视频流数据,因此在播放视频时会消耗一定的系统资源,如 CPU 和内存。在一些性能较低的设备上,可能会出现视频播放卡顿、掉帧等问题。

ffmpeg使用教程 - 简书

javascript - 移动端视频播放方案——JSMpeg - 个人文章 - SegmentFault 思否

ffmpeg使用教程 - 简书

使用

所以我们想使用JSMpeg 需要将我们的mp4转成格式ts 输入视频的分辨率为 750x1624。 这里的编码格式是不能随意修改的,不然会导致播放不成功。

当你进行转换时,若要保证分辨率不变,可使用如下

ffmpeg -i video.mp4 -c:v mpeg1video -s 750x1624 -c:a copy -f mpegts outputs.ts 

video.mp4 是你的目标视频,output.ts是你的结果视频 如果觉得视频清晰度下降过大可以这样做。

ffmpeg -i video.mp4 -c:v mpeg1video -b:v 5000k -s 750x1624 -c:a copy -f mpegts outputs.ts

这里的 -b:v 5000k 把视频码率设置成了 5000kbps,较高的码率通常能提供更好的视频质量。你可以根据实际需求来调整这个值。

   <template>
  <div class="test">
    <canvas ref="videoRef" id="jsmpeg-canvas"></canvas>
  </div>
</template>

<script setup lang="ts">
import { onMounted, ref } from 'vue';

const props = defineProps({
  videoUrl: {
    type: String,
    required: true
  }
});

const videoRef = ref<HTMLCanvasElement | null>(null);

onMounted(() => {
  if (videoRef.value) {
    const demo = new JSMpeg.Player(props.videoUrl, {
      canvas: videoRef.value,
      throttled: false,
      progressive: false,
      loop: true,
      audio: false,
      autoplay: true,
      onSourceCompleted: () => {
        console.log('completed');
      },
      onPlay: () => {
        console.log('play');
      },
      onPause: () => {
        console.log('pause');
      },
      onEnded: () => {
        console.log('end');
      },
      onStalled: () => {
        console.log('没有足够的数据用于播放');
      },
      onSourceEstablished: () => {
        console.log('第一次收到数据');
      }
    });
  }
});
</script> 

这样就实现了视频的自动播放,不仅仅是可以通过这种方式实现视频动画,大家也可以根据 JSMpeg的特点结合自己的业务需求在其它场景下使用。