/
mux-mp4-worker.ts
126 lines (108 loc) · 2.76 KB
/
mux-mp4-worker.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import mp4box from 'mp4box'
import { autoReadStream, file2stream, recodemux } from '@webav/av-cliper'
import { TClearFn, EWorkerMsg, IWorkerOpts } from './types'
if (import.meta.env.DEV) {
mp4box.Log.setLogLevel(mp4box.Log.debug)
}
enum State {
Preparing = 'preparing',
Running = 'running',
Stopped = 'stopped'
}
let STATE = State.Preparing
let clear: TClearFn | null = null
self.onmessage = async (evt: MessageEvent) => {
const { type, data } = evt.data
switch (type) {
case EWorkerMsg.Start:
if (STATE === State.Preparing) {
STATE = State.Running
clear = init(data, () => {
STATE = State.Stopped
})
}
break
case EWorkerMsg.Stop:
STATE = State.Stopped
await clear?.()
self.postMessage({ type: EWorkerMsg.SafeExit })
break
}
}
function init (opts: IWorkerOpts, onEnded: TClearFn): TClearFn {
let stopEncodeVideo: TClearFn | null = null
let stopEncodeAudio: TClearFn | null = null
const recoder = recodemux({
video: opts.video,
audio: opts.audio,
bitrate: opts.bitrate ?? 3_000_000
})
let stoped = false
if (opts.streams.video != null) {
const encode = encodeVideoFrame(opts.video.expectFPS, recoder.encodeVideo)
stopEncodeVideo = autoReadStream(opts.streams.video, {
onChunk: async vf => {
if (stoped) return
encode(vf)
},
onDone: () => {}
})
}
if (opts.audio != null && opts.streams.audio != null) {
stopEncodeAudio = autoReadStream(opts.streams.audio, {
onChunk: async ad => {
if (stoped) return
recoder.encodeAudio(ad)
},
onDone: () => {}
})
}
const { stream, stop: stopStream } = file2stream(
recoder.mp4file,
opts.timeSlice,
() => {
exit()
onEnded()
}
)
self.postMessage(
{
type: EWorkerMsg.OutputStream,
data: stream
},
// @ts-expect-error
[stream]
)
function exit () {
stoped = true
stopEncodeVideo?.()
stopEncodeAudio?.()
recoder.close()
stopStream()
}
return exit
}
function encodeVideoFrame (expectFPS: number, encode: VideoEncoder['encode']) {
let frameCount = 0
const startTime = performance.now()
let lastTime = startTime
const maxFPS = expectFPS * 1.1
let frameCnt = 0
return (frame: VideoFrame) => {
const now = performance.now()
const offsetTime = now - startTime
// 避免帧率超出期望太高
if ((frameCnt / offsetTime) * 1000 > maxFPS) return
const vf = new VideoFrame(frame, {
// timestamp 单位 微妙
timestamp: offsetTime * 1000,
duration: (now - lastTime) * 1000
})
lastTime = now
encode(vf, { keyFrame: frameCount % 150 === 0 })
frameCnt += 1
vf.close()
frame.close()
frameCount += 1
}
}