@clappr/hlsjs-playback 1.8.3 → 1.9.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 +49 -11
- package/dist/hlsjs-playback.external.js +49 -11
- package/dist/hlsjs-playback.external.min.js +1 -1
- package/dist/hlsjs-playback.external.min.js.map +1 -1
- package/dist/hlsjs-playback.js +49 -11
- package/dist/hlsjs-playback.min.js +1 -1
- package/dist/hlsjs-playback.min.js.map +1 -1
- package/package.json +3 -4
- package/src/hls.js +188 -114
- package/src/hls.test.js +59 -26
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clappr/hlsjs-playback",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.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",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@babel/core": "^7.14.2",
|
|
44
44
|
"@babel/preset-env": "^7.14.2",
|
|
45
|
-
"@clappr/core": "^0.
|
|
45
|
+
"@clappr/core": "^0.13.0",
|
|
46
46
|
"@rollup/plugin-babel": "^5.3.0",
|
|
47
47
|
"@rollup/plugin-commonjs": "^19.0.0",
|
|
48
48
|
"@rollup/plugin-node-resolve": "^13.0.0",
|
|
@@ -74,6 +74,5 @@
|
|
|
74
74
|
]
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
|
-
}
|
|
78
|
-
"gitHead": "20261f4b956c071733625fc303e5b607f75befd7"
|
|
77
|
+
}
|
|
79
78
|
}
|
package/src/hls.js
CHANGED
|
@@ -13,18 +13,24 @@ Events.register('PLAYBACK_FRAGMENT_CHANGED')
|
|
|
13
13
|
Events.register('PLAYBACK_FRAGMENT_PARSING_METADATA')
|
|
14
14
|
|
|
15
15
|
export default class HlsjsPlayback extends HTML5Video {
|
|
16
|
-
get name() {
|
|
16
|
+
get name() {
|
|
17
|
+
return 'hls'
|
|
18
|
+
}
|
|
17
19
|
|
|
18
|
-
get supportedVersion() {
|
|
20
|
+
get supportedVersion() {
|
|
21
|
+
return { min: CLAPPR_CORE_VERSION }
|
|
22
|
+
}
|
|
19
23
|
|
|
20
|
-
get levels() {
|
|
24
|
+
get levels() {
|
|
25
|
+
return this._levels || []
|
|
26
|
+
}
|
|
21
27
|
|
|
22
28
|
get currentLevel() {
|
|
23
|
-
if (this._currentLevel === null || this._currentLevel === undefined)
|
|
29
|
+
if (this._currentLevel === null || this._currentLevel === undefined) {
|
|
24
30
|
return AUTO
|
|
25
|
-
else
|
|
26
|
-
return this._currentLevel
|
|
27
|
-
|
|
31
|
+
} else {
|
|
32
|
+
return this._currentLevel
|
|
33
|
+
} // 0 is a valid level ID
|
|
28
34
|
}
|
|
29
35
|
|
|
30
36
|
get isReady() {
|
|
@@ -34,10 +40,11 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
34
40
|
set currentLevel(id) {
|
|
35
41
|
this._currentLevel = id
|
|
36
42
|
this.trigger(Events.PLAYBACK_LEVEL_SWITCH_START)
|
|
37
|
-
if (this.options.playback.hlsUseNextLevel)
|
|
43
|
+
if (this.options.playback.hlsUseNextLevel) {
|
|
38
44
|
this._hls.nextLevel = this._currentLevel
|
|
39
|
-
else
|
|
45
|
+
} else {
|
|
40
46
|
this._hls.currentLevel = this._currentLevel
|
|
47
|
+
}
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
get latency() {
|
|
@@ -53,8 +60,9 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
53
60
|
}
|
|
54
61
|
|
|
55
62
|
get _startTime() {
|
|
56
|
-
if (this._playbackType === Playback.LIVE && this._playlistType !== 'EVENT')
|
|
63
|
+
if (this._playbackType === Playback.LIVE && this._playlistType !== 'EVENT') {
|
|
57
64
|
return this._extrapolatedStartTime
|
|
65
|
+
}
|
|
58
66
|
|
|
59
67
|
return this._playableRegionStartTime
|
|
60
68
|
}
|
|
@@ -66,25 +74,32 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
66
74
|
// the time in the video element which should represent the start of the sliding window
|
|
67
75
|
// extrapolated to increase in real time (instead of jumping as the early segments are removed)
|
|
68
76
|
get _extrapolatedStartTime() {
|
|
69
|
-
if (!this._localStartTimeCorrelation)
|
|
77
|
+
if (!this._localStartTimeCorrelation) {
|
|
70
78
|
return this._playableRegionStartTime
|
|
79
|
+
}
|
|
71
80
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
81
|
+
const corr = this._localStartTimeCorrelation
|
|
82
|
+
const timePassed = this._now - corr.local
|
|
83
|
+
const extrapolatedWindowStartTime = (corr.remote + timePassed) / 1000
|
|
75
84
|
// cap at the end of the extrapolated window duration
|
|
76
|
-
return Math.min(
|
|
85
|
+
return Math.min(
|
|
86
|
+
extrapolatedWindowStartTime,
|
|
87
|
+
this._playableRegionStartTime + this._extrapolatedWindowDuration
|
|
88
|
+
)
|
|
77
89
|
}
|
|
78
90
|
|
|
79
91
|
// the time in the video element which should represent the end of the content
|
|
80
92
|
// extrapolated to increase in real time (instead of jumping as segments are added)
|
|
81
93
|
get _extrapolatedEndTime() {
|
|
82
|
-
|
|
94
|
+
const actualEndTime = this._playableRegionStartTime + this._playableRegionDuration
|
|
83
95
|
if (!this._localEndTimeCorrelation) return actualEndTime
|
|
84
96
|
const correlation = this._localEndTimeCorrelation
|
|
85
97
|
const timePassed = this._now - correlation.local
|
|
86
98
|
const extrapolatedEndTime = (correlation.remote + timePassed) / 1000
|
|
87
|
-
return Math.max(
|
|
99
|
+
return Math.max(
|
|
100
|
+
actualEndTime - this._extrapolatedWindowDuration,
|
|
101
|
+
Math.min(extrapolatedEndTime, actualEndTime)
|
|
102
|
+
)
|
|
88
103
|
}
|
|
89
104
|
|
|
90
105
|
get _duration() {
|
|
@@ -109,8 +124,9 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
109
124
|
// . . . .
|
|
110
125
|
// extrapolatedStartTime
|
|
111
126
|
get _extrapolatedWindowDuration() {
|
|
112
|
-
if (this._segmentTargetDuration === null)
|
|
127
|
+
if (this._segmentTargetDuration === null) {
|
|
113
128
|
return 0
|
|
129
|
+
}
|
|
114
130
|
|
|
115
131
|
return this._extrapolatedWindowNumSegments * this._segmentTargetDuration
|
|
116
132
|
}
|
|
@@ -124,7 +140,7 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
124
140
|
}
|
|
125
141
|
|
|
126
142
|
get customListeners() {
|
|
127
|
-
return this.options.hlsPlayback && this.options.hlsPlayback.customListeners || []
|
|
143
|
+
return (this.options.hlsPlayback && this.options.hlsPlayback.customListeners) || []
|
|
128
144
|
}
|
|
129
145
|
|
|
130
146
|
get sourceMedia() {
|
|
@@ -154,12 +170,17 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
154
170
|
}
|
|
155
171
|
|
|
156
172
|
_setInitialState() {
|
|
157
|
-
this._minDvrSize =
|
|
173
|
+
this._minDvrSize =
|
|
174
|
+
typeof this.options.hlsMinimumDvrSize === 'undefined' ? 60 : this.options.hlsMinimumDvrSize
|
|
158
175
|
// The size of the start time extrapolation window measured as a multiple of segments.
|
|
159
176
|
// Should be 2 or higher, or 0 to disable. Should only need to be increased above 2 if more than one segment is
|
|
160
177
|
// removed from the start of the playlist at a time. E.g if the playlist is cached for 10 seconds and new chunks are
|
|
161
178
|
// added/removed every 5.
|
|
162
|
-
this._extrapolatedWindowNumSegments =
|
|
179
|
+
this._extrapolatedWindowNumSegments =
|
|
180
|
+
!this.options.playback ||
|
|
181
|
+
typeof this.options.playback.extrapolatedWindowNumSegments === 'undefined'
|
|
182
|
+
? 2
|
|
183
|
+
: this.options.playback.extrapolatedWindowNumSegments
|
|
163
184
|
|
|
164
185
|
this._playbackType = Playback.VOD
|
|
165
186
|
this._lastTimeUpdate = { current: 0, total: 0, firstFragDateTime: 0 }
|
|
@@ -224,25 +245,33 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
224
245
|
|
|
225
246
|
_listenHLSEvents() {
|
|
226
247
|
if (!this._hls) return
|
|
227
|
-
this._hls.once(HLSJS.Events.MEDIA_ATTACHED, () => {
|
|
228
|
-
|
|
248
|
+
this._hls.once(HLSJS.Events.MEDIA_ATTACHED, () => {
|
|
249
|
+
this.options.hlsPlayback.preload && this._hls.loadSource(this.options.src)
|
|
250
|
+
})
|
|
251
|
+
this._hls.on(HLSJS.Events.MANIFEST_LOADING, () => {
|
|
252
|
+
this._manifestLoading = true
|
|
253
|
+
})
|
|
229
254
|
this._hls.on(HLSJS.Events.LEVEL_LOADED, (evt, data) => this._updatePlaybackType(evt, data))
|
|
230
255
|
this._hls.on(HLSJS.Events.LEVEL_UPDATED, (evt, data) => this._onLevelUpdated(evt, data))
|
|
231
|
-
this._hls.on(HLSJS.Events.LEVEL_SWITCHED, (evt,data) => this._onLevelSwitch(evt, data))
|
|
256
|
+
this._hls.on(HLSJS.Events.LEVEL_SWITCHED, (evt, data) => this._onLevelSwitch(evt, data))
|
|
232
257
|
this._hls.on(HLSJS.Events.FRAG_CHANGED, (evt, data) => this._onFragmentChanged(evt, data))
|
|
233
258
|
this._hls.on(HLSJS.Events.FRAG_LOADED, (evt, data) => this._onFragmentLoaded(evt, data))
|
|
234
259
|
this._hls.on(HLSJS.Events.FRAG_BUFFERED, (evt, data) => this._onFragmentBuffered(evt, data))
|
|
235
|
-
this._hls.on(HLSJS.Events.FRAG_PARSING_METADATA, (evt, data) =>
|
|
260
|
+
this._hls.on(HLSJS.Events.FRAG_PARSING_METADATA, (evt, data) =>
|
|
261
|
+
this._onFragmentParsingMetadata(evt, data)
|
|
262
|
+
)
|
|
236
263
|
this._hls.on(HLSJS.Events.ERROR, (evt, data) => this._onHLSJSError(evt, data))
|
|
237
|
-
this._hls.on(HLSJS.Events.SUBTITLE_TRACK_LOADED, (evt, data) =>
|
|
238
|
-
|
|
264
|
+
this._hls.on(HLSJS.Events.SUBTITLE_TRACK_LOADED, (evt, data) =>
|
|
265
|
+
this._onSubtitleLoaded(evt, data)
|
|
266
|
+
)
|
|
267
|
+
this._hls.on(HLSJS.Events.SUBTITLE_TRACKS_UPDATED, () => (this._ccTracksUpdated = true))
|
|
239
268
|
this.bindCustomListeners()
|
|
240
269
|
}
|
|
241
270
|
|
|
242
271
|
bindCustomListeners() {
|
|
243
272
|
this.customListeners.forEach(item => {
|
|
244
273
|
const requestedEventName = item.eventName
|
|
245
|
-
const typeOfListener = item.once ? 'once': 'on'
|
|
274
|
+
const typeOfListener = item.once ? 'once' : 'on'
|
|
246
275
|
requestedEventName && this._hls[`${typeOfListener}`](requestedEventName, item.callback)
|
|
247
276
|
})
|
|
248
277
|
}
|
|
@@ -318,6 +347,16 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
318
347
|
return this._duration
|
|
319
348
|
}
|
|
320
349
|
|
|
350
|
+
// the frame rate should be retrieved from the HLS.js level information
|
|
351
|
+
// as it is not necessarily exposed directly by the video element
|
|
352
|
+
getFrameRate() {
|
|
353
|
+
if (this._hls && this._hls.levels && this._hls.currentLevel >= 0) {
|
|
354
|
+
const level = this._hls.levels[this._hls.currentLevel]
|
|
355
|
+
return level && level.frameRate ? level.frameRate : null
|
|
356
|
+
}
|
|
357
|
+
return null
|
|
358
|
+
}
|
|
359
|
+
|
|
321
360
|
getCurrentTime() {
|
|
322
361
|
// e.g. can be < 0 if user pauses near the start
|
|
323
362
|
// eventually they will then be kicked to the end by hlsjs if they run out of buffer
|
|
@@ -333,15 +372,15 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
333
372
|
}
|
|
334
373
|
|
|
335
374
|
seekPercentage(percentage) {
|
|
336
|
-
const seekTo =
|
|
337
|
-
? this._duration * (percentage / 100)
|
|
338
|
-
: this._duration
|
|
375
|
+
const seekTo = percentage > 0 ? this._duration * (percentage / 100) : this._duration
|
|
339
376
|
this.seek(seekTo)
|
|
340
377
|
}
|
|
341
378
|
|
|
342
379
|
seek(time) {
|
|
343
380
|
if (time < 0) {
|
|
344
|
-
Log.warn(
|
|
381
|
+
Log.warn(
|
|
382
|
+
'Attempt to seek to a negative time. Resetting to live point. Use seekToLivePoint() to seek to the live point.'
|
|
383
|
+
)
|
|
345
384
|
time = this.getDuration()
|
|
346
385
|
}
|
|
347
386
|
time += this._startTime
|
|
@@ -353,12 +392,13 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
353
392
|
}
|
|
354
393
|
|
|
355
394
|
_updateSettings() {
|
|
356
|
-
if (this._playbackType === Playback.VOD)
|
|
395
|
+
if (this._playbackType === Playback.VOD) {
|
|
357
396
|
this.settings.left = ['playpause', 'position', 'duration']
|
|
358
|
-
else if (this.dvrEnabled)
|
|
397
|
+
} else if (this.dvrEnabled) {
|
|
359
398
|
this.settings.left = ['playpause']
|
|
360
|
-
else
|
|
399
|
+
} else {
|
|
361
400
|
this.settings.left = ['playstop']
|
|
401
|
+
}
|
|
362
402
|
|
|
363
403
|
this.settings.seekEnabled = this.isSeekEnabled()
|
|
364
404
|
this.trigger(Events.PLAYBACK_SETTINGSUPDATE)
|
|
@@ -368,7 +408,7 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
368
408
|
const error = {
|
|
369
409
|
code: `${data.type}_${data.details}`,
|
|
370
410
|
description: `${this.name} error: type: ${data.type}, details: ${data.details}`,
|
|
371
|
-
raw: data
|
|
411
|
+
raw: data
|
|
372
412
|
}
|
|
373
413
|
let formattedError
|
|
374
414
|
if (data.response) error.description += `, response: ${JSON.stringify(data.response)}`
|
|
@@ -378,42 +418,45 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
378
418
|
if (this._recoverAttemptsRemaining > 0) {
|
|
379
419
|
this._recoverAttemptsRemaining -= 1
|
|
380
420
|
switch (data.type) {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
421
|
+
case HLSJS.ErrorTypes.NETWORK_ERROR:
|
|
422
|
+
switch (data.details) {
|
|
423
|
+
// The following network errors cannot be recovered with HLS.startLoad()
|
|
424
|
+
// For more details, see https://github.com/video-dev/hls.js/blob/master/doc/design.md#error-detection-and-handling
|
|
425
|
+
// For "level load" fatal errors, see https://github.com/video-dev/hls.js/issues/1138
|
|
426
|
+
case HLSJS.ErrorDetails.MANIFEST_LOAD_ERROR:
|
|
427
|
+
case HLSJS.ErrorDetails.MANIFEST_LOAD_TIMEOUT:
|
|
428
|
+
case HLSJS.ErrorDetails.MANIFEST_PARSING_ERROR:
|
|
429
|
+
case HLSJS.ErrorDetails.LEVEL_LOAD_ERROR:
|
|
430
|
+
case HLSJS.ErrorDetails.LEVEL_LOAD_TIMEOUT:
|
|
431
|
+
Log.error('hlsjs: unrecoverable network fatal error.', { evt, data })
|
|
432
|
+
formattedError = this.createError(error)
|
|
433
|
+
this.trigger(Events.PLAYBACK_ERROR, formattedError)
|
|
434
|
+
this.stop()
|
|
435
|
+
break
|
|
436
|
+
default:
|
|
437
|
+
Log.warn('hlsjs: trying to recover from network error.', { evt, data })
|
|
438
|
+
error.level = PlayerError.Levels.WARN
|
|
439
|
+
this._hls.startLoad()
|
|
440
|
+
break
|
|
441
|
+
}
|
|
442
|
+
break
|
|
443
|
+
case HLSJS.ErrorTypes.MEDIA_ERROR:
|
|
444
|
+
Log.warn('hlsjs: trying to recover from media error.', { evt, data })
|
|
445
|
+
error.level = PlayerError.Levels.WARN
|
|
446
|
+
this._recover(evt, data, error)
|
|
447
|
+
break
|
|
448
|
+
default:
|
|
449
|
+
Log.error('hlsjs: could not recover from error.', { evt, data })
|
|
392
450
|
formattedError = this.createError(error)
|
|
393
451
|
this.trigger(Events.PLAYBACK_ERROR, formattedError)
|
|
394
452
|
this.stop()
|
|
395
453
|
break
|
|
396
|
-
default:
|
|
397
|
-
Log.warn('hlsjs: trying to recover from network error.', { evt, data })
|
|
398
|
-
error.level = PlayerError.Levels.WARN
|
|
399
|
-
this._hls.startLoad()
|
|
400
|
-
break
|
|
401
|
-
}
|
|
402
|
-
break
|
|
403
|
-
case HLSJS.ErrorTypes.MEDIA_ERROR:
|
|
404
|
-
Log.warn('hlsjs: trying to recover from media error.', { evt, data })
|
|
405
|
-
error.level = PlayerError.Levels.WARN
|
|
406
|
-
this._recover(evt, data, error)
|
|
407
|
-
break
|
|
408
|
-
default:
|
|
409
|
-
Log.error('hlsjs: could not recover from error.', { evt, data })
|
|
410
|
-
formattedError = this.createError(error)
|
|
411
|
-
this.trigger(Events.PLAYBACK_ERROR, formattedError)
|
|
412
|
-
this.stop()
|
|
413
|
-
break
|
|
414
454
|
}
|
|
415
455
|
} else {
|
|
416
|
-
Log.error('hlsjs: could not recover from error after maximum number of attempts.', {
|
|
456
|
+
Log.error('hlsjs: could not recover from error after maximum number of attempts.', {
|
|
457
|
+
evt,
|
|
458
|
+
data
|
|
459
|
+
})
|
|
417
460
|
formattedError = this.createError(error)
|
|
418
461
|
this.trigger(Events.PLAYBACK_ERROR, formattedError)
|
|
419
462
|
this.stop()
|
|
@@ -437,14 +480,20 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
437
480
|
}
|
|
438
481
|
|
|
439
482
|
_keyIsDenied(data) {
|
|
440
|
-
return
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
483
|
+
return (
|
|
484
|
+
data.type === HLSJS.ErrorTypes.NETWORK_ERROR &&
|
|
485
|
+
data.details === HLSJS.ErrorDetails.KEY_LOAD_ERROR &&
|
|
486
|
+
data.response &&
|
|
487
|
+
data.response.code >= 400
|
|
488
|
+
)
|
|
444
489
|
}
|
|
445
490
|
|
|
446
491
|
_onTimeUpdate() {
|
|
447
|
-
const update = {
|
|
492
|
+
const update = {
|
|
493
|
+
current: this.getCurrentTime(),
|
|
494
|
+
total: this.getDuration(),
|
|
495
|
+
firstFragDateTime: this.getProgramDateTime()
|
|
496
|
+
}
|
|
448
497
|
const shouldThrottle = this._shouldThrottleTimeUpdate(update)
|
|
449
498
|
if (shouldThrottle) return
|
|
450
499
|
this._lastTimeUpdate = update
|
|
@@ -453,9 +502,12 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
453
502
|
}
|
|
454
503
|
|
|
455
504
|
_shouldThrottleTimeUpdate(update) {
|
|
456
|
-
const isSameTime =
|
|
457
|
-
|
|
458
|
-
const
|
|
505
|
+
const isSameTime =
|
|
506
|
+
Math.abs(update.current - this._lastTimeUpdate.current) < this._timeUpdateFiringRate
|
|
507
|
+
const isSameDuration =
|
|
508
|
+
Math.abs(update.total - this._lastTimeUpdate.total) < this._durationChangeMinOffset
|
|
509
|
+
const isSameFirstFragDateTime =
|
|
510
|
+
update.firstFragDateTime === this._lastTimeUpdate.firstFragDateTime
|
|
459
511
|
const isSameEventPayload = isSameTime && isSameDuration && isSameFirstFragDateTime
|
|
460
512
|
const isThrottled = this._now - this._lastTimeUpdateFiredTime < this._timeUpdateThrottleDelay
|
|
461
513
|
|
|
@@ -475,14 +527,17 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
475
527
|
let buffered = []
|
|
476
528
|
let bufferedPos = 0
|
|
477
529
|
for (let i = 0; i < this.el.buffered.length; i++) {
|
|
478
|
-
buffered = [
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
530
|
+
buffered = [
|
|
531
|
+
...buffered,
|
|
532
|
+
{
|
|
533
|
+
// for a stream with sliding window dvr something that is buffered my slide off the start of the timeline
|
|
534
|
+
start: Math.max(0, this.el.buffered.start(i) - this._playableRegionStartTime),
|
|
535
|
+
end: Math.max(0, this.el.buffered.end(i) - this._playableRegionStartTime)
|
|
536
|
+
}
|
|
537
|
+
]
|
|
538
|
+
if (this.el.currentTime >= buffered[i].start && this.el.currentTime <= buffered[i].end) {
|
|
484
539
|
bufferedPos = i
|
|
485
|
-
|
|
540
|
+
}
|
|
486
541
|
}
|
|
487
542
|
const progress = {
|
|
488
543
|
start: buffered[bufferedPos].start,
|
|
@@ -492,7 +547,7 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
492
547
|
this.trigger(Events.PLAYBACK_PROGRESS, progress, buffered)
|
|
493
548
|
}
|
|
494
549
|
|
|
495
|
-
load(url) {
|
|
550
|
+
load(url) {
|
|
496
551
|
this._stopTimeUpdateTimer()
|
|
497
552
|
this.options.src = url
|
|
498
553
|
this._setup()
|
|
@@ -500,7 +555,9 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
500
555
|
|
|
501
556
|
play() {
|
|
502
557
|
!this._hls && this._setup()
|
|
503
|
-
!this._manifestLoading &&
|
|
558
|
+
!this._manifestLoading &&
|
|
559
|
+
!this.options.hlsPlayback.preload &&
|
|
560
|
+
this._hls.loadSource(this.options.src)
|
|
504
561
|
super.play()
|
|
505
562
|
this._startTimeUpdateTimer()
|
|
506
563
|
}
|
|
@@ -526,14 +583,18 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
526
583
|
this._playbackType = data.details.live ? Playback.LIVE : Playback.VOD
|
|
527
584
|
this._onLevelUpdated(evt, data)
|
|
528
585
|
// Live stream subtitle tracks detection hack (may not immediately available)
|
|
529
|
-
if (
|
|
586
|
+
if (
|
|
587
|
+
this._ccTracksUpdated &&
|
|
588
|
+
this._playbackType === Playback.LIVE &&
|
|
589
|
+
this.hasClosedCaptionsTracks
|
|
590
|
+
) {
|
|
530
591
|
this._onSubtitleLoaded()
|
|
531
|
-
|
|
592
|
+
}
|
|
532
593
|
}
|
|
533
594
|
|
|
534
595
|
_fillLevels() {
|
|
535
596
|
this._levels = this._hls.levels.map((level, index) => {
|
|
536
|
-
return { id: index, level: level, label: `${level.bitrate/1000}Kbps` }
|
|
597
|
+
return { id: index, level: level, label: `${level.bitrate / 1000}Kbps` }
|
|
537
598
|
})
|
|
538
599
|
this.trigger(Events.PLAYBACK_LEVELS_AVAILABLE, this._levels)
|
|
539
600
|
}
|
|
@@ -543,13 +604,14 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
543
604
|
this._playlistType = data.details.type || null
|
|
544
605
|
let startTimeChanged = false
|
|
545
606
|
let durationChanged = false
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
607
|
+
const fragments = data.details.fragments
|
|
608
|
+
const previousPlayableRegionStartTime = this._playableRegionStartTime
|
|
609
|
+
const previousPlayableRegionDuration = this._playableRegionDuration
|
|
549
610
|
if (fragments.length === 0) return
|
|
550
611
|
// #EXT-X-PROGRAM-DATE-TIME
|
|
551
|
-
if (fragments[0].rawProgramDateTime)
|
|
612
|
+
if (fragments[0].rawProgramDateTime) {
|
|
552
613
|
this._programDateTime = fragments[0].rawProgramDateTime
|
|
614
|
+
}
|
|
553
615
|
if (this._playableRegionStartTime !== fragments[0].start) {
|
|
554
616
|
startTimeChanged = true
|
|
555
617
|
this._playableRegionStartTime = fragments[0].start
|
|
@@ -560,14 +622,14 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
560
622
|
// set the correlation to map to middle of the extrapolation window
|
|
561
623
|
this._localStartTimeCorrelation = {
|
|
562
624
|
local: this._now,
|
|
563
|
-
remote: (fragments[0].start +
|
|
625
|
+
remote: (fragments[0].start + this._extrapolatedWindowDuration / 2) * 1000
|
|
564
626
|
}
|
|
565
627
|
} else {
|
|
566
628
|
// check if the correlation still works
|
|
567
|
-
|
|
568
|
-
|
|
629
|
+
const corr = this._localStartTimeCorrelation
|
|
630
|
+
const timePassed = this._now - corr.local
|
|
569
631
|
// this should point to a time within the extrapolation window
|
|
570
|
-
|
|
632
|
+
const startTime = (corr.remote + timePassed) / 1000
|
|
571
633
|
if (startTime < fragments[0].start) {
|
|
572
634
|
// our start time is now earlier than the first chunk
|
|
573
635
|
// (maybe the chunk was removed early)
|
|
@@ -583,26 +645,32 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
583
645
|
// update the correlation so that the time starts counting again from the value it's on now
|
|
584
646
|
this._localStartTimeCorrelation = {
|
|
585
647
|
local: this._now,
|
|
586
|
-
remote:
|
|
648
|
+
remote:
|
|
649
|
+
Math.max(
|
|
650
|
+
fragments[0].start,
|
|
651
|
+
previousPlayableRegionStartTime + this._extrapolatedWindowDuration
|
|
652
|
+
) * 1000
|
|
587
653
|
}
|
|
588
654
|
}
|
|
589
655
|
}
|
|
590
656
|
}
|
|
591
|
-
|
|
657
|
+
|
|
592
658
|
let newDuration = data.details.totalduration
|
|
593
659
|
// if it's a live stream then shorten the duration to remove access
|
|
594
660
|
// to the area after hlsjs's live sync point
|
|
595
661
|
// seeks to areas after this point sometimes have issues
|
|
596
662
|
if (this._playbackType === Playback.LIVE) {
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
663
|
+
const fragmentTargetDuration = data.details.targetduration
|
|
664
|
+
const hlsjsConfig = this.options.playback.hlsjsConfig || {}
|
|
665
|
+
const liveSyncDurationCount =
|
|
666
|
+
hlsjsConfig.liveSyncDurationCount || HLSJS.DefaultConfig.liveSyncDurationCount
|
|
667
|
+
const hiddenAreaDuration = fragmentTargetDuration * liveSyncDurationCount
|
|
601
668
|
if (hiddenAreaDuration <= newDuration) {
|
|
602
669
|
newDuration -= hiddenAreaDuration
|
|
603
670
|
this._durationExcludesAfterLiveSyncPoint = true
|
|
604
|
-
} else {
|
|
605
|
-
|
|
671
|
+
} else {
|
|
672
|
+
this._durationExcludesAfterLiveSyncPoint = false
|
|
673
|
+
}
|
|
606
674
|
}
|
|
607
675
|
if (newDuration !== this._playableRegionDuration) {
|
|
608
676
|
durationChanged = true
|
|
@@ -610,9 +678,9 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
610
678
|
}
|
|
611
679
|
// Note the end time is not the playableRegionDuration
|
|
612
680
|
// The end time will always increase even if content is removed from the beginning
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
681
|
+
const endTime = fragments[0].start + newDuration
|
|
682
|
+
const previousEndTime = previousPlayableRegionStartTime + previousPlayableRegionDuration
|
|
683
|
+
const endTimeChanged = endTime !== previousEndTime
|
|
616
684
|
if (endTimeChanged) {
|
|
617
685
|
if (!this._localEndTimeCorrelation) {
|
|
618
686
|
// set the correlation to map to the end
|
|
@@ -622,10 +690,10 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
622
690
|
}
|
|
623
691
|
} else {
|
|
624
692
|
// check if the correlation still works
|
|
625
|
-
|
|
626
|
-
|
|
693
|
+
const corr = this._localEndTimeCorrelation
|
|
694
|
+
const timePassed = this._now - corr.local
|
|
627
695
|
// this should point to a time within the extrapolation window from the end
|
|
628
|
-
|
|
696
|
+
const extrapolatedEndTime = (corr.remote + timePassed) / 1000
|
|
629
697
|
if (extrapolatedEndTime > endTime) {
|
|
630
698
|
this._localEndTimeCorrelation = {
|
|
631
699
|
local: this._now,
|
|
@@ -684,10 +752,10 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
684
752
|
if (!this.levels.length) this._fillLevels()
|
|
685
753
|
this.trigger(Events.PLAYBACK_LEVEL_SWITCH_END)
|
|
686
754
|
this.trigger(Events.PLAYBACK_LEVEL_SWITCH, data)
|
|
687
|
-
|
|
755
|
+
const currentLevel = this._hls.levels[data.level]
|
|
688
756
|
if (currentLevel) {
|
|
689
757
|
// TODO should highDefinition be private and maybe have a read only accessor if it's used somewhere
|
|
690
|
-
this.highDefinition =
|
|
758
|
+
this.highDefinition = currentLevel.height >= 720 || currentLevel.bitrate / 1000 >= 2000
|
|
691
759
|
this.trigger(Events.PLAYBACK_HIGHDEFINITIONUPDATE, this.highDefinition)
|
|
692
760
|
this.trigger(Events.PLAYBACK_BITRATE, {
|
|
693
761
|
height: currentLevel.height,
|
|
@@ -704,7 +772,11 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
704
772
|
// - the duration does not include content after hlsjs's live sync point
|
|
705
773
|
// - the playable region duration is longer than the configured duration to enable dvr after
|
|
706
774
|
// - the playback type is LIVE.
|
|
707
|
-
return (
|
|
775
|
+
return (
|
|
776
|
+
this._durationExcludesAfterLiveSyncPoint &&
|
|
777
|
+
this._duration >= this._minDvrSize &&
|
|
778
|
+
this.getPlaybackType() === Playback.LIVE
|
|
779
|
+
)
|
|
708
780
|
}
|
|
709
781
|
|
|
710
782
|
getPlaybackType() {
|
|
@@ -712,12 +784,14 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
712
784
|
}
|
|
713
785
|
|
|
714
786
|
isSeekEnabled() {
|
|
715
|
-
return
|
|
787
|
+
return this._playbackType === Playback.VOD || this.dvrEnabled
|
|
716
788
|
}
|
|
717
789
|
}
|
|
718
790
|
|
|
719
|
-
HlsjsPlayback.canPlay = function(resource, mimeType) {
|
|
791
|
+
HlsjsPlayback.canPlay = function (resource, mimeType) {
|
|
720
792
|
const resourceParts = resource.split('?')[0].match(/.*\.(.*)$/) || []
|
|
721
|
-
const isHls =
|
|
793
|
+
const isHls =
|
|
794
|
+
(resourceParts.length > 1 && resourceParts[1].toLowerCase() === 'm3u8') ||
|
|
795
|
+
listContainsIgnoreCase(mimeType, ['application/vnd.apple.mpegurl', 'application/x-mpegURL'])
|
|
722
796
|
return !!(HLSJS.isSupported() && isHls)
|
|
723
797
|
}
|