shaka-player 4.7.13 → 4.8.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 (114) hide show
  1. package/AUTHORS +1 -0
  2. package/CONTRIBUTORS +1 -0
  3. package/LICENSE +54 -0
  4. package/README.md +22 -1
  5. package/dist/controls.css +1 -1
  6. package/dist/controls.css.map +1 -1
  7. package/dist/deps.js +34 -23
  8. package/dist/locales.js +44 -34
  9. package/dist/shaka-player.compiled.d.ts +194 -44
  10. package/dist/shaka-player.compiled.debug.d.ts +194 -44
  11. package/dist/shaka-player.compiled.debug.externs.js +474 -337
  12. package/dist/shaka-player.compiled.debug.js +949 -832
  13. package/dist/shaka-player.compiled.debug.map +4 -4
  14. package/dist/shaka-player.compiled.externs.js +474 -337
  15. package/dist/shaka-player.compiled.js +1237 -1171
  16. package/dist/shaka-player.compiled.map +4 -4
  17. package/dist/shaka-player.ui.d.ts +257 -47
  18. package/dist/shaka-player.ui.debug.d.ts +257 -47
  19. package/dist/shaka-player.ui.debug.externs.js +591 -376
  20. package/dist/shaka-player.ui.debug.js +1165 -965
  21. package/dist/shaka-player.ui.debug.map +4 -4
  22. package/dist/shaka-player.ui.externs.js +591 -376
  23. package/dist/shaka-player.ui.js +1521 -1404
  24. package/dist/shaka-player.ui.map +4 -4
  25. package/externs/device_sensor_event.js +18 -0
  26. package/externs/hisense.js +16 -0
  27. package/externs/playstation.js +31 -0
  28. package/externs/shaka/abr_manager.js +3 -1
  29. package/externs/shaka/ads.js +5 -0
  30. package/externs/shaka/codecs.js +13 -0
  31. package/externs/shaka/manifest.js +58 -6
  32. package/externs/shaka/net.js +13 -1
  33. package/externs/shaka/player.js +184 -13
  34. package/externs/shaka/text.js +19 -0
  35. package/externs/tizen.js +41 -0
  36. package/externs/webos.js +19 -0
  37. package/externs/xbox.js +43 -0
  38. package/lib/abr/simple_abr_manager.js +9 -1
  39. package/lib/ads/ad_manager.js +19 -2
  40. package/lib/ads/client_side_ad_manager.js +5 -3
  41. package/lib/ads/media_tailor_ad_manager.js +9 -0
  42. package/lib/cast/cast_receiver.js +0 -14
  43. package/lib/cast/cast_utils.js +1 -0
  44. package/lib/cea/mp4_cea_parser.js +6 -1
  45. package/lib/dash/content_protection.js +97 -53
  46. package/lib/dash/dash_parser.js +230 -176
  47. package/lib/dash/mpd_utils.js +72 -65
  48. package/lib/dash/segment_base.js +27 -21
  49. package/lib/dash/segment_list.js +18 -16
  50. package/lib/dash/segment_template.js +26 -21
  51. package/lib/hls/hls_parser.js +358 -164
  52. package/lib/media/buffering_observer.js +25 -1
  53. package/lib/media/drm_engine.js +91 -71
  54. package/lib/media/manifest_filterer.js +220 -0
  55. package/lib/media/media_source_engine.js +34 -5
  56. package/lib/media/preload_manager.js +827 -0
  57. package/lib/media/presentation_timeline.js +11 -0
  58. package/lib/media/segment_index.js +40 -30
  59. package/lib/media/segment_prefetch.js +140 -43
  60. package/lib/media/segment_reference.js +31 -8
  61. package/lib/media/segment_utils.js +208 -25
  62. package/lib/media/streaming_engine.js +319 -63
  63. package/lib/mss/content_protection.js +31 -25
  64. package/lib/mss/mss_parser.js +74 -55
  65. package/lib/net/networking_engine.js +49 -4
  66. package/lib/offline/storage.js +3 -3
  67. package/lib/player.js +882 -673
  68. package/lib/polyfill/media_capabilities.js +16 -1
  69. package/lib/text/simple_text_displayer.js +9 -0
  70. package/lib/text/stub_text_displayer.js +7 -0
  71. package/lib/text/text_engine.js +16 -3
  72. package/lib/text/ttml_text_parser.js +116 -114
  73. package/lib/text/ui_text_displayer.js +28 -11
  74. package/lib/text/vtt_text_parser.js +54 -115
  75. package/lib/transmuxer/opus.js +91 -0
  76. package/lib/transmuxer/ts_transmuxer.js +115 -0
  77. package/lib/util/cmcd_manager.js +152 -3
  78. package/lib/util/dom_utils.js +11 -0
  79. package/lib/util/error.js +5 -0
  80. package/lib/util/fake_event.js +2 -0
  81. package/lib/util/manifest_parser_utils.js +97 -3
  82. package/lib/util/mp4_box_parsers.js +346 -17
  83. package/lib/util/mp4_generator.js +23 -0
  84. package/lib/util/periods.js +19 -0
  85. package/lib/util/platform.js +161 -9
  86. package/lib/util/player_configuration.js +61 -3
  87. package/lib/util/stats.js +16 -0
  88. package/lib/util/stream_utils.js +16 -6
  89. package/lib/util/string_utils.js +36 -0
  90. package/lib/util/tXml.js +814 -0
  91. package/lib/util/ts_parser.js +50 -0
  92. package/lib/util/xml_utils.js +10 -338
  93. package/package.json +2 -2
  94. package/shaka-player.uncompiled.js +8 -0
  95. package/ui/controls.js +187 -5
  96. package/ui/enums.js +2 -0
  97. package/ui/externs/ui.js +30 -3
  98. package/ui/gl_matrix/matrix_4x4.js +568 -0
  99. package/ui/gl_matrix/matrix_quaternion.js +45 -0
  100. package/ui/less/containers.less +11 -0
  101. package/ui/locales/en-GB.json +2 -0
  102. package/ui/locales/en.json +2 -0
  103. package/ui/locales/es-419.json +2 -0
  104. package/ui/locales/es.json +2 -0
  105. package/ui/locales/source.json +8 -0
  106. package/ui/recenter_vr.js +126 -0
  107. package/ui/resolution_selection.js +25 -5
  108. package/ui/statistics_button.js +12 -0
  109. package/ui/text_selection.js +1 -1
  110. package/ui/toggle_stereoscopic.js +127 -0
  111. package/ui/ui.js +74 -38
  112. package/ui/vr_manager.js +557 -0
  113. package/ui/vr_utils.js +232 -0
  114. package/ui/vr_webgl.js +697 -0
@@ -37,9 +37,9 @@ goog.require('shaka.util.OperationManager');
37
37
  goog.require('shaka.util.Pssh');
38
38
  goog.require('shaka.media.SegmentUtils');
39
39
  goog.require('shaka.util.Timer');
40
+ goog.require('shaka.util.TXml');
40
41
  goog.require('shaka.util.Platform');
41
42
  goog.require('shaka.util.Uint8ArrayUtils');
42
- goog.require('shaka.util.XmlUtils');
43
43
  goog.requireType('shaka.hls.Segment');
44
44
 
45
45
 
@@ -217,6 +217,18 @@ shaka.hls.HlsParser = class {
217
217
  * {!Map.<string, !shaka.media.InitSegmentReference>} */
218
218
  this.mapTagToInitSegmentRefMap_ = new Map();
219
219
 
220
+ /** @private {Map.<string, !shaka.extern.aesKey>} */
221
+ this.aesKeyInfoMap_ = new Map();
222
+
223
+ /** @private {Map.<string, !Promise.<shaka.extern.Response>>} */
224
+ this.aesKeyMap_ = new Map();
225
+
226
+ /** @private {Map.<string, !Promise.<shaka.extern.Response>>} */
227
+ this.identityKeyMap_ = new Map();
228
+
229
+ /** @private {Map.<!shaka.media.InitSegmentReference, ?string>} */
230
+ this.identityKidMap_ = new Map();
231
+
220
232
  /** @private {boolean} */
221
233
  this.lowLatencyMode_ = false;
222
234
 
@@ -297,6 +309,10 @@ shaka.hls.HlsParser = class {
297
309
  this.groupIdToCodecsMap_.clear();
298
310
  this.globalVariables_.clear();
299
311
  this.mapTagToInitSegmentRefMap_.clear();
312
+ this.aesKeyInfoMap_.clear();
313
+ this.aesKeyMap_.clear();
314
+ this.identityKeyMap_.clear();
315
+ this.identityKidMap_.clear();
300
316
 
301
317
  if (this.contentSteeringManager_) {
302
318
  this.contentSteeringManager_.destroy();
@@ -434,7 +450,8 @@ shaka.hls.HlsParser = class {
434
450
 
435
451
  const mediaSequenceToStartTime =
436
452
  this.getMediaSequenceToStartTimeFor_(streamInfo);
437
- const {keyIds, drmInfos} = this.parseDrmInfo_(playlist, stream.mimeType);
453
+ const {keyIds, drmInfos} = await this.parseDrmInfo_(
454
+ playlist, stream.mimeType, streamInfo.getUris, mediaVariables);
438
455
 
439
456
  const keysAreEqual =
440
457
  (a, b) => a.size === b.size && [...a].every((value) => b.has(value));
@@ -447,7 +464,7 @@ shaka.hls.HlsParser = class {
447
464
 
448
465
  const {segments, bandwidth} = this.createSegments_(
449
466
  playlist, stream, mediaSequenceToStartTime, mediaVariables,
450
- streamInfo.getUris);
467
+ streamInfo.getUris, streamInfo.type);
451
468
  if (bandwidth) {
452
469
  stream.bandwidth = bandwidth;
453
470
  }
@@ -581,6 +598,9 @@ shaka.hls.HlsParser = class {
581
598
  this.minSequenceNumber_);
582
599
 
583
600
  for (const streamInfo of streamInfos) {
601
+ if (!this.ignoreManifestProgramDateTimeFor_(streamInfo.type)) {
602
+ continue;
603
+ }
584
604
  const segmentIndex = streamInfo.stream.segmentIndex;
585
605
  if (segmentIndex) {
586
606
  // Drop any earlier references.
@@ -637,6 +657,9 @@ shaka.hls.HlsParser = class {
637
657
  shaka.log.debug('Syncing HLS streams against base time:', lowestSyncTime);
638
658
 
639
659
  for (const streamInfo of this.uriToStreamInfosMap_.values()) {
660
+ if (this.ignoreManifestProgramDateTimeFor_(streamInfo.type)) {
661
+ continue;
662
+ }
640
663
  const segmentIndex = streamInfo.stream.segmentIndex;
641
664
  if (segmentIndex != null) {
642
665
  // A segment's startTime should be based on its syncTime vs the lowest
@@ -718,12 +741,12 @@ shaka.hls.HlsParser = class {
718
741
  /** @type {!Array.<!shaka.extern.Stream>} */
719
742
  let imageStreams = [];
720
743
 
744
+ const getUris = () => {
745
+ return [uri];
746
+ };
747
+
721
748
  // Parsing a media playlist results in a single-variant stream.
722
749
  if (playlist.type == shaka.hls.PlaylistType.MEDIA) {
723
- const getUris = () => {
724
- return [uri];
725
- };
726
-
727
750
  // Get necessary info for this stream. These are things we would normally
728
751
  // find from the master playlist (e.g. from values on EXT-X-MEDIA tags).
729
752
  const basicInfo =
@@ -737,6 +760,7 @@ shaka.hls.HlsParser = class {
737
760
  const channelsCount = basicInfo.channelCount;
738
761
  const sampleRate = basicInfo.sampleRate;
739
762
  const closedCaptions = basicInfo.closedCaptions;
763
+ const videoRange = basicInfo.videoRange;
740
764
 
741
765
  // Some values we cannot figure out, and aren't important enough to ask
742
766
  // the user to provide through config values. A lot of these are only
@@ -757,8 +781,7 @@ shaka.hls.HlsParser = class {
757
781
 
758
782
  if (type == 'video') {
759
783
  this.addVideoAttributes_(streamInfo.stream, width, height,
760
- /* frameRate= */ null, /* videoRange= */ null,
761
- /* videoLayout= */ null);
784
+ /* frameRate= */ null, videoRange, /* videoLayout= */ null);
762
785
  }
763
786
 
764
787
  // Wrap the stream from that stream info with a variant.
@@ -805,8 +828,9 @@ shaka.hls.HlsParser = class {
805
828
  this.parseCodecs_(variantTags);
806
829
 
807
830
  this.parseClosedCaptions_(mediaTags);
808
- variants =
809
- this.createVariantsForTags_(variantTags, sessionKeyTags, mediaTags);
831
+ variants = await this.createVariantsForTags_(
832
+ variantTags, sessionKeyTags, mediaTags, getUris,
833
+ this.globalVariables_);
810
834
  textStreams = this.parseTexts_(mediaTags);
811
835
  imageStreams = await this.parseImages_(imageTags, iFrameTags);
812
836
  }
@@ -876,6 +900,19 @@ shaka.hls.HlsParser = class {
876
900
  }
877
901
 
878
902
  this.playerInterface_.makeTextStreamsForClosedCaptions(this.manifest_);
903
+
904
+ if (variants.length == 1) {
905
+ const createSegmentIndexPromises = [];
906
+ const variant = variants[0];
907
+ for (const stream of [variant.video, variant.audio]) {
908
+ if (stream && !stream.segmentIndex) {
909
+ createSegmentIndexPromises.push(stream.createSegmentIndex());
910
+ }
911
+ }
912
+ if (createSegmentIndexPromises.length > 0) {
913
+ await Promise.all(createSegmentIndexPromises);
914
+ }
915
+ }
879
916
  }
880
917
 
881
918
  /**
@@ -986,27 +1023,27 @@ shaka.hls.HlsParser = class {
986
1023
  this.presentationTimeline_.setSegmentAvailabilityDuration(
987
1024
  segmentAvailabilityDuration);
988
1025
  }
989
-
990
- if (!this.presentationTimeline_.isStartTimeLocked()) {
991
- for (const streamInfo of this.uriToStreamInfosMap_.values()) {
992
- if (!streamInfo.stream.segmentIndex) {
993
- continue; // Not active.
994
- }
995
- if (streamInfo.type != 'audio' && streamInfo.type != 'video') {
996
- continue;
997
- }
998
- const firstReference = streamInfo.stream.segmentIndex.get(0);
999
- if (firstReference && firstReference.syncTime) {
1000
- const syncTime = firstReference.syncTime;
1001
- this.presentationTimeline_.setInitialProgramDateTime(syncTime);
1002
- }
1003
- }
1004
- }
1005
1026
  } else {
1006
1027
  // Use the minimum duration as the presentation duration.
1007
1028
  this.presentationTimeline_.setDuration(this.getMinDuration_());
1008
1029
  }
1009
1030
 
1031
+ if (!this.presentationTimeline_.isStartTimeLocked()) {
1032
+ for (const streamInfo of this.uriToStreamInfosMap_.values()) {
1033
+ if (!streamInfo.stream.segmentIndex) {
1034
+ continue; // Not active.
1035
+ }
1036
+ if (streamInfo.type != 'audio' && streamInfo.type != 'video') {
1037
+ continue;
1038
+ }
1039
+ const firstReference = streamInfo.stream.segmentIndex.get(0);
1040
+ if (firstReference && firstReference.syncTime) {
1041
+ const syncTime = firstReference.syncTime;
1042
+ this.presentationTimeline_.setInitialProgramDateTime(syncTime);
1043
+ }
1044
+ }
1045
+ }
1046
+
1010
1047
  // This is the first point where we have a meaningful presentation start
1011
1048
  // time, and we need to tell PresentationTimeline that so that it can
1012
1049
  // maintain consistency from here on.
@@ -1323,37 +1360,48 @@ shaka.hls.HlsParser = class {
1323
1360
  * from the playlist.
1324
1361
  * @param {!Array.<!shaka.hls.Tag>} mediaTags EXT-X-MEDIA tags from the
1325
1362
  * playlist.
1326
- * @return {!Array.<!shaka.extern.Variant>}
1363
+ * @param {function():!Array.<string>} getUris
1364
+ * @param {?Map.<string, string>=} variables
1365
+ * @return {!Promise.<!Array.<!shaka.extern.Variant>>}
1327
1366
  * @private
1328
1367
  */
1329
- createVariantsForTags_(tags, sessionKeyTags, mediaTags) {
1368
+ async createVariantsForTags_(tags, sessionKeyTags, mediaTags, getUris,
1369
+ variables) {
1330
1370
  // EXT-X-SESSION-KEY processing
1331
1371
  const drmInfos = [];
1332
1372
  const keyIds = new Set();
1333
1373
  if (sessionKeyTags.length > 0) {
1334
1374
  for (const drmTag of sessionKeyTags) {
1335
1375
  const method = drmTag.getRequiredAttrValue('METHOD');
1336
- if (method != 'NONE' && method != 'AES-128') {
1337
- // According to the HLS spec, KEYFORMAT is optional and implicitly
1338
- // defaults to "identity".
1339
- // https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis-11#section-4.4.4.4
1340
- const keyFormat =
1341
- drmTag.getAttributeValue('KEYFORMAT') || 'identity';
1376
+ // According to the HLS spec, KEYFORMAT is optional and implicitly
1377
+ // defaults to "identity".
1378
+ // https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis-11#section-4.4.4.4
1379
+ const keyFormat =
1380
+ drmTag.getAttributeValue('KEYFORMAT') || 'identity';
1381
+ let drmInfo = null;
1382
+ if (method == 'NONE' || this.isAesMethod_(method)) {
1383
+ continue;
1384
+ } else if (keyFormat == 'identity') {
1385
+ // eslint-disable-next-line no-await-in-loop
1386
+ drmInfo = await this.identityDrmParser_(
1387
+ drmTag, /* mimeType= */ '', getUris,
1388
+ /* initSegmentRef= */ null, variables);
1389
+ } else {
1342
1390
  const drmParser =
1343
1391
  shaka.hls.HlsParser.KEYFORMATS_TO_DRM_PARSERS_[keyFormat];
1344
1392
 
1345
- const drmInfo = drmParser ?
1393
+ drmInfo = drmParser ?
1346
1394
  drmParser(drmTag, /* mimeType= */ '') : null;
1347
- if (drmInfo) {
1348
- if (drmInfo.keyIds) {
1349
- for (const keyId of drmInfo.keyIds) {
1350
- keyIds.add(keyId);
1351
- }
1395
+ }
1396
+ if (drmInfo) {
1397
+ if (drmInfo.keyIds) {
1398
+ for (const keyId of drmInfo.keyIds) {
1399
+ keyIds.add(keyId);
1352
1400
  }
1353
- drmInfos.push(drmInfo);
1354
- } else {
1355
- shaka.log.warning('Unsupported HLS KEYFORMAT', keyFormat);
1356
1401
  }
1402
+ drmInfos.push(drmInfo);
1403
+ } else {
1404
+ shaka.log.warning('Unsupported HLS KEYFORMAT', keyFormat);
1357
1405
  }
1358
1406
  }
1359
1407
  }
@@ -1587,6 +1635,9 @@ shaka.hls.HlsParser = class {
1587
1635
  getCodecsForVariantTag_(tag) {
1588
1636
  let codecsString = tag.getAttributeValue('CODECS') || '';
1589
1637
 
1638
+ const supplementalCodecsString =
1639
+ tag.getAttributeValue('SUPPLEMENTAL-CODECS');
1640
+
1590
1641
  this.codecInfoInManifest_ = codecsString.length > 0;
1591
1642
 
1592
1643
  if (!this.codecInfoInManifest_ && !this.config_.hls.disableCodecGuessing) {
@@ -1607,34 +1658,15 @@ shaka.hls.HlsParser = class {
1607
1658
  /** @type {!Array.<string>} */
1608
1659
  const codecs = codecsString.split(/\s*,\s*/);
1609
1660
 
1610
- return this.filterDuplicateCodecs_(codecs);
1611
- }
1612
-
1613
-
1614
- /**
1615
- * @param {!Array.<string>} codecs
1616
- * @return {!Array.<string>} codecs
1617
- * @private
1618
- */
1619
- filterDuplicateCodecs_(codecs) {
1620
- // Filter out duplicate codecs.
1621
- const seen = new Set();
1622
- const ret = [];
1623
- for (const codec of codecs) {
1624
- // HLS says the CODECS field needs to include all codecs that appear in
1625
- // the content. This means that if the content changes profiles, it should
1626
- // include both. Since all known browsers support changing profiles
1627
- // without any other work, just ignore them. See also:
1628
- // https://github.com/shaka-project/shaka-player/issues/1817
1629
- const shortCodec = shaka.util.MimeUtils.getCodecBase(codec);
1630
- if (!seen.has(shortCodec)) {
1631
- ret.push(codec);
1632
- seen.add(shortCodec);
1633
- } else {
1634
- shaka.log.debug('Ignoring duplicate codec');
1635
- }
1661
+ if (supplementalCodecsString) {
1662
+ const supplementalCodecs = supplementalCodecsString.split(/\s*,\s*/)
1663
+ .map((codec) => {
1664
+ return codec.split('/')[0];
1665
+ });
1666
+ codecs.push(...supplementalCodecs);
1636
1667
  }
1637
- return ret;
1668
+
1669
+ return shaka.media.SegmentUtils.codecsFiltering(codecs);
1638
1670
  }
1639
1671
 
1640
1672
  /**
@@ -2444,7 +2476,23 @@ shaka.hls.HlsParser = class {
2444
2476
  this.syncStreamsWithSequenceNumber_(streamInfos);
2445
2477
  } else {
2446
2478
  this.syncStreamsWithProgramDateTime_(streamInfos);
2479
+ if (this.config_.hls.ignoreManifestProgramDateTimeForTypes.length > 0) {
2480
+ this.syncStreamsWithSequenceNumber_(streamInfos);
2481
+ }
2482
+ }
2483
+ }
2484
+
2485
+ /**
2486
+ * @param {string} type
2487
+ * @return {boolean}
2488
+ * @private
2489
+ */
2490
+ ignoreManifestProgramDateTimeFor_(type) {
2491
+ if (this.config_.hls.ignoreManifestProgramDateTime) {
2492
+ return true;
2447
2493
  }
2494
+ const forTypes = this.config_.hls.ignoreManifestProgramDateTimeForTypes;
2495
+ return forTypes.includes(type);
2448
2496
  }
2449
2497
 
2450
2498
  /**
@@ -2534,7 +2582,7 @@ shaka.hls.HlsParser = class {
2534
2582
  }
2535
2583
 
2536
2584
  const {drmInfos, keyIds, encrypted, aesEncrypted} =
2537
- this.parseDrmInfo_(playlist, mimeType);
2585
+ await this.parseDrmInfo_(playlist, mimeType, getUris, mediaVariables);
2538
2586
 
2539
2587
  if (encrypted && !drmInfos.length && !aesEncrypted) {
2540
2588
  throw new shaka.util.Error(
@@ -2556,7 +2604,8 @@ shaka.hls.HlsParser = class {
2556
2604
  this.mediaSequenceToStartTimeByType_.get(type) : new Map();
2557
2605
 
2558
2606
  const {segments, bandwidth} = this.createSegments_(
2559
- playlist, stream, mediaSequenceToStartTime, mediaVariables, getUris);
2607
+ playlist, stream, mediaSequenceToStartTime, mediaVariables, getUris,
2608
+ type);
2560
2609
  if (bandwidth) {
2561
2610
  stream.bandwidth = bandwidth;
2562
2611
  }
@@ -2753,22 +2802,31 @@ shaka.hls.HlsParser = class {
2753
2802
  /**
2754
2803
  * @param {!shaka.hls.Playlist} playlist
2755
2804
  * @param {string} mimeType
2756
- * @return {{
2805
+ * @param {function():!Array.<string>} getUris
2806
+ * @param {?Map.<string, string>=} variables
2807
+ * @return {Promise.<{
2757
2808
  * drmInfos: !Array.<shaka.extern.DrmInfo>,
2758
2809
  * keyIds: !Set.<string>,
2759
2810
  * encrypted: boolean,
2760
2811
  * aesEncrypted: boolean
2761
- * }}
2812
+ * }>}
2762
2813
  * @private
2763
2814
  */
2764
- parseDrmInfo_(playlist, mimeType) {
2765
- /** @type {!Array.<!shaka.hls.Tag>} */
2766
- const drmTags = [];
2815
+ async parseDrmInfo_(playlist, mimeType, getUris, variables) {
2816
+ /** @type {!Map<!shaka.hls.Tag, ?shaka.media.InitSegmentReference>} */
2817
+ const drmTagsMap = new Map();
2767
2818
  if (playlist.segments) {
2768
2819
  for (const segment of playlist.segments) {
2769
2820
  const segmentKeyTags = shaka.hls.Utils.filterTagsByName(segment.tags,
2770
2821
  'EXT-X-KEY');
2771
- drmTags.push(...segmentKeyTags);
2822
+ let initSegmentRef = null;
2823
+ if (segmentKeyTags.length) {
2824
+ initSegmentRef = this.getInitSegmentReference_(playlist,
2825
+ segment.tags, getUris, variables);
2826
+ for (const segmentKeyTag of segmentKeyTags) {
2827
+ drmTagsMap.set(segmentKeyTag, initSegmentRef);
2828
+ }
2829
+ }
2772
2830
  }
2773
2831
  }
2774
2832
 
@@ -2779,34 +2837,43 @@ shaka.hls.HlsParser = class {
2779
2837
  const drmInfos = [];
2780
2838
  const keyIds = new Set();
2781
2839
 
2782
- for (const drmTag of drmTags) {
2840
+ for (const [key, value] of drmTagsMap) {
2841
+ const drmTag = /** @type {!shaka.hls.Tag} */ (key);
2842
+ const initSegmentRef =
2843
+ /** @type {?shaka.media.InitSegmentReference} */ (value);
2783
2844
  const method = drmTag.getRequiredAttrValue('METHOD');
2784
2845
  if (method != 'NONE') {
2785
2846
  encrypted = true;
2786
2847
 
2787
- if (method == 'AES-128') {
2848
+ // According to the HLS spec, KEYFORMAT is optional and implicitly
2849
+ // defaults to "identity".
2850
+ // https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis-11#section-4.4.4.4
2851
+ const keyFormat =
2852
+ drmTag.getAttributeValue('KEYFORMAT') || 'identity';
2853
+ let drmInfo = null;
2854
+
2855
+ if (this.isAesMethod_(method)) {
2788
2856
  // These keys are handled separately.
2789
2857
  aesEncrypted = true;
2858
+ continue;
2859
+ } else if (keyFormat == 'identity') {
2860
+ // eslint-disable-next-line no-await-in-loop
2861
+ drmInfo = await this.identityDrmParser_(
2862
+ drmTag, mimeType, getUris, initSegmentRef, variables);
2790
2863
  } else {
2791
- // According to the HLS spec, KEYFORMAT is optional and implicitly
2792
- // defaults to "identity".
2793
- // https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis-11#section-4.4.4.4
2794
- const keyFormat =
2795
- drmTag.getAttributeValue('KEYFORMAT') || 'identity';
2796
2864
  const drmParser =
2797
2865
  shaka.hls.HlsParser.KEYFORMATS_TO_DRM_PARSERS_[keyFormat];
2798
-
2799
- const drmInfo = drmParser ? drmParser(drmTag, mimeType) : null;
2800
- if (drmInfo) {
2801
- if (drmInfo.keyIds) {
2802
- for (const keyId of drmInfo.keyIds) {
2803
- keyIds.add(keyId);
2804
- }
2866
+ drmInfo = drmParser ? drmParser(drmTag, mimeType) : null;
2867
+ }
2868
+ if (drmInfo) {
2869
+ if (drmInfo.keyIds) {
2870
+ for (const keyId of drmInfo.keyIds) {
2871
+ keyIds.add(keyId);
2805
2872
  }
2806
- drmInfos.push(drmInfo);
2807
- } else {
2808
- shaka.log.warning('Unsupported HLS KEYFORMAT', keyFormat);
2809
2873
  }
2874
+ drmInfos.push(drmInfo);
2875
+ } else {
2876
+ shaka.log.warning('Unsupported HLS KEYFORMAT', keyFormat);
2810
2877
  }
2811
2878
  }
2812
2879
  }
@@ -2819,14 +2886,14 @@ shaka.hls.HlsParser = class {
2819
2886
  * @param {!shaka.hls.Playlist} playlist
2820
2887
  * @param {function():!Array.<string>} getUris
2821
2888
  * @param {?Map.<string, string>=} variables
2822
- * @return {!shaka.extern.aes128Key}
2889
+ * @return {!shaka.extern.aesKey}
2823
2890
  * @private
2824
2891
  */
2825
- parseAES128DrmTag_(drmTag, playlist, getUris, variables) {
2892
+ parseAESDrmTag_(drmTag, playlist, getUris, variables) {
2826
2893
  // Check if the Web Crypto API is available.
2827
2894
  if (!window.crypto || !window.crypto.subtle) {
2828
2895
  shaka.log.alwaysWarn('Web Crypto API is not available to decrypt ' +
2829
- 'AES-128. (Web Crypto only exists in secure origins like https)');
2896
+ 'AES. (Web Crypto only exists in secure origins like https)');
2830
2897
  throw new shaka.util.Error(
2831
2898
  shaka.util.Error.Severity.CRITICAL,
2832
2899
  shaka.util.Error.Category.MANIFEST,
@@ -2857,36 +2924,63 @@ shaka.hls.HlsParser = class {
2857
2924
  }
2858
2925
  }
2859
2926
 
2860
- const keyInfo = {method: 'AES-128', iv, firstMediaSequenceNumber};
2861
-
2862
- // Don't download the key object until the segment is parsed, to avoid a
2863
- // startup delay for long manifests with lots of keys.
2864
- keyInfo.fetchKey = async () => {
2865
- const keyUris = shaka.hls.Utils.constructSegmentUris(
2866
- getUris(), drmTag.getRequiredAttrValue('URI'), variables);
2867
-
2868
- const requestType = shaka.net.NetworkingEngine.RequestType.KEY;
2869
- const request = shaka.net.NetworkingEngine.makeRequest(
2870
- keyUris, this.config_.retryParameters);
2871
- const keyResponse = await this.makeNetworkRequest_(request, requestType);
2927
+ const aesKeyInfoKey = `${drmTag.toString()}-${firstMediaSequenceNumber}`;
2928
+ if (!this.aesKeyInfoMap_.has(aesKeyInfoKey)) {
2929
+ // Default AES-128
2930
+ const keyInfo = {
2931
+ bitsKey: 128,
2932
+ blockCipherMode: 'CBC',
2933
+ iv,
2934
+ firstMediaSequenceNumber,
2935
+ };
2872
2936
 
2873
- // keyResponse.status is undefined when URI is "data:text/plain;base64,"
2874
- if (!keyResponse.data || keyResponse.data.byteLength != 16) {
2875
- throw new shaka.util.Error(
2876
- shaka.util.Error.Severity.CRITICAL,
2877
- shaka.util.Error.Category.MANIFEST,
2878
- shaka.util.Error.Code.AES_128_INVALID_KEY_LENGTH);
2937
+ const method = drmTag.getRequiredAttrValue('METHOD');
2938
+ switch (method) {
2939
+ case 'AES-256':
2940
+ keyInfo.bitsKey = 256;
2941
+ break;
2942
+ case 'AES-256-CTR':
2943
+ keyInfo.bitsKey = 256;
2944
+ keyInfo.blockCipherMode = 'CTR';
2945
+ break;
2879
2946
  }
2880
2947
 
2881
- const algorithm = {
2882
- name: 'AES-CBC',
2883
- };
2884
- keyInfo.cryptoKey = await window.crypto.subtle.importKey(
2885
- 'raw', keyResponse.data, algorithm, true, ['decrypt']);
2886
- keyInfo.fetchKey = undefined; // No longer needed.
2887
- };
2948
+ // Don't download the key object until the segment is parsed, to avoid a
2949
+ // startup delay for long manifests with lots of keys.
2950
+ keyInfo.fetchKey = async () => {
2951
+ const keyUris = shaka.hls.Utils.constructSegmentUris(
2952
+ getUris(), drmTag.getRequiredAttrValue('URI'), variables);
2953
+
2954
+ const keyMapKey = keyUris.sort().join('');
2955
+ if (!this.aesKeyMap_.has(keyMapKey)) {
2956
+ const requestType = shaka.net.NetworkingEngine.RequestType.KEY;
2957
+ const request = shaka.net.NetworkingEngine.makeRequest(
2958
+ keyUris, this.config_.retryParameters);
2959
+ const keyResponse = this.makeNetworkRequest_(request, requestType);
2960
+ this.aesKeyMap_.set(keyMapKey, keyResponse);
2961
+ }
2962
+ const keyResponse = await this.aesKeyMap_.get(keyMapKey);
2963
+
2964
+ // keyResponse.status is undefined when URI is "data:text/plain;base64,"
2965
+ if (!keyResponse.data ||
2966
+ keyResponse.data.byteLength != (keyInfo.bitsKey / 8)) {
2967
+ throw new shaka.util.Error(
2968
+ shaka.util.Error.Severity.CRITICAL,
2969
+ shaka.util.Error.Category.MANIFEST,
2970
+ shaka.util.Error.Code.AES_128_INVALID_KEY_LENGTH);
2971
+ }
2888
2972
 
2889
- return keyInfo;
2973
+ const algorithm = {
2974
+ name: keyInfo.blockCipherMode == 'CTR' ? 'AES-CTR' : 'AES-CBC',
2975
+ length: keyInfo.bitsKey,
2976
+ };
2977
+ keyInfo.cryptoKey = await window.crypto.subtle.importKey(
2978
+ 'raw', keyResponse.data, algorithm, true, ['decrypt']);
2979
+ keyInfo.fetchKey = undefined; // No longer needed.
2980
+ };
2981
+ this.aesKeyInfoMap_.set(aesKeyInfoKey, keyInfo);
2982
+ }
2983
+ return this.aesKeyInfoMap_.get(aesKeyInfoKey);
2890
2984
  }
2891
2985
 
2892
2986
 
@@ -3066,22 +3160,22 @@ shaka.hls.HlsParser = class {
3066
3160
  mapTag.getAttributeValue('BYTERANGE', ''),
3067
3161
  ].join('-');
3068
3162
  if (!this.mapTagToInitSegmentRefMap_.has(mapTagKey)) {
3069
- /** @type {shaka.extern.aes128Key|undefined} */
3070
- let aes128Key = undefined;
3163
+ /** @type {shaka.extern.aesKey|undefined} */
3164
+ let aesKey = undefined;
3071
3165
  let byteRangeTag = null;
3072
3166
  for (const tag of tags) {
3073
3167
  if (tag.name == 'EXT-X-KEY') {
3074
- if (tag.getRequiredAttrValue('METHOD') == 'AES-128' &&
3168
+ if (this.isAesMethod_(tag.getRequiredAttrValue('METHOD')) &&
3075
3169
  tag.id < mapTag.id) {
3076
- aes128Key =
3077
- this.parseAES128DrmTag_(tag, playlist, getUris, variables);
3170
+ aesKey =
3171
+ this.parseAESDrmTag_(tag, playlist, getUris, variables);
3078
3172
  }
3079
3173
  } else if (tag.name == 'EXT-X-BYTERANGE' && tag.id < mapTag.id) {
3080
3174
  byteRangeTag = tag;
3081
3175
  }
3082
3176
  }
3083
3177
  const initSegmentRef = this.createInitSegmentReference_(
3084
- absoluteInitSegmentUris, mapTag, byteRangeTag, aes128Key);
3178
+ absoluteInitSegmentUris, mapTag, byteRangeTag, aesKey);
3085
3179
  this.mapTagToInitSegmentRefMap_.set(mapTagKey, initSegmentRef);
3086
3180
  }
3087
3181
  return this.mapTagToInitSegmentRefMap_.get(mapTagKey);
@@ -3093,12 +3187,12 @@ shaka.hls.HlsParser = class {
3093
3187
  * @param {!Array.<string>} absoluteInitSegmentUris
3094
3188
  * @param {!shaka.hls.Tag} mapTag EXT-X-MAP
3095
3189
  * @param {shaka.hls.Tag=} byteRangeTag EXT-X-BYTERANGE
3096
- * @param {shaka.extern.aes128Key=} aes128Key
3190
+ * @param {shaka.extern.aesKey=} aesKey
3097
3191
  * @return {!shaka.media.InitSegmentReference}
3098
3192
  * @private
3099
3193
  */
3100
3194
  createInitSegmentReference_(absoluteInitSegmentUris, mapTag, byteRangeTag,
3101
- aes128Key) {
3195
+ aesKey) {
3102
3196
  let startByte = 0;
3103
3197
  let endByte = null;
3104
3198
  let byterange = mapTag.getAttributeValue('BYTERANGE');
@@ -3113,8 +3207,8 @@ shaka.hls.HlsParser = class {
3113
3207
  startByte = Number(blocks[1]);
3114
3208
  endByte = startByte + byteLength - 1;
3115
3209
 
3116
- if (aes128Key) {
3117
- // MAP segment encrypted with method 'AES-128', when served with
3210
+ if (aesKey) {
3211
+ // MAP segment encrypted with method AES, when served with
3118
3212
  // HTTP Range, has the unencrypted size specified in the range.
3119
3213
  // See: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
3120
3214
  const length = (endByte + 1) - startByte;
@@ -3131,7 +3225,7 @@ shaka.hls.HlsParser = class {
3131
3225
  /* mediaQuality= */ null,
3132
3226
  /* timescale= */ null,
3133
3227
  /* segmentData= */ null,
3134
- aes128Key);
3228
+ aesKey);
3135
3229
  return initSegmentRef;
3136
3230
  }
3137
3231
 
@@ -3146,13 +3240,13 @@ shaka.hls.HlsParser = class {
3146
3240
  * @param {!shaka.hls.Playlist} playlist
3147
3241
  * @param {shaka.extern.Stream} stream
3148
3242
  * @param {function():!Array.<string>} getUris
3149
- * @param {shaka.extern.aes128Key=} aes128Key
3243
+ * @param {shaka.extern.aesKey=} aesKey
3150
3244
  * @return {shaka.media.SegmentReference}
3151
3245
  * @private
3152
3246
  */
3153
3247
  createSegmentReference_(
3154
3248
  initSegmentReference, previousReference, hlsSegment, startTime,
3155
- variables, playlist, stream, getUris, aes128Key) {
3249
+ variables, playlist, stream, getUris, aesKey) {
3156
3250
  const tags = hlsSegment.tags;
3157
3251
  const extinfTag =
3158
3252
  shaka.hls.Utils.getFirstTagWithName(tags, 'EXTINF');
@@ -3173,7 +3267,7 @@ shaka.hls.HlsParser = class {
3173
3267
  const dateTimeTag =
3174
3268
  shaka.hls.Utils.getFirstTagWithName(tags, 'EXT-X-PROGRAM-DATE-TIME');
3175
3269
  if (dateTimeTag && dateTimeTag.value) {
3176
- syncTime = shaka.util.XmlUtils.parseDate(dateTimeTag.value);
3270
+ syncTime = shaka.util.TXml.parseDate(dateTimeTag.value);
3177
3271
  goog.asserts.assert(syncTime != null,
3178
3272
  'EXT-X-PROGRAM-DATE-TIME format not valid');
3179
3273
  }
@@ -3298,7 +3392,7 @@ shaka.hls.HlsParser = class {
3298
3392
  /* tileDuration= */ null,
3299
3393
  partialSyncTime,
3300
3394
  partialStatus,
3301
- aes128Key);
3395
+ aesKey);
3302
3396
  if (item.name == 'EXT-X-PRELOAD-HINT') {
3303
3397
  partial.markAsPreload();
3304
3398
  isPreloadSegment = true;
@@ -3413,7 +3507,7 @@ shaka.hls.HlsParser = class {
3413
3507
  tileDuration,
3414
3508
  syncTime,
3415
3509
  status,
3416
- aes128Key,
3510
+ aesKey,
3417
3511
  allPartialSegments,
3418
3512
  );
3419
3513
 
@@ -3467,12 +3561,13 @@ shaka.hls.HlsParser = class {
3467
3561
  * @param {!Map.<number, number>} mediaSequenceToStartTime
3468
3562
  * @param {!Map.<string, string>} variables
3469
3563
  * @param {function():!Array.<string>} getUris
3564
+ * @param {string} type
3470
3565
  * @return {{segments: !Array.<!shaka.media.SegmentReference>,
3471
3566
  * bandwidth: (number|undefined)}}
3472
3567
  * @private
3473
3568
  */
3474
3569
  createSegments_(playlist, stream, mediaSequenceToStartTime, variables,
3475
- getUris) {
3570
+ getUris, type) {
3476
3571
  /** @type {Array.<!shaka.hls.Segment>} */
3477
3572
  const hlsSegments = playlist.segments;
3478
3573
  goog.asserts.assert(hlsSegments.length, 'Playlist should have segments!');
@@ -3480,8 +3575,8 @@ shaka.hls.HlsParser = class {
3480
3575
  /** @type {shaka.media.InitSegmentReference} */
3481
3576
  let initSegmentRef;
3482
3577
 
3483
- /** @type {shaka.extern.aes128Key|undefined} */
3484
- let aes128Key = undefined;
3578
+ /** @type {shaka.extern.aesKey|undefined} */
3579
+ let aesKey = undefined;
3485
3580
 
3486
3581
  let discontinuitySequence = shaka.hls.Utils.getFirstTagWithNameAsNumber(
3487
3582
  playlist.tags, 'EXT-X-DISCONTINUITY-SEQUENCE', 0);
@@ -3527,14 +3622,14 @@ shaka.hls.HlsParser = class {
3527
3622
  discontinuitySequence++;
3528
3623
  }
3529
3624
 
3530
- // Apply new AES-128 tags as you see them, keeping a running total.
3625
+ // Apply new AES tags as you see them, keeping a running total.
3531
3626
  for (const drmTag of item.tags) {
3532
3627
  if (drmTag.name == 'EXT-X-KEY') {
3533
- if (drmTag.getRequiredAttrValue('METHOD') == 'AES-128') {
3534
- aes128Key =
3535
- this.parseAES128DrmTag_(drmTag, playlist, getUris, variables);
3628
+ if (this.isAesMethod_(drmTag.getRequiredAttrValue('METHOD'))) {
3629
+ aesKey =
3630
+ this.parseAESDrmTag_(drmTag, playlist, getUris, variables);
3536
3631
  } else {
3537
- aes128Key = undefined;
3632
+ aesKey = undefined;
3538
3633
  }
3539
3634
  }
3540
3635
  }
@@ -3565,7 +3660,7 @@ shaka.hls.HlsParser = class {
3565
3660
  playlist,
3566
3661
  stream,
3567
3662
  getUris,
3568
- aes128Key);
3663
+ aesKey);
3569
3664
 
3570
3665
  if (reference) {
3571
3666
  const bitrate = shaka.hls.Utils.getFirstTagWithNameAsNumber(
@@ -3586,7 +3681,7 @@ shaka.hls.HlsParser = class {
3586
3681
  previousReference = reference;
3587
3682
  reference.discontinuitySequence = discontinuitySequence;
3588
3683
 
3589
- if (this.config_.hls.ignoreManifestProgramDateTime &&
3684
+ if (this.ignoreManifestProgramDateTimeFor_(type) &&
3590
3685
  this.minSequenceNumber_ != null &&
3591
3686
  position < this.minSequenceNumber_) {
3592
3687
  // This segment is ignored as part of our fallback synchronization
@@ -3687,8 +3782,10 @@ shaka.hls.HlsParser = class {
3687
3782
  // skip this step, and wait until we have sync time across stream types.
3688
3783
  const lowestSyncTime = this.lowestSyncTime_;
3689
3784
  if (someSyncTime && lowestSyncTime != Infinity) {
3690
- for (const reference of references) {
3691
- reference.syncAgainst(lowestSyncTime);
3785
+ if (!this.ignoreManifestProgramDateTimeFor_(type)) {
3786
+ for (const reference of references) {
3787
+ reference.syncAgainst(lowestSyncTime);
3788
+ }
3692
3789
  }
3693
3790
  }
3694
3791
 
@@ -4037,6 +4134,17 @@ shaka.hls.HlsParser = class {
4037
4134
  return op.promise;
4038
4135
  }
4039
4136
 
4137
+ /**
4138
+ * @param {string} method
4139
+ * @return {boolean}
4140
+ * @private
4141
+ */
4142
+ isAesMethod_(method) {
4143
+ return method == 'AES-128' ||
4144
+ method == 'AES-256' ||
4145
+ method == 'AES-256-CTR';
4146
+ }
4147
+
4040
4148
  /**
4041
4149
  * @param {!shaka.hls.Tag} drmTag
4042
4150
  * @param {string} mimeType
@@ -4059,13 +4167,28 @@ shaka.hls.HlsParser = class {
4059
4167
  .HLS_MSE_ENCRYPTED_LEGACY_APPLE_MEDIA_KEYS_NOT_SUPPORTED);
4060
4168
  }
4061
4169
 
4170
+ const method = drmTag.getRequiredAttrValue('METHOD');
4171
+ const VALID_METHODS = ['SAMPLE-AES', 'SAMPLE-AES-CTR'];
4172
+ if (!VALID_METHODS.includes(method)) {
4173
+ shaka.log.error('FairPlay in HLS is only supported with [',
4174
+ VALID_METHODS.join(', '), '], not', method);
4175
+ return null;
4176
+ }
4177
+
4178
+ let encryptionScheme = 'cenc';
4179
+ if (method == 'SAMPLE-AES') {
4180
+ // It should be 'cbcs-1-9' but Safari doesn't support it.
4181
+ // See: https://github.com/WebKit/WebKit/blob/main/Source/WebCore/Modules/encryptedmedia/MediaKeyEncryptionScheme.idl
4182
+ encryptionScheme = 'cbcs';
4183
+ }
4184
+
4062
4185
  /*
4063
4186
  * Even if we're not able to construct initData through the HLS tag, adding
4064
4187
  * a DRMInfo will allow DRM Engine to request a media key system access
4065
4188
  * with the correct keySystem and initDataType
4066
4189
  */
4067
4190
  const drmInfo = shaka.util.ManifestParserUtils.createDrmInfo(
4068
- 'com.apple.fps', [
4191
+ 'com.apple.fps', encryptionScheme, [
4069
4192
  {initDataType: 'sinf', initData: new Uint8Array(0), keyId: null},
4070
4193
  ]);
4071
4194
 
@@ -4086,13 +4209,18 @@ shaka.hls.HlsParser = class {
4086
4209
  return null;
4087
4210
  }
4088
4211
 
4212
+ let encryptionScheme = 'cenc';
4213
+ if (method == 'SAMPLE-AES') {
4214
+ encryptionScheme = 'cbcs';
4215
+ }
4216
+
4089
4217
  const uri = drmTag.getRequiredAttrValue('URI');
4090
4218
  const parsedData = shaka.net.DataUriPlugin.parseRaw(uri);
4091
4219
 
4092
4220
  // The data encoded in the URI is a PSSH box to be used as init data.
4093
4221
  const pssh = shaka.util.BufferUtils.toUint8(parsedData.data);
4094
4222
  const drmInfo = shaka.util.ManifestParserUtils.createDrmInfo(
4095
- 'com.widevine.alpha', [
4223
+ 'com.widevine.alpha', encryptionScheme, [
4096
4224
  {initDataType: 'cenc', initData: pssh},
4097
4225
  ]);
4098
4226
 
@@ -4124,6 +4252,11 @@ shaka.hls.HlsParser = class {
4124
4252
  return null;
4125
4253
  }
4126
4254
 
4255
+ let encryptionScheme = 'cenc';
4256
+ if (method == 'SAMPLE-AES') {
4257
+ encryptionScheme = 'cbcs';
4258
+ }
4259
+
4127
4260
  const uri = drmTag.getRequiredAttrValue('URI');
4128
4261
  const parsedData = shaka.net.DataUriPlugin.parseRaw(uri);
4129
4262
 
@@ -4139,7 +4272,7 @@ shaka.hls.HlsParser = class {
4139
4272
  const pssh =
4140
4273
  shaka.util.Pssh.createPssh(data, systemId, keyIds, psshVersion);
4141
4274
  const drmInfo = shaka.util.ManifestParserUtils.createDrmInfo(
4142
- 'com.microsoft.playready', [
4275
+ 'com.microsoft.playready', encryptionScheme, [
4143
4276
  {initDataType: 'cenc', initData: pssh},
4144
4277
  ]);
4145
4278
 
@@ -4150,10 +4283,15 @@ shaka.hls.HlsParser = class {
4150
4283
  * See: https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis-11#section-5.1
4151
4284
  *
4152
4285
  * @param {!shaka.hls.Tag} drmTag
4153
- * @return {?shaka.extern.DrmInfo}
4286
+ * @param {string} mimeType
4287
+ * @param {function():!Array.<string>} getUris
4288
+ * @param {?shaka.media.InitSegmentReference} initSegmentRef
4289
+ * @param {?Map.<string, string>=} variables
4290
+ * @return {!Promise.<?shaka.extern.DrmInfo>}
4154
4291
  * @private
4155
4292
  */
4156
- static identityDrmParser_(drmTag, mimeType) {
4293
+ async identityDrmParser_(drmTag, mimeType, getUris, initSegmentRef,
4294
+ variables) {
4157
4295
  if (mimeType == 'video/mp2t') {
4158
4296
  throw new shaka.util.Error(
4159
4297
  shaka.util.Error.Severity.CRITICAL,
@@ -4177,15 +4315,73 @@ shaka.hls.HlsParser = class {
4177
4315
  return null;
4178
4316
  }
4179
4317
 
4318
+ const keyUris = shaka.hls.Utils.constructSegmentUris(
4319
+ getUris(), drmTag.getRequiredAttrValue('URI'), variables);
4320
+
4321
+ let key;
4322
+ if (keyUris[0].startsWith('data:text/plain;base64,')) {
4323
+ key = shaka.util.Uint8ArrayUtils.toHex(
4324
+ shaka.util.Uint8ArrayUtils.fromBase64(
4325
+ keyUris[0].split('data:text/plain;base64,').pop()));
4326
+ } else {
4327
+ const keyMapKey = keyUris.sort().join('');
4328
+ if (!this.identityKeyMap_.has(keyMapKey)) {
4329
+ const requestType = shaka.net.NetworkingEngine.RequestType.KEY;
4330
+ const request = shaka.net.NetworkingEngine.makeRequest(
4331
+ keyUris, this.config_.retryParameters);
4332
+ const keyResponse = this.makeNetworkRequest_(request, requestType);
4333
+ this.identityKeyMap_.set(keyMapKey, keyResponse);
4334
+ }
4335
+ const keyResponse = await this.identityKeyMap_.get(keyMapKey);
4336
+ key = shaka.util.Uint8ArrayUtils.toHex(keyResponse.data);
4337
+ }
4338
+
4180
4339
  // NOTE: The ClearKey CDM requires a key-id to key mapping. HLS doesn't
4181
4340
  // provide a key ID anywhere. So although we could use the 'URI' attribute
4182
4341
  // to fetch the actual 16-byte key, without a key ID, we can't provide this
4183
- // automatically to the ClearKey CDM. Instead, the application will have
4184
- // to use player.configure('drm.clearKeys', { ... }) to provide the key IDs
4342
+ // automatically to the ClearKey CDM. By default we assume that keyId is 0,
4343
+ // but we will try to get key ID from Init Segment.
4344
+ // If the application want override this behavior will have to use
4345
+ // player.configure('drm.clearKeys', { ... }) to provide the key IDs
4185
4346
  // and keys or player.configure('drm.servers.org\.w3\.clearkey', ...) to
4186
4347
  // provide a ClearKey license server URI.
4187
- return shaka.util.ManifestParserUtils.createDrmInfo(
4188
- 'org.w3.clearkey', /* initDatas= */ null);
4348
+ let keyId = '00000000000000000000000000000000';
4349
+
4350
+ if (initSegmentRef) {
4351
+ let defaultKID;
4352
+ if (this.identityKidMap_.has(initSegmentRef)) {
4353
+ defaultKID = this.identityKidMap_.get(initSegmentRef);
4354
+ } else {
4355
+ const initSegmentRequest = shaka.util.Networking.createSegmentRequest(
4356
+ initSegmentRef.getUris(),
4357
+ initSegmentRef.getStartByte(),
4358
+ initSegmentRef.getEndByte(),
4359
+ this.config_.retryParameters);
4360
+ const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
4361
+ const initType =
4362
+ shaka.net.NetworkingEngine.AdvancedRequestType.INIT_SEGMENT;
4363
+ const initResponse = await this.makeNetworkRequest_(
4364
+ initSegmentRequest, requestType, {type: initType});
4365
+
4366
+ defaultKID = shaka.media.SegmentUtils.getDefaultKID(
4367
+ initResponse.data);
4368
+ this.identityKidMap_.set(initSegmentRef, defaultKID);
4369
+ }
4370
+ if (defaultKID) {
4371
+ keyId = defaultKID;
4372
+ }
4373
+ }
4374
+
4375
+ const clearkeys = new Map();
4376
+ clearkeys.set(keyId, key);
4377
+
4378
+ let encryptionScheme = 'cenc';
4379
+ if (method == 'SAMPLE-AES') {
4380
+ encryptionScheme = 'cbcs';
4381
+ }
4382
+
4383
+ return shaka.util.ManifestParserUtils.createDrmInfoFromClearKeys(
4384
+ clearkeys, encryptionScheme);
4189
4385
  }
4190
4386
  };
4191
4387
 
@@ -4366,8 +4562,6 @@ shaka.hls.HlsParser.KEYFORMATS_TO_DRM_PARSERS_ = {
4366
4562
  shaka.hls.HlsParser.widevineDrmParser_,
4367
4563
  'com.microsoft.playready':
4368
4564
  shaka.hls.HlsParser.playreadyDrmParser_,
4369
- 'identity':
4370
- shaka.hls.HlsParser.identityDrmParser_,
4371
4565
  };
4372
4566
 
4373
4567