shaka-player 4.1.1 → 4.2.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.
- package/.github/workflows/build-and-test.yaml +67 -15
- package/.github/workflows/compute-incremental-coverage.py +251 -0
- package/.github/workflows/report-incremental-coverage.yaml +39 -0
- package/.github/workflows/selenium-lab-tests.yaml +44 -1
- package/AUTHORS +1 -0
- package/CHANGELOG.md +36 -8
- package/CONTRIBUTORS +3 -0
- package/README.md +20 -6
- package/demo/common/assets.js +25 -0
- package/demo/common/message_ids.js +2 -0
- package/demo/config.js +5 -1
- package/demo/index.html +1 -3
- package/demo/locales/en.json +2 -0
- package/demo/locales/source.json +8 -0
- package/dist/controls.css +1 -1
- package/dist/demo.compiled.debug.js +25 -23
- package/dist/demo.compiled.debug.map +3 -3
- package/dist/demo.compiled.js +25 -23
- package/dist/demo.compiled.map +3 -3
- package/dist/deps.js +6 -5
- package/dist/locales.js +30 -30
- package/dist/receiver.compiled.debug.js +24 -22
- package/dist/receiver.compiled.debug.map +3 -3
- package/dist/receiver.compiled.js +24 -22
- package/dist/receiver.compiled.map +3 -3
- package/dist/shaka-player.compiled.d.ts +69 -5
- package/dist/shaka-player.compiled.debug.d.ts +69 -5
- package/dist/shaka-player.compiled.debug.externs.js +111 -42
- package/dist/shaka-player.compiled.debug.js +352 -301
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +111 -42
- package/dist/shaka-player.compiled.js +823 -802
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +69 -5
- package/dist/shaka-player.ui.debug.d.ts +69 -5
- package/dist/shaka-player.ui.debug.externs.js +111 -42
- package/dist/shaka-player.ui.debug.js +360 -310
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +111 -42
- package/dist/shaka-player.ui.js +980 -957
- package/dist/shaka-player.ui.map +4 -4
- package/docs/tutorials/fairplay.md +149 -0
- package/docs/tutorials/ui.md +6 -6
- package/docs/tutorials/upgrade.md +1 -0
- package/docs/tutorials/welcome.md +6 -3
- package/externs/shaka/abr_manager.js +8 -0
- package/externs/shaka/manifest.js +43 -0
- package/externs/shaka/player.js +13 -2
- package/karma.conf.js +3 -11
- package/lib/abr/simple_abr_manager.js +72 -4
- package/lib/ads/client_side_ad_manager.js +1 -1
- package/lib/dash/dash_parser.js +5 -0
- package/lib/hls/hls_parser.js +123 -27
- package/lib/media/drm_engine.js +1 -3
- package/lib/media/media_source_engine.js +57 -24
- package/lib/media/presentation_timeline.js +24 -1
- package/lib/media/segment_index.js +8 -0
- package/lib/media/segment_reference.js +7 -1
- package/lib/media/streaming_engine.js +42 -6
- package/lib/media/time_ranges_utils.js +46 -0
- package/lib/net/networking_engine.js +1 -0
- package/lib/player.js +8 -6
- package/lib/polyfill/media_capabilities.js +9 -0
- package/lib/polyfill/patchedmediakeys_apple.js +729 -0
- package/lib/polyfill/videoplaybackquality.js +3 -1
- package/lib/text/cue.js +0 -5
- package/lib/text/simple_text_displayer.js +21 -9
- package/lib/text/ui_text_displayer.js +83 -19
- package/lib/text/vtt_text_parser.js +61 -4
- package/lib/util/buffer_utils.js +3 -0
- package/lib/util/error.js +21 -1
- package/lib/util/fairplay_utils.js +100 -2
- package/lib/util/object_utils.js +1 -0
- package/lib/util/platform.js +30 -1
- package/lib/util/player_configuration.js +3 -10
- package/lib/util/stream_utils.js +23 -3
- package/lib/util/string_utils.js +102 -13
- package/maintained-branches.md +15 -0
- package/package.json +3 -5
- package/shaka-player.uncompiled.js +1 -0
- package/test/hls/hls_parser_unit.js +49 -33
- package/test/media/drm_engine_integration.js +24 -8
- package/test/media/media_source_engine_integration.js +24 -10
- package/test/media/media_source_engine_unit.js +53 -38
- package/test/media/streaming_engine_unit.js +107 -32
- package/test/test/util/fake_media_source_engine.js +10 -9
- package/test/test/util/simple_fakes.js +4 -0
- package/test/test/util/streaming_engine_util.js +10 -2
- package/test/text/simple_text_displayer_unit.js +27 -0
- package/test/text/ui_text_displayer_unit.js +65 -0
- package/test/text/vtt_text_parser_unit.js +26 -0
- package/test/util/string_utils_unit.js +46 -8
- package/ui/text_selection.js +2 -2
- package/app-engine/README.md +0 -9
- package/app-engine/demo-version-index/README.md +0 -14
- package/app-engine/demo-version-index/app.yaml +0 -28
- package/app-engine/demo-version-index/generate.py +0 -164
- package/app-engine/demo-version-index/main.py +0 -11
- package/app-engine/demo-version-index/requirements.txt +0 -5
- package/app-engine/demo-version-index/templates/index.html +0 -114
- package/app-engine/shaka-player-demo/README.md +0 -18
- package/app-engine/shaka-player-demo/app.yaml +0 -61
- package/app-engine/shaka-player-demo/main.py +0 -95
- package/app-engine/shaka-player-demo/posters/10yo.png +0 -0
- package/app-engine/shaka-player-demo/posters/10yo.xcf +0 -0
- package/app-engine/shaka-player-demo/posters/1yo.jpg +0 -0
- package/app-engine/shaka-player-demo/posters/2yo.png +0 -0
- package/app-engine/shaka-player-demo/posters/2yo.xcf +0 -0
- package/app-engine/shaka-player-demo/posters/3yo.png +0 -0
- package/app-engine/shaka-player-demo/posters/3yo.xcf +0 -0
- package/app-engine/shaka-player-demo/posters/4yo.png +0 -0
- package/app-engine/shaka-player-demo/posters/4yo.xcf +0 -0
- package/app-engine/shaka-player-demo/posters/5yo.png +0 -0
- package/app-engine/shaka-player-demo/posters/5yo.xcf +0 -0
- package/app-engine/shaka-player-demo/posters/6yo.png +0 -0
- package/app-engine/shaka-player-demo/posters/6yo.xcf +0 -0
- package/app-engine/shaka-player-demo/posters/7yo.png +0 -0
- package/app-engine/shaka-player-demo/posters/7yo.xcf +0 -0
- package/app-engine/shaka-player-demo/posters/8yo.png +0 -0
- package/app-engine/shaka-player-demo/posters/8yo.xcf +0 -0
- package/app-engine/shaka-player-demo/posters/9yo.png +0 -0
- package/app-engine/shaka-player-demo/posters/9yo.xcf +0 -0
- package/app-engine/shaka-player-demo/posters/audioOnly.gif +0 -0
- package/app-engine/shaka-player-demo/posters/audioRickRoll.gif +0 -0
- package/app-engine/shaka-player-demo/posters/colorbars.gif +0 -0
- package/app-engine/shaka-player-demo/posters/fireworks.jpg +0 -0
- package/app-engine/shaka-player-demo/posters/party-hat.png +0 -0
- package/app-engine/shaka-player-demo/posters/sources.txt +0 -46
- package/app-engine/shaka-player-demo/posters/standard.png +0 -0
- package/app-engine/shaka-player-demo/posters/trek.jpg +0 -0
- package/app-engine/shaka-player-demo/posters/valentines.png +0 -0
- package/app-engine/shaka-player-demo/posters/zombie.jpg +0 -0
- package/app-engine/shaka-player-demo/posters.py +0 -110
- package/app-engine/shaka-player-demo/requirements.txt +0 -5
- package/app-engine/shaka-player-demo/time.txt +0 -1
- package/build/README.md +0 -127
- package/build/__init__.py +0 -0
- package/build/all.py +0 -150
- package/build/apps.py +0 -186
- package/build/build.py +0 -392
- package/build/check.py +0 -354
- package/build/checkversion.py +0 -84
- package/build/compiler.py +0 -513
- package/build/conformance.textproto +0 -335
- package/build/docker/Dockerfile +0 -20
- package/build/docker/docker-compose.yml +0 -10
- package/build/docs.py +0 -54
- package/build/eslint-plugin-shaka-rules/arg-comment-spacing.js +0 -72
- package/build/eslint-plugin-shaka-rules/index.js +0 -24
- package/build/eslint-plugin-shaka-rules/package.json +0 -7
- package/build/eslint-plugin-shaka-rules/private.js +0 -109
- package/build/gendeps.py +0 -71
- package/build/generateExterns.js +0 -867
- package/build/generateLocalizations.py +0 -262
- package/build/generateTsDefs.py +0 -137
- package/build/install-linux-prereqs.sh +0 -136
- package/build/license-header +0 -5
- package/build/misspellings.txt +0 -39
- package/build/pixelsChanged.js +0 -35
- package/build/shaka-lab.yaml +0 -161
- package/build/shakaBuildHelpers.py +0 -356
- package/build/stats.py +0 -816
- package/build/subprocessWindowsPatch.py +0 -103
- package/build/test.py +0 -541
- package/build/types/ads +0 -8
- package/build/types/cast +0 -6
- package/build/types/cea +0 -13
- package/build/types/complete +0 -9
- package/build/types/core +0 -108
- package/build/types/dash +0 -8
- package/build/types/hls +0 -9
- package/build/types/manifests +0 -5
- package/build/types/networking +0 -6
- package/build/types/offline +0 -22
- package/build/types/polyfill +0 -18
- package/build/types/text +0 -11
- package/build/types/ui +0 -39
- package/build/updateScreenshots.py +0 -85
- package/build/wrapper.template.js +0 -52
package/lib/hls/hls_parser.js
CHANGED
|
@@ -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
|
|
|
@@ -740,8 +741,14 @@ shaka.hls.HlsParser = class {
|
|
|
740
741
|
for (const stream of streamsToNotify) {
|
|
741
742
|
this.segmentsToNotifyByStream_.push(stream.segmentIndex.references);
|
|
742
743
|
}
|
|
744
|
+
|
|
743
745
|
this.notifySegments_();
|
|
744
746
|
|
|
747
|
+
// This is the first point where we have a meaningful presentation start
|
|
748
|
+
// time, and we need to tell PresentationTimeline that so that it can
|
|
749
|
+
// maintain consistency from here on.
|
|
750
|
+
this.presentationTimeline_.lockStartTime();
|
|
751
|
+
|
|
745
752
|
// This asserts that the live edge is being calculated from segment times.
|
|
746
753
|
// For VOD and event streams, this check should still pass.
|
|
747
754
|
goog.asserts.assert(
|
|
@@ -1638,34 +1645,30 @@ shaka.hls.HlsParser = class {
|
|
|
1638
1645
|
if (method != 'NONE') {
|
|
1639
1646
|
encrypted = true;
|
|
1640
1647
|
|
|
1641
|
-
// We do not support AES-128 encryption with HLS yet. So, do not create
|
|
1642
|
-
// StreamInfo for the playlist encrypted with AES-128.
|
|
1643
|
-
// TODO: Remove the error message once we add support for AES-128.
|
|
1644
1648
|
if (method == 'AES-128') {
|
|
1645
|
-
|
|
1649
|
+
// These keys are handled separately.
|
|
1646
1650
|
this.aesEncrypted_ = true;
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
keyIds.add(keyId);
|
|
1651
|
+
} else {
|
|
1652
|
+
const keyFormat = drmTag.getRequiredAttrValue('KEYFORMAT');
|
|
1653
|
+
const drmParser =
|
|
1654
|
+
shaka.hls.HlsParser.KEYFORMATS_TO_DRM_PARSERS_[keyFormat];
|
|
1655
|
+
|
|
1656
|
+
const drmInfo = drmParser ? drmParser(drmTag, mimeType) : null;
|
|
1657
|
+
if (drmInfo) {
|
|
1658
|
+
if (drmInfo.keyIds) {
|
|
1659
|
+
for (const keyId of drmInfo.keyIds) {
|
|
1660
|
+
keyIds.add(keyId);
|
|
1661
|
+
}
|
|
1659
1662
|
}
|
|
1663
|
+
drmInfos.push(drmInfo);
|
|
1664
|
+
} else {
|
|
1665
|
+
shaka.log.warning('Unsupported HLS KEYFORMAT', keyFormat);
|
|
1660
1666
|
}
|
|
1661
|
-
drmInfos.push(drmInfo);
|
|
1662
|
-
} else {
|
|
1663
|
-
shaka.log.warning('Unsupported HLS KEYFORMAT', keyFormat);
|
|
1664
1667
|
}
|
|
1665
1668
|
}
|
|
1666
1669
|
}
|
|
1667
1670
|
|
|
1668
|
-
if (encrypted && !drmInfos.length) {
|
|
1671
|
+
if (encrypted && !drmInfos.length && !this.aesEncrypted_) {
|
|
1669
1672
|
throw new shaka.util.Error(
|
|
1670
1673
|
shaka.util.Error.Severity.CRITICAL,
|
|
1671
1674
|
shaka.util.Error.Category.MANIFEST,
|
|
@@ -1682,9 +1685,8 @@ shaka.hls.HlsParser = class {
|
|
|
1682
1685
|
|
|
1683
1686
|
let segments;
|
|
1684
1687
|
try {
|
|
1685
|
-
segments = this.createSegments_(verbatimMediaPlaylistUri,
|
|
1686
|
-
|
|
1687
|
-
codecs);
|
|
1688
|
+
segments = this.createSegments_(verbatimMediaPlaylistUri, playlist, type,
|
|
1689
|
+
mimeType, mediaSequenceToStartTime, mediaVariables, codecs);
|
|
1688
1690
|
} catch (error) {
|
|
1689
1691
|
if (error.code == shaka.util.Error.Code.HLS_INTERNAL_SKIP_STREAM) {
|
|
1690
1692
|
shaka.log.alwaysWarn('Skipping unsupported HLS stream',
|
|
@@ -1759,6 +1761,78 @@ shaka.hls.HlsParser = class {
|
|
|
1759
1761
|
}
|
|
1760
1762
|
|
|
1761
1763
|
|
|
1764
|
+
/**
|
|
1765
|
+
* @param {!shaka.hls.Tag} drmTag
|
|
1766
|
+
* @param {!shaka.hls.Playlist} playlist
|
|
1767
|
+
* @return {!shaka.extern.HlsAes128Key}
|
|
1768
|
+
* @private
|
|
1769
|
+
*/
|
|
1770
|
+
parseAES128DrmTag_(drmTag, playlist) {
|
|
1771
|
+
// Check if the Web Crypto API is available.
|
|
1772
|
+
if (!window.crypto || !window.crypto.subtle) {
|
|
1773
|
+
shaka.log.alwaysWarn('Web Crypto API is not available to decrypt ' +
|
|
1774
|
+
'AES-128. (Web Crypto only exists in secure origins like https)');
|
|
1775
|
+
throw new shaka.util.Error(
|
|
1776
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
1777
|
+
shaka.util.Error.Category.MANIFEST,
|
|
1778
|
+
shaka.util.Error.Code.NO_WEB_CRYPTO_API);
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
// HLS RFC 8216 Section 5.2:
|
|
1782
|
+
// An EXT-X-KEY tag with a KEYFORMAT of "identity" that does not have an IV
|
|
1783
|
+
// attribute indicates that the Media Sequence Number is to be used as the
|
|
1784
|
+
// IV when decrypting a Media Segment, by putting its big-endian binary
|
|
1785
|
+
// representation into a 16-octet (128-bit) buffer and padding (on the left)
|
|
1786
|
+
// with zeros.
|
|
1787
|
+
let firstMediaSequenceNumber = 0;
|
|
1788
|
+
let iv;
|
|
1789
|
+
const ivHex = drmTag.getAttributeValue('IV', '');
|
|
1790
|
+
if (!ivHex) {
|
|
1791
|
+
// Media Sequence Number will be used as IV.
|
|
1792
|
+
firstMediaSequenceNumber = shaka.hls.Utils.getFirstTagWithNameAsNumber(
|
|
1793
|
+
playlist.tags, 'EXT-X-MEDIA-SEQUENCE', 0);
|
|
1794
|
+
} else {
|
|
1795
|
+
// Exclude 0x at the start of string.
|
|
1796
|
+
iv = shaka.util.Uint8ArrayUtils.fromHex(ivHex.substr(2));
|
|
1797
|
+
if (iv.byteLength != 16) {
|
|
1798
|
+
throw new shaka.util.Error(
|
|
1799
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
1800
|
+
shaka.util.Error.Category.MANIFEST,
|
|
1801
|
+
shaka.util.Error.Code.HLS_AES_128_INVALID_IV_LENGTH);
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
const keyUri = shaka.hls.Utils.constructAbsoluteUri(
|
|
1806
|
+
playlist.absoluteUri, drmTag.getRequiredAttrValue('URI'));
|
|
1807
|
+
|
|
1808
|
+
const requestType = shaka.net.NetworkingEngine.RequestType.KEY;
|
|
1809
|
+
const request = shaka.net.NetworkingEngine.makeRequest(
|
|
1810
|
+
[keyUri], this.config_.retryParameters);
|
|
1811
|
+
|
|
1812
|
+
const keyInfo = {method: 'AES-128', iv, firstMediaSequenceNumber};
|
|
1813
|
+
|
|
1814
|
+
// Don't download the key object until the segment is parsed, to avoid a
|
|
1815
|
+
// startup delay for long manifests with lots of keys.
|
|
1816
|
+
keyInfo.fetchKey = async () => {
|
|
1817
|
+
const keyResponse = await this.makeNetworkRequest_(request, requestType);
|
|
1818
|
+
|
|
1819
|
+
// keyResponse.status is undefined when URI is "data:text/plain;base64,"
|
|
1820
|
+
if (!keyResponse.data || keyResponse.data.byteLength != 16) {
|
|
1821
|
+
throw new shaka.util.Error(
|
|
1822
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
1823
|
+
shaka.util.Error.Category.MANIFEST,
|
|
1824
|
+
shaka.util.Error.Code.HLS_AES_128_INVALID_KEY_LENGTH);
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
keyInfo.cryptoKey = await window.crypto.subtle.importKey(
|
|
1828
|
+
'raw', keyResponse.data, 'AES-CBC', true, ['decrypt']);
|
|
1829
|
+
keyInfo.fetchKey = undefined; // No longer needed.
|
|
1830
|
+
};
|
|
1831
|
+
|
|
1832
|
+
return keyInfo;
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
|
|
1762
1836
|
/**
|
|
1763
1837
|
* @param {!shaka.hls.Playlist} playlist
|
|
1764
1838
|
* @private
|
|
@@ -1859,8 +1933,6 @@ shaka.hls.HlsParser = class {
|
|
|
1859
1933
|
/* presentationStartTime= */ null, /* delay= */ 0);
|
|
1860
1934
|
this.presentationTimeline_.setStatic(true);
|
|
1861
1935
|
}
|
|
1862
|
-
|
|
1863
|
-
this.notifySegments_();
|
|
1864
1936
|
}
|
|
1865
1937
|
|
|
1866
1938
|
/**
|
|
@@ -1935,12 +2007,13 @@ shaka.hls.HlsParser = class {
|
|
|
1935
2007
|
* @param {!Map.<string, string>} variables
|
|
1936
2008
|
* @param {string} absoluteMediaPlaylistUri
|
|
1937
2009
|
* @param {string} type
|
|
2010
|
+
* @param {shaka.extern.HlsAes128Key=} hlsAes128Key
|
|
1938
2011
|
* @return {shaka.media.SegmentReference}
|
|
1939
2012
|
* @private
|
|
1940
2013
|
*/
|
|
1941
2014
|
createSegmentReference_(
|
|
1942
2015
|
initSegmentReference, previousReference, hlsSegment, startTime,
|
|
1943
|
-
variables, absoluteMediaPlaylistUri, type) {
|
|
2016
|
+
variables, absoluteMediaPlaylistUri, type, hlsAes128Key) {
|
|
1944
2017
|
const tags = hlsSegment.tags;
|
|
1945
2018
|
const absoluteSegmentUri = this.variableSubstitution_(
|
|
1946
2019
|
hlsSegment.absoluteUri, variables);
|
|
@@ -2035,6 +2108,8 @@ shaka.hls.HlsParser = class {
|
|
|
2035
2108
|
partialStatus = shaka.media.SegmentReference.Status.MISSING;
|
|
2036
2109
|
}
|
|
2037
2110
|
|
|
2111
|
+
// We do not set the AES-128 key information for partial segments, as we
|
|
2112
|
+
// do not support AES-128 and low-latency at the same time.
|
|
2038
2113
|
const partial = new shaka.media.SegmentReference(
|
|
2039
2114
|
pStartTime,
|
|
2040
2115
|
pEndTime,
|
|
@@ -2116,6 +2191,7 @@ shaka.hls.HlsParser = class {
|
|
|
2116
2191
|
tileDuration,
|
|
2117
2192
|
syncTime,
|
|
2118
2193
|
status,
|
|
2194
|
+
hlsAes128Key,
|
|
2119
2195
|
);
|
|
2120
2196
|
}
|
|
2121
2197
|
|
|
@@ -2182,6 +2258,9 @@ shaka.hls.HlsParser = class {
|
|
|
2182
2258
|
/** @type {shaka.media.InitSegmentReference} */
|
|
2183
2259
|
let initSegmentRef;
|
|
2184
2260
|
|
|
2261
|
+
/** @type {shaka.extern.HlsAes128Key|undefined} */
|
|
2262
|
+
let hlsAes128Key = undefined;
|
|
2263
|
+
|
|
2185
2264
|
// We may need to look at the media itself to determine a segment start
|
|
2186
2265
|
// time.
|
|
2187
2266
|
const mediaSequenceNumber = shaka.hls.Utils.getFirstTagWithNameAsNumber(
|
|
@@ -2210,6 +2289,14 @@ shaka.hls.HlsParser = class {
|
|
|
2210
2289
|
(i == 0) ? firstStartTime : previousReference.endTime;
|
|
2211
2290
|
position = mediaSequenceNumber + skippedSegments + i;
|
|
2212
2291
|
|
|
2292
|
+
// Apply new AES-128 tags as you see them, keeping a running total.
|
|
2293
|
+
for (const drmTag of item.tags) {
|
|
2294
|
+
if (drmTag.name == 'EXT-X-KEY' &&
|
|
2295
|
+
drmTag.getRequiredAttrValue('METHOD') == 'AES-128') {
|
|
2296
|
+
hlsAes128Key = this.parseAES128DrmTag_(drmTag, playlist);
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2213
2300
|
mediaSequenceToStartTime.set(position, startTime);
|
|
2214
2301
|
|
|
2215
2302
|
initSegmentRef = this.getInitSegmentReference_(playlist.absoluteUri,
|
|
@@ -2234,7 +2321,8 @@ shaka.hls.HlsParser = class {
|
|
|
2234
2321
|
startTime,
|
|
2235
2322
|
variables,
|
|
2236
2323
|
playlist.absoluteUri,
|
|
2237
|
-
type
|
|
2324
|
+
type,
|
|
2325
|
+
hlsAes128Key);
|
|
2238
2326
|
previousReference = reference;
|
|
2239
2327
|
|
|
2240
2328
|
if (reference) {
|
|
@@ -2607,6 +2695,14 @@ shaka.hls.HlsParser = class {
|
|
|
2607
2695
|
shaka.util.Error.Code.HLS_MSE_ENCRYPTED_MP2T_NOT_SUPPORTED);
|
|
2608
2696
|
}
|
|
2609
2697
|
|
|
2698
|
+
if (shaka.util.Platform.isMediaKeysPolyfilled()) {
|
|
2699
|
+
throw new shaka.util.Error(
|
|
2700
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
2701
|
+
shaka.util.Error.Category.MANIFEST,
|
|
2702
|
+
shaka.util.Error.Code
|
|
2703
|
+
.HLS_MSE_ENCRYPTED_LEGACY_APPLE_MEDIA_KEYS_NOT_SUPPORTED);
|
|
2704
|
+
}
|
|
2705
|
+
|
|
2610
2706
|
/*
|
|
2611
2707
|
* Even if we're not able to construct initData through the HLS tag, adding
|
|
2612
2708
|
* a DRMInfo will allow DRM Engine to request a media key system access
|
package/lib/media/drm_engine.js
CHANGED
|
@@ -1548,7 +1548,7 @@ shaka.media.DrmEngine = class {
|
|
|
1548
1548
|
if (shaka.media.DrmEngine.isPlayReadyKeySystem(
|
|
1549
1549
|
this.currentDrmInfo_.keySystem) &&
|
|
1550
1550
|
keyId.byteLength == 16 &&
|
|
1551
|
-
shaka.util.Platform.isEdge()) {
|
|
1551
|
+
(shaka.util.Platform.isEdge() || shaka.util.Platform.isPS4())) {
|
|
1552
1552
|
// Read out some fields in little-endian:
|
|
1553
1553
|
const dataView = shaka.util.BufferUtils.toDataView(keyId);
|
|
1554
1554
|
const part0 = dataView.getUint32(0, /* LE= */ true);
|
|
@@ -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',
|
|
@@ -10,6 +10,7 @@ goog.require('goog.asserts');
|
|
|
10
10
|
goog.require('shaka.log');
|
|
11
11
|
goog.require('shaka.media.ContentWorkarounds');
|
|
12
12
|
goog.require('shaka.media.IClosedCaptionParser');
|
|
13
|
+
goog.require('shaka.media.SegmentReference');
|
|
13
14
|
goog.require('shaka.media.TimeRangesUtils');
|
|
14
15
|
goog.require('shaka.media.Transmuxer');
|
|
15
16
|
goog.require('shaka.text.TextEngine');
|
|
@@ -466,13 +467,13 @@ shaka.media.MediaSourceEngine = class {
|
|
|
466
467
|
*/
|
|
467
468
|
getBufferedInfo() {
|
|
468
469
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
469
|
-
const
|
|
470
|
+
const TimeRangesUtils = shaka.media.TimeRangesUtils;
|
|
470
471
|
|
|
471
472
|
const info = {
|
|
472
|
-
total:
|
|
473
|
-
audio:
|
|
473
|
+
total: TimeRangesUtils.getBufferedInfo(this.video_.buffered),
|
|
474
|
+
audio: TimeRangesUtils.getBufferedInfo(
|
|
474
475
|
this.getBuffered_(ContentType.AUDIO)),
|
|
475
|
-
video:
|
|
476
|
+
video: TimeRangesUtils.getBufferedInfo(
|
|
476
477
|
this.getBuffered_(ContentType.VIDEO)),
|
|
477
478
|
text: [],
|
|
478
479
|
};
|
|
@@ -516,15 +517,14 @@ shaka.media.MediaSourceEngine = class {
|
|
|
516
517
|
*
|
|
517
518
|
* @param {shaka.util.ManifestParserUtils.ContentType} contentType
|
|
518
519
|
* @param {!BufferSource} data
|
|
519
|
-
* @param {?
|
|
520
|
-
*
|
|
520
|
+
* @param {?shaka.media.SegmentReference} reference The segment reference
|
|
521
|
+
* we are appending, or null for init segments
|
|
521
522
|
* @param {?boolean} hasClosedCaptions True if the buffer contains CEA closed
|
|
522
|
-
*
|
|
523
|
+
* captions
|
|
523
524
|
* @param {boolean=} seeked True if we just seeked
|
|
524
525
|
* @return {!Promise}
|
|
525
526
|
*/
|
|
526
|
-
async appendBuffer(
|
|
527
|
-
contentType, data, startTime, endTime, hasClosedCaptions, seeked) {
|
|
527
|
+
async appendBuffer(contentType, data, reference, hasClosedCaptions, seeked) {
|
|
528
528
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
529
529
|
|
|
530
530
|
if (contentType == ContentType.TEXT) {
|
|
@@ -533,7 +533,10 @@ shaka.media.MediaSourceEngine = class {
|
|
|
533
533
|
const offset = await this.textSequenceModeOffset_;
|
|
534
534
|
this.textEngine_.setTimestampOffset(offset);
|
|
535
535
|
}
|
|
536
|
-
await this.textEngine_.appendBuffer(
|
|
536
|
+
await this.textEngine_.appendBuffer(
|
|
537
|
+
data,
|
|
538
|
+
reference ? reference.startTime : null,
|
|
539
|
+
reference ? reference.endTime : null);
|
|
537
540
|
return;
|
|
538
541
|
}
|
|
539
542
|
|
|
@@ -549,7 +552,10 @@ shaka.media.MediaSourceEngine = class {
|
|
|
549
552
|
if (transmuxedData.metadata) {
|
|
550
553
|
const timestampOffset =
|
|
551
554
|
this.sourceBuffers_[contentType].timestampOffset;
|
|
552
|
-
this.onMetadata_(
|
|
555
|
+
this.onMetadata_(
|
|
556
|
+
transmuxedData.metadata,
|
|
557
|
+
timestampOffset,
|
|
558
|
+
reference ? reference.endTime : null);
|
|
553
559
|
}
|
|
554
560
|
// This doesn't work for native TS support (ex. Edge/Chromecast),
|
|
555
561
|
// since no transmuxing is needed for native TS.
|
|
@@ -559,7 +565,10 @@ shaka.media.MediaSourceEngine = class {
|
|
|
559
565
|
const closedCaptions = this.textEngine_
|
|
560
566
|
.convertMuxjsCaptionsToShakaCaptions(transmuxedData.captions);
|
|
561
567
|
this.textEngine_.storeAndAppendClosedCaptions(
|
|
562
|
-
closedCaptions,
|
|
568
|
+
closedCaptions,
|
|
569
|
+
reference ? reference.startTime : null,
|
|
570
|
+
reference ? reference.endTime : null,
|
|
571
|
+
videoOffset);
|
|
563
572
|
}
|
|
564
573
|
|
|
565
574
|
data = transmuxedData.data;
|
|
@@ -569,7 +578,7 @@ shaka.media.MediaSourceEngine = class {
|
|
|
569
578
|
}
|
|
570
579
|
// If it is the init segment for closed captions, initialize the closed
|
|
571
580
|
// caption parser.
|
|
572
|
-
if (
|
|
581
|
+
if (!reference) {
|
|
573
582
|
this.captionParser_.init(data);
|
|
574
583
|
} else {
|
|
575
584
|
const closedCaptions = this.captionParser_.parseFrom(data);
|
|
@@ -577,18 +586,21 @@ shaka.media.MediaSourceEngine = class {
|
|
|
577
586
|
const videoOffset =
|
|
578
587
|
this.sourceBuffers_[ContentType.VIDEO].timestampOffset;
|
|
579
588
|
this.textEngine_.storeAndAppendClosedCaptions(
|
|
580
|
-
closedCaptions,
|
|
589
|
+
closedCaptions,
|
|
590
|
+
reference.startTime,
|
|
591
|
+
reference.endTime,
|
|
592
|
+
videoOffset);
|
|
581
593
|
}
|
|
582
594
|
}
|
|
583
595
|
}
|
|
584
596
|
|
|
585
|
-
data = this.workAroundBrokenPlatforms_(
|
|
597
|
+
data = this.workAroundBrokenPlatforms_(
|
|
598
|
+
data, reference ? reference.startTime : null, contentType);
|
|
586
599
|
|
|
587
600
|
const sourceBuffer = this.sourceBuffers_[contentType];
|
|
588
601
|
const SEQUENCE = shaka.media.MediaSourceEngine.SourceBufferMode_.SEQUENCE;
|
|
589
602
|
|
|
590
|
-
if (this.sequenceMode_ && sourceBuffer.mode != SEQUENCE &&
|
|
591
|
-
startTime != null) {
|
|
603
|
+
if (this.sequenceMode_ && sourceBuffer.mode != SEQUENCE && reference) {
|
|
592
604
|
// This is the first media segment to be appended to a SourceBuffer in
|
|
593
605
|
// sequence mode. We set the mode late so that we can trick MediaSource
|
|
594
606
|
// into extracting a timestamp for us to align text segments in sequence
|
|
@@ -623,7 +635,7 @@ shaka.media.MediaSourceEngine = class {
|
|
|
623
635
|
// segments.
|
|
624
636
|
const mediaStartTime = shaka.media.TimeRangesUtils.bufferStart(
|
|
625
637
|
this.getBuffered_(contentType));
|
|
626
|
-
const textOffset = (startTime || 0) - (mediaStartTime || 0);
|
|
638
|
+
const textOffset = (reference.startTime || 0) - (mediaStartTime || 0);
|
|
627
639
|
this.textSequenceModeOffset_.resolve(textOffset);
|
|
628
640
|
|
|
629
641
|
// Finally, clear the buffer.
|
|
@@ -636,22 +648,43 @@ shaka.media.MediaSourceEngine = class {
|
|
|
636
648
|
sourceBuffer.mode = SEQUENCE;
|
|
637
649
|
}
|
|
638
650
|
|
|
639
|
-
if (
|
|
640
|
-
contentType != ContentType.TEXT) {
|
|
651
|
+
if (reference && this.sequenceMode_ && contentType != ContentType.TEXT) {
|
|
641
652
|
// In sequence mode, for non-text streams, if we just cleared the buffer
|
|
642
653
|
// and are performing an unbuffered seek, we need to set a new
|
|
643
654
|
// timestampOffset on the sourceBuffer.
|
|
644
655
|
if (seeked) {
|
|
645
|
-
const timestampOffset =
|
|
656
|
+
const timestampOffset = reference.startTime;
|
|
646
657
|
this.enqueueOperation_(
|
|
647
658
|
contentType,
|
|
648
659
|
() => this.setTimestampOffset_(contentType, timestampOffset));
|
|
649
660
|
}
|
|
650
661
|
}
|
|
651
662
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
663
|
+
let bufferedBefore = null;
|
|
664
|
+
|
|
665
|
+
await this.enqueueOperation_(contentType, () => {
|
|
666
|
+
if (goog.DEBUG && reference) {
|
|
667
|
+
bufferedBefore = this.getBuffered_(contentType);
|
|
668
|
+
}
|
|
669
|
+
this.append_(contentType, data);
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
if (goog.DEBUG && reference) {
|
|
673
|
+
const bufferedAfter = this.getBuffered_(contentType);
|
|
674
|
+
const newBuffered = shaka.media.TimeRangesUtils.computeAddedRange(
|
|
675
|
+
bufferedBefore, bufferedAfter);
|
|
676
|
+
if (newBuffered) {
|
|
677
|
+
const segmentDuration = reference.endTime - reference.startTime;
|
|
678
|
+
if (Math.abs(newBuffered.start - reference.startTime) >
|
|
679
|
+
segmentDuration / 2) {
|
|
680
|
+
shaka.log.error('Possible encoding problem detected!',
|
|
681
|
+
'Unexpected buffered range for reference', reference,
|
|
682
|
+
'from URIs', reference.getUris(),
|
|
683
|
+
'should be', {start: reference.startTime, end: reference.endTime},
|
|
684
|
+
'but got', newBuffered);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
655
688
|
}
|
|
656
689
|
|
|
657
690
|
/**
|
|
@@ -93,6 +93,9 @@ shaka.media.PresentationTimeline = class {
|
|
|
93
93
|
* @private {number}
|
|
94
94
|
*/
|
|
95
95
|
this.availabilityTimeOffset_ = 0;
|
|
96
|
+
|
|
97
|
+
/** @private {boolean} */
|
|
98
|
+
this.startTimeLocked_ = false;
|
|
96
99
|
}
|
|
97
100
|
|
|
98
101
|
|
|
@@ -229,7 +232,8 @@ shaka.media.PresentationTimeline = class {
|
|
|
229
232
|
this.maxSegmentEndTime_ =
|
|
230
233
|
Math.max(this.maxSegmentEndTime_, lastReferenceEndTime);
|
|
231
234
|
|
|
232
|
-
if (this.presentationStartTime_ != null && this.autoCorrectDrift_
|
|
235
|
+
if (this.presentationStartTime_ != null && this.autoCorrectDrift_ &&
|
|
236
|
+
!this.startTimeLocked_) {
|
|
233
237
|
// Since we have explicit segment end times, calculate a presentation
|
|
234
238
|
// start based on them. This start time accounts for drift.
|
|
235
239
|
// Date.now() is in milliseconds, from which we compute "now" in seconds.
|
|
@@ -243,6 +247,25 @@ shaka.media.PresentationTimeline = class {
|
|
|
243
247
|
}
|
|
244
248
|
|
|
245
249
|
|
|
250
|
+
/**
|
|
251
|
+
* Lock the presentation timeline's start time. After this is called, no
|
|
252
|
+
* further adjustments to presentationStartTime_ will be permitted.
|
|
253
|
+
*
|
|
254
|
+
* This should be called after all Periods have been parsed, and all calls to
|
|
255
|
+
* notifySegments() from the initial manifest parse have been made.
|
|
256
|
+
*
|
|
257
|
+
* Without this, we can get assertion failures in SegmentIndex for certain
|
|
258
|
+
* DAI content. If DAI adds ad segments to the manifest faster than
|
|
259
|
+
* real-time, adjustments to presentationStartTime_ can cause availability
|
|
260
|
+
* windows to jump around on updates.
|
|
261
|
+
*
|
|
262
|
+
* @export
|
|
263
|
+
*/
|
|
264
|
+
lockStartTime() {
|
|
265
|
+
this.startTimeLocked_ = true;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
|
|
246
269
|
/**
|
|
247
270
|
* Gives PresentationTimeline a Stream's minimum segment start time.
|
|
248
271
|
*
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
@@ -1555,8 +1591,8 @@ shaka.media.StreamingEngine = class {
|
|
|
1555
1591
|
const hasClosedCaptions = mediaState.stream.closedCaptions &&
|
|
1556
1592
|
mediaState.stream.closedCaptions.size > 0;
|
|
1557
1593
|
await this.playerInterface_.mediaSourceEngine.appendBuffer(
|
|
1558
|
-
mediaState.type, initSegment, /*
|
|
1559
|
-
|
|
1594
|
+
mediaState.type, initSegment, /* reference= */ null,
|
|
1595
|
+
hasClosedCaptions);
|
|
1560
1596
|
} catch (error) {
|
|
1561
1597
|
mediaState.lastInitSegmentReference = null;
|
|
1562
1598
|
throw error;
|
|
@@ -1609,8 +1645,7 @@ shaka.media.StreamingEngine = class {
|
|
|
1609
1645
|
await this.playerInterface_.mediaSourceEngine.appendBuffer(
|
|
1610
1646
|
mediaState.type,
|
|
1611
1647
|
segment,
|
|
1612
|
-
reference
|
|
1613
|
-
reference.endTime,
|
|
1648
|
+
reference,
|
|
1614
1649
|
hasClosedCaptions,
|
|
1615
1650
|
seeked);
|
|
1616
1651
|
this.destroyer_.ensureNotDestroyed();
|
|
@@ -1791,6 +1826,7 @@ shaka.media.StreamingEngine = class {
|
|
|
1791
1826
|
*
|
|
1792
1827
|
* @return {!Promise.<BufferSource>}
|
|
1793
1828
|
* @private
|
|
1829
|
+
* @suppress {strictMissingProperties}
|
|
1794
1830
|
*/
|
|
1795
1831
|
async fetch_(mediaState, reference, streamDataCallback) {
|
|
1796
1832
|
const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
|
|
@@ -155,4 +155,50 @@ shaka.media.TimeRangesUtils = class {
|
|
|
155
155
|
}
|
|
156
156
|
return ret;
|
|
157
157
|
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* This operation can be potentially EXPENSIVE and should only be done in
|
|
161
|
+
* debug builds for debugging purposes.
|
|
162
|
+
*
|
|
163
|
+
* @param {TimeRanges} oldRanges
|
|
164
|
+
* @param {TimeRanges} newRanges
|
|
165
|
+
* @return {?shaka.extern.BufferedRange} The last added range,
|
|
166
|
+
* chronologically by presentation time.
|
|
167
|
+
*/
|
|
168
|
+
static computeAddedRange(oldRanges, newRanges) {
|
|
169
|
+
const TimeRangesUtils = shaka.media.TimeRangesUtils;
|
|
170
|
+
|
|
171
|
+
if (!oldRanges || !oldRanges.length) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
if (!newRanges || !newRanges.length) {
|
|
175
|
+
return TimeRangesUtils.getBufferedInfo(newRanges).pop();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const newRangesReversed =
|
|
179
|
+
TimeRangesUtils.getBufferedInfo(newRanges).reverse();
|
|
180
|
+
const oldRangesReversed =
|
|
181
|
+
TimeRangesUtils.getBufferedInfo(oldRanges).reverse();
|
|
182
|
+
for (const newRange of newRangesReversed) {
|
|
183
|
+
let foundOverlap = false;
|
|
184
|
+
|
|
185
|
+
for (const oldRange of oldRangesReversed) {
|
|
186
|
+
if (oldRange.end >= newRange.start && oldRange.end <= newRange.end) {
|
|
187
|
+
foundOverlap = true;
|
|
188
|
+
|
|
189
|
+
// If the new range goes beyond the corresponding old one, the
|
|
190
|
+
// difference is newly-added.
|
|
191
|
+
if (newRange.end > oldRange.end) {
|
|
192
|
+
return {start: oldRange.end, end: newRange.end};
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (!foundOverlap) {
|
|
198
|
+
return newRange;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
158
204
|
};
|