由来
前几天在逛Github的时候,发现了一个项目叫做libmedia
,号称是"一个 TypeScript 实现的高性能媒体库"
在我眼中,lib
开头的库大多是优秀到足以给大家使用的,如libxml
zlib
libopus
之类的,于是我点击去了解了一下,好家伙连文档都没有,只有TypeDOC给的少量注释
但是这个难不倒我,于是我去扒源码,然后给了我极大的震撼
作者将ffmpeg的一些库使用TypeScript重写了一遍,
然后为了性能,使用了cheap库
cheap底层原理,readme也基本上说清楚了
cheap 将 struct 的概念引入 typescript,struct 和 C 中的 struct 概念一致,表示一段内存中的数据结构布局,如此可以在 wasm 和 js 中各自根据布局操作内存,而双方之间传递数据时传递内存的开始位置,也就是 pointer(指针),从而避免了数据的序列化和反序列化的开销。
有了 struct 了我们不仅可以在 wasm 和 js 之间高效的传递数据,还想到目前 js 中多线程编程也面临着无法进行数据共享的问题,如果我们的 struct 是在 SharedArrayBuffer 中,让所有 worker 使用同一个 SharedArrayBuffer,这样所有 worker 都实现了数据共享,不同 worker 之间通过传递指针来高效的进行数据传递。
震惊了我,cheap使TypeScript都有指针了,连int32
float64
之类的类型全部定义了一遍,而且聪明的编译器居然学习了VUE将指针自动解包。
于是我得出了一个结论,cheap
将成为WASM的未来方向,而libmedia
将成为Web多媒体的黑马。她不火谁火?
一些小问题和原因
于是立刻马上,我开始尝试将libmedia
打包入我的项目vlist5
,结果,哈哈哈(自嘲)报错了。详细的讨论见 https://github.com/zhaohappy/libmedia/issues/5
WebPack这个旧时代的骄傲已经被Vite抢走了皇冠,但是Cheap居然不支持Vite编译
气呼呼的,我翻了半天WebPack文档,绝望地发现,WebPack压根不支持打包成ES模块
那么只能从UMD模块中找到未来了,为此我还特意研究了一下UMD,想到了3种对策
尝试:require()
CommonJS是NodeJS的模块管理器,Vite
支持对应的Loader,可以打包CJS模块
安装:
npm i vite-plugin-require-transform --save-dev
配置:
import { defineConfig } from 'vite'
import requireTransform from 'vite-plugin-require-transform';
export default defineConfig({
plugins: [
requireTransform({
fileRegex: /.js$/
}),
],
);
导入:
const a = require('libmedia/dist/avplayer/avplayer.js');
然后
我为此特意扒了一下源码,发现因为document.currentScript
是null
造成的
使用eval
可惜,document.currentScript
是只读的属性,无法修改骗过UMD加载器
于是我想到了隔离法,将globalThis
和window
变量遮蔽,暴露一些用到的库
的确可以,实测能用,但是太不雅观了
import AVPLAYER_SRC from 'libmedia/dist/avplayer/avplayer?url';
const Func = await (await fetch(FILE_PATH)).text(),
_G = globalThis;
(function(){
const globalThis = {
document: {
currentScript: {
src: AVPLAYER_SRC
}
},
Math: _G.Math,
URL: _G.URL,
parseFloat: _G.parseFloat,
parseInt: _G.parseInt,
navigator: _G.navigator
},
window = globalThis;
eval(Func);
})();
要是未来添加了几个依赖,那工程量就很大了。放弃
使用<script>
标签
最后我还是向 恶UMD 势力低头了,古老的加载方式,我想想...这还是在vList3那会儿使用过的<script>
大法
const script = document.createElement('script');
script.src = AVPLAYER_SRC;
document.body.append(script);
await new Promise(rs => script.onload = rs);
的确可以,就是需要加一个全局定义的TypeScript Declare文件(.d.ts
)
懒得写,想躺平,于是偷懒使用JavaScript + .d.ts
中转,将API二次包装成reactive
export interface Export {
url: string,
playBackRate: number,
loop: boolean,
volume: 1,
time: {
total: number,
current: number
},
tracks: {
audio: Array<Stream>,
audioTrack: number,
video: Array<Stream>,
videoTrack: number,
subtitle: Array<Stream>,
subTrack: number
},
ended: boolean,
play: boolean,
stop: boolean,
destroy: boolean,
status: Stat,
display: {
fill: boolean,
rotate: 0,
flip: {
vertical: boolean,
horizontal: boolean
}
},
func: {
snapshot: boolean,
seek: number,
resize: [number, number]
}
}
这样TypeScript也有了,Vite打包也有了放屁,这些需要动态加载的文件怎么办
我苦苦思索,最后决定手动拷贝,在package.json
上动手脚
"build": "run-p type-check \"build-only {@}\" -- && cp node_modules/libmedia/dist/avplayer/*.avplayer.js dist/assets/ || copy node_modules\\libmedia\\dist\\avplayer\\*.avplayer.js dist/assets/",
这串超长JSON实现了跨平台(Windows/Unix)编译并拷贝动态文件到dist/
,至此我们的打包完成了!
初始化
官方只有一个 网站用作示例,不服输的我直接扒源代码找到了初始化方法(大哥,写点文档不难吧)
player = new AVPlayer({
container: document.querySelector(id),
isLive: isLiveComponent.isLive,
getWasm: (type, codecId) => {
switch (type) {
case 'decoder': {
if (codecId >= 65536 && codecId <= 65572) {
return `../dist/decode/pcm${(enableSimdComponent.enableSimd) ? '-simd' : (supportAtomic ? '-atomic' : '')}.wasm`
}
switch (codecId) {
// H264
case 27:
return `../dist/decode/h264${(enableSimdComponent.enableSimd) ? '-simd' : (supportAtomic ? '-atomic' : '')}.wasm`
// AAC
case 86018:
return `../dist/decode/aac${(enableSimdComponent.enableSimd) ? '-simd' : (supportAtomic ? '-atomic' : '')}.wasm`
// MP3
case 86017:
return `../dist/decode/mp3${(enableSimdComponent.enableSimd) ? '-simd' : (supportAtomic ? '-atomic' : '')}.wasm`
// HEVC
case 173:
return `../dist/decode/hevc${(enableSimdComponent.enableSimd) ? '-simd' : (supportAtomic ? '-atomic' : '')}.wasm`
// VVC
case 196:
return `../dist/decode/vvc${(enableSimdComponent.enableSimd) ? '-simd' : (supportAtomic ? '-atomic' : '')}.wasm`
// Mpeg4
case 12:
return `../dist/decode/mpeg4${(enableSimdComponent.enableSimd) ? '-simd' : (supportAtomic ? '-atomic' : '')}.wasm`
// AV1
case 225:
return `../dist/decode/av1${(enableSimdComponent.enableSimd) ? '-simd' : (supportAtomic ? '-atomic' : '')}.wasm`
// Speex
case 86051:
return `../dist/decode/speex${(enableSimdComponent.enableSimd) ? '-simd' : (supportAtomic ? '-atomic' : '')}.wasm`
// Opus
case 86076:
return `../dist/decode/opus${(enableSimdComponent.enableSimd) ? '-simd' : (supportAtomic ? '-atomic' : '')}.wasm`
// flac
case 86028:
return `../dist/decode/flac${(enableSimdComponent.enableSimd) ? '-simd' : (supportAtomic ? '-atomic' : '')}.wasm`
// vorbis
case 86021:
return `../dist/decode/vorbis${(enableSimdComponent.enableSimd) ? '-simd' : (supportAtomic ? '-atomic' : '')}.wasm`
// vp8
case 139:
return `../dist/decode/vp8${(enableSimdComponent.enableSimd) ? '-simd' : (supportAtomic ? '-atomic' : '')}.wasm`
// vp9
case 167:
return `../dist/decode/vp9${(enableSimdComponent.enableSimd) ? '-simd' : (supportAtomic ? '-atomic' : '')}.wasm`
default:
return null
}
break
}
case 'resampler':
return `../dist/resample/resample${(enableSimdComponent.enableSimd) ? '-simd' : (supportAtomic ? '-atomic' : '')}.wasm`
case 'stretchpitcher':
return `../dist/stretchpitch/stretchpitch${(enableSimdComponent.enableSimd) ? '-simd' : (supportAtomic ? '-atomic' : '')}.wasm`
}
},
checkUseMES: (streams) => {
return useMseComponent.useMse
},
enableHardware: enableWebcodecComponent.enableHardwareAcceleration,
enableWebGPU: enableWebGPUComponent.enableWebGPU,
loop: loopComponent.loop,
jitterBufferMax: 4,
jitterBufferMin: 1,
lowLatency: true
})
这么长?什么情况?
别慌,这么长大部分都是 getWasm
函数,还有simd
atomic
加速
可以看到simd兼容性不错,我们就不判断了,直接使用simd
加速
import PCM_WASM from 'libmedia/dist/decode/pcm-simd.wasm?url';
import H264_WASM from 'libmedia/dist/decode/h264-simd.wasm?url';
import AAC_WASM from 'libmedia/dist/decode/aac-simd.wasm?url';
import MP3_WASM from 'libmedia/dist/decode/mp3-simd.wasm?url';
import HEVC_WASM from 'libmedia/dist/decode/hevc-simd.wasm?url';
import VVC_WASM from 'libmedia/dist/decode/vvc-simd.wasm?url';
import MP4_WASM from 'libmedia/dist/decode/mpeg4-simd.wasm?url';
import AV1_WASM from 'libmedia/dist/decode/av1-simd.wasm?url';
import OPUS_WASM from 'libmedia/dist/decode/opus-simd.wasm?url';
import FLAC_WASM from 'libmedia/dist/decode/flac-simd.wasm?url';
import OGG_WASM from 'libmedia/dist/decode/vorbis-simd.wasm?url';
import VP9_WASM from 'libmedia/dist/decode/vp9-simd.wasm?url';
import RSP_WASM from 'libmedia/dist/resample/resample-simd.wasm?url';
import SP_WASM from 'libmedia/dist/stretchpitch/stretchpitch-simd.wasm?url';
const CODEC_MAP = {
12: MP4_WASM,
27: H264_WASM,
167: VP9_WASM,
173: HEVC_WASM,
196: VVC_WASM,
225: AV1_WASM,
86018: AAC_WASM,
86017: MP3_WASM,
86021: OGG_WASM,
86028: FLAC_WASM,
86076: OPUS_WASM,
};
把你想要用的wasm一股脑全部import
一遍,别忘了?url
结尾
然后体验一下超短初始化命令
const player = new globalThis.AVPlayer({
"container": el,
"enableHardware": true,
"getWasm": (type, codecId) => {
switch (type) {
case 'decoder':
if (codecId >= 65536 && codecId <= 65572) return PCM_WASM;
else return CODEC_MAP[codecId] || null;
case 'resampler':
return RSP_WASM;
case 'stretchpitcher':
return SP_WASM;
}
},
"enableWebGPU": false,
"simd": true
});
怎么样,这下不可怕了吧
使用
摩拳擦掌,我们终于开始battle浏览器了,首先是初始化视频
player.load('/a.mkv');
当然,这个.load()
是异步函数,想要体验video.onload
欢迎使用这个Promise
注意返回值是ms
const duration = ref(0);
player.load('/a.mkv').then(() => duration.value = Number(player.getDuration()));
不要忘记使用
Number()
处理返回值 暂停
player.pause()
播放
player.play()
拖拽进度条然后seek
注意返回值是ms
player.seek(102400)
于是这个播放器就可以使用啦,恭喜
本文由 zlh 创作,采用 知识共享署名4.0 国际许可协议进行许可。
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。