@zenvor/hls.js 1.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 (159) hide show
  1. package/LICENSE +28 -0
  2. package/README.md +472 -0
  3. package/dist/hls-demo.js +26995 -0
  4. package/dist/hls-demo.js.map +1 -0
  5. package/dist/hls.d.mts +4204 -0
  6. package/dist/hls.d.ts +4204 -0
  7. package/dist/hls.js +40050 -0
  8. package/dist/hls.js.d.ts +4204 -0
  9. package/dist/hls.js.map +1 -0
  10. package/dist/hls.light.js +27145 -0
  11. package/dist/hls.light.js.map +1 -0
  12. package/dist/hls.light.min.js +2 -0
  13. package/dist/hls.light.min.js.map +1 -0
  14. package/dist/hls.light.mjs +26392 -0
  15. package/dist/hls.light.mjs.map +1 -0
  16. package/dist/hls.min.js +2 -0
  17. package/dist/hls.min.js.map +1 -0
  18. package/dist/hls.mjs +38956 -0
  19. package/dist/hls.mjs.map +1 -0
  20. package/dist/hls.worker.js +2 -0
  21. package/dist/hls.worker.js.map +1 -0
  22. package/package.json +143 -0
  23. package/src/config.ts +794 -0
  24. package/src/controller/abr-controller.ts +1019 -0
  25. package/src/controller/algo-data-controller.ts +794 -0
  26. package/src/controller/audio-stream-controller.ts +1099 -0
  27. package/src/controller/audio-track-controller.ts +454 -0
  28. package/src/controller/base-playlist-controller.ts +438 -0
  29. package/src/controller/base-stream-controller.ts +2526 -0
  30. package/src/controller/buffer-controller.ts +2015 -0
  31. package/src/controller/buffer-operation-queue.ts +159 -0
  32. package/src/controller/cap-level-controller.ts +367 -0
  33. package/src/controller/cmcd-controller.ts +422 -0
  34. package/src/controller/content-steering-controller.ts +622 -0
  35. package/src/controller/eme-controller.ts +1617 -0
  36. package/src/controller/error-controller.ts +627 -0
  37. package/src/controller/fps-controller.ts +146 -0
  38. package/src/controller/fragment-finders.ts +256 -0
  39. package/src/controller/fragment-tracker.ts +567 -0
  40. package/src/controller/gap-controller.ts +719 -0
  41. package/src/controller/id3-track-controller.ts +488 -0
  42. package/src/controller/interstitial-player.ts +302 -0
  43. package/src/controller/interstitials-controller.ts +2895 -0
  44. package/src/controller/interstitials-schedule.ts +698 -0
  45. package/src/controller/latency-controller.ts +294 -0
  46. package/src/controller/level-controller.ts +776 -0
  47. package/src/controller/stream-controller.ts +1597 -0
  48. package/src/controller/subtitle-stream-controller.ts +508 -0
  49. package/src/controller/subtitle-track-controller.ts +617 -0
  50. package/src/controller/timeline-controller.ts +677 -0
  51. package/src/crypt/aes-crypto.ts +36 -0
  52. package/src/crypt/aes-decryptor.ts +339 -0
  53. package/src/crypt/decrypter-aes-mode.ts +4 -0
  54. package/src/crypt/decrypter.ts +225 -0
  55. package/src/crypt/fast-aes-key.ts +39 -0
  56. package/src/define-plugin.d.ts +17 -0
  57. package/src/demux/audio/aacdemuxer.ts +126 -0
  58. package/src/demux/audio/ac3-demuxer.ts +170 -0
  59. package/src/demux/audio/adts.ts +249 -0
  60. package/src/demux/audio/base-audio-demuxer.ts +205 -0
  61. package/src/demux/audio/dolby.ts +21 -0
  62. package/src/demux/audio/mp3demuxer.ts +85 -0
  63. package/src/demux/audio/mpegaudio.ts +177 -0
  64. package/src/demux/chunk-cache.ts +42 -0
  65. package/src/demux/dummy-demuxed-track.ts +13 -0
  66. package/src/demux/inject-worker.ts +75 -0
  67. package/src/demux/mp4demuxer.ts +234 -0
  68. package/src/demux/sample-aes.ts +198 -0
  69. package/src/demux/transmuxer-interface.ts +449 -0
  70. package/src/demux/transmuxer-worker.ts +221 -0
  71. package/src/demux/transmuxer.ts +560 -0
  72. package/src/demux/tsdemuxer.ts +1256 -0
  73. package/src/demux/video/avc-video-parser.ts +401 -0
  74. package/src/demux/video/base-video-parser.ts +198 -0
  75. package/src/demux/video/exp-golomb.ts +153 -0
  76. package/src/demux/video/hevc-video-parser.ts +736 -0
  77. package/src/empty-es.js +5 -0
  78. package/src/empty.js +3 -0
  79. package/src/errors.ts +107 -0
  80. package/src/events.ts +548 -0
  81. package/src/exports-default.ts +3 -0
  82. package/src/exports-named.ts +81 -0
  83. package/src/hls.ts +1613 -0
  84. package/src/is-supported.ts +54 -0
  85. package/src/loader/date-range.ts +207 -0
  86. package/src/loader/fragment-loader.ts +403 -0
  87. package/src/loader/fragment.ts +487 -0
  88. package/src/loader/interstitial-asset-list.ts +162 -0
  89. package/src/loader/interstitial-event.ts +337 -0
  90. package/src/loader/key-loader.ts +439 -0
  91. package/src/loader/level-details.ts +203 -0
  92. package/src/loader/level-key.ts +259 -0
  93. package/src/loader/load-stats.ts +17 -0
  94. package/src/loader/m3u8-parser.ts +1072 -0
  95. package/src/loader/playlist-loader.ts +839 -0
  96. package/src/polyfills/number.ts +15 -0
  97. package/src/remux/aac-helper.ts +81 -0
  98. package/src/remux/mp4-generator.ts +1380 -0
  99. package/src/remux/mp4-remuxer.ts +1261 -0
  100. package/src/remux/passthrough-remuxer.ts +434 -0
  101. package/src/task-loop.ts +130 -0
  102. package/src/types/algo.ts +44 -0
  103. package/src/types/buffer.ts +105 -0
  104. package/src/types/component-api.ts +20 -0
  105. package/src/types/demuxer.ts +208 -0
  106. package/src/types/events.ts +574 -0
  107. package/src/types/fragment-tracker.ts +23 -0
  108. package/src/types/level.ts +268 -0
  109. package/src/types/loader.ts +198 -0
  110. package/src/types/media-playlist.ts +92 -0
  111. package/src/types/network-details.ts +3 -0
  112. package/src/types/remuxer.ts +104 -0
  113. package/src/types/track.ts +12 -0
  114. package/src/types/transmuxer.ts +46 -0
  115. package/src/types/tuples.ts +6 -0
  116. package/src/types/vtt.ts +11 -0
  117. package/src/utils/arrays.ts +22 -0
  118. package/src/utils/attr-list.ts +192 -0
  119. package/src/utils/binary-search.ts +46 -0
  120. package/src/utils/buffer-helper.ts +173 -0
  121. package/src/utils/cea-608-parser.ts +1413 -0
  122. package/src/utils/chunker.ts +41 -0
  123. package/src/utils/codecs.ts +314 -0
  124. package/src/utils/cues.ts +96 -0
  125. package/src/utils/discontinuities.ts +174 -0
  126. package/src/utils/encryption-methods-util.ts +21 -0
  127. package/src/utils/error-helper.ts +95 -0
  128. package/src/utils/event-listener-helper.ts +16 -0
  129. package/src/utils/ewma-bandwidth-estimator.ts +97 -0
  130. package/src/utils/ewma.ts +43 -0
  131. package/src/utils/fetch-loader.ts +331 -0
  132. package/src/utils/global.ts +2 -0
  133. package/src/utils/hash.ts +10 -0
  134. package/src/utils/hdr.ts +67 -0
  135. package/src/utils/hex.ts +32 -0
  136. package/src/utils/imsc1-ttml-parser.ts +261 -0
  137. package/src/utils/keysystem-util.ts +45 -0
  138. package/src/utils/level-helper.ts +629 -0
  139. package/src/utils/logger.ts +120 -0
  140. package/src/utils/media-option-attributes.ts +49 -0
  141. package/src/utils/mediacapabilities-helper.ts +301 -0
  142. package/src/utils/mediakeys-helper.ts +210 -0
  143. package/src/utils/mediasource-helper.ts +37 -0
  144. package/src/utils/mp4-tools.ts +1473 -0
  145. package/src/utils/number.ts +3 -0
  146. package/src/utils/numeric-encoding-utils.ts +26 -0
  147. package/src/utils/output-filter.ts +46 -0
  148. package/src/utils/rendition-helper.ts +505 -0
  149. package/src/utils/safe-json-stringify.ts +22 -0
  150. package/src/utils/texttrack-utils.ts +164 -0
  151. package/src/utils/time-ranges.ts +17 -0
  152. package/src/utils/timescale-conversion.ts +46 -0
  153. package/src/utils/utf8-utils.ts +18 -0
  154. package/src/utils/variable-substitution.ts +105 -0
  155. package/src/utils/vttcue.ts +384 -0
  156. package/src/utils/vttparser.ts +497 -0
  157. package/src/utils/webvtt-parser.ts +166 -0
  158. package/src/utils/xhr-loader.ts +337 -0
  159. package/src/version.ts +1 -0
@@ -0,0 +1,159 @@
1
+ import type {
2
+ BufferOperation,
3
+ BufferOperationQueues,
4
+ SourceBufferName,
5
+ SourceBufferTrackSet,
6
+ } from '../types/buffer';
7
+
8
+ export default class BufferOperationQueue {
9
+ private tracks: SourceBufferTrackSet | null;
10
+ private queues: BufferOperationQueues | null = {
11
+ video: [],
12
+ audio: [],
13
+ audiovideo: [],
14
+ };
15
+
16
+ constructor(sourceBufferReference: SourceBufferTrackSet) {
17
+ this.tracks = sourceBufferReference;
18
+ }
19
+
20
+ public destroy() {
21
+ this.tracks = this.queues = null;
22
+ }
23
+
24
+ public append(
25
+ operation: BufferOperation,
26
+ type: SourceBufferName,
27
+ pending?: boolean | undefined,
28
+ ) {
29
+ if (this.queues === null || this.tracks === null) {
30
+ return;
31
+ }
32
+ const queue = this.queues[type];
33
+ queue.push(operation);
34
+ if (queue.length === 1 && !pending) {
35
+ this.executeNext(type);
36
+ }
37
+ }
38
+
39
+ public appendBlocker(type: SourceBufferName): Promise<void> {
40
+ return new Promise((resolve) => {
41
+ const operation: BufferOperation = {
42
+ label: 'async-blocker',
43
+ execute: resolve,
44
+ onStart: () => {},
45
+ onComplete: () => {},
46
+ onError: () => {},
47
+ };
48
+ this.append(operation, type);
49
+ });
50
+ }
51
+
52
+ public prependBlocker(type: SourceBufferName): Promise<void> {
53
+ return new Promise((resolve) => {
54
+ if (this.queues) {
55
+ const operation: BufferOperation = {
56
+ label: 'async-blocker-prepend',
57
+ execute: resolve,
58
+ onStart: () => {},
59
+ onComplete: () => {},
60
+ onError: () => {},
61
+ };
62
+ this.queues[type].unshift(operation);
63
+ }
64
+ });
65
+ }
66
+
67
+ public removeBlockers() {
68
+ if (this.queues === null) {
69
+ return;
70
+ }
71
+ [this.queues.video, this.queues.audio, this.queues.audiovideo].forEach(
72
+ (queue) => {
73
+ const label = queue[0]?.label;
74
+ if (label === 'async-blocker' || label === 'async-blocker-prepend') {
75
+ queue[0].execute();
76
+ queue.splice(0, 1);
77
+ }
78
+ },
79
+ );
80
+ }
81
+
82
+ public unblockAudio(op: BufferOperation) {
83
+ if (this.queues === null) {
84
+ return;
85
+ }
86
+ const queue = this.queues.audio;
87
+ if (queue[0] === op) {
88
+ this.shiftAndExecuteNext('audio');
89
+ }
90
+ }
91
+
92
+ public executeNext(type: SourceBufferName) {
93
+ if (this.queues === null || this.tracks === null) {
94
+ return;
95
+ }
96
+ const queue = this.queues[type];
97
+ if (queue.length) {
98
+ const operation: BufferOperation = queue[0];
99
+ try {
100
+ // Operations are expected to result in an 'updateend' event being fired. If not, the queue will lock. Operations
101
+ // which do not end with this event must call _onSBUpdateEnd manually
102
+ operation.execute();
103
+ } catch (error) {
104
+ operation.onError(error);
105
+ if (this.queues === null || this.tracks === null) {
106
+ return;
107
+ }
108
+
109
+ // Only shift the current operation off, otherwise the updateend handler will do this for us
110
+ const sb = this.tracks[type]?.buffer;
111
+ if (!sb?.updating) {
112
+ this.shiftAndExecuteNext(type);
113
+ }
114
+ }
115
+ }
116
+ }
117
+
118
+ public shiftAndExecuteNext(type: SourceBufferName) {
119
+ if (this.queues === null) {
120
+ return;
121
+ }
122
+ this.queues[type].shift();
123
+ this.executeNext(type);
124
+ }
125
+
126
+ public current(type: SourceBufferName): BufferOperation | null {
127
+ return this.queues?.[type][0] || null;
128
+ }
129
+
130
+ public toString(): string {
131
+ const { queues, tracks } = this;
132
+ if (queues === null || tracks === null) {
133
+ return `<destroyed>`;
134
+ }
135
+ return `
136
+ ${this.list('video')}
137
+ ${this.list('audio')}
138
+ ${this.list('audiovideo')}}`;
139
+ }
140
+
141
+ public list(type: SourceBufferName): string {
142
+ return this.queues?.[type] || this.tracks?.[type]
143
+ ? `${type}: (${this.listSbInfo(type)}) ${this.listOps(type)}`
144
+ : '';
145
+ }
146
+
147
+ private listSbInfo(type: SourceBufferName): string {
148
+ const track = this.tracks?.[type];
149
+ const sb = track?.buffer;
150
+ if (!sb) {
151
+ return 'none';
152
+ }
153
+ return `SourceBuffer${sb.updating ? ' updating' : ''}${track.ended ? ' ended' : ''}${track.ending ? ' ending' : ''}`;
154
+ }
155
+
156
+ private listOps(type: SourceBufferName): string {
157
+ return this.queues?.[type].map((op) => op.label).join(', ') || '';
158
+ }
159
+ }
@@ -0,0 +1,367 @@
1
+ /*
2
+ * cap stream level to media size dimension controller
3
+ */
4
+
5
+ import { Events } from '../events';
6
+ import type StreamController from './stream-controller';
7
+ import type Hls from '../hls';
8
+ import type { ComponentAPI } from '../types/component-api';
9
+ import type {
10
+ BufferCodecsData,
11
+ FPSDropLevelCappingData,
12
+ LevelsUpdatedData,
13
+ ManifestParsedData,
14
+ MediaAttachingData,
15
+ } from '../types/events';
16
+ import type { Level } from '../types/level';
17
+
18
+ type RestrictedLevel = { width: number; height: number; bitrate: number };
19
+ class CapLevelController implements ComponentAPI {
20
+ private hls: Hls | null = null;
21
+ private autoLevelCapping: number;
22
+ private media: HTMLVideoElement | null;
23
+ private restrictedLevels: RestrictedLevel[];
24
+ private timer?: number;
25
+ private observer?: ResizeObserver;
26
+ private clientRect: { width: number; height: number } | null;
27
+ private streamController?: StreamController;
28
+
29
+ constructor(hls: Hls) {
30
+ this.hls = hls;
31
+ this.autoLevelCapping = Number.POSITIVE_INFINITY;
32
+ this.media = null;
33
+ this.restrictedLevels = [];
34
+ this.clientRect = null;
35
+
36
+ this.registerListeners();
37
+ }
38
+
39
+ public setStreamController(streamController: StreamController) {
40
+ this.streamController = streamController;
41
+ }
42
+
43
+ public destroy() {
44
+ if (this.hls) {
45
+ this.unregisterListener();
46
+ }
47
+ if (this.timer || this.observer) {
48
+ this.stopCapping();
49
+ }
50
+ this.media = this.clientRect = this.hls = null;
51
+ // @ts-ignore
52
+ this.streamController = undefined;
53
+ }
54
+
55
+ protected registerListeners() {
56
+ const { hls } = this;
57
+ if (hls) {
58
+ hls.on(Events.FPS_DROP_LEVEL_CAPPING, this.onFpsDropLevelCapping, this);
59
+ hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
60
+ hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
61
+ hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
62
+ hls.on(Events.BUFFER_CODECS, this.onBufferCodecs, this);
63
+ hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
64
+ }
65
+ }
66
+
67
+ protected unregisterListener() {
68
+ const { hls } = this;
69
+ if (hls) {
70
+ hls.off(Events.FPS_DROP_LEVEL_CAPPING, this.onFpsDropLevelCapping, this);
71
+ hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
72
+ hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
73
+ hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
74
+ hls.off(Events.BUFFER_CODECS, this.onBufferCodecs, this);
75
+ hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
76
+ }
77
+ }
78
+
79
+ protected onFpsDropLevelCapping(
80
+ event: Events.FPS_DROP_LEVEL_CAPPING,
81
+ data: FPSDropLevelCappingData,
82
+ ) {
83
+ if (!this.hls) {
84
+ return;
85
+ }
86
+ // Don't add a restricted level more than once
87
+ const level = this.hls.levels[data.droppedLevel];
88
+ if (this.isLevelAllowed(level)) {
89
+ this.restrictedLevels.push({
90
+ bitrate: level.bitrate,
91
+ height: level.height,
92
+ width: level.width,
93
+ });
94
+ }
95
+ }
96
+
97
+ protected onMediaAttaching(
98
+ event: Events.MEDIA_ATTACHING,
99
+ data: MediaAttachingData,
100
+ ) {
101
+ const media = data.media;
102
+ this.clientRect = null;
103
+ if (!this.hls) {
104
+ return;
105
+ }
106
+ if (media instanceof HTMLVideoElement) {
107
+ this.media = media;
108
+ if (this.hls.config.capLevelToPlayerSize) {
109
+ this.observe();
110
+ }
111
+ } else {
112
+ this.media = null;
113
+ }
114
+ if ((this.timer || this.observer) && this.hls.levels.length) {
115
+ this.detectPlayerSize();
116
+ }
117
+ }
118
+
119
+ protected onManifestParsed(
120
+ event: Events.MANIFEST_PARSED,
121
+ data: ManifestParsedData,
122
+ ) {
123
+ const hls = this.hls;
124
+ this.restrictedLevels = [];
125
+ if (hls?.config.capLevelToPlayerSize && data.video) {
126
+ // Start capping immediately if the manifest has signaled video codecs
127
+ this.startCapping();
128
+ }
129
+ }
130
+
131
+ private onLevelsUpdated(
132
+ event: Events.LEVELS_UPDATED,
133
+ data: LevelsUpdatedData,
134
+ ) {
135
+ if (
136
+ (this.timer || this.observer) &&
137
+ Number.isFinite(this.autoLevelCapping)
138
+ ) {
139
+ this.detectPlayerSize();
140
+ }
141
+ }
142
+
143
+ // Only activate capping when playing a video stream; otherwise, multi-bitrate audio-only streams will be restricted
144
+ // to the first level
145
+ protected onBufferCodecs(
146
+ event: Events.BUFFER_CODECS,
147
+ data: BufferCodecsData,
148
+ ) {
149
+ const hls = this.hls;
150
+ if (hls?.config.capLevelToPlayerSize && data.video) {
151
+ // If the manifest did not signal a video codec capping has been deferred until we're certain video is present
152
+ this.startCapping();
153
+ }
154
+ }
155
+
156
+ protected onMediaDetaching() {
157
+ this.stopCapping();
158
+ this.media = null;
159
+ }
160
+
161
+ detectPlayerSize() {
162
+ if (this.media) {
163
+ if (this.mediaHeight <= 0 || this.mediaWidth <= 0 || !this.hls) {
164
+ this.clientRect = null;
165
+ return;
166
+ }
167
+ const levels = this.hls.levels;
168
+ if (levels.length) {
169
+ const hls = this.hls;
170
+ const maxLevel = this.getMaxLevel(levels.length - 1);
171
+ if (maxLevel !== this.autoLevelCapping) {
172
+ hls.logger.log(
173
+ `Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`,
174
+ );
175
+ }
176
+ hls.autoLevelCapping = maxLevel;
177
+ if (
178
+ hls.autoLevelEnabled &&
179
+ hls.autoLevelCapping > this.autoLevelCapping &&
180
+ this.streamController
181
+ ) {
182
+ // if auto level capping has a higher value for the previous one, flush the buffer using nextLevelSwitch
183
+ // usually happen when the user go to the fullscreen mode.
184
+ this.streamController.nextLevelSwitch();
185
+ }
186
+ this.autoLevelCapping = hls.autoLevelCapping;
187
+ }
188
+ }
189
+ }
190
+
191
+ /*
192
+ * returns level should be the one with the dimensions equal or greater than the media (player) dimensions (so the video will be downscaled)
193
+ */
194
+ getMaxLevel(capLevelIndex: number): number {
195
+ if (!this.hls) {
196
+ return -1;
197
+ }
198
+ const levels = this.hls.levels;
199
+ if (!levels.length) {
200
+ return -1;
201
+ }
202
+
203
+ const validLevels = levels.filter(
204
+ (level, index) => this.isLevelAllowed(level) && index <= capLevelIndex,
205
+ );
206
+ if (!this.observer) {
207
+ this.clientRect = null;
208
+ }
209
+ return CapLevelController.getMaxLevelByMediaSize(
210
+ validLevels,
211
+ this.mediaWidth,
212
+ this.mediaHeight,
213
+ );
214
+ }
215
+
216
+ private observe() {
217
+ const ResizeObserver = self.ResizeObserver;
218
+ if (ResizeObserver) {
219
+ this.observer = new ResizeObserver((entries) => {
220
+ const bounds = entries[0]?.contentRect;
221
+ if (bounds) {
222
+ this.clientRect = bounds;
223
+ this.detectPlayerSize();
224
+ }
225
+ });
226
+ }
227
+ if (this.observer && this.media) {
228
+ this.observer.observe(this.media);
229
+ }
230
+ }
231
+
232
+ startCapping() {
233
+ if (this.timer || this.observer) {
234
+ // Don't reset capping if started twice; this can happen if the manifest signals a video codec
235
+ return;
236
+ }
237
+ self.clearInterval(this.timer);
238
+ this.timer = undefined;
239
+ this.autoLevelCapping = Number.POSITIVE_INFINITY;
240
+ this.observe();
241
+ if (!this.observer) {
242
+ this.timer = self.setInterval(this.detectPlayerSize.bind(this), 1000);
243
+ }
244
+ this.detectPlayerSize();
245
+ }
246
+
247
+ stopCapping() {
248
+ this.restrictedLevels = [];
249
+ this.autoLevelCapping = Number.POSITIVE_INFINITY;
250
+ if (this.timer) {
251
+ self.clearInterval(this.timer);
252
+ this.timer = undefined;
253
+ }
254
+ if (this.observer) {
255
+ this.observer.disconnect();
256
+ this.observer = undefined;
257
+ }
258
+ }
259
+
260
+ getDimensions(): { width: number; height: number } {
261
+ if (this.clientRect) {
262
+ return this.clientRect;
263
+ }
264
+ const media = this.media;
265
+ const boundsRect = {
266
+ width: 0,
267
+ height: 0,
268
+ };
269
+
270
+ if (media) {
271
+ const clientRect = media.getBoundingClientRect();
272
+ boundsRect.width = clientRect.width;
273
+ boundsRect.height = clientRect.height;
274
+ if (!boundsRect.width && !boundsRect.height) {
275
+ // When the media element has no width or height (equivalent to not being in the DOM),
276
+ // then use its width and height attributes (media.width, media.height)
277
+ boundsRect.width =
278
+ clientRect.right - clientRect.left || media.width || 0;
279
+ boundsRect.height =
280
+ clientRect.bottom - clientRect.top || media.height || 0;
281
+ }
282
+ }
283
+ this.clientRect = boundsRect;
284
+ return boundsRect;
285
+ }
286
+
287
+ get mediaWidth(): number {
288
+ return this.getDimensions().width * this.contentScaleFactor;
289
+ }
290
+
291
+ get mediaHeight(): number {
292
+ return this.getDimensions().height * this.contentScaleFactor;
293
+ }
294
+
295
+ get contentScaleFactor(): number {
296
+ let pixelRatio = 1;
297
+ if (!this.hls) {
298
+ return pixelRatio;
299
+ }
300
+ const { ignoreDevicePixelRatio, maxDevicePixelRatio } = this.hls.config;
301
+ if (!ignoreDevicePixelRatio) {
302
+ try {
303
+ pixelRatio = self.devicePixelRatio;
304
+ } catch (e) {
305
+ /* no-op */
306
+ }
307
+ }
308
+ return Math.min(pixelRatio, maxDevicePixelRatio);
309
+ }
310
+
311
+ private isLevelAllowed(level: Level): boolean {
312
+ const restrictedLevels = this.restrictedLevels;
313
+ return !restrictedLevels.some((restrictedLevel) => {
314
+ return (
315
+ level.bitrate === restrictedLevel.bitrate &&
316
+ level.width === restrictedLevel.width &&
317
+ level.height === restrictedLevel.height
318
+ );
319
+ });
320
+ }
321
+
322
+ static getMaxLevelByMediaSize(
323
+ levels: Array<Level>,
324
+ width: number,
325
+ height: number,
326
+ ): number {
327
+ if (!levels?.length) {
328
+ return -1;
329
+ }
330
+
331
+ // Levels can have the same dimensions but differing bandwidths - since levels are ordered, we can look to the next
332
+ // to determine whether we've chosen the greatest bandwidth for the media's dimensions
333
+ const atGreatestBandwidth = (
334
+ curLevel: Level,
335
+ nextLevel: Level | undefined,
336
+ ) => {
337
+ if (!nextLevel) {
338
+ return true;
339
+ }
340
+
341
+ return (
342
+ curLevel.width !== nextLevel.width ||
343
+ curLevel.height !== nextLevel.height
344
+ );
345
+ };
346
+
347
+ // If we run through the loop without breaking, the media's dimensions are greater than every level, so default to
348
+ // the max level
349
+ let maxLevelIndex = levels.length - 1;
350
+ // Prevent changes in aspect-ratio from causing capping to toggle back and forth
351
+ const squareSize = Math.max(width, height);
352
+ for (let i = 0; i < levels.length; i += 1) {
353
+ const level = levels[i];
354
+ if (
355
+ (level.width >= squareSize || level.height >= squareSize) &&
356
+ atGreatestBandwidth(level, levels[i + 1])
357
+ ) {
358
+ maxLevelIndex = i;
359
+ break;
360
+ }
361
+ }
362
+
363
+ return maxLevelIndex;
364
+ }
365
+ }
366
+
367
+ export default CapLevelController;