@clappr/hlsjs-playback 1.0.1 → 1.2.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 +19779 -23676
- package/dist/hlsjs-playback.external.js +309 -168
- package/dist/hlsjs-playback.external.min.js +1 -1
- package/dist/hlsjs-playback.external.min.js.map +1 -1
- package/dist/hlsjs-playback.js +19780 -23677
- package/dist/hlsjs-playback.min.js +72 -268
- package/dist/hlsjs-playback.min.js.map +1 -1
- package/package.json +3 -2
- package/src/hls.js +73 -64
- package/src/hls.test.js +12 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clappr/hlsjs-playback",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.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",
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"test:debug": "node --inspect node_modules/.bin/jest ./src --runInBand",
|
|
16
16
|
"test:watch": "jest ./src --watch",
|
|
17
17
|
"lint": "eslint *.js ./src",
|
|
18
|
-
"lint:fix": "yarn lint -- --fix"
|
|
18
|
+
"lint:fix": "yarn lint -- --fix",
|
|
19
|
+
"prepublishOnly": "npm run release"
|
|
19
20
|
},
|
|
20
21
|
"files": [
|
|
21
22
|
"/dist",
|
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')
|
|
@@ -40,6 +40,14 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
40
40
|
this._hls.currentLevel = this._currentLevel
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
get latency() {
|
|
44
|
+
return this._hls.latency
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
get currentProgramDateTime() {
|
|
48
|
+
return this._hls.playingDate
|
|
49
|
+
}
|
|
50
|
+
|
|
43
51
|
get _startTime() {
|
|
44
52
|
if (this._playbackType === Playback.LIVE && this._playlistType !== 'EVENT')
|
|
45
53
|
return this._extrapolatedStartTime
|
|
@@ -68,12 +76,10 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
68
76
|
// extrapolated to increase in real time (instead of jumping as segments are added)
|
|
69
77
|
get _extrapolatedEndTime() {
|
|
70
78
|
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
|
|
79
|
+
if (!this._localEndTimeCorrelation) return actualEndTime
|
|
80
|
+
const correlation = this._localEndTimeCorrelation
|
|
81
|
+
const timePassed = this._now - correlation.local
|
|
82
|
+
const extrapolatedEndTime = (correlation.remote + timePassed) / 1000
|
|
77
83
|
return Math.max(actualEndTime - this._extrapolatedWindowDuration, Math.min(extrapolatedEndTime, actualEndTime))
|
|
78
84
|
}
|
|
79
85
|
|
|
@@ -117,15 +123,21 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
117
123
|
return this.options.hlsPlayback && this.options.hlsPlayback.customListeners || []
|
|
118
124
|
}
|
|
119
125
|
|
|
126
|
+
get sourceMedia() {
|
|
127
|
+
return this.options.src
|
|
128
|
+
}
|
|
129
|
+
|
|
120
130
|
static get HLSJS() {
|
|
121
131
|
return HLSJS
|
|
122
132
|
}
|
|
123
133
|
|
|
124
134
|
constructor(...args) {
|
|
125
135
|
super(...args)
|
|
126
|
-
// backwards compatibility (TODO: remove on 0.3.0)
|
|
127
|
-
this.options.playback = { ...this.options, ...this.options.playback }
|
|
128
136
|
this.options.hlsPlayback = { ...this.defaultOptions, ...this.options.hlsPlayback }
|
|
137
|
+
this._setInitialState()
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
_setInitialState() {
|
|
129
141
|
this._minDvrSize = typeof (this.options.hlsMinimumDvrSize) === 'undefined' ? 60 : this.options.hlsMinimumDvrSize
|
|
130
142
|
// The size of the start time extrapolation window measured as a multiple of segments.
|
|
131
143
|
// 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 +175,38 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
163
175
|
this._segmentTargetDuration = null
|
|
164
176
|
// #EXT-X-PLAYLIST-TYPE
|
|
165
177
|
this._playlistType = null
|
|
166
|
-
this._recoverAttemptsRemaining = this.options.hlsRecoverAttempts ||
|
|
178
|
+
this._recoverAttemptsRemaining = this.options.hlsRecoverAttempts || DEFAULT_RECOVER_ATTEMPTS
|
|
167
179
|
}
|
|
168
180
|
|
|
169
181
|
_setup() {
|
|
182
|
+
this._destroyHLSInstance()
|
|
183
|
+
this._createHLSInstance()
|
|
184
|
+
this._listenHLSEvents()
|
|
185
|
+
this._attachHLSMedia()
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
_destroyHLSInstance() {
|
|
189
|
+
if (!this._hls) return
|
|
170
190
|
this._manifestParsed = false
|
|
171
191
|
this._ccIsSetup = false
|
|
172
192
|
this._ccTracksUpdated = false
|
|
173
|
-
this.
|
|
174
|
-
this._hls
|
|
193
|
+
this._setInitialState()
|
|
194
|
+
this._hls.destroy()
|
|
195
|
+
this._hls = null
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
_createHLSInstance() {
|
|
199
|
+
const config = { ...this.options.playback.hlsjsConfig }
|
|
200
|
+
this._hls = new HLSJS(config)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
_attachHLSMedia() {
|
|
204
|
+
if (!this._hls) return
|
|
205
|
+
this._hls.attachMedia(this.el)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
_listenHLSEvents() {
|
|
209
|
+
if (!this._hls) return
|
|
175
210
|
this._hls.once(HLSJS.Events.MEDIA_ATTACHED, () => { this.options.hlsPlayback.preload && this._hls.loadSource(this.options.src) })
|
|
176
211
|
this._hls.on(HLSJS.Events.MANIFEST_PARSED, () => this._manifestParsed = true)
|
|
177
212
|
this._hls.on(HLSJS.Events.LEVEL_LOADED, (evt, data) => this._updatePlaybackType(evt, data))
|
|
@@ -183,10 +218,7 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
183
218
|
this._hls.on(HLSJS.Events.ERROR, (evt, data) => this._onHLSJSError(evt, data))
|
|
184
219
|
this._hls.on(HLSJS.Events.SUBTITLE_TRACK_LOADED, (evt, data) => this._onSubtitleLoaded(evt, data))
|
|
185
220
|
this._hls.on(HLSJS.Events.SUBTITLE_TRACKS_UPDATED, () => this._ccTracksUpdated = true)
|
|
186
|
-
|
|
187
221
|
this.bindCustomListeners()
|
|
188
|
-
|
|
189
|
-
this._hls.attachMedia(this.el)
|
|
190
222
|
}
|
|
191
223
|
|
|
192
224
|
bindCustomListeners() {
|
|
@@ -238,13 +270,11 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
238
270
|
}
|
|
239
271
|
|
|
240
272
|
// override
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
}
|
|
273
|
+
// this playback manages the src on the video element itself
|
|
274
|
+
_setupSrc(srcUrl) {} // eslint-disable-line no-unused-vars
|
|
244
275
|
|
|
245
276
|
_startTimeUpdateTimer() {
|
|
246
277
|
if (this._timeUpdateTimer) return
|
|
247
|
-
|
|
248
278
|
this._timeUpdateTimer = setInterval(() => {
|
|
249
279
|
this._onDurationChange()
|
|
250
280
|
this._onTimeUpdate()
|
|
@@ -253,7 +283,6 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
253
283
|
|
|
254
284
|
_stopTimeUpdateTimer() {
|
|
255
285
|
if (!this._timeUpdateTimer) return
|
|
256
|
-
|
|
257
286
|
clearInterval(this._timeUpdateTimer)
|
|
258
287
|
this._timeUpdateTimer = null
|
|
259
288
|
}
|
|
@@ -261,6 +290,7 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
261
290
|
getProgramDateTime() {
|
|
262
291
|
return this._programDateTime
|
|
263
292
|
}
|
|
293
|
+
|
|
264
294
|
// the duration on the video element itself should not be used
|
|
265
295
|
// as this does not necesarily represent the duration of the stream
|
|
266
296
|
// https://github.com/clappr/clappr/issues/668#issuecomment-157036678
|
|
@@ -283,10 +313,9 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
283
313
|
}
|
|
284
314
|
|
|
285
315
|
seekPercentage(percentage) {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
316
|
+
const seekTo = (percentage > 0)
|
|
317
|
+
? this._duration * (percentage / 100)
|
|
318
|
+
: this._duration
|
|
290
319
|
this.seek(seekTo)
|
|
291
320
|
}
|
|
292
321
|
|
|
@@ -402,30 +431,24 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
402
431
|
}
|
|
403
432
|
|
|
404
433
|
_onTimeUpdate() {
|
|
405
|
-
|
|
406
|
-
|
|
434
|
+
const update = { current: this.getCurrentTime(), total: this.getDuration(), firstFragDateTime: this.getProgramDateTime() }
|
|
435
|
+
const isSame = this._lastTimeUpdate && (
|
|
407
436
|
update.current === this._lastTimeUpdate.current &&
|
|
408
|
-
update.total === this._lastTimeUpdate.total)
|
|
409
|
-
if (isSame)
|
|
410
|
-
return
|
|
411
|
-
|
|
437
|
+
update.total === this._lastTimeUpdate.total)
|
|
438
|
+
if (isSame) return
|
|
412
439
|
this._lastTimeUpdate = update
|
|
413
440
|
this.trigger(Events.PLAYBACK_TIMEUPDATE, update, this.name)
|
|
414
441
|
}
|
|
415
442
|
|
|
416
443
|
_onDurationChange() {
|
|
417
|
-
|
|
418
|
-
if (this._lastDuration === duration)
|
|
419
|
-
return
|
|
420
|
-
|
|
444
|
+
const duration = this.getDuration()
|
|
445
|
+
if (this._lastDuration === duration) return
|
|
421
446
|
this._lastDuration = duration
|
|
422
447
|
super._onDurationChange()
|
|
423
448
|
}
|
|
424
449
|
|
|
425
450
|
_onProgress() {
|
|
426
|
-
if (!this.el.buffered.length)
|
|
427
|
-
return
|
|
428
|
-
|
|
451
|
+
if (!this.el.buffered.length) return
|
|
429
452
|
let buffered = []
|
|
430
453
|
let bufferedPos = 0
|
|
431
454
|
for (let i = 0; i < this.el.buffered.length; i++) {
|
|
@@ -446,10 +469,15 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
446
469
|
this.trigger(Events.PLAYBACK_PROGRESS, progress, buffered)
|
|
447
470
|
}
|
|
448
471
|
|
|
472
|
+
load(url) {
|
|
473
|
+
this._stopTimeUpdateTimer()
|
|
474
|
+
this.options.src = url
|
|
475
|
+
this._setup()
|
|
476
|
+
}
|
|
477
|
+
|
|
449
478
|
play() {
|
|
450
479
|
!this._hls && this._setup()
|
|
451
480
|
!this._manifestParsed && !this.options.hlsPlayback.preload && this._hls.loadSource(this.options.src)
|
|
452
|
-
|
|
453
481
|
super.play()
|
|
454
482
|
this._startTimeUpdateTimer()
|
|
455
483
|
}
|
|
@@ -462,26 +490,19 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
462
490
|
|
|
463
491
|
stop() {
|
|
464
492
|
this._stopTimeUpdateTimer()
|
|
465
|
-
if (this._hls)
|
|
466
|
-
|
|
467
|
-
this._hls.destroy()
|
|
468
|
-
delete this._hls
|
|
469
|
-
}
|
|
493
|
+
if (this._hls) super.stop()
|
|
494
|
+
this._destroyHLSInstance()
|
|
470
495
|
}
|
|
471
496
|
|
|
472
497
|
destroy() {
|
|
473
498
|
this._stopTimeUpdateTimer()
|
|
474
|
-
|
|
475
|
-
this._hls.destroy()
|
|
476
|
-
delete this._hls
|
|
477
|
-
}
|
|
499
|
+
this._destroyHLSInstance()
|
|
478
500
|
super.destroy()
|
|
479
501
|
}
|
|
480
502
|
|
|
481
503
|
_updatePlaybackType(evt, data) {
|
|
482
504
|
this._playbackType = data.details.live ? Playback.LIVE : Playback.VOD
|
|
483
505
|
this._onLevelUpdated(evt, data)
|
|
484
|
-
|
|
485
506
|
// Live stream subtitle tracks detection hack (may not immediately available)
|
|
486
507
|
if (this._ccTracksUpdated && this._playbackType === Playback.LIVE && this.hasClosedCaptionsTracks)
|
|
487
508
|
this._onSubtitleLoaded()
|
|
@@ -498,22 +519,15 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
498
519
|
_onLevelUpdated(evt, data) {
|
|
499
520
|
this._segmentTargetDuration = data.details.targetduration
|
|
500
521
|
this._playlistType = data.details.type || null
|
|
501
|
-
|
|
502
522
|
let startTimeChanged = false
|
|
503
523
|
let durationChanged = false
|
|
504
524
|
let fragments = data.details.fragments
|
|
505
525
|
let previousPlayableRegionStartTime = this._playableRegionStartTime
|
|
506
526
|
let previousPlayableRegionDuration = this._playableRegionDuration
|
|
507
|
-
|
|
508
|
-
if (fragments.length === 0)
|
|
509
|
-
return
|
|
510
|
-
|
|
511
|
-
|
|
527
|
+
if (fragments.length === 0) return
|
|
512
528
|
// #EXT-X-PROGRAM-DATE-TIME
|
|
513
529
|
if (fragments[0].rawProgramDateTime)
|
|
514
530
|
this._programDateTime = fragments[0].rawProgramDateTime
|
|
515
|
-
|
|
516
|
-
|
|
517
531
|
if (this._playableRegionStartTime !== fragments[0].start) {
|
|
518
532
|
startTimeChanged = true
|
|
519
533
|
this._playableRegionStartTime = fragments[0].start
|
|
@@ -552,7 +566,7 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
552
566
|
}
|
|
553
567
|
}
|
|
554
568
|
}
|
|
555
|
-
|
|
569
|
+
|
|
556
570
|
let newDuration = data.details.totalduration
|
|
557
571
|
// if it's a live stream then shorten the duration to remove access
|
|
558
572
|
// to the area after hlsjs's live sync point
|
|
@@ -568,12 +582,10 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
568
582
|
} else { this._durationExcludesAfterLiveSyncPoint = false }
|
|
569
583
|
|
|
570
584
|
}
|
|
571
|
-
|
|
572
585
|
if (newDuration !== this._playableRegionDuration) {
|
|
573
586
|
durationChanged = true
|
|
574
587
|
this._playableRegionDuration = newDuration
|
|
575
588
|
}
|
|
576
|
-
|
|
577
589
|
// Note the end time is not the playableRegionDuration
|
|
578
590
|
// The end time will always increase even if content is removed from the beginning
|
|
579
591
|
let endTime = fragments[0].start + newDuration
|
|
@@ -642,9 +654,7 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
642
654
|
}
|
|
643
655
|
|
|
644
656
|
_onLevelSwitch(evt, data) {
|
|
645
|
-
if (!this.levels.length)
|
|
646
|
-
this._fillLevels()
|
|
647
|
-
|
|
657
|
+
if (!this.levels.length) this._fillLevels()
|
|
648
658
|
this.trigger(Events.PLAYBACK_LEVEL_SWITCH_END)
|
|
649
659
|
this.trigger(Events.PLAYBACK_LEVEL_SWITCH, data)
|
|
650
660
|
let currentLevel = this._hls.levels[data.level]
|
|
@@ -682,6 +692,5 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
682
692
|
HlsjsPlayback.canPlay = function(resource, mimeType) {
|
|
683
693
|
const resourceParts = resource.split('?')[0].match(/.*\.(.*)$/) || []
|
|
684
694
|
const isHls = ((resourceParts.length > 1 && resourceParts[1].toLowerCase() === 'm3u8') || listContainsIgnoreCase(mimeType, ['application/vnd.apple.mpegurl', 'application/x-mpegURL']))
|
|
685
|
-
|
|
686
695
|
return !!(HLSJS.isSupported() && isHls)
|
|
687
696
|
}
|
package/src/hls.test.js
CHANGED
|
@@ -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()
|