@gcorevideo/player 2.10.0 → 2.12.2
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/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 +3115 -1209
- package/lib/Player.d.ts +3 -2
- package/lib/Player.d.ts.map +1 -1
- package/lib/Player.js +32 -24
- package/lib/index.d.ts +5 -5
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +5 -5
- package/lib/internal.types.d.ts +1 -10
- 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 +19 -0
- package/lib/playback.types.d.ts.map +1 -1
- package/lib/playback.types.js +9 -1
- package/lib/plugins/dash-playback/DashPlayback.d.ts +1 -1
- package/lib/plugins/dash-playback/DashPlayback.d.ts.map +1 -1
- package/lib/plugins/dash-playback/DashPlayback.js +39 -100
- 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 +6 -7
- package/lib/plugins/hls-playback/HlsPlayback.d.ts.map +1 -1
- package/lib/plugins/hls-playback/HlsPlayback.js +131 -80
- package/lib/types.d.ts +3 -3
- package/lib/types.d.ts.map +1 -1
- package/lib/utils/mediaSources.d.ts +14 -6
- package/lib/utils/mediaSources.d.ts.map +1 -1
- package/lib/utils/mediaSources.js +56 -53
- 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/package.json +6 -4
- package/src/Player.ts +40 -31
- package/src/__tests__/Player.test.ts +357 -0
- package/src/index.ts +5 -5
- package/src/internal.types.ts +1 -12
- package/src/playback/index.ts +17 -0
- package/src/playback.types.ts +29 -8
- package/src/plugins/dash-playback/DashPlayback.ts +44 -120
- package/src/plugins/hls-playback/HlsPlayback.ts +544 -390
- package/src/types.ts +5 -3
- 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 +78 -64
- package/src/utils/testUtils.ts +15 -0
- package/tsconfig.json +0 -9
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.config.ts +8 -0
- package/licenses.json +0 -782
- 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 { PlaybackError, 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,260 @@ 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
|
-
_recover(evt: HlsEvents.ERROR, data: HlsErrorData, error:
|
|
420
|
-
assert(this._hls, 'Hls.js instance is not available')
|
|
496
|
+
_recover(evt: HlsEvents.ERROR, data: HlsErrorData, error: PlaybackError) {
|
|
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
|
|
432
508
|
|
|
433
|
-
this.
|
|
434
|
-
this.stop();
|
|
509
|
+
this.triggerError(error)
|
|
435
510
|
}
|
|
436
511
|
}
|
|
437
512
|
|
|
438
|
-
// override
|
|
439
513
|
// this playback manages the src on the video element itself
|
|
440
|
-
|
|
514
|
+
protected override _setupSrc(srcUrl: string) {} // eslint-disable-line no-unused-vars
|
|
441
515
|
|
|
442
516
|
_startTimeUpdateTimer() {
|
|
443
517
|
if (this._timeUpdateTimer) {
|
|
444
|
-
return
|
|
518
|
+
return
|
|
445
519
|
}
|
|
446
520
|
this._timeUpdateTimer = setInterval(() => {
|
|
447
|
-
this._onDurationChange()
|
|
448
|
-
this._onTimeUpdate()
|
|
449
|
-
}, 100)
|
|
521
|
+
this._onDurationChange()
|
|
522
|
+
this._onTimeUpdate()
|
|
523
|
+
}, 100)
|
|
450
524
|
}
|
|
451
525
|
|
|
452
526
|
_stopTimeUpdateTimer() {
|
|
453
527
|
if (!this._timeUpdateTimer) {
|
|
454
|
-
return
|
|
528
|
+
return
|
|
455
529
|
}
|
|
456
|
-
clearInterval(this._timeUpdateTimer)
|
|
457
|
-
this._timeUpdateTimer = null
|
|
530
|
+
clearInterval(this._timeUpdateTimer)
|
|
531
|
+
this._timeUpdateTimer = null
|
|
458
532
|
}
|
|
459
533
|
|
|
460
534
|
getProgramDateTime() {
|
|
461
|
-
return this._programDateTime
|
|
535
|
+
return this._programDateTime ?? 0
|
|
462
536
|
}
|
|
463
537
|
|
|
464
538
|
// the duration on the video element itself should not be used
|
|
465
539
|
// as this does not necesarily represent the duration of the stream
|
|
466
540
|
// https://github.com/clappr/clappr/issues/668#issuecomment-157036678
|
|
467
541
|
getDuration() {
|
|
468
|
-
return this._duration
|
|
542
|
+
return this._duration
|
|
469
543
|
}
|
|
470
544
|
|
|
471
545
|
getCurrentTime() {
|
|
472
546
|
// e.g. can be < 0 if user pauses near the start
|
|
473
547
|
// eventually they will then be kicked to the end by hlsjs if they run out of buffer
|
|
474
548
|
// before the official start time
|
|
475
|
-
return Math.max(
|
|
549
|
+
return Math.max(
|
|
550
|
+
0,
|
|
551
|
+
(this.el as HTMLMediaElement).currentTime - this._startTime,
|
|
552
|
+
)
|
|
476
553
|
}
|
|
477
554
|
|
|
478
555
|
// the time that "0" now represents relative to when playback started
|
|
479
556
|
// for a stream with a sliding window this will increase as content is
|
|
480
557
|
// removed from the beginning
|
|
481
558
|
getStartTimeOffset() {
|
|
482
|
-
return this._startTime
|
|
559
|
+
return this._startTime
|
|
483
560
|
}
|
|
484
561
|
|
|
485
562
|
seekPercentage(percentage: number) {
|
|
486
|
-
const seekTo =
|
|
487
|
-
? this._duration * (percentage / 100)
|
|
488
|
-
: this._duration;
|
|
563
|
+
const seekTo =
|
|
564
|
+
percentage > 0 ? this._duration * (percentage / 100) : this._duration
|
|
489
565
|
|
|
490
|
-
this.seek(seekTo)
|
|
566
|
+
this.seek(seekTo)
|
|
491
567
|
}
|
|
492
568
|
|
|
493
569
|
seek(time: number) {
|
|
494
570
|
if (time < 0) {
|
|
495
|
-
Log.warn(
|
|
496
|
-
|
|
571
|
+
Log.warn(
|
|
572
|
+
'Attempt to seek to a negative time. Resetting to live point. Use seekToLivePoint() to seek to the live point.',
|
|
573
|
+
)
|
|
574
|
+
time = this.getDuration()
|
|
497
575
|
}
|
|
498
576
|
// 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
|
|
577
|
+
this.dvrEnabled && this._updateDvr(time < this.getDuration() - 3)
|
|
578
|
+
time += this._startTime
|
|
579
|
+
;(this.el as HTMLMediaElement).currentTime = time
|
|
502
580
|
}
|
|
503
581
|
|
|
504
582
|
seekToLivePoint() {
|
|
505
|
-
this.seek(this.getDuration())
|
|
583
|
+
this.seek(this.getDuration())
|
|
506
584
|
}
|
|
507
585
|
|
|
508
586
|
_updateDvr(status: boolean) {
|
|
509
|
-
this.trigger(Events.PLAYBACK_DVR, status)
|
|
510
|
-
this.trigger(Events.PLAYBACK_STATS_ADD, {
|
|
587
|
+
this.trigger(Events.PLAYBACK_DVR, status)
|
|
588
|
+
this.trigger(Events.PLAYBACK_STATS_ADD, { dvr: status })
|
|
511
589
|
}
|
|
512
590
|
|
|
513
591
|
_updateSettings() {
|
|
514
592
|
if (this._playbackType === Playback.VOD) {
|
|
515
593
|
// @ts-expect-error
|
|
516
|
-
this.settings.left = ['playpause', 'position', 'duration']
|
|
594
|
+
this.settings.left = ['playpause', 'position', 'duration']
|
|
517
595
|
} else if (this.dvrEnabled) {
|
|
518
596
|
// @ts-expect-error
|
|
519
|
-
this.settings.left = ['playpause']
|
|
597
|
+
this.settings.left = ['playpause']
|
|
520
598
|
} else {
|
|
521
599
|
// @ts-expect-error
|
|
522
|
-
this.settings.left = ['playstop']
|
|
600
|
+
this.settings.left = ['playstop']
|
|
523
601
|
}
|
|
524
602
|
|
|
525
|
-
|
|
526
|
-
this.settings.seekEnabled = this.isSeekEnabled()
|
|
527
|
-
this.trigger(Events.PLAYBACK_SETTINGSUPDATE)
|
|
603
|
+
// @ts-expect-error
|
|
604
|
+
this.settings.seekEnabled = this.isSeekEnabled()
|
|
605
|
+
this.trigger(Events.PLAYBACK_SETTINGSUPDATE)
|
|
528
606
|
}
|
|
529
607
|
|
|
530
608
|
_onHLSJSError(evt: HlsEvents.ERROR, data: HlsErrorData) {
|
|
531
|
-
const error:
|
|
532
|
-
code:
|
|
533
|
-
description: `${this.name} error: type: ${data.type}, details: ${data.details}`,
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
609
|
+
const error: PlaybackError = {
|
|
610
|
+
code: PlaybackErrorCode.Generic,
|
|
611
|
+
description: `${this.name} error: type: ${data.type}, details: ${data.details} fatal: ${data.fatal}`,
|
|
612
|
+
level: data.fatal ? PlayerError.Levels.FATAL : PlayerError.Levels.WARN,
|
|
613
|
+
message: `${this.name} error: type: ${data.type}, details: ${data.details}`,
|
|
614
|
+
}
|
|
537
615
|
|
|
538
616
|
if (data.response) {
|
|
539
|
-
error.description += `, response: ${JSON.stringify(data.response)}
|
|
617
|
+
error.description += `, response: ${JSON.stringify(data.response)}`
|
|
540
618
|
}
|
|
541
619
|
// only report/handle errors if they are fatal
|
|
542
620
|
// hlsjs should automatically handle non fatal errors
|
|
543
621
|
if (data.fatal) {
|
|
544
622
|
if (this._recoverAttemptsRemaining > 0) {
|
|
545
|
-
this._recoverAttemptsRemaining -= 1
|
|
623
|
+
this._recoverAttemptsRemaining -= 1
|
|
546
624
|
switch (data.type) {
|
|
547
625
|
case HLSJS.ErrorTypes.NETWORK_ERROR:
|
|
548
626
|
switch (data.details) {
|
|
@@ -554,197 +632,239 @@ export default class HlsPlayback extends HTML5Video {
|
|
|
554
632
|
case HLSJS.ErrorDetails.MANIFEST_PARSING_ERROR:
|
|
555
633
|
case HLSJS.ErrorDetails.LEVEL_LOAD_ERROR:
|
|
556
634
|
case HLSJS.ErrorDetails.LEVEL_LOAD_TIMEOUT:
|
|
557
|
-
Log.error('hlsjs: unrecoverable network fatal error.', {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
635
|
+
Log.error('hlsjs: unrecoverable network fatal error.', {
|
|
636
|
+
evt,
|
|
637
|
+
data,
|
|
638
|
+
})
|
|
639
|
+
error.code = PlaybackErrorCode.MediaSourceUnavailable;
|
|
640
|
+
this.triggerError(error)
|
|
641
|
+
break
|
|
562
642
|
default:
|
|
563
|
-
Log.warn('hlsjs: trying to recover from network error.', {
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
643
|
+
Log.warn('hlsjs: trying to recover from network error.', {
|
|
644
|
+
evt,
|
|
645
|
+
data,
|
|
646
|
+
})
|
|
647
|
+
error.level = PlayerError.Levels.WARN
|
|
648
|
+
this._hls?.startLoad()
|
|
649
|
+
break
|
|
568
650
|
}
|
|
569
|
-
break
|
|
651
|
+
break
|
|
570
652
|
case HLSJS.ErrorTypes.MEDIA_ERROR:
|
|
571
|
-
Log.warn('hlsjs: trying to recover from media error.', {
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
653
|
+
Log.warn('hlsjs: trying to recover from media error.', {
|
|
654
|
+
evt,
|
|
655
|
+
data,
|
|
656
|
+
})
|
|
657
|
+
error.level = PlayerError.Levels.WARN
|
|
658
|
+
this._recover(evt, data, error)
|
|
659
|
+
break
|
|
575
660
|
default:
|
|
576
|
-
Log.error('hlsjs: could not recover from error.', { evt, data })
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
this.stop();
|
|
580
|
-
break;
|
|
661
|
+
Log.error('hlsjs: could not recover from error.', { evt, data })
|
|
662
|
+
this.triggerError(error)
|
|
663
|
+
break
|
|
581
664
|
}
|
|
582
665
|
} else {
|
|
583
|
-
Log.error(
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
666
|
+
Log.error(
|
|
667
|
+
'hlsjs: could not recover from error after maximum number of attempts.',
|
|
668
|
+
{ evt, data },
|
|
669
|
+
)
|
|
670
|
+
// TODO
|
|
671
|
+
this.triggerError(error)
|
|
587
672
|
}
|
|
588
673
|
} else {
|
|
589
674
|
// Transforms HLSJS.ErrorDetails.KEY_LOAD_ERROR non-fatal error to
|
|
590
675
|
// playback fatal error if triggerFatalErrorOnResourceDenied playback
|
|
591
676
|
// option is set. HLSJS.ErrorTypes.KEY_SYSTEM_ERROR are fatal errors
|
|
592
677
|
// and therefore already handled.
|
|
593
|
-
if (
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
678
|
+
if (
|
|
679
|
+
this.options.playback.triggerFatalErrorOnResourceDenied &&
|
|
680
|
+
this._keyIsDenied(data)
|
|
681
|
+
) {
|
|
682
|
+
Log.error('hlsjs: could not load decrypt key.', { evt, data })
|
|
683
|
+
this.triggerError(error)
|
|
684
|
+
|
|
685
|
+
return
|
|
600
686
|
}
|
|
601
687
|
|
|
602
|
-
|
|
603
|
-
Log.warn('hlsjs: non-fatal error occurred', { evt, data });
|
|
688
|
+
Log.warn('hlsjs: non-fatal error occurred', { evt, data })
|
|
604
689
|
}
|
|
605
690
|
}
|
|
606
691
|
|
|
607
692
|
_keyIsDenied(data: HlsErrorData) {
|
|
608
|
-
return
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
693
|
+
return (
|
|
694
|
+
data.type === HLSJS.ErrorTypes.NETWORK_ERROR &&
|
|
695
|
+
data.details === HLSJS.ErrorDetails.KEY_LOAD_ERROR &&
|
|
696
|
+
data.response &&
|
|
697
|
+
data.response.code &&
|
|
698
|
+
data.response.code >= 400
|
|
699
|
+
)
|
|
613
700
|
}
|
|
614
701
|
|
|
615
702
|
_onTimeUpdate() {
|
|
616
|
-
const update = {
|
|
617
|
-
|
|
703
|
+
const update: TimeUpdate = {
|
|
704
|
+
current: this.getCurrentTime(),
|
|
705
|
+
total: this.getDuration(),
|
|
706
|
+
firstFragDateTime: this.getProgramDateTime(),
|
|
707
|
+
}
|
|
708
|
+
const isSame =
|
|
709
|
+
this._lastTimeUpdate &&
|
|
618
710
|
update.current === this._lastTimeUpdate.current &&
|
|
619
|
-
update.total === this._lastTimeUpdate.total
|
|
711
|
+
update.total === this._lastTimeUpdate.total
|
|
620
712
|
|
|
621
713
|
if (isSame) {
|
|
622
|
-
return
|
|
714
|
+
return
|
|
623
715
|
}
|
|
624
|
-
this._lastTimeUpdate = update
|
|
625
|
-
this.trigger(Events.PLAYBACK_TIMEUPDATE, update, this.name)
|
|
716
|
+
this._lastTimeUpdate = update
|
|
717
|
+
this.trigger(Events.PLAYBACK_TIMEUPDATE, update, this.name)
|
|
626
718
|
}
|
|
627
719
|
|
|
628
720
|
_onDurationChange() {
|
|
629
|
-
const duration = this.getDuration()
|
|
721
|
+
const duration = this.getDuration()
|
|
630
722
|
|
|
631
723
|
if (this._lastDuration === duration) {
|
|
632
|
-
return
|
|
724
|
+
return
|
|
633
725
|
}
|
|
634
|
-
this._lastDuration = duration
|
|
635
|
-
super._onDurationChange()
|
|
726
|
+
this._lastDuration = duration
|
|
727
|
+
super._onDurationChange()
|
|
636
728
|
}
|
|
637
729
|
|
|
638
730
|
_onProgress() {
|
|
639
731
|
if (!(this.el as HTMLMediaElement).buffered.length) {
|
|
640
|
-
return
|
|
732
|
+
return
|
|
641
733
|
}
|
|
642
|
-
let buffered: MediaSegment[] = []
|
|
643
|
-
let bufferedPos = 0
|
|
734
|
+
let buffered: MediaSegment[] = []
|
|
735
|
+
let bufferedPos = 0
|
|
644
736
|
|
|
645
737
|
for (let i = 0; i < (this.el as HTMLMediaElement).buffered.length; i++) {
|
|
646
|
-
buffered = [
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
738
|
+
buffered = [
|
|
739
|
+
...buffered,
|
|
740
|
+
{
|
|
741
|
+
// for a stream with sliding window dvr something that is buffered my slide off the start of the timeline
|
|
742
|
+
start: Math.max(
|
|
743
|
+
0,
|
|
744
|
+
(this.el as HTMLMediaElement).buffered.start(i) -
|
|
745
|
+
this._playableRegionStartTime,
|
|
746
|
+
),
|
|
747
|
+
end: Math.max(
|
|
748
|
+
0,
|
|
749
|
+
(this.el as HTMLMediaElement).buffered.end(i) -
|
|
750
|
+
this._playableRegionStartTime,
|
|
751
|
+
),
|
|
752
|
+
},
|
|
753
|
+
]
|
|
754
|
+
if (
|
|
755
|
+
(this.el as HTMLMediaElement).currentTime >= buffered[i].start &&
|
|
756
|
+
(this.el as HTMLMediaElement).currentTime <= buffered[i].end
|
|
757
|
+
) {
|
|
758
|
+
bufferedPos = i
|
|
653
759
|
}
|
|
654
760
|
}
|
|
655
761
|
const progress: PlaybackProgress = {
|
|
656
762
|
start: buffered[bufferedPos].start,
|
|
657
763
|
current: buffered[bufferedPos].end,
|
|
658
|
-
total: this.getDuration()
|
|
659
|
-
}
|
|
764
|
+
total: this.getDuration(),
|
|
765
|
+
}
|
|
660
766
|
|
|
661
|
-
this.trigger(Events.PLAYBACK_PROGRESS, progress, buffered)
|
|
767
|
+
this.trigger(Events.PLAYBACK_PROGRESS, progress, buffered)
|
|
662
768
|
}
|
|
663
769
|
|
|
664
770
|
load(url: string) {
|
|
665
|
-
this._stopTimeUpdateTimer()
|
|
666
|
-
this.options.src = url
|
|
667
|
-
this._setup()
|
|
771
|
+
this._stopTimeUpdateTimer()
|
|
772
|
+
this.options.src = url
|
|
773
|
+
this._setup()
|
|
668
774
|
}
|
|
669
775
|
|
|
670
776
|
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
|
-
|
|
777
|
+
trace(`${T} play`, { hls: !!this._hls, ...this.options.hlsPlayback })
|
|
778
|
+
!this._hls && this._setup()
|
|
779
|
+
assert.ok(this._hls, 'Hls.js instance is not available')
|
|
780
|
+
!this._manifestParsed &&
|
|
781
|
+
!this.options.hlsPlayback.preload &&
|
|
782
|
+
this._hls.loadSource(this.options.src)
|
|
783
|
+
super.play()
|
|
784
|
+
this._startTimeUpdateTimer()
|
|
677
785
|
}
|
|
678
786
|
|
|
679
787
|
pause() {
|
|
680
788
|
if (!this._hls) {
|
|
681
|
-
return
|
|
789
|
+
return
|
|
682
790
|
}
|
|
683
|
-
(this.el as HTMLMediaElement).pause()
|
|
791
|
+
;(this.el as HTMLMediaElement).pause()
|
|
684
792
|
if (this.dvrEnabled) {
|
|
685
|
-
this._updateDvr(true)
|
|
793
|
+
this._updateDvr(true)
|
|
686
794
|
}
|
|
687
795
|
}
|
|
688
796
|
|
|
689
797
|
stop() {
|
|
690
|
-
this._stopTimeUpdateTimer()
|
|
798
|
+
this._stopTimeUpdateTimer()
|
|
691
799
|
if (this._hls) {
|
|
692
|
-
super.stop()
|
|
800
|
+
super.stop()
|
|
693
801
|
}
|
|
694
|
-
this._destroyHLSInstance()
|
|
802
|
+
this._destroyHLSInstance()
|
|
695
803
|
}
|
|
696
804
|
|
|
697
805
|
destroy() {
|
|
698
|
-
this._stopTimeUpdateTimer()
|
|
699
|
-
this._destroyHLSInstance()
|
|
700
|
-
return super.destroy()
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
private _updatePlaybackType(
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
806
|
+
this._stopTimeUpdateTimer()
|
|
807
|
+
this._destroyHLSInstance()
|
|
808
|
+
return super.destroy()
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
private _updatePlaybackType(
|
|
812
|
+
evt: HlsEvents.LEVEL_LOADED,
|
|
813
|
+
data: LevelLoadedData,
|
|
814
|
+
) {
|
|
815
|
+
const prevPlaybackType = this._playbackType
|
|
816
|
+
this._playbackType = (
|
|
817
|
+
data.details.live ? Playback.LIVE : Playback.VOD
|
|
818
|
+
) as PlaybackType
|
|
819
|
+
this._onLevelUpdated(evt, data)
|
|
707
820
|
// Live stream subtitle tracks detection hack (may not immediately available)
|
|
708
|
-
if (
|
|
709
|
-
this.
|
|
821
|
+
if (
|
|
822
|
+
this._ccTracksUpdated &&
|
|
823
|
+
this._playbackType === Playback.LIVE &&
|
|
824
|
+
this.hasClosedCaptionsTracks
|
|
825
|
+
) {
|
|
826
|
+
this._onSubtitleLoaded()
|
|
710
827
|
}
|
|
711
828
|
if (prevPlaybackType !== this._playbackType) {
|
|
712
|
-
this._updateSettings()
|
|
829
|
+
this._updateSettings()
|
|
713
830
|
}
|
|
714
831
|
}
|
|
715
832
|
|
|
716
833
|
private _fillLevels() {
|
|
717
|
-
assert.ok(this._hls, 'Hls.js instance is not available')
|
|
834
|
+
assert.ok(this._hls, 'Hls.js instance is not available')
|
|
718
835
|
this._levels = this._hls.levels.map((level, index) => {
|
|
719
836
|
return {
|
|
720
837
|
level: index, // or level.id?
|
|
721
838
|
width: level.width,
|
|
722
839
|
height: level.height,
|
|
723
840
|
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
|
-
|
|
841
|
+
}
|
|
842
|
+
})
|
|
843
|
+
this.trigger(Events.PLAYBACK_LEVELS_AVAILABLE, this._levels)
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
private _onLevelUpdated(
|
|
847
|
+
evt: HlsEvents.LEVEL_UPDATED | HlsEvents.LEVEL_LOADED,
|
|
848
|
+
data: LevelUpdatedData,
|
|
849
|
+
) {
|
|
850
|
+
this._segmentTargetDuration = data.details.targetduration
|
|
851
|
+
this._playlistType = (data.details.type as PlaylistType) || null
|
|
852
|
+
let startTimeChanged = false
|
|
853
|
+
let durationChanged = false
|
|
854
|
+
const fragments = data.details.fragments
|
|
855
|
+
const previousPlayableRegionStartTime = this._playableRegionStartTime
|
|
856
|
+
const previousPlayableRegionDuration = this._playableRegionDuration
|
|
737
857
|
|
|
738
858
|
if (fragments.length === 0) {
|
|
739
|
-
return
|
|
859
|
+
return
|
|
740
860
|
}
|
|
741
861
|
// #EXT-X-PROGRAM-DATE-TIME
|
|
742
862
|
if (fragments[0].rawProgramDateTime) {
|
|
743
|
-
this._programDateTime = fragments[0].rawProgramDateTime
|
|
863
|
+
this._programDateTime = Number(fragments[0].rawProgramDateTime)
|
|
744
864
|
}
|
|
745
865
|
if (this._playableRegionStartTime !== fragments[0].start) {
|
|
746
|
-
startTimeChanged = true
|
|
747
|
-
this._playableRegionStartTime = fragments[0].start
|
|
866
|
+
startTimeChanged = true
|
|
867
|
+
this._playableRegionStartTime = fragments[0].start
|
|
748
868
|
}
|
|
749
869
|
|
|
750
870
|
if (startTimeChanged) {
|
|
@@ -752,14 +872,15 @@ export default class HlsPlayback extends HTML5Video {
|
|
|
752
872
|
// set the correlation to map to middle of the extrapolation window
|
|
753
873
|
this._localStartTimeCorrelation = {
|
|
754
874
|
local: this._now,
|
|
755
|
-
remote:
|
|
756
|
-
|
|
875
|
+
remote:
|
|
876
|
+
(fragments[0].start + this._extrapolatedWindowDuration / 2) * 1000,
|
|
877
|
+
}
|
|
757
878
|
} else {
|
|
758
879
|
// check if the correlation still works
|
|
759
|
-
const corr = this._localStartTimeCorrelation
|
|
760
|
-
const timePassed = this._now - corr.local
|
|
880
|
+
const corr = this._localStartTimeCorrelation
|
|
881
|
+
const timePassed = this._now - corr.local
|
|
761
882
|
// this should point to a time within the extrapolation window
|
|
762
|
-
const startTime = (corr.remote + timePassed) / 1000
|
|
883
|
+
const startTime = (corr.remote + timePassed) / 1000
|
|
763
884
|
|
|
764
885
|
if (startTime < fragments[0].start) {
|
|
765
886
|
// our start time is now earlier than the first chunk
|
|
@@ -767,134 +888,150 @@ export default class HlsPlayback extends HTML5Video {
|
|
|
767
888
|
// reset correlation so that it sits at the beginning of the first available chunk
|
|
768
889
|
this._localStartTimeCorrelation = {
|
|
769
890
|
local: this._now,
|
|
770
|
-
remote: fragments[0].start * 1000
|
|
771
|
-
}
|
|
772
|
-
} else if (
|
|
891
|
+
remote: fragments[0].start * 1000,
|
|
892
|
+
}
|
|
893
|
+
} else if (
|
|
894
|
+
startTime >
|
|
895
|
+
previousPlayableRegionStartTime + this._extrapolatedWindowDuration
|
|
896
|
+
) {
|
|
773
897
|
// start time was past the end of the old extrapolation window (so would have been capped)
|
|
774
898
|
// see if now that time would be inside the window, and if it would be set the correlation
|
|
775
899
|
// so that it resumes from the time it was at at the end of the old window
|
|
776
900
|
// update the correlation so that the time starts counting again from the value it's on now
|
|
777
901
|
this._localStartTimeCorrelation = {
|
|
778
902
|
local: this._now,
|
|
779
|
-
remote:
|
|
780
|
-
|
|
903
|
+
remote:
|
|
904
|
+
Math.max(
|
|
905
|
+
fragments[0].start,
|
|
906
|
+
previousPlayableRegionStartTime +
|
|
907
|
+
this._extrapolatedWindowDuration,
|
|
908
|
+
) * 1000,
|
|
909
|
+
}
|
|
781
910
|
}
|
|
782
911
|
}
|
|
783
912
|
}
|
|
784
913
|
|
|
785
|
-
let newDuration = data.details.totalduration
|
|
914
|
+
let newDuration = data.details.totalduration
|
|
786
915
|
|
|
787
916
|
// if it's a live stream then shorten the duration to remove access
|
|
788
917
|
// to the area after hlsjs's live sync point
|
|
789
918
|
// seeks to areas after this point sometimes have issues
|
|
790
919
|
if (this._playbackType === Playback.LIVE) {
|
|
791
|
-
const fragmentTargetDuration = data.details.targetduration
|
|
792
|
-
const hlsjsConfig = this.options.playback.hlsjsConfig || {}
|
|
793
|
-
const liveSyncDurationCount =
|
|
794
|
-
|
|
920
|
+
const fragmentTargetDuration = data.details.targetduration
|
|
921
|
+
const hlsjsConfig = this.options.playback.hlsjsConfig || {}
|
|
922
|
+
const liveSyncDurationCount =
|
|
923
|
+
hlsjsConfig.liveSyncDurationCount ||
|
|
924
|
+
HLSJS.DefaultConfig.liveSyncDurationCount
|
|
925
|
+
const hiddenAreaDuration = fragmentTargetDuration * liveSyncDurationCount
|
|
795
926
|
|
|
796
927
|
if (hiddenAreaDuration <= newDuration) {
|
|
797
|
-
newDuration -= hiddenAreaDuration
|
|
798
|
-
this._durationExcludesAfterLiveSyncPoint = true
|
|
928
|
+
newDuration -= hiddenAreaDuration
|
|
929
|
+
this._durationExcludesAfterLiveSyncPoint = true
|
|
799
930
|
} else {
|
|
800
|
-
this._durationExcludesAfterLiveSyncPoint = false
|
|
931
|
+
this._durationExcludesAfterLiveSyncPoint = false
|
|
801
932
|
}
|
|
802
933
|
}
|
|
803
934
|
if (newDuration !== this._playableRegionDuration) {
|
|
804
|
-
durationChanged = true
|
|
805
|
-
this._playableRegionDuration = newDuration
|
|
935
|
+
durationChanged = true
|
|
936
|
+
this._playableRegionDuration = newDuration
|
|
806
937
|
}
|
|
807
938
|
// Note the end time is not the playableRegionDuration
|
|
808
939
|
// 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
|
-
|
|
940
|
+
const endTime = fragments[0].start + newDuration
|
|
941
|
+
const previousEndTime =
|
|
942
|
+
previousPlayableRegionStartTime + previousPlayableRegionDuration
|
|
943
|
+
const endTimeChanged = endTime !== previousEndTime
|
|
812
944
|
|
|
813
945
|
if (endTimeChanged) {
|
|
814
946
|
if (!this._localEndTimeCorrelation) {
|
|
815
947
|
// set the correlation to map to the end
|
|
816
948
|
this._localEndTimeCorrelation = {
|
|
817
949
|
local: this._now,
|
|
818
|
-
remote: endTime * 1000
|
|
819
|
-
}
|
|
950
|
+
remote: endTime * 1000,
|
|
951
|
+
}
|
|
820
952
|
} else {
|
|
821
953
|
// check if the correlation still works
|
|
822
|
-
const corr = this._localEndTimeCorrelation
|
|
823
|
-
const timePassed = this._now - corr.local
|
|
954
|
+
const corr = this._localEndTimeCorrelation
|
|
955
|
+
const timePassed = this._now - corr.local
|
|
824
956
|
// this should point to a time within the extrapolation window from the end
|
|
825
|
-
const extrapolatedEndTime = (corr.remote + timePassed) / 1000
|
|
957
|
+
const extrapolatedEndTime = (corr.remote + timePassed) / 1000
|
|
826
958
|
|
|
827
959
|
if (extrapolatedEndTime > endTime) {
|
|
828
960
|
this._localEndTimeCorrelation = {
|
|
829
961
|
local: this._now,
|
|
830
|
-
remote: endTime * 1000
|
|
831
|
-
}
|
|
832
|
-
} else if (
|
|
962
|
+
remote: endTime * 1000,
|
|
963
|
+
}
|
|
964
|
+
} else if (
|
|
965
|
+
extrapolatedEndTime <
|
|
966
|
+
endTime - this._extrapolatedWindowDuration
|
|
967
|
+
) {
|
|
833
968
|
// our extrapolated end time is now earlier than the extrapolation window from the actual end time
|
|
834
969
|
// (maybe a chunk became available early)
|
|
835
970
|
// reset correlation so that it sits at the beginning of the extrapolation window from the end time
|
|
836
971
|
this._localEndTimeCorrelation = {
|
|
837
972
|
local: this._now,
|
|
838
|
-
remote: (endTime - this._extrapolatedWindowDuration) * 1000
|
|
839
|
-
}
|
|
973
|
+
remote: (endTime - this._extrapolatedWindowDuration) * 1000,
|
|
974
|
+
}
|
|
840
975
|
} else if (extrapolatedEndTime > previousEndTime) {
|
|
841
976
|
// end time was past the old end time (so would have been capped)
|
|
842
977
|
// set the correlation so that it resumes from the time it was at at the end of the old window
|
|
843
978
|
this._localEndTimeCorrelation = {
|
|
844
979
|
local: this._now,
|
|
845
|
-
remote: previousEndTime * 1000
|
|
846
|
-
}
|
|
980
|
+
remote: previousEndTime * 1000,
|
|
981
|
+
}
|
|
847
982
|
}
|
|
848
983
|
}
|
|
849
984
|
}
|
|
850
985
|
|
|
851
986
|
// now that the values have been updated call any methods that use on them so they get the updated values
|
|
852
987
|
// immediately
|
|
853
|
-
durationChanged && this._onDurationChange()
|
|
854
|
-
startTimeChanged && this._onProgress()
|
|
988
|
+
durationChanged && this._onDurationChange()
|
|
989
|
+
startTimeChanged && this._onProgress()
|
|
855
990
|
}
|
|
856
991
|
|
|
857
992
|
_onFragmentChanged(evt: HlsEvents.FRAG_CHANGED, data: FragChangedData) {
|
|
858
|
-
this._currentFragment = data.frag
|
|
993
|
+
this._currentFragment = data.frag
|
|
859
994
|
// @ts-ignore
|
|
860
|
-
this.trigger(Events.Custom.PLAYBACK_FRAGMENT_CHANGED, data)
|
|
995
|
+
this.trigger(Events.Custom.PLAYBACK_FRAGMENT_CHANGED, data)
|
|
861
996
|
}
|
|
862
997
|
|
|
863
998
|
_onFragmentLoaded(evt: HlsEvents.FRAG_LOADED, data: FragLoadedData) {
|
|
864
|
-
this.trigger(Events.PLAYBACK_FRAGMENT_LOADED, data)
|
|
999
|
+
this.trigger(Events.PLAYBACK_FRAGMENT_LOADED, data)
|
|
865
1000
|
}
|
|
866
1001
|
|
|
867
1002
|
_onSubtitleLoaded() {
|
|
868
1003
|
// This event may be triggered multiple times
|
|
869
1004
|
// Setup CC only once (disable CC by default)
|
|
870
1005
|
if (!this._ccIsSetup) {
|
|
871
|
-
this.trigger(Events.PLAYBACK_SUBTITLE_AVAILABLE)
|
|
872
|
-
const trackId =
|
|
1006
|
+
this.trigger(Events.PLAYBACK_SUBTITLE_AVAILABLE)
|
|
1007
|
+
const trackId =
|
|
1008
|
+
this._playbackType === Playback.LIVE ? -1 : this.closedCaptionsTrackId
|
|
873
1009
|
|
|
874
|
-
this.closedCaptionsTrackId = trackId
|
|
875
|
-
this._ccIsSetup = true
|
|
1010
|
+
this.closedCaptionsTrackId = trackId
|
|
1011
|
+
this._ccIsSetup = true
|
|
876
1012
|
}
|
|
877
1013
|
}
|
|
878
1014
|
|
|
879
1015
|
_onLevelSwitch(evt: HlsEvents.LEVEL_SWITCHING, data: LevelSwitchingData) {
|
|
880
1016
|
if (!this.levels.length) {
|
|
881
|
-
this._fillLevels()
|
|
1017
|
+
this._fillLevels()
|
|
882
1018
|
}
|
|
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]
|
|
1019
|
+
this.trigger(Events.PLAYBACK_LEVEL_SWITCH_END)
|
|
1020
|
+
this.trigger(Events.PLAYBACK_LEVEL_SWITCH, data)
|
|
1021
|
+
assert(this._hls, 'Hls.js instance is not available')
|
|
1022
|
+
const currentLevel = this._hls.levels[data.level]
|
|
887
1023
|
|
|
888
1024
|
if (currentLevel) {
|
|
889
1025
|
// TODO should highDefinition be private and maybe have a read only accessor if it's used somewhere
|
|
890
|
-
this.highDefinition =
|
|
891
|
-
|
|
1026
|
+
this.highDefinition =
|
|
1027
|
+
currentLevel.height >= 720 || currentLevel.bitrate / 1000 >= 2000
|
|
1028
|
+
this.trigger(Events.PLAYBACK_HIGHDEFINITIONUPDATE, this.highDefinition)
|
|
892
1029
|
this.trigger(Events.PLAYBACK_BITRATE, {
|
|
893
1030
|
height: currentLevel.height,
|
|
894
1031
|
width: currentLevel.width,
|
|
895
1032
|
bitrate: currentLevel.bitrate,
|
|
896
|
-
level: data.level
|
|
897
|
-
})
|
|
1033
|
+
level: data.level,
|
|
1034
|
+
})
|
|
898
1035
|
}
|
|
899
1036
|
}
|
|
900
1037
|
|
|
@@ -903,24 +1040,41 @@ export default class HlsPlayback extends HTML5Video {
|
|
|
903
1040
|
// - the duration does not include content after hlsjs's live sync point
|
|
904
1041
|
// - the playable region duration is longer than the configured duration to enable dvr after
|
|
905
1042
|
// - the playback type is LIVE.
|
|
906
|
-
return (
|
|
1043
|
+
return (
|
|
1044
|
+
this._durationExcludesAfterLiveSyncPoint &&
|
|
1045
|
+
this._duration >= this._minDvrSize &&
|
|
1046
|
+
this.getPlaybackType() === Playback.LIVE
|
|
1047
|
+
)
|
|
907
1048
|
}
|
|
908
1049
|
|
|
909
1050
|
getPlaybackType() {
|
|
910
|
-
return this._playbackType
|
|
1051
|
+
return this._playbackType
|
|
911
1052
|
}
|
|
912
1053
|
|
|
913
1054
|
isSeekEnabled() {
|
|
914
|
-
return
|
|
1055
|
+
return this._playbackType === Playback.VOD || this.dvrEnabled
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
private triggerError(error: PlaybackError) {
|
|
1059
|
+
trace(`${T} triggerError`, { error })
|
|
1060
|
+
this.trigger(Events.PLAYBACK_ERROR, error)
|
|
1061
|
+
this.stop()
|
|
915
1062
|
}
|
|
916
1063
|
}
|
|
917
1064
|
|
|
918
1065
|
HlsPlayback.canPlay = function (resource: string, mimeType?: string): boolean {
|
|
919
|
-
const resourceParts = resource.split('?')[0].match(/.*\.(.*)$/) || []
|
|
920
|
-
const isHls =
|
|
921
|
-
|
|
1066
|
+
const resourceParts = resource.split('?')[0].match(/.*\.(.*)$/) || []
|
|
1067
|
+
const isHls =
|
|
1068
|
+
(resourceParts.length > 1 && resourceParts[1].toLowerCase() === 'm3u8') ||
|
|
1069
|
+
listContainsIgnoreCase(mimeType, [
|
|
1070
|
+
'application/vnd.apple.mpegurl',
|
|
1071
|
+
'application/x-mpegURL',
|
|
1072
|
+
])
|
|
1073
|
+
const hasSupport = HLSJS.isSupported()
|
|
922
1074
|
trace(`${T} canPlay`, {
|
|
923
|
-
hasSupport,
|
|
1075
|
+
hasSupport,
|
|
1076
|
+
isHls,
|
|
1077
|
+
resource,
|
|
924
1078
|
})
|
|
925
|
-
return !!(hasSupport && isHls)
|
|
926
|
-
}
|
|
1079
|
+
return !!(hasSupport && isHls)
|
|
1080
|
+
}
|