前言
最近碰到一个bug,客户上传的avi格式视频不能在Chrome浏览器播放,让支持播放。代码直接用的video标签播放,搜索一番了解到是浏览器本身不支持avi格式的播放,于是让后端在视频上传时将avi转为mp4保存,但是客户不满意,客户爸爸的话就是圣旨,就只能让前端想办法兼容一下。
探索思路
Chrome浏览器支持avi的播放,针对该问题,经过思考与搜索,得到了三种解决思路:
- 播放器:代码中用的是video标签,而电脑自带的多媒体播放器就能打开.avi格式的视频,如果有支持播放avi格式的播放器依赖,我直接下载引入,简单快捷。
- 格式转换:将avi格式转为浏览器支持播放的格式,如mp4格式,相当于把刚开始后端的工作放到前端,这样直接使用video标签即可。
- 源码修改:chrome浏览器底层是用FFmpeg去解码播放的,底层代码配置项不包含avi格式,这也就是不兼容的原因。可以找到源码去添加配置项,重新编译,将编译好的文件替换旧文件,就能播放avi格式了。
上述思路,果断去掉第三种,该方法自己的浏览器可以尝试一下,剩下两种我们一个个去尝试。
plyr播放器
播放器的思路是找到支持播放avi格式的播放器,先去npm用video+avi格式搜索了一下,搜索结果更多是转换格式的库,于是问了一下ai,提到了plry的库,该库是一个简洁功能强大的音频视频播放器,视频支持画中画、字幕等功能。选定好了库,写一个简单的demo看能不能播放avi格式。 新建一个html文档,用cdn方式引入plry,plry的样式也需要引入。
js
<link rel="stylesheet" href="https://cdn.plyr.io/3.7.8/plyr.css" />
<script src="https://cdn.plyr.io/3.7.8/plyr.js"></script>
plry在网页端应用很简单,直接new一个对象去绑定video元素即可。
js
<video id="player" control>
<source src="./123.avi" type="video/avi"></source>
</video>
<script>
const player = new Plyr('#player');
</script>
字幕画中画等功能我们按照官方给出的配置项做对应的配置即可,这里我们就直接绑定不搞花里胡哨,打开页面,不能播放。
该样式要比原生video样式好看,但就是不支持播放avi格式,我就去搜了下issue,作者提到了是浏览器本身不支持,也就是思路三提到的。看来plyr只是对video标签进行了功能添加与样式修改,本质上没有对视频格式进行兼容出来。到此我觉得播放器大多应该是对功能的支持,具体格式还是受限于浏览器自身,只能尝试格式转换。
格式转换
将avi转为mp4肯定能兼容浏览器,问了下后端转换方法是使用FFmpeg相关的库,谷歌也是用的FFmpeg,前端转换看来也是要用FFmpeg,站内搜索了FFmpeg,有好几篇大佬介绍FFmpeg相关的文章,就决定使用ffmpeg.wasm,接下来开整。
格式转换播放思路很明确,借助库将avi格式转为mp4格式,然后用createObjectURL 创建一个url,用video去播放该url。该库不支持cdn方式引入,官网给出了解释: 我当时试的时候还有cdn引入的例子,也用cdn方式写了demo,运行不起来,现在文档直接取消并解释了,很好的团队。
该依赖有两大版本,具体的api跟node版本的支持有所区别,我们可以根据需要安装合适的版本。
- 0.11x版本,该版本创建实例api为createFFmpeg。
- 0.12+版本,创建实例api为 new FFmpeg(),该版本node需要大于等于18.17.0 。
详细对比可以查看官网文档,因为公司项目node版本达不到18以上,就安装了0.11x版本。
js
<template>
<div>
<video ref="videoRef" controls></video>
</div>
</template>
<script setup lang="ts">
import { createFFmpeg, fetchFile } from '@ffmpeg/ffmpeg'
import { ref, onMounted } from 'vue'
const videoRef = ref<HTMLVideoElement | null>(null)
const ffmpeg = createFFmpeg({ log: true })
const load = async () => {
// 加载
await ffmpeg.load()
// 写入文件
ffmpeg.FS(
'writeFile',
'test.avi',
await fetchFile('/video/test.avi'),
)
// 执行 格式转换 命令
await ffmpeg.run('-i', 'test.avi', 'test.mp4')
// 读取mp4文件
const data = ffmpeg.FS('readFile', 'test.mp4')
//
if (videoRef.value) {
videoRef.value.src = URL.createObjectURL(
new Blob([data.buffer], { type: 'video/mp4' }),
)
}
}
onMounted(() => {
load()
})
</script>
ffmpeg加载过程要使用await ,等待所需资源加载完。运行完毕打开页面会有一个报错,SharedArrayBuffer is not defined,Chrome安全策略的变更导致这一错误,这个错误在60-91的版本不会存在。解决的办法也很简单,在vite.config,ts中添加给响应头添加两个属性。
js
headers: {
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp',
},
再次运行等待一会,我们就能看到video加载出来视频了,在console页面也能看到转换过程。
该库实现的转换会比较慢,10M的视频转换了3分钟,这个结果客户肯定不满意。官网也提到了慢的问题,并建议使用mt多线程版本,但是多线程需要兼容SharedArrayBuffer 。除此之外,如果想要上线还要进行一些配置,具体操作可以看大佬的ffmpeg实现web在线转码播放,简单来说,ffmpeg.wasm在加载过程中会加载放到unpkg上的工具包,客户一般往往会有内网需求,所以需要配置。
总结
以上就是探索过程,最终实现效果不太理想,也勉强算是实现了,在这个过程中也学到了一些知识。如果客户不介意转换速度,可以尝试用ffmpeg.wasm库兼容avi视频的播放;如果浏览器版本支持多线程的话,可以用多线程加快转换过程。