hls.js 1.5.8-0.canary.10153 → 1.5.8-0.canary.10154

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.
@@ -512,7 +512,7 @@ function enableLogs(debugConfig, context, id) {
512
512
  // Some browsers don't allow to use bind on console object anyway
513
513
  // fallback to default if needed
514
514
  try {
515
- newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.5.8-0.canary.10153"}`);
515
+ newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.5.8-0.canary.10154"}`);
516
516
  } catch (e) {
517
517
  /* log fn threw an exception. All logger methods are no-ops. */
518
518
  return createLogger();
@@ -1049,273 +1049,6 @@ function sliceUint8(array, start, end) {
1049
1049
  return Uint8Array.prototype.slice ? array.slice(start, end) : new Uint8Array(Array.prototype.slice.call(array, start, end));
1050
1050
  }
1051
1051
 
1052
- // breaking up those two types in order to clarify what is happening in the decoding path.
1053
-
1054
- /**
1055
- * Returns true if an ID3 header can be found at offset in data
1056
- * @param data - The data to search
1057
- * @param offset - The offset at which to start searching
1058
- */
1059
- const isHeader$2 = (data, offset) => {
1060
- /*
1061
- * http://id3.org/id3v2.3.0
1062
- * [0] = 'I'
1063
- * [1] = 'D'
1064
- * [2] = '3'
1065
- * [3,4] = {Version}
1066
- * [5] = {Flags}
1067
- * [6-9] = {ID3 Size}
1068
- *
1069
- * An ID3v2 tag can be detected with the following pattern:
1070
- * $49 44 33 yy yy xx zz zz zz zz
1071
- * Where yy is less than $FF, xx is the 'flags' byte and zz is less than $80
1072
- */
1073
- if (offset + 10 <= data.length) {
1074
- // look for 'ID3' identifier
1075
- if (data[offset] === 0x49 && data[offset + 1] === 0x44 && data[offset + 2] === 0x33) {
1076
- // check version is within range
1077
- if (data[offset + 3] < 0xff && data[offset + 4] < 0xff) {
1078
- // check size is within range
1079
- if (data[offset + 6] < 0x80 && data[offset + 7] < 0x80 && data[offset + 8] < 0x80 && data[offset + 9] < 0x80) {
1080
- return true;
1081
- }
1082
- }
1083
- }
1084
- }
1085
- return false;
1086
- };
1087
-
1088
- /**
1089
- * Returns true if an ID3 footer can be found at offset in data
1090
- * @param data - The data to search
1091
- * @param offset - The offset at which to start searching
1092
- */
1093
- const isFooter = (data, offset) => {
1094
- /*
1095
- * The footer is a copy of the header, but with a different identifier
1096
- */
1097
- if (offset + 10 <= data.length) {
1098
- // look for '3DI' identifier
1099
- if (data[offset] === 0x33 && data[offset + 1] === 0x44 && data[offset + 2] === 0x49) {
1100
- // check version is within range
1101
- if (data[offset + 3] < 0xff && data[offset + 4] < 0xff) {
1102
- // check size is within range
1103
- if (data[offset + 6] < 0x80 && data[offset + 7] < 0x80 && data[offset + 8] < 0x80 && data[offset + 9] < 0x80) {
1104
- return true;
1105
- }
1106
- }
1107
- }
1108
- }
1109
- return false;
1110
- };
1111
-
1112
- /**
1113
- * Returns any adjacent ID3 tags found in data starting at offset, as one block of data
1114
- * @param data - The data to search in
1115
- * @param offset - The offset at which to start searching
1116
- * @returns the block of data containing any ID3 tags found
1117
- * or *undefined* if no header is found at the starting offset
1118
- */
1119
- const getID3Data = (data, offset) => {
1120
- const front = offset;
1121
- let length = 0;
1122
- while (isHeader$2(data, offset)) {
1123
- // ID3 header is 10 bytes
1124
- length += 10;
1125
- const size = readSize(data, offset + 6);
1126
- length += size;
1127
- if (isFooter(data, offset + 10)) {
1128
- // ID3 footer is 10 bytes
1129
- length += 10;
1130
- }
1131
- offset += length;
1132
- }
1133
- if (length > 0) {
1134
- return data.subarray(front, front + length);
1135
- }
1136
- return undefined;
1137
- };
1138
- const readSize = (data, offset) => {
1139
- let size = 0;
1140
- size = (data[offset] & 0x7f) << 21;
1141
- size |= (data[offset + 1] & 0x7f) << 14;
1142
- size |= (data[offset + 2] & 0x7f) << 7;
1143
- size |= data[offset + 3] & 0x7f;
1144
- return size;
1145
- };
1146
- const canParse$2 = (data, offset) => {
1147
- return isHeader$2(data, offset) && readSize(data, offset + 6) + 10 <= data.length - offset;
1148
- };
1149
-
1150
- /**
1151
- * Searches for the Elementary Stream timestamp found in the ID3 data chunk
1152
- * @param data - Block of data containing one or more ID3 tags
1153
- */
1154
- const getTimeStamp = data => {
1155
- const frames = getID3Frames(data);
1156
- for (let i = 0; i < frames.length; i++) {
1157
- const frame = frames[i];
1158
- if (isTimeStampFrame(frame)) {
1159
- return readTimeStamp(frame);
1160
- }
1161
- }
1162
- return undefined;
1163
- };
1164
-
1165
- /**
1166
- * Returns true if the ID3 frame is an Elementary Stream timestamp frame
1167
- */
1168
- const isTimeStampFrame = frame => {
1169
- return frame && frame.key === 'PRIV' && frame.info === 'com.apple.streaming.transportStreamTimestamp';
1170
- };
1171
- const getFrameData = data => {
1172
- /*
1173
- Frame ID $xx xx xx xx (four characters)
1174
- Size $xx xx xx xx
1175
- Flags $xx xx
1176
- */
1177
- const type = String.fromCharCode(data[0], data[1], data[2], data[3]);
1178
- const size = readSize(data, 4);
1179
-
1180
- // skip frame id, size, and flags
1181
- const offset = 10;
1182
- return {
1183
- type,
1184
- size,
1185
- data: data.subarray(offset, offset + size)
1186
- };
1187
- };
1188
-
1189
- /**
1190
- * Returns an array of ID3 frames found in all the ID3 tags in the id3Data
1191
- * @param id3Data - The ID3 data containing one or more ID3 tags
1192
- */
1193
- const getID3Frames = id3Data => {
1194
- let offset = 0;
1195
- const frames = [];
1196
- while (isHeader$2(id3Data, offset)) {
1197
- const size = readSize(id3Data, offset + 6);
1198
- // skip past ID3 header
1199
- offset += 10;
1200
- const end = offset + size;
1201
- // loop through frames in the ID3 tag
1202
- while (offset + 8 < end) {
1203
- const frameData = getFrameData(id3Data.subarray(offset));
1204
- const frame = decodeFrame(frameData);
1205
- if (frame) {
1206
- frames.push(frame);
1207
- }
1208
-
1209
- // skip frame header and frame data
1210
- offset += frameData.size + 10;
1211
- }
1212
- if (isFooter(id3Data, offset)) {
1213
- offset += 10;
1214
- }
1215
- }
1216
- return frames;
1217
- };
1218
- const decodeFrame = frame => {
1219
- if (frame.type === 'PRIV') {
1220
- return decodePrivFrame(frame);
1221
- } else if (frame.type[0] === 'W') {
1222
- return decodeURLFrame(frame);
1223
- }
1224
- return decodeTextFrame(frame);
1225
- };
1226
- const decodePrivFrame = frame => {
1227
- /*
1228
- Format: <text string>\0<binary data>
1229
- */
1230
- if (frame.size < 2) {
1231
- return undefined;
1232
- }
1233
- const owner = utf8ArrayToStr(frame.data, true);
1234
- const privateData = new Uint8Array(frame.data.subarray(owner.length + 1));
1235
- return {
1236
- key: frame.type,
1237
- info: owner,
1238
- data: privateData.buffer
1239
- };
1240
- };
1241
- const decodeTextFrame = frame => {
1242
- if (frame.size < 2) {
1243
- return undefined;
1244
- }
1245
- if (frame.type === 'TXXX') {
1246
- /*
1247
- Format:
1248
- [0] = {Text Encoding}
1249
- [1-?] = {Description}\0{Value}
1250
- */
1251
- let index = 1;
1252
- const description = utf8ArrayToStr(frame.data.subarray(index), true);
1253
- index += description.length + 1;
1254
- const value = utf8ArrayToStr(frame.data.subarray(index));
1255
- return {
1256
- key: frame.type,
1257
- info: description,
1258
- data: value
1259
- };
1260
- }
1261
- /*
1262
- Format:
1263
- [0] = {Text Encoding}
1264
- [1-?] = {Value}
1265
- */
1266
- const text = utf8ArrayToStr(frame.data.subarray(1));
1267
- return {
1268
- key: frame.type,
1269
- data: text
1270
- };
1271
- };
1272
- const decodeURLFrame = frame => {
1273
- if (frame.type === 'WXXX') {
1274
- /*
1275
- Format:
1276
- [0] = {Text Encoding}
1277
- [1-?] = {Description}\0{URL}
1278
- */
1279
- if (frame.size < 2) {
1280
- return undefined;
1281
- }
1282
- let index = 1;
1283
- const description = utf8ArrayToStr(frame.data.subarray(index), true);
1284
- index += description.length + 1;
1285
- const value = utf8ArrayToStr(frame.data.subarray(index));
1286
- return {
1287
- key: frame.type,
1288
- info: description,
1289
- data: value
1290
- };
1291
- }
1292
- /*
1293
- Format:
1294
- [0-?] = {URL}
1295
- */
1296
- const url = utf8ArrayToStr(frame.data);
1297
- return {
1298
- key: frame.type,
1299
- data: url
1300
- };
1301
- };
1302
- const readTimeStamp = timeStampFrame => {
1303
- if (timeStampFrame.data.byteLength === 8) {
1304
- const data = new Uint8Array(timeStampFrame.data);
1305
- // timestamp is 33 bit expressed as a big-endian eight-octet number,
1306
- // with the upper 31 bits set to zero.
1307
- const pts33Bit = data[3] & 0x1;
1308
- let timestamp = (data[4] << 23) + (data[5] << 15) + (data[6] << 7) + data[7];
1309
- timestamp /= 45;
1310
- if (pts33Bit) {
1311
- timestamp += 47721858.84;
1312
- } // 2^32 / 90
1313
-
1314
- return Math.round(timestamp);
1315
- }
1316
- return undefined;
1317
- };
1318
-
1319
1052
  // http://stackoverflow.com/questions/8936984/uint8array-to-string-in-javascript/22373197
1320
1053
  // http://www.onicos.com/staff/iz/amuse/javascript/expert/utf.txt
1321
1054
  /* utf.js - UTF-8 <=> UTF-16 convertion
@@ -1325,16 +1058,26 @@ const readTimeStamp = timeStampFrame => {
1325
1058
  * LastModified: Dec 25 1999
1326
1059
  * This library is free. You can redistribute it and/or modify it.
1327
1060
  */
1328
- const utf8ArrayToStr = (array, exitOnNull = false) => {
1329
- const decoder = getTextDecoder();
1330
- if (decoder) {
1061
+ /**
1062
+ * Converts a UTF-8 array to a string.
1063
+ *
1064
+ * @param array - The UTF-8 array to convert
1065
+ *
1066
+ * @returns The string
1067
+ *
1068
+ * @group Utils
1069
+ *
1070
+ * @beta
1071
+ */
1072
+ function utf8ArrayToStr(array, exitOnNull = false) {
1073
+ if (typeof TextDecoder !== 'undefined') {
1074
+ const decoder = new TextDecoder('utf-8');
1331
1075
  const decoded = decoder.decode(array);
1332
1076
  if (exitOnNull) {
1333
1077
  // grab up to the first null
1334
1078
  const idx = decoded.indexOf('\0');
1335
1079
  return idx !== -1 ? decoded.substring(0, idx) : decoded;
1336
1080
  }
1337
-
1338
1081
  // remove any null characters
1339
1082
  return decoded.replace(/\0/g, '');
1340
1083
  }
@@ -1379,18 +1122,6 @@ const utf8ArrayToStr = (array, exitOnNull = false) => {
1379
1122
  }
1380
1123
  }
1381
1124
  return out;
1382
- };
1383
- let decoder;
1384
- function getTextDecoder() {
1385
- // On Play Station 4, TextDecoder is defined but partially implemented.
1386
- // Manual decoding option is preferable
1387
- if (navigator.userAgent.includes('PlayStation 4')) {
1388
- return;
1389
- }
1390
- if (!decoder && typeof self.TextDecoder !== 'undefined') {
1391
- decoder = new self.TextDecoder('utf-8');
1392
- }
1393
- return decoder;
1394
1125
  }
1395
1126
 
1396
1127
  /**
@@ -3885,78 +3616,477 @@ function clearCurrentCues(track) {
3885
3616
  track.removeCue(track.cues[i]);
3886
3617
  }
3887
3618
  }
3888
- if (mode === 'disabled') {
3889
- track.mode = mode;
3619
+ if (mode === 'disabled') {
3620
+ track.mode = mode;
3621
+ }
3622
+ }
3623
+ function removeCuesInRange(track, start, end, predicate) {
3624
+ const mode = track.mode;
3625
+ if (mode === 'disabled') {
3626
+ track.mode = 'hidden';
3627
+ }
3628
+ if (track.cues && track.cues.length > 0) {
3629
+ const cues = getCuesInRange(track.cues, start, end);
3630
+ for (let i = 0; i < cues.length; i++) {
3631
+ if (!predicate || predicate(cues[i])) {
3632
+ track.removeCue(cues[i]);
3633
+ }
3634
+ }
3635
+ }
3636
+ if (mode === 'disabled') {
3637
+ track.mode = mode;
3638
+ }
3639
+ }
3640
+
3641
+ // Find first cue starting after given time.
3642
+ // Modified version of binary search O(log(n)).
3643
+ function getFirstCueIndexAfterTime(cues, time) {
3644
+ // If first cue starts after time, start there
3645
+ if (time < cues[0].startTime) {
3646
+ return 0;
3647
+ }
3648
+ // If the last cue ends before time there is no overlap
3649
+ const len = cues.length - 1;
3650
+ if (time > cues[len].endTime) {
3651
+ return -1;
3652
+ }
3653
+ let left = 0;
3654
+ let right = len;
3655
+ while (left <= right) {
3656
+ const mid = Math.floor((right + left) / 2);
3657
+ if (time < cues[mid].startTime) {
3658
+ right = mid - 1;
3659
+ } else if (time > cues[mid].startTime && left < len) {
3660
+ left = mid + 1;
3661
+ } else {
3662
+ // If it's not lower or higher, it must be equal.
3663
+ return mid;
3664
+ }
3665
+ }
3666
+ // At this point, left and right have swapped.
3667
+ // No direct match was found, left or right element must be the closest. Check which one has the smallest diff.
3668
+ return cues[left].startTime - time < time - cues[right].startTime ? left : right;
3669
+ }
3670
+ function getCuesInRange(cues, start, end) {
3671
+ const cuesFound = [];
3672
+ const firstCueInRange = getFirstCueIndexAfterTime(cues, start);
3673
+ if (firstCueInRange > -1) {
3674
+ for (let i = firstCueInRange, len = cues.length; i < len; i++) {
3675
+ const cue = cues[i];
3676
+ if (cue.startTime >= start && cue.endTime <= end) {
3677
+ cuesFound.push(cue);
3678
+ } else if (cue.startTime > end) {
3679
+ return cuesFound;
3680
+ }
3681
+ }
3682
+ }
3683
+ return cuesFound;
3684
+ }
3685
+
3686
+ var MetadataSchema = {
3687
+ audioId3: "org.id3",
3688
+ dateRange: "com.apple.quicktime.HLS",
3689
+ emsg: "https://aomedia.org/emsg/ID3"
3690
+ };
3691
+
3692
+ /**
3693
+ * Decode an ID3 PRIV frame.
3694
+ *
3695
+ * @param frame - the ID3 PRIV frame
3696
+ *
3697
+ * @returns The decoded ID3 PRIV frame
3698
+ *
3699
+ * @internal
3700
+ *
3701
+ * @group ID3
3702
+ */
3703
+ function decodeId3PrivFrame(frame) {
3704
+ /*
3705
+ Format: <text string>\0<binary data>
3706
+ */
3707
+ if (frame.size < 2) {
3708
+ return undefined;
3709
+ }
3710
+ const owner = utf8ArrayToStr(frame.data, true);
3711
+ const privateData = new Uint8Array(frame.data.subarray(owner.length + 1));
3712
+ return {
3713
+ key: frame.type,
3714
+ info: owner,
3715
+ data: privateData.buffer
3716
+ };
3717
+ }
3718
+
3719
+ /**
3720
+ * Decodes an ID3 text frame
3721
+ *
3722
+ * @param frame - the ID3 text frame
3723
+ *
3724
+ * @returns The decoded ID3 text frame
3725
+ *
3726
+ * @internal
3727
+ *
3728
+ * @group ID3
3729
+ */
3730
+ function decodeId3TextFrame(frame) {
3731
+ if (frame.size < 2) {
3732
+ return undefined;
3733
+ }
3734
+ if (frame.type === 'TXXX') {
3735
+ /*
3736
+ Format:
3737
+ [0] = {Text Encoding}
3738
+ [1-?] = {Description}\0{Value}
3739
+ */
3740
+ let index = 1;
3741
+ const description = utf8ArrayToStr(frame.data.subarray(index), true);
3742
+ index += description.length + 1;
3743
+ const value = utf8ArrayToStr(frame.data.subarray(index));
3744
+ return {
3745
+ key: frame.type,
3746
+ info: description,
3747
+ data: value
3748
+ };
3749
+ }
3750
+ /*
3751
+ Format:
3752
+ [0] = {Text Encoding}
3753
+ [1-?] = {Value}
3754
+ */
3755
+ const text = utf8ArrayToStr(frame.data.subarray(1));
3756
+ return {
3757
+ key: frame.type,
3758
+ info: '',
3759
+ data: text
3760
+ };
3761
+ }
3762
+
3763
+ /**
3764
+ * Decode a URL frame
3765
+ *
3766
+ * @param frame - the ID3 URL frame
3767
+ *
3768
+ * @returns The decoded ID3 URL frame
3769
+ *
3770
+ * @internal
3771
+ *
3772
+ * @group ID3
3773
+ */
3774
+ function decodeId3UrlFrame(frame) {
3775
+ if (frame.type === 'WXXX') {
3776
+ /*
3777
+ Format:
3778
+ [0] = {Text Encoding}
3779
+ [1-?] = {Description}\0{URL}
3780
+ */
3781
+ if (frame.size < 2) {
3782
+ return undefined;
3783
+ }
3784
+ let index = 1;
3785
+ const description = utf8ArrayToStr(frame.data.subarray(index), true);
3786
+ index += description.length + 1;
3787
+ const value = utf8ArrayToStr(frame.data.subarray(index));
3788
+ return {
3789
+ key: frame.type,
3790
+ info: description,
3791
+ data: value
3792
+ };
3793
+ }
3794
+ /*
3795
+ Format:
3796
+ [0-?] = {URL}
3797
+ */
3798
+ const url = utf8ArrayToStr(frame.data);
3799
+ return {
3800
+ key: frame.type,
3801
+ info: '',
3802
+ data: url
3803
+ };
3804
+ }
3805
+
3806
+ function toUint8(data, offset = 0, length = Infinity) {
3807
+ return view(data, offset, length, Uint8Array);
3808
+ }
3809
+ function view(data, offset, length, Type) {
3810
+ const buffer = unsafeGetArrayBuffer(data);
3811
+ let bytesPerElement = 1;
3812
+ if ('BYTES_PER_ELEMENT' in Type) {
3813
+ bytesPerElement = Type.BYTES_PER_ELEMENT;
3814
+ }
3815
+ // Absolute end of the |data| view within |buffer|.
3816
+ const dataOffset = isArrayBufferView(data) ? data.byteOffset : 0;
3817
+ const dataEnd = (dataOffset + data.byteLength) / bytesPerElement;
3818
+ // Absolute start of the result within |buffer|.
3819
+ const rawStart = (dataOffset + offset) / bytesPerElement;
3820
+ const start = Math.floor(Math.max(0, Math.min(rawStart, dataEnd)));
3821
+ // Absolute end of the result within |buffer|.
3822
+ const end = Math.floor(Math.min(start + Math.max(length, 0), dataEnd));
3823
+ return new Type(buffer, start, end - start);
3824
+ }
3825
+ function unsafeGetArrayBuffer(view) {
3826
+ if (view instanceof ArrayBuffer) {
3827
+ return view;
3828
+ } else {
3829
+ return view.buffer;
3830
+ }
3831
+ }
3832
+ function isArrayBufferView(obj) {
3833
+ return obj && obj.buffer instanceof ArrayBuffer && obj.byteLength !== undefined && obj.byteOffset !== undefined;
3834
+ }
3835
+
3836
+ function toArrayBuffer(view) {
3837
+ if (view instanceof ArrayBuffer) {
3838
+ return view;
3839
+ } else {
3840
+ if (view.byteOffset == 0 && view.byteLength == view.buffer.byteLength) {
3841
+ // This is a TypedArray over the whole buffer.
3842
+ return view.buffer;
3843
+ }
3844
+ // This is a 'view' on the buffer. Create a new buffer that only contains
3845
+ // the data. Note that since this isn't an ArrayBuffer, the 'new' call
3846
+ // will allocate a new buffer to hold the copy.
3847
+ return new Uint8Array(view).buffer;
3848
+ }
3849
+ }
3850
+
3851
+ function decodeId3ImageFrame(frame) {
3852
+ const metadataFrame = {
3853
+ key: frame.type,
3854
+ description: '',
3855
+ data: '',
3856
+ mimeType: null,
3857
+ pictureType: null
3858
+ };
3859
+ const utf8Encoding = 0x03;
3860
+ if (frame.size < 2) {
3861
+ return undefined;
3862
+ }
3863
+ if (frame.data[0] !== utf8Encoding) {
3864
+ console.log('Ignore frame with unrecognized character ' + 'encoding');
3865
+ return undefined;
3866
+ }
3867
+ const mimeTypeEndIndex = frame.data.subarray(1).indexOf(0);
3868
+ if (mimeTypeEndIndex === -1) {
3869
+ return undefined;
3870
+ }
3871
+ const mimeType = utf8ArrayToStr(toUint8(frame.data, 1, mimeTypeEndIndex));
3872
+ const pictureType = frame.data[2 + mimeTypeEndIndex];
3873
+ const descriptionEndIndex = frame.data.subarray(3 + mimeTypeEndIndex).indexOf(0);
3874
+ if (descriptionEndIndex === -1) {
3875
+ return undefined;
3876
+ }
3877
+ const description = utf8ArrayToStr(toUint8(frame.data, 3 + mimeTypeEndIndex, descriptionEndIndex));
3878
+ let data;
3879
+ if (mimeType === '-->') {
3880
+ data = utf8ArrayToStr(toUint8(frame.data, 4 + mimeTypeEndIndex + descriptionEndIndex));
3881
+ } else {
3882
+ data = toArrayBuffer(frame.data.subarray(4 + mimeTypeEndIndex + descriptionEndIndex));
3890
3883
  }
3884
+ metadataFrame.mimeType = mimeType;
3885
+ metadataFrame.pictureType = pictureType;
3886
+ metadataFrame.description = description;
3887
+ metadataFrame.data = data;
3888
+ return metadataFrame;
3891
3889
  }
3892
- function removeCuesInRange(track, start, end, predicate) {
3893
- const mode = track.mode;
3894
- if (mode === 'disabled') {
3895
- track.mode = 'hidden';
3890
+
3891
+ /**
3892
+ * Decode an ID3 frame.
3893
+ *
3894
+ * @param frame - the ID3 frame
3895
+ *
3896
+ * @returns The decoded ID3 frame
3897
+ *
3898
+ * @internal
3899
+ *
3900
+ * @group ID3
3901
+ */
3902
+ function decodeId3Frame(frame) {
3903
+ if (frame.type === 'PRIV') {
3904
+ return decodeId3PrivFrame(frame);
3905
+ } else if (frame.type[0] === 'W') {
3906
+ return decodeId3UrlFrame(frame);
3907
+ } else if (frame.type === 'APIC') {
3908
+ return decodeId3ImageFrame(frame);
3896
3909
  }
3897
- if (track.cues && track.cues.length > 0) {
3898
- const cues = getCuesInRange(track.cues, start, end);
3899
- for (let i = 0; i < cues.length; i++) {
3900
- if (!predicate || predicate(cues[i])) {
3901
- track.removeCue(cues[i]);
3910
+ return decodeId3TextFrame(frame);
3911
+ }
3912
+
3913
+ /**
3914
+ * Read ID3 size
3915
+ *
3916
+ * @param data - The data to read from
3917
+ * @param offset - The offset at which to start reading
3918
+ *
3919
+ * @returns The size
3920
+ *
3921
+ * @internal
3922
+ *
3923
+ * @group ID3
3924
+ */
3925
+ function readId3Size(data, offset) {
3926
+ let size = 0;
3927
+ size = (data[offset] & 0x7f) << 21;
3928
+ size |= (data[offset + 1] & 0x7f) << 14;
3929
+ size |= (data[offset + 2] & 0x7f) << 7;
3930
+ size |= data[offset + 3] & 0x7f;
3931
+ return size;
3932
+ }
3933
+
3934
+ /**
3935
+ * Returns the data of an ID3 frame.
3936
+ *
3937
+ * @param data - The data to read from
3938
+ *
3939
+ * @returns The data of the ID3 frame
3940
+ *
3941
+ * @internal
3942
+ *
3943
+ * @group ID3
3944
+ */
3945
+ function getId3FrameData(data) {
3946
+ /*
3947
+ Frame ID $xx xx xx xx (four characters)
3948
+ Size $xx xx xx xx
3949
+ Flags $xx xx
3950
+ */
3951
+ const type = String.fromCharCode(data[0], data[1], data[2], data[3]);
3952
+ const size = readId3Size(data, 4);
3953
+ // skip frame id, size, and flags
3954
+ const offset = 10;
3955
+ return {
3956
+ type,
3957
+ size,
3958
+ data: data.subarray(offset, offset + size)
3959
+ };
3960
+ }
3961
+
3962
+ /**
3963
+ * Returns true if an ID3 footer can be found at offset in data
3964
+ *
3965
+ * @param data - The data to search in
3966
+ * @param offset - The offset at which to start searching
3967
+ *
3968
+ * @returns `true` if an ID3 footer is found
3969
+ *
3970
+ * @internal
3971
+ *
3972
+ * @group ID3
3973
+ */
3974
+ function isId3Footer(data, offset) {
3975
+ /*
3976
+ * The footer is a copy of the header, but with a different identifier
3977
+ */
3978
+ if (offset + 10 <= data.length) {
3979
+ // look for '3DI' identifier
3980
+ if (data[offset] === 0x33 && data[offset + 1] === 0x44 && data[offset + 2] === 0x49) {
3981
+ // check version is within range
3982
+ if (data[offset + 3] < 0xff && data[offset + 4] < 0xff) {
3983
+ // check size is within range
3984
+ if (data[offset + 6] < 0x80 && data[offset + 7] < 0x80 && data[offset + 8] < 0x80 && data[offset + 9] < 0x80) {
3985
+ return true;
3986
+ }
3902
3987
  }
3903
3988
  }
3904
3989
  }
3905
- if (mode === 'disabled') {
3906
- track.mode = mode;
3907
- }
3990
+ return false;
3908
3991
  }
3909
3992
 
3910
- // Find first cue starting after given time.
3911
- // Modified version of binary search O(log(n)).
3912
- function getFirstCueIndexAfterTime(cues, time) {
3913
- // If first cue starts after time, start there
3914
- if (time < cues[0].startTime) {
3915
- return 0;
3916
- }
3917
- // If the last cue ends before time there is no overlap
3918
- const len = cues.length - 1;
3919
- if (time > cues[len].endTime) {
3920
- return -1;
3921
- }
3922
- let left = 0;
3923
- let right = len;
3924
- while (left <= right) {
3925
- const mid = Math.floor((right + left) / 2);
3926
- if (time < cues[mid].startTime) {
3927
- right = mid - 1;
3928
- } else if (time > cues[mid].startTime && left < len) {
3929
- left = mid + 1;
3930
- } else {
3931
- // If it's not lower or higher, it must be equal.
3932
- return mid;
3993
+ /**
3994
+ * Returns true if an ID3 header can be found at offset in data
3995
+ *
3996
+ * @param data - The data to search in
3997
+ * @param offset - The offset at which to start searching
3998
+ *
3999
+ * @returns `true` if an ID3 header is found
4000
+ *
4001
+ * @internal
4002
+ *
4003
+ * @group ID3
4004
+ */
4005
+ function isId3Header(data, offset) {
4006
+ /*
4007
+ * http://id3.org/id3v2.3.0
4008
+ * [0] = 'I'
4009
+ * [1] = 'D'
4010
+ * [2] = '3'
4011
+ * [3,4] = {Version}
4012
+ * [5] = {Flags}
4013
+ * [6-9] = {ID3 Size}
4014
+ *
4015
+ * An ID3v2 tag can be detected with the following pattern:
4016
+ * $49 44 33 yy yy xx zz zz zz zz
4017
+ * Where yy is less than $FF, xx is the 'flags' byte and zz is less than $80
4018
+ */
4019
+ if (offset + 10 <= data.length) {
4020
+ // look for 'ID3' identifier
4021
+ if (data[offset] === 0x49 && data[offset + 1] === 0x44 && data[offset + 2] === 0x33) {
4022
+ // check version is within range
4023
+ if (data[offset + 3] < 0xff && data[offset + 4] < 0xff) {
4024
+ // check size is within range
4025
+ if (data[offset + 6] < 0x80 && data[offset + 7] < 0x80 && data[offset + 8] < 0x80 && data[offset + 9] < 0x80) {
4026
+ return true;
4027
+ }
4028
+ }
3933
4029
  }
3934
4030
  }
3935
- // At this point, left and right have swapped.
3936
- // No direct match was found, left or right element must be the closest. Check which one has the smallest diff.
3937
- return cues[left].startTime - time < time - cues[right].startTime ? left : right;
4031
+ return false;
3938
4032
  }
3939
- function getCuesInRange(cues, start, end) {
3940
- const cuesFound = [];
3941
- const firstCueInRange = getFirstCueIndexAfterTime(cues, start);
3942
- if (firstCueInRange > -1) {
3943
- for (let i = firstCueInRange, len = cues.length; i < len; i++) {
3944
- const cue = cues[i];
3945
- if (cue.startTime >= start && cue.endTime <= end) {
3946
- cuesFound.push(cue);
3947
- } else if (cue.startTime > end) {
3948
- return cuesFound;
4033
+
4034
+ const HEADER_FOOTER_SIZE = 10;
4035
+ const FRAME_SIZE = 10;
4036
+ /**
4037
+ * Returns an array of ID3 frames found in all the ID3 tags in the id3Data
4038
+ *
4039
+ * @param id3Data - The ID3 data containing one or more ID3 tags
4040
+ *
4041
+ * @returns Array of ID3 frame objects
4042
+ *
4043
+ * @group ID3
4044
+ *
4045
+ * @beta
4046
+ */
4047
+ function getId3Frames(id3Data) {
4048
+ let offset = 0;
4049
+ const frames = [];
4050
+ while (isId3Header(id3Data, offset)) {
4051
+ const size = readId3Size(id3Data, offset + 6);
4052
+ if (id3Data[offset + 5] >> 6 & 1) {
4053
+ // skip extended header
4054
+ offset += HEADER_FOOTER_SIZE;
4055
+ }
4056
+ // skip past ID3 header
4057
+ offset += HEADER_FOOTER_SIZE;
4058
+ const end = offset + size;
4059
+ // loop through frames in the ID3 tag
4060
+ while (offset + FRAME_SIZE < end) {
4061
+ const frameData = getId3FrameData(id3Data.subarray(offset));
4062
+ const frame = decodeId3Frame(frameData);
4063
+ if (frame) {
4064
+ frames.push(frame);
3949
4065
  }
4066
+ // skip frame header and frame data
4067
+ offset += frameData.size + HEADER_FOOTER_SIZE;
4068
+ }
4069
+ if (isId3Footer(id3Data, offset)) {
4070
+ offset += HEADER_FOOTER_SIZE;
3950
4071
  }
3951
4072
  }
3952
- return cuesFound;
4073
+ return frames;
3953
4074
  }
3954
4075
 
3955
- var MetadataSchema = {
3956
- audioId3: "org.id3",
3957
- dateRange: "com.apple.quicktime.HLS",
3958
- emsg: "https://aomedia.org/emsg/ID3"
3959
- };
4076
+ /**
4077
+ * Returns true if the ID3 frame is an Elementary Stream timestamp frame
4078
+ *
4079
+ * @param frame - the ID3 frame
4080
+ *
4081
+ * @returns `true` if the ID3 frame is an Elementary Stream timestamp frame
4082
+ *
4083
+ * @internal
4084
+ *
4085
+ * @group ID3
4086
+ */
4087
+ function isId3TimestampFrame(frame) {
4088
+ return frame && frame.key === 'PRIV' && frame.info === 'com.apple.streaming.transportStreamTimestamp';
4089
+ }
3960
4090
 
3961
4091
  const MIN_CUE_DURATION = 0.25;
3962
4092
  function getCueClass() {
@@ -4102,7 +4232,7 @@ class ID3TrackController {
4102
4232
  if (type === MetadataSchema.emsg && !enableEmsgMetadataCues || !enableID3MetadataCues) {
4103
4233
  continue;
4104
4234
  }
4105
- const frames = getID3Frames(samples[i].data);
4235
+ const frames = getId3Frames(samples[i].data);
4106
4236
  if (frames) {
4107
4237
  const startTime = samples[i].pts;
4108
4238
  let endTime = startTime + samples[i].duration;
@@ -4116,7 +4246,7 @@ class ID3TrackController {
4116
4246
  for (let j = 0; j < frames.length; j++) {
4117
4247
  const frame = frames[j];
4118
4248
  // Safari doesn't put the timestamp frame in the TextTrack
4119
- if (!isTimeStampFrame(frame)) {
4249
+ if (!isId3TimestampFrame(frame)) {
4120
4250
  // add a bounds to any unbounded cues
4121
4251
  this.updateId3CueEnds(startTime, type);
4122
4252
  const cue = createCueWithDataFields(Cue, startTime, endTime, frame, type);
@@ -13465,6 +13595,104 @@ function dummyTrack(type = '', inputTimeScale = 90000) {
13465
13595
  };
13466
13596
  }
13467
13597
 
13598
+ /**
13599
+ * Returns any adjacent ID3 tags found in data starting at offset, as one block of data
13600
+ *
13601
+ * @param data - The data to search in
13602
+ * @param offset - The offset at which to start searching
13603
+ *
13604
+ * @returns The block of data containing any ID3 tags found
13605
+ * or `undefined` if no header is found at the starting offset
13606
+ *
13607
+ * @internal
13608
+ *
13609
+ * @group ID3
13610
+ */
13611
+ function getId3Data(data, offset) {
13612
+ const front = offset;
13613
+ let length = 0;
13614
+ while (isId3Header(data, offset)) {
13615
+ // ID3 header is 10 bytes
13616
+ length += 10;
13617
+ const size = readId3Size(data, offset + 6);
13618
+ length += size;
13619
+ if (isId3Footer(data, offset + 10)) {
13620
+ // ID3 footer is 10 bytes
13621
+ length += 10;
13622
+ }
13623
+ offset += length;
13624
+ }
13625
+ if (length > 0) {
13626
+ return data.subarray(front, front + length);
13627
+ }
13628
+ return undefined;
13629
+ }
13630
+
13631
+ /**
13632
+ * Read a 33 bit timestamp from an ID3 frame.
13633
+ *
13634
+ * @param timeStampFrame - the ID3 frame
13635
+ *
13636
+ * @returns The timestamp
13637
+ *
13638
+ * @internal
13639
+ *
13640
+ * @group ID3
13641
+ */
13642
+ function readId3Timestamp(timeStampFrame) {
13643
+ if (timeStampFrame.data.byteLength === 8) {
13644
+ const data = new Uint8Array(timeStampFrame.data);
13645
+ // timestamp is 33 bit expressed as a big-endian eight-octet number,
13646
+ // with the upper 31 bits set to zero.
13647
+ const pts33Bit = data[3] & 0x1;
13648
+ let timestamp = (data[4] << 23) + (data[5] << 15) + (data[6] << 7) + data[7];
13649
+ timestamp /= 45;
13650
+ if (pts33Bit) {
13651
+ timestamp += 47721858.84;
13652
+ } // 2^32 / 90
13653
+ return Math.round(timestamp);
13654
+ }
13655
+ return undefined;
13656
+ }
13657
+
13658
+ /**
13659
+ * Searches for the Elementary Stream timestamp found in the ID3 data chunk
13660
+ *
13661
+ * @param data - Block of data containing one or more ID3 tags
13662
+ *
13663
+ * @returns The timestamp
13664
+ *
13665
+ * @group ID3
13666
+ *
13667
+ * @beta
13668
+ */
13669
+ function getId3Timestamp(data) {
13670
+ const frames = getId3Frames(data);
13671
+ for (let i = 0; i < frames.length; i++) {
13672
+ const frame = frames[i];
13673
+ if (isId3TimestampFrame(frame)) {
13674
+ return readId3Timestamp(frame);
13675
+ }
13676
+ }
13677
+ return undefined;
13678
+ }
13679
+
13680
+ /**
13681
+ * Checks if the given data contains an ID3 tag.
13682
+ *
13683
+ * @param data - The data to check
13684
+ * @param offset - The offset at which to start checking
13685
+ *
13686
+ * @returns `true` if an ID3 tag is found
13687
+ *
13688
+ * @group ID3
13689
+ *
13690
+ * @beta
13691
+ */
13692
+ function canParseId3(data, offset) {
13693
+ return isId3Header(data, offset) && readId3Size(data, offset + 6) + 10 <= data.length - offset;
13694
+ }
13695
+
13468
13696
  class BaseAudioDemuxer {
13469
13697
  constructor() {
13470
13698
  this._audioTrack = void 0;
@@ -13506,12 +13734,12 @@ class BaseAudioDemuxer {
13506
13734
  data = appendUint8Array(this.cachedData, data);
13507
13735
  this.cachedData = null;
13508
13736
  }
13509
- let id3Data = getID3Data(data, 0);
13737
+ let id3Data = getId3Data(data, 0);
13510
13738
  let offset = id3Data ? id3Data.length : 0;
13511
13739
  let lastDataIndex;
13512
13740
  const track = this._audioTrack;
13513
13741
  const id3Track = this._id3Track;
13514
- const timestamp = id3Data ? getTimeStamp(id3Data) : undefined;
13742
+ const timestamp = id3Data ? getId3Timestamp(id3Data) : undefined;
13515
13743
  const length = data.length;
13516
13744
  if (this.basePTS === null || this.frameIndex === 0 && isFiniteNumber(timestamp)) {
13517
13745
  this.basePTS = initPTSFn(timestamp, timeOffset, this.initPTS);
@@ -13542,9 +13770,9 @@ class BaseAudioDemuxer {
13542
13770
  } else {
13543
13771
  offset = length;
13544
13772
  }
13545
- } else if (canParse$2(data, offset)) {
13546
- // after a ID3.canParse, a call to ID3.getID3Data *should* always returns some data
13547
- id3Data = getID3Data(data, offset);
13773
+ } else if (canParseId3(data, offset)) {
13774
+ // after a canParse, a call to getId3Data *should* always returns some data
13775
+ id3Data = getId3Data(data, offset);
13548
13776
  id3Track.samples.push({
13549
13777
  pts: this.lastPTS,
13550
13778
  dts: this.lastPTS,
@@ -14038,7 +14266,7 @@ class AACDemuxer extends BaseAudioDemuxer {
14038
14266
  // Look for ADTS header | 1111 1111 | 1111 X00X | where X can be either 0 or 1
14039
14267
  // Layer bits (position 14 and 15) in header should be always 0 for ADTS
14040
14268
  // More info https://wiki.multimedia.cx/index.php?title=ADTS
14041
- const id3Data = getID3Data(data, 0);
14269
+ const id3Data = getId3Data(data, 0);
14042
14270
  let offset = (id3Data == null ? void 0 : id3Data.length) || 0;
14043
14271
  if (probe(data, offset)) {
14044
14272
  return false;
@@ -15765,11 +15993,11 @@ class MP3Demuxer extends BaseAudioDemuxer {
15765
15993
  // Look for MPEG header | 1111 1111 | 111X XYZX | where X can be either 0 or 1 and Y or Z should be 1
15766
15994
  // Layer bits (position 14 and 15) in header should be always different from 0 (Layer I or Layer II or Layer III)
15767
15995
  // More info http://www.mp3-tech.org/programmer/frame_header.html
15768
- const id3Data = getID3Data(data, 0);
15996
+ const id3Data = getId3Data(data, 0);
15769
15997
  let offset = (id3Data == null ? void 0 : id3Data.length) || 0;
15770
15998
 
15771
15999
  // Check for ac-3|ec-3 sync bytes and return false if present
15772
- if (id3Data && data[offset] === 0x0b && data[offset + 1] === 0x77 && getTimeStamp(id3Data) !== undefined &&
16000
+ if (id3Data && data[offset] === 0x0b && data[offset + 1] === 0x77 && getId3Timestamp(id3Data) !== undefined &&
15773
16001
  // check the bsid to confirm ac-3 or ec-3 (not mp3)
15774
16002
  getAudioBSID(data, offset) <= 16) {
15775
16003
  return false;
@@ -20127,7 +20355,7 @@ class Hls {
20127
20355
  * Get the video-dev/hls.js package version.
20128
20356
  */
20129
20357
  static get version() {
20130
- return "1.5.8-0.canary.10153";
20358
+ return "1.5.8-0.canary.10154";
20131
20359
  }
20132
20360
 
20133
20361
  /**