@gcorevideo/player 2.9.0 → 2.12.1
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/LICENSE +13 -0
- package/README.md +39 -0
- package/api-extractor.json +454 -0
- package/coverage/clover.xml +6 -0
- package/coverage/coverage-final.json +1 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +101 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +196 -0
- package/coverage/lcov.info +0 -0
- package/dist/index.js +4344 -1602
- package/dist/player.d.ts +407 -0
- package/docs/api/index.md +31 -0
- package/docs/api/player.dashsettings.md +16 -0
- package/docs/api/player.langtag.md +15 -0
- package/docs/api/player.md +295 -0
- package/docs/api/player.mediatransport.md +15 -0
- package/docs/api/player.playbackmodule.md +15 -0
- package/docs/api/player.playbacktype.md +16 -0
- package/docs/api/player.player._constructor_.md +50 -0
- package/docs/api/player.player.attachto.md +56 -0
- package/docs/api/player.player.configure.md +58 -0
- package/docs/api/player.player.destroy.md +20 -0
- package/docs/api/player.player.getcurrenttime.md +22 -0
- package/docs/api/player.player.getduration.md +22 -0
- package/docs/api/player.player.md +304 -0
- package/docs/api/player.player.mute.md +20 -0
- package/docs/api/player.player.off.md +72 -0
- package/docs/api/player.player.on.md +72 -0
- package/docs/api/player.player.pause.md +20 -0
- package/docs/api/player.player.play.md +20 -0
- package/docs/api/player.player.registerplugin.md +56 -0
- package/docs/api/player.player.resize.md +59 -0
- package/docs/api/player.player.seek.md +56 -0
- package/docs/api/player.player.stop.md +20 -0
- package/docs/api/player.player.unmute.md +20 -0
- package/docs/api/player.player.unregisterplugin.md +56 -0
- package/docs/api/player.playerconfig.autoplay.md +16 -0
- package/docs/api/player.playerconfig.dash.md +16 -0
- package/docs/api/player.playerconfig.debug.md +16 -0
- package/docs/api/player.playerconfig.language.md +16 -0
- package/docs/api/player.playerconfig.loop.md +16 -0
- package/docs/api/player.playerconfig.md +266 -0
- package/docs/api/player.playerconfig.mute.md +16 -0
- package/docs/api/player.playerconfig.playbacktype.md +16 -0
- package/docs/api/player.playerconfig.prioritytransport.md +16 -0
- package/docs/api/player.playerconfig.sources.md +16 -0
- package/docs/api/player.playerconfig.strings.md +16 -0
- package/docs/api/player.playerdebugsettings.md +20 -0
- package/docs/api/player.playerdebugtag.md +15 -0
- package/docs/api/player.playerevent.md +116 -0
- package/docs/api/player.playereventhandler.md +17 -0
- package/docs/api/player.playermediasource.md +18 -0
- package/docs/api/player.playermediasourcedesc.md +83 -0
- package/docs/api/player.playermediasourcedesc.mimetype.md +16 -0
- package/docs/api/player.playermediasourcedesc.source.md +16 -0
- package/docs/api/player.playerplugin.md +17 -0
- package/docs/api/player.qualitylevel.md +20 -0
- package/docs/api/player.translationkey.md +15 -0
- package/docs/api/player.translationsettings.md +35 -0
- package/docs/api/player.transportpreference.md +17 -0
- package/docs/api/player.version.md +27 -0
- package/lib/Player.d.ts +86 -7
- package/lib/Player.d.ts.map +1 -1
- package/lib/Player.js +132 -45
- package/lib/constants.d.ts +0 -18
- package/lib/constants.d.ts.map +1 -1
- package/lib/constants.js +1 -18
- package/lib/gcore.types.d.ts +84 -0
- package/lib/gcore.types.d.ts.map +1 -0
- package/lib/gcore.types.js +9 -0
- package/lib/index.d.ts +15 -5
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +14 -5
- package/lib/internal.types.d.ts +30 -11
- package/lib/internal.types.d.ts.map +1 -1
- package/lib/playback/index.d.ts +4 -0
- package/lib/playback/index.d.ts.map +1 -0
- package/lib/playback/index.js +13 -0
- package/lib/playback.types.d.ts +28 -5
- package/lib/playback.types.d.ts.map +1 -1
- package/lib/playback.types.js +9 -1
- package/lib/plugins/dash-playback/DashPlayback.d.ts +0 -1
- package/lib/plugins/dash-playback/DashPlayback.d.ts.map +1 -1
- package/lib/plugins/dash-playback/DashPlayback.js +32 -96
- package/lib/plugins/dash-playback/types.d.ts +6 -0
- package/lib/plugins/dash-playback/types.d.ts.map +1 -0
- package/lib/plugins/dash-playback/types.js +1 -0
- package/lib/plugins/hls-playback/HlsPlayback.d.ts +3 -3
- package/lib/plugins/hls-playback/HlsPlayback.d.ts.map +1 -1
- package/lib/plugins/hls-playback/HlsPlayback.js +136 -63
- package/lib/tsdoc-metadata.json +11 -0
- package/lib/types.d.ts +181 -64
- package/lib/types.d.ts.map +1 -1
- package/lib/types.js +19 -0
- package/lib/utils/mediaSources.d.ts +14 -8
- package/lib/utils/mediaSources.d.ts.map +1 -1
- package/lib/utils/mediaSources.js +56 -62
- package/lib/utils/testUtils.d.ts +3 -0
- package/lib/utils/testUtils.d.ts.map +1 -0
- package/lib/utils/testUtils.js +12 -0
- package/lib/version.d.ts +5 -0
- package/lib/version.d.ts.map +1 -1
- package/lib/version.js +5 -0
- package/package.json +10 -4
- package/src/Player.ts +155 -54
- package/src/__tests__/Player.test.ts +357 -0
- package/src/index.ts +16 -5
- package/src/internal.types.ts +30 -15
- package/src/playback/index.ts +17 -0
- package/src/playback.types.ts +31 -7
- package/src/plugins/dash-playback/DashPlayback.ts +38 -114
- package/src/plugins/hls-playback/HlsPlayback.ts +561 -386
- package/src/types.ts +199 -75
- package/src/typings/@clappr/core/error_mixin.d.ts +0 -2
- package/src/typings/@clappr/core/index.d.ts +5 -0
- package/src/typings/@clappr/index.d.ts +1 -0
- package/src/utils/__tests__/mediaSources.test.ts +230 -0
- package/src/utils/mediaSources.ts +65 -75
- package/src/utils/testUtils.ts +15 -0
- package/src/version.ts +5 -0
- package/temp/player.api.json +1950 -0
- package/tsconfig.json +0 -9
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.config.ts +8 -0
- package/licenses.json +0 -782
- package/src/constants.ts +0 -17
- package/src/plugins/dash-playback/_DashPlayback.js +0 -688
- package/src/typings/@clappr/plugins.d.ts +0 -23
- package/src/utils/queryParams.ts +0 -5
|
@@ -2,12 +2,20 @@
|
|
|
2
2
|
// Use of this source code is governed by a BSD-style
|
|
3
3
|
// license that can be found in the LICENSE file.
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
|
|
5
|
+
import {
|
|
6
|
+
Events,
|
|
7
|
+
HTML5Video,
|
|
8
|
+
Log,
|
|
9
|
+
Playback,
|
|
10
|
+
PlayerError,
|
|
11
|
+
Utils,
|
|
12
|
+
} from '@clappr/core'
|
|
13
|
+
import { trace } from '@gcorevideo/utils'
|
|
14
|
+
import assert from 'assert'
|
|
7
15
|
import HLSJS, {
|
|
8
|
-
HlsEvents,
|
|
16
|
+
Events as HlsEvents,
|
|
9
17
|
HlsListeners,
|
|
10
|
-
type HlsErrorData,
|
|
18
|
+
type ErrorData as HlsErrorData,
|
|
11
19
|
type Fragment,
|
|
12
20
|
type FragChangedData,
|
|
13
21
|
type FragLoadedData,
|
|
@@ -15,190 +23,199 @@ import HLSJS, {
|
|
|
15
23
|
type LevelUpdatedData,
|
|
16
24
|
type LevelLoadedData,
|
|
17
25
|
type LevelSwitchingData,
|
|
18
|
-
} from 'hls.js'
|
|
26
|
+
} from 'hls.js'
|
|
19
27
|
|
|
20
|
-
import { QualityLevel, TimePosition } from '../../playback.types.js'
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import { TimerId } from '../../utils/types';
|
|
28
|
+
import { PlaybackErrorCode, QualityLevel, TimePosition, TimeUpdate } from '../../playback.types.js'
|
|
29
|
+
import { PlaybackType } from '../../types'
|
|
30
|
+
import { TimerId } from '../../utils/types'
|
|
24
31
|
|
|
25
|
-
import { CLAPPR_VERSION } from
|
|
32
|
+
import { CLAPPR_VERSION } from '../../build.js'
|
|
26
33
|
|
|
27
|
-
const { now, listContainsIgnoreCase } = Utils
|
|
34
|
+
const { now, listContainsIgnoreCase } = Utils
|
|
28
35
|
|
|
29
|
-
const AUTO = -1
|
|
30
|
-
const DEFAULT_RECOVER_ATTEMPTS = 16
|
|
36
|
+
const AUTO = -1
|
|
37
|
+
const DEFAULT_RECOVER_ATTEMPTS = 16
|
|
31
38
|
|
|
32
|
-
Events.register('PLAYBACK_FRAGMENT_CHANGED')
|
|
33
|
-
Events.register('PLAYBACK_FRAGMENT_PARSING_METADATA')
|
|
39
|
+
Events.register('PLAYBACK_FRAGMENT_CHANGED')
|
|
40
|
+
Events.register('PLAYBACK_FRAGMENT_PARSING_METADATA')
|
|
34
41
|
|
|
35
|
-
const T = 'plugins.hls'
|
|
42
|
+
const T = 'plugins.hls'
|
|
36
43
|
|
|
37
44
|
type MediaSegment = {
|
|
38
|
-
start: number
|
|
39
|
-
end: number
|
|
45
|
+
start: number
|
|
46
|
+
end: number
|
|
40
47
|
}
|
|
41
48
|
|
|
42
49
|
type TimeCorrelation = {
|
|
43
|
-
local: number
|
|
44
|
-
remote: number
|
|
50
|
+
local: number
|
|
51
|
+
remote: number
|
|
45
52
|
}
|
|
46
53
|
|
|
47
|
-
type PlaylistType = 'EVENT' | 'VOD'
|
|
54
|
+
type PlaylistType = 'EVENT' | 'VOD'
|
|
48
55
|
|
|
49
56
|
type PlaybackProgress = {
|
|
50
|
-
start: number
|
|
51
|
-
current: number
|
|
52
|
-
total: number
|
|
57
|
+
start: number
|
|
58
|
+
current: number
|
|
59
|
+
total: number
|
|
53
60
|
}
|
|
54
61
|
|
|
55
62
|
type CustomListener = {
|
|
56
|
-
callback: (...args: any[]) => void
|
|
57
|
-
eventName: keyof HlsListeners
|
|
58
|
-
once?: boolean
|
|
59
|
-
}
|
|
63
|
+
callback: (...args: any[]) => void
|
|
64
|
+
eventName: keyof HlsListeners
|
|
65
|
+
once?: boolean
|
|
66
|
+
}
|
|
60
67
|
|
|
61
68
|
// TODO level, code, description, etc
|
|
62
|
-
type ErrorInfo = Record<string, unknown
|
|
69
|
+
type ErrorInfo = Record<string, unknown>
|
|
63
70
|
|
|
64
71
|
// @ts-expect-error
|
|
65
72
|
export default class HlsPlayback extends HTML5Video {
|
|
66
|
-
private _ccIsSetup = false
|
|
73
|
+
private _ccIsSetup = false
|
|
67
74
|
|
|
68
|
-
private _ccTracksUpdated = false
|
|
75
|
+
private _ccTracksUpdated = false
|
|
69
76
|
|
|
70
|
-
private _currentFragment: Fragment | null = null
|
|
77
|
+
private _currentFragment: Fragment | null = null
|
|
71
78
|
|
|
72
|
-
private _currentLevel: number | null = null
|
|
79
|
+
private _currentLevel: number | null = null
|
|
73
80
|
|
|
74
|
-
private _durationExcludesAfterLiveSyncPoint = false
|
|
81
|
+
private _durationExcludesAfterLiveSyncPoint = false
|
|
75
82
|
|
|
76
|
-
private _extrapolatedWindowNumSegments = 0
|
|
83
|
+
private _extrapolatedWindowNumSegments = 0 // TODO
|
|
77
84
|
|
|
78
|
-
private highDefinition = false
|
|
85
|
+
private highDefinition = false
|
|
79
86
|
|
|
80
|
-
private _hls: HLSJS | null = null
|
|
87
|
+
private _hls: HLSJS | null = null
|
|
81
88
|
|
|
82
|
-
private _isReadyState = false
|
|
89
|
+
private _isReadyState = false
|
|
83
90
|
|
|
84
|
-
private _lastDuration: number | null = null
|
|
91
|
+
private _lastDuration: number | null = null
|
|
85
92
|
|
|
86
|
-
private _lastTimeUpdate: TimePosition | null = null
|
|
93
|
+
private _lastTimeUpdate: TimePosition | null = null
|
|
87
94
|
|
|
88
|
-
private _levels: QualityLevel[] | null = null
|
|
95
|
+
private _levels: QualityLevel[] | null = null
|
|
89
96
|
|
|
90
|
-
private _localStartTimeCorrelation: TimeCorrelation | null = null
|
|
97
|
+
private _localStartTimeCorrelation: TimeCorrelation | null = null
|
|
91
98
|
|
|
92
|
-
private _localEndTimeCorrelation: TimeCorrelation | null = null
|
|
99
|
+
private _localEndTimeCorrelation: TimeCorrelation | null = null
|
|
93
100
|
|
|
94
|
-
private _manifestParsed = false
|
|
101
|
+
private _manifestParsed = false
|
|
95
102
|
|
|
96
|
-
private _playableRegionDuration = 0
|
|
103
|
+
private _playableRegionDuration = 0
|
|
97
104
|
|
|
98
|
-
private _playbackType: PlaybackType = Playback.VOD as PlaybackType
|
|
105
|
+
private _playbackType: PlaybackType = Playback.VOD as PlaybackType
|
|
99
106
|
|
|
100
|
-
private _playlistType: PlaylistType | null = null
|
|
107
|
+
private _playlistType: PlaylistType | null = null
|
|
101
108
|
|
|
102
|
-
private _playableRegionStartTime = 0
|
|
109
|
+
private _playableRegionStartTime = 0
|
|
103
110
|
|
|
104
|
-
private _programDateTime:
|
|
111
|
+
private _programDateTime: number | null = null
|
|
105
112
|
|
|
106
|
-
private _recoverAttemptsRemaining = 0
|
|
113
|
+
private _recoverAttemptsRemaining = 0
|
|
107
114
|
|
|
108
|
-
private _recoveredAudioCodecError = false
|
|
115
|
+
private _recoveredAudioCodecError = false
|
|
109
116
|
|
|
110
|
-
private _recoveredDecodingError = false
|
|
117
|
+
private _recoveredDecodingError = false
|
|
111
118
|
|
|
112
|
-
private _segmentTargetDuration: number | null = null
|
|
119
|
+
private _segmentTargetDuration: number | null = null
|
|
113
120
|
|
|
114
|
-
private _timeUpdateTimer: TimerId | null = null
|
|
121
|
+
private _timeUpdateTimer: TimerId | null = null
|
|
115
122
|
|
|
116
123
|
get name() {
|
|
117
|
-
return 'hls'
|
|
124
|
+
return 'hls'
|
|
118
125
|
}
|
|
119
126
|
|
|
120
127
|
get supportedVersion() {
|
|
121
|
-
return { min: CLAPPR_VERSION }
|
|
128
|
+
return { min: CLAPPR_VERSION }
|
|
122
129
|
}
|
|
123
130
|
|
|
124
131
|
get levels() {
|
|
125
|
-
return this._levels || []
|
|
132
|
+
return this._levels || []
|
|
126
133
|
}
|
|
127
134
|
|
|
128
135
|
get currentLevel() {
|
|
129
|
-
return this._currentLevel ?? AUTO
|
|
136
|
+
return this._currentLevel ?? AUTO
|
|
130
137
|
}
|
|
131
138
|
|
|
132
139
|
get isReady() {
|
|
133
|
-
return this._isReadyState
|
|
140
|
+
return this._isReadyState
|
|
134
141
|
}
|
|
135
142
|
|
|
136
143
|
set currentLevel(id: number) {
|
|
137
|
-
this._currentLevel = id
|
|
138
|
-
this.trigger(Events.PLAYBACK_LEVEL_SWITCH_START)
|
|
139
|
-
assert.ok(this._hls, 'Hls.js instance is not available')
|
|
144
|
+
this._currentLevel = id
|
|
145
|
+
this.trigger(Events.PLAYBACK_LEVEL_SWITCH_START)
|
|
146
|
+
assert.ok(this._hls, 'Hls.js instance is not available')
|
|
140
147
|
if (this.options.playback.hlsUseNextLevel) {
|
|
141
|
-
this._hls.nextLevel = this._currentLevel
|
|
148
|
+
this._hls.nextLevel = this._currentLevel
|
|
142
149
|
} else {
|
|
143
|
-
this._hls.currentLevel = this._currentLevel
|
|
150
|
+
this._hls.currentLevel = this._currentLevel
|
|
144
151
|
}
|
|
145
152
|
}
|
|
146
153
|
|
|
147
154
|
get latency() {
|
|
148
|
-
assert.ok(this._hls, 'Hls.js instance is not available')
|
|
149
|
-
return this._hls.latency
|
|
155
|
+
assert.ok(this._hls, 'Hls.js instance is not available')
|
|
156
|
+
return this._hls.latency
|
|
150
157
|
}
|
|
151
158
|
|
|
152
159
|
get currentProgramDateTime() {
|
|
153
|
-
assert.ok(this._hls, 'Hls.js instance is not available')
|
|
154
|
-
assert.ok(this._hls.playingDate, 'Hls.js playingDate is not defined')
|
|
155
|
-
return this._hls.playingDate
|
|
160
|
+
assert.ok(this._hls, 'Hls.js instance is not available')
|
|
161
|
+
assert.ok(this._hls.playingDate, 'Hls.js playingDate is not defined')
|
|
162
|
+
return this._hls.playingDate
|
|
156
163
|
}
|
|
157
164
|
|
|
158
165
|
get _startTime() {
|
|
159
|
-
if (
|
|
160
|
-
|
|
166
|
+
if (
|
|
167
|
+
this._playbackType === Playback.LIVE &&
|
|
168
|
+
this._playlistType !== 'EVENT'
|
|
169
|
+
) {
|
|
170
|
+
return this._extrapolatedStartTime
|
|
161
171
|
}
|
|
162
172
|
|
|
163
|
-
return this._playableRegionStartTime
|
|
173
|
+
return this._playableRegionStartTime
|
|
164
174
|
}
|
|
165
175
|
|
|
166
176
|
get _now() {
|
|
167
|
-
return now()
|
|
177
|
+
return now()
|
|
168
178
|
}
|
|
169
179
|
|
|
170
180
|
// the time in the video element which should represent the start of the sliding window
|
|
171
181
|
// extrapolated to increase in real time (instead of jumping as the early segments are removed)
|
|
172
182
|
get _extrapolatedStartTime() {
|
|
173
183
|
if (!this._localStartTimeCorrelation) {
|
|
174
|
-
return this._playableRegionStartTime
|
|
184
|
+
return this._playableRegionStartTime
|
|
175
185
|
}
|
|
176
186
|
|
|
177
|
-
const corr = this._localStartTimeCorrelation
|
|
178
|
-
const timePassed = this._now - corr.local
|
|
179
|
-
const extrapolatedWindowStartTime = (corr.remote + timePassed) / 1000
|
|
187
|
+
const corr = this._localStartTimeCorrelation
|
|
188
|
+
const timePassed = this._now - corr.local
|
|
189
|
+
const extrapolatedWindowStartTime = (corr.remote + timePassed) / 1000
|
|
180
190
|
|
|
181
191
|
// cap at the end of the extrapolated window duration
|
|
182
|
-
return Math.min(
|
|
192
|
+
return Math.min(
|
|
193
|
+
extrapolatedWindowStartTime,
|
|
194
|
+
this._playableRegionStartTime + this._extrapolatedWindowDuration,
|
|
195
|
+
)
|
|
183
196
|
}
|
|
184
197
|
|
|
185
198
|
// the time in the video element which should represent the end of the content
|
|
186
199
|
// extrapolated to increase in real time (instead of jumping as segments are added)
|
|
187
200
|
get _extrapolatedEndTime() {
|
|
188
|
-
const actualEndTime =
|
|
201
|
+
const actualEndTime =
|
|
202
|
+
this._playableRegionStartTime + this._playableRegionDuration
|
|
189
203
|
|
|
190
204
|
if (!this._localEndTimeCorrelation) {
|
|
191
|
-
return actualEndTime
|
|
205
|
+
return actualEndTime
|
|
192
206
|
}
|
|
193
|
-
const correlation = this._localEndTimeCorrelation
|
|
194
|
-
const timePassed = this._now - correlation.local
|
|
195
|
-
const extrapolatedEndTime = (correlation.remote + timePassed) / 1000
|
|
207
|
+
const correlation = this._localEndTimeCorrelation
|
|
208
|
+
const timePassed = this._now - correlation.local
|
|
209
|
+
const extrapolatedEndTime = (correlation.remote + timePassed) / 1000
|
|
196
210
|
|
|
197
|
-
return Math.max(
|
|
211
|
+
return Math.max(
|
|
212
|
+
actualEndTime - this._extrapolatedWindowDuration,
|
|
213
|
+
Math.min(extrapolatedEndTime, actualEndTime),
|
|
214
|
+
)
|
|
198
215
|
}
|
|
199
216
|
|
|
200
217
|
get _duration() {
|
|
201
|
-
return this._extrapolatedEndTime - this._startTime
|
|
218
|
+
return this._extrapolatedEndTime - this._startTime
|
|
202
219
|
}
|
|
203
220
|
|
|
204
221
|
// Returns the duration (seconds) of the window that the extrapolated start time is allowed
|
|
@@ -220,64 +237,80 @@ export default class HlsPlayback extends HTML5Video {
|
|
|
220
237
|
// extrapolatedStartTime
|
|
221
238
|
get _extrapolatedWindowDuration() {
|
|
222
239
|
if (this._segmentTargetDuration === null) {
|
|
223
|
-
return 0
|
|
240
|
+
return 0
|
|
224
241
|
}
|
|
225
242
|
|
|
226
|
-
return this._extrapolatedWindowNumSegments * this._segmentTargetDuration
|
|
243
|
+
return this._extrapolatedWindowNumSegments * this._segmentTargetDuration
|
|
227
244
|
}
|
|
228
245
|
|
|
229
246
|
get bandwidthEstimate() {
|
|
230
|
-
return this._hls && this._hls.bandwidthEstimate
|
|
247
|
+
return this._hls && this._hls.bandwidthEstimate
|
|
231
248
|
}
|
|
232
249
|
|
|
233
250
|
get defaultOptions() {
|
|
234
|
-
return { preload: true }
|
|
251
|
+
return { preload: true }
|
|
235
252
|
}
|
|
236
253
|
|
|
237
254
|
get customListeners() {
|
|
238
|
-
return
|
|
255
|
+
return (
|
|
256
|
+
(this.options.hlsPlayback && this.options.hlsPlayback.customListeners) ||
|
|
257
|
+
[]
|
|
258
|
+
)
|
|
239
259
|
}
|
|
240
260
|
|
|
241
261
|
get sourceMedia() {
|
|
242
|
-
return this.options.src
|
|
262
|
+
return this.options.src
|
|
243
263
|
}
|
|
244
264
|
|
|
245
265
|
get currentTimestamp() {
|
|
246
266
|
if (!this._currentFragment) {
|
|
247
|
-
return null
|
|
267
|
+
return null
|
|
248
268
|
}
|
|
249
|
-
assert(
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
const
|
|
269
|
+
assert(
|
|
270
|
+
this._currentFragment.programDateTime !== null,
|
|
271
|
+
'Hls.js programDateTime is not defined',
|
|
272
|
+
)
|
|
273
|
+
const startTime = this._currentFragment.programDateTime
|
|
274
|
+
const playbackTime = (this.el as HTMLMediaElement).currentTime
|
|
275
|
+
const playTimeOffSet = playbackTime - this._currentFragment.start
|
|
276
|
+
const currentTimestampInMs = startTime + playTimeOffSet * 1000
|
|
254
277
|
|
|
255
|
-
return currentTimestampInMs / 1000
|
|
278
|
+
return currentTimestampInMs / 1000
|
|
256
279
|
}
|
|
257
280
|
|
|
258
281
|
static get HLSJS() {
|
|
259
|
-
return HLSJS
|
|
282
|
+
return HLSJS
|
|
260
283
|
}
|
|
261
284
|
|
|
262
285
|
constructor(...args: any[]) {
|
|
263
286
|
// @ts-ignore
|
|
264
|
-
super(...args)
|
|
265
|
-
this.options.hlsPlayback = {
|
|
266
|
-
|
|
287
|
+
super(...args)
|
|
288
|
+
this.options.hlsPlayback = {
|
|
289
|
+
...this.defaultOptions,
|
|
290
|
+
...this.options.hlsPlayback,
|
|
291
|
+
}
|
|
292
|
+
this._setInitialState()
|
|
267
293
|
}
|
|
268
294
|
|
|
269
295
|
_setInitialState() {
|
|
270
296
|
// @ts-ignore
|
|
271
|
-
this._minDvrSize =
|
|
297
|
+
this._minDvrSize =
|
|
298
|
+
typeof this.options.hlsMinimumDvrSize === 'undefined'
|
|
299
|
+
? 60
|
|
300
|
+
: this.options.hlsMinimumDvrSize
|
|
272
301
|
// The size of the start time extrapolation window measured as a multiple of segments.
|
|
273
302
|
// Should be 2 or higher, or 0 to disable. Should only need to be increased above 2 if more than one segment is
|
|
274
303
|
// removed from the start of the playlist at a time. E.g if the playlist is cached for 10 seconds and new chunks are
|
|
275
304
|
// added/removed every 5.
|
|
276
|
-
this._extrapolatedWindowNumSegments =
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
305
|
+
this._extrapolatedWindowNumSegments =
|
|
306
|
+
!this.options.playback ||
|
|
307
|
+
typeof this.options.playback.extrapolatedWindowNumSegments === 'undefined'
|
|
308
|
+
? 2
|
|
309
|
+
: this.options.playback.extrapolatedWindowNumSegments
|
|
310
|
+
|
|
311
|
+
this._playbackType = Playback.VOD as PlaybackType
|
|
312
|
+
this._lastTimeUpdate = { current: 0, total: 0 }
|
|
313
|
+
this._lastDuration = null
|
|
281
314
|
// for hls streams which have dvr with a sliding window,
|
|
282
315
|
// the content at the start of the playlist is removed as new
|
|
283
316
|
// content is appended at the end.
|
|
@@ -285,47 +318,48 @@ export default class HlsPlayback extends HTML5Video {
|
|
|
285
318
|
// start content is deleted
|
|
286
319
|
// For streams with dvr where the entire recording is kept from the
|
|
287
320
|
// beginning this should stay as 0
|
|
288
|
-
this._playableRegionStartTime = 0
|
|
321
|
+
this._playableRegionStartTime = 0
|
|
289
322
|
// {local, remote} remote is the time in the video element that should represent 0
|
|
290
323
|
// local is the system time when the 'remote' measurment took place
|
|
291
|
-
this._localStartTimeCorrelation = null
|
|
324
|
+
this._localStartTimeCorrelation = null
|
|
292
325
|
// {local, remote} remote is the time in the video element that should represents the end
|
|
293
326
|
// local is the system time when the 'remote' measurment took place
|
|
294
|
-
this._localEndTimeCorrelation = null
|
|
327
|
+
this._localEndTimeCorrelation = null
|
|
295
328
|
// if content is removed from the beginning then this empty area should
|
|
296
329
|
// be ignored. "playableRegionDuration" excludes the empty area
|
|
297
|
-
this._playableRegionDuration = 0
|
|
330
|
+
this._playableRegionDuration = 0
|
|
298
331
|
// #EXT-X-PROGRAM-DATE-TIME
|
|
299
|
-
this._programDateTime = null
|
|
332
|
+
this._programDateTime = null
|
|
300
333
|
// true when the actual duration is longer than hlsjs's live sync point
|
|
301
334
|
// when this is false playableRegionDuration will be the actual duration
|
|
302
335
|
// when this is true playableRegionDuration will exclude the time after the sync point
|
|
303
|
-
this._durationExcludesAfterLiveSyncPoint = false
|
|
336
|
+
this._durationExcludesAfterLiveSyncPoint = false
|
|
304
337
|
// #EXT-X-TARGETDURATION
|
|
305
|
-
this._segmentTargetDuration = null
|
|
338
|
+
this._segmentTargetDuration = null
|
|
306
339
|
// #EXT-X-PLAYLIST-TYPE
|
|
307
|
-
this._playlistType = null
|
|
340
|
+
this._playlistType = null
|
|
308
341
|
// TODO options.hlsRecoverAttempts
|
|
309
|
-
this._recoverAttemptsRemaining =
|
|
342
|
+
this._recoverAttemptsRemaining =
|
|
343
|
+
this.options.hlsRecoverAttempts || DEFAULT_RECOVER_ATTEMPTS
|
|
310
344
|
}
|
|
311
345
|
|
|
312
346
|
_setup() {
|
|
313
|
-
this._destroyHLSInstance()
|
|
314
|
-
this._createHLSInstance()
|
|
315
|
-
this._listenHLSEvents()
|
|
316
|
-
this._attachHLSMedia()
|
|
347
|
+
this._destroyHLSInstance()
|
|
348
|
+
this._createHLSInstance()
|
|
349
|
+
this._listenHLSEvents()
|
|
350
|
+
this._attachHLSMedia()
|
|
317
351
|
}
|
|
318
352
|
|
|
319
353
|
_destroyHLSInstance() {
|
|
320
354
|
if (!this._hls) {
|
|
321
|
-
return
|
|
355
|
+
return
|
|
322
356
|
}
|
|
323
|
-
this._manifestParsed = false
|
|
324
|
-
this._ccIsSetup = false
|
|
325
|
-
this._ccTracksUpdated = false
|
|
326
|
-
this._setInitialState()
|
|
327
|
-
this._hls.destroy()
|
|
328
|
-
this._hls = null
|
|
357
|
+
this._manifestParsed = false
|
|
358
|
+
this._ccIsSetup = false
|
|
359
|
+
this._ccTracksUpdated = false
|
|
360
|
+
this._setInitialState()
|
|
361
|
+
this._hls.destroy()
|
|
362
|
+
this._hls = null
|
|
329
363
|
}
|
|
330
364
|
|
|
331
365
|
_createHLSInstance() {
|
|
@@ -333,216 +367,263 @@ export default class HlsPlayback extends HTML5Video {
|
|
|
333
367
|
...this.options.playback.hlsjsConfig,
|
|
334
368
|
maxBufferLength: 2,
|
|
335
369
|
maxMaxBufferLength: 4,
|
|
336
|
-
}
|
|
370
|
+
}
|
|
337
371
|
|
|
338
|
-
this._hls = new HLSJS(config)
|
|
372
|
+
this._hls = new HLSJS(config)
|
|
339
373
|
}
|
|
340
374
|
|
|
341
375
|
_attachHLSMedia() {
|
|
342
376
|
if (!this._hls) {
|
|
343
|
-
return
|
|
377
|
+
return
|
|
344
378
|
}
|
|
345
|
-
this._hls.attachMedia(this.el as HTMLMediaElement)
|
|
379
|
+
this._hls.attachMedia(this.el as HTMLMediaElement)
|
|
346
380
|
}
|
|
347
381
|
|
|
348
382
|
_listenHLSEvents() {
|
|
349
383
|
if (!this._hls) {
|
|
350
|
-
return
|
|
384
|
+
return
|
|
351
385
|
}
|
|
352
386
|
this._hls.once(HLSJS.Events.MEDIA_ATTACHED, () => {
|
|
353
|
-
assert.ok(this._hls, 'Hls.js instance is not available')
|
|
354
|
-
this.options.hlsPlayback.preload && this._hls.loadSource(this.options.src)
|
|
355
|
-
})
|
|
387
|
+
assert.ok(this._hls, 'Hls.js instance is not available')
|
|
388
|
+
this.options.hlsPlayback.preload && this._hls.loadSource(this.options.src)
|
|
389
|
+
})
|
|
356
390
|
|
|
357
391
|
const onPlaying = () => {
|
|
358
392
|
if (this._hls) {
|
|
359
|
-
this._hls.config.maxBufferLength =
|
|
360
|
-
|
|
393
|
+
this._hls.config.maxBufferLength =
|
|
394
|
+
this.options.hlsPlayback.maxBufferLength || 30
|
|
395
|
+
this._hls.config.maxMaxBufferLength =
|
|
396
|
+
this.options.hlsPlayback.maxMaxBufferLength || 60
|
|
361
397
|
}
|
|
362
|
-
this.el.removeEventListener('playing', onPlaying)
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
this.el.addEventListener('playing', onPlaying)
|
|
366
|
-
|
|
367
|
-
this._hls.on(
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
this._hls.on(
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
398
|
+
this.el.removeEventListener('playing', onPlaying)
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
this.el.addEventListener('playing', onPlaying)
|
|
402
|
+
|
|
403
|
+
this._hls.on(
|
|
404
|
+
HLSJS.Events.MANIFEST_PARSED,
|
|
405
|
+
() => (this._manifestParsed = true),
|
|
406
|
+
)
|
|
407
|
+
this._hls.on(
|
|
408
|
+
HLSJS.Events.LEVEL_LOADED,
|
|
409
|
+
(evt: HlsEvents.LEVEL_LOADED, data: LevelLoadedData) =>
|
|
410
|
+
this._updatePlaybackType(evt, data),
|
|
411
|
+
)
|
|
412
|
+
this._hls.on(
|
|
413
|
+
HLSJS.Events.LEVEL_UPDATED,
|
|
414
|
+
(evt: HlsEvents.LEVEL_UPDATED, data: LevelUpdatedData) =>
|
|
415
|
+
this._onLevelUpdated(evt, data),
|
|
416
|
+
)
|
|
417
|
+
this._hls.on(
|
|
418
|
+
HLSJS.Events.LEVEL_SWITCHING,
|
|
419
|
+
(evt: HlsEvents.LEVEL_SWITCHING, data: LevelSwitchingData) =>
|
|
420
|
+
this._onLevelSwitch(evt, data),
|
|
421
|
+
)
|
|
422
|
+
this._hls.on(
|
|
423
|
+
HLSJS.Events.FRAG_CHANGED,
|
|
424
|
+
(evt: HlsEvents.FRAG_CHANGED, data: FragChangedData) =>
|
|
425
|
+
this._onFragmentChanged(evt, data),
|
|
426
|
+
)
|
|
427
|
+
this._hls.on(
|
|
428
|
+
HLSJS.Events.FRAG_LOADED,
|
|
429
|
+
(evt: HlsEvents.FRAG_LOADED, data: FragLoadedData) =>
|
|
430
|
+
this._onFragmentLoaded(evt, data),
|
|
431
|
+
)
|
|
432
|
+
this._hls.on(
|
|
433
|
+
HLSJS.Events.FRAG_PARSING_METADATA,
|
|
434
|
+
(evt: HlsEvents.FRAG_PARSING_METADATA, data: FragParsingMetadataData) =>
|
|
435
|
+
this._onFragmentParsingMetadata(evt, data),
|
|
436
|
+
)
|
|
437
|
+
this._hls.on(HLSJS.Events.ERROR, (evt, data) =>
|
|
438
|
+
this._onHLSJSError(evt, data),
|
|
439
|
+
)
|
|
375
440
|
// this._hls.on(HLSJS.Events.SUBTITLE_TRACK_LOADED, (evt, data) => this._onSubtitleLoaded(evt, data));
|
|
376
|
-
this._hls.on(HLSJS.Events.SUBTITLE_TRACK_LOADED, () =>
|
|
377
|
-
|
|
378
|
-
|
|
441
|
+
this._hls.on(HLSJS.Events.SUBTITLE_TRACK_LOADED, () =>
|
|
442
|
+
this._onSubtitleLoaded(),
|
|
443
|
+
)
|
|
444
|
+
this._hls.on(
|
|
445
|
+
HLSJS.Events.SUBTITLE_TRACKS_UPDATED,
|
|
446
|
+
() => (this._ccTracksUpdated = true),
|
|
447
|
+
)
|
|
448
|
+
this.bindCustomListeners()
|
|
379
449
|
}
|
|
380
450
|
|
|
381
451
|
bindCustomListeners() {
|
|
382
452
|
this.customListeners.forEach((item: CustomListener) => {
|
|
383
|
-
const requestedEventName = item.eventName
|
|
384
|
-
const typeOfListener = item.once ? 'once' : 'on'
|
|
385
|
-
assert.ok(this._hls, 'Hls.js instance is not available')
|
|
386
|
-
requestedEventName &&
|
|
387
|
-
|
|
453
|
+
const requestedEventName = item.eventName
|
|
454
|
+
const typeOfListener = item.once ? 'once' : 'on'
|
|
455
|
+
assert.ok(this._hls, 'Hls.js instance is not available')
|
|
456
|
+
requestedEventName &&
|
|
457
|
+
this._hls[`${typeOfListener}`](requestedEventName, item.callback)
|
|
458
|
+
})
|
|
388
459
|
}
|
|
389
460
|
|
|
390
461
|
unbindCustomListeners() {
|
|
391
462
|
this.customListeners.forEach((item: CustomListener) => {
|
|
392
|
-
const requestedEventName = item.eventName
|
|
463
|
+
const requestedEventName = item.eventName
|
|
393
464
|
|
|
394
|
-
assert.ok(this._hls, 'Hls.js instance is not available')
|
|
395
|
-
requestedEventName && this._hls.off(requestedEventName, item.callback)
|
|
396
|
-
})
|
|
465
|
+
assert.ok(this._hls, 'Hls.js instance is not available')
|
|
466
|
+
requestedEventName && this._hls.off(requestedEventName, item.callback)
|
|
467
|
+
})
|
|
397
468
|
}
|
|
398
469
|
|
|
399
|
-
_onFragmentParsingMetadata(
|
|
470
|
+
_onFragmentParsingMetadata(
|
|
471
|
+
evt: HlsEvents.FRAG_PARSING_METADATA,
|
|
472
|
+
data: FragParsingMetadataData,
|
|
473
|
+
) {
|
|
400
474
|
// @ts-ignore
|
|
401
|
-
this.trigger(Events.Custom.PLAYBACK_FRAGMENT_PARSING_METADATA, {
|
|
475
|
+
this.trigger(Events.Custom.PLAYBACK_FRAGMENT_PARSING_METADATA, {
|
|
476
|
+
evt,
|
|
477
|
+
data,
|
|
478
|
+
})
|
|
402
479
|
}
|
|
403
480
|
|
|
404
481
|
render() {
|
|
405
|
-
this._ready()
|
|
482
|
+
this._ready()
|
|
406
483
|
|
|
407
|
-
return super.render()
|
|
484
|
+
return super.render()
|
|
408
485
|
}
|
|
409
486
|
|
|
410
487
|
_ready() {
|
|
411
488
|
if (this._isReadyState) {
|
|
412
|
-
return
|
|
489
|
+
return
|
|
413
490
|
}
|
|
414
|
-
!this._hls && this._setup()
|
|
415
|
-
this._isReadyState = true
|
|
416
|
-
this.trigger(Events.PLAYBACK_READY, this.name)
|
|
491
|
+
!this._hls && this._setup()
|
|
492
|
+
this._isReadyState = true
|
|
493
|
+
this.trigger(Events.PLAYBACK_READY, this.name)
|
|
417
494
|
}
|
|
418
495
|
|
|
419
496
|
_recover(evt: HlsEvents.ERROR, data: HlsErrorData, error: ErrorInfo) {
|
|
420
|
-
assert(this._hls, 'Hls.js instance is not available')
|
|
497
|
+
assert(this._hls, 'Hls.js instance is not available')
|
|
421
498
|
if (!this._recoveredDecodingError) {
|
|
422
|
-
this._recoveredDecodingError = true
|
|
423
|
-
this._hls.recoverMediaError()
|
|
499
|
+
this._recoveredDecodingError = true
|
|
500
|
+
this._hls.recoverMediaError()
|
|
424
501
|
} else if (!this._recoveredAudioCodecError) {
|
|
425
|
-
this._recoveredAudioCodecError = true
|
|
426
|
-
this._hls.swapAudioCodec()
|
|
427
|
-
this._hls.recoverMediaError()
|
|
502
|
+
this._recoveredAudioCodecError = true
|
|
503
|
+
this._hls.swapAudioCodec()
|
|
504
|
+
this._hls.recoverMediaError()
|
|
428
505
|
} else {
|
|
429
|
-
Log.error('hlsjs: failed to recover', { evt, data })
|
|
430
|
-
error.level = PlayerError.Levels.FATAL
|
|
431
|
-
const formattedError = this.createError(error)
|
|
506
|
+
Log.error('hlsjs: failed to recover', { evt, data })
|
|
507
|
+
error.level = PlayerError.Levels.FATAL
|
|
508
|
+
const formattedError = this.createError(error)
|
|
432
509
|
|
|
433
|
-
this.trigger(Events.PLAYBACK_ERROR, formattedError)
|
|
434
|
-
this.stop()
|
|
510
|
+
this.trigger(Events.PLAYBACK_ERROR, formattedError)
|
|
511
|
+
this.stop()
|
|
435
512
|
}
|
|
436
513
|
}
|
|
437
514
|
|
|
438
|
-
// override
|
|
439
515
|
// this playback manages the src on the video element itself
|
|
440
|
-
|
|
516
|
+
protected override _setupSrc(srcUrl: string) {} // eslint-disable-line no-unused-vars
|
|
441
517
|
|
|
442
518
|
_startTimeUpdateTimer() {
|
|
443
519
|
if (this._timeUpdateTimer) {
|
|
444
|
-
return
|
|
520
|
+
return
|
|
445
521
|
}
|
|
446
522
|
this._timeUpdateTimer = setInterval(() => {
|
|
447
|
-
this._onDurationChange()
|
|
448
|
-
this._onTimeUpdate()
|
|
449
|
-
}, 100)
|
|
523
|
+
this._onDurationChange()
|
|
524
|
+
this._onTimeUpdate()
|
|
525
|
+
}, 100)
|
|
450
526
|
}
|
|
451
527
|
|
|
452
528
|
_stopTimeUpdateTimer() {
|
|
453
529
|
if (!this._timeUpdateTimer) {
|
|
454
|
-
return
|
|
530
|
+
return
|
|
455
531
|
}
|
|
456
|
-
clearInterval(this._timeUpdateTimer)
|
|
457
|
-
this._timeUpdateTimer = null
|
|
532
|
+
clearInterval(this._timeUpdateTimer)
|
|
533
|
+
this._timeUpdateTimer = null
|
|
458
534
|
}
|
|
459
535
|
|
|
460
536
|
getProgramDateTime() {
|
|
461
|
-
return this._programDateTime
|
|
537
|
+
return this._programDateTime ?? 0
|
|
462
538
|
}
|
|
463
539
|
|
|
464
540
|
// the duration on the video element itself should not be used
|
|
465
541
|
// as this does not necesarily represent the duration of the stream
|
|
466
542
|
// https://github.com/clappr/clappr/issues/668#issuecomment-157036678
|
|
467
543
|
getDuration() {
|
|
468
|
-
return this._duration
|
|
544
|
+
return this._duration
|
|
469
545
|
}
|
|
470
546
|
|
|
471
547
|
getCurrentTime() {
|
|
472
548
|
// e.g. can be < 0 if user pauses near the start
|
|
473
549
|
// eventually they will then be kicked to the end by hlsjs if they run out of buffer
|
|
474
550
|
// before the official start time
|
|
475
|
-
return Math.max(
|
|
551
|
+
return Math.max(
|
|
552
|
+
0,
|
|
553
|
+
(this.el as HTMLMediaElement).currentTime - this._startTime,
|
|
554
|
+
)
|
|
476
555
|
}
|
|
477
556
|
|
|
478
557
|
// the time that "0" now represents relative to when playback started
|
|
479
558
|
// for a stream with a sliding window this will increase as content is
|
|
480
559
|
// removed from the beginning
|
|
481
560
|
getStartTimeOffset() {
|
|
482
|
-
return this._startTime
|
|
561
|
+
return this._startTime
|
|
483
562
|
}
|
|
484
563
|
|
|
485
564
|
seekPercentage(percentage: number) {
|
|
486
|
-
const seekTo =
|
|
487
|
-
? this._duration * (percentage / 100)
|
|
488
|
-
: this._duration;
|
|
565
|
+
const seekTo =
|
|
566
|
+
percentage > 0 ? this._duration * (percentage / 100) : this._duration
|
|
489
567
|
|
|
490
|
-
this.seek(seekTo)
|
|
568
|
+
this.seek(seekTo)
|
|
491
569
|
}
|
|
492
570
|
|
|
493
571
|
seek(time: number) {
|
|
494
572
|
if (time < 0) {
|
|
495
|
-
Log.warn(
|
|
496
|
-
|
|
573
|
+
Log.warn(
|
|
574
|
+
'Attempt to seek to a negative time. Resetting to live point. Use seekToLivePoint() to seek to the live point.',
|
|
575
|
+
)
|
|
576
|
+
time = this.getDuration()
|
|
497
577
|
}
|
|
498
578
|
// assume live if time within 3 seconds of end of stream
|
|
499
|
-
this.dvrEnabled && this._updateDvr(time < this.getDuration() - 3)
|
|
500
|
-
time += this._startTime
|
|
501
|
-
(this.el as HTMLMediaElement).currentTime = time
|
|
579
|
+
this.dvrEnabled && this._updateDvr(time < this.getDuration() - 3)
|
|
580
|
+
time += this._startTime
|
|
581
|
+
;(this.el as HTMLMediaElement).currentTime = time
|
|
502
582
|
}
|
|
503
583
|
|
|
504
584
|
seekToLivePoint() {
|
|
505
|
-
this.seek(this.getDuration())
|
|
585
|
+
this.seek(this.getDuration())
|
|
506
586
|
}
|
|
507
587
|
|
|
508
588
|
_updateDvr(status: boolean) {
|
|
509
|
-
this.trigger(Events.PLAYBACK_DVR, status)
|
|
510
|
-
this.trigger(Events.PLAYBACK_STATS_ADD, {
|
|
589
|
+
this.trigger(Events.PLAYBACK_DVR, status)
|
|
590
|
+
this.trigger(Events.PLAYBACK_STATS_ADD, { dvr: status })
|
|
511
591
|
}
|
|
512
592
|
|
|
513
593
|
_updateSettings() {
|
|
514
594
|
if (this._playbackType === Playback.VOD) {
|
|
515
595
|
// @ts-expect-error
|
|
516
|
-
this.settings.left = ['playpause', 'position', 'duration']
|
|
596
|
+
this.settings.left = ['playpause', 'position', 'duration']
|
|
517
597
|
} else if (this.dvrEnabled) {
|
|
518
598
|
// @ts-expect-error
|
|
519
|
-
this.settings.left = ['playpause']
|
|
599
|
+
this.settings.left = ['playpause']
|
|
520
600
|
} else {
|
|
521
601
|
// @ts-expect-error
|
|
522
|
-
this.settings.left = ['playstop']
|
|
602
|
+
this.settings.left = ['playstop']
|
|
523
603
|
}
|
|
524
604
|
|
|
525
|
-
|
|
526
|
-
this.settings.seekEnabled = this.isSeekEnabled()
|
|
527
|
-
this.trigger(Events.PLAYBACK_SETTINGSUPDATE)
|
|
605
|
+
// @ts-expect-error
|
|
606
|
+
this.settings.seekEnabled = this.isSeekEnabled()
|
|
607
|
+
this.trigger(Events.PLAYBACK_SETTINGSUPDATE)
|
|
528
608
|
}
|
|
529
609
|
|
|
530
610
|
_onHLSJSError(evt: HlsEvents.ERROR, data: HlsErrorData) {
|
|
531
611
|
const error: Record<string, unknown> = {
|
|
532
|
-
code: `${data.type}_${data.details}`,
|
|
612
|
+
// code: `${data.type}_${data.details}`,
|
|
613
|
+
code: PlaybackErrorCode.Generic,
|
|
533
614
|
description: `${this.name} error: type: ${data.type}, details: ${data.details}`,
|
|
534
615
|
raw: data,
|
|
535
|
-
}
|
|
536
|
-
let formattedError
|
|
616
|
+
}
|
|
617
|
+
let formattedError
|
|
537
618
|
|
|
538
619
|
if (data.response) {
|
|
539
|
-
error.description += `, response: ${JSON.stringify(data.response)}
|
|
620
|
+
error.description += `, response: ${JSON.stringify(data.response)}`
|
|
540
621
|
}
|
|
541
622
|
// only report/handle errors if they are fatal
|
|
542
623
|
// hlsjs should automatically handle non fatal errors
|
|
543
624
|
if (data.fatal) {
|
|
544
625
|
if (this._recoverAttemptsRemaining > 0) {
|
|
545
|
-
this._recoverAttemptsRemaining -= 1
|
|
626
|
+
this._recoverAttemptsRemaining -= 1
|
|
546
627
|
switch (data.type) {
|
|
547
628
|
case HLSJS.ErrorTypes.NETWORK_ERROR:
|
|
548
629
|
switch (data.details) {
|
|
@@ -554,197 +635,263 @@ export default class HlsPlayback extends HTML5Video {
|
|
|
554
635
|
case HLSJS.ErrorDetails.MANIFEST_PARSING_ERROR:
|
|
555
636
|
case HLSJS.ErrorDetails.LEVEL_LOAD_ERROR:
|
|
556
637
|
case HLSJS.ErrorDetails.LEVEL_LOAD_TIMEOUT:
|
|
557
|
-
Log.error('hlsjs: unrecoverable network fatal error.', {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
638
|
+
Log.error('hlsjs: unrecoverable network fatal error.', {
|
|
639
|
+
evt,
|
|
640
|
+
data,
|
|
641
|
+
})
|
|
642
|
+
error.code = [
|
|
643
|
+
HLSJS.ErrorDetails.MANIFEST_LOAD_ERROR,
|
|
644
|
+
HLSJS.ErrorDetails.MANIFEST_LOAD_TIMEOUT,
|
|
645
|
+
HLSJS.ErrorDetails.MANIFEST_PARSING_ERROR
|
|
646
|
+
].includes(data.details)
|
|
647
|
+
? PlaybackErrorCode.MediaSourceUnavailable
|
|
648
|
+
: PlaybackErrorCode.QualityLevelUnavailable
|
|
649
|
+
formattedError = this.createError(error, {
|
|
650
|
+
useCodePrefix: false,
|
|
651
|
+
})
|
|
652
|
+
this.trigger(Events.PLAYBACK_ERROR, formattedError)
|
|
653
|
+
this.stop()
|
|
654
|
+
break
|
|
562
655
|
default:
|
|
563
|
-
Log.warn('hlsjs: trying to recover from network error.', {
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
656
|
+
Log.warn('hlsjs: trying to recover from network error.', {
|
|
657
|
+
evt,
|
|
658
|
+
data,
|
|
659
|
+
})
|
|
660
|
+
error.level = PlayerError.Levels.WARN
|
|
661
|
+
assert(this._hls, 'Hls.js instance is not available')
|
|
662
|
+
this._hls.startLoad()
|
|
663
|
+
break
|
|
568
664
|
}
|
|
569
|
-
break
|
|
665
|
+
break
|
|
570
666
|
case HLSJS.ErrorTypes.MEDIA_ERROR:
|
|
571
|
-
Log.warn('hlsjs: trying to recover from media error.', {
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
667
|
+
Log.warn('hlsjs: trying to recover from media error.', {
|
|
668
|
+
evt,
|
|
669
|
+
data,
|
|
670
|
+
})
|
|
671
|
+
error.level = PlayerError.Levels.WARN
|
|
672
|
+
this._recover(evt, data, error)
|
|
673
|
+
break
|
|
575
674
|
default:
|
|
576
|
-
Log.error('hlsjs: could not recover from error.', { evt, data })
|
|
577
|
-
formattedError = this.createError(error
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
675
|
+
Log.error('hlsjs: could not recover from error.', { evt, data })
|
|
676
|
+
formattedError = this.createError(error, {
|
|
677
|
+
useCodePrefix: false,
|
|
678
|
+
})
|
|
679
|
+
this.trigger(Events.PLAYBACK_ERROR, formattedError)
|
|
680
|
+
this.stop()
|
|
681
|
+
break
|
|
581
682
|
}
|
|
582
683
|
} else {
|
|
583
|
-
Log.error(
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
684
|
+
Log.error(
|
|
685
|
+
'hlsjs: could not recover from error after maximum number of attempts.',
|
|
686
|
+
{ evt, data },
|
|
687
|
+
)
|
|
688
|
+
// TODO
|
|
689
|
+
formattedError = this.createError(error, {
|
|
690
|
+
useCodePrefix: false,
|
|
691
|
+
})
|
|
692
|
+
this.trigger(Events.PLAYBACK_ERROR, formattedError)
|
|
693
|
+
this.stop()
|
|
587
694
|
}
|
|
588
695
|
} else {
|
|
589
696
|
// Transforms HLSJS.ErrorDetails.KEY_LOAD_ERROR non-fatal error to
|
|
590
697
|
// playback fatal error if triggerFatalErrorOnResourceDenied playback
|
|
591
698
|
// option is set. HLSJS.ErrorTypes.KEY_SYSTEM_ERROR are fatal errors
|
|
592
699
|
// and therefore already handled.
|
|
593
|
-
if (
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
700
|
+
if (
|
|
701
|
+
this.options.playback.triggerFatalErrorOnResourceDenied &&
|
|
702
|
+
this._keyIsDenied(data)
|
|
703
|
+
) {
|
|
704
|
+
Log.error('hlsjs: could not load decrypt key.', { evt, data })
|
|
705
|
+
formattedError = this.createError(error, {
|
|
706
|
+
useCodePrefix: false,
|
|
707
|
+
})
|
|
708
|
+
this.trigger(Events.PLAYBACK_ERROR, formattedError)
|
|
709
|
+
this.stop()
|
|
710
|
+
|
|
711
|
+
return
|
|
600
712
|
}
|
|
601
713
|
|
|
602
|
-
error.level = PlayerError.Levels.WARN
|
|
603
|
-
Log.warn('hlsjs: non-fatal error occurred', { evt, data })
|
|
714
|
+
error.level = PlayerError.Levels.WARN
|
|
715
|
+
Log.warn('hlsjs: non-fatal error occurred', { evt, data })
|
|
604
716
|
}
|
|
605
717
|
}
|
|
606
718
|
|
|
607
719
|
_keyIsDenied(data: HlsErrorData) {
|
|
608
|
-
return
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
720
|
+
return (
|
|
721
|
+
data.type === HLSJS.ErrorTypes.NETWORK_ERROR &&
|
|
722
|
+
data.details === HLSJS.ErrorDetails.KEY_LOAD_ERROR &&
|
|
723
|
+
data.response &&
|
|
724
|
+
data.response.code &&
|
|
725
|
+
data.response.code >= 400
|
|
726
|
+
)
|
|
613
727
|
}
|
|
614
728
|
|
|
615
729
|
_onTimeUpdate() {
|
|
616
|
-
const update = {
|
|
617
|
-
|
|
730
|
+
const update: TimeUpdate = {
|
|
731
|
+
current: this.getCurrentTime(),
|
|
732
|
+
total: this.getDuration(),
|
|
733
|
+
firstFragDateTime: this.getProgramDateTime(),
|
|
734
|
+
}
|
|
735
|
+
const isSame =
|
|
736
|
+
this._lastTimeUpdate &&
|
|
618
737
|
update.current === this._lastTimeUpdate.current &&
|
|
619
|
-
update.total === this._lastTimeUpdate.total
|
|
738
|
+
update.total === this._lastTimeUpdate.total
|
|
620
739
|
|
|
621
740
|
if (isSame) {
|
|
622
|
-
return
|
|
741
|
+
return
|
|
623
742
|
}
|
|
624
|
-
this._lastTimeUpdate = update
|
|
625
|
-
this.trigger(Events.PLAYBACK_TIMEUPDATE, update, this.name)
|
|
743
|
+
this._lastTimeUpdate = update
|
|
744
|
+
this.trigger(Events.PLAYBACK_TIMEUPDATE, update, this.name)
|
|
626
745
|
}
|
|
627
746
|
|
|
628
747
|
_onDurationChange() {
|
|
629
|
-
const duration = this.getDuration()
|
|
748
|
+
const duration = this.getDuration()
|
|
630
749
|
|
|
631
750
|
if (this._lastDuration === duration) {
|
|
632
|
-
return
|
|
751
|
+
return
|
|
633
752
|
}
|
|
634
|
-
this._lastDuration = duration
|
|
635
|
-
super._onDurationChange()
|
|
753
|
+
this._lastDuration = duration
|
|
754
|
+
super._onDurationChange()
|
|
636
755
|
}
|
|
637
756
|
|
|
638
757
|
_onProgress() {
|
|
639
758
|
if (!(this.el as HTMLMediaElement).buffered.length) {
|
|
640
|
-
return
|
|
759
|
+
return
|
|
641
760
|
}
|
|
642
|
-
let buffered: MediaSegment[] = []
|
|
643
|
-
let bufferedPos = 0
|
|
761
|
+
let buffered: MediaSegment[] = []
|
|
762
|
+
let bufferedPos = 0
|
|
644
763
|
|
|
645
764
|
for (let i = 0; i < (this.el as HTMLMediaElement).buffered.length; i++) {
|
|
646
|
-
buffered = [
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
765
|
+
buffered = [
|
|
766
|
+
...buffered,
|
|
767
|
+
{
|
|
768
|
+
// for a stream with sliding window dvr something that is buffered my slide off the start of the timeline
|
|
769
|
+
start: Math.max(
|
|
770
|
+
0,
|
|
771
|
+
(this.el as HTMLMediaElement).buffered.start(i) -
|
|
772
|
+
this._playableRegionStartTime,
|
|
773
|
+
),
|
|
774
|
+
end: Math.max(
|
|
775
|
+
0,
|
|
776
|
+
(this.el as HTMLMediaElement).buffered.end(i) -
|
|
777
|
+
this._playableRegionStartTime,
|
|
778
|
+
),
|
|
779
|
+
},
|
|
780
|
+
]
|
|
781
|
+
if (
|
|
782
|
+
(this.el as HTMLMediaElement).currentTime >= buffered[i].start &&
|
|
783
|
+
(this.el as HTMLMediaElement).currentTime <= buffered[i].end
|
|
784
|
+
) {
|
|
785
|
+
bufferedPos = i
|
|
653
786
|
}
|
|
654
787
|
}
|
|
655
788
|
const progress: PlaybackProgress = {
|
|
656
789
|
start: buffered[bufferedPos].start,
|
|
657
790
|
current: buffered[bufferedPos].end,
|
|
658
|
-
total: this.getDuration()
|
|
659
|
-
}
|
|
791
|
+
total: this.getDuration(),
|
|
792
|
+
}
|
|
660
793
|
|
|
661
|
-
this.trigger(Events.PLAYBACK_PROGRESS, progress, buffered)
|
|
794
|
+
this.trigger(Events.PLAYBACK_PROGRESS, progress, buffered)
|
|
662
795
|
}
|
|
663
796
|
|
|
664
797
|
load(url: string) {
|
|
665
|
-
this._stopTimeUpdateTimer()
|
|
666
|
-
this.options.src = url
|
|
667
|
-
this._setup()
|
|
798
|
+
this._stopTimeUpdateTimer()
|
|
799
|
+
this.options.src = url
|
|
800
|
+
this._setup()
|
|
668
801
|
}
|
|
669
802
|
|
|
670
803
|
play() {
|
|
671
|
-
trace(`${T} play`, { hls: !!this._hls, ...this.options.hlsPlayback })
|
|
672
|
-
!this._hls && this._setup()
|
|
673
|
-
assert.ok(this._hls, 'Hls.js instance is not available')
|
|
674
|
-
!this._manifestParsed &&
|
|
675
|
-
|
|
676
|
-
|
|
804
|
+
trace(`${T} play`, { hls: !!this._hls, ...this.options.hlsPlayback })
|
|
805
|
+
!this._hls && this._setup()
|
|
806
|
+
assert.ok(this._hls, 'Hls.js instance is not available')
|
|
807
|
+
!this._manifestParsed &&
|
|
808
|
+
!this.options.hlsPlayback.preload &&
|
|
809
|
+
this._hls.loadSource(this.options.src)
|
|
810
|
+
super.play()
|
|
811
|
+
this._startTimeUpdateTimer()
|
|
677
812
|
}
|
|
678
813
|
|
|
679
814
|
pause() {
|
|
680
815
|
if (!this._hls) {
|
|
681
|
-
return
|
|
816
|
+
return
|
|
682
817
|
}
|
|
683
|
-
(this.el as HTMLMediaElement).pause()
|
|
818
|
+
;(this.el as HTMLMediaElement).pause()
|
|
684
819
|
if (this.dvrEnabled) {
|
|
685
|
-
this._updateDvr(true)
|
|
820
|
+
this._updateDvr(true)
|
|
686
821
|
}
|
|
687
822
|
}
|
|
688
823
|
|
|
689
824
|
stop() {
|
|
690
|
-
this._stopTimeUpdateTimer()
|
|
825
|
+
this._stopTimeUpdateTimer()
|
|
691
826
|
if (this._hls) {
|
|
692
|
-
super.stop()
|
|
827
|
+
super.stop()
|
|
693
828
|
}
|
|
694
|
-
this._destroyHLSInstance()
|
|
829
|
+
this._destroyHLSInstance()
|
|
695
830
|
}
|
|
696
831
|
|
|
697
832
|
destroy() {
|
|
698
|
-
this._stopTimeUpdateTimer()
|
|
699
|
-
this._destroyHLSInstance()
|
|
700
|
-
return super.destroy()
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
private _updatePlaybackType(
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
833
|
+
this._stopTimeUpdateTimer()
|
|
834
|
+
this._destroyHLSInstance()
|
|
835
|
+
return super.destroy()
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
private _updatePlaybackType(
|
|
839
|
+
evt: HlsEvents.LEVEL_LOADED,
|
|
840
|
+
data: LevelLoadedData,
|
|
841
|
+
) {
|
|
842
|
+
const prevPlaybackType = this._playbackType
|
|
843
|
+
this._playbackType = (
|
|
844
|
+
data.details.live ? Playback.LIVE : Playback.VOD
|
|
845
|
+
) as PlaybackType
|
|
846
|
+
this._onLevelUpdated(evt, data)
|
|
707
847
|
// Live stream subtitle tracks detection hack (may not immediately available)
|
|
708
|
-
if (
|
|
709
|
-
this.
|
|
848
|
+
if (
|
|
849
|
+
this._ccTracksUpdated &&
|
|
850
|
+
this._playbackType === Playback.LIVE &&
|
|
851
|
+
this.hasClosedCaptionsTracks
|
|
852
|
+
) {
|
|
853
|
+
this._onSubtitleLoaded()
|
|
710
854
|
}
|
|
711
855
|
if (prevPlaybackType !== this._playbackType) {
|
|
712
|
-
this._updateSettings()
|
|
856
|
+
this._updateSettings()
|
|
713
857
|
}
|
|
714
858
|
}
|
|
715
859
|
|
|
716
860
|
private _fillLevels() {
|
|
717
|
-
assert.ok(this._hls, 'Hls.js instance is not available')
|
|
861
|
+
assert.ok(this._hls, 'Hls.js instance is not available')
|
|
718
862
|
this._levels = this._hls.levels.map((level, index) => {
|
|
719
863
|
return {
|
|
720
864
|
level: index, // or level.id?
|
|
721
865
|
width: level.width,
|
|
722
866
|
height: level.height,
|
|
723
867
|
bitrate: level.bitrate,
|
|
724
|
-
}
|
|
725
|
-
})
|
|
726
|
-
this.trigger(Events.PLAYBACK_LEVELS_AVAILABLE, this._levels)
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
private _onLevelUpdated(
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
868
|
+
}
|
|
869
|
+
})
|
|
870
|
+
this.trigger(Events.PLAYBACK_LEVELS_AVAILABLE, this._levels)
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
private _onLevelUpdated(
|
|
874
|
+
evt: HlsEvents.LEVEL_UPDATED | HlsEvents.LEVEL_LOADED,
|
|
875
|
+
data: LevelUpdatedData,
|
|
876
|
+
) {
|
|
877
|
+
this._segmentTargetDuration = data.details.targetduration
|
|
878
|
+
this._playlistType = (data.details.type as PlaylistType) || null
|
|
879
|
+
let startTimeChanged = false
|
|
880
|
+
let durationChanged = false
|
|
881
|
+
const fragments = data.details.fragments
|
|
882
|
+
const previousPlayableRegionStartTime = this._playableRegionStartTime
|
|
883
|
+
const previousPlayableRegionDuration = this._playableRegionDuration
|
|
737
884
|
|
|
738
885
|
if (fragments.length === 0) {
|
|
739
|
-
return
|
|
886
|
+
return
|
|
740
887
|
}
|
|
741
888
|
// #EXT-X-PROGRAM-DATE-TIME
|
|
742
889
|
if (fragments[0].rawProgramDateTime) {
|
|
743
|
-
this._programDateTime = fragments[0].rawProgramDateTime
|
|
890
|
+
this._programDateTime = Number(fragments[0].rawProgramDateTime)
|
|
744
891
|
}
|
|
745
892
|
if (this._playableRegionStartTime !== fragments[0].start) {
|
|
746
|
-
startTimeChanged = true
|
|
747
|
-
this._playableRegionStartTime = fragments[0].start
|
|
893
|
+
startTimeChanged = true
|
|
894
|
+
this._playableRegionStartTime = fragments[0].start
|
|
748
895
|
}
|
|
749
896
|
|
|
750
897
|
if (startTimeChanged) {
|
|
@@ -752,14 +899,15 @@ export default class HlsPlayback extends HTML5Video {
|
|
|
752
899
|
// set the correlation to map to middle of the extrapolation window
|
|
753
900
|
this._localStartTimeCorrelation = {
|
|
754
901
|
local: this._now,
|
|
755
|
-
remote:
|
|
756
|
-
|
|
902
|
+
remote:
|
|
903
|
+
(fragments[0].start + this._extrapolatedWindowDuration / 2) * 1000,
|
|
904
|
+
}
|
|
757
905
|
} else {
|
|
758
906
|
// check if the correlation still works
|
|
759
|
-
const corr = this._localStartTimeCorrelation
|
|
760
|
-
const timePassed = this._now - corr.local
|
|
907
|
+
const corr = this._localStartTimeCorrelation
|
|
908
|
+
const timePassed = this._now - corr.local
|
|
761
909
|
// this should point to a time within the extrapolation window
|
|
762
|
-
const startTime = (corr.remote + timePassed) / 1000
|
|
910
|
+
const startTime = (corr.remote + timePassed) / 1000
|
|
763
911
|
|
|
764
912
|
if (startTime < fragments[0].start) {
|
|
765
913
|
// our start time is now earlier than the first chunk
|
|
@@ -767,134 +915,150 @@ export default class HlsPlayback extends HTML5Video {
|
|
|
767
915
|
// reset correlation so that it sits at the beginning of the first available chunk
|
|
768
916
|
this._localStartTimeCorrelation = {
|
|
769
917
|
local: this._now,
|
|
770
|
-
remote: fragments[0].start * 1000
|
|
771
|
-
}
|
|
772
|
-
} else if (
|
|
918
|
+
remote: fragments[0].start * 1000,
|
|
919
|
+
}
|
|
920
|
+
} else if (
|
|
921
|
+
startTime >
|
|
922
|
+
previousPlayableRegionStartTime + this._extrapolatedWindowDuration
|
|
923
|
+
) {
|
|
773
924
|
// start time was past the end of the old extrapolation window (so would have been capped)
|
|
774
925
|
// see if now that time would be inside the window, and if it would be set the correlation
|
|
775
926
|
// so that it resumes from the time it was at at the end of the old window
|
|
776
927
|
// update the correlation so that the time starts counting again from the value it's on now
|
|
777
928
|
this._localStartTimeCorrelation = {
|
|
778
929
|
local: this._now,
|
|
779
|
-
remote:
|
|
780
|
-
|
|
930
|
+
remote:
|
|
931
|
+
Math.max(
|
|
932
|
+
fragments[0].start,
|
|
933
|
+
previousPlayableRegionStartTime +
|
|
934
|
+
this._extrapolatedWindowDuration,
|
|
935
|
+
) * 1000,
|
|
936
|
+
}
|
|
781
937
|
}
|
|
782
938
|
}
|
|
783
939
|
}
|
|
784
940
|
|
|
785
|
-
let newDuration = data.details.totalduration
|
|
941
|
+
let newDuration = data.details.totalduration
|
|
786
942
|
|
|
787
943
|
// if it's a live stream then shorten the duration to remove access
|
|
788
944
|
// to the area after hlsjs's live sync point
|
|
789
945
|
// seeks to areas after this point sometimes have issues
|
|
790
946
|
if (this._playbackType === Playback.LIVE) {
|
|
791
|
-
const fragmentTargetDuration = data.details.targetduration
|
|
792
|
-
const hlsjsConfig = this.options.playback.hlsjsConfig || {}
|
|
793
|
-
const liveSyncDurationCount =
|
|
794
|
-
|
|
947
|
+
const fragmentTargetDuration = data.details.targetduration
|
|
948
|
+
const hlsjsConfig = this.options.playback.hlsjsConfig || {}
|
|
949
|
+
const liveSyncDurationCount =
|
|
950
|
+
hlsjsConfig.liveSyncDurationCount ||
|
|
951
|
+
HLSJS.DefaultConfig.liveSyncDurationCount
|
|
952
|
+
const hiddenAreaDuration = fragmentTargetDuration * liveSyncDurationCount
|
|
795
953
|
|
|
796
954
|
if (hiddenAreaDuration <= newDuration) {
|
|
797
|
-
newDuration -= hiddenAreaDuration
|
|
798
|
-
this._durationExcludesAfterLiveSyncPoint = true
|
|
955
|
+
newDuration -= hiddenAreaDuration
|
|
956
|
+
this._durationExcludesAfterLiveSyncPoint = true
|
|
799
957
|
} else {
|
|
800
|
-
this._durationExcludesAfterLiveSyncPoint = false
|
|
958
|
+
this._durationExcludesAfterLiveSyncPoint = false
|
|
801
959
|
}
|
|
802
960
|
}
|
|
803
961
|
if (newDuration !== this._playableRegionDuration) {
|
|
804
|
-
durationChanged = true
|
|
805
|
-
this._playableRegionDuration = newDuration
|
|
962
|
+
durationChanged = true
|
|
963
|
+
this._playableRegionDuration = newDuration
|
|
806
964
|
}
|
|
807
965
|
// Note the end time is not the playableRegionDuration
|
|
808
966
|
// The end time will always increase even if content is removed from the beginning
|
|
809
|
-
const endTime = fragments[0].start + newDuration
|
|
810
|
-
const previousEndTime =
|
|
811
|
-
|
|
967
|
+
const endTime = fragments[0].start + newDuration
|
|
968
|
+
const previousEndTime =
|
|
969
|
+
previousPlayableRegionStartTime + previousPlayableRegionDuration
|
|
970
|
+
const endTimeChanged = endTime !== previousEndTime
|
|
812
971
|
|
|
813
972
|
if (endTimeChanged) {
|
|
814
973
|
if (!this._localEndTimeCorrelation) {
|
|
815
974
|
// set the correlation to map to the end
|
|
816
975
|
this._localEndTimeCorrelation = {
|
|
817
976
|
local: this._now,
|
|
818
|
-
remote: endTime * 1000
|
|
819
|
-
}
|
|
977
|
+
remote: endTime * 1000,
|
|
978
|
+
}
|
|
820
979
|
} else {
|
|
821
980
|
// check if the correlation still works
|
|
822
|
-
const corr = this._localEndTimeCorrelation
|
|
823
|
-
const timePassed = this._now - corr.local
|
|
981
|
+
const corr = this._localEndTimeCorrelation
|
|
982
|
+
const timePassed = this._now - corr.local
|
|
824
983
|
// this should point to a time within the extrapolation window from the end
|
|
825
|
-
const extrapolatedEndTime = (corr.remote + timePassed) / 1000
|
|
984
|
+
const extrapolatedEndTime = (corr.remote + timePassed) / 1000
|
|
826
985
|
|
|
827
986
|
if (extrapolatedEndTime > endTime) {
|
|
828
987
|
this._localEndTimeCorrelation = {
|
|
829
988
|
local: this._now,
|
|
830
|
-
remote: endTime * 1000
|
|
831
|
-
}
|
|
832
|
-
} else if (
|
|
989
|
+
remote: endTime * 1000,
|
|
990
|
+
}
|
|
991
|
+
} else if (
|
|
992
|
+
extrapolatedEndTime <
|
|
993
|
+
endTime - this._extrapolatedWindowDuration
|
|
994
|
+
) {
|
|
833
995
|
// our extrapolated end time is now earlier than the extrapolation window from the actual end time
|
|
834
996
|
// (maybe a chunk became available early)
|
|
835
997
|
// reset correlation so that it sits at the beginning of the extrapolation window from the end time
|
|
836
998
|
this._localEndTimeCorrelation = {
|
|
837
999
|
local: this._now,
|
|
838
|
-
remote: (endTime - this._extrapolatedWindowDuration) * 1000
|
|
839
|
-
}
|
|
1000
|
+
remote: (endTime - this._extrapolatedWindowDuration) * 1000,
|
|
1001
|
+
}
|
|
840
1002
|
} else if (extrapolatedEndTime > previousEndTime) {
|
|
841
1003
|
// end time was past the old end time (so would have been capped)
|
|
842
1004
|
// set the correlation so that it resumes from the time it was at at the end of the old window
|
|
843
1005
|
this._localEndTimeCorrelation = {
|
|
844
1006
|
local: this._now,
|
|
845
|
-
remote: previousEndTime * 1000
|
|
846
|
-
}
|
|
1007
|
+
remote: previousEndTime * 1000,
|
|
1008
|
+
}
|
|
847
1009
|
}
|
|
848
1010
|
}
|
|
849
1011
|
}
|
|
850
1012
|
|
|
851
1013
|
// now that the values have been updated call any methods that use on them so they get the updated values
|
|
852
1014
|
// immediately
|
|
853
|
-
durationChanged && this._onDurationChange()
|
|
854
|
-
startTimeChanged && this._onProgress()
|
|
1015
|
+
durationChanged && this._onDurationChange()
|
|
1016
|
+
startTimeChanged && this._onProgress()
|
|
855
1017
|
}
|
|
856
1018
|
|
|
857
1019
|
_onFragmentChanged(evt: HlsEvents.FRAG_CHANGED, data: FragChangedData) {
|
|
858
|
-
this._currentFragment = data.frag
|
|
1020
|
+
this._currentFragment = data.frag
|
|
859
1021
|
// @ts-ignore
|
|
860
|
-
this.trigger(Events.Custom.PLAYBACK_FRAGMENT_CHANGED, data)
|
|
1022
|
+
this.trigger(Events.Custom.PLAYBACK_FRAGMENT_CHANGED, data)
|
|
861
1023
|
}
|
|
862
1024
|
|
|
863
1025
|
_onFragmentLoaded(evt: HlsEvents.FRAG_LOADED, data: FragLoadedData) {
|
|
864
|
-
this.trigger(Events.PLAYBACK_FRAGMENT_LOADED, data)
|
|
1026
|
+
this.trigger(Events.PLAYBACK_FRAGMENT_LOADED, data)
|
|
865
1027
|
}
|
|
866
1028
|
|
|
867
1029
|
_onSubtitleLoaded() {
|
|
868
1030
|
// This event may be triggered multiple times
|
|
869
1031
|
// Setup CC only once (disable CC by default)
|
|
870
1032
|
if (!this._ccIsSetup) {
|
|
871
|
-
this.trigger(Events.PLAYBACK_SUBTITLE_AVAILABLE)
|
|
872
|
-
const trackId =
|
|
1033
|
+
this.trigger(Events.PLAYBACK_SUBTITLE_AVAILABLE)
|
|
1034
|
+
const trackId =
|
|
1035
|
+
this._playbackType === Playback.LIVE ? -1 : this.closedCaptionsTrackId
|
|
873
1036
|
|
|
874
|
-
this.closedCaptionsTrackId = trackId
|
|
875
|
-
this._ccIsSetup = true
|
|
1037
|
+
this.closedCaptionsTrackId = trackId
|
|
1038
|
+
this._ccIsSetup = true
|
|
876
1039
|
}
|
|
877
1040
|
}
|
|
878
1041
|
|
|
879
1042
|
_onLevelSwitch(evt: HlsEvents.LEVEL_SWITCHING, data: LevelSwitchingData) {
|
|
880
1043
|
if (!this.levels.length) {
|
|
881
|
-
this._fillLevels()
|
|
1044
|
+
this._fillLevels()
|
|
882
1045
|
}
|
|
883
|
-
this.trigger(Events.PLAYBACK_LEVEL_SWITCH_END)
|
|
884
|
-
this.trigger(Events.PLAYBACK_LEVEL_SWITCH, data)
|
|
885
|
-
assert(this._hls, 'Hls.js instance is not available')
|
|
886
|
-
const currentLevel = this._hls.levels[data.level]
|
|
1046
|
+
this.trigger(Events.PLAYBACK_LEVEL_SWITCH_END)
|
|
1047
|
+
this.trigger(Events.PLAYBACK_LEVEL_SWITCH, data)
|
|
1048
|
+
assert(this._hls, 'Hls.js instance is not available')
|
|
1049
|
+
const currentLevel = this._hls.levels[data.level]
|
|
887
1050
|
|
|
888
1051
|
if (currentLevel) {
|
|
889
1052
|
// TODO should highDefinition be private and maybe have a read only accessor if it's used somewhere
|
|
890
|
-
this.highDefinition =
|
|
891
|
-
|
|
1053
|
+
this.highDefinition =
|
|
1054
|
+
currentLevel.height >= 720 || currentLevel.bitrate / 1000 >= 2000
|
|
1055
|
+
this.trigger(Events.PLAYBACK_HIGHDEFINITIONUPDATE, this.highDefinition)
|
|
892
1056
|
this.trigger(Events.PLAYBACK_BITRATE, {
|
|
893
1057
|
height: currentLevel.height,
|
|
894
1058
|
width: currentLevel.width,
|
|
895
1059
|
bitrate: currentLevel.bitrate,
|
|
896
|
-
level: data.level
|
|
897
|
-
})
|
|
1060
|
+
level: data.level,
|
|
1061
|
+
})
|
|
898
1062
|
}
|
|
899
1063
|
}
|
|
900
1064
|
|
|
@@ -903,24 +1067,35 @@ export default class HlsPlayback extends HTML5Video {
|
|
|
903
1067
|
// - the duration does not include content after hlsjs's live sync point
|
|
904
1068
|
// - the playable region duration is longer than the configured duration to enable dvr after
|
|
905
1069
|
// - the playback type is LIVE.
|
|
906
|
-
return (
|
|
1070
|
+
return (
|
|
1071
|
+
this._durationExcludesAfterLiveSyncPoint &&
|
|
1072
|
+
this._duration >= this._minDvrSize &&
|
|
1073
|
+
this.getPlaybackType() === Playback.LIVE
|
|
1074
|
+
)
|
|
907
1075
|
}
|
|
908
1076
|
|
|
909
1077
|
getPlaybackType() {
|
|
910
|
-
return this._playbackType
|
|
1078
|
+
return this._playbackType
|
|
911
1079
|
}
|
|
912
1080
|
|
|
913
1081
|
isSeekEnabled() {
|
|
914
|
-
return
|
|
1082
|
+
return this._playbackType === Playback.VOD || this.dvrEnabled
|
|
915
1083
|
}
|
|
916
1084
|
}
|
|
917
1085
|
|
|
918
1086
|
HlsPlayback.canPlay = function (resource: string, mimeType?: string): boolean {
|
|
919
|
-
const resourceParts = resource.split('?')[0].match(/.*\.(.*)$/) || []
|
|
920
|
-
const isHls =
|
|
921
|
-
|
|
1087
|
+
const resourceParts = resource.split('?')[0].match(/.*\.(.*)$/) || []
|
|
1088
|
+
const isHls =
|
|
1089
|
+
(resourceParts.length > 1 && resourceParts[1].toLowerCase() === 'm3u8') ||
|
|
1090
|
+
listContainsIgnoreCase(mimeType, [
|
|
1091
|
+
'application/vnd.apple.mpegurl',
|
|
1092
|
+
'application/x-mpegURL',
|
|
1093
|
+
])
|
|
1094
|
+
const hasSupport = HLSJS.isSupported()
|
|
922
1095
|
trace(`${T} canPlay`, {
|
|
923
|
-
hasSupport,
|
|
1096
|
+
hasSupport,
|
|
1097
|
+
isHls,
|
|
1098
|
+
resource,
|
|
924
1099
|
})
|
|
925
|
-
return !!(hasSupport && isHls)
|
|
926
|
-
}
|
|
1100
|
+
return !!(hasSupport && isHls)
|
|
1101
|
+
}
|