@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.
Files changed (65) hide show
  1. package/LICENSE +13 -0
  2. package/coverage/clover.xml +6 -0
  3. package/coverage/coverage-final.json +1 -0
  4. package/coverage/lcov-report/base.css +224 -0
  5. package/coverage/lcov-report/block-navigation.js +87 -0
  6. package/coverage/lcov-report/favicon.png +0 -0
  7. package/coverage/lcov-report/index.html +101 -0
  8. package/coverage/lcov-report/prettify.css +1 -0
  9. package/coverage/lcov-report/prettify.js +2 -0
  10. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  11. package/coverage/lcov-report/sorter.js +196 -0
  12. package/coverage/lcov.info +0 -0
  13. package/dist/index.js +3115 -1209
  14. package/lib/Player.d.ts +3 -2
  15. package/lib/Player.d.ts.map +1 -1
  16. package/lib/Player.js +32 -24
  17. package/lib/index.d.ts +5 -5
  18. package/lib/index.d.ts.map +1 -1
  19. package/lib/index.js +5 -5
  20. package/lib/internal.types.d.ts +1 -10
  21. package/lib/internal.types.d.ts.map +1 -1
  22. package/lib/playback/index.d.ts +4 -0
  23. package/lib/playback/index.d.ts.map +1 -0
  24. package/lib/playback/index.js +13 -0
  25. package/lib/playback.types.d.ts +19 -0
  26. package/lib/playback.types.d.ts.map +1 -1
  27. package/lib/playback.types.js +9 -1
  28. package/lib/plugins/dash-playback/DashPlayback.d.ts +1 -1
  29. package/lib/plugins/dash-playback/DashPlayback.d.ts.map +1 -1
  30. package/lib/plugins/dash-playback/DashPlayback.js +39 -100
  31. package/lib/plugins/dash-playback/types.d.ts +6 -0
  32. package/lib/plugins/dash-playback/types.d.ts.map +1 -0
  33. package/lib/plugins/dash-playback/types.js +1 -0
  34. package/lib/plugins/hls-playback/HlsPlayback.d.ts +6 -7
  35. package/lib/plugins/hls-playback/HlsPlayback.d.ts.map +1 -1
  36. package/lib/plugins/hls-playback/HlsPlayback.js +131 -80
  37. package/lib/types.d.ts +3 -3
  38. package/lib/types.d.ts.map +1 -1
  39. package/lib/utils/mediaSources.d.ts +14 -6
  40. package/lib/utils/mediaSources.d.ts.map +1 -1
  41. package/lib/utils/mediaSources.js +56 -53
  42. package/lib/utils/testUtils.d.ts +3 -0
  43. package/lib/utils/testUtils.d.ts.map +1 -0
  44. package/lib/utils/testUtils.js +12 -0
  45. package/package.json +6 -4
  46. package/src/Player.ts +40 -31
  47. package/src/__tests__/Player.test.ts +357 -0
  48. package/src/index.ts +5 -5
  49. package/src/internal.types.ts +1 -12
  50. package/src/playback/index.ts +17 -0
  51. package/src/playback.types.ts +29 -8
  52. package/src/plugins/dash-playback/DashPlayback.ts +44 -120
  53. package/src/plugins/hls-playback/HlsPlayback.ts +544 -390
  54. package/src/types.ts +5 -3
  55. package/src/typings/@clappr/core/error_mixin.d.ts +0 -2
  56. package/src/typings/@clappr/core/index.d.ts +5 -0
  57. package/src/typings/@clappr/index.d.ts +1 -0
  58. package/src/utils/__tests__/mediaSources.test.ts +230 -0
  59. package/src/utils/mediaSources.ts +78 -64
  60. package/src/utils/testUtils.ts +15 -0
  61. package/tsconfig.json +0 -9
  62. package/tsconfig.tsbuildinfo +1 -1
  63. package/vitest.config.ts +8 -0
  64. package/licenses.json +0 -782
  65. 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 { Events, HTML5Video, Log, Playback, PlayerError, Utils } from '@clappr/core';
6
- import assert from 'assert';
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 { trace } from '@gcorevideo/utils';
22
- import { PlaybackType } from '../../types';
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 "../../build.js";
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; // TODO
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: string | null = null;
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 (this._playbackType === Playback.LIVE && this._playlistType !== 'EVENT') {
160
- return this._extrapolatedStartTime;
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(extrapolatedWindowStartTime, this._playableRegionStartTime + this._extrapolatedWindowDuration);
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 = this._playableRegionStartTime + this._playableRegionDuration;
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(actualEndTime - this._extrapolatedWindowDuration, Math.min(extrapolatedEndTime, actualEndTime));
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 this.options.hlsPlayback && this.options.hlsPlayback.customListeners || [];
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(this._currentFragment.programDateTime !== null, 'Hls.js programDateTime is not defined');
250
- const startTime = this._currentFragment.programDateTime;
251
- const playbackTime = (this.el as HTMLMediaElement).currentTime;
252
- const playTimeOffSet = playbackTime - this._currentFragment.start;
253
- const currentTimestampInMs = startTime + playTimeOffSet * 1000;
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 = { ...this.defaultOptions, ...this.options.hlsPlayback };
266
- this._setInitialState();
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 = typeof (this.options.hlsMinimumDvrSize) === 'undefined' ? 60 : this.options.hlsMinimumDvrSize;
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 = !this.options.playback || typeof (this.options.playback.extrapolatedWindowNumSegments) === 'undefined' ? 2 : this.options.playback.extrapolatedWindowNumSegments;
277
-
278
- this._playbackType = Playback.VOD as PlaybackType;
279
- this._lastTimeUpdate = { current: 0, total: 0 };
280
- this._lastDuration = null;
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 = this.options.hlsRecoverAttempts || DEFAULT_RECOVER_ATTEMPTS;
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 = this.options.hlsPlayback.maxBufferLength || 30;
360
- this._hls.config.maxMaxBufferLength = this.options.hlsPlayback.maxMaxBufferLength || 60;
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(HLSJS.Events.MANIFEST_PARSED, () => this._manifestParsed = true);
368
- this._hls.on(HLSJS.Events.LEVEL_LOADED, (evt: HlsEvents.LEVEL_LOADED, data: LevelLoadedData) => this._updatePlaybackType(evt, data));
369
- this._hls.on(HLSJS.Events.LEVEL_UPDATED, (evt: HlsEvents.LEVEL_UPDATED, data: LevelUpdatedData) => this._onLevelUpdated(evt, data));
370
- this._hls.on(HLSJS.Events.LEVEL_SWITCHING, (evt: HlsEvents.LEVEL_SWITCHING, data: LevelSwitchingData) => this._onLevelSwitch(evt, data));
371
- this._hls.on(HLSJS.Events.FRAG_CHANGED, (evt: HlsEvents.FRAG_CHANGED, data: FragChangedData) => this._onFragmentChanged(evt, data));
372
- this._hls.on(HLSJS.Events.FRAG_LOADED, (evt: HlsEvents.FRAG_LOADED, data: FragLoadedData) => this._onFragmentLoaded(evt, data));
373
- this._hls.on(HLSJS.Events.FRAG_PARSING_METADATA, (evt: HlsEvents.FRAG_PARSING_METADATA, data: FragParsingMetadataData) => this._onFragmentParsingMetadata(evt, data));
374
- this._hls.on(HLSJS.Events.ERROR, (evt, data) => this._onHLSJSError(evt, data));
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, () => this._onSubtitleLoaded());
377
- this._hls.on(HLSJS.Events.SUBTITLE_TRACKS_UPDATED, () => this._ccTracksUpdated = true);
378
- this.bindCustomListeners();
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 && this._hls[`${typeOfListener}`](requestedEventName, item.callback);
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(evt: HlsEvents.FRAG_PARSING_METADATA, data: FragParsingMetadataData) {
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, { evt, data });
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: ErrorInfo) {
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.trigger(Events.PLAYBACK_ERROR, formattedError);
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
- private override _setupSrc(srcUrl: string) { } // eslint-disable-line no-unused-vars
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(0, (this.el as HTMLMediaElement).currentTime - this._startTime);
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 = (percentage > 0)
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('Attempt to seek to a negative time. Resetting to live point. Use seekToLivePoint() to seek to the live point.');
496
- time = this.getDuration();
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, { 'dvr': status });
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
- // @ts-expect-error
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: Record<string, unknown> = {
532
- code: `${data.type}_${data.details}`,
533
- description: `${this.name} error: type: ${data.type}, details: ${data.details}`,
534
- raw: data,
535
- };
536
- let formattedError;
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.', { evt, data });
558
- formattedError = this.createError(error);
559
- this.trigger(Events.PLAYBACK_ERROR, formattedError);
560
- this.stop();
561
- break;
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.', { evt, data });
564
- error.level = PlayerError.Levels.WARN;
565
- assert(this._hls, 'Hls.js instance is not available');
566
- this._hls.startLoad();
567
- break;
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.', { evt, data });
572
- error.level = PlayerError.Levels.WARN;
573
- this._recover(evt, data, error);
574
- break;
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
- formattedError = this.createError(error);
578
- this.trigger(Events.PLAYBACK_ERROR, formattedError);
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('hlsjs: could not recover from error after maximum number of attempts.', { evt, data });
584
- formattedError = this.createError(error);
585
- this.trigger(Events.PLAYBACK_ERROR, formattedError);
586
- this.stop();
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 (this.options.playback.triggerFatalErrorOnResourceDenied && this._keyIsDenied(data)) {
594
- Log.error('hlsjs: could not load decrypt key.', { evt, data });
595
- formattedError = this.createError(error);
596
- this.trigger(Events.PLAYBACK_ERROR, formattedError);
597
- this.stop();
598
-
599
- return;
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
- error.level = PlayerError.Levels.WARN;
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 data.type === HLSJS.ErrorTypes.NETWORK_ERROR
609
- && data.details === HLSJS.ErrorDetails.KEY_LOAD_ERROR
610
- && data.response
611
- && data.response.code
612
- && data.response.code >= 400;
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 = { current: this.getCurrentTime(), total: this.getDuration(), firstFragDateTime: this.getProgramDateTime() };
617
- const isSame = this._lastTimeUpdate && (
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 = [...buffered, {
647
- // for a stream with sliding window dvr something that is buffered my slide off the start of the timeline
648
- start: Math.max(0, (this.el as HTMLMediaElement).buffered.start(i) - this._playableRegionStartTime),
649
- end: Math.max(0, (this.el as HTMLMediaElement).buffered.end(i) - this._playableRegionStartTime)
650
- }];
651
- if ((this.el as HTMLMediaElement).currentTime >= buffered[i].start && (this.el as HTMLMediaElement).currentTime <= buffered[i].end) {
652
- bufferedPos = i;
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 && !this.options.hlsPlayback.preload && this._hls.loadSource(this.options.src);
675
- super.play();
676
- this._startTimeUpdateTimer();
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(evt: HlsEvents.LEVEL_LOADED, data: LevelLoadedData) {
704
- const prevPlaybackType = this._playbackType;
705
- this._playbackType = (data.details.live ? Playback.LIVE : Playback.VOD) as PlaybackType;
706
- this._onLevelUpdated(evt, data);
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 (this._ccTracksUpdated && this._playbackType === Playback.LIVE && this.hasClosedCaptionsTracks) {
709
- this._onSubtitleLoaded();
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(evt: HlsEvents.LEVEL_UPDATED | HlsEvents.LEVEL_LOADED, data: LevelUpdatedData) {
730
- this._segmentTargetDuration = data.details.targetduration;
731
- this._playlistType = (data.details.type as PlaylistType) || null;
732
- let startTimeChanged = false;
733
- let durationChanged = false;
734
- const fragments = data.details.fragments;
735
- const previousPlayableRegionStartTime = this._playableRegionStartTime;
736
- const previousPlayableRegionDuration = this._playableRegionDuration;
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: (fragments[0].start + (this._extrapolatedWindowDuration / 2)) * 1000
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 (startTime > previousPlayableRegionStartTime + this._extrapolatedWindowDuration) {
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: Math.max(fragments[0].start, previousPlayableRegionStartTime + this._extrapolatedWindowDuration) * 1000
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 = hlsjsConfig.liveSyncDurationCount || HLSJS.DefaultConfig.liveSyncDurationCount;
794
- const hiddenAreaDuration = fragmentTargetDuration * liveSyncDurationCount;
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 = previousPlayableRegionStartTime + previousPlayableRegionDuration;
811
- const endTimeChanged = endTime !== previousEndTime;
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 (extrapolatedEndTime < endTime - this._extrapolatedWindowDuration) {
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 = this._playbackType === Playback.LIVE ? -1 : this.closedCaptionsTrackId;
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 = (currentLevel.height >= 720 || (currentLevel.bitrate / 1000) >= 2000);
891
- this.trigger(Events.PLAYBACK_HIGHDEFINITIONUPDATE, this.highDefinition);
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 (this._durationExcludesAfterLiveSyncPoint && this._duration >= this._minDvrSize && this.getPlaybackType() === Playback.LIVE);
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 (this._playbackType === Playback.VOD || this.dvrEnabled);
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 = ((resourceParts.length > 1 && resourceParts[1].toLowerCase() === 'm3u8') || listContainsIgnoreCase(mimeType, ['application/vnd.apple.mpegurl', 'application/x-mpegURL']));
921
- const hasSupport = HLSJS.isSupported();
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, isHls, resource,
1075
+ hasSupport,
1076
+ isHls,
1077
+ resource,
924
1078
  })
925
- return !!(hasSupport && isHls);
926
- };
1079
+ return !!(hasSupport && isHls)
1080
+ }