@havue/solutions 1.0.0 → 1.1.1

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.
@@ -1,4 +1,4 @@
1
- import MP4Box from 'mp4box'
1
+ import MP4Box from './mp4box'
2
2
  import { EventBus } from '@havue/shared'
3
3
 
4
4
  // #region typedefine
@@ -54,24 +54,34 @@ export type RenderEvents = {
54
54
  export class Render extends EventBus<RenderEvents> {
55
55
  /** video元素 */
56
56
  private _videoEl: HTMLVideoElement | undefined = undefined
57
- /** pixi.js 实例 */
58
- // private _pixiApp: Application | null = null
59
57
  /** mp4box 实例 */
60
58
  private _mp4box: MP4Box = MP4Box.createFile()
61
- /** 接收到的socket消息 视频数据buffer数组 */
62
- private _bufsQueue: ArrayBuffer[] = []
59
+ /** mp4box onFragment获取的视频数据buffer数组 */
60
+ private _audioBufsQueue: ArrayBuffer[] = []
61
+ private _videoBufsQueue: ArrayBuffer[] = []
63
62
  /** MediaSource 实例 */
64
63
  private _mediaSource: MediaSource | undefined
65
64
  /** SourceBuffer 实例 */
66
- private _sourceBuffer: SourceBuffer | undefined
65
+ private _audioSourceBuffer: SourceBuffer | undefined
66
+ private _videoSourceBuffer: SourceBuffer | undefined
67
+
68
+ private _audioTrackId: number | undefined
69
+ private _videoTrackId: number | undefined
67
70
  /** 用于MediaSource的mimeType */
68
71
  private _mimeType: string = ''
72
+ private _audioMimeType: string = ''
73
+ private _videoMimeType: string = ''
69
74
  /** 是否暂停播放 */
70
75
  private _paused: boolean = false
71
76
  private _options: RenderConstructorOptionType
72
77
 
73
78
  private _cacheAnimationID: number | undefined = undefined
74
79
 
80
+ /** fmp4初始化片段是否已经添加 */
81
+ private _isAudioInitSegmentAdded: boolean = false
82
+ private _isVideoInitSegmentAdded: boolean = false
83
+ private _offset: number = 0
84
+
75
85
  // 调试代码
76
86
  // private divID = ''
77
87
 
@@ -81,6 +91,7 @@ export class Render extends EventBus<RenderEvents> {
81
91
  ? Object.assign({}, WS_VIDEO_RENDER_DEFAULT_OPTIONS, options)
82
92
  : WS_VIDEO_RENDER_DEFAULT_OPTIONS
83
93
  this._mp4box.onReady = this._onMp4boxReady.bind(this)
94
+ this._mp4box.onSegment = this._onSegment.bind(this)
84
95
  this._setupVideo()
85
96
  }
86
97
 
@@ -126,30 +137,12 @@ export class Render extends EventBus<RenderEvents> {
126
137
  if (this._paused) {
127
138
  return
128
139
  }
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()
140
+ bufs.forEach((b) => {
141
+ b.fileStart = this._offset
142
+ this._offset += b.byteLength
143
+ this._mp4box.appendBuffer(b)
144
+ })
145
+ return
153
146
  }
154
147
 
155
148
  /**
@@ -158,7 +151,6 @@ export class Render extends EventBus<RenderEvents> {
158
151
  */
159
152
  private _onMp4boxReady(info: any) {
160
153
  console.log('onMp4boxReady', info)
161
- this._mp4box.flush()
162
154
  if (!info.isFragmented) {
163
155
  console.error('not fragmented mp4')
164
156
  return
@@ -171,12 +163,70 @@ export class Render extends EventBus<RenderEvents> {
171
163
  width,
172
164
  height
173
165
  })
166
+ const audioTrack = info.audioTracks[0]
167
+ const videoTrack = info.videoTracks[0]
168
+
169
+ if (audioTrack) {
170
+ this._audioMimeType = `audio/mp4; codecs="${audioTrack.codec}"`
171
+ this._audioTrackId = audioTrack.id
172
+ this._mp4box.setSegmentOptions(audioTrack.id, undefined, {
173
+ nbSamples: 100
174
+ })
175
+ }
176
+
177
+ if (videoTrack) {
178
+ this._videoMimeType = `video/mp4; codecs="${videoTrack.codec}"`
179
+ this._videoTrackId = videoTrack.id
180
+ this._mp4box.setSegmentOptions(videoTrack.id, undefined, {
181
+ nbSamples: 100
182
+ })
183
+ }
184
+
185
+ const initSegments = this._mp4box.initializeSegmentation()
186
+ for (const seg of initSegments) {
187
+ if (audioTrack && seg.id === audioTrack.id) {
188
+ this._audioBufsQueue.push(seg.buffer)
189
+ } else if (videoTrack && seg.id === videoTrack.id) {
190
+ this._videoBufsQueue.push(seg.buffer)
191
+ }
192
+ }
174
193
  } catch (error) {
175
194
  console.error(error)
176
195
  }
177
- // this._setupVideo()
178
- // this.setupPixi()
179
196
  this._setupMSE()
197
+ this._mp4box.start()
198
+ }
199
+
200
+ private _onSegment(id: number, __: any, buffer: ArrayBuffer, sampleNumber: number) {
201
+ const isAudio = id === this._audioTrackId
202
+ const isVideo = id === this._videoTrackId
203
+ const bufQueue = isAudio ? this._audioBufsQueue : isVideo ? this._videoBufsQueue : null
204
+ if (!bufQueue) {
205
+ return
206
+ }
207
+ const sourceBuffer = isVideo ? this._videoSourceBuffer : this._audioSourceBuffer
208
+ bufQueue.push(buffer)
209
+ // 清除已使用的samples
210
+ this._mp4box.releaseUsedSamples(id, sampleNumber)
211
+ this._mp4box.removeUsedSamples(id)
212
+ const segmentAdded = isAudio ? this._isAudioInitSegmentAdded : this._isVideoInitSegmentAdded
213
+ if (segmentAdded && sourceBuffer && !this._videoEl?.paused && bufQueue.length > 2) {
214
+ const len = bufQueue.length
215
+ const maxTotal = this._options.maxCacheBufByte
216
+ let lastIndex = len - 1
217
+ let total = 0
218
+ for (let i = len - 1; i > 0; i--) {
219
+ total += bufQueue[i].byteLength
220
+ lastIndex = i
221
+ if (total >= maxTotal) {
222
+ bufQueue.splice(0, lastIndex)
223
+ break
224
+ }
225
+ }
226
+ }
227
+ this._cacheAnimationID && cancelAnimationFrame(this._cacheAnimationID)
228
+ this._cacheAnimationID = undefined
229
+ this._cache(isVideo)
180
230
  }
181
231
 
182
232
  /**
@@ -292,55 +342,75 @@ export class Render extends EventBus<RenderEvents> {
292
342
  this._videoEl.src = URL.createObjectURL(this._mediaSource)
293
343
 
294
344
  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
- }
345
+ if (!this._mediaSource) {
346
+ return
347
+ }
326
348
 
327
- const delBufEnd = sourceBuffer.buffered.end(lastIndex - 1)
328
- if (!this._sourceBuffer!.updating && currentTime > delBufEnd) {
329
- this._sourceBuffer?.remove(0, delBufEnd)
330
- }
349
+ // 音频轨
350
+ if (this._audioMimeType) {
351
+ this._audioSourceBuffer = this._mediaSource.addSourceBuffer(this._audioMimeType)
352
+ this._audioSourceBuffer.mode = 'sequence'
353
+ this._setupSourceBuffer(this._audioSourceBuffer, false)
354
+ }
355
+
356
+ // 视频轨
357
+ if (this._videoMimeType) {
358
+ this._videoSourceBuffer = this._mediaSource.addSourceBuffer(this._videoMimeType)
359
+ this._videoSourceBuffer.mode = 'sequence'
360
+ this._setupSourceBuffer(this._videoSourceBuffer, true)
361
+ }
362
+ })
363
+ }
364
+
365
+ private _setupSourceBuffer(sourceBuffer: SourceBuffer, isVideo: boolean = false) {
366
+ sourceBuffer.onupdateend = () => {
367
+ if (
368
+ !this._videoEl ||
369
+ !sourceBuffer ||
370
+ this._mediaSource?.readyState !== 'open' ||
371
+ ![...this._mediaSource.sourceBuffers].includes(sourceBuffer)
372
+ ) {
373
+ return
374
+ }
375
+ const currentTime = this._videoEl.currentTime
376
+ // 调试代码
377
+ // const div = document.getElementById(this.divID)
378
+ // let innerHTML = `len:${sourceBuffer.buffered.length}`
379
+
380
+ if (sourceBuffer.buffered.length > 0) {
381
+ let bufferedLen = sourceBuffer.buffered.length
382
+ /** 是否需要删除sourceBuffer中的buffer段 */
383
+ const needDelBuf = bufferedLen > 1
384
+ /**
385
+ * sourceBuffer中有多个buffered,时间不连续
386
+ * 导致视频播放到其中一个buffer最后就暂停了
387
+ * 如果出现多个buffered,删除之前有的buffer
388
+ * 使用最新的视频buffer进行播放
389
+ */
390
+ if (needDelBuf && currentTime) {
391
+ const lastIndex = bufferedLen - 1
392
+ if (currentTime < sourceBuffer.buffered.start(lastIndex)) {
393
+ this._videoEl.currentTime = sourceBuffer.buffered.start(lastIndex)
331
394
  }
332
395
 
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)
396
+ const delBufEnd = sourceBuffer.buffered.end(lastIndex - 1)
397
+ if (!sourceBuffer!.updating && currentTime > delBufEnd) {
398
+ sourceBuffer?.remove(0, delBufEnd)
399
+ }
400
+ }
401
+
402
+ // 调试代码
403
+ // innerHTML += ` - start:${sourceBuffer.buffered.start(
404
+ // sourceBuffer.buffered.length - 1
405
+ // )} - end:${sourceBuffer.buffered.end(sourceBuffer.buffered.length - 1)} <br/>`
406
+ // innerHTML += ` - currentTime: ${currentTime}`
407
+ // div && (div.innerHTML = innerHTML)
339
408
 
340
- bufferedLen = sourceBuffer.buffered.length
341
- const start = sourceBuffer.buffered.start(bufferedLen - 1)
342
- const end = sourceBuffer.buffered.end(bufferedLen - 1)
409
+ bufferedLen = sourceBuffer.buffered.length
410
+ const start = sourceBuffer.buffered.start(bufferedLen - 1)
411
+ const end = sourceBuffer.buffered.end(bufferedLen - 1)
343
412
 
413
+ if (isVideo) {
344
414
  // 设置开始时间
345
415
  if (!currentTime && start) {
346
416
  this._videoEl.currentTime = start
@@ -354,43 +424,48 @@ export class Render extends EventBus<RenderEvents> {
354
424
  this._videoEl.currentTime = end - 0.1
355
425
  }
356
426
  }
357
- // 移除当前时间之前的buffer
358
- if (!this._sourceBuffer!.updating && currentTime - start > this._options.maxCache) {
359
- this._sourceBuffer?.remove(0, currentTime - this._options.maxCache / 2)
360
- }
427
+ }
428
+
429
+ // 移除当前时间之前的buffer
430
+ if (!sourceBuffer!.updating && currentTime - start > this._options.maxCache) {
431
+ sourceBuffer?.remove(0, currentTime - this._options.maxCache / 2)
361
432
  }
362
433
  }
363
- })
434
+ }
364
435
  }
365
436
 
366
437
  /**
367
438
  * 将_bufsQueue中的数据添加到SourceBuffer中
368
439
  * @returns
369
440
  */
370
- private _cache() {
441
+ private _cache(isVideo: boolean = false) {
371
442
  if (!this._videoEl) {
372
443
  return
373
444
  }
445
+ const queue = isVideo ? this._videoBufsQueue : this._audioBufsQueue
446
+ const sourceBuffer = isVideo ? this._videoSourceBuffer : this._audioSourceBuffer
374
447
  if (
375
448
  !this._mediaSource ||
376
- !this._sourceBuffer ||
377
- this._sourceBuffer.updating ||
378
- !this._bufsQueue.length ||
449
+ !sourceBuffer ||
450
+ sourceBuffer.updating ||
451
+ !queue.length ||
379
452
  this._mediaSource.readyState !== 'open'
380
453
  ) {
381
- this._cacheAnimationID === undefined && (this._cacheAnimationID = requestAnimationFrame(() => this._cache()))
454
+ this._cacheAnimationID === undefined &&
455
+ (this._cacheAnimationID = requestAnimationFrame(() => this._cache(isVideo)))
382
456
  return
383
457
  }
384
458
  if (this._videoEl.error) {
385
459
  this._setupMSE()
386
460
  return (
387
- this._cacheAnimationID === undefined && (this._cacheAnimationID = requestAnimationFrame(() => this._cache()))
461
+ this._cacheAnimationID === undefined &&
462
+ (this._cacheAnimationID = requestAnimationFrame(() => this._cache(isVideo)))
388
463
  )
389
464
  }
390
465
  this._cacheAnimationID = undefined
391
466
  let frame: Uint8Array
392
- if (this._bufsQueue.length > 1) {
393
- const freeBuffer = this._bufsQueue.splice(0, this._bufsQueue.length)
467
+ if (queue.length > 1) {
468
+ const freeBuffer = queue.splice(0, queue.length)
394
469
  const length = freeBuffer.map((e) => e.byteLength).reduce((a, b) => a + b, 0)
395
470
  const buffer = new Uint8Array(length)
396
471
  let offset = 0
@@ -401,11 +476,16 @@ export class Render extends EventBus<RenderEvents> {
401
476
  }
402
477
  frame = buffer
403
478
  } else {
404
- frame = new Uint8Array(this._bufsQueue.shift() || [])
479
+ frame = new Uint8Array(queue.shift() || [])
405
480
  }
406
481
 
407
482
  if (frame) {
408
- this._sourceBuffer.appendBuffer(frame)
483
+ if (isVideo) {
484
+ !this._isVideoInitSegmentAdded && (this._isVideoInitSegmentAdded = true)
485
+ } else {
486
+ !this._isAudioInitSegmentAdded && (this._isAudioInitSegmentAdded = true)
487
+ }
488
+ sourceBuffer.appendBuffer(frame)
409
489
  }
410
490
  }
411
491
 
@@ -421,13 +501,14 @@ export class Render extends EventBus<RenderEvents> {
421
501
 
422
502
  /** 重置解析的视频mime type */
423
503
  public resetMimeType() {
504
+ this.destroyMp4box()
424
505
  this.destroyMediaSource()
425
506
  if (this._videoEl) {
426
507
  this._videoEl.src = ''
427
508
  }
428
509
  this._mp4box = MP4Box.createFile()
429
510
  this._mp4box.onReady = this._onMp4boxReady.bind(this)
430
- this._bufsQueue.length = 0
511
+ this._mp4box.onSegment = this._onSegment.bind(this)
431
512
  }
432
513
 
433
514
  private destroyMediaSource() {
@@ -437,23 +518,44 @@ export class Render extends EventBus<RenderEvents> {
437
518
  this._videoEl.src = ''
438
519
  }
439
520
  if (this._mediaSource.readyState === 'open') {
440
- if (this._sourceBuffer) {
441
- this._sourceBuffer.abort()
442
- this._mediaSource.removeSourceBuffer(this._sourceBuffer)
521
+ if (this._audioSourceBuffer) {
522
+ this._audioSourceBuffer.abort()
523
+ this._mediaSource.removeSourceBuffer(this._audioSourceBuffer)
524
+ }
525
+ if (this._videoSourceBuffer) {
526
+ this._videoSourceBuffer.abort()
527
+ this._mediaSource.removeSourceBuffer(this._videoSourceBuffer)
443
528
  }
444
529
  this._mediaSource.endOfStream()
445
530
  }
446
531
 
447
532
  this._mediaSource = undefined
448
- this._sourceBuffer = undefined
533
+ this._audioSourceBuffer = undefined
534
+ this._videoSourceBuffer = undefined
449
535
  }
450
536
  }
451
537
 
538
+ public destroyMp4box() {
539
+ this._audioTrackId = undefined
540
+ this._videoTrackId = undefined
541
+ this._mimeType = ''
542
+ this._audioMimeType = ''
543
+ this._videoMimeType = ''
544
+ this._isAudioInitSegmentAdded = false
545
+ this._isVideoInitSegmentAdded = false
546
+ this._audioBufsQueue.length = 0
547
+ this._videoBufsQueue.length = 0
548
+ this._offset = 0
549
+ this._mp4box.stop()
550
+ this._mp4box.flush()
551
+ this._mp4box.destroy()
552
+ this._mp4box = null
553
+ }
554
+
452
555
  /**
453
556
  * 销毁
454
557
  */
455
558
  public destroy() {
456
- this._bufsQueue = []
457
559
  if (this._videoEl) {
458
560
  this._videoEl.pause()
459
561
  this._videoEl.currentTime = 0
@@ -465,11 +567,9 @@ export class Render extends EventBus<RenderEvents> {
465
567
  this._videoEl = undefined
466
568
  }
467
569
 
468
- this._mimeType = ''
469
-
470
570
  this._cacheAnimationID && cancelAnimationFrame(this._cacheAnimationID)
471
571
  this._cacheAnimationID = undefined
472
-
572
+ this.destroyMp4box()
473
573
  this.destroyMediaSource()
474
574
  }
475
575
  }
@@ -0,0 +1,58 @@
1
+ import MP4Box from 'mp4box'
2
+
3
+ function ExtendMp4box() {
4
+ const MP4BoxFile = MP4Box.createFile().constructor
5
+ // 清空samples
6
+ MP4BoxFile.prototype.removeUsedSamples = function (id: number) {
7
+ const track = this.getTrackById(id)
8
+ const samples = track.samples
9
+ const lastSample = samples[samples.length - 1]
10
+ lastSample.data = null
11
+ lastSample.description = null
12
+ lastSample.alreadyRead = 0
13
+ track.samples = []
14
+ track.samples.push(lastSample)
15
+ track.nextSample = track.samples.length
16
+ this.boxes = []
17
+ this.mdats = []
18
+ this.moofs = []
19
+ this.lastMoofIndex = 0
20
+ }
21
+
22
+ MP4BoxFile.prototype.destroy = function () {
23
+ if (this.stream) {
24
+ this.stream.buffers = []
25
+ this.stream.bufferIndex = -1
26
+ this.stream = null
27
+ }
28
+ this.boxes = []
29
+ this.mdats = []
30
+ this.moofs = []
31
+ this.isProgressive = false
32
+ this.moovStartFound = false
33
+ this.onMoovStart = null
34
+ this.moovStartSent = false
35
+ this.onReady = null
36
+ this.readySent = false
37
+ this.onSegment = null
38
+ this.onSamples = null
39
+ this.onError = null
40
+ this.sampleListBuilt = false
41
+ this.fragmentedTracks = []
42
+ this.extractedTracks = []
43
+ this.isFragmentationInitialized = false
44
+ this.sampleProcessingStarted = false
45
+ this.nextMoofNumber = 0
46
+ this.itemListBuilt = false
47
+ this.onSidx = null
48
+ this.sidxSent = false
49
+ this.moov = null
50
+ this.ftyp = null
51
+ this.items = []
52
+ this.entity_groups = []
53
+ }
54
+ }
55
+
56
+ ExtendMp4box()
57
+
58
+ export default MP4Box