@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/dist/hlsjs-playback.esm.js +23725 -19744
- package/dist/hlsjs-playback.external.js +202 -259
- package/dist/hlsjs-playback.external.min.js +1 -1
- package/dist/hlsjs-playback.external.min.js.map +1 -1
- package/dist/hlsjs-playback.js +23726 -19745
- package/dist/hlsjs-playback.min.js +268 -72
- package/dist/hlsjs-playback.min.js.map +1 -1
- package/package.json +5 -5
- package/src/hls.js +65 -64
- package/src/hls.test.js +16 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clappr/hlsjs-playback",
|
|
3
|
-
"version": "
|
|
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.
|
|
39
|
-
"hls.js": "^
|
|
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.
|
|
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": "^
|
|
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,
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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 ||
|
|
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.
|
|
174
|
-
this._hls
|
|
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
|
-
|
|
242
|
-
|
|
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
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
-
|
|
406
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
})
|