@clappr/hlsjs-playback 1.8.3 → 1.8.5
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 +37 -11
- package/dist/hlsjs-playback.external.js +37 -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 +37 -11
- package/dist/hlsjs-playback.min.js +1 -1
- package/dist/hlsjs-playback.min.js.map +1 -1
- package/package.json +3 -3
- package/src/hls.js +178 -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.8.
|
|
3
|
+
"version": "1.8.5",
|
|
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",
|
|
@@ -75,5 +75,5 @@
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
},
|
|
78
|
-
"gitHead": "
|
|
78
|
+
"gitHead": "893e3ad46908fdee89812396e66546f06d35fb09"
|
|
79
79
|
}
|
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
|
}
|
|
@@ -333,15 +362,15 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
333
362
|
}
|
|
334
363
|
|
|
335
364
|
seekPercentage(percentage) {
|
|
336
|
-
const seekTo =
|
|
337
|
-
? this._duration * (percentage / 100)
|
|
338
|
-
: this._duration
|
|
365
|
+
const seekTo = percentage > 0 ? this._duration * (percentage / 100) : this._duration
|
|
339
366
|
this.seek(seekTo)
|
|
340
367
|
}
|
|
341
368
|
|
|
342
369
|
seek(time) {
|
|
343
370
|
if (time < 0) {
|
|
344
|
-
Log.warn(
|
|
371
|
+
Log.warn(
|
|
372
|
+
'Attempt to seek to a negative time. Resetting to live point. Use seekToLivePoint() to seek to the live point.'
|
|
373
|
+
)
|
|
345
374
|
time = this.getDuration()
|
|
346
375
|
}
|
|
347
376
|
time += this._startTime
|
|
@@ -353,12 +382,13 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
353
382
|
}
|
|
354
383
|
|
|
355
384
|
_updateSettings() {
|
|
356
|
-
if (this._playbackType === Playback.VOD)
|
|
385
|
+
if (this._playbackType === Playback.VOD) {
|
|
357
386
|
this.settings.left = ['playpause', 'position', 'duration']
|
|
358
|
-
else if (this.dvrEnabled)
|
|
387
|
+
} else if (this.dvrEnabled) {
|
|
359
388
|
this.settings.left = ['playpause']
|
|
360
|
-
else
|
|
389
|
+
} else {
|
|
361
390
|
this.settings.left = ['playstop']
|
|
391
|
+
}
|
|
362
392
|
|
|
363
393
|
this.settings.seekEnabled = this.isSeekEnabled()
|
|
364
394
|
this.trigger(Events.PLAYBACK_SETTINGSUPDATE)
|
|
@@ -368,7 +398,7 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
368
398
|
const error = {
|
|
369
399
|
code: `${data.type}_${data.details}`,
|
|
370
400
|
description: `${this.name} error: type: ${data.type}, details: ${data.details}`,
|
|
371
|
-
raw: data
|
|
401
|
+
raw: data
|
|
372
402
|
}
|
|
373
403
|
let formattedError
|
|
374
404
|
if (data.response) error.description += `, response: ${JSON.stringify(data.response)}`
|
|
@@ -378,42 +408,45 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
378
408
|
if (this._recoverAttemptsRemaining > 0) {
|
|
379
409
|
this._recoverAttemptsRemaining -= 1
|
|
380
410
|
switch (data.type) {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
411
|
+
case HLSJS.ErrorTypes.NETWORK_ERROR:
|
|
412
|
+
switch (data.details) {
|
|
413
|
+
// The following network errors cannot be recovered with HLS.startLoad()
|
|
414
|
+
// For more details, see https://github.com/video-dev/hls.js/blob/master/doc/design.md#error-detection-and-handling
|
|
415
|
+
// For "level load" fatal errors, see https://github.com/video-dev/hls.js/issues/1138
|
|
416
|
+
case HLSJS.ErrorDetails.MANIFEST_LOAD_ERROR:
|
|
417
|
+
case HLSJS.ErrorDetails.MANIFEST_LOAD_TIMEOUT:
|
|
418
|
+
case HLSJS.ErrorDetails.MANIFEST_PARSING_ERROR:
|
|
419
|
+
case HLSJS.ErrorDetails.LEVEL_LOAD_ERROR:
|
|
420
|
+
case HLSJS.ErrorDetails.LEVEL_LOAD_TIMEOUT:
|
|
421
|
+
Log.error('hlsjs: unrecoverable network fatal error.', { evt, data })
|
|
422
|
+
formattedError = this.createError(error)
|
|
423
|
+
this.trigger(Events.PLAYBACK_ERROR, formattedError)
|
|
424
|
+
this.stop()
|
|
425
|
+
break
|
|
426
|
+
default:
|
|
427
|
+
Log.warn('hlsjs: trying to recover from network error.', { evt, data })
|
|
428
|
+
error.level = PlayerError.Levels.WARN
|
|
429
|
+
this._hls.startLoad()
|
|
430
|
+
break
|
|
431
|
+
}
|
|
432
|
+
break
|
|
433
|
+
case HLSJS.ErrorTypes.MEDIA_ERROR:
|
|
434
|
+
Log.warn('hlsjs: trying to recover from media error.', { evt, data })
|
|
435
|
+
error.level = PlayerError.Levels.WARN
|
|
436
|
+
this._recover(evt, data, error)
|
|
437
|
+
break
|
|
438
|
+
default:
|
|
439
|
+
Log.error('hlsjs: could not recover from error.', { evt, data })
|
|
392
440
|
formattedError = this.createError(error)
|
|
393
441
|
this.trigger(Events.PLAYBACK_ERROR, formattedError)
|
|
394
442
|
this.stop()
|
|
395
443
|
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
444
|
}
|
|
415
445
|
} else {
|
|
416
|
-
Log.error('hlsjs: could not recover from error after maximum number of attempts.', {
|
|
446
|
+
Log.error('hlsjs: could not recover from error after maximum number of attempts.', {
|
|
447
|
+
evt,
|
|
448
|
+
data
|
|
449
|
+
})
|
|
417
450
|
formattedError = this.createError(error)
|
|
418
451
|
this.trigger(Events.PLAYBACK_ERROR, formattedError)
|
|
419
452
|
this.stop()
|
|
@@ -437,14 +470,20 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
437
470
|
}
|
|
438
471
|
|
|
439
472
|
_keyIsDenied(data) {
|
|
440
|
-
return
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
473
|
+
return (
|
|
474
|
+
data.type === HLSJS.ErrorTypes.NETWORK_ERROR &&
|
|
475
|
+
data.details === HLSJS.ErrorDetails.KEY_LOAD_ERROR &&
|
|
476
|
+
data.response &&
|
|
477
|
+
data.response.code >= 400
|
|
478
|
+
)
|
|
444
479
|
}
|
|
445
480
|
|
|
446
481
|
_onTimeUpdate() {
|
|
447
|
-
const update = {
|
|
482
|
+
const update = {
|
|
483
|
+
current: this.getCurrentTime(),
|
|
484
|
+
total: this.getDuration(),
|
|
485
|
+
firstFragDateTime: this.getProgramDateTime()
|
|
486
|
+
}
|
|
448
487
|
const shouldThrottle = this._shouldThrottleTimeUpdate(update)
|
|
449
488
|
if (shouldThrottle) return
|
|
450
489
|
this._lastTimeUpdate = update
|
|
@@ -453,9 +492,12 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
453
492
|
}
|
|
454
493
|
|
|
455
494
|
_shouldThrottleTimeUpdate(update) {
|
|
456
|
-
const isSameTime =
|
|
457
|
-
|
|
458
|
-
const
|
|
495
|
+
const isSameTime =
|
|
496
|
+
Math.abs(update.current - this._lastTimeUpdate.current) < this._timeUpdateFiringRate
|
|
497
|
+
const isSameDuration =
|
|
498
|
+
Math.abs(update.total - this._lastTimeUpdate.total) < this._durationChangeMinOffset
|
|
499
|
+
const isSameFirstFragDateTime =
|
|
500
|
+
update.firstFragDateTime === this._lastTimeUpdate.firstFragDateTime
|
|
459
501
|
const isSameEventPayload = isSameTime && isSameDuration && isSameFirstFragDateTime
|
|
460
502
|
const isThrottled = this._now - this._lastTimeUpdateFiredTime < this._timeUpdateThrottleDelay
|
|
461
503
|
|
|
@@ -475,14 +517,17 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
475
517
|
let buffered = []
|
|
476
518
|
let bufferedPos = 0
|
|
477
519
|
for (let i = 0; i < this.el.buffered.length; i++) {
|
|
478
|
-
buffered = [
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
520
|
+
buffered = [
|
|
521
|
+
...buffered,
|
|
522
|
+
{
|
|
523
|
+
// for a stream with sliding window dvr something that is buffered my slide off the start of the timeline
|
|
524
|
+
start: Math.max(0, this.el.buffered.start(i) - this._playableRegionStartTime),
|
|
525
|
+
end: Math.max(0, this.el.buffered.end(i) - this._playableRegionStartTime)
|
|
526
|
+
}
|
|
527
|
+
]
|
|
528
|
+
if (this.el.currentTime >= buffered[i].start && this.el.currentTime <= buffered[i].end) {
|
|
484
529
|
bufferedPos = i
|
|
485
|
-
|
|
530
|
+
}
|
|
486
531
|
}
|
|
487
532
|
const progress = {
|
|
488
533
|
start: buffered[bufferedPos].start,
|
|
@@ -492,7 +537,7 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
492
537
|
this.trigger(Events.PLAYBACK_PROGRESS, progress, buffered)
|
|
493
538
|
}
|
|
494
539
|
|
|
495
|
-
load(url) {
|
|
540
|
+
load(url) {
|
|
496
541
|
this._stopTimeUpdateTimer()
|
|
497
542
|
this.options.src = url
|
|
498
543
|
this._setup()
|
|
@@ -500,7 +545,9 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
500
545
|
|
|
501
546
|
play() {
|
|
502
547
|
!this._hls && this._setup()
|
|
503
|
-
!this._manifestLoading &&
|
|
548
|
+
!this._manifestLoading &&
|
|
549
|
+
!this.options.hlsPlayback.preload &&
|
|
550
|
+
this._hls.loadSource(this.options.src)
|
|
504
551
|
super.play()
|
|
505
552
|
this._startTimeUpdateTimer()
|
|
506
553
|
}
|
|
@@ -526,14 +573,18 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
526
573
|
this._playbackType = data.details.live ? Playback.LIVE : Playback.VOD
|
|
527
574
|
this._onLevelUpdated(evt, data)
|
|
528
575
|
// Live stream subtitle tracks detection hack (may not immediately available)
|
|
529
|
-
if (
|
|
576
|
+
if (
|
|
577
|
+
this._ccTracksUpdated &&
|
|
578
|
+
this._playbackType === Playback.LIVE &&
|
|
579
|
+
this.hasClosedCaptionsTracks
|
|
580
|
+
) {
|
|
530
581
|
this._onSubtitleLoaded()
|
|
531
|
-
|
|
582
|
+
}
|
|
532
583
|
}
|
|
533
584
|
|
|
534
585
|
_fillLevels() {
|
|
535
586
|
this._levels = this._hls.levels.map((level, index) => {
|
|
536
|
-
return { id: index, level: level, label: `${level.bitrate/1000}Kbps` }
|
|
587
|
+
return { id: index, level: level, label: `${level.bitrate / 1000}Kbps` }
|
|
537
588
|
})
|
|
538
589
|
this.trigger(Events.PLAYBACK_LEVELS_AVAILABLE, this._levels)
|
|
539
590
|
}
|
|
@@ -543,13 +594,14 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
543
594
|
this._playlistType = data.details.type || null
|
|
544
595
|
let startTimeChanged = false
|
|
545
596
|
let durationChanged = false
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
597
|
+
const fragments = data.details.fragments
|
|
598
|
+
const previousPlayableRegionStartTime = this._playableRegionStartTime
|
|
599
|
+
const previousPlayableRegionDuration = this._playableRegionDuration
|
|
549
600
|
if (fragments.length === 0) return
|
|
550
601
|
// #EXT-X-PROGRAM-DATE-TIME
|
|
551
|
-
if (fragments[0].rawProgramDateTime)
|
|
602
|
+
if (fragments[0].rawProgramDateTime) {
|
|
552
603
|
this._programDateTime = fragments[0].rawProgramDateTime
|
|
604
|
+
}
|
|
553
605
|
if (this._playableRegionStartTime !== fragments[0].start) {
|
|
554
606
|
startTimeChanged = true
|
|
555
607
|
this._playableRegionStartTime = fragments[0].start
|
|
@@ -560,14 +612,14 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
560
612
|
// set the correlation to map to middle of the extrapolation window
|
|
561
613
|
this._localStartTimeCorrelation = {
|
|
562
614
|
local: this._now,
|
|
563
|
-
remote: (fragments[0].start +
|
|
615
|
+
remote: (fragments[0].start + this._extrapolatedWindowDuration / 2) * 1000
|
|
564
616
|
}
|
|
565
617
|
} else {
|
|
566
618
|
// check if the correlation still works
|
|
567
|
-
|
|
568
|
-
|
|
619
|
+
const corr = this._localStartTimeCorrelation
|
|
620
|
+
const timePassed = this._now - corr.local
|
|
569
621
|
// this should point to a time within the extrapolation window
|
|
570
|
-
|
|
622
|
+
const startTime = (corr.remote + timePassed) / 1000
|
|
571
623
|
if (startTime < fragments[0].start) {
|
|
572
624
|
// our start time is now earlier than the first chunk
|
|
573
625
|
// (maybe the chunk was removed early)
|
|
@@ -583,26 +635,32 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
583
635
|
// update the correlation so that the time starts counting again from the value it's on now
|
|
584
636
|
this._localStartTimeCorrelation = {
|
|
585
637
|
local: this._now,
|
|
586
|
-
remote:
|
|
638
|
+
remote:
|
|
639
|
+
Math.max(
|
|
640
|
+
fragments[0].start,
|
|
641
|
+
previousPlayableRegionStartTime + this._extrapolatedWindowDuration
|
|
642
|
+
) * 1000
|
|
587
643
|
}
|
|
588
644
|
}
|
|
589
645
|
}
|
|
590
646
|
}
|
|
591
|
-
|
|
647
|
+
|
|
592
648
|
let newDuration = data.details.totalduration
|
|
593
649
|
// if it's a live stream then shorten the duration to remove access
|
|
594
650
|
// to the area after hlsjs's live sync point
|
|
595
651
|
// seeks to areas after this point sometimes have issues
|
|
596
652
|
if (this._playbackType === Playback.LIVE) {
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
653
|
+
const fragmentTargetDuration = data.details.targetduration
|
|
654
|
+
const hlsjsConfig = this.options.playback.hlsjsConfig || {}
|
|
655
|
+
const liveSyncDurationCount =
|
|
656
|
+
hlsjsConfig.liveSyncDurationCount || HLSJS.DefaultConfig.liveSyncDurationCount
|
|
657
|
+
const hiddenAreaDuration = fragmentTargetDuration * liveSyncDurationCount
|
|
601
658
|
if (hiddenAreaDuration <= newDuration) {
|
|
602
659
|
newDuration -= hiddenAreaDuration
|
|
603
660
|
this._durationExcludesAfterLiveSyncPoint = true
|
|
604
|
-
} else {
|
|
605
|
-
|
|
661
|
+
} else {
|
|
662
|
+
this._durationExcludesAfterLiveSyncPoint = false
|
|
663
|
+
}
|
|
606
664
|
}
|
|
607
665
|
if (newDuration !== this._playableRegionDuration) {
|
|
608
666
|
durationChanged = true
|
|
@@ -610,9 +668,9 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
610
668
|
}
|
|
611
669
|
// Note the end time is not the playableRegionDuration
|
|
612
670
|
// The end time will always increase even if content is removed from the beginning
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
671
|
+
const endTime = fragments[0].start + newDuration
|
|
672
|
+
const previousEndTime = previousPlayableRegionStartTime + previousPlayableRegionDuration
|
|
673
|
+
const endTimeChanged = endTime !== previousEndTime
|
|
616
674
|
if (endTimeChanged) {
|
|
617
675
|
if (!this._localEndTimeCorrelation) {
|
|
618
676
|
// set the correlation to map to the end
|
|
@@ -622,10 +680,10 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
622
680
|
}
|
|
623
681
|
} else {
|
|
624
682
|
// check if the correlation still works
|
|
625
|
-
|
|
626
|
-
|
|
683
|
+
const corr = this._localEndTimeCorrelation
|
|
684
|
+
const timePassed = this._now - corr.local
|
|
627
685
|
// this should point to a time within the extrapolation window from the end
|
|
628
|
-
|
|
686
|
+
const extrapolatedEndTime = (corr.remote + timePassed) / 1000
|
|
629
687
|
if (extrapolatedEndTime > endTime) {
|
|
630
688
|
this._localEndTimeCorrelation = {
|
|
631
689
|
local: this._now,
|
|
@@ -684,10 +742,10 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
684
742
|
if (!this.levels.length) this._fillLevels()
|
|
685
743
|
this.trigger(Events.PLAYBACK_LEVEL_SWITCH_END)
|
|
686
744
|
this.trigger(Events.PLAYBACK_LEVEL_SWITCH, data)
|
|
687
|
-
|
|
745
|
+
const currentLevel = this._hls.levels[data.level]
|
|
688
746
|
if (currentLevel) {
|
|
689
747
|
// TODO should highDefinition be private and maybe have a read only accessor if it's used somewhere
|
|
690
|
-
this.highDefinition =
|
|
748
|
+
this.highDefinition = currentLevel.height >= 720 || currentLevel.bitrate / 1000 >= 2000
|
|
691
749
|
this.trigger(Events.PLAYBACK_HIGHDEFINITIONUPDATE, this.highDefinition)
|
|
692
750
|
this.trigger(Events.PLAYBACK_BITRATE, {
|
|
693
751
|
height: currentLevel.height,
|
|
@@ -704,7 +762,11 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
704
762
|
// - the duration does not include content after hlsjs's live sync point
|
|
705
763
|
// - the playable region duration is longer than the configured duration to enable dvr after
|
|
706
764
|
// - the playback type is LIVE.
|
|
707
|
-
return (
|
|
765
|
+
return (
|
|
766
|
+
this._durationExcludesAfterLiveSyncPoint &&
|
|
767
|
+
this._duration >= this._minDvrSize &&
|
|
768
|
+
this.getPlaybackType() === Playback.LIVE
|
|
769
|
+
)
|
|
708
770
|
}
|
|
709
771
|
|
|
710
772
|
getPlaybackType() {
|
|
@@ -712,12 +774,14 @@ export default class HlsjsPlayback extends HTML5Video {
|
|
|
712
774
|
}
|
|
713
775
|
|
|
714
776
|
isSeekEnabled() {
|
|
715
|
-
return
|
|
777
|
+
return this._playbackType === Playback.VOD || this.dvrEnabled
|
|
716
778
|
}
|
|
717
779
|
}
|
|
718
780
|
|
|
719
|
-
HlsjsPlayback.canPlay = function(resource, mimeType) {
|
|
781
|
+
HlsjsPlayback.canPlay = function (resource, mimeType) {
|
|
720
782
|
const resourceParts = resource.split('?')[0].match(/.*\.(.*)$/) || []
|
|
721
|
-
const isHls =
|
|
783
|
+
const isHls =
|
|
784
|
+
(resourceParts.length > 1 && resourceParts[1].toLowerCase() === 'm3u8') ||
|
|
785
|
+
listContainsIgnoreCase(mimeType, ['application/vnd.apple.mpegurl', 'application/x-mpegURL'])
|
|
722
786
|
return !!(HLSJS.isSupported() && isHls)
|
|
723
787
|
}
|