@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,120 @@
1
+ export interface ILogFunction {
2
+ (message?: any, ...optionalParams: any[]): void;
3
+ }
4
+
5
+ export interface ILogger {
6
+ trace: ILogFunction;
7
+ debug: ILogFunction;
8
+ log: ILogFunction;
9
+ warn: ILogFunction;
10
+ info: ILogFunction;
11
+ error: ILogFunction;
12
+ }
13
+
14
+ export class Logger implements ILogger {
15
+ trace: ILogFunction;
16
+ debug: ILogFunction;
17
+ log: ILogFunction;
18
+ warn: ILogFunction;
19
+ info: ILogFunction;
20
+ error: ILogFunction;
21
+
22
+ constructor(label: string, logger: ILogger) {
23
+ const lb = `[${label}]:`;
24
+ this.trace = noop;
25
+ this.debug = logger.debug.bind(null, lb);
26
+ this.log = logger.log.bind(null, lb);
27
+ this.warn = logger.warn.bind(null, lb);
28
+ this.info = logger.info.bind(null, lb);
29
+ this.error = logger.error.bind(null, lb);
30
+ }
31
+ }
32
+
33
+ const noop: ILogFunction = function () {};
34
+
35
+ const fakeLogger: ILogger = {
36
+ trace: noop,
37
+ debug: noop,
38
+ log: noop,
39
+ warn: noop,
40
+ info: noop,
41
+ error: noop,
42
+ };
43
+
44
+ function createLogger() {
45
+ return Object.assign({}, fakeLogger);
46
+ }
47
+
48
+ // let lastCallTime;
49
+ // function formatMsgWithTimeInfo(type, msg) {
50
+ // const now = Date.now();
51
+ // const diff = lastCallTime ? '+' + (now - lastCallTime) : '0';
52
+ // lastCallTime = now;
53
+ // msg = (new Date(now)).toISOString() + ' | [' + type + '] > ' + msg + ' ( ' + diff + ' ms )';
54
+ // return msg;
55
+ // }
56
+
57
+ function consolePrintFn(type: string, id: string | undefined): ILogFunction {
58
+ const func: ILogFunction = self.console[type];
59
+ return func
60
+ ? func.bind(self.console, `${id ? '[' + id + '] ' : ''}[${type}] >`)
61
+ : noop;
62
+ }
63
+
64
+ function getLoggerFn(
65
+ key: string,
66
+ debugConfig: boolean | Partial<ILogger>,
67
+ id?: string,
68
+ ): ILogFunction {
69
+ return debugConfig[key]
70
+ ? debugConfig[key].bind(debugConfig)
71
+ : consolePrintFn(key, id);
72
+ }
73
+
74
+ const exportedLogger: ILogger = createLogger();
75
+
76
+ export function enableLogs(
77
+ debugConfig: boolean | ILogger,
78
+ context: string,
79
+ id?: string | undefined,
80
+ ): ILogger {
81
+ // check that console is available
82
+ const newLogger = createLogger();
83
+ if (
84
+ (typeof console === 'object' && debugConfig === true) ||
85
+ typeof debugConfig === 'object'
86
+ ) {
87
+ const keys: (keyof ILogger)[] = [
88
+ // Remove out from list here to hard-disable a log-level
89
+ // 'trace',
90
+ 'debug',
91
+ 'log',
92
+ 'info',
93
+ 'warn',
94
+ 'error',
95
+ ];
96
+ keys.forEach((key) => {
97
+ newLogger[key] = getLoggerFn(key, debugConfig, id);
98
+ });
99
+ // Some browsers don't allow to use bind on console object anyway
100
+ // fallback to default if needed
101
+ try {
102
+ newLogger.log(
103
+ `Debug logs enabled for "${context}" in hls.js version ${__VERSION__}`,
104
+ );
105
+ } catch (e) {
106
+ /* log fn threw an exception. All logger methods are no-ops. */
107
+ return createLogger();
108
+ }
109
+ // global exported logger uses the same functions as new logger without `id`
110
+ keys.forEach((key) => {
111
+ exportedLogger[key] = getLoggerFn(key, debugConfig);
112
+ });
113
+ } else {
114
+ // Reset global exported logger
115
+ Object.assign(exportedLogger, newLogger);
116
+ }
117
+ return newLogger;
118
+ }
119
+
120
+ export const logger: ILogger = exportedLogger;
@@ -0,0 +1,49 @@
1
+ import type { Level } from '../types/level';
2
+ import type { MediaAttributes, MediaPlaylist } from '../types/media-playlist';
3
+
4
+ export function subtitleOptionsIdentical(
5
+ trackList1: MediaPlaylist[] | Level[],
6
+ trackList2: MediaPlaylist[],
7
+ ): boolean {
8
+ if (trackList1.length !== trackList2.length) {
9
+ return false;
10
+ }
11
+ for (let i = 0; i < trackList1.length; i++) {
12
+ if (
13
+ !mediaAttributesIdentical(
14
+ trackList1[i].attrs as MediaAttributes,
15
+ trackList2[i].attrs,
16
+ )
17
+ ) {
18
+ return false;
19
+ }
20
+ }
21
+ return true;
22
+ }
23
+
24
+ export function mediaAttributesIdentical(
25
+ attrs1: MediaAttributes,
26
+ attrs2: MediaAttributes,
27
+ customAttributes?: string[],
28
+ ): boolean {
29
+ // Media options with the same rendition ID must be bit identical
30
+ const stableRenditionId = attrs1['STABLE-RENDITION-ID'];
31
+ if (stableRenditionId && !customAttributes) {
32
+ return stableRenditionId === attrs2['STABLE-RENDITION-ID'];
33
+ }
34
+ // When rendition ID is not present, compare attributes
35
+ return !(
36
+ customAttributes || [
37
+ 'LANGUAGE',
38
+ 'NAME',
39
+ 'CHARACTERISTICS',
40
+ 'AUTOSELECT',
41
+ 'DEFAULT',
42
+ 'FORCED',
43
+ 'ASSOC-LANGUAGE',
44
+ ]
45
+ ).some(
46
+ (subtitleAttribute) =>
47
+ attrs1[subtitleAttribute] !== attrs2[subtitleAttribute],
48
+ );
49
+ }
@@ -0,0 +1,301 @@
1
+ import {
2
+ fillInMissingAV01Params,
3
+ getCodecsForMimeType,
4
+ mimeTypeForCodec,
5
+ userAgentHevcSupportIsInaccurate,
6
+ } from './codecs';
7
+ import { isHEVC } from './mp4-tools';
8
+ import type { AudioTracksByGroup } from './rendition-helper';
9
+ import type { Level, VideoRange } from '../types/level';
10
+ import type { AudioSelectionOption } from '../types/media-playlist';
11
+
12
+ export type MediaDecodingInfo = {
13
+ supported: boolean;
14
+ configurations: readonly MediaDecodingConfiguration[];
15
+ decodingInfoResults: readonly MediaCapabilitiesDecodingInfo[];
16
+ error?: Error;
17
+ };
18
+
19
+ // @ts-ignore
20
+ const supportedResult: MediaCapabilitiesDecodingInfo = {
21
+ supported: true,
22
+ powerEfficient: true,
23
+ smooth: true,
24
+ // keySystemAccess: null,
25
+ };
26
+
27
+ // @ts-ignore
28
+ const unsupportedResult: MediaCapabilitiesDecodingInfo = {
29
+ supported: false,
30
+ smooth: false,
31
+ powerEfficient: false,
32
+ // keySystemAccess: null,
33
+ };
34
+
35
+ export const SUPPORTED_INFO_DEFAULT: MediaDecodingInfo = {
36
+ supported: true,
37
+ configurations: [] as MediaDecodingConfiguration[],
38
+ decodingInfoResults: [supportedResult],
39
+ } as const;
40
+
41
+ export function getUnsupportedResult(
42
+ error: Error,
43
+ configurations: MediaDecodingConfiguration[],
44
+ ): MediaDecodingInfo {
45
+ return {
46
+ supported: false,
47
+ configurations,
48
+ decodingInfoResults: [unsupportedResult],
49
+ error,
50
+ };
51
+ }
52
+
53
+ export function requiresMediaCapabilitiesDecodingInfo(
54
+ level: Level,
55
+ audioTracksByGroup: AudioTracksByGroup,
56
+ currentVideoRange: VideoRange | undefined,
57
+ currentFrameRate: number,
58
+ currentBw: number,
59
+ audioPreference: AudioSelectionOption | undefined,
60
+ ): boolean {
61
+ // Only test support when configuration is exceeds minimum options
62
+ const videoCodecs = level.videoCodec;
63
+ const audioGroups = level.audioCodec ? level.audioGroups : null;
64
+ const audioCodecPreference = audioPreference?.audioCodec;
65
+ const channelsPreference = audioPreference?.channels;
66
+ const maxChannels = channelsPreference
67
+ ? parseInt(channelsPreference)
68
+ : audioCodecPreference
69
+ ? Infinity
70
+ : 2;
71
+ let audioChannels: Record<string, number> | null = null;
72
+ if (audioGroups?.length) {
73
+ try {
74
+ if (audioGroups.length === 1 && audioGroups[0]) {
75
+ audioChannels = audioTracksByGroup.groups[audioGroups[0]].channels;
76
+ } else {
77
+ audioChannels = audioGroups.reduce(
78
+ (acc, groupId) => {
79
+ if (groupId) {
80
+ const audioTrackGroup = audioTracksByGroup.groups[groupId];
81
+ if (!audioTrackGroup) {
82
+ throw new Error(`Audio track group ${groupId} not found`);
83
+ }
84
+ // Sum all channel key values
85
+ Object.keys(audioTrackGroup.channels).forEach((key) => {
86
+ acc[key] = (acc[key] || 0) + audioTrackGroup.channels[key];
87
+ });
88
+ }
89
+ return acc;
90
+ },
91
+ { 2: 0 },
92
+ );
93
+ }
94
+ } catch (error) {
95
+ return true;
96
+ }
97
+ }
98
+ return (
99
+ (videoCodecs !== undefined &&
100
+ // Force media capabilities check for HEVC to avoid failure on Windows
101
+ (videoCodecs.split(',').some((videoCodec) => isHEVC(videoCodec)) ||
102
+ (level.width > 1920 && level.height > 1088) ||
103
+ (level.height > 1920 && level.width > 1088) ||
104
+ level.frameRate > Math.max(currentFrameRate, 30) ||
105
+ (level.videoRange !== 'SDR' &&
106
+ level.videoRange !== currentVideoRange) ||
107
+ level.bitrate > Math.max(currentBw, 8e6))) ||
108
+ (!!audioChannels &&
109
+ Number.isFinite(maxChannels) &&
110
+ Object.keys(audioChannels).some(
111
+ (channels) => parseInt(channels) > maxChannels,
112
+ ))
113
+ );
114
+ }
115
+
116
+ export function getMediaDecodingInfoPromise(
117
+ level: Level,
118
+ audioTracksByGroup: AudioTracksByGroup,
119
+ mediaCapabilities: MediaCapabilities | undefined,
120
+ cache: Record<
121
+ string,
122
+ Promise<MediaCapabilitiesDecodingInfo> | undefined
123
+ > = {},
124
+ ): Promise<MediaDecodingInfo> {
125
+ const videoCodecs = level.videoCodec;
126
+ if ((!videoCodecs && !level.audioCodec) || !mediaCapabilities) {
127
+ return Promise.resolve(SUPPORTED_INFO_DEFAULT);
128
+ }
129
+
130
+ const configurations: MediaDecodingConfiguration[] = [];
131
+
132
+ const videoDecodeList = makeVideoConfigurations(level);
133
+ const videoCount = videoDecodeList.length;
134
+ const audioDecodeList = makeAudioConfigurations(
135
+ level,
136
+ audioTracksByGroup,
137
+ videoCount > 0,
138
+ );
139
+ const audioCount = audioDecodeList.length;
140
+ for (let i = videoCount || 1 * audioCount || 1; i--; ) {
141
+ const configuration: MediaDecodingConfiguration = {
142
+ type: 'media-source',
143
+ };
144
+ if (videoCount) {
145
+ configuration.video = videoDecodeList[i % videoCount];
146
+ }
147
+ if (audioCount) {
148
+ configuration.audio = audioDecodeList[i % audioCount];
149
+ const audioBitrate = configuration.audio.bitrate;
150
+ if (configuration.video && audioBitrate) {
151
+ configuration.video.bitrate -= audioBitrate;
152
+ }
153
+ }
154
+ configurations.push(configuration);
155
+ }
156
+
157
+ if (videoCodecs) {
158
+ // Override Windows Firefox HEVC MediaCapabilities result (https://github.com/video-dev/hls.js/issues/7046)
159
+ const ua = navigator.userAgent;
160
+ if (
161
+ videoCodecs.split(',').some((videoCodec) => isHEVC(videoCodec)) &&
162
+ userAgentHevcSupportIsInaccurate()
163
+ ) {
164
+ return Promise.resolve(
165
+ getUnsupportedResult(
166
+ new Error(
167
+ `Overriding Windows Firefox HEVC MediaCapabilities result based on user-agent string: (${ua})`,
168
+ ),
169
+ configurations,
170
+ ),
171
+ );
172
+ }
173
+ }
174
+
175
+ return Promise.all(
176
+ configurations.map((configuration) => {
177
+ // Cache MediaCapabilities promises
178
+ const decodingInfoKey = getMediaDecodingInfoKey(configuration);
179
+ return (
180
+ cache[decodingInfoKey] ||
181
+ (cache[decodingInfoKey] = mediaCapabilities.decodingInfo(configuration))
182
+ );
183
+ }),
184
+ )
185
+ .then((decodingInfoResults) => ({
186
+ supported: !decodingInfoResults.some((info) => !info.supported),
187
+ configurations,
188
+ decodingInfoResults,
189
+ }))
190
+ .catch((error) => ({
191
+ supported: false,
192
+ configurations,
193
+ decodingInfoResults: [] as MediaCapabilitiesDecodingInfo[],
194
+ error,
195
+ }));
196
+ }
197
+
198
+ function makeVideoConfigurations(level: Level): VideoConfiguration[] {
199
+ const videoCodecs = level.videoCodec?.split(',');
200
+ const bitrate = getVariantDecodingBitrate(level);
201
+ const width = level.width || 640;
202
+ const height = level.height || 480;
203
+ // Assume a framerate of 30fps since MediaCapabilities will not accept Level default of 0.
204
+ const framerate = level.frameRate || 30;
205
+ const videoRange = level.videoRange.toLowerCase() as 'sdr' | 'pq' | 'hlg';
206
+ return videoCodecs
207
+ ? videoCodecs.map((videoCodec: string) => {
208
+ const videoConfiguration: VideoConfiguration = {
209
+ contentType: mimeTypeForCodec(
210
+ fillInMissingAV01Params(videoCodec),
211
+ 'video',
212
+ ),
213
+ width,
214
+ height,
215
+ bitrate,
216
+ framerate,
217
+ };
218
+ if (videoRange !== 'sdr') {
219
+ videoConfiguration.transferFunction = videoRange as TransferFunction;
220
+ }
221
+ return videoConfiguration;
222
+ })
223
+ : [];
224
+ }
225
+
226
+ function makeAudioConfigurations(
227
+ level: Level,
228
+ audioTracksByGroup: AudioTracksByGroup,
229
+ hasVideo: boolean,
230
+ ): AudioConfiguration[] {
231
+ const audioCodecs = level.audioCodec?.split(',');
232
+ const combinedBitrate = getVariantDecodingBitrate(level);
233
+ if (audioCodecs && level.audioGroups) {
234
+ return level.audioGroups.reduce((configurations, audioGroupId) => {
235
+ const tracks = audioGroupId
236
+ ? audioTracksByGroup.groups[audioGroupId]?.tracks
237
+ : null;
238
+ if (tracks) {
239
+ return tracks.reduce((configs, audioTrack) => {
240
+ if (audioTrack.groupId === audioGroupId) {
241
+ const channelsNumber = parseFloat(audioTrack.channels || '');
242
+ audioCodecs.forEach((audioCodec) => {
243
+ const audioConfiguration: AudioConfiguration = {
244
+ contentType: mimeTypeForCodec(audioCodec, 'audio'),
245
+ bitrate: hasVideo
246
+ ? estimatedAudioBitrate(audioCodec, combinedBitrate)
247
+ : combinedBitrate,
248
+ };
249
+ if (channelsNumber) {
250
+ audioConfiguration.channels = '' + channelsNumber;
251
+ }
252
+ configs.push(audioConfiguration);
253
+ });
254
+ }
255
+ return configs;
256
+ }, configurations);
257
+ }
258
+ return configurations;
259
+ }, [] as AudioConfiguration[]);
260
+ }
261
+ return [];
262
+ }
263
+
264
+ export function estimatedAudioBitrate(
265
+ audioCodec: string | undefined,
266
+ levelBitrate: number,
267
+ ): number {
268
+ if (levelBitrate <= 1) {
269
+ return 1;
270
+ }
271
+ let audioBitrate = 128000;
272
+ if (audioCodec === 'ec-3') {
273
+ audioBitrate = 768000;
274
+ } else if (audioCodec === 'ac-3') {
275
+ audioBitrate = 640000;
276
+ }
277
+ return Math.min(levelBitrate / 2, audioBitrate); // Don't exceed some % of level bitrate
278
+ }
279
+
280
+ function getVariantDecodingBitrate(level: Level): number {
281
+ return (
282
+ Math.ceil(Math.max(level.bitrate * 0.9, level.averageBitrate) / 1000) *
283
+ 1000 || 1
284
+ );
285
+ }
286
+
287
+ function getMediaDecodingInfoKey(config: MediaDecodingConfiguration): string {
288
+ let key = '';
289
+ const { audio, video } = config;
290
+ if (video) {
291
+ const codec = getCodecsForMimeType(video.contentType);
292
+ key += `${codec}_r${video.height}x${video.width}f${Math.ceil(video.framerate)}${
293
+ video.transferFunction || 'sd'
294
+ }_${Math.ceil(video.bitrate / 1e5)}`;
295
+ }
296
+ if (audio) {
297
+ const codec = getCodecsForMimeType(audio.contentType);
298
+ key += `${video ? '_' : ''}${codec}_c${audio.channels}`;
299
+ }
300
+ return key;
301
+ }
@@ -0,0 +1,210 @@
1
+ import { optionalSelf } from './global';
2
+ import { changeEndianness } from './keysystem-util';
3
+ import { base64Decode } from './numeric-encoding-utils';
4
+ import type { DRMSystemOptions, EMEControllerConfig } from '../config';
5
+
6
+ /**
7
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Navigator/requestMediaKeySystemAccess
8
+ */
9
+ export const enum KeySystems {
10
+ CLEARKEY = 'org.w3.clearkey',
11
+ FAIRPLAY = 'com.apple.fps',
12
+ PLAYREADY = 'com.microsoft.playready',
13
+ WIDEVINE = 'com.widevine.alpha',
14
+ }
15
+
16
+ // Playlist #EXT-X-KEY KEYFORMAT values
17
+ export const enum KeySystemFormats {
18
+ CLEARKEY = 'org.w3.clearkey',
19
+ FAIRPLAY = 'com.apple.streamingkeydelivery',
20
+ PLAYREADY = 'com.microsoft.playready',
21
+ WIDEVINE = 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed',
22
+ }
23
+
24
+ export function keySystemFormatToKeySystemDomain(
25
+ format: KeySystemFormats,
26
+ ): KeySystems | undefined {
27
+ switch (format) {
28
+ case KeySystemFormats.FAIRPLAY:
29
+ return KeySystems.FAIRPLAY;
30
+ case KeySystemFormats.PLAYREADY:
31
+ return KeySystems.PLAYREADY;
32
+ case KeySystemFormats.WIDEVINE:
33
+ return KeySystems.WIDEVINE;
34
+ case KeySystemFormats.CLEARKEY:
35
+ return KeySystems.CLEARKEY;
36
+ }
37
+ }
38
+
39
+ // System IDs for which we can extract a key ID from "encrypted" event PSSH
40
+ export const enum KeySystemIds {
41
+ CENC = '1077efecc0b24d02ace33c1e52e2fb4b',
42
+ CLEARKEY = 'e2719d58a985b3c9781ab030af78d30e',
43
+ FAIRPLAY = '94ce86fb07ff4f43adb893d2fa968ca2',
44
+ PLAYREADY = '9a04f07998404286ab92e65be0885f95',
45
+ WIDEVINE = 'edef8ba979d64acea3c827dcd51d21ed',
46
+ }
47
+
48
+ export function keySystemIdToKeySystemDomain(
49
+ systemId: KeySystemIds,
50
+ ): KeySystems | undefined {
51
+ if (systemId === KeySystemIds.WIDEVINE) {
52
+ return KeySystems.WIDEVINE;
53
+ } else if (systemId === KeySystemIds.PLAYREADY) {
54
+ return KeySystems.PLAYREADY;
55
+ } else if (
56
+ systemId === KeySystemIds.CENC ||
57
+ systemId === KeySystemIds.CLEARKEY
58
+ ) {
59
+ return KeySystems.CLEARKEY;
60
+ }
61
+ }
62
+
63
+ export function keySystemDomainToKeySystemFormat(
64
+ keySystem: KeySystems,
65
+ ): KeySystemFormats | undefined {
66
+ switch (keySystem) {
67
+ case KeySystems.FAIRPLAY:
68
+ return KeySystemFormats.FAIRPLAY;
69
+ case KeySystems.PLAYREADY:
70
+ return KeySystemFormats.PLAYREADY;
71
+ case KeySystems.WIDEVINE:
72
+ return KeySystemFormats.WIDEVINE;
73
+ case KeySystems.CLEARKEY:
74
+ return KeySystemFormats.CLEARKEY;
75
+ }
76
+ }
77
+
78
+ export function getKeySystemsForConfig(
79
+ config: EMEControllerConfig,
80
+ ): KeySystems[] {
81
+ const { drmSystems, widevineLicenseUrl } = config;
82
+ const keySystemsToAttempt: KeySystems[] = drmSystems
83
+ ? [
84
+ KeySystems.FAIRPLAY,
85
+ KeySystems.WIDEVINE,
86
+ KeySystems.PLAYREADY,
87
+ KeySystems.CLEARKEY,
88
+ ].filter((keySystem) => !!drmSystems[keySystem])
89
+ : [];
90
+ if (!keySystemsToAttempt[KeySystems.WIDEVINE] && widevineLicenseUrl) {
91
+ keySystemsToAttempt.push(KeySystems.WIDEVINE);
92
+ }
93
+ return keySystemsToAttempt;
94
+ }
95
+
96
+ export type MediaKeyFunc = (
97
+ keySystem: KeySystems,
98
+ supportedConfigurations: MediaKeySystemConfiguration[],
99
+ ) => Promise<MediaKeySystemAccess>;
100
+
101
+ export const requestMediaKeySystemAccess = (function (): MediaKeyFunc | null {
102
+ if (optionalSelf?.navigator?.requestMediaKeySystemAccess) {
103
+ return self.navigator.requestMediaKeySystemAccess.bind(self.navigator);
104
+ } else {
105
+ return null;
106
+ }
107
+ })();
108
+
109
+ /**
110
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/MediaKeySystemConfiguration
111
+ */
112
+ export function getSupportedMediaKeySystemConfigurations(
113
+ keySystem: KeySystems,
114
+ audioCodecs: string[],
115
+ videoCodecs: string[],
116
+ drmSystemOptions: DRMSystemOptions,
117
+ ): MediaKeySystemConfiguration[] {
118
+ let initDataTypes: string[];
119
+ switch (keySystem) {
120
+ case KeySystems.FAIRPLAY:
121
+ initDataTypes = ['cenc', 'sinf'];
122
+ break;
123
+ case KeySystems.WIDEVINE:
124
+ case KeySystems.PLAYREADY:
125
+ initDataTypes = ['cenc'];
126
+ break;
127
+ case KeySystems.CLEARKEY:
128
+ initDataTypes = ['cenc', 'keyids'];
129
+ break;
130
+ default:
131
+ throw new Error(`Unknown key-system: ${keySystem}`);
132
+ }
133
+ return createMediaKeySystemConfigurations(
134
+ initDataTypes,
135
+ audioCodecs,
136
+ videoCodecs,
137
+ drmSystemOptions,
138
+ );
139
+ }
140
+
141
+ function createMediaKeySystemConfigurations(
142
+ initDataTypes: string[],
143
+ audioCodecs: string[],
144
+ videoCodecs: string[],
145
+ drmSystemOptions: DRMSystemOptions,
146
+ ): MediaKeySystemConfiguration[] {
147
+ const baseConfig: MediaKeySystemConfiguration = {
148
+ initDataTypes: initDataTypes,
149
+ persistentState: drmSystemOptions.persistentState || 'optional',
150
+ distinctiveIdentifier: drmSystemOptions.distinctiveIdentifier || 'optional',
151
+ sessionTypes: drmSystemOptions.sessionTypes || [
152
+ drmSystemOptions.sessionType || 'temporary',
153
+ ],
154
+ audioCapabilities: audioCodecs.map((codec) => ({
155
+ contentType: `audio/mp4; codecs=${codec}`,
156
+ robustness: drmSystemOptions.audioRobustness || '',
157
+ encryptionScheme: drmSystemOptions.audioEncryptionScheme || null,
158
+ })),
159
+ videoCapabilities: videoCodecs.map((codec) => ({
160
+ contentType: `video/mp4; codecs=${codec}`,
161
+ robustness: drmSystemOptions.videoRobustness || '',
162
+ encryptionScheme: drmSystemOptions.videoEncryptionScheme || null,
163
+ })),
164
+ };
165
+
166
+ return [baseConfig];
167
+ }
168
+
169
+ export function isPersistentSessionType(
170
+ drmSystemOptions: DRMSystemOptions | undefined,
171
+ ): boolean {
172
+ return (
173
+ !!drmSystemOptions &&
174
+ (drmSystemOptions.sessionType === 'persistent-license' ||
175
+ !!drmSystemOptions.sessionTypes?.some(
176
+ (type) => type === 'persistent-license',
177
+ ))
178
+ );
179
+ }
180
+
181
+ export function parsePlayReadyWRM(keyBytes: Uint8Array<ArrayBuffer>) {
182
+ const keyBytesUtf16 = new Uint16Array(
183
+ keyBytes.buffer,
184
+ keyBytes.byteOffset,
185
+ keyBytes.byteLength / 2,
186
+ );
187
+ const keyByteStr = String.fromCharCode.apply(null, Array.from(keyBytesUtf16));
188
+
189
+ // Parse Playready WRMHeader XML
190
+ const xmlKeyBytes = keyByteStr.substring(
191
+ keyByteStr.indexOf('<'),
192
+ keyByteStr.length,
193
+ );
194
+ const parser = new DOMParser();
195
+ const xmlDoc = parser.parseFromString(xmlKeyBytes, 'text/xml');
196
+ const keyData = xmlDoc.getElementsByTagName('KID')[0];
197
+ if (keyData) {
198
+ const keyId = keyData.childNodes[0]
199
+ ? keyData.childNodes[0].nodeValue
200
+ : keyData.getAttribute('VALUE');
201
+ if (keyId) {
202
+ const keyIdArray = base64Decode(keyId).subarray(0, 16);
203
+ // KID value in PRO is a base64-encoded little endian GUID interpretation of UUID
204
+ // KID value in ‘tenc’ is a big endian UUID GUID interpretation of UUID
205
+ changeEndianness(keyIdArray);
206
+ return keyIdArray;
207
+ }
208
+ }
209
+ return null;
210
+ }