hls.js 1.5.14-0.canary.10567 → 1.5.14-0.canary.10569

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.
package/package.json CHANGED
@@ -130,5 +130,5 @@
130
130
  "url-toolkit": "2.2.5",
131
131
  "wrangler": "3.72.3"
132
132
  },
133
- "version": "1.5.14-0.canary.10567"
133
+ "version": "1.5.14-0.canary.10569"
134
134
  }
@@ -12,10 +12,10 @@ import {
12
12
  keySystemDomainToKeySystemFormat as keySystemToKeySystemFormat,
13
13
  KeySystemFormats,
14
14
  keySystemFormatToKeySystemDomain,
15
- KeySystemIds,
16
15
  keySystemIdToKeySystemDomain,
17
16
  KeySystems,
18
17
  requestMediaKeySystemAccess,
18
+ parsePlayReadyWRM,
19
19
  } from '../utils/mediakeys-helper';
20
20
  import { strToUtf8array } from '../utils/utf8-utils';
21
21
  import { base64Decode } from '../utils/numeric-encoding-utils';
@@ -25,8 +25,8 @@ import {
25
25
  bin2str,
26
26
  parseMultiPssh,
27
27
  parseSinf,
28
- PsshData,
29
- PsshInvalidResult,
28
+ type PsshData,
29
+ type PsshInvalidResult,
30
30
  } from '../utils/mp4-tools';
31
31
  import { EventEmitter } from 'eventemitter3';
32
32
  import type Hls from '../hls';
@@ -127,7 +127,7 @@ class EMEController extends Logger implements ComponentAPI {
127
127
  this.hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
128
128
  }
129
129
 
130
- private getLicenseServerUrl(keySystem: KeySystems): string | never {
130
+ private getLicenseServerUrl(keySystem: KeySystems): string | undefined {
131
131
  const { drmSystems, widevineLicenseUrl } = this.config;
132
132
  const keySystemConfiguration = drmSystems[keySystem];
133
133
 
@@ -139,10 +139,16 @@ class EMEController extends Logger implements ComponentAPI {
139
139
  if (keySystem === KeySystems.WIDEVINE && widevineLicenseUrl) {
140
140
  return widevineLicenseUrl;
141
141
  }
142
+ }
142
143
 
143
- throw new Error(
144
- `no license server URL configured for key-system "${keySystem}"`,
145
- );
144
+ private getLicenseServerUrlOrThrow(keySystem: KeySystems): string | never {
145
+ const url = this.getLicenseServerUrl(keySystem);
146
+ if (url === undefined) {
147
+ throw new Error(
148
+ `no license server URL configured for key-system "${keySystem}"`,
149
+ );
150
+ }
151
+ return url;
146
152
  }
147
153
 
148
154
  private getServerCertificateUrl(keySystem: KeySystems): string | void {
@@ -524,12 +530,12 @@ class EMEController extends Logger implements ComponentAPI {
524
530
  return;
525
531
  }
526
532
 
527
- let keyId: Uint8Array | undefined;
533
+ let keyId: Uint8Array | null | undefined;
528
534
  let keySystemDomain: KeySystems | undefined;
529
535
 
530
536
  if (
531
537
  initDataType === 'sinf' &&
532
- this.config.drmSystems[KeySystems.FAIRPLAY]
538
+ this.getLicenseServerUrl(KeySystems.FAIRPLAY)
533
539
  ) {
534
540
  // Match sinf keyId to playlist skd://keyId=
535
541
  const json = bin2str(new Uint8Array(initData));
@@ -547,12 +553,25 @@ class EMEController extends Logger implements ComponentAPI {
547
553
  this.warn(`${logMessage} Failed to parse sinf: ${error}`);
548
554
  return;
549
555
  }
550
- } else {
556
+ } else if (this.getLicenseServerUrl(KeySystems.WIDEVINE)) {
551
557
  // Support Widevine clear-lead key-session creation (otherwise depend on playlist keys)
552
558
  const psshResults = parseMultiPssh(initData);
553
- const psshInfo = psshResults.filter(
554
- (pssh): pssh is PsshData => pssh.systemId === KeySystemIds.WIDEVINE,
555
- )[0];
559
+
560
+ // TODO: If using keySystemAccessPromises we might want to wait until one is resolved
561
+ let keySystems = Object.keys(
562
+ this.keySystemAccessPromises,
563
+ ) as KeySystems[];
564
+ if (!keySystems.length) {
565
+ keySystems = getKeySystemsForConfig(this.config);
566
+ }
567
+
568
+ const psshInfo = psshResults.filter((pssh): pssh is PsshData => {
569
+ const keySystem = pssh.systemId
570
+ ? keySystemIdToKeySystemDomain(pssh.systemId)
571
+ : null;
572
+ return keySystem ? keySystems.indexOf(keySystem) > -1 : false;
573
+ })[0];
574
+
556
575
  if (!psshInfo) {
557
576
  if (
558
577
  psshResults.length === 0 ||
@@ -568,10 +587,15 @@ class EMEController extends Logger implements ComponentAPI {
568
587
  }
569
588
  return;
570
589
  }
590
+
571
591
  keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
572
592
  if (psshInfo.version === 0 && psshInfo.data) {
573
- const offset = psshInfo.data.length - 22;
574
- keyId = psshInfo.data.subarray(offset, offset + 16);
593
+ if (keySystemDomain === KeySystems.WIDEVINE) {
594
+ const offset = psshInfo.data.length - 22;
595
+ keyId = psshInfo.data.subarray(offset, offset + 16);
596
+ } else if (keySystemDomain === KeySystems.PLAYREADY) {
597
+ keyId = parsePlayReadyWRM(psshInfo.data);
598
+ }
575
599
  }
576
600
  }
577
601
 
@@ -1098,7 +1122,7 @@ class EMEController extends Logger implements ComponentAPI {
1098
1122
  ): Promise<ArrayBuffer> {
1099
1123
  const keyLoadPolicy = this.config.keyLoadPolicy.default;
1100
1124
  return new Promise((resolve, reject) => {
1101
- const url = this.getLicenseServerUrl(keySessionContext.keySystem);
1125
+ const url = this.getLicenseServerUrlOrThrow(keySessionContext.keySystem);
1102
1126
  this.log(`Sending license request to URL: ${url}`);
1103
1127
  const xhr = new XMLHttpRequest();
1104
1128
  xhr.responseType = 'arraybuffer';
@@ -7,6 +7,7 @@ import * as MpegAudio from './mpegaudio';
7
7
  import { getId3Data } from '@svta/common-media-library/id3/getId3Data';
8
8
  import type { HlsEventEmitter } from '../../events';
9
9
  import type { HlsConfig } from '../../config';
10
+ import type { DemuxedAudioTrack } from '../../types/demuxer';
10
11
  import type { ILogger } from '../../utils/logger';
11
12
 
12
13
  class AACDemuxer extends BaseAudioDemuxer {
@@ -71,7 +72,7 @@ class AACDemuxer extends BaseAudioDemuxer {
71
72
  return ADTS.canParse(data, offset);
72
73
  }
73
74
 
74
- appendFrame(track, data, offset) {
75
+ appendFrame(track: DemuxedAudioTrack, data: Uint8Array, offset: number) {
75
76
  ADTS.initTrackConfig(
76
77
  track,
77
78
  this.observer,
@@ -13,12 +13,12 @@ import type {
13
13
  } from '../../types/demuxer';
14
14
 
15
15
  type AudioConfig = {
16
- config: number[];
16
+ config: [number, number];
17
17
  samplerate: number;
18
18
  channelCount: number;
19
19
  codec: string;
20
20
  parsedCodec: string;
21
- manifestCodec: string;
21
+ manifestCodec: string | undefined;
22
22
  };
23
23
 
24
24
  type FrameHeader = {
@@ -30,24 +30,15 @@ export function getAudioConfig(
30
30
  observer: HlsEventEmitter,
31
31
  data: Uint8Array,
32
32
  offset: number,
33
- audioCodec: string,
33
+ manifestCodec: string | undefined,
34
34
  ): AudioConfig | void {
35
- let adtsObjectType: number;
36
- let originalAdtsObjectType: number;
37
- let adtsExtensionSamplingIndex: number;
38
- let adtsChannelConfig: number;
39
- let config: number[];
40
- const userAgent = navigator.userAgent.toLowerCase();
41
- const manifestCodec = audioCodec;
42
35
  const adtsSamplingRates = [
43
36
  96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025,
44
37
  8000, 7350,
45
38
  ];
46
- // byte 2
47
- adtsObjectType = originalAdtsObjectType =
48
- ((data[offset + 2] & 0xc0) >>> 6) + 1;
49
- const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
50
- if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
39
+ const byte2 = data[offset + 2];
40
+ const adtsSamplingIndex = (byte2 >> 2) & 0xf;
41
+ if (adtsSamplingIndex > 12) {
51
42
  const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
52
43
  observer.emit(Events.ERROR, Events.ERROR, {
53
44
  type: ErrorTypes.MEDIA_ERROR,
@@ -58,66 +49,12 @@ export function getAudioConfig(
58
49
  });
59
50
  return;
60
51
  }
61
- adtsChannelConfig = (data[offset + 2] & 0x01) << 2;
62
- // byte 3
63
- adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
64
- logger.log(
65
- `manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`,
66
- );
67
- // Firefox and Pale Moon: freq less than 24kHz = AAC SBR (HE-AAC)
68
- if (/firefox|palemoon/i.test(userAgent)) {
69
- if (adtsSamplingIndex >= 6) {
70
- adtsObjectType = 5;
71
- config = new Array(4);
72
- // HE-AAC uses SBR (Spectral Band Replication) , high frequencies are constructed from low frequencies
73
- // there is a factor 2 between frame sample rate and output sample rate
74
- // multiply frequency by 2 (see table below, equivalent to substract 3)
75
- adtsExtensionSamplingIndex = adtsSamplingIndex - 3;
76
- } else {
77
- adtsObjectType = 2;
78
- config = new Array(2);
79
- adtsExtensionSamplingIndex = adtsSamplingIndex;
80
- }
81
- // Android : always use AAC
82
- } else if (userAgent.indexOf('android') !== -1) {
83
- adtsObjectType = 2;
84
- config = new Array(2);
85
- adtsExtensionSamplingIndex = adtsSamplingIndex;
86
- } else {
87
- /* for other browsers (Chrome/Vivaldi/Opera ...)
88
- always force audio type to be HE-AAC SBR, as some browsers do not support audio codec switch properly (like Chrome ...)
89
- */
90
- adtsObjectType = 5;
91
- config = new Array(4);
92
- // if (manifest codec is HE-AAC or HE-AACv2) OR (manifest codec not specified AND frequency less than 24kHz)
93
- if (
94
- (audioCodec &&
95
- (audioCodec.indexOf('mp4a.40.29') !== -1 ||
96
- audioCodec.indexOf('mp4a.40.5') !== -1)) ||
97
- (!audioCodec && adtsSamplingIndex >= 6)
98
- ) {
99
- // HE-AAC uses SBR (Spectral Band Replication) , high frequencies are constructed from low frequencies
100
- // there is a factor 2 between frame sample rate and output sample rate
101
- // multiply frequency by 2 (see table below, equivalent to substract 3)
102
- adtsExtensionSamplingIndex = adtsSamplingIndex - 3;
103
- } else {
104
- // if (manifest codec is AAC) AND (frequency less than 24kHz AND nb channel is 1) OR (manifest codec not specified and mono audio)
105
- // Chrome fails to play back with low frequency AAC LC mono when initialized with HE-AAC. This is not a problem with stereo.
106
- if (
107
- (audioCodec &&
108
- audioCodec.indexOf('mp4a.40.2') !== -1 &&
109
- ((adtsSamplingIndex >= 6 && adtsChannelConfig === 1) ||
110
- /vivaldi/i.test(userAgent))) ||
111
- (!audioCodec && adtsChannelConfig === 1)
112
- ) {
113
- adtsObjectType = 2;
114
- config = new Array(2);
115
- }
116
- adtsExtensionSamplingIndex = adtsSamplingIndex;
117
- }
118
- }
52
+ // MPEG-4 Audio Object Type (profile_ObjectType+1)
53
+ const adtsObjectType = ((byte2 >> 6) & 0x3) + 1;
54
+ const channelCount = ((data[offset + 3] >> 6) & 0x3) | ((byte2 & 1) << 2);
55
+ const codec = 'mp4a.40.' + adtsObjectType;
119
56
  /* refer to http://wiki.multimedia.cx/index.php?title=MPEG-4_Audio#Audio_Specific_Config
120
- ISO 14496-3 (AAC).pdf - Table 1.13 — Syntax of AudioSpecificConfig()
57
+ ISO/IEC 14496-3 - Table 1.13 — Syntax of AudioSpecificConfig()
121
58
  Audio Profile / Audio Object Type
122
59
  0: Null
123
60
  1: AAC Main
@@ -150,27 +87,27 @@ export function getAudioConfig(
150
87
  2: 2 channels: front-left, front-right
151
88
  */
152
89
  // audioObjectType = profile => profile, the MPEG-4 Audio Object Type minus 1
153
- config[0] = adtsObjectType << 3;
154
- // samplingFrequencyIndex
155
- config[0] |= (adtsSamplingIndex & 0x0e) >> 1;
156
- config[1] |= (adtsSamplingIndex & 0x01) << 7;
157
- // channelConfiguration
158
- config[1] |= adtsChannelConfig << 3;
159
- if (adtsObjectType === 5) {
160
- // adtsExtensionSamplingIndex
161
- config[1] |= (adtsExtensionSamplingIndex & 0x0e) >> 1;
162
- config[2] = (adtsExtensionSamplingIndex & 0x01) << 7;
163
- // adtsObjectType (force to 2, chrome is checking that object type is less than 5 ???
164
- // https://chromium.googlesource.com/chromium/src.git/+/master/media/formats/mp4/aac.cc
165
- config[2] |= 2 << 2;
166
- config[3] = 0;
90
+ const samplerate = adtsSamplingRates[adtsSamplingIndex];
91
+ let aacSampleIndex = adtsSamplingIndex;
92
+ if (adtsObjectType === 5 || adtsObjectType === 29) {
93
+ // HE-AAC uses SBR (Spectral Band Replication) , high frequencies are constructed from low frequencies
94
+ // there is a factor 2 between frame sample rate and output sample rate
95
+ // multiply frequency by 2 (see table above, equivalent to substract 3)
96
+ aacSampleIndex -= 3;
167
97
  }
98
+ const config: [number, number] = [
99
+ (adtsObjectType << 3) | ((aacSampleIndex & 0x0e) >> 1),
100
+ ((aacSampleIndex & 0x01) << 7) | (channelCount << 3),
101
+ ];
102
+ logger.log(
103
+ `manifest codec:${manifestCodec}, parsed codec:${codec}, channels:${channelCount}, rate:${samplerate} (ADTS object type:${adtsObjectType} sampling index:${adtsSamplingIndex})`,
104
+ );
168
105
  return {
169
106
  config,
170
- samplerate: adtsSamplingRates[adtsSamplingIndex],
171
- channelCount: adtsChannelConfig,
172
- codec: 'mp4a.40.' + adtsObjectType,
173
- parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
107
+ samplerate,
108
+ channelCount,
109
+ codec,
110
+ parsedCodec: codec,
174
111
  manifestCodec,
175
112
  };
176
113
  }
@@ -236,22 +173,14 @@ export function initTrackConfig(
236
173
  observer: HlsEventEmitter,
237
174
  data: Uint8Array,
238
175
  offset: number,
239
- audioCodec: string,
176
+ audioCodec: string | undefined,
240
177
  ) {
241
178
  if (!track.samplerate) {
242
179
  const config = getAudioConfig(observer, data, offset, audioCodec);
243
180
  if (!config) {
244
181
  return;
245
182
  }
246
- track.config = config.config;
247
- track.samplerate = config.samplerate;
248
- track.channelCount = config.channelCount;
249
- track.codec = config.codec;
250
- track.manifestCodec = config.manifestCodec;
251
- track.parsedCodec = config.parsedCodec;
252
- logger.log(
253
- `parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`,
254
- );
183
+ Object.assign(track, config);
255
184
  }
256
185
  }
257
186
 
@@ -648,13 +648,7 @@ class TSDemuxer implements Demuxer {
648
648
  }
649
649
  }
650
650
 
651
- ADTS.initTrackConfig(
652
- track,
653
- this.observer,
654
- data,
655
- offset,
656
- this.audioCodec as string,
657
- );
651
+ ADTS.initTrackConfig(track, this.observer, data, offset, this.audioCodec);
658
652
 
659
653
  let pts: number;
660
654
  if (pes.pts !== undefined) {
@@ -1,12 +1,8 @@
1
- import {
2
- changeEndianness,
3
- convertDataUriToArrayBytes,
4
- } from '../utils/keysystem-util';
1
+ import { convertDataUriToArrayBytes } from '../utils/keysystem-util';
5
2
  import { isFullSegmentEncryption } from '../utils/encryption-methods-util';
6
- import { KeySystemFormats } from '../utils/mediakeys-helper';
3
+ import { KeySystemFormats, parsePlayReadyWRM } from '../utils/mediakeys-helper';
7
4
  import { mp4pssh } from '../utils/mp4-tools';
8
5
  import { logger } from '../utils/logger';
9
- import { base64Decode } from '../utils/numeric-encoding-utils';
10
6
 
11
7
  let keyUriToKeyIdMap: { [uri: string]: Uint8Array } = {};
12
8
 
@@ -122,6 +118,8 @@ export class LevelKey implements DecryptData {
122
118
  if (keyBytes) {
123
119
  switch (this.keyFormat) {
124
120
  case KeySystemFormats.WIDEVINE:
121
+ // Setting `pssh` on this LevelKey/DecryptData allows HLS.js to generate a session using
122
+ // the playlist-key before the "encrypted" event. (Comment out to only use "encrypted" path.)
125
123
  this.pssh = keyBytes;
126
124
  // In case of widevine keyID is embedded in PSSH box. Read Key ID.
127
125
  if (keyBytes.length >= 22) {
@@ -137,38 +135,12 @@ export class LevelKey implements DecryptData {
137
135
  0x5b, 0xe0, 0x88, 0x5f, 0x95,
138
136
  ]);
139
137
 
138
+ // Setting `pssh` on this LevelKey/DecryptData allows HLS.js to generate a session using
139
+ // the playlist-key before the "encrypted" event. (Comment out to only use "encrypted" path.)
140
140
  this.pssh = mp4pssh(PlayReadyKeySystemUUID, null, keyBytes);
141
141
 
142
- const keyBytesUtf16 = new Uint16Array(
143
- keyBytes.buffer,
144
- keyBytes.byteOffset,
145
- keyBytes.byteLength / 2,
146
- );
147
- const keyByteStr = String.fromCharCode.apply(
148
- null,
149
- Array.from(keyBytesUtf16),
150
- );
151
-
152
- // Parse Playready WRMHeader XML
153
- const xmlKeyBytes = keyByteStr.substring(
154
- keyByteStr.indexOf('<'),
155
- keyByteStr.length,
156
- );
157
- const parser = new DOMParser();
158
- const xmlDoc = parser.parseFromString(xmlKeyBytes, 'text/xml');
159
- const keyData = xmlDoc.getElementsByTagName('KID')[0];
160
- if (keyData) {
161
- const keyId = keyData.childNodes[0]
162
- ? keyData.childNodes[0].nodeValue
163
- : keyData.getAttribute('VALUE');
164
- if (keyId) {
165
- const keyIdArray = base64Decode(keyId).subarray(0, 16);
166
- // KID value in PRO is a base64-encoded little endian GUID interpretation of UUID
167
- // KID value in ‘tenc’ is a big endian UUID GUID interpretation of UUID
168
- changeEndianness(keyIdArray);
169
- this.keyId = keyIdArray;
170
- }
171
- }
142
+ this.keyId = parsePlayReadyWRM(keyBytes);
143
+
172
144
  break;
173
145
  }
174
146
  default: {
@@ -2,6 +2,7 @@
2
2
  * Generate MP4 Box
3
3
  */
4
4
 
5
+ import type { DemuxedAudioTrack } from '../types/demuxer';
5
6
  import { appendUint8Array } from '../utils/mp4-tools';
6
7
 
7
8
  type HdlrTypes = {
@@ -739,43 +740,45 @@ class MP4 {
739
740
  );
740
741
  }
741
742
 
742
- static esds(track) {
743
- const configlen = track.config.length;
744
- return new Uint8Array(
745
- [
746
- 0x00, // version 0
747
- 0x00,
748
- 0x00,
749
- 0x00, // flags
743
+ static esds(track: DemuxedAudioTrack) {
744
+ const config = track.config as [number, number];
745
+ return new Uint8Array([
746
+ 0x00, // version 0
747
+ 0x00,
748
+ 0x00,
749
+ 0x00, // flags
750
750
 
751
- 0x03, // descriptor_type
752
- 0x17 + configlen, // length
753
- 0x00,
754
- 0x01, // es_id
755
- 0x00, // stream_priority
751
+ 0x03, // descriptor_type
752
+ 0x19, // length
756
753
 
757
- 0x04, // descriptor_type
758
- 0x0f + configlen, // length
759
- 0x40, // codec : mpeg4_audio
760
- 0x15, // stream_type
761
- 0x00,
762
- 0x00,
763
- 0x00, // buffer_size
764
- 0x00,
765
- 0x00,
766
- 0x00,
767
- 0x00, // maxBitrate
768
- 0x00,
769
- 0x00,
770
- 0x00,
771
- 0x00, // avgBitrate
754
+ 0x00,
755
+ 0x01, // es_id
756
+
757
+ 0x00, // stream_priority
758
+
759
+ 0x04, // descriptor_type
760
+ 0x11, // length
761
+ 0x40, // codec : mpeg4_audio
762
+ 0x15, // stream_type
763
+ 0x00,
764
+ 0x00,
765
+ 0x00, // buffer_size
766
+ 0x00,
767
+ 0x00,
768
+ 0x00,
769
+ 0x00, // maxBitrate
770
+ 0x00,
771
+ 0x00,
772
+ 0x00,
773
+ 0x00, // avgBitrate
772
774
 
773
- 0x05, // descriptor_type
774
- ]
775
- .concat([configlen])
776
- .concat(track.config)
777
- .concat([0x06, 0x01, 0x02]),
778
- ); // GASpecificConfig)); // length + audio config descriptor
775
+ 0x05, // descriptor_type
776
+ 0x02, // length
777
+ ...config,
778
+ 0x06,
779
+ 0x01,
780
+ 0x02, // GASpecificConfig)); // length + audio config descriptor
781
+ ]);
779
782
  }
780
783
 
781
784
  static audioStsd(track) {
@@ -1,5 +1,7 @@
1
1
  import type { DRMSystemOptions, EMEControllerConfig } from '../config';
2
2
  import { optionalSelf } from './global';
3
+ import { changeEndianness } from './keysystem-util';
4
+ import { base64Decode } from './numeric-encoding-utils';
3
5
 
4
6
  /**
5
7
  * @see https://developer.mozilla.org/en-US/docs/Web/API/Navigator/requestMediaKeySystemAccess
@@ -163,3 +165,34 @@ function createMediaKeySystemConfigurations(
163
165
 
164
166
  return [baseConfig];
165
167
  }
168
+
169
+ export function parsePlayReadyWRM(keyBytes: Uint8Array): Uint8Array | null {
170
+ const keyBytesUtf16 = new Uint16Array(
171
+ keyBytes.buffer,
172
+ keyBytes.byteOffset,
173
+ keyBytes.byteLength / 2,
174
+ );
175
+ const keyByteStr = String.fromCharCode.apply(null, Array.from(keyBytesUtf16));
176
+
177
+ // Parse Playready WRMHeader XML
178
+ const xmlKeyBytes = keyByteStr.substring(
179
+ keyByteStr.indexOf('<'),
180
+ keyByteStr.length,
181
+ );
182
+ const parser = new DOMParser();
183
+ const xmlDoc = parser.parseFromString(xmlKeyBytes, 'text/xml');
184
+ const keyData = xmlDoc.getElementsByTagName('KID')[0];
185
+ if (keyData) {
186
+ const keyId = keyData.childNodes[0]
187
+ ? keyData.childNodes[0].nodeValue
188
+ : keyData.getAttribute('VALUE');
189
+ if (keyId) {
190
+ const keyIdArray = base64Decode(keyId).subarray(0, 16);
191
+ // KID value in PRO is a base64-encoded little endian GUID interpretation of UUID
192
+ // KID value in ‘tenc’ is a big endian UUID GUID interpretation of UUID
193
+ changeEndianness(keyIdArray);
194
+ return keyIdArray;
195
+ }
196
+ }
197
+ return null;
198
+ }