@codercms/web-player 0.0.37 → 0.0.40

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 (62) hide show
  1. package/dist/HybridDoublyLinkedList.js.map +1 -1
  2. package/dist/HybridLinkedList.js.map +1 -1
  3. package/dist/core/AudioCtx/AudioContext.d.ts +2 -0
  4. package/dist/core/AudioCtx/AudioContext.js +92 -0
  5. package/dist/core/AudioCtx/AudioContext.js.map +1 -0
  6. package/dist/core/Loaders/BaseLoader.d.ts +17 -0
  7. package/dist/core/Loaders/BaseLoader.js +50 -0
  8. package/dist/core/Loaders/BaseLoader.js.map +1 -0
  9. package/dist/core/Loaders/HLSLoader.d.ts +17 -0
  10. package/dist/core/Loaders/HLSLoader.js +313 -0
  11. package/dist/core/Loaders/HLSLoader.js.map +1 -0
  12. package/dist/core/{LoaderInterface.d.ts → Loaders/LoaderInterface.d.ts} +11 -3
  13. package/dist/core/{LoaderInterface.js → Loaders/LoaderInterface.js} +6 -0
  14. package/dist/core/{LoaderInterface.js.map → Loaders/LoaderInterface.js.map} +1 -1
  15. package/dist/core/Loaders/NativeAudioTrackList.d.ts +39 -0
  16. package/dist/core/Loaders/NativeAudioTrackList.js.map +1 -0
  17. package/dist/core/Loaders/NativeLoader.d.ts +16 -0
  18. package/dist/core/Loaders/NativeLoader.js +114 -0
  19. package/dist/core/Loaders/NativeLoader.js.map +1 -0
  20. package/dist/core/Playlist.d.ts +4 -3
  21. package/dist/core/Playlist.js.map +1 -1
  22. package/dist/core/PlaylistPlayer.d.ts +3 -2
  23. package/dist/core/PlaylistPlayer.js +5 -3
  24. package/dist/core/PlaylistPlayer.js.map +1 -1
  25. package/dist/core/VideoPlayer.d.ts +22 -184
  26. package/dist/core/VideoPlayer.js +102 -577
  27. package/dist/core/VideoPlayer.js.map +1 -1
  28. package/dist/core/VideoPlayerEvents.d.ts +74 -0
  29. package/dist/core/VideoPlayerEvents.js +40 -0
  30. package/dist/core/VideoPlayerEvents.js.map +1 -0
  31. package/dist/core/VideoPlayerMediaLoadManager.d.ts +50 -0
  32. package/dist/core/VideoPlayerMediaLoadManager.js +269 -0
  33. package/dist/core/VideoPlayerMediaLoadManager.js.map +1 -0
  34. package/dist/core/VideoPlayerMediaState.d.ts +39 -0
  35. package/dist/core/VideoPlayerMediaState.js +161 -0
  36. package/dist/core/VideoPlayerMediaState.js.map +1 -0
  37. package/dist/core/types/BufferedPart.d.ts +4 -0
  38. package/dist/core/types/BufferedPart.js +1 -0
  39. package/dist/core/types/BufferedPart.js.map +1 -0
  40. package/dist/core/types/PlayerLoadParams.d.ts +11 -0
  41. package/dist/core/types/PlayerLoadParams.js +1 -0
  42. package/dist/core/types/PlayerLoadParams.js.map +1 -0
  43. package/dist/core/types/QualityLevel.js +1 -0
  44. package/dist/index.d.ts +9 -4
  45. package/dist/index.js +9 -4
  46. package/dist/ui/LoadingIndicator.svelte +35 -16
  47. package/dist/ui/LoadingIndicator.svelte.d.ts +1 -1
  48. package/dist/ui/ProgressBar.svelte +6 -6
  49. package/dist/ui/ProgressBar.svelte.d.ts +1 -1
  50. package/package.json +1 -1
  51. package/dist/core/HLSLoader.d.ts +0 -19
  52. package/dist/core/HLSLoader.js +0 -196
  53. package/dist/core/HLSLoader.js.map +0 -1
  54. package/dist/core/NativeLoader.d.ts +0 -14
  55. package/dist/core/NativeLoader.js +0 -66
  56. package/dist/core/NativeLoader.js.map +0 -1
  57. /package/dist/core/{AudioTrack.js → Loaders/NativeAudioTrackList.js} +0 -0
  58. /package/dist/core/{AudioTrack.d.ts → types/AudioTrack.d.ts} +0 -0
  59. /package/dist/core/{QualityLevel.js → types/AudioTrack.js} +0 -0
  60. /package/dist/core/{AudioTrack.js.map → types/AudioTrack.js.map} +0 -0
  61. /package/dist/core/{QualityLevel.d.ts → types/QualityLevel.d.ts} +0 -0
  62. /package/dist/core/{QualityLevel.js.map → types/QualityLevel.js.map} +0 -0
@@ -1,124 +1,26 @@
1
1
  import { EventEmitter } from 'eventemitter3';
2
- import { readonly, writable } from 'svelte/store';
3
- import { HLSLoader, isHlsJsSupported } from './HLSLoader.js';
4
- import { NativeLoader } from './NativeLoader.js';
5
- import { LoaderEvents } from './LoaderInterface.js';
6
- import { DOMEventRegistry } from './DOMEventRegistry.js';
7
2
  import screenfull from 'screenfull';
8
3
  import { BROWSER } from 'esm-env';
4
+ import { HLSLoader, IS_HLS_JS_SUPPORTED } from './Loaders/HLSLoader.js';
5
+ import { NativeLoader } from './Loaders/NativeLoader.js';
6
+ import { DOMEventRegistry } from './DOMEventRegistry.js';
7
+ import { PlayerEvents } from './VideoPlayerEvents.js';
8
+ import { VideoPlayerMediaState } from './VideoPlayerMediaState.js';
9
9
  import { IS_IOS, IS_IPAD_NEW } from '../ui/utils/ios.js';
10
- export var PlayerEvents;
11
- (function (PlayerEvents) {
12
- PlayerEvents["Play"] = "player.play";
13
- PlayerEvents["Pause"] = "player.pause";
14
- PlayerEvents["Ended"] = "player.ended";
15
- PlayerEvents["Error"] = "player.error";
16
- PlayerEvents["Playing"] = "player.playing";
17
- PlayerEvents["Seeking"] = "player.seeking";
18
- PlayerEvents["Seeked"] = "player.seeked";
19
- PlayerEvents["MediaElementAttached"] = "player.mediaElementAttached";
20
- PlayerEvents["MediaElementDetached"] = "player.mediaElementDetached";
21
- PlayerEvents["ContainerElementAttached"] = "player.containerElementAttached";
22
- PlayerEvents["ContainerElementDetached"] = "player.containerElementDetached";
23
- PlayerEvents["FullScreenEnter"] = "player.fullscreenEnter";
24
- PlayerEvents["FullScreenExit"] = "player.fullscreenExit";
25
- PlayerEvents["FullScreenToggle"] = "player.fullscreenToggle";
26
- // LoadingStarted happens when player's load method called
27
- PlayerEvents["LoadingStarted"] = "player.loadingStarted";
28
- // LoadingMetadataEnded happens when loader process has been completed
29
- PlayerEvents["LoadingMetadataEnded"] = "player.loadingMetadataEnded";
30
- // LoadingMetadataFailed happens when loader fails to load video
31
- PlayerEvents["LoadingMetadataFailed"] = "player.loadingMedataFailed";
32
- // LoadingEnded happens when metadata loading is done and there's also initial video content fetched
33
- PlayerEvents["LoadingEnded"] = "player.loadingEnded";
34
- PlayerEvents["BufferingStarted"] = "player.bufferingStarted";
35
- PlayerEvents["BufferingEnded"] = "player.bufferingEnded";
36
- PlayerEvents["PlaybackBufferingProgress"] = "player.bufferingProgress";
37
- PlayerEvents["PlaybackDurationChange"] = "player.durationChange";
38
- PlayerEvents["PlaybackTimeUpdate"] = "player.timeUpdate";
39
- PlayerEvents["PlaybackRateChange"] = "player.playbackRateChange";
40
- PlayerEvents["VolumeChange"] = "player.volumeChange";
41
- PlayerEvents["MuteChange"] = "player.muteChange";
42
- PlayerEvents["VolumeStateChange"] = "player.volumeStateChange";
43
- PlayerEvents["QualityLevelsUpdated"] = "player.qualityLevelsUpdated";
44
- PlayerEvents["QualityLevelSwitching"] = "player.qualityLevelSwitching";
45
- PlayerEvents["QualityLevelSwitched"] = "player.qualityLevelSwitched";
46
- PlayerEvents["AudioTracksUpdated"] = "player.audioTracksUpdated";
47
- PlayerEvents["AudioTrackSwitching"] = "player.audioTrackSwitching";
48
- PlayerEvents["AudioTrackSwitched"] = "player.audioTrackSwitched";
49
- })(PlayerEvents || (PlayerEvents = {}));
50
- export var Loader;
51
- (function (Loader) {
52
- Loader[Loader["Native"] = 0] = "Native";
53
- Loader[Loader["HLS"] = 1] = "HLS";
54
- Loader[Loader["DASH"] = 2] = "DASH";
55
- })(Loader || (Loader = {}));
56
- async function getLoadUrl(paramsUrl, signal) {
57
- let url;
58
- if (typeof paramsUrl === 'string') {
59
- url = paramsUrl;
60
- }
61
- else if (typeof paramsUrl === 'function') {
62
- url = await paramsUrl(signal);
63
- }
64
- else {
65
- url = await paramsUrl;
66
- }
67
- if (!url || !url.length) {
68
- throw Error('Bad load url');
69
- }
70
- return url;
71
- }
72
- export var VideoLoadingState;
73
- (function (VideoLoadingState) {
74
- VideoLoadingState[VideoLoadingState["LoadingMetadata"] = 0] = "LoadingMetadata";
75
- VideoLoadingState[VideoLoadingState["LoadingData"] = 1] = "LoadingData";
76
- VideoLoadingState[VideoLoadingState["LoadingDone"] = 2] = "LoadingDone";
77
- VideoLoadingState[VideoLoadingState["LoadingFailed"] = 3] = "LoadingFailed";
78
- })(VideoLoadingState || (VideoLoadingState = {}));
10
+ import { VideoLoadingState, VideoPlayerMediaLoadManager } from './VideoPlayerMediaLoadManager.js';
11
+ export { IS_AUDIO_CONTEXT_SUPPORTED, IS_AUDIO_GAIN_SUPPORTED } from './AudioCtx/AudioContext.js';
12
+ export const IS_FULLSCREEN_ALLOWED = BROWSER && screenfull.isEnabled;
13
+ export const IS_VOLUME_CHANGE_ALLOWED = BROWSER &&
14
+ Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'volume')?.set !== undefined;
79
15
  export class VideoPlayer {
80
- static _isFullScreenAllowed = screenfull.isEnabled;
81
16
  _el;
82
17
  _containerEl;
83
18
  elEventsRegistry;
84
19
  _cfg;
85
20
  emitter = new EventEmitter();
86
- loadAbortController = null;
87
- currentLoader = null;
88
- hlsLoader = null;
89
- nativeLoader = null;
90
- _loadingState = VideoLoadingState.LoadingDone;
91
- // _isBuffering indicates is there and buffering process which block playback
92
- _isBuffering = false;
93
- // _isLoading flag indicates combined state of buffering + video loading progress
94
- _isLoading = false;
21
+ _mediaState = new VideoPlayerMediaState(this.emitter);
22
+ _loadingMgr = new VideoPlayerMediaLoadManager(this.emitter, this._mediaState);
95
23
  _isFullScreen = false;
96
- _paused = true;
97
- _duration = 0;
98
- _currentTime = 0;
99
- _playbackRate = 0;
100
- _muted = false;
101
- _volume = 1;
102
- _buffered = [];
103
- _isAutoQuality = true;
104
- _currentQualityLevel = null;
105
- _qualityLevels = [];
106
- _currentAudioTrack = null;
107
- _audioTracks = [];
108
- _isFullScreenStore = writable(this._isFullScreen);
109
- _isLoadingStore = writable(this._isLoading);
110
- _pausedStore = writable(this._paused);
111
- _durationStore = writable(this._duration);
112
- _currentTimeStore = writable(this._currentTime);
113
- _playbackRateStore = writable(this._playbackRate);
114
- _mutedStore = writable(this._muted);
115
- _volumeStore = writable(this._volume);
116
- _bufferedStore = writable(this._buffered);
117
- _isAutoQualityStore = writable(this._isAutoQuality);
118
- _currentQualityLevelStore = writable(this._currentQualityLevel);
119
- _qualityLevelsStore = writable(this._qualityLevels);
120
- _currentAudioTrackStore = writable(this._currentAudioTrack);
121
- _audioTracksStore = writable(this._audioTracks);
122
24
  constructor(el, cfg) {
123
25
  this._cfg = cfg ?? {};
124
26
  // Do nothing in SSR environment
@@ -128,27 +30,22 @@ export class VideoPlayer {
128
30
  if (el) {
129
31
  this.attachEl(el);
130
32
  }
131
- if (VideoPlayer._isFullScreenAllowed) {
33
+ if (IS_FULLSCREEN_ALLOWED) {
132
34
  screenfull.on('change', this._onFullScreenChange.bind(this));
133
35
  }
134
- this.emitter.on(LoaderEvents.QualityLevelsUpdated, this._onLoaderQualityLevelsUpdated.bind(this));
135
- this.emitter.on(LoaderEvents.QualityLevelSwitching, this._onLoaderQualityLevelSwitching.bind(this));
136
- this.emitter.on(LoaderEvents.QualityLevelSwitched, this._onLoaderQualityLevelSwitched.bind(this));
137
- this.emitter.on(LoaderEvents.AudioTracksUpdated, this._onLoaderAudioTracksUpdated.bind(this));
138
- this.emitter.on(LoaderEvents.AudioTrackSwitching, this._onLoaderAudioTrackSwitching.bind(this));
139
- this.emitter.on(LoaderEvents.AudioTrackSwitched, this._onLoaderAudioTrackSwitched.bind(this));
140
36
  }
141
37
  _registerVideoEvents(el) {
142
38
  this.elEventsRegistry?.unsubscribeAll();
143
39
  this.elEventsRegistry = new DOMEventRegistry(el);
144
40
  this.elEventsRegistry.subscribe('play', () => {
145
- this._updatePaused(el.paused);
41
+ this._mediaState.updatePaused(el.paused);
146
42
  });
147
43
  this.elEventsRegistry.subscribe('pause', () => {
148
- this._updatePaused(el.paused);
44
+ this._mediaState.updatePaused(el.paused);
149
45
  });
150
46
  this.elEventsRegistry.subscribe('playing', () => {
151
- this._onBufferingEnded();
47
+ this._mediaState.updatePaused(el.paused);
48
+ this._mediaState.updateBuffering(false);
152
49
  this.emitter.emit(PlayerEvents.Playing);
153
50
  });
154
51
  this.elEventsRegistry.subscribe('seeking', () => {
@@ -158,220 +55,46 @@ export class VideoPlayer {
158
55
  this.emitter.emit(PlayerEvents.Seeked);
159
56
  });
160
57
  this.elEventsRegistry.subscribe('waiting', () => {
161
- this._onBufferingStarted();
58
+ this._mediaState.updateBuffering(true);
162
59
  });
60
+ // this.elEventsRegistry.subscribe('stalled', () => {
61
+ // this._mediaState.updateBuffering(true);
62
+ // });
163
63
  this.elEventsRegistry.subscribe('ended', () => {
164
- this._onBufferingEnded();
64
+ this._mediaState.updateBuffering(false);
165
65
  this.emitter.emit(PlayerEvents.Ended);
166
66
  });
167
67
  this.elEventsRegistry.subscribe('progress', () => {
168
- this._onProgress(el);
68
+ this._mediaState.updateBufferedFromTimeRanges(el.buffered);
169
69
  });
170
70
  this.elEventsRegistry.subscribe('canplaythrough', () => {
171
- this._onBufferingEnded();
71
+ this._mediaState.updateBuffering(false);
172
72
  });
173
73
  this.elEventsRegistry.subscribe('durationchange', () => {
174
- let newDur = el.duration;
175
- if (!isFinite(newDur) || isNaN(newDur)) {
176
- newDur = 0;
177
- }
178
- if (this._duration !== newDur) {
179
- this._updateDuration(newDur);
180
- }
74
+ this._mediaState.updateDuration(el.duration);
181
75
  });
182
76
  this.elEventsRegistry.subscribe('timeupdate', () => {
183
- let newTime = el.currentTime;
184
- if (!isFinite(newTime) || isNaN(newTime)) {
185
- newTime = 0;
186
- }
187
- if (this._currentTime !== newTime) {
188
- this._updateCurrentTime(newTime);
189
- }
77
+ this._mediaState.updateCurrentTime(el.currentTime);
190
78
  // Buffered parts may not be dispatched in on progress event
191
79
  // so take care that there is always fresh buffered parts
192
- this._onProgress(el);
80
+ this._mediaState.updateBufferedFromTimeRanges(el.buffered);
193
81
  });
194
82
  this.elEventsRegistry.subscribe('ratechange', () => {
195
- const newRate = el.playbackRate;
196
- if (this._playbackRate !== newRate) {
197
- this._updatePlaybackRate(newRate);
198
- }
83
+ this._mediaState.updatePlaybackRate(el.playbackRate);
199
84
  });
200
85
  this.elEventsRegistry.subscribe('volumechange', () => {
201
- const newVol = el.volume;
202
- const newMuted = el.muted;
203
- const isVolChanged = this._volume !== newVol;
204
- const isMuteChanged = this._muted !== newMuted;
205
- if (isVolChanged) {
206
- this._updateVolume(newVol);
207
- }
208
- if (isMuteChanged) {
209
- this._updateMuted(newMuted);
210
- }
211
- if (isVolChanged || isMuteChanged) {
212
- this.emitter.emit(PlayerEvents.VolumeStateChange, newVol, newMuted);
213
- }
86
+ this._mediaState.updateVolume(el.volume, el.muted);
214
87
  });
215
88
  // this.elEventsRegistry.subscribe('emptied', () => {
216
89
  // this._resetState();
217
90
  // });
218
91
  this.elEventsRegistry.subscribe('error', (ev) => {
219
- this.emitter.emit(PlayerEvents.Error, ev.error ?? 'unknown playback error');
92
+ this.emitter.emit(PlayerEvents.Error, ev.error ?? new Error('Unknown playback error'));
220
93
  });
221
94
  }
222
95
  _unregisterVideoElementEvents() {
223
96
  this.elEventsRegistry?.unsubscribeAll();
224
97
  }
225
- _onBufferingStarted() {
226
- if (!this._isBuffering) {
227
- this._isBuffering = true;
228
- this._calcIsLoadingState();
229
- this.emitter.emit(PlayerEvents.BufferingStarted);
230
- }
231
- }
232
- _onBufferingEnded() {
233
- if (this._isBuffering) {
234
- this._isBuffering = false;
235
- if (this._loadingState === VideoLoadingState.LoadingData) {
236
- this._loadingState = VideoLoadingState.LoadingDone;
237
- this.emitter.emit(PlayerEvents.LoadingEnded);
238
- }
239
- this._calcIsLoadingState();
240
- this.emitter.emit(PlayerEvents.BufferingEnded);
241
- }
242
- }
243
- _calcIsLoadingState() {
244
- const isFinishedVideoLoading = this._loadingState === VideoLoadingState.LoadingDone ||
245
- this._loadingState === VideoLoadingState.LoadingFailed;
246
- this._isLoading = !isFinishedVideoLoading || this._isBuffering;
247
- this._isLoadingStore.set(this._isLoading);
248
- }
249
- _onProgress(el) {
250
- const newBuf = el.buffered;
251
- const parts = [];
252
- if (newBuf.length !== this._buffered.length) {
253
- for (let i = 0; i < newBuf.length; i++) {
254
- parts.push({ start: newBuf.start(i), end: newBuf.end(i) });
255
- }
256
- this._updateBufferedParts(parts);
257
- return;
258
- }
259
- let isChanged = false;
260
- for (let i = 0; i < newBuf.length; i++) {
261
- const start = newBuf.start(i);
262
- const end = newBuf.end(i);
263
- isChanged = start !== this._buffered[i].start || end !== this._buffered[i].end;
264
- parts.push({ start, end });
265
- }
266
- if (!isChanged) {
267
- return;
268
- }
269
- this._updateBufferedParts(parts);
270
- }
271
- _resetState(newLoadingState) {
272
- this._updateAudioTracks([]);
273
- this._updateQualityLevels([]);
274
- this._updateDuration(0);
275
- this._updateCurrentTime(0);
276
- this._updateBufferedParts([]);
277
- this._currentQualityLevel = null;
278
- this._currentQualityLevelStore.set(null);
279
- this._currentAudioTrack = null;
280
- this._currentAudioTrackStore.set(null);
281
- if (newLoadingState !== undefined) {
282
- this._loadingState = newLoadingState;
283
- }
284
- else if (this._loadingState !== VideoLoadingState.LoadingDone &&
285
- this._loadingState !== VideoLoadingState.LoadingFailed) {
286
- this._loadingState = VideoLoadingState.LoadingDone;
287
- }
288
- this._onBufferingEnded();
289
- this._calcIsLoadingState();
290
- }
291
- _refreshState() {
292
- const el = this.el;
293
- let currTime = el.currentTime;
294
- if (isNaN(currTime) || !isFinite(currTime)) {
295
- currTime = 0;
296
- }
297
- let duration = el.duration;
298
- if (isNaN(duration) || !isFinite(duration)) {
299
- duration = 0;
300
- }
301
- if (el.paused !== this._paused) {
302
- this._updatePaused(el.paused);
303
- }
304
- if (el.muted !== this._muted) {
305
- this._updateMuted(el.muted);
306
- }
307
- if (el.volume !== this._volume) {
308
- this._updateVolume(el.volume);
309
- }
310
- if (currTime !== this._currentTime) {
311
- this._updateCurrentTime(currTime);
312
- }
313
- if (duration !== this._duration) {
314
- this._updateDuration(duration);
315
- }
316
- if (el.playbackRate !== this._playbackRate) {
317
- this._updatePlaybackRate(el.playbackRate);
318
- }
319
- this._onProgress(el);
320
- }
321
- _onLoaderQualityLevelsUpdated(newLevels) {
322
- // console.log('Quality levels updated', newLevels);
323
- this._updateQualityLevels(newLevels);
324
- }
325
- _onLoaderQualityLevelSwitching(newLevelId, isAutoLevel) {
326
- const newLevel = this._qualityLevels.find((lvl) => lvl.id === newLevelId);
327
- if (!newLevel) {
328
- console.error(`Quality level switch to unknown level`, {
329
- newLevelId,
330
- levels: this._qualityLevels
331
- });
332
- return;
333
- }
334
- this._isAutoQuality = isAutoLevel;
335
- this.emitter.emit(PlayerEvents.QualityLevelSwitching, newLevel, isAutoLevel);
336
- }
337
- _onLoaderQualityLevelSwitched(newLevelId, isAutoLevel) {
338
- const newLevel = this._qualityLevels.find((lvl) => lvl.id === newLevelId);
339
- if (!newLevel) {
340
- console.error(`Quality level switch to unknown level`, {
341
- newLevelId,
342
- levels: this._qualityLevels
343
- });
344
- return;
345
- }
346
- this._updateQualityLevel(newLevel, isAutoLevel);
347
- }
348
- _onLoaderAudioTracksUpdated(newTracks) {
349
- // console.log('Audio tracks updated', newTracks);
350
- this._updateAudioTracks(newTracks);
351
- }
352
- _onLoaderAudioTrackSwitching(newTrackId) {
353
- const newTrack = this._audioTracks.find((lvl) => lvl.id === newTrackId);
354
- if (!newTrack) {
355
- console.error(`Audio track switch to unknown track`, {
356
- newTrackId,
357
- tracks: this._audioTracks
358
- });
359
- return;
360
- }
361
- this.emitter.emit(PlayerEvents.AudioTrackSwitching, newTrack);
362
- }
363
- _onLoaderAudioTrackSwitched(newTrackId) {
364
- const newTrack = this._audioTracks.find((lvl) => lvl.id === newTrackId);
365
- if (!newTrack) {
366
- console.error(`Audio track switch to unknown track`, {
367
- newTrackId,
368
- tracks: this._audioTracks
369
- });
370
- return;
371
- }
372
- this._updateCurrentAudioTrack(newTrack);
373
- this.emitter.emit(PlayerEvents.AudioTrackSwitching, newTrack);
374
- }
375
98
  _onFullScreenChange = () => {
376
99
  const newFullScreen = screenfull.isFullscreen;
377
100
  if (newFullScreen !== this._isFullScreen) {
@@ -380,7 +103,7 @@ export class VideoPlayer {
380
103
  };
381
104
  _updateIsFullScreen(isFullScreen) {
382
105
  this._isFullScreen = isFullScreen;
383
- this._isFullScreenStore.set(isFullScreen);
106
+ // this._isFullScreenStore.set(isFullScreen);
384
107
  if (isFullScreen) {
385
108
  this.emitter.emit(PlayerEvents.FullScreenEnter);
386
109
  }
@@ -389,68 +112,6 @@ export class VideoPlayer {
389
112
  }
390
113
  this.emitter.emit(PlayerEvents.FullScreenToggle, isFullScreen);
391
114
  }
392
- _updateBufferedParts(buffered) {
393
- this._buffered = buffered;
394
- this._bufferedStore.set(buffered);
395
- this.emitter.emit(PlayerEvents.PlaybackBufferingProgress, buffered);
396
- }
397
- _updateDuration(duration) {
398
- this._duration = duration;
399
- this._durationStore.set(duration);
400
- this.emitter.emit(PlayerEvents.PlaybackDurationChange, duration);
401
- }
402
- _updateCurrentTime(currentTime) {
403
- this._currentTime = currentTime;
404
- this._currentTimeStore.set(currentTime);
405
- this.emitter.emit(PlayerEvents.PlaybackTimeUpdate, currentTime);
406
- }
407
- _updatePlaybackRate(rate) {
408
- this._playbackRate = rate;
409
- this._playbackRateStore.set(rate);
410
- this.emitter.emit(PlayerEvents.PlaybackRateChange, rate);
411
- }
412
- _updateVolume(volume) {
413
- this._volume = volume;
414
- this._volumeStore.set(volume);
415
- this.emitter.emit(PlayerEvents.VolumeChange, volume);
416
- }
417
- _updateMuted(muted) {
418
- this._muted = muted;
419
- this._mutedStore.set(muted);
420
- this.emitter.emit(PlayerEvents.MuteChange, muted);
421
- }
422
- _updatePaused(paused) {
423
- this._paused = paused;
424
- this._pausedStore.set(paused);
425
- if (!paused) {
426
- this.emitter.emit(PlayerEvents.Play);
427
- }
428
- else {
429
- this.emitter.emit(PlayerEvents.Pause);
430
- }
431
- }
432
- _updateQualityLevels(levels) {
433
- this._qualityLevels = levels;
434
- this._qualityLevelsStore.set(levels);
435
- this.emitter.emit(PlayerEvents.QualityLevelsUpdated, levels);
436
- }
437
- _updateQualityLevel(level, isAutoQuality) {
438
- this._currentQualityLevel = level;
439
- this._currentQualityLevelStore.set(level);
440
- this._isAutoQuality = isAutoQuality;
441
- this._isAutoQualityStore.set(isAutoQuality);
442
- this.emitter.emit(PlayerEvents.QualityLevelSwitched, level, isAutoQuality);
443
- }
444
- _updateAudioTracks(audioTracks) {
445
- this._audioTracks = audioTracks;
446
- this._audioTracksStore.set(audioTracks);
447
- this.emitter.emit(PlayerEvents.AudioTracksUpdated, audioTracks);
448
- }
449
- _updateCurrentAudioTrack(audioTrack) {
450
- this._currentAudioTrack = audioTrack;
451
- this._currentAudioTrackStore.set(audioTrack);
452
- this.emitter.emit(PlayerEvents.AudioTrackSwitched, audioTrack);
453
- }
454
115
  attachEl(el) {
455
116
  if (this._el === el) {
456
117
  return;
@@ -459,15 +120,15 @@ export class VideoPlayer {
459
120
  this.detachEl();
460
121
  }
461
122
  this._el = el;
462
- this._refreshState();
123
+ this._mediaState.syncFromEl(el);
463
124
  this._registerVideoEvents(el);
464
125
  // Setup HLS.js loader
465
- if (isHlsJsSupported()) {
466
- this.hlsLoader = new HLSLoader(this.emitter, this._el, this._cfg.hlsConfig ?? {
126
+ if (IS_HLS_JS_SUPPORTED) {
127
+ this._loadingMgr.setHlsLoader(new HLSLoader(el, this._cfg.hlsConfig ?? {
467
128
  progressive: true
468
- });
129
+ }));
469
130
  }
470
- this.nativeLoader = new NativeLoader(this.emitter, this.el);
131
+ this._loadingMgr.setNativeLoader(new NativeLoader(this._el));
471
132
  this.emitter.emit(PlayerEvents.MediaElementAttached, el);
472
133
  }
473
134
  attachContainerEl(el) {
@@ -484,9 +145,11 @@ export class VideoPlayer {
484
145
  if (!this._el) {
485
146
  return;
486
147
  }
148
+ // this._audioGain?.disconnect();
487
149
  this._unregisterVideoElementEvents();
488
150
  const el = this._el;
489
151
  this._el = undefined;
152
+ this._loadingMgr.destroy();
490
153
  this.emitter.emit(PlayerEvents.MediaElementDetached, el);
491
154
  }
492
155
  detachContainerEl() {
@@ -500,131 +163,22 @@ export class VideoPlayer {
500
163
  destroy() {
501
164
  this.detachEl();
502
165
  this.detachContainerEl();
503
- this.emitter.off(LoaderEvents.QualityLevelsUpdated, this._onLoaderQualityLevelsUpdated.bind(this));
504
- this.emitter.off(LoaderEvents.QualityLevelSwitching, this._onLoaderQualityLevelSwitching.bind(this));
505
- this.emitter.off(LoaderEvents.QualityLevelSwitched, this._onLoaderQualityLevelSwitched.bind(this));
506
- this.emitter.off(LoaderEvents.AudioTracksUpdated, this._onLoaderAudioTracksUpdated.bind(this));
507
- this.emitter.off(LoaderEvents.AudioTrackSwitching, this._onLoaderAudioTrackSwitching.bind(this));
508
- this.emitter.off(LoaderEvents.AudioTrackSwitched, this._onLoaderAudioTrackSwitched.bind(this));
509
- if (this.currentLoader) {
510
- this.currentLoader.stop();
511
- this.currentLoader = null;
512
- this.hlsLoader = null;
513
- }
514
- if (VideoPlayer._isFullScreenAllowed) {
166
+ if (IS_FULLSCREEN_ALLOWED) {
515
167
  screenfull.off('change', this._onFullScreenChange.bind(this));
516
168
  }
517
169
  }
518
170
  stop() {
519
- if (this.currentLoader) {
520
- if (this.loadAbortController) {
521
- this.loadAbortController.abort('Player stop called');
522
- this.loadAbortController = null;
523
- }
524
- if (!this.el.paused) {
525
- this.el.pause();
526
- this._paused = true;
527
- this._pausedStore.set(true);
528
- }
529
- try {
530
- this.currentLoader.stop();
531
- }
532
- catch (e) {
533
- // Ignore any loader stopping error
534
- console.log('loader error during stop', e);
535
- }
536
- this._resetState();
537
- }
538
- }
539
- _getLoaderByType(loaderType) {
540
- let loader = null;
541
- switch (loaderType) {
542
- case Loader.Native:
543
- loader = this.nativeLoader;
544
- break;
545
- case Loader.HLS:
546
- loader = this.hlsLoader ?? this.nativeLoader;
547
- break;
548
- case Loader.DASH:
549
- loader = this.nativeLoader;
550
- break;
551
- default:
552
- throw Error('Unknown loader selected');
553
- }
554
- if (!loader) {
555
- throw Error('No suitable loader found');
556
- }
557
- return loader;
558
- }
559
- _getLoaderFromUrl(url) {
560
- let loader = null;
561
- const urlParsed = new URL(url);
562
- const path = urlParsed.pathname;
563
- if (path.endsWith('.m3u8')) {
564
- // HLS
565
- loader = this.hlsLoader ?? this.nativeLoader;
566
- }
567
- else if (path.endsWith('.mpd')) {
568
- // DASH
569
- loader = this.nativeLoader;
570
- }
571
- else {
572
- // For everything else native loader should be used
573
- loader = this.nativeLoader;
574
- }
575
- if (!loader) {
576
- throw Error('No suitable loader found');
577
- }
578
- return loader;
171
+ this._loadingMgr.stop();
172
+ this._mediaState.resetForLoadingStart();
579
173
  }
580
174
  async load(params) {
581
- if (!this.el) {
582
- return;
583
- }
584
- let neededLoader = null;
585
- if (params.loader !== undefined) {
586
- neededLoader = this._getLoaderByType(params.loader);
587
- }
588
- // Unsubscribe from video events before loading
175
+ const el = this.el;
176
+ this.emitter.emit(PlayerEvents.BeforeLoading, params);
589
177
  this._unregisterVideoElementEvents();
590
- const abortController = new AbortController();
591
- this.loadAbortController?.abort('Player load called before previous load is done');
592
- this.loadAbortController = abortController;
593
- this._resetState(VideoLoadingState.LoadingMetadata);
594
- this._calcIsLoadingState();
595
- this.emitter.emit(PlayerEvents.LoadingStarted, params);
596
- try {
597
- const loadUrl = await getLoadUrl(params.url, this.loadAbortController.signal);
598
- // Detect needed loader based on load url
599
- if (neededLoader === null) {
600
- neededLoader = this._getLoaderFromUrl(loadUrl);
601
- }
602
- if (this.currentLoader && this.currentLoader !== neededLoader) {
603
- this.currentLoader.stop();
604
- this.currentLoader = neededLoader;
605
- }
606
- // Subscribe to video events before loader load call, because it will produce HTML events
607
- this._registerVideoEvents(this.el);
608
- await neededLoader.load({
609
- abortSignal: abortController.signal,
610
- url: loadUrl,
611
- startLevel: params.startLevel,
612
- startPosition: params.startPosition,
613
- audioTrack: params.audioTrack
614
- });
615
- }
616
- catch (e) {
617
- this.loadAbortController = null;
618
- this._loadingState = VideoLoadingState.LoadingFailed;
619
- this._calcIsLoadingState();
620
- this.emitter.emit(PlayerEvents.LoadingMetadataFailed, e);
621
- throw e;
622
- }
623
- this.loadAbortController = null;
624
- this._loadingState = VideoLoadingState.LoadingData;
625
- this._onBufferingStarted();
626
- this._calcIsLoadingState();
627
- this.emitter.emit(PlayerEvents.LoadingMetadataEnded);
178
+ this._mediaState.resetForLoadingStart();
179
+ await this._loadingMgr.load(params);
180
+ this._registerVideoEvents(el);
181
+ this._mediaState.syncFromEl(el);
628
182
  if (params.playAfterLoad) {
629
183
  await this.play();
630
184
  }
@@ -658,18 +212,18 @@ export class VideoPlayer {
658
212
  if (pos < 0) {
659
213
  pos = 0;
660
214
  }
661
- else if (pos > this._duration) {
662
- pos = this._duration;
215
+ else if (pos > this._mediaState.duration) {
216
+ pos = this._mediaState.duration;
663
217
  }
664
- this._updateCurrentTime(pos);
218
+ this._mediaState.updateCurrentTime(pos);
665
219
  this._el.currentTime = pos;
666
220
  }
667
221
  seekForward(step) {
668
- const newCurrentTime = this._currentTime + step;
222
+ const newCurrentTime = this._mediaState.currentTime + step;
669
223
  this.seekTo(newCurrentTime);
670
224
  }
671
225
  seekBackward(step) {
672
- const newCurrentTime = this._currentTime - step;
226
+ const newCurrentTime = this._mediaState.currentTime - step;
673
227
  this.seekTo(newCurrentTime);
674
228
  }
675
229
  setVolume(vol) {
@@ -687,6 +241,20 @@ export class VideoPlayer {
687
241
  }
688
242
  this._el.muted = muted;
689
243
  }
244
+ // setVolumeGain(gain: number) {
245
+ // if (!this._el) {
246
+ // throw Error('Player is not attached to media element');
247
+ // }
248
+ //
249
+ // if (!this._audioGain) {
250
+ // if (!IS_AUDIO_GAIN_SUPPORTED) {
251
+ // console.error('Audio gain is not supported by the browser');
252
+ // return;
253
+ // }
254
+ //
255
+ // this._audioGain = createGainNode();
256
+ // }
257
+ // }
690
258
  setPlaybackRate(rate) {
691
259
  if (!this._el) {
692
260
  throw Error('Player is not attached to media element');
@@ -699,9 +267,6 @@ export class VideoPlayer {
699
267
  }
700
268
  this._el.muted = !this._el.muted;
701
269
  }
702
- static isFullScreenAllowed() {
703
- return VideoPlayer._isFullScreenAllowed;
704
- }
705
270
  async toggleFullScreen() {
706
271
  // On iOS only native full screen supported
707
272
  if (IS_IOS && !IS_IPAD_NEW) {
@@ -719,30 +284,33 @@ export class VideoPlayer {
719
284
  return await screenfull.toggle(this._el);
720
285
  }
721
286
  setQualityLevel(id, graceful) {
722
- const found = this._qualityLevels.find((lvl) => lvl.id === id);
287
+ const found = this._mediaState.findQualityLevelById(id);
723
288
  if (!found) {
724
289
  throw Error(`Quality level not found: ${id}`);
725
290
  }
726
- if (!this.currentLoader) {
291
+ const loader = this._loadingMgr.getCurrentLoader();
292
+ if (!loader) {
727
293
  throw Error('Loader is not set');
728
294
  }
729
- this.currentLoader.switchQualityLevel(id, graceful);
295
+ loader.switchQualityLevel(id, graceful);
730
296
  }
731
297
  setAutoQualityLevel(graceful) {
732
- if (!this.currentLoader) {
298
+ const loader = this._loadingMgr.getCurrentLoader();
299
+ if (!loader) {
733
300
  throw Error('Loader is not set');
734
301
  }
735
- this.currentLoader.switchQualityLevel(this.currentLoader.getAutoQualityLevelId(), graceful);
302
+ loader.switchQualityLevel(loader.getAutoQualityLevelId(), graceful);
736
303
  }
737
304
  setAudioTrack(trackId, graceful) {
738
- const found = this._audioTracks.find((track) => track.id === trackId);
305
+ const found = this._mediaState.findAudioTrackById(trackId);
739
306
  if (!found) {
740
307
  throw Error(`Audio track not found: ${trackId}`);
741
308
  }
742
- if (!this.currentLoader) {
309
+ const loader = this._loadingMgr.getCurrentLoader();
310
+ if (!loader) {
743
311
  throw Error('Loader is not set');
744
312
  }
745
- this.currentLoader.switchAudioTrack(trackId, graceful);
313
+ loader.switchAudioTrack(trackId, graceful);
746
314
  }
747
315
  subscribe(ev, cb) {
748
316
  this.emitter.on(ev, cb);
@@ -765,98 +333,55 @@ export class VideoPlayer {
765
333
  }
766
334
  return this._containerEl;
767
335
  }
768
- get loader() {
769
- return this.currentLoader;
770
- }
771
336
  get isFullScreen() {
772
337
  return this._isFullScreen;
773
338
  }
774
- get loadingState() {
775
- return this._loadingState;
776
- }
777
- get isBuffering() {
778
- return this._isBuffering;
339
+ get loader() {
340
+ return this._loadingMgr.getCurrentLoader();
779
341
  }
780
- // isLoading indicates is there any buffering process or metadata loading
781
342
  get isLoading() {
782
- return this._isLoading;
783
- }
784
- get qualityLevels() {
785
- return this._qualityLevels;
343
+ return this._loadingMgr.getLoadingState() === VideoLoadingState.Loading;
786
344
  }
787
- get isAutoQuality() {
788
- return this._isAutoQuality;
789
- }
790
- get qualityLevel() {
791
- return this._currentQualityLevel;
345
+ get loadingState() {
346
+ return this._loadingMgr.getLoadingState();
792
347
  }
793
- get audioTracks() {
794
- return this._audioTracks;
348
+ get isBuffering() {
349
+ return this._mediaState.buffering;
795
350
  }
796
- get audioTrack() {
797
- return this._currentAudioTrack;
351
+ get paused() {
352
+ return this._mediaState.paused;
798
353
  }
799
354
  get duration() {
800
- return this._duration;
801
- }
802
- get paused() {
803
- return this._paused;
355
+ return this._mediaState.duration;
804
356
  }
805
357
  get currentTime() {
806
- return this._currentTime;
358
+ return this._mediaState.currentTime;
807
359
  }
808
360
  get buffered() {
809
- return this._buffered;
361
+ return this._mediaState.buffered;
810
362
  }
811
363
  get playbackRate() {
812
- return this._playbackRate;
364
+ return this._mediaState.playbackRate;
813
365
  }
814
366
  get volume() {
815
- return this._volume;
367
+ return this._mediaState.volume;
816
368
  }
817
369
  get muted() {
818
- return this._muted;
819
- }
820
- get pausedStore() {
821
- return readonly(this._pausedStore);
370
+ return this._mediaState.muted;
822
371
  }
823
- get isFullScreenStore() {
824
- return readonly(this._isFullScreenStore);
825
- }
826
- get isLoadingStore() {
827
- return readonly(this._isLoadingStore);
828
- }
829
- get qualityLevelsStore() {
830
- return readonly(this._qualityLevelsStore);
831
- }
832
- get isAutoQualityStore() {
833
- return readonly(this._isAutoQualityStore);
834
- }
835
- get qualityLevelStore() {
836
- return readonly(this._currentQualityLevelStore);
837
- }
838
- get audioTracksStore() {
839
- return readonly(this._audioTracksStore);
840
- }
841
- get audioTrackStore() {
842
- return readonly(this._currentAudioTrackStore);
843
- }
844
- get durationStore() {
845
- return readonly(this._durationStore);
846
- }
847
- get currentTimeStore() {
848
- return readonly(this._currentTimeStore);
372
+ get qualityLevels() {
373
+ return this._mediaState.qualityLevels;
849
374
  }
850
- get bufferedStore() {
851
- return readonly(this._bufferedStore);
375
+ get isAutoQuality() {
376
+ return this._mediaState.isAutoQuality;
852
377
  }
853
- get playbackRateStore() {
854
- return readonly(this._playbackRateStore);
378
+ get qualityLevel() {
379
+ return this._mediaState.currentQualityLevel;
855
380
  }
856
- get volumeStore() {
857
- return readonly(this._volumeStore);
381
+ get audioTracks() {
382
+ return this._mediaState.audioTracks;
858
383
  }
859
- get mutedStore() {
860
- return readonly(this._mutedStore);
384
+ get audioTrack() {
385
+ return this._mediaState.currentAudioTrack;
861
386
  }
862
387
  }