@havue/solutions 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -0
- package/bc-connect/README.md +3 -0
- package/bc-connect/dist/bc-connect.mjs +342 -0
- package/bc-connect/dist/bc-connect.umd.js +346 -0
- package/bc-connect/dist/types/src/index.d.ts +1 -0
- package/bc-connect/dist/types/src/manager.d.ts +135 -0
- package/bc-connect/package.json +38 -0
- package/bc-connect/src/index.ts +1 -0
- package/bc-connect/src/manager.ts +388 -0
- package/bc-connect/vite.config.ts +4 -0
- package/dist/solutions.full.js +8504 -0
- package/dist/solutions.full.min.js +18 -0
- package/dist/solutions.full.min.js.map +1 -0
- package/dist/solutions.mjs +2 -0
- package/dist/solutions.umd.js +18 -0
- package/dist/types/bc-connect/src/index.d.ts +1 -0
- package/dist/types/bc-connect/src/manager.d.ts +135 -0
- package/dist/types/src/index.d.ts +2 -0
- package/dist/types/ws-video-manager/src/hooks/useVideoPlay.d.ts +73 -0
- package/dist/types/ws-video-manager/src/index.d.ts +5 -0
- package/dist/types/ws-video-manager/src/loader/index.d.ts +2 -0
- package/dist/types/ws-video-manager/src/loader/websocket-loader.d.ts +62 -0
- package/dist/types/ws-video-manager/src/manager/index.d.ts +144 -0
- package/dist/types/ws-video-manager/src/render/drawer.d.ts +44 -0
- package/dist/types/ws-video-manager/src/render/index.d.ts +105 -0
- package/package.json +44 -0
- package/src/index.ts +2 -0
- package/vite.config.ts +4 -0
- package/ws-video-manager/README.md +3 -0
- package/ws-video-manager/dist/types/src/hooks/useVideoPlay.d.ts +73 -0
- package/ws-video-manager/dist/types/src/index.d.ts +5 -0
- package/ws-video-manager/dist/types/src/loader/index.d.ts +2 -0
- package/ws-video-manager/dist/types/src/loader/websocket-loader.d.ts +62 -0
- package/ws-video-manager/dist/types/src/manager/index.d.ts +144 -0
- package/ws-video-manager/dist/types/src/render/drawer.d.ts +44 -0
- package/ws-video-manager/dist/types/src/render/index.d.ts +105 -0
- package/ws-video-manager/dist/ws-video-manager.mjs +8132 -0
- package/ws-video-manager/dist/ws-video-manager.umd.js +8135 -0
- package/ws-video-manager/node_modules/.bin/tsc +17 -0
- package/ws-video-manager/node_modules/.bin/tsc.CMD +12 -0
- package/ws-video-manager/node_modules/.bin/tsc.ps1 +41 -0
- package/ws-video-manager/node_modules/.bin/tsserver +17 -0
- package/ws-video-manager/node_modules/.bin/tsserver.CMD +12 -0
- package/ws-video-manager/node_modules/.bin/tsserver.ps1 +41 -0
- package/ws-video-manager/package.json +41 -0
- package/ws-video-manager/src/hooks/useVideoPlay.ts +357 -0
- package/ws-video-manager/src/index.ts +6 -0
- package/ws-video-manager/src/loader/index.ts +3 -0
- package/ws-video-manager/src/loader/websocket-loader.ts +278 -0
- package/ws-video-manager/src/manager/index.ts +429 -0
- package/ws-video-manager/src/render/drawer.ts +255 -0
- package/ws-video-manager/src/render/index.ts +475 -0
- package/ws-video-manager/vite.config.ts +4 -0
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
import MP4Box from 'mp4box'
|
|
2
|
+
import { EventBus } from '@havue/shared'
|
|
3
|
+
|
|
4
|
+
// #region typedefine
|
|
5
|
+
export type RenderConstructorOptionType = {
|
|
6
|
+
/** 当前播放currentTime和最新视频时长最多相差 秒数,默认0.3s */
|
|
7
|
+
liveMaxLatency: number
|
|
8
|
+
/** 最多缓存ws传输的未处理的buffer数据大小, 默认200kb */
|
|
9
|
+
maxCacheBufByte: number
|
|
10
|
+
/** 最多存储的时间,用于清除在currentTime之前x秒时间节点前的buffer数据, 默认10s */
|
|
11
|
+
maxCache: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const WS_VIDEO_RENDER_DEFAULT_OPTIONS = Object.freeze({
|
|
15
|
+
liveMaxLatency: 0.3,
|
|
16
|
+
maxCacheBufByte: 200 * 1024,
|
|
17
|
+
maxCache: 10
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
export enum AudioState {
|
|
21
|
+
NOTMUTED = 'notmuted',
|
|
22
|
+
MUTED = 'muted'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export enum VideoState {
|
|
26
|
+
PLAY = 'play',
|
|
27
|
+
PAUSE = 'pause'
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type VideoInfo = {
|
|
31
|
+
width: number
|
|
32
|
+
height: number
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export enum RenderEventsEnum {
|
|
36
|
+
AUDIO_STATE_CHANGE = 'audioStateChange',
|
|
37
|
+
VIDEO_STATE_CHANGE = 'videoStateChange',
|
|
38
|
+
VIDEO_INFO_UPDATE = 'videoInfoUpdate'
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type RenderEvents = {
|
|
42
|
+
[RenderEventsEnum.AUDIO_STATE_CHANGE]: (s: AudioState) => void
|
|
43
|
+
[RenderEventsEnum.VIDEO_STATE_CHANGE]: (s: VideoState) => void
|
|
44
|
+
[RenderEventsEnum.VIDEO_INFO_UPDATE]: (info: VideoInfo) => void
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// #endregion typedefine
|
|
48
|
+
|
|
49
|
+
// 调试代码
|
|
50
|
+
// let id = 0
|
|
51
|
+
// let curPosX = 0
|
|
52
|
+
// let curPosY = 0
|
|
53
|
+
|
|
54
|
+
export class Render extends EventBus<RenderEvents> {
|
|
55
|
+
/** video元素 */
|
|
56
|
+
private _videoEl: HTMLVideoElement | undefined = undefined
|
|
57
|
+
/** pixi.js 实例 */
|
|
58
|
+
// private _pixiApp: Application | null = null
|
|
59
|
+
/** mp4box 实例 */
|
|
60
|
+
private _mp4box: MP4Box = MP4Box.createFile()
|
|
61
|
+
/** 接收到的socket消息 视频数据buffer数组 */
|
|
62
|
+
private _bufsQueue: ArrayBuffer[] = []
|
|
63
|
+
/** MediaSource 实例 */
|
|
64
|
+
private _mediaSource: MediaSource | undefined
|
|
65
|
+
/** SourceBuffer 实例 */
|
|
66
|
+
private _sourceBuffer: SourceBuffer | undefined
|
|
67
|
+
/** 用于MediaSource的mimeType */
|
|
68
|
+
private _mimeType: string = ''
|
|
69
|
+
/** 是否暂停播放 */
|
|
70
|
+
private _paused: boolean = false
|
|
71
|
+
private _options: RenderConstructorOptionType
|
|
72
|
+
|
|
73
|
+
private _cacheAnimationID: number | undefined = undefined
|
|
74
|
+
|
|
75
|
+
// 调试代码
|
|
76
|
+
// private divID = ''
|
|
77
|
+
|
|
78
|
+
constructor(options: Partial<RenderConstructorOptionType> = {}) {
|
|
79
|
+
super()
|
|
80
|
+
this._options = options
|
|
81
|
+
? Object.assign({}, WS_VIDEO_RENDER_DEFAULT_OPTIONS, options)
|
|
82
|
+
: WS_VIDEO_RENDER_DEFAULT_OPTIONS
|
|
83
|
+
this._mp4box.onReady = this._onMp4boxReady.bind(this)
|
|
84
|
+
this._setupVideo()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
get muted(): boolean {
|
|
88
|
+
return this._videoEl?.muted || false
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
set muted(val: boolean) {
|
|
92
|
+
if (this._videoEl) {
|
|
93
|
+
this._videoEl.muted = val
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
set paused(paused: boolean) {
|
|
98
|
+
this._paused = paused
|
|
99
|
+
if (paused) {
|
|
100
|
+
this._videoEl?.pause()
|
|
101
|
+
} else {
|
|
102
|
+
this._videoEl?.play()
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
get paused(): boolean {
|
|
107
|
+
return this._paused
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
get videoEl(): HTMLVideoElement | undefined {
|
|
111
|
+
return this._videoEl
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** 更新实例配置 */
|
|
115
|
+
public updateOptions(option: Partial<RenderConstructorOptionType> = {}) {
|
|
116
|
+
Object.assign(this._options, {
|
|
117
|
+
...option
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 添加视频流buffer数据
|
|
123
|
+
* @param buf
|
|
124
|
+
*/
|
|
125
|
+
public appendMediaBuffer(bufs: Array<ArrayBuffer & { fileStart: number }>) {
|
|
126
|
+
if (this._paused) {
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
if (!this._sourceBuffer) {
|
|
130
|
+
const buf = bufs[0]
|
|
131
|
+
buf.fileStart = 0
|
|
132
|
+
this._mp4box.appendBuffer(buf)
|
|
133
|
+
}
|
|
134
|
+
this._bufsQueue.push(...bufs)
|
|
135
|
+
|
|
136
|
+
if (this._sourceBuffer && !this._videoEl?.paused && this._bufsQueue.length > 2) {
|
|
137
|
+
const len = this._bufsQueue.length
|
|
138
|
+
const maxTotal = this._options.maxCacheBufByte
|
|
139
|
+
let lastIndex = len - 1
|
|
140
|
+
let total = 0
|
|
141
|
+
for (let i = len - 1; i > 0; i--) {
|
|
142
|
+
total += this._bufsQueue[i].byteLength
|
|
143
|
+
lastIndex = i
|
|
144
|
+
if (total >= maxTotal) {
|
|
145
|
+
this._bufsQueue = this._bufsQueue.slice(lastIndex)
|
|
146
|
+
break
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
this._cacheAnimationID && cancelAnimationFrame(this._cacheAnimationID)
|
|
151
|
+
this._cacheAnimationID = undefined
|
|
152
|
+
this._cache()
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* mp4box解析完成
|
|
157
|
+
* @param info mp4box解析信息
|
|
158
|
+
*/
|
|
159
|
+
private _onMp4boxReady(info: any) {
|
|
160
|
+
console.log('onMp4boxReady', info)
|
|
161
|
+
this._mp4box.flush()
|
|
162
|
+
if (!info.isFragmented) {
|
|
163
|
+
console.error('not fragmented mp4')
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
this._mimeType = info.mime
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const { width, height } = info.videoTracks[0].video
|
|
170
|
+
this.emit(RenderEventsEnum.VIDEO_INFO_UPDATE, {
|
|
171
|
+
width,
|
|
172
|
+
height
|
|
173
|
+
})
|
|
174
|
+
} catch (error) {
|
|
175
|
+
console.error(error)
|
|
176
|
+
}
|
|
177
|
+
// this._setupVideo()
|
|
178
|
+
// this.setupPixi()
|
|
179
|
+
this._setupMSE()
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* 初始化视频元素
|
|
184
|
+
*/
|
|
185
|
+
private _setupVideo() {
|
|
186
|
+
this._videoEl = document.createElement('video')
|
|
187
|
+
this._videoEl.preload = 'auto'
|
|
188
|
+
this._videoEl.controls = false
|
|
189
|
+
this._videoEl.muted = true
|
|
190
|
+
this._videoEl.autoplay = true
|
|
191
|
+
this._videoEl.loop = false
|
|
192
|
+
this._videoEl.crossOrigin = 'anonymous'
|
|
193
|
+
this._videoEl.playsInline = true
|
|
194
|
+
this._videoEl['webkit-playsinline'] = true
|
|
195
|
+
|
|
196
|
+
this._videoEl.addEventListener('canplay', () => {
|
|
197
|
+
if (!this._paused) {
|
|
198
|
+
this._videoEl?.play()
|
|
199
|
+
}
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
this._videoEl.addEventListener('play', () => {
|
|
203
|
+
this.emit(RenderEventsEnum.VIDEO_STATE_CHANGE, VideoState.PLAY)
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
this._videoEl.addEventListener('pause', () => {
|
|
207
|
+
this.emit(RenderEventsEnum.VIDEO_STATE_CHANGE, VideoState.PAUSE)
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
this._videoEl.addEventListener('volumechange', () => {
|
|
211
|
+
this.emit(RenderEventsEnum.AUDIO_STATE_CHANGE, this._videoEl?.muted ? AudioState.MUTED : AudioState.NOTMUTED)
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
this._videoEl.addEventListener('error', (error) => {
|
|
215
|
+
console.error('video error', error)
|
|
216
|
+
// setTimeout(() => {
|
|
217
|
+
// this._mediaSource && this._setupMSE()
|
|
218
|
+
// }, 500)
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* bug: 因预监画面播放一段时间后,
|
|
223
|
+
* 虽然视频时间在走,但video的画面会暂停,
|
|
224
|
+
* 发现将视频元素添加到视口中,画面就不会暂停,
|
|
225
|
+
* 可能与浏览器的资源优化策略有关,先将video标签添加到视口中,
|
|
226
|
+
* 后面有时间寻找一下是否有其他解决方案
|
|
227
|
+
*/
|
|
228
|
+
// this._videoEl.controls = true
|
|
229
|
+
document.body.appendChild(this._videoEl)
|
|
230
|
+
this._videoEl.style.position = 'fixed'
|
|
231
|
+
this._videoEl.style.left = `0px`
|
|
232
|
+
this._videoEl.style.top = `0px`
|
|
233
|
+
this._videoEl.style.zIndex = '-10000'
|
|
234
|
+
this._videoEl.style.width = '1px'
|
|
235
|
+
this._videoEl.style.height = '1px'
|
|
236
|
+
this._videoEl.style.opacity = '0.01'
|
|
237
|
+
this._videoEl.style.pointerEvents = 'none'
|
|
238
|
+
this._videoEl.style.touchAction = 'none'
|
|
239
|
+
|
|
240
|
+
// 调试代码
|
|
241
|
+
// this._videoEl.style.left = `${curPosX}px`
|
|
242
|
+
// this._videoEl.style.top = `${curPosY}px`
|
|
243
|
+
// this._videoEl.style.zIndex = '99999'
|
|
244
|
+
// this._videoEl.style.width = '200px'
|
|
245
|
+
// this._videoEl.style.height = '100px'
|
|
246
|
+
// this._videoEl.controls = true
|
|
247
|
+
// this._videoEl.style.opacity = '1'
|
|
248
|
+
// const div = document.createElement('div')
|
|
249
|
+
// div.id = `divID${id}`
|
|
250
|
+
// this.divID = `divID${id}`
|
|
251
|
+
// id = id + 1
|
|
252
|
+
|
|
253
|
+
// div.style.position = 'fixed'
|
|
254
|
+
// div.style.left = `${curPosX}px`
|
|
255
|
+
// div.style.top = `${curPosY}px`
|
|
256
|
+
// curPosY += 100
|
|
257
|
+
// if (curPosY >= 400) {
|
|
258
|
+
// curPosY = 0
|
|
259
|
+
// curPosX += 200
|
|
260
|
+
// }
|
|
261
|
+
// div.style.zIndex = '99999'
|
|
262
|
+
// document.body.appendChild(div)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* 是否支持Media Source Extention
|
|
267
|
+
* @returns boolean
|
|
268
|
+
*/
|
|
269
|
+
public isSupportMSE() {
|
|
270
|
+
return 'MediaSource' in window
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* 初始化MSE
|
|
275
|
+
* @returns
|
|
276
|
+
*/
|
|
277
|
+
private _setupMSE(): void {
|
|
278
|
+
if (!this.isSupportMSE()) {
|
|
279
|
+
console.error('your borwser do not support MediaSource')
|
|
280
|
+
return
|
|
281
|
+
}
|
|
282
|
+
if (!MediaSource.isTypeSupported(this._mimeType)) {
|
|
283
|
+
console.error('Unsupported MIME type or codec: ', this._mimeType)
|
|
284
|
+
return
|
|
285
|
+
}
|
|
286
|
+
if (!this._videoEl) {
|
|
287
|
+
return
|
|
288
|
+
}
|
|
289
|
+
this.destroyMediaSource()
|
|
290
|
+
this._mediaSource = new MediaSource()
|
|
291
|
+
URL.revokeObjectURL(this._videoEl.src)
|
|
292
|
+
this._videoEl.src = URL.createObjectURL(this._mediaSource)
|
|
293
|
+
|
|
294
|
+
this._mediaSource.addEventListener('sourceopen', () => {
|
|
295
|
+
const sourceBuffer = (this._sourceBuffer = this._mediaSource!.addSourceBuffer(this._mimeType))
|
|
296
|
+
sourceBuffer.mode = 'sequence'
|
|
297
|
+
sourceBuffer.onupdateend = () => {
|
|
298
|
+
if (
|
|
299
|
+
!this._videoEl ||
|
|
300
|
+
!sourceBuffer ||
|
|
301
|
+
this._mediaSource?.readyState !== 'open' ||
|
|
302
|
+
![...this._mediaSource.sourceBuffers].includes(sourceBuffer)
|
|
303
|
+
) {
|
|
304
|
+
return
|
|
305
|
+
}
|
|
306
|
+
const currentTime = this._videoEl.currentTime
|
|
307
|
+
// 调试代码
|
|
308
|
+
// const div = document.getElementById(this.divID)
|
|
309
|
+
// let innerHTML = `len:${sourceBuffer.buffered.length}`
|
|
310
|
+
|
|
311
|
+
if (sourceBuffer.buffered.length > 0) {
|
|
312
|
+
let bufferedLen = sourceBuffer.buffered.length
|
|
313
|
+
/** 是否需要删除sourceBuffer中的buffer段 */
|
|
314
|
+
const needDelBuf = bufferedLen > 1
|
|
315
|
+
/**
|
|
316
|
+
* sourceBuffer中有多个buffered,时间不连续
|
|
317
|
+
* 导致视频播放到其中一个buffer最后就暂停了
|
|
318
|
+
* 如果出现多个buffered,删除之前有的buffer
|
|
319
|
+
* 使用最新的视频buffer进行播放
|
|
320
|
+
*/
|
|
321
|
+
if (needDelBuf && currentTime) {
|
|
322
|
+
const lastIndex = bufferedLen - 1
|
|
323
|
+
if (currentTime < sourceBuffer.buffered.start(lastIndex)) {
|
|
324
|
+
this._videoEl.currentTime = sourceBuffer.buffered.start(lastIndex)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const delBufEnd = sourceBuffer.buffered.end(lastIndex - 1)
|
|
328
|
+
if (!this._sourceBuffer!.updating && currentTime > delBufEnd) {
|
|
329
|
+
this._sourceBuffer?.remove(0, delBufEnd)
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// 调试代码
|
|
334
|
+
// innerHTML += ` - start:${sourceBuffer.buffered.start(
|
|
335
|
+
// sourceBuffer.buffered.length - 1
|
|
336
|
+
// )} - end:${sourceBuffer.buffered.end(sourceBuffer.buffered.length - 1)} <br/>`
|
|
337
|
+
// innerHTML += ` - currentTime: ${currentTime}`
|
|
338
|
+
// div && (div.innerHTML = innerHTML)
|
|
339
|
+
|
|
340
|
+
bufferedLen = sourceBuffer.buffered.length
|
|
341
|
+
const start = sourceBuffer.buffered.start(bufferedLen - 1)
|
|
342
|
+
const end = sourceBuffer.buffered.end(bufferedLen - 1)
|
|
343
|
+
|
|
344
|
+
// 设置开始时间
|
|
345
|
+
if (!currentTime && start) {
|
|
346
|
+
this._videoEl.currentTime = start
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// 限制最低延迟时间
|
|
350
|
+
if (this._options.liveMaxLatency) {
|
|
351
|
+
const offsetMaxLatency = this._options.liveMaxLatency
|
|
352
|
+
|
|
353
|
+
if (end - currentTime > offsetMaxLatency) {
|
|
354
|
+
this._videoEl.currentTime = end - 0.1
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
// 移除当前时间之前的buffer
|
|
358
|
+
if (!this._sourceBuffer!.updating && currentTime - start > this._options.maxCache) {
|
|
359
|
+
this._sourceBuffer?.remove(0, currentTime - this._options.maxCache / 2)
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
})
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* 将_bufsQueue中的数据添加到SourceBuffer中
|
|
368
|
+
* @returns
|
|
369
|
+
*/
|
|
370
|
+
private _cache() {
|
|
371
|
+
if (!this._videoEl) {
|
|
372
|
+
return
|
|
373
|
+
}
|
|
374
|
+
if (
|
|
375
|
+
!this._mediaSource ||
|
|
376
|
+
!this._sourceBuffer ||
|
|
377
|
+
this._sourceBuffer.updating ||
|
|
378
|
+
!this._bufsQueue.length ||
|
|
379
|
+
this._mediaSource.readyState !== 'open'
|
|
380
|
+
) {
|
|
381
|
+
this._cacheAnimationID === undefined && (this._cacheAnimationID = requestAnimationFrame(() => this._cache()))
|
|
382
|
+
return
|
|
383
|
+
}
|
|
384
|
+
if (this._videoEl.error) {
|
|
385
|
+
this._setupMSE()
|
|
386
|
+
return (
|
|
387
|
+
this._cacheAnimationID === undefined && (this._cacheAnimationID = requestAnimationFrame(() => this._cache()))
|
|
388
|
+
)
|
|
389
|
+
}
|
|
390
|
+
this._cacheAnimationID = undefined
|
|
391
|
+
let frame: Uint8Array
|
|
392
|
+
if (this._bufsQueue.length > 1) {
|
|
393
|
+
const freeBuffer = this._bufsQueue.splice(0, this._bufsQueue.length)
|
|
394
|
+
const length = freeBuffer.map((e) => e.byteLength).reduce((a, b) => a + b, 0)
|
|
395
|
+
const buffer = new Uint8Array(length)
|
|
396
|
+
let offset = 0
|
|
397
|
+
for (const data of freeBuffer) {
|
|
398
|
+
const frame = new Uint8Array(data)
|
|
399
|
+
buffer.set(frame, offset)
|
|
400
|
+
offset += data.byteLength
|
|
401
|
+
}
|
|
402
|
+
frame = buffer
|
|
403
|
+
} else {
|
|
404
|
+
frame = new Uint8Array(this._bufsQueue.shift() || [])
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (frame) {
|
|
408
|
+
this._sourceBuffer.appendBuffer(frame)
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* 刷新播放时间为最新
|
|
414
|
+
*/
|
|
415
|
+
public refresh() {
|
|
416
|
+
if (this._videoEl && this._videoEl.buffered.length) {
|
|
417
|
+
const end = this._videoEl.buffered.end(this._videoEl.buffered.length - 1)
|
|
418
|
+
this._videoEl.currentTime = end
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/** 重置解析的视频mime type */
|
|
423
|
+
public resetMimeType() {
|
|
424
|
+
this.destroyMediaSource()
|
|
425
|
+
if (this._videoEl) {
|
|
426
|
+
this._videoEl.src = ''
|
|
427
|
+
}
|
|
428
|
+
this._mp4box = MP4Box.createFile()
|
|
429
|
+
this._mp4box.onReady = this._onMp4boxReady.bind(this)
|
|
430
|
+
this._bufsQueue.length = 0
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
private destroyMediaSource() {
|
|
434
|
+
if (this._mediaSource) {
|
|
435
|
+
if (this._videoEl) {
|
|
436
|
+
URL.revokeObjectURL(this._videoEl?.src || '')
|
|
437
|
+
this._videoEl.src = ''
|
|
438
|
+
}
|
|
439
|
+
if (this._mediaSource.readyState === 'open') {
|
|
440
|
+
if (this._sourceBuffer) {
|
|
441
|
+
this._sourceBuffer.abort()
|
|
442
|
+
this._mediaSource.removeSourceBuffer(this._sourceBuffer)
|
|
443
|
+
}
|
|
444
|
+
this._mediaSource.endOfStream()
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
this._mediaSource = undefined
|
|
448
|
+
this._sourceBuffer = undefined
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* 销毁
|
|
454
|
+
*/
|
|
455
|
+
public destroy() {
|
|
456
|
+
this._bufsQueue = []
|
|
457
|
+
if (this._videoEl) {
|
|
458
|
+
this._videoEl.pause()
|
|
459
|
+
this._videoEl.currentTime = 0
|
|
460
|
+
if (this._videoEl.parentElement === document.body) {
|
|
461
|
+
document.body.removeChild(this._videoEl)
|
|
462
|
+
}
|
|
463
|
+
URL.revokeObjectURL(this._videoEl.src)
|
|
464
|
+
this._videoEl.src = ''
|
|
465
|
+
this._videoEl = undefined
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
this._mimeType = ''
|
|
469
|
+
|
|
470
|
+
this._cacheAnimationID && cancelAnimationFrame(this._cacheAnimationID)
|
|
471
|
+
this._cacheAnimationID = undefined
|
|
472
|
+
|
|
473
|
+
this.destroyMediaSource()
|
|
474
|
+
}
|
|
475
|
+
}
|