@gcorevideo/player 2.9.0 → 2.12.1

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