shaka-player 4.1.3 → 4.2.1

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 (68) hide show
  1. package/.github/workflows/selenium-lab-tests.yaml +158 -53
  2. package/CHANGELOG.md +39 -29
  3. package/demo/common/assets.js +25 -0
  4. package/demo/common/message_ids.js +2 -0
  5. package/demo/config.js +5 -1
  6. package/demo/index.html +1 -1
  7. package/demo/locales/en.json +2 -0
  8. package/demo/locales/source.json +8 -0
  9. package/dist/controls.css +1 -1
  10. package/dist/controls.css.map +1 -1
  11. package/dist/demo.compiled.debug.js +25 -23
  12. package/dist/demo.compiled.debug.map +3 -3
  13. package/dist/demo.compiled.js +25 -23
  14. package/dist/demo.compiled.map +3 -3
  15. package/dist/deps.js +3 -2
  16. package/dist/locales.js +32 -32
  17. package/dist/receiver.compiled.debug.js +24 -22
  18. package/dist/receiver.compiled.debug.map +3 -3
  19. package/dist/receiver.compiled.js +24 -22
  20. package/dist/receiver.compiled.map +3 -3
  21. package/dist/shaka-player.compiled.d.ts +57 -6
  22. package/dist/shaka-player.compiled.debug.d.ts +57 -6
  23. package/dist/shaka-player.compiled.debug.externs.js +62 -6
  24. package/dist/shaka-player.compiled.debug.js +305 -262
  25. package/dist/shaka-player.compiled.debug.map +4 -4
  26. package/dist/shaka-player.compiled.externs.js +62 -6
  27. package/dist/shaka-player.compiled.js +817 -801
  28. package/dist/shaka-player.compiled.map +4 -4
  29. package/dist/shaka-player.ui.d.ts +57 -6
  30. package/dist/shaka-player.ui.debug.d.ts +57 -6
  31. package/dist/shaka-player.ui.debug.externs.js +62 -6
  32. package/dist/shaka-player.ui.debug.js +308 -265
  33. package/dist/shaka-player.ui.debug.map +4 -4
  34. package/dist/shaka-player.ui.externs.js +62 -6
  35. package/dist/shaka-player.ui.js +942 -925
  36. package/dist/shaka-player.ui.map +4 -4
  37. package/docs/tutorials/fairplay.md +149 -0
  38. package/docs/tutorials/ui.md +6 -6
  39. package/externs/shaka/abr_manager.js +8 -0
  40. package/externs/shaka/manifest.js +43 -0
  41. package/externs/shaka/player.js +13 -2
  42. package/lib/abr/simple_abr_manager.js +72 -4
  43. package/lib/hls/hls_parser.js +138 -96
  44. package/lib/media/drm_engine.js +0 -2
  45. package/lib/media/segment_index.js +8 -0
  46. package/lib/media/segment_reference.js +7 -1
  47. package/lib/media/streaming_engine.js +38 -2
  48. package/lib/net/networking_engine.js +1 -0
  49. package/lib/player.js +20 -1
  50. package/lib/polyfill/patchedmediakeys_apple.js +729 -0
  51. package/lib/text/ui_text_displayer.js +8 -0
  52. package/lib/text/vtt_text_parser.js +61 -4
  53. package/lib/util/error.js +23 -11
  54. package/lib/util/fairplay_utils.js +100 -2
  55. package/lib/util/platform.js +12 -1
  56. package/lib/util/player_configuration.js +4 -10
  57. package/package.json +1 -1
  58. package/shaka-player.uncompiled.js +1 -0
  59. package/test/hls/hls_parser_unit.js +101 -34
  60. package/test/media/drm_engine_unit.js +29 -0
  61. package/test/media/streaming_engine_unit.js +79 -5
  62. package/test/player_src_equals_integration.js +2 -0
  63. package/test/player_unit.js +39 -1
  64. package/test/test/assets/small.mp4 +0 -0
  65. package/test/test/util/simple_fakes.js +4 -0
  66. package/test/test/util/streaming_engine_util.js +10 -2
  67. package/test/text/vtt_text_parser_unit.js +26 -0
  68. package/ui/less/ad_controls.less +4 -0
@@ -37,6 +37,7 @@ goog.require('shaka.util.OperationManager');
37
37
  goog.require('shaka.util.Pssh');
38
38
  goog.require('shaka.util.Timer');
39
39
  goog.require('shaka.util.Platform');
40
+ goog.require('shaka.util.Uint8ArrayUtils');
40
41
  goog.require('shaka.util.XmlUtils');
41
42
  goog.requireType('shaka.hls.Segment');
42
43
 
@@ -179,10 +180,6 @@ shaka.hls.HlsParser = class {
179
180
  */
180
181
  this.groupIdToClosedCaptionsMap_ = new Map();
181
182
 
182
- /** True if some of the variants in the playlist is encrypted with AES-128.
183
- * @private {boolean} */
184
- this.aesEncrypted_ = false;
185
-
186
183
  /** @private {Map.<string, string>} */
187
184
  this.groupIdToCodecsMap_ = new Map();
188
185
 
@@ -575,7 +572,6 @@ shaka.hls.HlsParser = class {
575
572
  playlist, uri, uri, codecs, type, language, primary, name,
576
573
  channelsCount, closedCaptions, characteristics, forced, spatialAudio,
577
574
  mimeType);
578
- goog.asserts.assert(streamInfo != null, 'StreamInfo is null!');
579
575
  this.uriToStreamInfosMap_.set(uri, streamInfo);
580
576
 
581
577
  // Wrap the stream from that stream info with a variant.
@@ -653,17 +649,6 @@ shaka.hls.HlsParser = class {
653
649
  this.syncStreamsWithProgramDateTime_();
654
650
  }
655
651
 
656
- if (this.aesEncrypted_ && variants.length == 0) {
657
- // We do not support AES-128 encryption with HLS yet. Variants is null
658
- // when the playlist is encrypted with AES-128.
659
- shaka.log.info('No stream is created, because we don\'t support AES-128',
660
- 'encryption yet');
661
- throw new shaka.util.Error(
662
- shaka.util.Error.Severity.CRITICAL,
663
- shaka.util.Error.Category.MANIFEST,
664
- shaka.util.Error.Code.HLS_AES_128_ENCRYPTION_NOT_SUPPORTED);
665
- }
666
-
667
652
  // Find the min and max timestamp of the earliest segment in all streams.
668
653
  // Find the minimum duration of all streams as well.
669
654
  let minDuration = Infinity;
@@ -862,8 +847,6 @@ shaka.hls.HlsParser = class {
862
847
  }
863
848
  try {
864
849
  const streamInfo = await this.createStreamInfoFromMediaTag_(tag);
865
- goog.asserts.assert(
866
- streamInfo, 'Should always have a streamInfo for text');
867
850
  return streamInfo.stream;
868
851
  } catch (e) {
869
852
  if (this.config_.hls.ignoreTextStreamFailures) {
@@ -906,8 +889,6 @@ shaka.hls.HlsParser = class {
906
889
  }
907
890
  try {
908
891
  const streamInfo = await this.createStreamInfoFromImageTag_(tag);
909
- goog.asserts.assert(
910
- streamInfo, 'Should always have a streamInfo for image');
911
892
  return streamInfo.stream;
912
893
  } catch (e) {
913
894
  if (this.config_.hls.ignoreImageStreamFailures) {
@@ -959,22 +940,17 @@ shaka.hls.HlsParser = class {
959
940
  const streamInfos = await this.createStreamInfosForVariantTag_(tag,
960
941
  resolution, frameRate);
961
942
 
962
- if (streamInfos) {
963
- goog.asserts.assert(streamInfos.audio.length ||
964
- streamInfos.video.length, 'We should have created a stream!');
965
-
966
- return this.createVariants_(
967
- streamInfos.audio,
968
- streamInfos.video,
969
- bandwidth,
970
- width,
971
- height,
972
- frameRate,
973
- videoRange);
974
- }
975
- // We do not support AES-128 encryption with HLS yet. If the streamInfos
976
- // is null because of AES-128 encryption, do not create variants for that.
977
- return [];
943
+ goog.asserts.assert(streamInfos.audio.length ||
944
+ streamInfos.video.length, 'We should have created a stream!');
945
+
946
+ return this.createVariants_(
947
+ streamInfos.audio,
948
+ streamInfos.video,
949
+ bandwidth,
950
+ width,
951
+ height,
952
+ frameRate,
953
+ videoRange);
978
954
  });
979
955
 
980
956
  const allVariants = await Promise.all(variantsPromises);
@@ -991,7 +967,7 @@ shaka.hls.HlsParser = class {
991
967
  * @param {!shaka.hls.Tag} tag
992
968
  * @param {?string} resolution
993
969
  * @param {?string} frameRate
994
- * @return {!Promise.<?shaka.hls.HlsParser.StreamInfos>}
970
+ * @return {!Promise.<!shaka.hls.HlsParser.StreamInfos>}
995
971
  * @private
996
972
  */
997
973
  async createStreamInfosForVariantTag_(tag, resolution, frameRate) {
@@ -1061,17 +1037,10 @@ shaka.hls.HlsParser = class {
1061
1037
  type = ContentType.VIDEO;
1062
1038
  }
1063
1039
 
1064
- let streamInfo;
1065
1040
  if (!ignoreStream) {
1066
- streamInfo =
1041
+ const streamInfo =
1067
1042
  await this.createStreamInfoFromVariantTag_(tag, allCodecs, type);
1068
- }
1069
- if (streamInfo) {
1070
1043
  res[streamInfo.stream.type] = [streamInfo];
1071
- } else if (streamInfo === null) {
1072
- // Triple-equals for undefined.
1073
- shaka.log.debug('streamInfo is null');
1074
- return null;
1075
1044
  }
1076
1045
  this.filterLegacyCodecs_(res);
1077
1046
  return res;
@@ -1363,7 +1332,7 @@ shaka.hls.HlsParser = class {
1363
1332
  * Parse EXT-X-MEDIA media tag into a Stream object.
1364
1333
  *
1365
1334
  * @param {shaka.hls.Tag} tag
1366
- * @return {!Promise.<?shaka.hls.HlsParser.StreamInfo>}
1335
+ * @return {!Promise.<!shaka.hls.HlsParser.StreamInfo>}
1367
1336
  * @private
1368
1337
  */
1369
1338
  async createStreamInfoFromMediaTag_(tag) {
@@ -1417,9 +1386,6 @@ shaka.hls.HlsParser = class {
1417
1386
  } else {
1418
1387
  this.groupIdToStreamInfosMap_.set(groupId, [streamInfo]);
1419
1388
  }
1420
- if (streamInfo == null) {
1421
- return null;
1422
- }
1423
1389
 
1424
1390
  // TODO: This check is necessary because of the possibility of multiple
1425
1391
  // calls to createStreamInfoFromMediaTag_ before either has resolved.
@@ -1434,7 +1400,7 @@ shaka.hls.HlsParser = class {
1434
1400
  * Parse EXT-X-MEDIA media tag into a Stream object.
1435
1401
  *
1436
1402
  * @param {shaka.hls.Tag} tag
1437
- * @return {!Promise.<?shaka.hls.HlsParser.StreamInfo>}
1403
+ * @return {!Promise.<!shaka.hls.HlsParser.StreamInfo>}
1438
1404
  * @private
1439
1405
  */
1440
1406
  async createStreamInfoFromImageTag_(tag) {
@@ -1462,9 +1428,6 @@ shaka.hls.HlsParser = class {
1462
1428
  verbatimImagePlaylistUri, codecs, type, language, /* primary= */ false,
1463
1429
  name, /* channelsCount= */ null, /* closedCaptions= */ null,
1464
1430
  characteristics, /* forced= */ false, /* spatialAudio= */ false);
1465
- if (streamInfo == null) {
1466
- return null;
1467
- }
1468
1431
 
1469
1432
  // TODO: This check is necessary because of the possibility of multiple
1470
1433
  // calls to createStreamInfoFromImageTag_ before either has resolved.
@@ -1505,7 +1468,7 @@ shaka.hls.HlsParser = class {
1505
1468
  * @param {!shaka.hls.Tag} tag
1506
1469
  * @param {!Array.<string>} allCodecs
1507
1470
  * @param {string} type
1508
- * @return {!Promise.<?shaka.hls.HlsParser.StreamInfo>}
1471
+ * @return {!Promise.<!shaka.hls.HlsParser.StreamInfo>}
1509
1472
  * @private
1510
1473
  */
1511
1474
  async createStreamInfoFromVariantTag_(tag, allCodecs, type) {
@@ -1525,9 +1488,6 @@ shaka.hls.HlsParser = class {
1525
1488
  /* name= */ null, /* channelcount= */ null, closedCaptions,
1526
1489
  /* characteristics= */ null, /* forced= */ false,
1527
1490
  /* spatialAudio= */ false);
1528
- if (streamInfo == null) {
1529
- return null;
1530
- }
1531
1491
  // TODO: This check is necessary because of the possibility of multiple
1532
1492
  // calls to createStreamInfoFromVariantTag_ before either has resolved.
1533
1493
  if (this.uriToStreamInfosMap_.has(verbatimMediaPlaylistUri)) {
@@ -1551,7 +1511,7 @@ shaka.hls.HlsParser = class {
1551
1511
  * @param {?string} characteristics
1552
1512
  * @param {boolean} forced
1553
1513
  * @param {boolean} spatialAudio
1554
- * @return {!Promise.<?shaka.hls.HlsParser.StreamInfo>}
1514
+ * @return {!Promise.<!shaka.hls.HlsParser.StreamInfo>}
1555
1515
  * @private
1556
1516
  */
1557
1517
  async createStreamInfo_(verbatimMediaPlaylistUri, codecs, type, language,
@@ -1591,7 +1551,7 @@ shaka.hls.HlsParser = class {
1591
1551
  * @param {boolean} forced
1592
1552
  * @param {boolean} spatialAudio
1593
1553
  * @param {(string|undefined)} mimeType
1594
- * @return {!Promise.<?shaka.hls.HlsParser.StreamInfo>}
1554
+ * @return {!Promise.<!shaka.hls.HlsParser.StreamInfo>}
1595
1555
  * @private
1596
1556
  */
1597
1557
  async convertParsedPlaylistIntoStreamInfo_(playlist, verbatimMediaPlaylistUri,
@@ -1634,6 +1594,8 @@ shaka.hls.HlsParser = class {
1634
1594
  }
1635
1595
 
1636
1596
  let encrypted = false;
1597
+ let aesEncrypted = false;
1598
+
1637
1599
  /** @type {!Array.<shaka.extern.DrmInfo>}*/
1638
1600
  const drmInfos = [];
1639
1601
  const keyIds = new Set();
@@ -1644,34 +1606,30 @@ shaka.hls.HlsParser = class {
1644
1606
  if (method != 'NONE') {
1645
1607
  encrypted = true;
1646
1608
 
1647
- // We do not support AES-128 encryption with HLS yet. So, do not create
1648
- // StreamInfo for the playlist encrypted with AES-128.
1649
- // TODO: Remove the error message once we add support for AES-128.
1650
1609
  if (method == 'AES-128') {
1651
- shaka.log.warning('Unsupported HLS Encryption', method);
1652
- this.aesEncrypted_ = true;
1653
- return null;
1654
- }
1655
-
1656
- const keyFormat = drmTag.getRequiredAttrValue('KEYFORMAT');
1657
- const drmParser =
1658
- shaka.hls.HlsParser.KEYFORMATS_TO_DRM_PARSERS_[keyFormat];
1659
-
1660
- const drmInfo = drmParser ? drmParser(drmTag, mimeType) : null;
1661
- if (drmInfo) {
1662
- if (drmInfo.keyIds) {
1663
- for (const keyId of drmInfo.keyIds) {
1664
- keyIds.add(keyId);
1610
+ // These keys are handled separately.
1611
+ aesEncrypted = true;
1612
+ } else {
1613
+ const keyFormat = drmTag.getRequiredAttrValue('KEYFORMAT');
1614
+ const drmParser =
1615
+ shaka.hls.HlsParser.KEYFORMATS_TO_DRM_PARSERS_[keyFormat];
1616
+
1617
+ const drmInfo = drmParser ? drmParser(drmTag, mimeType) : null;
1618
+ if (drmInfo) {
1619
+ if (drmInfo.keyIds) {
1620
+ for (const keyId of drmInfo.keyIds) {
1621
+ keyIds.add(keyId);
1622
+ }
1665
1623
  }
1624
+ drmInfos.push(drmInfo);
1625
+ } else {
1626
+ shaka.log.warning('Unsupported HLS KEYFORMAT', keyFormat);
1666
1627
  }
1667
- drmInfos.push(drmInfo);
1668
- } else {
1669
- shaka.log.warning('Unsupported HLS KEYFORMAT', keyFormat);
1670
1628
  }
1671
1629
  }
1672
1630
  }
1673
1631
 
1674
- if (encrypted && !drmInfos.length) {
1632
+ if (encrypted && !drmInfos.length && !aesEncrypted) {
1675
1633
  throw new shaka.util.Error(
1676
1634
  shaka.util.Error.Severity.CRITICAL,
1677
1635
  shaka.util.Error.Category.MANIFEST,
@@ -1686,20 +1644,8 @@ shaka.hls.HlsParser = class {
1686
1644
  /** @type {!Map.<number, number>} */
1687
1645
  const mediaSequenceToStartTime = new Map();
1688
1646
 
1689
- let segments;
1690
- try {
1691
- segments = this.createSegments_(verbatimMediaPlaylistUri,
1692
- playlist, type, mimeType, mediaSequenceToStartTime, mediaVariables,
1693
- codecs);
1694
- } catch (error) {
1695
- if (error.code == shaka.util.Error.Code.HLS_INTERNAL_SKIP_STREAM) {
1696
- shaka.log.alwaysWarn('Skipping unsupported HLS stream',
1697
- mimeType, verbatimMediaPlaylistUri);
1698
- return null;
1699
- }
1700
-
1701
- throw error;
1702
- }
1647
+ const segments = this.createSegments_(verbatimMediaPlaylistUri, playlist,
1648
+ type, mimeType, mediaSequenceToStartTime, mediaVariables, codecs);
1703
1649
 
1704
1650
  const lastEndTime = segments[segments.length - 1].endTime;
1705
1651
  /** @type {!shaka.media.SegmentIndex} */
@@ -1765,6 +1711,78 @@ shaka.hls.HlsParser = class {
1765
1711
  }
1766
1712
 
1767
1713
 
1714
+ /**
1715
+ * @param {!shaka.hls.Tag} drmTag
1716
+ * @param {!shaka.hls.Playlist} playlist
1717
+ * @return {!shaka.extern.HlsAes128Key}
1718
+ * @private
1719
+ */
1720
+ parseAES128DrmTag_(drmTag, playlist) {
1721
+ // Check if the Web Crypto API is available.
1722
+ if (!window.crypto || !window.crypto.subtle) {
1723
+ shaka.log.alwaysWarn('Web Crypto API is not available to decrypt ' +
1724
+ 'AES-128. (Web Crypto only exists in secure origins like https)');
1725
+ throw new shaka.util.Error(
1726
+ shaka.util.Error.Severity.CRITICAL,
1727
+ shaka.util.Error.Category.MANIFEST,
1728
+ shaka.util.Error.Code.NO_WEB_CRYPTO_API);
1729
+ }
1730
+
1731
+ // HLS RFC 8216 Section 5.2:
1732
+ // An EXT-X-KEY tag with a KEYFORMAT of "identity" that does not have an IV
1733
+ // attribute indicates that the Media Sequence Number is to be used as the
1734
+ // IV when decrypting a Media Segment, by putting its big-endian binary
1735
+ // representation into a 16-octet (128-bit) buffer and padding (on the left)
1736
+ // with zeros.
1737
+ let firstMediaSequenceNumber = 0;
1738
+ let iv;
1739
+ const ivHex = drmTag.getAttributeValue('IV', '');
1740
+ if (!ivHex) {
1741
+ // Media Sequence Number will be used as IV.
1742
+ firstMediaSequenceNumber = shaka.hls.Utils.getFirstTagWithNameAsNumber(
1743
+ playlist.tags, 'EXT-X-MEDIA-SEQUENCE', 0);
1744
+ } else {
1745
+ // Exclude 0x at the start of string.
1746
+ iv = shaka.util.Uint8ArrayUtils.fromHex(ivHex.substr(2));
1747
+ if (iv.byteLength != 16) {
1748
+ throw new shaka.util.Error(
1749
+ shaka.util.Error.Severity.CRITICAL,
1750
+ shaka.util.Error.Category.MANIFEST,
1751
+ shaka.util.Error.Code.HLS_AES_128_INVALID_IV_LENGTH);
1752
+ }
1753
+ }
1754
+
1755
+ const keyUri = shaka.hls.Utils.constructAbsoluteUri(
1756
+ playlist.absoluteUri, drmTag.getRequiredAttrValue('URI'));
1757
+
1758
+ const requestType = shaka.net.NetworkingEngine.RequestType.KEY;
1759
+ const request = shaka.net.NetworkingEngine.makeRequest(
1760
+ [keyUri], this.config_.retryParameters);
1761
+
1762
+ const keyInfo = {method: 'AES-128', iv, firstMediaSequenceNumber};
1763
+
1764
+ // Don't download the key object until the segment is parsed, to avoid a
1765
+ // startup delay for long manifests with lots of keys.
1766
+ keyInfo.fetchKey = async () => {
1767
+ const keyResponse = await this.makeNetworkRequest_(request, requestType);
1768
+
1769
+ // keyResponse.status is undefined when URI is "data:text/plain;base64,"
1770
+ if (!keyResponse.data || keyResponse.data.byteLength != 16) {
1771
+ throw new shaka.util.Error(
1772
+ shaka.util.Error.Severity.CRITICAL,
1773
+ shaka.util.Error.Category.MANIFEST,
1774
+ shaka.util.Error.Code.HLS_AES_128_INVALID_KEY_LENGTH);
1775
+ }
1776
+
1777
+ keyInfo.cryptoKey = await window.crypto.subtle.importKey(
1778
+ 'raw', keyResponse.data, 'AES-CBC', true, ['decrypt']);
1779
+ keyInfo.fetchKey = undefined; // No longer needed.
1780
+ };
1781
+
1782
+ return keyInfo;
1783
+ }
1784
+
1785
+
1768
1786
  /**
1769
1787
  * @param {!shaka.hls.Playlist} playlist
1770
1788
  * @private
@@ -1939,12 +1957,13 @@ shaka.hls.HlsParser = class {
1939
1957
  * @param {!Map.<string, string>} variables
1940
1958
  * @param {string} absoluteMediaPlaylistUri
1941
1959
  * @param {string} type
1960
+ * @param {shaka.extern.HlsAes128Key=} hlsAes128Key
1942
1961
  * @return {shaka.media.SegmentReference}
1943
1962
  * @private
1944
1963
  */
1945
1964
  createSegmentReference_(
1946
1965
  initSegmentReference, previousReference, hlsSegment, startTime,
1947
- variables, absoluteMediaPlaylistUri, type) {
1966
+ variables, absoluteMediaPlaylistUri, type, hlsAes128Key) {
1948
1967
  const tags = hlsSegment.tags;
1949
1968
  const absoluteSegmentUri = this.variableSubstitution_(
1950
1969
  hlsSegment.absoluteUri, variables);
@@ -2039,6 +2058,8 @@ shaka.hls.HlsParser = class {
2039
2058
  partialStatus = shaka.media.SegmentReference.Status.MISSING;
2040
2059
  }
2041
2060
 
2061
+ // We do not set the AES-128 key information for partial segments, as we
2062
+ // do not support AES-128 and low-latency at the same time.
2042
2063
  const partial = new shaka.media.SegmentReference(
2043
2064
  pStartTime,
2044
2065
  pEndTime,
@@ -2120,6 +2141,7 @@ shaka.hls.HlsParser = class {
2120
2141
  tileDuration,
2121
2142
  syncTime,
2122
2143
  status,
2144
+ hlsAes128Key,
2123
2145
  );
2124
2146
  }
2125
2147
 
@@ -2186,6 +2208,9 @@ shaka.hls.HlsParser = class {
2186
2208
  /** @type {shaka.media.InitSegmentReference} */
2187
2209
  let initSegmentRef;
2188
2210
 
2211
+ /** @type {shaka.extern.HlsAes128Key|undefined} */
2212
+ let hlsAes128Key = undefined;
2213
+
2189
2214
  // We may need to look at the media itself to determine a segment start
2190
2215
  // time.
2191
2216
  const mediaSequenceNumber = shaka.hls.Utils.getFirstTagWithNameAsNumber(
@@ -2214,6 +2239,14 @@ shaka.hls.HlsParser = class {
2214
2239
  (i == 0) ? firstStartTime : previousReference.endTime;
2215
2240
  position = mediaSequenceNumber + skippedSegments + i;
2216
2241
 
2242
+ // Apply new AES-128 tags as you see them, keeping a running total.
2243
+ for (const drmTag of item.tags) {
2244
+ if (drmTag.name == 'EXT-X-KEY' &&
2245
+ drmTag.getRequiredAttrValue('METHOD') == 'AES-128') {
2246
+ hlsAes128Key = this.parseAES128DrmTag_(drmTag, playlist);
2247
+ }
2248
+ }
2249
+
2217
2250
  mediaSequenceToStartTime.set(position, startTime);
2218
2251
 
2219
2252
  initSegmentRef = this.getInitSegmentReference_(playlist.absoluteUri,
@@ -2238,7 +2271,8 @@ shaka.hls.HlsParser = class {
2238
2271
  startTime,
2239
2272
  variables,
2240
2273
  playlist.absoluteUri,
2241
- type);
2274
+ type,
2275
+ hlsAes128Key);
2242
2276
  previousReference = reference;
2243
2277
 
2244
2278
  if (reference) {
@@ -2611,6 +2645,14 @@ shaka.hls.HlsParser = class {
2611
2645
  shaka.util.Error.Code.HLS_MSE_ENCRYPTED_MP2T_NOT_SUPPORTED);
2612
2646
  }
2613
2647
 
2648
+ if (shaka.util.Platform.isMediaKeysPolyfilled()) {
2649
+ throw new shaka.util.Error(
2650
+ shaka.util.Error.Severity.CRITICAL,
2651
+ shaka.util.Error.Category.MANIFEST,
2652
+ shaka.util.Error.Code
2653
+ .HLS_MSE_ENCRYPTED_LEGACY_APPLE_MEDIA_KEYS_NOT_SUPPORTED);
2654
+ }
2655
+
2614
2656
  /*
2615
2657
  * Even if we're not able to construct initData through the HLS tag, adding
2616
2658
  * a DRMInfo will allow DRM Engine to request a media key system access
@@ -1668,8 +1668,6 @@ shaka.media.DrmEngine = class {
1668
1668
  'com.widevine.alpha',
1669
1669
  'com.microsoft.playready',
1670
1670
  'com.microsoft.playready.recommendation',
1671
- 'com.apple.fps.3_0',
1672
- 'com.apple.fps.2_0',
1673
1671
  'com.apple.fps.1_0',
1674
1672
  'com.apple.fps',
1675
1673
  'com.adobe.primetime',
@@ -529,6 +529,14 @@ shaka.media.SegmentIterator = class {
529
529
  this.currentPartialPosition_ = partialSegmentIndex;
530
530
  }
531
531
 
532
+ /**
533
+ * @return {number}
534
+ * @export
535
+ */
536
+ currentPosition() {
537
+ return this.currentPosition_;
538
+ }
539
+
532
540
  /**
533
541
  * @return {shaka.media.SegmentReference}
534
542
  * @export
@@ -166,12 +166,15 @@ shaka.media.SegmentReference = class {
166
166
  * @param {shaka.media.SegmentReference.Status=} status
167
167
  * The segment status is used to indicate that a segment does not exist or is
168
168
  * not available.
169
+ * @param {?shaka.extern.HlsAes128Key=} hlsAes128Key
170
+ * The segment's AES-128-CBC full segment encryption key and iv.
169
171
  */
170
172
  constructor(
171
173
  startTime, endTime, uris, startByte, endByte, initSegmentReference,
172
174
  timestampOffset, appendWindowStart, appendWindowEnd,
173
175
  partialReferences = [], tilesLayout = '', tileDuration = null,
174
- syncTime = null, status = shaka.media.SegmentReference.Status.AVAILABLE) {
176
+ syncTime = null, status = shaka.media.SegmentReference.Status.AVAILABLE,
177
+ hlsAes128Key = null) {
175
178
  // A preload hinted Partial Segment has the same startTime and endTime.
176
179
  goog.asserts.assert(startTime <= endTime,
177
180
  'startTime must be less than or equal to endTime');
@@ -233,6 +236,9 @@ shaka.media.SegmentReference = class {
233
236
 
234
237
  /** @type {shaka.media.SegmentReference.Status} */
235
238
  this.status = status;
239
+
240
+ /** @type {?shaka.extern.HlsAes128Key} */
241
+ this.hlsAes128Key = hlsAes128Key;
236
242
  }
237
243
 
238
244
  /**
@@ -1233,7 +1233,10 @@ shaka.media.StreamingEngine = class {
1233
1233
  stream.mimeType == 'audio/mp4';
1234
1234
  const isReadableStreamSupported = window.ReadableStream;
1235
1235
  // Enable MP4 low latency streaming with ReadableStream chunked data.
1236
- if (this.config_.lowLatencyMode && isReadableStreamSupported && isMP4) {
1236
+ // Disabled when AES-128 is present, as we cannot decrypt part of a
1237
+ // segment.
1238
+ if (this.config_.lowLatencyMode && isReadableStreamSupported && isMP4 &&
1239
+ !reference.hlsAes128Key) {
1237
1240
  let remaining = new Uint8Array(0);
1238
1241
  const streamDataCallback = async (data) => {
1239
1242
  this.destroyer_.ensureNotDestroyed();
@@ -1273,11 +1276,16 @@ shaka.media.StreamingEngine = class {
1273
1276
  'ReadableStream is not supported by the browser.');
1274
1277
  }
1275
1278
  const fetchSegment = this.fetch_(mediaState, reference);
1276
- const result = await fetchSegment;
1279
+ let result = await fetchSegment;
1277
1280
  this.destroyer_.ensureNotDestroyed();
1278
1281
  if (this.fatalError_) {
1279
1282
  return;
1280
1283
  }
1284
+ if (reference.hlsAes128Key) {
1285
+ goog.asserts.assert(iter, 'mediaState.segmentIterator should exist');
1286
+ result = await this.aes128Decrypt_(result, reference, iter);
1287
+ }
1288
+ this.destroyer_.ensureNotDestroyed();
1281
1289
 
1282
1290
  // If the text stream gets switched between fetch_() and append_(), the
1283
1291
  // new text parser is initialized, but the new init segment is not
@@ -1368,6 +1376,34 @@ shaka.media.StreamingEngine = class {
1368
1376
  }
1369
1377
  }
1370
1378
 
1379
+ /**
1380
+ * @param {!BufferSource} rawResult
1381
+ * @param {!shaka.media.SegmentReference} reference
1382
+ * @param {!shaka.media.SegmentIterator} iter
1383
+ * @return {!Promise.<!BufferSource>} finalResult
1384
+ * @private
1385
+ */
1386
+ async aes128Decrypt_(rawResult, reference, iter) {
1387
+ const key = reference.hlsAes128Key;
1388
+ if (!key.cryptoKey) {
1389
+ goog.asserts.assert(key.fetchKey, 'If AES-128 cryptoKey was not ' +
1390
+ 'preloaded, fetchKey function should be provided');
1391
+ await key.fetchKey();
1392
+ goog.asserts.assert(key.cryptoKey, 'AES-128 cryptoKey should now be set');
1393
+ }
1394
+ let iv = key.iv;
1395
+ if (!iv) {
1396
+ iv = shaka.util.BufferUtils.toUint8(new ArrayBuffer(16));
1397
+ let sequence = key.firstMediaSequenceNumber + iter.currentPosition();
1398
+ for (let i = iv.byteLength - 1; i >= 0; i--) {
1399
+ iv[i] = sequence & 0xff;
1400
+ sequence >>= 8;
1401
+ }
1402
+ }
1403
+ return window.crypto.subtle.decrypt(
1404
+ {name: 'AES-CBC', iv}, key.cryptoKey, rawResult);
1405
+ }
1406
+
1371
1407
 
1372
1408
  /**
1373
1409
  * Clear per-stream error states and retry any failed streams.
@@ -749,6 +749,7 @@ shaka.net.NetworkingEngine.RequestType = {
749
749
  'APP': 3,
750
750
  'TIMING': 4,
751
751
  'SERVER_CERTIFICATE': 5,
752
+ 'KEY': 6,
752
753
  };
753
754
 
754
755
 
package/lib/player.js CHANGED
@@ -1948,6 +1948,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1948
1948
  if (!this.abrManager_ || this.abrManagerFactory_ != abrFactory) {
1949
1949
  this.abrManagerFactory_ = abrFactory;
1950
1950
  this.abrManager_ = abrFactory();
1951
+ if (typeof this.abrManager_.setMediaElement != 'function') {
1952
+ shaka.Deprecate.deprecateFeature(5,
1953
+ 'AbrManager',
1954
+ 'Please use an AbrManager with setMediaElement function.');
1955
+ this.abrManager_.setMediaElement = () => {};
1956
+ }
1951
1957
  this.abrManager_.configure(this.config_.abr);
1952
1958
  }
1953
1959
 
@@ -1970,6 +1976,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1970
1976
  this.abrManager_.init((variant, clearBuffer, safeMargin) => {
1971
1977
  return this.switch_(variant, clearBuffer, safeMargin);
1972
1978
  });
1979
+ this.abrManager_.setMediaElement(mediaElement);
1973
1980
 
1974
1981
  this.playhead_ = this.createPlayhead(has.startTime);
1975
1982
  this.playheadObservers_ = this.createPlayheadObserversForMSE_();
@@ -4452,6 +4459,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4452
4459
  this.stats_.setBandwidthEstimate(estimate);
4453
4460
  }
4454
4461
 
4462
+ if (this.loadMode_ == shaka.Player.LoadMode.SRC_EQUALS) {
4463
+ this.stats_.setResolution(
4464
+ /* width= */ element.videoWidth || NaN,
4465
+ /* height= */ element.videoHeight || NaN);
4466
+ }
4467
+
4455
4468
  return this.stats_.getBlob();
4456
4469
  }
4457
4470
 
@@ -5618,6 +5631,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5618
5631
  return false;
5619
5632
  }
5620
5633
 
5634
+ if (!navigator.onLine) {
5635
+ // Don't restrict variants if we're completely offline, or else we end up
5636
+ // rapidly restricting all of them.
5637
+ return false;
5638
+ }
5639
+
5621
5640
  let maxDisabledTime = this.config_.streaming.maxDisabledTime;
5622
5641
  if (maxDisabledTime == 0) {
5623
5642
  if (error.code == shaka.util.Error.Code.SEGMENT_MISSING) {
@@ -6551,7 +6570,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6551
6570
  * @define {string} A version number taken from git at compile time.
6552
6571
  * @export
6553
6572
  */
6554
- shaka.Player.version = 'v4.1.3-uncompiled';
6573
+ shaka.Player.version = 'v4.2.1-uncompiled';
6555
6574
 
6556
6575
  // Initialize the deprecation system using the version string we just set
6557
6576
  // on the player.