@clappr/hlsjs-playback 0.6.0 → 1.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clappr/hlsjs-playback",
3
- "version": "0.6.0",
3
+ "version": "1.1.0",
4
4
  "description": "HLS Playback based on hls.js",
5
5
  "main": "./dist/hlsjs-playback.js",
6
6
  "module": "./dist/hlsjs-playback.esm.js",
@@ -35,13 +35,13 @@
35
35
  },
36
36
  "homepage": "https://github.com/clappr/hlsjs-playback",
37
37
  "peerDependencies": {
38
- "@clappr/core": "^0.4.18",
39
- "hls.js": "^0.14.17"
38
+ "@clappr/core": "^0.4.27",
39
+ "hls.js": "^1.2.4"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@babel/core": "^7.14.2",
43
43
  "@babel/preset-env": "^7.14.2",
44
- "@clappr/core": "^0.4.18",
44
+ "@clappr/core": "^0.4.27",
45
45
  "@rollup/plugin-babel": "^5.3.0",
46
46
  "@rollup/plugin-commonjs": "^19.0.0",
47
47
  "@rollup/plugin-node-resolve": "^13.0.0",
@@ -50,7 +50,7 @@
50
50
  "coveralls": "^3.1.0",
51
51
  "cz-conventional-changelog": "^3.3.0",
52
52
  "eslint": "^7.26.0",
53
- "hls.js": "^0.14.17",
53
+ "hls.js": "^1.2.4",
54
54
  "jest": "^26.6.3",
55
55
  "rollup": "^2.47.0",
56
56
  "rollup-plugin-filesize": "^9.1.1",
package/src/hls.js CHANGED
@@ -5,9 +5,9 @@
5
5
  import { Events, HTML5Video, Log, Playback, PlayerError, Utils } from '@clappr/core'
6
6
  import HLSJS from 'hls.js'
7
7
 
8
- const { now, assign, listContainsIgnoreCase } = Utils
9
-
8
+ const { now, listContainsIgnoreCase } = Utils
10
9
  const AUTO = -1
10
+ const DEFAULT_RECOVER_ATTEMPTS = 16
11
11
 
12
12
  Events.register('PLAYBACK_FRAGMENT_CHANGED')
13
13
  Events.register('PLAYBACK_FRAGMENT_PARSING_METADATA')
@@ -68,12 +68,10 @@ export default class HlsjsPlayback extends HTML5Video {
68
68
  // extrapolated to increase in real time (instead of jumping as segments are added)
69
69
  get _extrapolatedEndTime() {
70
70
  let actualEndTime = this._playableRegionStartTime + this._playableRegionDuration
71
- if (!this._localEndTimeCorrelation)
72
- return actualEndTime
73
-
74
- let corr = this._localEndTimeCorrelation
75
- let timePassed = this._now - corr.local
76
- let extrapolatedEndTime = (corr.remote + timePassed) / 1000
71
+ if (!this._localEndTimeCorrelation) return actualEndTime
72
+ const correlation = this._localEndTimeCorrelation
73
+ const timePassed = this._now - correlation.local
74
+ const extrapolatedEndTime = (correlation.remote + timePassed) / 1000
77
75
  return Math.max(actualEndTime - this._extrapolatedWindowDuration, Math.min(extrapolatedEndTime, actualEndTime))
78
76
  }
79
77
 
@@ -117,15 +115,21 @@ export default class HlsjsPlayback extends HTML5Video {
117
115
  return this.options.hlsPlayback && this.options.hlsPlayback.customListeners || []
118
116
  }
119
117
 
118
+ get sourceMedia() {
119
+ return this.options.src
120
+ }
121
+
120
122
  static get HLSJS() {
121
123
  return HLSJS
122
124
  }
123
125
 
124
126
  constructor(...args) {
125
127
  super(...args)
126
- // backwards compatibility (TODO: remove on 0.3.0)
127
- this.options.playback = { ...this.options, ...this.options.playback }
128
128
  this.options.hlsPlayback = { ...this.defaultOptions, ...this.options.hlsPlayback }
129
+ this._setInitialState()
130
+ }
131
+
132
+ _setInitialState() {
129
133
  this._minDvrSize = typeof (this.options.hlsMinimumDvrSize) === 'undefined' ? 60 : this.options.hlsMinimumDvrSize
130
134
  // The size of the start time extrapolation window measured as a multiple of segments.
131
135
  // Should be 2 or higher, or 0 to disable. Should only need to be increased above 2 if more than one segment is
@@ -163,15 +167,38 @@ export default class HlsjsPlayback extends HTML5Video {
163
167
  this._segmentTargetDuration = null
164
168
  // #EXT-X-PLAYLIST-TYPE
165
169
  this._playlistType = null
166
- this._recoverAttemptsRemaining = this.options.hlsRecoverAttempts || 16
170
+ this._recoverAttemptsRemaining = this.options.hlsRecoverAttempts || DEFAULT_RECOVER_ATTEMPTS
167
171
  }
168
172
 
169
173
  _setup() {
174
+ this._destroyHLSInstance()
175
+ this._createHLSInstance()
176
+ this._listenHLSEvents()
177
+ this._attachHLSMedia()
178
+ }
179
+
180
+ _destroyHLSInstance() {
181
+ if (!this._hls) return
170
182
  this._manifestParsed = false
171
183
  this._ccIsSetup = false
172
184
  this._ccTracksUpdated = false
173
- this._hls && this._hls.destroy()
174
- this._hls = new HLSJS(assign({}, this.options.playback.hlsjsConfig))
185
+ this._setInitialState()
186
+ this._hls.destroy()
187
+ this._hls = null
188
+ }
189
+
190
+ _createHLSInstance() {
191
+ const config = { ...this.options.playback.hlsjsConfig }
192
+ this._hls = new HLSJS(config)
193
+ }
194
+
195
+ _attachHLSMedia() {
196
+ if (!this._hls) return
197
+ this._hls.attachMedia(this.el)
198
+ }
199
+
200
+ _listenHLSEvents() {
201
+ if (!this._hls) return
175
202
  this._hls.once(HLSJS.Events.MEDIA_ATTACHED, () => { this.options.hlsPlayback.preload && this._hls.loadSource(this.options.src) })
176
203
  this._hls.on(HLSJS.Events.MANIFEST_PARSED, () => this._manifestParsed = true)
177
204
  this._hls.on(HLSJS.Events.LEVEL_LOADED, (evt, data) => this._updatePlaybackType(evt, data))
@@ -183,10 +210,7 @@ export default class HlsjsPlayback extends HTML5Video {
183
210
  this._hls.on(HLSJS.Events.ERROR, (evt, data) => this._onHLSJSError(evt, data))
184
211
  this._hls.on(HLSJS.Events.SUBTITLE_TRACK_LOADED, (evt, data) => this._onSubtitleLoaded(evt, data))
185
212
  this._hls.on(HLSJS.Events.SUBTITLE_TRACKS_UPDATED, () => this._ccTracksUpdated = true)
186
-
187
213
  this.bindCustomListeners()
188
-
189
- this._hls.attachMedia(this.el)
190
214
  }
191
215
 
192
216
  bindCustomListeners() {
@@ -238,13 +262,11 @@ export default class HlsjsPlayback extends HTML5Video {
238
262
  }
239
263
 
240
264
  // override
241
- _setupSrc(srcUrl) { // eslint-disable-line no-unused-vars
242
- // this playback manages the src on the video element itself
243
- }
265
+ // this playback manages the src on the video element itself
266
+ _setupSrc(srcUrl) {} // eslint-disable-line no-unused-vars
244
267
 
245
268
  _startTimeUpdateTimer() {
246
269
  if (this._timeUpdateTimer) return
247
-
248
270
  this._timeUpdateTimer = setInterval(() => {
249
271
  this._onDurationChange()
250
272
  this._onTimeUpdate()
@@ -253,7 +275,6 @@ export default class HlsjsPlayback extends HTML5Video {
253
275
 
254
276
  _stopTimeUpdateTimer() {
255
277
  if (!this._timeUpdateTimer) return
256
-
257
278
  clearInterval(this._timeUpdateTimer)
258
279
  this._timeUpdateTimer = null
259
280
  }
@@ -261,6 +282,7 @@ export default class HlsjsPlayback extends HTML5Video {
261
282
  getProgramDateTime() {
262
283
  return this._programDateTime
263
284
  }
285
+
264
286
  // the duration on the video element itself should not be used
265
287
  // as this does not necesarily represent the duration of the stream
266
288
  // https://github.com/clappr/clappr/issues/668#issuecomment-157036678
@@ -283,10 +305,9 @@ export default class HlsjsPlayback extends HTML5Video {
283
305
  }
284
306
 
285
307
  seekPercentage(percentage) {
286
- let seekTo = this._duration
287
- if (percentage > 0)
288
- seekTo = this._duration * (percentage / 100)
289
-
308
+ const seekTo = (percentage > 0)
309
+ ? this._duration * (percentage / 100)
310
+ : this._duration
290
311
  this.seek(seekTo)
291
312
  }
292
313
 
@@ -402,30 +423,24 @@ export default class HlsjsPlayback extends HTML5Video {
402
423
  }
403
424
 
404
425
  _onTimeUpdate() {
405
- let update = { current: this.getCurrentTime(), total: this.getDuration(), firstFragDateTime: this.getProgramDateTime() }
406
- let isSame = this._lastTimeUpdate && (
426
+ const update = { current: this.getCurrentTime(), total: this.getDuration(), firstFragDateTime: this.getProgramDateTime() }
427
+ const isSame = this._lastTimeUpdate && (
407
428
  update.current === this._lastTimeUpdate.current &&
408
- update.total === this._lastTimeUpdate.total)
409
- if (isSame)
410
- return
411
-
429
+ update.total === this._lastTimeUpdate.total)
430
+ if (isSame) return
412
431
  this._lastTimeUpdate = update
413
432
  this.trigger(Events.PLAYBACK_TIMEUPDATE, update, this.name)
414
433
  }
415
434
 
416
435
  _onDurationChange() {
417
- let duration = this.getDuration()
418
- if (this._lastDuration === duration)
419
- return
420
-
436
+ const duration = this.getDuration()
437
+ if (this._lastDuration === duration) return
421
438
  this._lastDuration = duration
422
439
  super._onDurationChange()
423
440
  }
424
441
 
425
442
  _onProgress() {
426
- if (!this.el.buffered.length)
427
- return
428
-
443
+ if (!this.el.buffered.length) return
429
444
  let buffered = []
430
445
  let bufferedPos = 0
431
446
  for (let i = 0; i < this.el.buffered.length; i++) {
@@ -446,10 +461,15 @@ export default class HlsjsPlayback extends HTML5Video {
446
461
  this.trigger(Events.PLAYBACK_PROGRESS, progress, buffered)
447
462
  }
448
463
 
464
+ load(url) {
465
+ this._stopTimeUpdateTimer()
466
+ this.options.src = url
467
+ this._setup()
468
+ }
469
+
449
470
  play() {
450
471
  !this._hls && this._setup()
451
472
  !this._manifestParsed && !this.options.hlsPlayback.preload && this._hls.loadSource(this.options.src)
452
-
453
473
  super.play()
454
474
  this._startTimeUpdateTimer()
455
475
  }
@@ -462,26 +482,19 @@ export default class HlsjsPlayback extends HTML5Video {
462
482
 
463
483
  stop() {
464
484
  this._stopTimeUpdateTimer()
465
- if (this._hls) {
466
- super.stop()
467
- this._hls.destroy()
468
- delete this._hls
469
- }
485
+ if (this._hls) super.stop()
486
+ this._destroyHLSInstance()
470
487
  }
471
488
 
472
489
  destroy() {
473
490
  this._stopTimeUpdateTimer()
474
- if (this._hls) {
475
- this._hls.destroy()
476
- delete this._hls
477
- }
491
+ this._destroyHLSInstance()
478
492
  super.destroy()
479
493
  }
480
494
 
481
495
  _updatePlaybackType(evt, data) {
482
496
  this._playbackType = data.details.live ? Playback.LIVE : Playback.VOD
483
497
  this._onLevelUpdated(evt, data)
484
-
485
498
  // Live stream subtitle tracks detection hack (may not immediately available)
486
499
  if (this._ccTracksUpdated && this._playbackType === Playback.LIVE && this.hasClosedCaptionsTracks)
487
500
  this._onSubtitleLoaded()
@@ -498,22 +511,15 @@ export default class HlsjsPlayback extends HTML5Video {
498
511
  _onLevelUpdated(evt, data) {
499
512
  this._segmentTargetDuration = data.details.targetduration
500
513
  this._playlistType = data.details.type || null
501
-
502
514
  let startTimeChanged = false
503
515
  let durationChanged = false
504
516
  let fragments = data.details.fragments
505
517
  let previousPlayableRegionStartTime = this._playableRegionStartTime
506
518
  let previousPlayableRegionDuration = this._playableRegionDuration
507
-
508
- if (fragments.length === 0)
509
- return
510
-
511
-
519
+ if (fragments.length === 0) return
512
520
  // #EXT-X-PROGRAM-DATE-TIME
513
521
  if (fragments[0].rawProgramDateTime)
514
522
  this._programDateTime = fragments[0].rawProgramDateTime
515
-
516
-
517
523
  if (this._playableRegionStartTime !== fragments[0].start) {
518
524
  startTimeChanged = true
519
525
  this._playableRegionStartTime = fragments[0].start
@@ -552,7 +558,7 @@ export default class HlsjsPlayback extends HTML5Video {
552
558
  }
553
559
  }
554
560
  }
555
-
561
+
556
562
  let newDuration = data.details.totalduration
557
563
  // if it's a live stream then shorten the duration to remove access
558
564
  // to the area after hlsjs's live sync point
@@ -568,12 +574,10 @@ export default class HlsjsPlayback extends HTML5Video {
568
574
  } else { this._durationExcludesAfterLiveSyncPoint = false }
569
575
 
570
576
  }
571
-
572
577
  if (newDuration !== this._playableRegionDuration) {
573
578
  durationChanged = true
574
579
  this._playableRegionDuration = newDuration
575
580
  }
576
-
577
581
  // Note the end time is not the playableRegionDuration
578
582
  // The end time will always increase even if content is removed from the beginning
579
583
  let endTime = fragments[0].start + newDuration
@@ -642,9 +646,7 @@ export default class HlsjsPlayback extends HTML5Video {
642
646
  }
643
647
 
644
648
  _onLevelSwitch(evt, data) {
645
- if (!this.levels.length)
646
- this._fillLevels()
647
-
649
+ if (!this.levels.length) this._fillLevels()
648
650
  this.trigger(Events.PLAYBACK_LEVEL_SWITCH_END)
649
651
  this.trigger(Events.PLAYBACK_LEVEL_SWITCH, data)
650
652
  let currentLevel = this._hls.levels[data.level]
@@ -682,6 +684,5 @@ export default class HlsjsPlayback extends HTML5Video {
682
684
  HlsjsPlayback.canPlay = function(resource, mimeType) {
683
685
  const resourceParts = resource.split('?')[0].match(/.*\.(.*)$/) || []
684
686
  const isHls = ((resourceParts.length > 1 && resourceParts[1].toLowerCase() === 'm3u8') || listContainsIgnoreCase(mimeType, ['application/vnd.apple.mpegurl', 'application/x-mpegURL']))
685
-
686
687
  return !!(HLSJS.isSupported() && isHls)
687
688
  }
package/src/hls.test.js CHANGED
@@ -164,14 +164,14 @@ describe('HlsjsPlayback', () => {
164
164
  const playback = new HlsjsPlayback({ src: 'http://clappr.io/foo.m3u8', hlsPlayback: { preload: false } })
165
165
  playback._setup()
166
166
  jest.spyOn(playback._hls, 'loadSource')
167
- playback._hls.trigger(HLSJS.Events.MEDIA_ATTACHED)
167
+ playback._hls.trigger(HLSJS.Events.MEDIA_ATTACHED, { media: playback.el })
168
168
 
169
169
  expect(playback._hls.loadSource).not.toHaveBeenCalled()
170
170
 
171
171
  playback.options.hlsPlayback.preload = true
172
172
  playback._setup()
173
173
  jest.spyOn(playback._hls, 'loadSource')
174
- playback._hls.trigger(HLSJS.Events.MEDIA_ATTACHED)
174
+ playback._hls.trigger(HLSJS.Events.MEDIA_ATTACHED, { media: playback.el })
175
175
 
176
176
  expect(playback._hls.loadSource).toHaveBeenCalledTimes(1)
177
177
  })
@@ -182,7 +182,7 @@ describe('HlsjsPlayback', () => {
182
182
  expect(playback._manifestParsed).toBeUndefined()
183
183
 
184
184
  playback._setup()
185
- playback._hls.trigger(HLSJS.Events.MANIFEST_PARSED)
185
+ playback._hls.trigger(HLSJS.Events.MANIFEST_PARSED, { levels: [] })
186
186
 
187
187
  expect(playback._manifestParsed).toBeTruthy()
188
188
  })
@@ -262,6 +262,18 @@ describe('HlsjsPlayback', () => {
262
262
  })
263
263
  })
264
264
 
265
+ describe('load method', () => {
266
+ test('loads a new source when called', () => {
267
+ const playback = new HlsjsPlayback({ src: 'http://clappr.io/foo.m3u8', hlsPlayback: { preload: true } })
268
+ const url = 'http://clappr.io/foo2.m3u8'
269
+ playback.load(url)
270
+ jest.spyOn(playback._hls, 'loadSource')
271
+ playback._hls.trigger(HLSJS.Events.MEDIA_ATTACHED, { media: playback.el })
272
+ expect(playback.options.src).toBe(url)
273
+ expect(playback._hls.loadSource).toHaveBeenCalledWith(url)
274
+ })
275
+ })
276
+
265
277
  describe('bindCustomListeners method', () => {
266
278
  test('creates listeners for each item configured on customListeners array', () => {
267
279
  const cb = jest.fn()
@@ -275,7 +287,7 @@ describe('HlsjsPlayback', () => {
275
287
 
276
288
  expect(cb).toHaveBeenCalledTimes(1)
277
289
 
278
- playback._hls.trigger(HLSJS.Events.MEDIA_ATTACHING)
290
+ playback._hls.trigger(HLSJS.Events.MEDIA_ATTACHING, { media: playback.el })
279
291
 
280
292
  expect(cb).toHaveBeenCalledTimes(2)
281
293
  })