shaka-player 3.2.2 → 3.3.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/ISSUE_TEMPLATE/bug_report.md +1 -1
- package/.github/ISSUE_TEMPLATE/feature_request.md +1 -1
- package/.github/ISSUE_TEMPLATE/question.md +1 -1
- package/AUTHORS +6 -5
- package/CHANGELOG.md +247 -0
- package/CONTRIBUTORS +4 -2
- package/build/types/core +3 -0
- package/build/types/offline +1 -0
- package/build/types/polyfill +1 -0
- package/build/types/ui +2 -0
- package/demo/common/assets.js +18 -0
- package/demo/common/message_ids.js +13 -1
- package/demo/config.js +31 -1
- package/demo/index.html +1 -1
- package/demo/locales/en.json +12 -0
- package/demo/locales/source.json +48 -0
- package/dist/controls.css +26 -2
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +19 -16
- package/dist/demo.compiled.debug.map +3 -3
- package/dist/demo.compiled.js +19 -16
- package/dist/demo.compiled.map +3 -3
- package/dist/deps.js +18 -11
- package/dist/locales.js +28 -26
- package/dist/receiver.compiled.debug.js +11 -9
- package/dist/receiver.compiled.debug.map +3 -3
- package/dist/receiver.compiled.js +11 -9
- package/dist/receiver.compiled.map +3 -3
- package/dist/shaka-player.compiled.d.ts +205 -15
- package/dist/shaka-player.compiled.debug.d.ts +205 -15
- package/dist/shaka-player.compiled.debug.externs.js +256 -6
- package/dist/shaka-player.compiled.debug.js +406 -354
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +256 -6
- package/dist/shaka-player.compiled.js +826 -800
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +221 -16
- package/dist/shaka-player.ui.debug.d.ts +221 -16
- package/dist/shaka-player.ui.debug.externs.js +283 -6
- package/dist/shaka-player.ui.debug.js +547 -476
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +283 -6
- package/dist/shaka-player.ui.js +1019 -980
- package/dist/shaka-player.ui.map +4 -4
- package/docs/design/bg-fetch-after.gv +29 -0
- package/docs/design/bg-fetch-after.gv.png +0 -0
- package/docs/design/bg-fetch-before.gv +18 -0
- package/docs/design/bg-fetch-before.gv.png +0 -0
- package/docs/design/bg-fetch.md +134 -0
- package/docs/tutorials/blob-url.md +15 -0
- package/docs/tutorials/index.json +1 -0
- package/docs/tutorials/ui-customization.md +66 -0
- package/docs/tutorials/ui.md +5 -5
- package/externs/ima.js +3 -0
- package/externs/shaka/ads.js +5 -0
- package/externs/shaka/manifest.js +12 -1
- package/externs/shaka/manifest_parser.js +10 -1
- package/externs/shaka/net.js +20 -4
- package/externs/shaka/offline.js +31 -2
- package/externs/shaka/player.js +130 -5
- package/externs/shaka/text.js +14 -0
- package/externs/webcrypto.js +18 -0
- package/lib/abr/ewma.js +14 -0
- package/lib/abr/ewma_bandwidth_estimator.js +16 -2
- package/lib/abr/simple_abr_manager.js +6 -0
- package/lib/ads/client_side_ad.js +8 -0
- package/lib/ads/client_side_ad_manager.js +8 -4
- package/lib/ads/server_side_ad.js +7 -0
- package/lib/cast/cast_utils.js +1 -0
- package/lib/dash/dash_parser.js +48 -14
- package/lib/dash/segment_base.js +25 -1
- package/lib/dash/segment_list.js +18 -9
- package/lib/dash/segment_template.js +23 -10
- package/lib/hls/hls_parser.js +86 -23
- package/lib/media/quality_observer.js +291 -0
- package/lib/media/segment_index.js +6 -0
- package/lib/media/segment_reference.js +18 -3
- package/lib/media/streaming_engine.js +42 -4
- package/lib/net/http_fetch_plugin.js +31 -11
- package/lib/net/http_plugin_utils.js +1 -0
- package/lib/net/http_xhr_plugin.js +40 -14
- package/lib/net/networking_engine.js +81 -18
- package/lib/offline/download_info.js +68 -0
- package/lib/offline/download_manager.js +13 -5
- package/lib/offline/indexeddb/base_storage_cell.js +35 -0
- package/lib/offline/indexeddb/storage_mechanism.js +1 -1
- package/lib/offline/indexeddb/v5_storage_cell.js +5 -0
- package/lib/offline/storage.js +393 -284
- package/lib/offline/stored_content_utils.js +2 -0
- package/lib/player.js +206 -7
- package/lib/polyfill/random_uuid.js +52 -0
- package/lib/text/cue.js +12 -0
- package/lib/text/ttml_text_parser.js +25 -2
- package/lib/text/ui_text_displayer.js +2 -2
- package/lib/util/cmcd_manager.js +826 -0
- package/lib/util/error.js +6 -0
- package/lib/util/language_utils.js +12 -0
- package/lib/util/mutex.js +52 -0
- package/lib/util/periods.js +98 -64
- package/lib/util/player_configuration.js +18 -0
- package/package.json +1 -1
- package/shaka-player.uncompiled.js +3 -0
- package/test/dash/dash_parser_content_protection_unit.js +2 -0
- package/test/dash/dash_parser_live_unit.js +101 -1
- package/test/dash/dash_parser_manifest_unit.js +35 -1
- package/test/dash/dash_parser_segment_base_unit.js +2 -0
- package/test/dash/dash_parser_segment_list_unit.js +2 -1
- package/test/dash/dash_parser_segment_template_unit.js +2 -1
- package/test/hls/hls_live_unit.js +2 -0
- package/test/hls/hls_parser_unit.js +2 -0
- package/test/media/quality_observer_unit.js +139 -0
- package/test/media/segment_reference_unit.js +16 -1
- package/test/media/streaming_engine_integration.js +2 -1
- package/test/media/streaming_engine_unit.js +2 -0
- package/test/net/http_plugin_unit.js +20 -15
- package/test/offline/storage_integration.js +108 -4
- package/test/player_integration.js +99 -0
- package/test/test/util/dash_parser_util.js +4 -0
- package/test/test/util/fake_ad.js +7 -0
- package/test/test/util/fake_drm_engine.js +9 -2
- package/test/test/util/manifest_generator.js +3 -2
- package/test/test/util/test_scheme.js +14 -1
- package/test/text/ttml_text_parser_unit.js +47 -0
- package/test/ui/ui_unit.js +250 -0
- package/test/util/cmcd_manager_unit.js +277 -0
- package/ui/airplay_button.js +1 -0
- package/ui/audio_language_selection.js +7 -0
- package/ui/cast_button.js +1 -0
- package/ui/context_menu.js +109 -0
- package/ui/controls.js +41 -7
- package/ui/controls.less +1 -0
- package/ui/enums.js +4 -0
- package/ui/externs/ui.js +28 -1
- package/ui/fast_forward_button.js +14 -5
- package/ui/fullscreen_button.js +1 -0
- package/ui/less/buttons.less +0 -4
- package/ui/less/containers.less +74 -0
- package/ui/less/overflow_menu.less +0 -10
- package/ui/less/tooltip.less +97 -0
- package/ui/locales/en.json +1 -0
- package/ui/locales/source.json +4 -0
- package/ui/loop_button.js +5 -0
- package/ui/mute_button.js +2 -1
- package/ui/overflow_menu.js +5 -25
- package/ui/pip_button.js +5 -0
- package/ui/play_button.js +2 -2
- package/ui/playback_rate_selection.js +8 -9
- package/ui/resolution_selection.js +7 -0
- package/ui/rewind_button.js +15 -6
- package/ui/seek_bar.js +4 -2
- package/ui/settings_menu.js +36 -4
- package/ui/small_play_button.js +1 -0
- package/ui/statistics_button.js +253 -0
- package/ui/text_selection.js +11 -4
- package/ui/ui.js +30 -0
- package/ui/volume_bar.js +1 -1
|
@@ -23,11 +23,13 @@ shaka.media.InitSegmentReference = class {
|
|
|
23
23
|
* of the resource containing the segment.
|
|
24
24
|
* @param {number} startByte The offset from the start of the resource to the
|
|
25
25
|
* start of the segment.
|
|
26
|
-
* @param {?number} endByte The offset from the start of the resource
|
|
27
|
-
* end of the segment, inclusive. A value of null indicates that the
|
|
26
|
+
* @param {?number} endByte The offset from the start of the resource
|
|
27
|
+
* to the end of the segment, inclusive. A value of null indicates that the
|
|
28
28
|
* segment extends to the end of the resource.
|
|
29
|
+
* @param {null|shaka.extern.MediaQualityInfo=} mediaQuality Information about
|
|
30
|
+
* the quality of the media associated with this init segment.
|
|
29
31
|
*/
|
|
30
|
-
constructor(uris, startByte, endByte) {
|
|
32
|
+
constructor(uris, startByte, endByte, mediaQuality = null) {
|
|
31
33
|
/** @type {function():!Array.<string>} */
|
|
32
34
|
this.getUris = uris;
|
|
33
35
|
|
|
@@ -36,6 +38,9 @@ shaka.media.InitSegmentReference = class {
|
|
|
36
38
|
|
|
37
39
|
/** @const {?number} */
|
|
38
40
|
this.endByte = endByte;
|
|
41
|
+
|
|
42
|
+
/** @const {shaka.extern.MediaQualityInfo|null} */
|
|
43
|
+
this.mediaQuality = mediaQuality;
|
|
39
44
|
}
|
|
40
45
|
|
|
41
46
|
/**
|
|
@@ -73,6 +78,16 @@ shaka.media.InitSegmentReference = class {
|
|
|
73
78
|
}
|
|
74
79
|
}
|
|
75
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Returns media quality information for the segments associated with
|
|
83
|
+
* this init segment.
|
|
84
|
+
*
|
|
85
|
+
* @return {?shaka.extern.MediaQualityInfo}
|
|
86
|
+
*/
|
|
87
|
+
getMediaQuality() {
|
|
88
|
+
return this.mediaQuality;
|
|
89
|
+
}
|
|
90
|
+
|
|
76
91
|
/**
|
|
77
92
|
* Check if two initSegmentReference have all the same values.
|
|
78
93
|
* @param {?shaka.media.InitSegmentReference} reference1
|
|
@@ -431,6 +431,11 @@ shaka.media.StreamingEngine = class {
|
|
|
431
431
|
this.playerInterface_.mediaSourceEngine.reinitText(fullMimeType);
|
|
432
432
|
}
|
|
433
433
|
|
|
434
|
+
// Releases the segmentIndex of the old stream.
|
|
435
|
+
if (mediaState.stream.closeSegmentIndex) {
|
|
436
|
+
mediaState.stream.closeSegmentIndex();
|
|
437
|
+
}
|
|
438
|
+
|
|
434
439
|
mediaState.stream = stream;
|
|
435
440
|
mediaState.segmentIterator = null;
|
|
436
441
|
|
|
@@ -863,7 +868,7 @@ shaka.media.StreamingEngine = class {
|
|
|
863
868
|
return;
|
|
864
869
|
}
|
|
865
870
|
|
|
866
|
-
// Make sure the segment index exists.
|
|
871
|
+
// Make sure the segment index exists. If not, create the segment index.
|
|
867
872
|
if (!mediaState.stream.segmentIndex) {
|
|
868
873
|
const thisStream = mediaState.stream;
|
|
869
874
|
|
|
@@ -873,6 +878,12 @@ shaka.media.StreamingEngine = class {
|
|
|
873
878
|
// We switched streams while in the middle of this async call to
|
|
874
879
|
// createSegmentIndex. Abandon this update and schedule a new one if
|
|
875
880
|
// there's not already one pending.
|
|
881
|
+
// Releases the segmentIndex of the old stream.
|
|
882
|
+
if (thisStream.closeSegmentIndex) {
|
|
883
|
+
goog.asserts.assert(!mediaState.stream.segmentIndex,
|
|
884
|
+
'mediastate.stream should not have segmentIndex yet.');
|
|
885
|
+
thisStream.closeSegmentIndex();
|
|
886
|
+
}
|
|
876
887
|
if (mediaState.updateTimer == null) {
|
|
877
888
|
this.scheduleUpdate_(mediaState, 0);
|
|
878
889
|
}
|
|
@@ -1530,6 +1541,8 @@ shaka.media.StreamingEngine = class {
|
|
|
1530
1541
|
throw error;
|
|
1531
1542
|
}
|
|
1532
1543
|
};
|
|
1544
|
+
this.playerInterface_.onInitSegmentAppended(
|
|
1545
|
+
reference.startTime, reference.initSegmentReference);
|
|
1533
1546
|
operations.push(append());
|
|
1534
1547
|
}
|
|
1535
1548
|
}
|
|
@@ -1555,7 +1568,9 @@ shaka.media.StreamingEngine = class {
|
|
|
1555
1568
|
|
|
1556
1569
|
const hasClosedCaptions = stream.closedCaptions &&
|
|
1557
1570
|
stream.closedCaptions.size > 0;
|
|
1558
|
-
if (stream.emsgSchemeIdUris != null &&
|
|
1571
|
+
if ((stream.emsgSchemeIdUris != null &&
|
|
1572
|
+
stream.emsgSchemeIdUris.length > 0) ||
|
|
1573
|
+
this.config_.dispatchAllEmsgBoxes) {
|
|
1559
1574
|
new shaka.util.Mp4Parser()
|
|
1560
1575
|
.fullBox(
|
|
1561
1576
|
'emsg',
|
|
@@ -1641,7 +1656,8 @@ shaka.media.StreamingEngine = class {
|
|
|
1641
1656
|
// See DASH sec. 5.10.3.3.1
|
|
1642
1657
|
// If a DASH client detects an event message box with a scheme that is not
|
|
1643
1658
|
// defined in MPD, the client is expected to ignore it.
|
|
1644
|
-
if (emsgSchemeIdUris.includes(schemeId)
|
|
1659
|
+
if (emsgSchemeIdUris.includes(schemeId) ||
|
|
1660
|
+
this.config_.dispatchAllEmsgBoxes) {
|
|
1645
1661
|
// See DASH sec. 5.10.4.1
|
|
1646
1662
|
// A special scheme in DASH used to signal manifest updates.
|
|
1647
1663
|
if (schemeId == 'urn:mpeg:dash:event:2012') {
|
|
@@ -1764,6 +1780,19 @@ shaka.media.StreamingEngine = class {
|
|
|
1764
1780
|
|
|
1765
1781
|
shaka.log.v2('fetching: reference=', reference);
|
|
1766
1782
|
|
|
1783
|
+
const stream = mediaState.stream;
|
|
1784
|
+
this.playerInterface_.modifySegmentRequest(
|
|
1785
|
+
request,
|
|
1786
|
+
{
|
|
1787
|
+
type: stream.type,
|
|
1788
|
+
init: reference instanceof shaka.media.InitSegmentReference,
|
|
1789
|
+
duration: reference.endTime - reference.startTime,
|
|
1790
|
+
mimeType: stream.mimeType,
|
|
1791
|
+
codecs: stream.codecs,
|
|
1792
|
+
bandwidth: stream.bandwidth,
|
|
1793
|
+
},
|
|
1794
|
+
);
|
|
1795
|
+
|
|
1767
1796
|
const op = this.playerInterface_.netEngine.request(requestType, request);
|
|
1768
1797
|
mediaState.operation = op;
|
|
1769
1798
|
const response = await op.promise;
|
|
@@ -1927,12 +1956,15 @@ shaka.media.StreamingEngine = class {
|
|
|
1927
1956
|
* @typedef {{
|
|
1928
1957
|
* getPresentationTime: function():number,
|
|
1929
1958
|
* getBandwidthEstimate: function():number,
|
|
1959
|
+
* modifySegmentRequest: function(shaka.extern.Request,
|
|
1960
|
+
* shaka.util.CmcdManager.SegmentInfo),
|
|
1930
1961
|
* mediaSourceEngine: !shaka.media.MediaSourceEngine,
|
|
1931
1962
|
* netEngine: shaka.net.NetworkingEngine,
|
|
1932
1963
|
* onError: function(!shaka.util.Error),
|
|
1933
1964
|
* onEvent: function(!Event),
|
|
1934
1965
|
* onManifestUpdate: function(),
|
|
1935
|
-
* onSegmentAppended: function()
|
|
1966
|
+
* onSegmentAppended: function(),
|
|
1967
|
+
* onInitSegmentAppended: function(!number,!shaka.media.InitSegmentReference)
|
|
1936
1968
|
* }}
|
|
1937
1969
|
*
|
|
1938
1970
|
* @property {function():number} getPresentationTime
|
|
@@ -1940,6 +1972,9 @@ shaka.media.StreamingEngine = class {
|
|
|
1940
1972
|
* viewer is seeing on screen right now.
|
|
1941
1973
|
* @property {function():number} getBandwidthEstimate
|
|
1942
1974
|
* Get the estimated bandwidth in bits per second.
|
|
1975
|
+
* @property {function(shaka.extern.Request,
|
|
1976
|
+
* shaka.extern.Cmcd.SegmentInfo)} modifySegmentRequest
|
|
1977
|
+
* The request modifier
|
|
1943
1978
|
* @property {!shaka.media.MediaSourceEngine} mediaSourceEngine
|
|
1944
1979
|
* The MediaSourceEngine. The caller retains ownership.
|
|
1945
1980
|
* @property {shaka.net.NetworkingEngine} netEngine
|
|
@@ -1954,6 +1989,9 @@ shaka.media.StreamingEngine = class {
|
|
|
1954
1989
|
* Called when an embedded 'emsg' box should trigger a manifest update.
|
|
1955
1990
|
* @property {function()} onSegmentAppended
|
|
1956
1991
|
* Called after a segment is successfully appended to a MediaSource.
|
|
1992
|
+
* @property
|
|
1993
|
+
* {function(!number, !shaka.media.InitSegmentReference)} onInitSegmentAppended
|
|
1994
|
+
* Called when an init segment is appended to a MediaSource.
|
|
1957
1995
|
*/
|
|
1958
1996
|
shaka.media.StreamingEngine.PlayerInterface;
|
|
1959
1997
|
|
|
@@ -27,10 +27,12 @@ shaka.net.HttpFetchPlugin = class {
|
|
|
27
27
|
* @param {shaka.net.NetworkingEngine.RequestType} requestType
|
|
28
28
|
* @param {shaka.extern.ProgressUpdated} progressUpdated Called when a
|
|
29
29
|
* progress event happened.
|
|
30
|
+
* @param {shaka.extern.HeadersReceived} headersReceived Called when the
|
|
31
|
+
* headers for the download are received, but before the body is.
|
|
30
32
|
* @return {!shaka.extern.IAbortableOperation.<shaka.extern.Response>}
|
|
31
33
|
* @export
|
|
32
34
|
*/
|
|
33
|
-
static parse(uri, request, requestType, progressUpdated) {
|
|
35
|
+
static parse(uri, request, requestType, progressUpdated, headersReceived) {
|
|
34
36
|
const headers = new shaka.net.HttpFetchPlugin.Headers_();
|
|
35
37
|
shaka.util.MapUtils.asMap(request.headers).forEach((value, key) => {
|
|
36
38
|
headers.append(key, value);
|
|
@@ -55,7 +57,7 @@ shaka.net.HttpFetchPlugin = class {
|
|
|
55
57
|
};
|
|
56
58
|
|
|
57
59
|
const pendingRequest = shaka.net.HttpFetchPlugin.request_(
|
|
58
|
-
uri, requestType, init, abortStatus, progressUpdated,
|
|
60
|
+
uri, requestType, init, abortStatus, progressUpdated, headersReceived,
|
|
59
61
|
request.streamDataCallback);
|
|
60
62
|
|
|
61
63
|
/** @type {!shaka.util.AbortableOperation} */
|
|
@@ -92,12 +94,13 @@ shaka.net.HttpFetchPlugin = class {
|
|
|
92
94
|
* @param {!RequestInit} init
|
|
93
95
|
* @param {shaka.net.HttpFetchPlugin.AbortStatus} abortStatus
|
|
94
96
|
* @param {shaka.extern.ProgressUpdated} progressUpdated
|
|
97
|
+
* @param {shaka.extern.HeadersReceived} headersReceived
|
|
95
98
|
* @param {?function(BufferSource):!Promise} streamDataCallback
|
|
96
99
|
* @return {!Promise<!shaka.extern.Response>}
|
|
97
100
|
* @private
|
|
98
101
|
*/
|
|
99
102
|
static async request_(uri, requestType, init, abortStatus, progressUpdated,
|
|
100
|
-
streamDataCallback) {
|
|
103
|
+
headersReceived, streamDataCallback) {
|
|
101
104
|
const fetch = shaka.net.HttpFetchPlugin.fetch_;
|
|
102
105
|
const ReadableStream = shaka.net.HttpFetchPlugin.ReadableStream_;
|
|
103
106
|
let response;
|
|
@@ -113,6 +116,10 @@ shaka.net.HttpFetchPlugin = class {
|
|
|
113
116
|
// headers are available. The download itself isn't done until the promise
|
|
114
117
|
// for retrieving the data (arrayBuffer, blob, etc) has resolved.
|
|
115
118
|
response = await fetch(uri, init);
|
|
119
|
+
// At this point in the process, we have the headers of the response, but
|
|
120
|
+
// not the body yet.
|
|
121
|
+
headersReceived(shaka.net.HttpFetchPlugin.headersToGenericObject_(
|
|
122
|
+
response.headers));
|
|
116
123
|
// Getting the reader in this way allows us to observe the process of
|
|
117
124
|
// downloading the body, instead of just waiting for an opaque promise to
|
|
118
125
|
// resolve.
|
|
@@ -194,19 +201,28 @@ shaka.net.HttpFetchPlugin = class {
|
|
|
194
201
|
}
|
|
195
202
|
}
|
|
196
203
|
|
|
197
|
-
const headers =
|
|
198
|
-
|
|
199
|
-
const responseHeaders = response.headers;
|
|
200
|
-
responseHeaders.forEach((value, key) => {
|
|
201
|
-
// Since Edge incorrectly return the header with a leading new line
|
|
202
|
-
// character ('\n'), we trim the header here.
|
|
203
|
-
headers[key.trim()] = value;
|
|
204
|
-
});
|
|
204
|
+
const headers = shaka.net.HttpFetchPlugin.headersToGenericObject_(
|
|
205
|
+
response.headers);
|
|
205
206
|
|
|
206
207
|
return shaka.net.HttpPluginUtils.makeResponse(
|
|
207
208
|
headers, arrayBuffer, response.status, uri, response.url, requestType);
|
|
208
209
|
}
|
|
209
210
|
|
|
211
|
+
/**
|
|
212
|
+
* @param {!Headers} headers
|
|
213
|
+
* @return {!Object.<string, string>}
|
|
214
|
+
* @private
|
|
215
|
+
*/
|
|
216
|
+
static headersToGenericObject_(headers) {
|
|
217
|
+
const headersObj = {};
|
|
218
|
+
headers.forEach((value, key) => {
|
|
219
|
+
// Since Edge incorrectly return the header with a leading new line
|
|
220
|
+
// character ('\n'), we trim the header here.
|
|
221
|
+
headersObj[key.trim()] = value;
|
|
222
|
+
});
|
|
223
|
+
return headersObj;
|
|
224
|
+
}
|
|
225
|
+
|
|
210
226
|
/**
|
|
211
227
|
* Determine if the Fetch API is supported in the browser. Note: this is
|
|
212
228
|
* deliberately exposed as a method to allow the client app to use the same
|
|
@@ -290,4 +306,8 @@ if (shaka.net.HttpFetchPlugin.isSupported()) {
|
|
|
290
306
|
'https', shaka.net.HttpFetchPlugin.parse,
|
|
291
307
|
shaka.net.NetworkingEngine.PluginPriority.PREFERRED,
|
|
292
308
|
/* progressSupport= */ true);
|
|
309
|
+
shaka.net.NetworkingEngine.registerScheme(
|
|
310
|
+
'blob', shaka.net.HttpFetchPlugin.parse,
|
|
311
|
+
shaka.net.NetworkingEngine.PluginPriority.PREFERRED,
|
|
312
|
+
/* progressSupport= */ true);
|
|
293
313
|
}
|
|
@@ -24,10 +24,12 @@ shaka.net.HttpXHRPlugin = class {
|
|
|
24
24
|
* @param {shaka.net.NetworkingEngine.RequestType} requestType
|
|
25
25
|
* @param {shaka.extern.ProgressUpdated} progressUpdated Called when a
|
|
26
26
|
* progress event happened.
|
|
27
|
+
* @param {shaka.extern.HeadersReceived} headersReceived Called when the
|
|
28
|
+
* headers for the download are received, but before the body is.
|
|
27
29
|
* @return {!shaka.extern.IAbortableOperation.<shaka.extern.Response>}
|
|
28
30
|
* @export
|
|
29
31
|
*/
|
|
30
|
-
static parse(uri, request, requestType, progressUpdated) {
|
|
32
|
+
static parse(uri, request, requestType, progressUpdated, headersReceived) {
|
|
31
33
|
const xhr = new shaka.net.HttpXHRPlugin.Xhr_();
|
|
32
34
|
|
|
33
35
|
// Last time stamp when we got a progress event.
|
|
@@ -48,23 +50,25 @@ shaka.net.HttpXHRPlugin = class {
|
|
|
48
50
|
shaka.util.Error.Code.OPERATION_ABORTED,
|
|
49
51
|
uri, requestType));
|
|
50
52
|
};
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
/** @type {!Array.<string>} */
|
|
60
|
-
const parts = header.split(': ');
|
|
61
|
-
headers[parts[0].toLowerCase()] = parts.slice(1).join(': ');
|
|
53
|
+
let calledHeadersReceived = false;
|
|
54
|
+
xhr.onreadystatechange = (event) => {
|
|
55
|
+
// See if the readyState is 2 ("HEADERS_RECEIVED").
|
|
56
|
+
if (xhr.readyState == 2 && !calledHeadersReceived) {
|
|
57
|
+
const headers = shaka.net.HttpXHRPlugin.headersToGenericObject_(xhr);
|
|
58
|
+
headersReceived(headers);
|
|
59
|
+
// Don't send out this event twice.
|
|
60
|
+
calledHeadersReceived = true;
|
|
62
61
|
}
|
|
62
|
+
};
|
|
63
|
+
xhr.onload = (event) => {
|
|
64
|
+
const headers = shaka.net.HttpXHRPlugin.headersToGenericObject_(xhr);
|
|
65
|
+
goog.asserts.assert(xhr.response instanceof ArrayBuffer,
|
|
66
|
+
'XHR should have a response by now!');
|
|
67
|
+
const xhrResponse = xhr.response;
|
|
63
68
|
|
|
64
69
|
try {
|
|
65
70
|
const response = shaka.net.HttpPluginUtils.makeResponse(headers,
|
|
66
|
-
|
|
67
|
-
requestType);
|
|
71
|
+
xhrResponse, xhr.status, uri, xhr.responseURL, requestType);
|
|
68
72
|
resolve(response);
|
|
69
73
|
} catch (error) {
|
|
70
74
|
goog.asserts.assert(error instanceof shaka.util.Error,
|
|
@@ -116,6 +120,24 @@ shaka.net.HttpXHRPlugin = class {
|
|
|
116
120
|
return Promise.resolve();
|
|
117
121
|
});
|
|
118
122
|
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @param {!XMLHttpRequest} xhr
|
|
126
|
+
* @return {!Object.<string, string>}
|
|
127
|
+
* @private
|
|
128
|
+
*/
|
|
129
|
+
static headersToGenericObject_(xhr) {
|
|
130
|
+
// Since Edge incorrectly return the header with a leading new
|
|
131
|
+
// line character ('\n'), we trim the header here.
|
|
132
|
+
const headerLines = xhr.getAllResponseHeaders().trim().split('\r\n');
|
|
133
|
+
const headers = {};
|
|
134
|
+
for (const header of headerLines) {
|
|
135
|
+
/** @type {!Array.<string>} */
|
|
136
|
+
const parts = header.split(': ');
|
|
137
|
+
headers[parts[0].toLowerCase()] = parts.slice(1).join(': ');
|
|
138
|
+
}
|
|
139
|
+
return headers;
|
|
140
|
+
}
|
|
119
141
|
};
|
|
120
142
|
|
|
121
143
|
|
|
@@ -136,4 +158,8 @@ shaka.net.NetworkingEngine.registerScheme(
|
|
|
136
158
|
'https', shaka.net.HttpXHRPlugin.parse,
|
|
137
159
|
shaka.net.NetworkingEngine.PluginPriority.FALLBACK,
|
|
138
160
|
/* progressSupport= */ true);
|
|
161
|
+
shaka.net.NetworkingEngine.registerScheme(
|
|
162
|
+
'blob', shaka.net.HttpXHRPlugin.parse,
|
|
163
|
+
shaka.net.NetworkingEngine.PluginPriority.FALLBACK,
|
|
164
|
+
/* progressSupport= */ true);
|
|
139
165
|
|
|
@@ -48,8 +48,12 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
|
|
|
48
48
|
* @param {function(number, number)=} onProgressUpdated Called when a progress
|
|
49
49
|
* event is triggered. Passed the duration, in milliseconds, that the
|
|
50
50
|
* request took, and the number of bytes transferred.
|
|
51
|
+
* @param {shaka.net.NetworkingEngine.OnHeadersReceived=} onHeadersReceived
|
|
52
|
+
* Called when the headers are received for a download.
|
|
53
|
+
* @param {shaka.net.NetworkingEngine.OnDownloadFailed=} onDownloadFailed
|
|
54
|
+
* Called when a download fails, for any reason.
|
|
51
55
|
*/
|
|
52
|
-
constructor(onProgressUpdated) {
|
|
56
|
+
constructor(onProgressUpdated, onHeadersReceived, onDownloadFailed) {
|
|
53
57
|
super();
|
|
54
58
|
|
|
55
59
|
/** @private {boolean} */
|
|
@@ -67,6 +71,12 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
|
|
|
67
71
|
/** @private {?function(number, number)} */
|
|
68
72
|
this.onProgressUpdated_ = onProgressUpdated || null;
|
|
69
73
|
|
|
74
|
+
/** @private {?shaka.net.NetworkingEngine.OnHeadersReceived} */
|
|
75
|
+
this.onHeadersReceived_ = onHeadersReceived || null;
|
|
76
|
+
|
|
77
|
+
/** @private {?shaka.net.NetworkingEngine.OnDownloadFailed} */
|
|
78
|
+
this.onDownloadFailed_ = onDownloadFailed || null;
|
|
79
|
+
|
|
70
80
|
/** @private {boolean} */
|
|
71
81
|
this.forceHTTPS_ = false;
|
|
72
82
|
}
|
|
@@ -447,6 +457,8 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
|
|
|
447
457
|
|
|
448
458
|
let aborted = false;
|
|
449
459
|
|
|
460
|
+
let headersReceivedCalled = false;
|
|
461
|
+
|
|
450
462
|
let startTimeMs;
|
|
451
463
|
const sendOperation = backoffOperation.chain(() => {
|
|
452
464
|
if (this.destroyed_) {
|
|
@@ -456,23 +468,27 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
|
|
|
456
468
|
startTimeMs = Date.now();
|
|
457
469
|
const segment = shaka.net.NetworkingEngine.RequestType.SEGMENT;
|
|
458
470
|
|
|
459
|
-
const
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
471
|
+
const progressUpdated = (time, bytes, numBytesRemaining) => {
|
|
472
|
+
if (connectionTimer) {
|
|
473
|
+
connectionTimer.stop();
|
|
474
|
+
}
|
|
475
|
+
if (stallTimer) {
|
|
476
|
+
stallTimer.tickAfter(stallTimeoutMs / 1000);
|
|
477
|
+
}
|
|
478
|
+
if (this.onProgressUpdated_ && type == segment) {
|
|
479
|
+
this.onProgressUpdated_(time, bytes);
|
|
480
|
+
gotProgress = true;
|
|
481
|
+
numBytesRemainingObj.setBytes(numBytesRemaining);
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
const headersReceived = (headers) => {
|
|
485
|
+
if (this.onHeadersReceived_) {
|
|
486
|
+
this.onHeadersReceived_(headers, request, type);
|
|
487
|
+
}
|
|
488
|
+
headersReceivedCalled = true;
|
|
489
|
+
};
|
|
490
|
+
const requestPlugin = plugin(
|
|
491
|
+
request.uris[index], request, type, progressUpdated, headersReceived);
|
|
476
492
|
|
|
477
493
|
if (!progressSupport) {
|
|
478
494
|
return requestPlugin;
|
|
@@ -511,6 +527,13 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
|
|
|
511
527
|
response: response,
|
|
512
528
|
gotProgress: gotProgress,
|
|
513
529
|
};
|
|
530
|
+
if (!headersReceivedCalled) {
|
|
531
|
+
// The plugin did not call headersReceived, perhaps because it is not
|
|
532
|
+
// able to track that information. So, fire the event manually.
|
|
533
|
+
if (this.onHeadersReceived_) {
|
|
534
|
+
this.onHeadersReceived_(response.headers, request, type);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
514
537
|
|
|
515
538
|
return responseAndGotProgress;
|
|
516
539
|
}, (error) => {
|
|
@@ -520,6 +543,17 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
|
|
|
520
543
|
if (stallTimer) {
|
|
521
544
|
stallTimer.stop();
|
|
522
545
|
}
|
|
546
|
+
if (this.onDownloadFailed_) {
|
|
547
|
+
let shakaError = null;
|
|
548
|
+
let httpResponseCode = 0;
|
|
549
|
+
if (error instanceof shaka.util.Error) {
|
|
550
|
+
shakaError = error;
|
|
551
|
+
if (error.code == shaka.util.Error.Code.BAD_HTTP_STATUS) {
|
|
552
|
+
httpResponseCode = /** @type {number} */ (error.data[1]);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
this.onDownloadFailed_(request, shakaError, httpResponseCode, aborted);
|
|
556
|
+
}
|
|
523
557
|
if (this.destroyed_) {
|
|
524
558
|
return shaka.util.AbortableOperation.aborted();
|
|
525
559
|
}
|
|
@@ -764,3 +798,32 @@ shaka.net.NetworkingEngine.schemes_ = {};
|
|
|
764
798
|
* @private
|
|
765
799
|
*/
|
|
766
800
|
shaka.net.NetworkingEngine.ResponseAndGotProgress;
|
|
801
|
+
|
|
802
|
+
|
|
803
|
+
/**
|
|
804
|
+
* @typedef {function(
|
|
805
|
+
* !Object.<string, string>,
|
|
806
|
+
* !shaka.extern.Request,
|
|
807
|
+
* !shaka.net.NetworkingEngine.RequestType)}
|
|
808
|
+
*
|
|
809
|
+
* @description
|
|
810
|
+
* A callback function that passes the shaka.extern.HeadersReceived along to
|
|
811
|
+
* the player, plus some extra data.
|
|
812
|
+
* @export
|
|
813
|
+
*/
|
|
814
|
+
shaka.net.NetworkingEngine.OnHeadersReceived;
|
|
815
|
+
|
|
816
|
+
|
|
817
|
+
/**
|
|
818
|
+
* @typedef {function(
|
|
819
|
+
* !shaka.extern.Request,
|
|
820
|
+
* ?shaka.util.Error,
|
|
821
|
+
* number,
|
|
822
|
+
* boolean)}
|
|
823
|
+
*
|
|
824
|
+
* @description
|
|
825
|
+
* A callback function that notifies the player when a download fails, for any
|
|
826
|
+
* reason (e.g. even if the download was aborted).
|
|
827
|
+
* @export
|
|
828
|
+
*/
|
|
829
|
+
shaka.net.NetworkingEngine.OnDownloadFailed;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/*! @license
|
|
2
|
+
* Shaka Player
|
|
3
|
+
* Copyright 2016 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
goog.provide('shaka.offline.DownloadInfo');
|
|
8
|
+
|
|
9
|
+
goog.require('shaka.util.Networking');
|
|
10
|
+
goog.requireType('shaka.media.InitSegmentReference');
|
|
11
|
+
goog.requireType('shaka.media.SegmentReference');
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* An object that represents a single segment, that the storage system will soon
|
|
16
|
+
* download, but has not yet started downloading.
|
|
17
|
+
*/
|
|
18
|
+
shaka.offline.DownloadInfo = class {
|
|
19
|
+
/**
|
|
20
|
+
* @param {shaka.media.SegmentReference|shaka.media.InitSegmentReference} ref
|
|
21
|
+
* @param {number} estimateId
|
|
22
|
+
* @param {number} groupId
|
|
23
|
+
* @param {boolean} isInitSegment
|
|
24
|
+
*/
|
|
25
|
+
constructor(ref, estimateId, groupId, isInitSegment) {
|
|
26
|
+
/** @type {shaka.media.SegmentReference|shaka.media.InitSegmentReference} */
|
|
27
|
+
this.ref = ref;
|
|
28
|
+
|
|
29
|
+
/** @type {number} */
|
|
30
|
+
this.estimateId = estimateId;
|
|
31
|
+
|
|
32
|
+
/** @type {number} */
|
|
33
|
+
this.groupId = groupId;
|
|
34
|
+
|
|
35
|
+
/** @type {boolean} */
|
|
36
|
+
this.isInitSegment = isInitSegment;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Creates an ID that encapsulates all important information in the ref, which
|
|
41
|
+
* can then be used to check for equality.
|
|
42
|
+
* @param {shaka.media.SegmentReference|shaka.media.InitSegmentReference} ref
|
|
43
|
+
* @return {string}
|
|
44
|
+
*/
|
|
45
|
+
static idForSegmentRef(ref) {
|
|
46
|
+
// Escape the URIs using encodeURI, to make sure that a weirdly formed URI
|
|
47
|
+
// cannot cause two unrelated refs to be considered equivalent.
|
|
48
|
+
return ref.getUris().map((uri) => '{' + encodeURI(uri) + '}').join('') +
|
|
49
|
+
':' + ref.startByte + ':' + ref.endByte;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** @return {string} */
|
|
53
|
+
getRefId() {
|
|
54
|
+
return shaka.offline.DownloadInfo.idForSegmentRef(this.ref);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @param {shaka.extern.PlayerConfiguration} config
|
|
59
|
+
* @return {!shaka.extern.Request}
|
|
60
|
+
*/
|
|
61
|
+
makeSegmentRequest(config) {
|
|
62
|
+
return shaka.util.Networking.createSegmentRequest(
|
|
63
|
+
this.ref.getUris(),
|
|
64
|
+
this.ref.startByte,
|
|
65
|
+
this.ref.endByte,
|
|
66
|
+
config.streaming.retryParameters);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
@@ -102,6 +102,16 @@ shaka.offline.DownloadManager = class {
|
|
|
102
102
|
return Promise.all(promises);
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Adds a byte length to the download estimate.
|
|
107
|
+
*
|
|
108
|
+
* @param {number} estimatedByteLength
|
|
109
|
+
* @return {number} estimateId
|
|
110
|
+
*/
|
|
111
|
+
addDownloadEstimate(estimatedByteLength) {
|
|
112
|
+
return this.estimator_.open(estimatedByteLength);
|
|
113
|
+
}
|
|
114
|
+
|
|
105
115
|
/**
|
|
106
116
|
* Add a request to be downloaded as part of a group.
|
|
107
117
|
*
|
|
@@ -109,18 +119,16 @@ shaka.offline.DownloadManager = class {
|
|
|
109
119
|
* The group to add this segment to. If the group does not exist, a new
|
|
110
120
|
* group will be created.
|
|
111
121
|
* @param {shaka.extern.Request} request
|
|
112
|
-
* @param {number}
|
|
122
|
+
* @param {number} estimateId
|
|
113
123
|
* @param {boolean} isInitSegment
|
|
114
124
|
* @param {function(BufferSource):!Promise} onDownloaded
|
|
115
125
|
* The callback for when this request has been downloaded. Downloading for
|
|
116
126
|
* |group| will pause until the promise returned by |onDownloaded| resolves.
|
|
117
127
|
* @return {!Promise} Resolved when this request is complete.
|
|
118
128
|
*/
|
|
119
|
-
queue(groupId, request,
|
|
129
|
+
queue(groupId, request, estimateId, isInitSegment, onDownloaded) {
|
|
120
130
|
this.destroyer_.ensureNotDestroyed();
|
|
121
131
|
|
|
122
|
-
const id = this.estimator_.open(estimatedByteLength);
|
|
123
|
-
|
|
124
132
|
const group = this.groups_.get(groupId) || Promise.resolve();
|
|
125
133
|
|
|
126
134
|
// Add another download to the group.
|
|
@@ -148,7 +156,7 @@ shaka.offline.DownloadManager = class {
|
|
|
148
156
|
}
|
|
149
157
|
|
|
150
158
|
// Update all our internal stats.
|
|
151
|
-
this.estimator_.close(
|
|
159
|
+
this.estimator_.close(estimateId, response.byteLength);
|
|
152
160
|
this.onProgress_(
|
|
153
161
|
this.estimator_.getEstimatedProgress(),
|
|
154
162
|
this.estimator_.getTotalDownloaded());
|
|
@@ -68,6 +68,28 @@ shaka.offline.indexeddb.BaseStorageCell = class {
|
|
|
68
68
|
return this.rejectAdd(this.manifestStore_);
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
/** @override */
|
|
72
|
+
updateManifest(key, manifest) {
|
|
73
|
+
// By default, reject all updates.
|
|
74
|
+
return this.rejectUpdate(this.manifestStore_);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @param {number} key
|
|
79
|
+
* @param {!shaka.extern.ManifestDB} manifest
|
|
80
|
+
* @return {!Promise}
|
|
81
|
+
* @protected
|
|
82
|
+
*/
|
|
83
|
+
updateManifestImplementation(key, manifest) {
|
|
84
|
+
const op = this.connection_.startReadWriteOperation(this.manifestStore_);
|
|
85
|
+
const store = op.store();
|
|
86
|
+
store.get(key).onsuccess = (e) => {
|
|
87
|
+
store.put(manifest, key);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
return op.promise();
|
|
91
|
+
}
|
|
92
|
+
|
|
71
93
|
/** @override */
|
|
72
94
|
updateManifestExpiration(key, newExpiration) {
|
|
73
95
|
const op = this.connection_.startReadWriteOperation(this.manifestStore_);
|
|
@@ -145,6 +167,19 @@ shaka.offline.indexeddb.BaseStorageCell = class {
|
|
|
145
167
|
'Cannot add new value to ' + storeName));
|
|
146
168
|
}
|
|
147
169
|
|
|
170
|
+
/**
|
|
171
|
+
* @param {string} storeName
|
|
172
|
+
* @return {!Promise}
|
|
173
|
+
* @protected
|
|
174
|
+
*/
|
|
175
|
+
rejectUpdate(storeName) {
|
|
176
|
+
return Promise.reject(new shaka.util.Error(
|
|
177
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
178
|
+
shaka.util.Error.Category.STORAGE,
|
|
179
|
+
shaka.util.Error.Code.MODIFY_OPERATION_NOT_SUPPORTED,
|
|
180
|
+
'Cannot modify values in ' + storeName));
|
|
181
|
+
}
|
|
182
|
+
|
|
148
183
|
/**
|
|
149
184
|
* @param {string} storeName
|
|
150
185
|
* @param {!Array.<T>} values
|
|
@@ -315,7 +315,7 @@ shaka.offline.indexeddb.StorageMechanism = class {
|
|
|
315
315
|
|
|
316
316
|
const del = window.indexedDB.deleteDatabase(name);
|
|
317
317
|
del.onblocked = (event) => {
|
|
318
|
-
shaka.log.warning('Deleting', name, 'is being blocked');
|
|
318
|
+
shaka.log.warning('Deleting', name, 'is being blocked', event);
|
|
319
319
|
};
|
|
320
320
|
del.onsuccess = (event) => {
|
|
321
321
|
p.resolve();
|
|
@@ -33,6 +33,11 @@ shaka.offline.indexeddb.V5StorageCell = class
|
|
|
33
33
|
return this.add(this.manifestStore_, manifests);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
/** @override */
|
|
37
|
+
updateManifest(key, manifest) {
|
|
38
|
+
return this.updateManifestImplementation(key, manifest);
|
|
39
|
+
}
|
|
40
|
+
|
|
36
41
|
/** @override */
|
|
37
42
|
convertManifest(old) {
|
|
38
43
|
// JSON serialization turns Infinity into null, so turn it back now.
|