@eluvio/elv-player-js 1.0.139 → 2.0.0

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 (98) hide show
  1. package/README.md +35 -6
  2. package/dist/.vite/manifest.json +17 -17
  3. package/dist/{Analytics-cQC_NR8f.mjs → Analytics-MzZmvYgy.mjs} +1 -1
  4. package/dist/{Analytics-z6nAtuJx.js → Analytics-jM8HcyUa.js} +1 -1
  5. package/dist/{dash.all.min-Uvqi9PBX.js → dash.all.min-16Sl6Y0h.js} +1 -1
  6. package/dist/{dash.all.min-JeIXEd1s.mjs → dash.all.min-2ST8aEXP.mjs} +1 -1
  7. package/dist/elv-player-js.cjs.js +1 -1
  8. package/dist/elv-player-js.css +1 -1
  9. package/dist/elv-player-js.es.js +1 -1
  10. package/dist/{index-RKrb2ZFL.js → index-BThzGsbn.js} +1 -1
  11. package/dist/index-Cw8L2-NE.js +367 -0
  12. package/dist/{index-FpQhGSc8.mjs → index-herSXPMN.mjs} +1 -1
  13. package/dist/{index-FmpRD8ov.mjs → index-mO9GR6Op.mjs} +25278 -24415
  14. package/lib/index.js +7 -0
  15. package/{src → lib/player}/Analytics.js +9 -8
  16. package/lib/player/Controls.js +912 -0
  17. package/{src → lib/player}/FairPlay.js +2 -0
  18. package/lib/player/Player.js +881 -0
  19. package/lib/player/PlayerParameters.js +173 -0
  20. package/lib/static/icons/Icons.js +29 -0
  21. package/lib/static/icons/svgs/backward-circle.svg +5 -0
  22. package/lib/static/icons/svgs/backward.svg +4 -0
  23. package/lib/static/icons/svgs/captions-off.svg +7 -0
  24. package/lib/static/icons/svgs/captions.svg +6 -0
  25. package/lib/static/icons/svgs/check.svg +1 -0
  26. package/lib/static/icons/svgs/chevron-left.svg +1 -0
  27. package/lib/static/icons/svgs/chevron-right.svg +1 -0
  28. package/lib/static/icons/svgs/forward-circle.svg +5 -0
  29. package/lib/static/icons/svgs/forward.svg +4 -0
  30. package/{src/static/icons/media/Full Screen icon.svg → lib/static/icons/svgs/full-screen.svg} +1 -1
  31. package/lib/static/icons/svgs/large-play-circle.svg +4 -0
  32. package/lib/static/icons/svgs/list.svg +1 -0
  33. package/{src/static/icons → lib/static/icons/svgs}/minimize.svg +1 -1
  34. package/{src/static/icons/media/Pause icon.svg → lib/static/icons/svgs/pause-circle.svg} +3 -3
  35. package/lib/static/icons/svgs/pause.svg +1 -0
  36. package/{src/static/icons/media/Play icon.svg → lib/static/icons/svgs/play-circle.svg} +1 -1
  37. package/lib/static/icons/svgs/play.svg +1 -0
  38. package/lib/static/icons/svgs/rotate-cw.svg +1 -0
  39. package/lib/static/icons/svgs/settings.svg +11 -0
  40. package/{src/static/icons/media/skip back icon.svg → lib/static/icons/svgs/skip-backward.svg} +2 -3
  41. package/{src/static/icons/media/Skip forward icon.svg → lib/static/icons/svgs/skip-forward.svg} +2 -3
  42. package/{src/static/icons/media/Volume icon.svg → lib/static/icons/svgs/volume-high.svg} +3 -3
  43. package/lib/static/icons/svgs/volume-low.svg +10 -0
  44. package/{src/static/icons/media/low volume icon.svg → lib/static/icons/svgs/volume-medium.svg} +2 -2
  45. package/{src/static/icons/media/no volume icon.svg → lib/static/icons/svgs/volume-off.svg} +3 -3
  46. package/lib/static/stylesheets/common.module.scss +486 -0
  47. package/lib/static/stylesheets/controls-tv.module.scss +488 -0
  48. package/lib/static/stylesheets/controls-web.module.scss +422 -0
  49. package/lib/static/stylesheets/player-profile-form.module.scss +141 -0
  50. package/lib/static/stylesheets/player.module.scss +92 -0
  51. package/lib/static/stylesheets/reset.module.scss +79 -0
  52. package/lib/static/stylesheets/ticket-form.module.scss +123 -0
  53. package/lib/ui/BuildIcons.cjs +44 -0
  54. package/lib/ui/Common.js +210 -0
  55. package/lib/ui/Components.jsx +342 -0
  56. package/lib/ui/Observers.js +449 -0
  57. package/lib/ui/PlayerProfileForm.jsx +106 -0
  58. package/lib/ui/PlayerUI.jsx +316 -0
  59. package/lib/ui/TVControls.jsx +337 -0
  60. package/lib/ui/TicketForm.jsx +147 -0
  61. package/lib/ui/WebControls.jsx +290 -0
  62. package/package.json +35 -47
  63. package/dist/index-88AgCVwU.js +0 -367
  64. package/src/BuildIcons.js +0 -27
  65. package/src/PlayerControls.js +0 -1478
  66. package/src/index.js +0 -1416
  67. package/src/static/icons/Icons.js +0 -15
  68. package/src/static/icons/Settings icon.svg +0 -4
  69. package/src/static/icons/chat icon collapse.svg +0 -1
  70. package/src/static/icons/chat icon.svg +0 -11
  71. package/src/static/icons/chat send.svg +0 -1
  72. package/src/static/icons/full screen.svg +0 -1
  73. package/src/static/icons/media/LargePlayIcon.svg +0 -4
  74. package/src/static/icons/media/Settings icon.svg +0 -4
  75. package/src/static/icons/media/Skip backward icon.svg +0 -4
  76. package/src/static/icons/media/list.svg +0 -1
  77. package/src/static/icons/media/loop icon.svg +0 -12
  78. package/src/static/icons/media/shuffle icon.svg +0 -13
  79. package/src/static/icons/muted.svg +0 -11
  80. package/src/static/icons/pause.svg +0 -1
  81. package/src/static/icons/play circle.svg +0 -1
  82. package/src/static/icons/play.svg +0 -1
  83. package/src/static/icons/settings.svg +0 -1
  84. package/src/static/icons/slider circle.svg +0 -1
  85. package/src/static/icons/unmuted.svg +0 -10
  86. package/src/static/images/ELUV.IO logo embed player.png +0 -0
  87. package/src/static/images/ELUV.IO logo embed player.svg +0 -1
  88. package/src/static/images/ELUVIO white.svg +0 -26
  89. package/src/static/images/Logo.png +0 -0
  90. package/src/static/stylesheets/player.scss +0 -1065
  91. package/webpack.config.js +0 -152
  92. /package/{src/static/icons → lib/static/icons/svgs}/arrow-left.svg +0 -0
  93. /package/{src/static/icons/live icon.svg → lib/static/icons/svgs/live.svg} +0 -0
  94. /package/{src/static/icons → lib/static/icons/svgs}/multiview.svg +0 -0
  95. /package/{src/static/icons/media → lib/static/icons/svgs}/next.svg +0 -0
  96. /package/{src/static/icons/media → lib/static/icons/svgs}/previous.svg +0 -0
  97. /package/{src/static/icons → lib/static/icons/svgs}/x.svg +0 -0
  98. /package/{src/static/images/ELUV.IO white 20 px V2.png → lib/static/images/Logo.png} +0 -0
@@ -0,0 +1,912 @@
1
+ import {Utils} from "@eluvio/elv-client-js";
2
+
3
+ const PlayerProfiles = {
4
+ default: {
5
+ label: "Default",
6
+ hlsSettings: Utils.HLSJSSettings({profile: "default"}),
7
+ },
8
+ low_latency: {
9
+ label: "Low Latency Live",
10
+ hlsSettings: Utils.HLSJSSettings({profile: "ll"})
11
+ },
12
+ ultra_low_latency: {
13
+ label: "Ultra Low Latency Live",
14
+ hlsSettings: Utils.HLSJSSettings({profile: "ull"})
15
+ },
16
+ custom: {
17
+ label: "Custom",
18
+ hlsSettings: {}
19
+ }
20
+ };
21
+
22
+ /**
23
+ * The Eluvio Player has a full set of APIs to control the video and retrieve information about the current state. Access player controls via `player.controls`
24
+ */
25
+ class PlayerControls {
26
+ constructor({player}) {
27
+ this.player = player;
28
+ this.visible = true;
29
+ this.allowRotation = true;
30
+ }
31
+
32
+ /**
33
+ * Check if the player controls are currently visible (not auto-hidden)
34
+ *
35
+ * @methodGroup Controls
36
+ * @returns {boolean} - Whether or not the controls are visible
37
+ */
38
+ IsVisible() {
39
+ return this.visible;
40
+ }
41
+
42
+ /**
43
+ * Check if the player has finished loading
44
+ *
45
+ * @methodGroup Playback
46
+ * @returns {boolean} - Whether or not the player has finished loading
47
+ */
48
+ IsReady() {
49
+ return !this.player.loading;
50
+ }
51
+
52
+ /**
53
+ * Check if the video is currently playing
54
+ *
55
+ * @methodGroup Playback
56
+ * @returns {boolean} - Whether or not the video is currently playing
57
+ */
58
+ IsPlaying() {
59
+ return !this.player.video.paused;
60
+ }
61
+
62
+ /**
63
+ * Check if video playback has been started since the content was loaded
64
+ *
65
+ * @methodGroup Playback
66
+ * @returns {boolean} - Whether or not playback has been started
67
+ */
68
+ HasPlaybackStarted() {
69
+ return this.player.playbackStarted;
70
+ }
71
+
72
+ /**
73
+ * Play the video
74
+ *
75
+ * @methodGroup Playback
76
+ *
77
+ * @returns {boolean} - Whether or not the video was able to start playing. Browser autoplay policies may block video from playing without user interaction.
78
+ */
79
+ async Play() {
80
+ return await this.player.__Play();
81
+ }
82
+
83
+ /**
84
+ * Pause the video
85
+ *
86
+ * @methodGroup Playback
87
+ */
88
+ Pause() {
89
+ this.player.video.pause();
90
+ }
91
+
92
+ /**
93
+ * Play the video if not playing, otherwise pause the video
94
+ *
95
+ * @methodGroup Playback
96
+ * @returns {boolean} - False if the video was paused, true if playback was started
97
+ */
98
+ TogglePlay() {
99
+ if(this.player.video.paused) {
100
+ this.Play();
101
+ return true;
102
+ } else {
103
+ this.Pause();
104
+ return false;
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Stop playback, return the seek position to the start, and reset 'playback started' to false
110
+ *
111
+ * @methodGroup Playback
112
+ */
113
+ Stop() {
114
+ this.Pause();
115
+ this.Seek({time: 0});
116
+ this.player.playbackStarted = false;
117
+
118
+ this.player.__SettingsUpdate();
119
+ }
120
+
121
+ /**
122
+ * Retrieve the current time of the video
123
+ *
124
+ * @methodGroup Seek
125
+ *
126
+ * @returns {number} - The current time of the video
127
+ */
128
+ GetCurrentTime() {
129
+ return this.player.video.currentTime;
130
+ }
131
+
132
+ /**
133
+ * Retrieve the duration of the video
134
+ *
135
+ * @methodGroup Seek
136
+ *
137
+ * @returns {number} - The duration of the video. May be Infinity if content is live
138
+ */
139
+ GetDuration() {
140
+ return this.player.video.duration;
141
+ }
142
+
143
+ /**
144
+ * Seek the video to the specified time
145
+ *
146
+ * @methodGroup Seek
147
+ * @namedParams
148
+ * @param {number=} time - Seek to the specified time
149
+ * @param {number=} relativeSeconds- Seek relative to the current time, e.g. `10` to skip 10 seconds ahead, or `-30` for 30 seconds back
150
+ * @param {number=} fraction - Specify a fraction of the video to seek to, e.g. `0.5` for the halfway point
151
+ *
152
+ * @returns {boolean} - False if the video was paused, true if playback was started
153
+ */
154
+ Seek({fraction, time, relativeSeconds}) {
155
+ if(!this.player.video || (fraction && !this.player.video.duration)) {
156
+ return;
157
+ }
158
+
159
+ const originalTime = this.player.video.currentTime;
160
+
161
+ if(relativeSeconds) {
162
+ this.player.video.currentTime = Math.max(
163
+ 0,
164
+ Math.min(
165
+ this.player.video.duration,
166
+ this.player.video.currentTime + relativeSeconds
167
+ )
168
+ );
169
+ } else if(typeof fraction !== "undefined") {
170
+ this.player.video.currentTime = this.player.video.duration * fraction;
171
+ } else {
172
+ this.player.video.currentTime = time;
173
+ }
174
+
175
+ return originalTime <= this.player.video.currentTime;
176
+ }
177
+
178
+ /**
179
+ * Retrieve the current volume of the video. Note that if the video is muted, this value may still be > 0
180
+ *
181
+ * @methodGroup Volume
182
+ *
183
+ * @returns {number} - The current volume of the video
184
+ */
185
+ GetVolume() {
186
+ return this.player.video.volume;
187
+ }
188
+
189
+ /**
190
+ * Set the video volume
191
+ *
192
+ * @methodGroup Volume
193
+ * @namedParams
194
+ * @param {number=} fraction - Set the volume to the specified amount - `0` for muted, `1` for for volume
195
+ * @param {number=} relativeFraction - Adjust the volume by the specified fraction, e.g. `0.1` for +10%
196
+ *
197
+ * @returns {boolean} - The resulting volume of the video
198
+ */
199
+ SetVolume({fraction, relativeFraction}) {
200
+ if(relativeFraction) {
201
+ this.player.video.volume = Math.min(1, Math.max(0, this.GetVolume() + relativeFraction));
202
+ } else {
203
+ this.player.video.volume = fraction;
204
+ }
205
+
206
+ if(this.player.video.volume > 0) {
207
+ this.Unmute(false);
208
+ }
209
+
210
+ return this.player.video.volume;
211
+ }
212
+
213
+ /**
214
+ * Retrieve whether or not the video is currently muted
215
+ *
216
+ * @methodGroup Volume
217
+ *
218
+ * @returns {boolean} - Whether or not the video is currently muted
219
+ */
220
+ IsMuted() {
221
+ return this.player.video.muted;
222
+ }
223
+
224
+ /**
225
+ * Mute the video
226
+ *
227
+ * @methodGroup Volume
228
+ */
229
+ Mute() {
230
+ this.player.video.muted = true;
231
+ }
232
+
233
+ /**
234
+ * Unmute the video
235
+ *
236
+ * @methodGroup Volume
237
+ */
238
+ Unmute() {
239
+ this.player.video.muted = false;
240
+ }
241
+
242
+ /**
243
+ * Toggle whether or not the video is muted
244
+ *
245
+ * @methodGroup Volume
246
+ *
247
+ * @returns {boolean} - True if the video was muted, false if unmuted
248
+ */
249
+ ToggleMuted() {
250
+ this.player.video.muted = !this.player.video.muted;
251
+ return this.player.video.muted;
252
+ }
253
+
254
+ /**
255
+ * Retrieve whether or not the player is currently fullscreen
256
+ *
257
+ * @methodGroup Fullscreen
258
+ *
259
+ * @returns {boolean} - Whether or not the player is currently fullscreen
260
+ */
261
+ IsFullscreen() {
262
+ return (
263
+ document.fullscreenElement ||
264
+ document.mozFullScreenElement ||
265
+ document.webkitFullscreenElement ||
266
+ document.msFullscreenElement
267
+ ) === this.player.target;
268
+ }
269
+
270
+ /**
271
+ * Request the player go fullscreen
272
+ *
273
+ * @methodGroup Fullscreen
274
+ */
275
+ Fullscreen() {
276
+ if(this.player.target.requestFullscreen) {
277
+ this.player.target.requestFullscreen({navigationUI: "hide"});
278
+ } else if(this.player.target.mozRequestFullScreen) {
279
+ this.player.target.mozRequestFullScreen({navigationUI: "hide"});
280
+ } else if(this.player.target.webkitRequestFullscreen) {
281
+ this.player.target.webkitRequestFullscreen({navigationUI: "hide"});
282
+ } else if(this.player.target.msRequestFullscreen) {
283
+ this.player.target.msRequestFullscreen({navigationUI: "hide"});
284
+ } else {
285
+ // iPhone - Use native fullscreen on video element only
286
+ this.player.target.querySelector("video").webkitEnterFullScreen();
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Exit fullscreen
292
+ *
293
+ * @methodGroup Fullscreen
294
+ */
295
+ ExitFullscreen() {
296
+ if(document.exitFullscreen) {
297
+ document.exitFullscreen();
298
+ } else if(document.webkitExitFullscreen) {
299
+ document.webkitExitFullscreen();
300
+ } else if(document.mozCancelFullScreen) {
301
+ document.mozCancelFullScreen();
302
+ } else if(document.msExitFullscreen) {
303
+ document.msExitFullscreen();
304
+ }
305
+ }
306
+
307
+ /**
308
+ * Request the player go fullscreen if not currently, otherwise exit fullscreen
309
+ *
310
+ * @methodGroup Fullscreen
311
+ *
312
+ * @returns {boolean} - True if fullscreen video was requested, false if fullscreen was exited
313
+ */
314
+ ToggleFullscreen() {
315
+ if(this.IsFullscreen()) {
316
+ this.ExitFullscreen();
317
+ return false;
318
+ } else {
319
+ this.Fullscreen();
320
+ return true;
321
+ }
322
+ }
323
+
324
+ // Collections
325
+
326
+ /**
327
+ * Retrieve info about the current collection
328
+ *
329
+ * @methodGroup Collections
330
+ *
331
+ * @returns {Object | undefined} - Info about the current collection if present, undefined if no collection is present
332
+ */
333
+ GetCollectionInfo() {
334
+ if(!this.player.collectionInfo) { return; }
335
+
336
+ const collectionInfo = {...this.player.collectionInfo };
337
+ collectionInfo.content = collectionInfo.content.map(content => ({
338
+ ...content,
339
+ active: content.mediaIndex === collectionInfo.mediaIndex
340
+ }));
341
+
342
+ collectionInfo.active = collectionInfo.content[collectionInfo.mediaIndex];
343
+
344
+ return collectionInfo;
345
+ }
346
+
347
+ /**
348
+ * Play the specified collection item
349
+ *
350
+ * @methodGroup Collections
351
+ * @namedParams
352
+ * @param {number=} mediaIndex - Specify a collection item by its index in the collection
353
+ * @param {string=} mediaId - Specify a collection item by its ID
354
+ * @param {boolean=} autoplay - Whether or not the video should automatically start playing when the collection item starts. If omitted, autoplay state will depend on whether the video is currently playing.
355
+ */
356
+ CollectionPlay({mediaIndex, mediaId, autoplay}) {
357
+ if(this.player.loading) { return; }
358
+
359
+ this.player.__CollectionPlay({mediaId, mediaIndex, autoplay});
360
+
361
+ this.player.__SettingsUpdate();
362
+ }
363
+
364
+ /**
365
+ * Play the next item in the collection, if present
366
+ *
367
+ * @methodGroup Collections
368
+ * @namedParams
369
+ * @param {boolean=} autoplay - Whether or not the video should automatically start playing when the collection item starts. If omitted, autoplay state will depend on whether the video is currently playing.
370
+ */
371
+ CollectionPlayNext({autoplay}={}) {
372
+ const collectionInfo = this.GetCollectionInfo();
373
+
374
+ if(!collectionInfo) { return; }
375
+
376
+ const nextIndex = Math.min(collectionInfo.mediaIndex + 1, collectionInfo.mediaLength - 1);
377
+
378
+ if(nextIndex === collectionInfo.mediaIndex) { return; }
379
+
380
+ this.CollectionPlay({mediaIndex: nextIndex, autoplay});
381
+ }
382
+
383
+ /**
384
+ * Play the previous item in the collection, if present
385
+ *
386
+ * @methodGroup Collections
387
+ * @namedParams
388
+ * @param {boolean=} autoplay - Whether or not the video should automatically start playing when the collection item starts. If omitted, autoplay state will depend on whether the video is currently playing.
389
+ */
390
+ CollectionPlayPrevious({autoplay}={}) {
391
+ const collectionInfo = this.GetCollectionInfo();
392
+
393
+ if(!collectionInfo) { return; }
394
+
395
+ const previousIndex = Math.max(0, collectionInfo.mediaIndex - 1);
396
+
397
+ if(previousIndex === collectionInfo.mediaIndex) { return; }
398
+
399
+ this.CollectionPlay({mediaIndex: previousIndex, autoplay});
400
+ }
401
+
402
+ // Content
403
+
404
+ /**
405
+ * Retrieve whether or not the current content is a live video
406
+ *
407
+ * @methodGroup Content Info
408
+ *
409
+ * @returns {boolean} - Whether or not the current content is a live video
410
+ */
411
+ IsLive() {
412
+ return this.player.isLive;
413
+ }
414
+
415
+ /**
416
+ * Retrieve information about the current content, including title, description, icon and poster images, and header texts
417
+ *
418
+ * @methodGroup Content Info
419
+ *
420
+ * @returns {Object} - Information about the current content
421
+ */
422
+ GetContentInfo() {
423
+ if(this.player.playerOptions.title !== false) {
424
+ const collectionInfo = this.GetCollectionInfo();
425
+ if(collectionInfo && collectionInfo.active) {
426
+ return {
427
+ title: collectionInfo.active.title || "",
428
+ subtitle: collectionInfo.active.title || "",
429
+ description: collectionInfo.active.description || "",
430
+ image: (collectionInfo.active.image && collectionInfo.active.image.url) || "",
431
+ posterImage: (collectionInfo.active.poster_image && collectionInfo.active.poster_image.url) || "",
432
+ headers: collectionInfo.active.headers || []
433
+ };
434
+ } else if(this.player.sourceOptions.contentInfo) {
435
+ return {
436
+ title: this.player.sourceOptions.contentInfo.title,
437
+ subtitle: this.player.sourceOptions.contentInfo.subtitle,
438
+ description: this.player.sourceOptions.contentInfo.description,
439
+ image: this.player.sourceOptions.contentInfo.image,
440
+ posterImage: this.player.sourceOptions.contentInfo.posterImage,
441
+ headers: this.player.sourceOptions.contentInfo.headers || []
442
+ };
443
+ }
444
+ }
445
+ }
446
+
447
+ // Menu items
448
+
449
+ /**
450
+ * Retrieve the currently available video quality levels
451
+ *
452
+ * @methodGroup Quality
453
+ *
454
+ * @returns {Object} - All options, as well as the active option.
455
+ */
456
+ GetQualityLevels() {
457
+ let levels = [];
458
+ if(this.player.hlsPlayer) {
459
+ levels = this.player.hlsPlayer.levels
460
+ .map((level, index) => ({
461
+ index,
462
+ active: this.player.hlsPlayer.currentLevel === index && !this.player.hlsPlayer.autoLevelEnabled,
463
+ resolution: level.attrs.RESOLUTION,
464
+ bitrate: level.bitrate,
465
+ audioTrack: !level.videoCodec,
466
+ label:
467
+ level.audioTrack ?
468
+ `${level.bitrate / 1000}kbps` :
469
+ `${level.attrs.RESOLUTION} (${(level.bitrate / 1000 / 1000).toFixed(1)}Mbps)`,
470
+ activeLabel:
471
+ level.audioTrack ?
472
+ `${level.bitrate / 1000}kbps` :
473
+ `${level.attrs.RESOLUTION}`
474
+ }))
475
+ .sort((a, b) => a.bitrate < b.bitrate ? 1 : -1);
476
+
477
+ if(levels.length > 0) {
478
+ const activeLevel = levels.find(level => this.player.hlsPlayer.currentLevel === level.index);
479
+ levels.unshift({
480
+ index: -1,
481
+ label: "Auto",
482
+ activeLabel: activeLevel ? `Auto (${activeLevel.activeLabel})` : "Auto",
483
+ active: this.player.hlsPlayer.autoLevelEnabled
484
+ });
485
+ }
486
+ } else if(this.player.dashPlayer) {
487
+ levels = this.player.dashPlayer.getBitrateInfoListFor("video")
488
+ .map((level) => ({
489
+ index: level.qualityIndex,
490
+ active: level.qualityIndex === this.player.dashPlayer.getQualityFor("video"),
491
+ resolution: `${level.width}x${level.height}`,
492
+ bitrate: level.bitrate,
493
+ label: `${level.width}x${level.height} (${(level.bitrate / 1000 / 1000).toFixed(1)}Mbps)`,
494
+ activeLabel: `${level.width}x${level.height}`,
495
+ }))
496
+ .sort((a, b) => a.bitrate < b.bitrate ? 1 : -1);
497
+ }
498
+
499
+ return {
500
+ options: levels,
501
+ active: levels.find(level => level.active)
502
+ };
503
+ }
504
+
505
+ /**
506
+ * Set the video quality to the specified level
507
+ *
508
+ * @methodGroup Quality
509
+ *
510
+ * @param levelIndex - The index of the quality level to set
511
+ */
512
+ SetQualityLevel(levelIndex) {
513
+ if(this.player.hlsPlayer) {
514
+ this.player.hlsPlayer.nextLevel = levelIndex;
515
+ this.player.hlsPlayer.streamController.immediateLevelSwitch();
516
+ } else if(this.player.dashPlayer) {
517
+ this.player.dashPlayer.setQualityFor("video", levelIndex);
518
+ this.player.dashPlayer.updateSettings({
519
+ streaming: {
520
+ trackSwitchMode: "alwaysReplace",
521
+ buffer: {
522
+ fastSwitchEnabled: true,
523
+ flushBufferAtTrackSwitch: true
524
+ },
525
+ abr: {
526
+ autoSwitchBitrate: {
527
+ video: levelIndex === -1
528
+ }
529
+ }
530
+ }
531
+ });
532
+ }
533
+
534
+ this.player.__SettingsUpdate();
535
+ }
536
+
537
+ /**
538
+ * Retrieve the currently available audio tracks
539
+ *
540
+ * @methodGroup Audio Tracks
541
+ *
542
+ * @returns {Object} - All options, as well as the active option.
543
+ */
544
+ GetAudioTracks() {
545
+ let tracks = [];
546
+ if(this.player.nativeHLS) {
547
+ tracks = Array.from(this.player.video.audioTracks).map(track => ({
548
+ index: track.id,
549
+ label: track.label || track.language,
550
+ active: track.enabled
551
+ }));
552
+ } else if(this.player.hlsPlayer) {
553
+ tracks = this.player.hlsPlayer.audioTracks.map(track => ({
554
+ index: track.id,
555
+ label: track.name,
556
+ active: track.id === this.player.hlsPlayer.audioTrack
557
+ }));
558
+ } else if(this.player.dashPlayer) {
559
+ tracks = this.player.dashPlayer.getTracksFor("audio").map(track => ({
560
+ index: track.index,
561
+ label: track.labels && track.labels.length > 0 ? track.labels[0].text : track.lang,
562
+ active: track.index === this.player.dashPlayer.getCurrentTrackFor("audio").index
563
+ }));
564
+ }
565
+
566
+ return {
567
+ options: tracks,
568
+ active: tracks.find(track => track.active)
569
+ };
570
+ }
571
+
572
+ /**
573
+ * Set the audio track to the specified level
574
+ *
575
+ * @methodGroup Audio Tracks
576
+ *
577
+ * @param index - The index of the audio track to set
578
+ */
579
+ SetAudioTrack(index) {
580
+ if(this.player.nativeHLS) {
581
+ Array.from(this.player.video.audioTracks).forEach(track =>
582
+ track.enabled = index.toString() === track.id
583
+ );
584
+ } else if(this.player.hlsPlayer) {
585
+ this.player.hlsPlayer.audioTrack = index;
586
+ this.player.hlsPlayer.streamController.immediateLevelSwitch();
587
+ } else if(this.player.dashPlayer) {
588
+ const track = this.player.dashPlayer.getTracksFor("audio").find(track => track.index === index);
589
+ this.player.dashPlayer.setCurrentTrack(track);
590
+ }
591
+ }
592
+
593
+ /**
594
+ * Retrieve the currently available text/subtitle tracks
595
+ *
596
+ * @methodGroup Text Tracks
597
+ *
598
+ * @returns {Object} - All options, as well as the active option.
599
+ */
600
+ GetTextTracks() {
601
+ let tracks = [];
602
+ let activeTrackIndex;
603
+ if(this.player.nativeHLS || this.player.hlsPlayer) {
604
+ activeTrackIndex = Array.from(this.player.video.textTracks).findIndex(track => track.mode === "showing");
605
+
606
+ tracks = Array.from(this.player.video.textTracks).map((track, index) => ({
607
+ index,
608
+ label: track.label || track.language,
609
+ language: track.language,
610
+ active: track.mode === "showing"
611
+ }));
612
+ } else if(this.player.dashPlayer) {
613
+ activeTrackIndex = this.player.dashPlayer.getCurrentTextTrackIndex();
614
+ tracks = this.player.dashPlayer.getTracksFor("text").map((track, index) => ({
615
+ index,
616
+ label: track.labels && track.labels.length > 0 ? track.labels[0].text : track.lang,
617
+ language: track.lang,
618
+ active: index === activeTrackIndex
619
+ }));
620
+ }
621
+
622
+ if(tracks.length > 0) {
623
+ tracks.unshift({
624
+ index: -1,
625
+ label: "Disabled",
626
+ active: activeTrackIndex < 0
627
+ });
628
+ }
629
+
630
+ return {
631
+ options: tracks,
632
+ active: tracks.find(track => track.active)
633
+ };
634
+ }
635
+
636
+ /**
637
+ * Set the text track to the specified track
638
+ *
639
+ * @methodGroup Text Tracks
640
+ *
641
+ * @param index - The index of the text track to set
642
+ */
643
+ SetTextTrack(index) {
644
+ index = parseInt(index);
645
+
646
+ if(this.player.nativeHLS || this.player.hlsPlayer) {
647
+ const tracks = Array.from(this.player.video.textTracks);
648
+ tracks.map(track => track.mode = "disabled");
649
+
650
+ if(index >= 0) {
651
+ tracks[index].mode = "showing";
652
+ }
653
+ } else if(this.player.dashPlayer) {
654
+ this.player.dashPlayer.setTextTrack(parseInt(index));
655
+ }
656
+
657
+ if(index >= 0) {
658
+ this.__lastTextTrackIndex = index;
659
+ }
660
+ }
661
+
662
+ /**
663
+ * Toggle the last used or most appropriate text track on or off.
664
+ *
665
+ * The text track will be selected based on the following:
666
+ * - The most recently enabled track
667
+ * - The first track matching the user's specified language as specified in `navigator.languages` in priority order (e.g. if the navigator.languages specifies `["pt-br", "en"]`, a pt-br track will be selected if present, otherwise an en track will be selected)
668
+ * - If no appropriate language tracks are found, the first available text track
669
+ *
670
+ * @methodGroup Text Tracks
671
+ *
672
+ * @returns {boolean} - Whether the text track was enabled or disabled
673
+ */
674
+ ToggleTextTrack() {
675
+ const {active, options} = this.GetTextTracks();
676
+
677
+ if(options.length === 0) { return; }
678
+
679
+ if(active && active.index >= 0) {
680
+ this.SetTextTrack(-1);
681
+ return false;
682
+ } else if(this.__lastTextTrackIndex >= 0) {
683
+ this.SetTextTrack(this.__lastTextTrackIndex);
684
+ return true;
685
+ } else {
686
+ // Try to find a text track that matches one of the user's languages
687
+ for(const languageCode of navigator.languages) {
688
+ const matchingTrack = options.find(option => option.language === languageCode || option.language === languageCode.split("-")[0]);
689
+
690
+ if(matchingTrack) {
691
+ this.SetTextTrack(matchingTrack.index);
692
+ return true;
693
+ }
694
+ }
695
+
696
+ // No matching tracks found, just enable first in list
697
+ this.SetTextTrack(0);
698
+ return true;
699
+ }
700
+ }
701
+
702
+ /**
703
+ * Retrieve the currently available playback rates
704
+ *
705
+ * @methodGroup Playback Rate
706
+ *
707
+ * @returns {Object} - All options, as well as the active option.
708
+ */
709
+ GetPlaybackRates() {
710
+ const options = ["0.25", "0.5", "0.75", "1", "1.25", "1.5", "1.75", "2"]
711
+ .map((speed, index) => ({
712
+ index,
713
+ rate: parseFloat(speed),
714
+ label: `${speed}x`,
715
+ active: this.player.video.playbackRate.toFixed(2) === parseFloat(speed).toFixed(2)
716
+ }));
717
+
718
+ let active = options.find(option => option.active);
719
+
720
+ if(!active) {
721
+ active = {
722
+ index: -1,
723
+ rate: this.player.video.playbackRate,
724
+ label: `${this.player.video.playbackRate}x`,
725
+ active: true
726
+ };
727
+ }
728
+
729
+ return {
730
+ options,
731
+ active
732
+ };
733
+ }
734
+
735
+ /**
736
+ * Set the player's playback rate
737
+ *
738
+ * @methodGroup Playback Rate
739
+ * @namedParams
740
+ * @param {number=} index - The index of the rate in the options list of `player.controls.GetPlaybackRates()`
741
+ * @param {number=} rate - The playback rate to set. Does not need to match any options in `player.controls.GetPlaybackRates()`
742
+ *
743
+ * @returns {Object} - The rate that was set, as well as whether the rate was increased or decreased
744
+ */
745
+ SetPlaybackRate({index, rate}) {
746
+ const originalSpeed = this.player.video.playbackRate;
747
+
748
+ if(rate) {
749
+ this.player.video.playbackRate = rate;
750
+
751
+ this.player.__SettingsUpdate();
752
+
753
+ return { rate, increase: originalSpeed <= rate };
754
+ } else {
755
+ const option = this.GetPlaybackRates().options[index];
756
+
757
+ if(option) {
758
+ this.player.video.playbackRate = option.rate;
759
+
760
+ this.player.__SettingsUpdate();
761
+
762
+ return { rate: option.rate, increase: originalSpeed <= option.rate };
763
+ }
764
+ }
765
+ }
766
+
767
+ /**
768
+ * Retrieve the currently available player profiles
769
+ *
770
+ * @methodGroup Player Profile
771
+ *
772
+ * @returns {Object} - All options, as well as the active option.
773
+ */
774
+ GetPlayerProfiles() {
775
+ let options = [];
776
+ if(this.player.hlsPlayer) {
777
+ options = Object.keys(PlayerProfiles)
778
+ .map(key => ({
779
+ index: key,
780
+ label: PlayerProfiles[key].label,
781
+ active: this.player.playerOptions.playerProfile === key,
782
+ }));
783
+ }
784
+
785
+ return {
786
+ options,
787
+ active: options.find(option => option.active)
788
+ };
789
+ }
790
+
791
+ /**
792
+ * Set the player profile
793
+ *
794
+ * @methodGroup Player Profile
795
+ * @namedParams
796
+ * @param {string} profile - The name of the profile to set
797
+ * @param {Object=} customOptions - If using a custom profile, custom player options to set
798
+ */
799
+ SetPlayerProfile({profile, customOptions={}}) {
800
+ this.player.SetPlayerProfile({profile, customHLSOptions: customOptions});
801
+ }
802
+
803
+ /**
804
+ * Show the custom player profile form
805
+ *
806
+ * @methodGroup Player Profile
807
+ */
808
+ ShowPlayerProfileForm() {
809
+ this.player.__showPlayerProfileForm = true;
810
+ this.player.__SettingsUpdate();
811
+ }
812
+
813
+ /**
814
+ * Hide the custom player profile form
815
+ *
816
+ * @methodGroup Player Profile
817
+ */
818
+ HidePlayerProfileForm() {
819
+ this.player.__showPlayerProfileForm = false;
820
+ this.player.__SettingsUpdate();
821
+ }
822
+
823
+ GetOptions() {
824
+ let options = {
825
+ quality: this.GetQualityLevels(),
826
+ audio: this.GetAudioTracks(),
827
+ text: this.GetTextTracks(),
828
+ profile: this.GetPlayerProfiles(),
829
+ rate: this.GetPlaybackRates()
830
+ };
831
+
832
+ options.hasQualityOptions = options.quality.options.length > 0;
833
+ options.hasAudioOptions = options.audio.options.length > 0;
834
+ options.hasTextOptions = options.text.options.length > 0;
835
+ options.hasProfileOptons = options.profile.options.length > 0;
836
+ options.hasRateOptions = !this.IsLive();
837
+
838
+ options.hasAnyOptions =
839
+ options.hasQualityOptions ||
840
+ options.hasAudioOptions ||
841
+ options.hasTextOptions ||
842
+ options.hasProfileOptons ||
843
+ options.hasRateOptions;
844
+
845
+ return options;
846
+ }
847
+
848
+ AllowRotation() {
849
+ return this.allowRotation;
850
+ }
851
+
852
+ SetAllowRotation(allowRotation) {
853
+ this.allowRotation = allowRotation;
854
+
855
+ this.player.__SettingsUpdate();
856
+ }
857
+
858
+ IsRotatable() {
859
+ return (
860
+ this.IsFullscreen() &&
861
+ window.innerHeight > window.innerWidth &&
862
+ this.player.video.videoWidth > this.player.video.videoHeight
863
+ );
864
+ }
865
+
866
+ /**
867
+ * Register a listener for a <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#events" target="_blank">standard HTML video event on the player's video element.
868
+ *
869
+ * Equivalent to `video.addEventListener(event, callback)`, but returns a disposal function that can be used to remove the listener
870
+ *
871
+ * @methodGroup Events
872
+ * @param event - HTML video event name
873
+ * @param callback - Event callback
874
+ *
875
+ * @returns {function} - A disposal function that, when called, will remove the listener
876
+ */
877
+ RegisterVideoEventListener(event, callback) {
878
+ return this.player.__RegisterVideoEventListener(event, callback);
879
+ }
880
+
881
+ /**
882
+ * Register a listener to be called when player state changes. State includes controls visibility, play/pause state, manual quality/audio/subtitle/playback speed changes, track changes
883
+ *
884
+ * @methodGroup Events
885
+ * @param callback - Settings update callback
886
+ *
887
+ * @returns {function} - A disposal function that, when called, will remove the listener
888
+ */
889
+ RegisterSettingsListener(callback) {
890
+ return this.player.__RegisterSettingsListener(callback);
891
+ }
892
+
893
+ /**
894
+ * Fully reset the player to its initial state
895
+ *
896
+ * @methodGroup Constructor
897
+ */
898
+ Reset() {
899
+ this.player.Reset();
900
+ }
901
+
902
+ /**
903
+ * Stop playback and fully remove the player.
904
+ *
905
+ * @methodGroup Constructor
906
+ */
907
+ Destroy() {
908
+ this.player.Destroy();
909
+ }
910
+ }
911
+
912
+ export default PlayerControls;