shaka-player 4.7.13 → 4.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AUTHORS +1 -0
- package/CONTRIBUTORS +1 -0
- package/LICENSE +54 -0
- package/README.md +22 -1
- package/dist/controls.css +1 -1
- package/dist/controls.css.map +1 -1
- package/dist/deps.js +34 -23
- package/dist/locales.js +44 -34
- package/dist/shaka-player.compiled.d.ts +194 -44
- package/dist/shaka-player.compiled.debug.d.ts +194 -44
- package/dist/shaka-player.compiled.debug.externs.js +474 -337
- package/dist/shaka-player.compiled.debug.js +949 -832
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +474 -337
- package/dist/shaka-player.compiled.js +1237 -1171
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +257 -47
- package/dist/shaka-player.ui.debug.d.ts +257 -47
- package/dist/shaka-player.ui.debug.externs.js +591 -376
- package/dist/shaka-player.ui.debug.js +1165 -965
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +591 -376
- package/dist/shaka-player.ui.js +1521 -1404
- package/dist/shaka-player.ui.map +4 -4
- package/externs/device_sensor_event.js +18 -0
- package/externs/hisense.js +16 -0
- package/externs/playstation.js +31 -0
- package/externs/shaka/abr_manager.js +3 -1
- package/externs/shaka/ads.js +5 -0
- package/externs/shaka/codecs.js +13 -0
- package/externs/shaka/manifest.js +58 -6
- package/externs/shaka/net.js +13 -1
- package/externs/shaka/player.js +184 -13
- package/externs/shaka/text.js +19 -0
- package/externs/tizen.js +41 -0
- package/externs/webos.js +19 -0
- package/externs/xbox.js +43 -0
- package/lib/abr/simple_abr_manager.js +9 -1
- package/lib/ads/ad_manager.js +19 -2
- package/lib/ads/client_side_ad_manager.js +5 -3
- package/lib/ads/media_tailor_ad_manager.js +9 -0
- package/lib/cast/cast_receiver.js +0 -14
- package/lib/cast/cast_utils.js +1 -0
- package/lib/cea/mp4_cea_parser.js +6 -1
- package/lib/dash/content_protection.js +97 -53
- package/lib/dash/dash_parser.js +230 -176
- package/lib/dash/mpd_utils.js +72 -65
- package/lib/dash/segment_base.js +27 -21
- package/lib/dash/segment_list.js +18 -16
- package/lib/dash/segment_template.js +26 -21
- package/lib/hls/hls_parser.js +358 -164
- package/lib/media/buffering_observer.js +25 -1
- package/lib/media/drm_engine.js +91 -71
- package/lib/media/manifest_filterer.js +220 -0
- package/lib/media/media_source_engine.js +34 -5
- package/lib/media/preload_manager.js +827 -0
- package/lib/media/presentation_timeline.js +11 -0
- package/lib/media/segment_index.js +40 -30
- package/lib/media/segment_prefetch.js +140 -43
- package/lib/media/segment_reference.js +31 -8
- package/lib/media/segment_utils.js +208 -25
- package/lib/media/streaming_engine.js +319 -63
- package/lib/mss/content_protection.js +31 -25
- package/lib/mss/mss_parser.js +74 -55
- package/lib/net/networking_engine.js +49 -4
- package/lib/offline/storage.js +3 -3
- package/lib/player.js +882 -673
- package/lib/polyfill/media_capabilities.js +16 -1
- package/lib/text/simple_text_displayer.js +9 -0
- package/lib/text/stub_text_displayer.js +7 -0
- package/lib/text/text_engine.js +16 -3
- package/lib/text/ttml_text_parser.js +116 -114
- package/lib/text/ui_text_displayer.js +28 -11
- package/lib/text/vtt_text_parser.js +54 -115
- package/lib/transmuxer/opus.js +91 -0
- package/lib/transmuxer/ts_transmuxer.js +115 -0
- package/lib/util/cmcd_manager.js +152 -3
- package/lib/util/dom_utils.js +11 -0
- package/lib/util/error.js +5 -0
- package/lib/util/fake_event.js +2 -0
- package/lib/util/manifest_parser_utils.js +97 -3
- package/lib/util/mp4_box_parsers.js +346 -17
- package/lib/util/mp4_generator.js +23 -0
- package/lib/util/periods.js +19 -0
- package/lib/util/platform.js +161 -9
- package/lib/util/player_configuration.js +61 -3
- package/lib/util/stats.js +16 -0
- package/lib/util/stream_utils.js +16 -6
- package/lib/util/string_utils.js +36 -0
- package/lib/util/tXml.js +814 -0
- package/lib/util/ts_parser.js +50 -0
- package/lib/util/xml_utils.js +10 -338
- package/package.json +2 -2
- package/shaka-player.uncompiled.js +8 -0
- package/ui/controls.js +187 -5
- package/ui/enums.js +2 -0
- package/ui/externs/ui.js +30 -3
- package/ui/gl_matrix/matrix_4x4.js +568 -0
- package/ui/gl_matrix/matrix_quaternion.js +45 -0
- package/ui/less/containers.less +11 -0
- package/ui/locales/en-GB.json +2 -0
- package/ui/locales/en.json +2 -0
- package/ui/locales/es-419.json +2 -0
- package/ui/locales/es.json +2 -0
- package/ui/locales/source.json +8 -0
- package/ui/recenter_vr.js +126 -0
- package/ui/resolution_selection.js +25 -5
- package/ui/statistics_button.js +12 -0
- package/ui/text_selection.js +1 -1
- package/ui/toggle_stereoscopic.js +127 -0
- package/ui/ui.js +74 -38
- package/ui/vr_manager.js +557 -0
- package/ui/vr_utils.js +232 -0
- package/ui/vr_webgl.js +697 -0
|
@@ -27,6 +27,7 @@ goog.require('shaka.util.Error');
|
|
|
27
27
|
goog.require('shaka.util.FakeEvent');
|
|
28
28
|
goog.require('shaka.util.IDestroyable');
|
|
29
29
|
goog.require('shaka.util.Id3Utils');
|
|
30
|
+
goog.require('shaka.util.LanguageUtils');
|
|
30
31
|
goog.require('shaka.util.ManifestParserUtils');
|
|
31
32
|
goog.require('shaka.util.MimeUtils');
|
|
32
33
|
goog.require('shaka.util.Mp4BoxParsers');
|
|
@@ -122,6 +123,17 @@ shaka.media.StreamingEngine = class {
|
|
|
122
123
|
|
|
123
124
|
/** @private {number} */
|
|
124
125
|
this.lastMediaSourceReset_ = Date.now() / 1000;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* @private {!Map<shaka.extern.Stream, !shaka.media.SegmentPrefetch>}
|
|
129
|
+
*/
|
|
130
|
+
this.audioPrefetchMap_ = new Map();
|
|
131
|
+
|
|
132
|
+
/** @private {!shaka.extern.SpatialVideoInfo} */
|
|
133
|
+
this.spatialVideoInfo_ = {
|
|
134
|
+
projection: null,
|
|
135
|
+
hfov: null,
|
|
136
|
+
};
|
|
125
137
|
}
|
|
126
138
|
|
|
127
139
|
/** @override */
|
|
@@ -140,10 +152,14 @@ shaka.media.StreamingEngine = class {
|
|
|
140
152
|
this.cancelUpdate_(state);
|
|
141
153
|
aborts.push(this.abortOperations_(state));
|
|
142
154
|
}
|
|
155
|
+
for (const prefetch of this.audioPrefetchMap_.values()) {
|
|
156
|
+
prefetch.clearAll();
|
|
157
|
+
}
|
|
143
158
|
|
|
144
159
|
await Promise.all(aborts);
|
|
145
160
|
|
|
146
161
|
this.mediaStates_.clear();
|
|
162
|
+
this.audioPrefetchMap_.clear();
|
|
147
163
|
|
|
148
164
|
this.playerInterface_ = null;
|
|
149
165
|
this.manifest_ = null;
|
|
@@ -180,6 +196,40 @@ shaka.media.StreamingEngine = class {
|
|
|
180
196
|
this.failureCallbackBackoff_ =
|
|
181
197
|
new shaka.net.Backoff(failureRetryParams, autoReset);
|
|
182
198
|
|
|
199
|
+
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
200
|
+
|
|
201
|
+
// disable audio segment prefetch if this is now set
|
|
202
|
+
if (config.disableAudioPrefetch) {
|
|
203
|
+
const state = this.mediaStates_.get(ContentType.AUDIO);
|
|
204
|
+
if (state && state.segmentPrefetch) {
|
|
205
|
+
state.segmentPrefetch.clearAll();
|
|
206
|
+
state.segmentPrefetch = null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
for (const stream of this.audioPrefetchMap_.keys()) {
|
|
210
|
+
const prefetch = this.audioPrefetchMap_.get(stream);
|
|
211
|
+
prefetch.clearAll();
|
|
212
|
+
this.audioPrefetchMap_.delete(stream);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// disable text segment prefetch if this is now set
|
|
216
|
+
if (config.disableTextPrefetch) {
|
|
217
|
+
const state = this.mediaStates_.get(ContentType.TEXT);
|
|
218
|
+
if (state && state.segmentPrefetch) {
|
|
219
|
+
state.segmentPrefetch.clearAll();
|
|
220
|
+
state.segmentPrefetch = null;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// disable video segment prefetch if this is now set
|
|
225
|
+
if (config.disableVideoPrefetch) {
|
|
226
|
+
const state = this.mediaStates_.get(ContentType.VIDEO);
|
|
227
|
+
if (state && state.segmentPrefetch) {
|
|
228
|
+
state.segmentPrefetch.clearAll();
|
|
229
|
+
state.segmentPrefetch = null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
183
233
|
// Allow configuring the segment prefetch in middle of the playback.
|
|
184
234
|
for (const type of this.mediaStates_.keys()) {
|
|
185
235
|
const state = this.mediaStates_.get(type);
|
|
@@ -194,6 +244,10 @@ shaka.media.StreamingEngine = class {
|
|
|
194
244
|
state.segmentPrefetch = this.createSegmentPrefetch_(state.stream);
|
|
195
245
|
}
|
|
196
246
|
}
|
|
247
|
+
|
|
248
|
+
if (!config.disableAudioPrefetch) {
|
|
249
|
+
this.updatePrefetchMapForAudio_();
|
|
250
|
+
}
|
|
197
251
|
}
|
|
198
252
|
|
|
199
253
|
|
|
@@ -205,14 +259,17 @@ shaka.media.StreamingEngine = class {
|
|
|
205
259
|
* chosen by a prior call to switchTextStream(). Once the Promise resolves,
|
|
206
260
|
* switch*() may be called freely.
|
|
207
261
|
*
|
|
262
|
+
* @param {!Map.<number, shaka.media.SegmentPrefetch>=} segmentPrefetchById
|
|
263
|
+
* If provided, segments prefetched for these streams will be used as needed
|
|
264
|
+
* during playback.
|
|
208
265
|
* @return {!Promise}
|
|
209
266
|
*/
|
|
210
|
-
async start() {
|
|
267
|
+
async start(segmentPrefetchById) {
|
|
211
268
|
goog.asserts.assert(this.config_,
|
|
212
269
|
'StreamingEngine configure() must be called before init()!');
|
|
213
270
|
|
|
214
271
|
// Setup the initial set of Streams and then begin each update cycle.
|
|
215
|
-
await this.initStreams_();
|
|
272
|
+
await this.initStreams_(segmentPrefetchById || (new Map()));
|
|
216
273
|
this.destroyer_.ensureNotDestroyed();
|
|
217
274
|
|
|
218
275
|
shaka.log.debug('init: completed initial Stream setup');
|
|
@@ -472,7 +529,9 @@ shaka.media.StreamingEngine = class {
|
|
|
472
529
|
return;
|
|
473
530
|
}
|
|
474
531
|
|
|
475
|
-
if (
|
|
532
|
+
if (this.audioPrefetchMap_.has(stream)) {
|
|
533
|
+
mediaState.segmentPrefetch = this.audioPrefetchMap_.get(stream);
|
|
534
|
+
} else if (mediaState.segmentPrefetch) {
|
|
476
535
|
mediaState.segmentPrefetch.switchStream(stream);
|
|
477
536
|
}
|
|
478
537
|
|
|
@@ -487,8 +546,11 @@ shaka.media.StreamingEngine = class {
|
|
|
487
546
|
}
|
|
488
547
|
|
|
489
548
|
// Releases the segmentIndex of the old stream.
|
|
490
|
-
|
|
491
|
-
|
|
549
|
+
// Do not close segment indexes we are prefetching.
|
|
550
|
+
if (!this.audioPrefetchMap_.has(mediaState.stream)) {
|
|
551
|
+
if (mediaState.stream.closeSegmentIndex) {
|
|
552
|
+
mediaState.stream.closeSegmentIndex();
|
|
553
|
+
}
|
|
492
554
|
}
|
|
493
555
|
|
|
494
556
|
mediaState.stream = stream;
|
|
@@ -789,10 +851,11 @@ shaka.media.StreamingEngine = class {
|
|
|
789
851
|
* Initializes the initial streams and media states. This will schedule
|
|
790
852
|
* updates for the given types.
|
|
791
853
|
*
|
|
854
|
+
* @param {!Map.<number, shaka.media.SegmentPrefetch>} segmentPrefetchById
|
|
792
855
|
* @return {!Promise}
|
|
793
856
|
* @private
|
|
794
857
|
*/
|
|
795
|
-
async initStreams_() {
|
|
858
|
+
async initStreams_(segmentPrefetchById) {
|
|
796
859
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
797
860
|
|
|
798
861
|
goog.asserts.assert(this.config_,
|
|
@@ -845,6 +908,15 @@ shaka.media.StreamingEngine = class {
|
|
|
845
908
|
const stream = streamsByType.get(type);
|
|
846
909
|
if (!this.mediaStates_.has(type)) {
|
|
847
910
|
const mediaState = this.createMediaState_(stream);
|
|
911
|
+
if (segmentPrefetchById.has(stream.id)) {
|
|
912
|
+
const segmentPrefetch = segmentPrefetchById.get(stream.id);
|
|
913
|
+
segmentPrefetch.replaceFetchDispatcher(
|
|
914
|
+
(reference, stream, streamDataCallback) => {
|
|
915
|
+
return this.dispatchFetch_(
|
|
916
|
+
reference, stream, streamDataCallback);
|
|
917
|
+
});
|
|
918
|
+
mediaState.segmentPrefetch = segmentPrefetch;
|
|
919
|
+
}
|
|
848
920
|
this.mediaStates_.set(type, mediaState);
|
|
849
921
|
this.scheduleUpdate_(mediaState, 0);
|
|
850
922
|
}
|
|
@@ -895,12 +967,37 @@ shaka.media.StreamingEngine = class {
|
|
|
895
967
|
* @private
|
|
896
968
|
*/
|
|
897
969
|
createSegmentPrefetch_(stream) {
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
970
|
+
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
971
|
+
if (stream.type === ContentType.VIDEO &&
|
|
972
|
+
this.config_.disableVideoPrefetch) {
|
|
973
|
+
return null;
|
|
974
|
+
}
|
|
975
|
+
if (stream.type === ContentType.AUDIO &&
|
|
976
|
+
this.config_.disableAudioPrefetch) {
|
|
902
977
|
return null;
|
|
903
978
|
}
|
|
979
|
+
const MimeUtils = shaka.util.MimeUtils;
|
|
980
|
+
const CEA608_MIME = MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE;
|
|
981
|
+
const CEA708_MIME = MimeUtils.CEA708_CLOSED_CAPTION_MIMETYPE;
|
|
982
|
+
if (stream.type === ContentType.TEXT &&
|
|
983
|
+
(stream.mimeType == CEA608_MIME || stream.mimeType == CEA708_MIME)) {
|
|
984
|
+
return null;
|
|
985
|
+
}
|
|
986
|
+
if (stream.type === ContentType.TEXT &&
|
|
987
|
+
this.config_.disableTextPrefetch) {
|
|
988
|
+
return null;
|
|
989
|
+
}
|
|
990
|
+
if (this.audioPrefetchMap_.has(stream)) {
|
|
991
|
+
return this.audioPrefetchMap_.get(stream);
|
|
992
|
+
}
|
|
993
|
+
const type = /** @type {!shaka.util.ManifestParserUtils.ContentType} */
|
|
994
|
+
(stream.type);
|
|
995
|
+
const mediaState = this.mediaStates_.get(type);
|
|
996
|
+
const currentSegmentPrefetch = mediaState && mediaState.segmentPrefetch;
|
|
997
|
+
if (currentSegmentPrefetch &&
|
|
998
|
+
stream === currentSegmentPrefetch.getStream()) {
|
|
999
|
+
return currentSegmentPrefetch;
|
|
1000
|
+
}
|
|
904
1001
|
if (this.config_.segmentPrefetchLimit > 0) {
|
|
905
1002
|
return new shaka.media.SegmentPrefetch(
|
|
906
1003
|
this.config_.segmentPrefetchLimit,
|
|
@@ -913,6 +1010,75 @@ shaka.media.StreamingEngine = class {
|
|
|
913
1010
|
return null;
|
|
914
1011
|
}
|
|
915
1012
|
|
|
1013
|
+
/**
|
|
1014
|
+
* Populates the prefetch map depending on the configuration
|
|
1015
|
+
* @private
|
|
1016
|
+
*/
|
|
1017
|
+
updatePrefetchMapForAudio_() {
|
|
1018
|
+
const prefetchLimit = this.config_.segmentPrefetchLimit;
|
|
1019
|
+
const prefetchLanguages = this.config_.prefetchAudioLanguages;
|
|
1020
|
+
const LanguageUtils = shaka.util.LanguageUtils;
|
|
1021
|
+
|
|
1022
|
+
for (const variant of this.manifest_.variants) {
|
|
1023
|
+
if (!variant.audio) {
|
|
1024
|
+
continue;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
if (this.audioPrefetchMap_.has(variant.audio)) {
|
|
1028
|
+
// if we already have a segment prefetch,
|
|
1029
|
+
// update it's prefetch limit and if the new limit isn't positive,
|
|
1030
|
+
// remove the segment prefetch from our prefetch map.
|
|
1031
|
+
const prefetch = this.audioPrefetchMap_.get(variant.audio);
|
|
1032
|
+
prefetch.resetLimit(prefetchLimit);
|
|
1033
|
+
if (!(prefetchLimit > 0) ||
|
|
1034
|
+
!prefetchLanguages.some(
|
|
1035
|
+
(lang) => LanguageUtils.areLanguageCompatible(
|
|
1036
|
+
variant.audio.language, lang))
|
|
1037
|
+
) {
|
|
1038
|
+
const type = /** @type {!shaka.util.ManifestParserUtils.ContentType}*/
|
|
1039
|
+
(variant.audio.type);
|
|
1040
|
+
const mediaState = this.mediaStates_.get(type);
|
|
1041
|
+
const currentSegmentPrefetch = mediaState &&
|
|
1042
|
+
mediaState.segmentPrefetch;
|
|
1043
|
+
// if this prefetch isn't the current one, we want to clear it
|
|
1044
|
+
if (prefetch !== currentSegmentPrefetch) {
|
|
1045
|
+
prefetch.clearAll();
|
|
1046
|
+
}
|
|
1047
|
+
this.audioPrefetchMap_.delete(variant.audio);
|
|
1048
|
+
}
|
|
1049
|
+
continue;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// don't try to create a new segment prefetch if the limit isn't positive.
|
|
1053
|
+
if (prefetchLimit <= 0) {
|
|
1054
|
+
continue;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
// only create a segment prefetch if its language is configured
|
|
1058
|
+
// to be prefetched
|
|
1059
|
+
if (!prefetchLanguages.some(
|
|
1060
|
+
(lang) => LanguageUtils.areLanguageCompatible(
|
|
1061
|
+
variant.audio.language, lang))) {
|
|
1062
|
+
continue;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
// use the helper to create a segment prefetch to ensure that existing
|
|
1066
|
+
// objects are reused.
|
|
1067
|
+
const segmentPrefetch = this.createSegmentPrefetch_(variant.audio);
|
|
1068
|
+
|
|
1069
|
+
// if a segment prefetch wasn't created, skip the rest
|
|
1070
|
+
if (!segmentPrefetch) {
|
|
1071
|
+
continue;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
if (!variant.audio.segmentIndex) {
|
|
1075
|
+
variant.audio.createSegmentIndex();
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
this.audioPrefetchMap_.set(variant.audio, segmentPrefetch);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
|
|
916
1082
|
/**
|
|
917
1083
|
* Sets the MediaSource's duration.
|
|
918
1084
|
*/
|
|
@@ -921,9 +1087,13 @@ shaka.media.StreamingEngine = class {
|
|
|
921
1087
|
if (duration < Infinity) {
|
|
922
1088
|
this.playerInterface_.mediaSourceEngine.setDuration(duration);
|
|
923
1089
|
} else {
|
|
1090
|
+
// To set the media source live duration as Infinity
|
|
1091
|
+
// If infiniteLiveStreamDuration as true
|
|
1092
|
+
const duration =
|
|
1093
|
+
this.config_.infiniteLiveStreamDuration ? Infinity : Math.pow(2, 32);
|
|
924
1094
|
// Not all platforms support infinite durations, so set a finite duration
|
|
925
1095
|
// so we can append segments and so the user agent can seek.
|
|
926
|
-
this.playerInterface_.mediaSourceEngine.setDuration(
|
|
1096
|
+
this.playerInterface_.mediaSourceEngine.setDuration(duration);
|
|
927
1097
|
}
|
|
928
1098
|
}
|
|
929
1099
|
|
|
@@ -1071,6 +1241,15 @@ shaka.media.StreamingEngine = class {
|
|
|
1071
1241
|
// Compute how far we've buffered ahead of the playhead.
|
|
1072
1242
|
const presentationTime = this.playerInterface_.getPresentationTime();
|
|
1073
1243
|
|
|
1244
|
+
if (mediaState.type === ContentType.AUDIO) {
|
|
1245
|
+
// evict all prefetched segments that are before the presentationTime
|
|
1246
|
+
for (const stream of this.audioPrefetchMap_.keys()) {
|
|
1247
|
+
const prefetch = this.audioPrefetchMap_.get(stream);
|
|
1248
|
+
prefetch.evict(presentationTime, /* clearInitSegments= */ true);
|
|
1249
|
+
prefetch.prefetchSegmentsByTime(presentationTime);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1074
1253
|
// Get the next timestamp we need.
|
|
1075
1254
|
const timeNeeded = this.getTimeNeeded_(mediaState, presentationTime);
|
|
1076
1255
|
shaka.log.v2(logPrefix, 'timeNeeded=' + timeNeeded);
|
|
@@ -1174,14 +1353,10 @@ shaka.media.StreamingEngine = class {
|
|
|
1174
1353
|
return this.config_.updateIntervalSeconds;
|
|
1175
1354
|
}
|
|
1176
1355
|
|
|
1177
|
-
if (mediaState.segmentPrefetch && mediaState.segmentIterator
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
initSegmentReference, mediaState.lastInitSegmentReference))) {
|
|
1182
|
-
mediaState.segmentPrefetch.prefetchInitSegment(initSegmentReference);
|
|
1183
|
-
}
|
|
1184
|
-
mediaState.segmentPrefetch.prefetchSegments(reference);
|
|
1356
|
+
if (mediaState.segmentPrefetch && mediaState.segmentIterator &&
|
|
1357
|
+
!this.audioPrefetchMap_.has(mediaState.stream)) {
|
|
1358
|
+
mediaState.segmentPrefetch.evict(presentationTime);
|
|
1359
|
+
mediaState.segmentPrefetch.prefetchSegmentsByTime(reference.startTime);
|
|
1185
1360
|
}
|
|
1186
1361
|
|
|
1187
1362
|
const p = this.fetchAndAppend_(mediaState, presentationTime, reference);
|
|
@@ -1390,8 +1565,8 @@ shaka.media.StreamingEngine = class {
|
|
|
1390
1565
|
/* isChunkedData= */ true);
|
|
1391
1566
|
|
|
1392
1567
|
if (mediaState.segmentPrefetch && mediaState.segmentIterator) {
|
|
1393
|
-
mediaState.segmentPrefetch.
|
|
1394
|
-
reference, /* skipFirst= */ true);
|
|
1568
|
+
mediaState.segmentPrefetch.prefetchSegmentsByTime(
|
|
1569
|
+
reference.startTime, /* skipFirst= */ true);
|
|
1395
1570
|
}
|
|
1396
1571
|
}
|
|
1397
1572
|
} catch (error) {
|
|
@@ -1431,8 +1606,8 @@ shaka.media.StreamingEngine = class {
|
|
|
1431
1606
|
}
|
|
1432
1607
|
|
|
1433
1608
|
if (mediaState.segmentPrefetch && mediaState.segmentIterator) {
|
|
1434
|
-
mediaState.segmentPrefetch.
|
|
1435
|
-
reference, /* skipFirst= */ true);
|
|
1609
|
+
mediaState.segmentPrefetch.prefetchSegmentsByTime(
|
|
1610
|
+
reference.startTime, /* skipFirst= */ true);
|
|
1436
1611
|
}
|
|
1437
1612
|
} else {
|
|
1438
1613
|
if (this.config_.lowLatencyMode && !isReadableStreamSupported) {
|
|
@@ -1528,18 +1703,18 @@ shaka.media.StreamingEngine = class {
|
|
|
1528
1703
|
|
|
1529
1704
|
/**
|
|
1530
1705
|
* @param {!BufferSource} rawResult
|
|
1531
|
-
* @param {shaka.extern.
|
|
1706
|
+
* @param {shaka.extern.aesKey} aesKey
|
|
1532
1707
|
* @param {number} position
|
|
1533
1708
|
* @return {!Promise.<!BufferSource>} finalResult
|
|
1534
1709
|
* @private
|
|
1535
1710
|
*/
|
|
1536
|
-
async
|
|
1537
|
-
const key =
|
|
1711
|
+
async aesDecrypt_(rawResult, aesKey, position) {
|
|
1712
|
+
const key = aesKey;
|
|
1538
1713
|
if (!key.cryptoKey) {
|
|
1539
|
-
goog.asserts.assert(key.fetchKey, 'If AES
|
|
1714
|
+
goog.asserts.assert(key.fetchKey, 'If AES cryptoKey was not ' +
|
|
1540
1715
|
'preloaded, fetchKey function should be provided');
|
|
1541
1716
|
await key.fetchKey();
|
|
1542
|
-
goog.asserts.assert(key.cryptoKey, 'AES
|
|
1717
|
+
goog.asserts.assert(key.cryptoKey, 'AES cryptoKey should now be set');
|
|
1543
1718
|
}
|
|
1544
1719
|
let iv = key.iv;
|
|
1545
1720
|
if (!iv) {
|
|
@@ -1550,8 +1725,22 @@ shaka.media.StreamingEngine = class {
|
|
|
1550
1725
|
sequence >>= 8;
|
|
1551
1726
|
}
|
|
1552
1727
|
}
|
|
1553
|
-
|
|
1554
|
-
|
|
1728
|
+
let algorithm;
|
|
1729
|
+
if (aesKey.blockCipherMode == 'CBC') {
|
|
1730
|
+
algorithm = {
|
|
1731
|
+
name: 'AES-CBC',
|
|
1732
|
+
iv,
|
|
1733
|
+
};
|
|
1734
|
+
} else {
|
|
1735
|
+
algorithm = {
|
|
1736
|
+
name: 'AES-CTR',
|
|
1737
|
+
counter: iv,
|
|
1738
|
+
// NIST SP800-38A standard suggests that the counter should occupy half
|
|
1739
|
+
// of the counter block
|
|
1740
|
+
length: 64,
|
|
1741
|
+
};
|
|
1742
|
+
}
|
|
1743
|
+
return window.crypto.subtle.decrypt(algorithm, key.cryptoKey, rawResult);
|
|
1555
1744
|
}
|
|
1556
1745
|
|
|
1557
1746
|
|
|
@@ -1733,9 +1922,16 @@ shaka.media.StreamingEngine = class {
|
|
|
1733
1922
|
otherState = this.mediaStates_.get(ContentType.VIDEO);
|
|
1734
1923
|
}
|
|
1735
1924
|
if (otherState) {
|
|
1925
|
+
// First, abort all operations in progress on the other stream.
|
|
1926
|
+
await this.abortOperations_(otherState).catch(() => {});
|
|
1927
|
+
// Then clear our cache of the last init segment, since MSE will be
|
|
1928
|
+
// reloaded and no init segment will be there post-reload.
|
|
1736
1929
|
otherState.lastInitSegmentReference = null;
|
|
1930
|
+
// Now force the existing buffer to be cleared. It is not necessary
|
|
1931
|
+
// to perform the MSE clear operation, but this has the side-effect
|
|
1932
|
+
// that our state for that stream will then match MSE's post-reload
|
|
1933
|
+
// state.
|
|
1737
1934
|
this.forceClearBuffer_(otherState);
|
|
1738
|
-
this.abortOperations_(otherState).catch(() => {});
|
|
1739
1935
|
}
|
|
1740
1936
|
}
|
|
1741
1937
|
|
|
@@ -1766,19 +1962,6 @@ shaka.media.StreamingEngine = class {
|
|
|
1766
1962
|
appendWindowEnd, ignoreTimestampOffset,
|
|
1767
1963
|
reference.mimeType || mediaState.stream.mimeType,
|
|
1768
1964
|
reference.codecs || mediaState.stream.codecs, streamsByType);
|
|
1769
|
-
if (isResetMediaSourceNecessary) {
|
|
1770
|
-
let otherState = null;
|
|
1771
|
-
if (mediaState.type === ContentType.VIDEO) {
|
|
1772
|
-
otherState = this.mediaStates_.get(ContentType.AUDIO);
|
|
1773
|
-
} else if (mediaState.type === ContentType.AUDIO) {
|
|
1774
|
-
otherState = this.mediaStates_.get(ContentType.VIDEO);
|
|
1775
|
-
}
|
|
1776
|
-
if (otherState) {
|
|
1777
|
-
otherState.waitingToClearBuffer = false;
|
|
1778
|
-
otherState.performingUpdate = false;
|
|
1779
|
-
this.forceClearBuffer_(otherState);
|
|
1780
|
-
}
|
|
1781
|
-
}
|
|
1782
1965
|
} catch (error) {
|
|
1783
1966
|
mediaState.lastAppendWindowStart = null;
|
|
1784
1967
|
mediaState.lastAppendWindowEnd = null;
|
|
@@ -1811,8 +1994,15 @@ shaka.media.StreamingEngine = class {
|
|
|
1811
1994
|
let lastTimescale = null;
|
|
1812
1995
|
const timescaleMap = new Map();
|
|
1813
1996
|
|
|
1997
|
+
/** @type {!shaka.extern.SpatialVideoInfo} */
|
|
1998
|
+
const spatialVideoInfo = {
|
|
1999
|
+
projection: null,
|
|
2000
|
+
hfov: null,
|
|
2001
|
+
};
|
|
2002
|
+
|
|
1814
2003
|
const parser = new shaka.util.Mp4Parser();
|
|
1815
2004
|
const Mp4Parser = shaka.util.Mp4Parser;
|
|
2005
|
+
const Mp4BoxParsers = shaka.util.Mp4BoxParsers;
|
|
1816
2006
|
parser.box('moov', Mp4Parser.children)
|
|
1817
2007
|
.box('trak', Mp4Parser.children)
|
|
1818
2008
|
.box('mdia', Mp4Parser.children)
|
|
@@ -1820,13 +2010,12 @@ shaka.media.StreamingEngine = class {
|
|
|
1820
2010
|
goog.asserts.assert(
|
|
1821
2011
|
box.version != null,
|
|
1822
2012
|
'MDHD is a full box and should have a valid version.');
|
|
1823
|
-
const parsedMDHDBox =
|
|
2013
|
+
const parsedMDHDBox = Mp4BoxParsers.parseMDHD(
|
|
1824
2014
|
box.reader, box.version);
|
|
1825
2015
|
lastTimescale = parsedMDHDBox.timescale;
|
|
1826
2016
|
})
|
|
1827
2017
|
.box('hdlr', (box) => {
|
|
1828
|
-
const parsedHDLR =
|
|
1829
|
-
box.reader);
|
|
2018
|
+
const parsedHDLR = Mp4BoxParsers.parseHDLR(box.reader);
|
|
1830
2019
|
switch (parsedHDLR.handlerType) {
|
|
1831
2020
|
case 'soun':
|
|
1832
2021
|
timescaleMap.set(ContentType.AUDIO, lastTimescale);
|
|
@@ -1837,8 +2026,32 @@ shaka.media.StreamingEngine = class {
|
|
|
1837
2026
|
}
|
|
1838
2027
|
lastTimescale = null;
|
|
1839
2028
|
})
|
|
2029
|
+
.box('minf', Mp4Parser.children)
|
|
2030
|
+
.box('stbl', Mp4Parser.children)
|
|
2031
|
+
.fullBox('stsd', Mp4Parser.sampleDescription)
|
|
2032
|
+
.box('encv', Mp4Parser.visualSampleEntry)
|
|
2033
|
+
.box('avc1', Mp4Parser.visualSampleEntry)
|
|
2034
|
+
.box('avc3', Mp4Parser.visualSampleEntry)
|
|
2035
|
+
.box('hev1', Mp4Parser.visualSampleEntry)
|
|
2036
|
+
.box('hvc1', Mp4Parser.visualSampleEntry)
|
|
2037
|
+
.box('dvav', Mp4Parser.visualSampleEntry)
|
|
2038
|
+
.box('dva1', Mp4Parser.visualSampleEntry)
|
|
2039
|
+
.box('dvh1', Mp4Parser.visualSampleEntry)
|
|
2040
|
+
.box('dvhe', Mp4Parser.visualSampleEntry)
|
|
2041
|
+
.box('vexu', Mp4Parser.children)
|
|
2042
|
+
.box('proj', Mp4Parser.children)
|
|
2043
|
+
.fullBox('prji', (box) => {
|
|
2044
|
+
const parsedPRJIBox = Mp4BoxParsers.parsePRJI(box.reader);
|
|
2045
|
+
spatialVideoInfo.projection = parsedPRJIBox.projection;
|
|
2046
|
+
})
|
|
2047
|
+
.box('hfov', (box) => {
|
|
2048
|
+
const parsedHFOVBox = Mp4BoxParsers.parseHFOV(box.reader);
|
|
2049
|
+
spatialVideoInfo.hfov = parsedHFOVBox.hfov;
|
|
2050
|
+
})
|
|
1840
2051
|
.parse(initSegment);
|
|
1841
2052
|
|
|
2053
|
+
this.updateSpatialVideoInfo_(spatialVideoInfo);
|
|
2054
|
+
|
|
1842
2055
|
if (timescaleMap.has(mediaState.type)) {
|
|
1843
2056
|
reference.initSegmentReference.timescale =
|
|
1844
2057
|
timescaleMap.get(mediaState.type);
|
|
@@ -2156,12 +2369,13 @@ shaka.media.StreamingEngine = class {
|
|
|
2156
2369
|
|
|
2157
2370
|
const overflow = bufferedBehind - bufferBehind;
|
|
2158
2371
|
// See: https://github.com/shaka-project/shaka-player/issues/6240
|
|
2159
|
-
if (overflow <=
|
|
2372
|
+
if (overflow <= this.config_.evictionGoal) {
|
|
2160
2373
|
shaka.log.v2(logPrefix,
|
|
2161
2374
|
'buffer behind okay:',
|
|
2162
2375
|
'presentationTime=' + presentationTime,
|
|
2163
2376
|
'bufferedBehind=' + bufferedBehind,
|
|
2164
2377
|
'bufferBehind=' + bufferBehind,
|
|
2378
|
+
'evictionGoal=' + this.config_.evictionGoal,
|
|
2165
2379
|
'underflow=' + Math.abs(overflow));
|
|
2166
2380
|
return;
|
|
2167
2381
|
}
|
|
@@ -2171,6 +2385,7 @@ shaka.media.StreamingEngine = class {
|
|
|
2171
2385
|
'presentationTime=' + presentationTime,
|
|
2172
2386
|
'bufferedBehind=' + bufferedBehind,
|
|
2173
2387
|
'bufferBehind=' + bufferBehind,
|
|
2388
|
+
'evictionGoal=' + this.config_.evictionGoal,
|
|
2174
2389
|
'overflow=' + overflow);
|
|
2175
2390
|
|
|
2176
2391
|
await this.playerInterface_.mediaSourceEngine.remove(mediaState.type,
|
|
@@ -2209,11 +2424,9 @@ shaka.media.StreamingEngine = class {
|
|
|
2209
2424
|
* @private
|
|
2210
2425
|
*/
|
|
2211
2426
|
async fetch_(mediaState, reference, streamDataCallback) {
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
return segmentData;
|
|
2216
|
-
}
|
|
2427
|
+
const segmentData = reference.getSegmentData();
|
|
2428
|
+
if (segmentData) {
|
|
2429
|
+
return segmentData;
|
|
2217
2430
|
}
|
|
2218
2431
|
let op = null;
|
|
2219
2432
|
if (mediaState.segmentPrefetch) {
|
|
@@ -2234,8 +2447,8 @@ shaka.media.StreamingEngine = class {
|
|
|
2234
2447
|
const response = await op.promise;
|
|
2235
2448
|
mediaState.operation = null;
|
|
2236
2449
|
let result = response.data;
|
|
2237
|
-
if (reference.
|
|
2238
|
-
result = await this.
|
|
2450
|
+
if (reference.aesKey) {
|
|
2451
|
+
result = await this.aesDecrypt_(result, reference.aesKey, position);
|
|
2239
2452
|
}
|
|
2240
2453
|
return result;
|
|
2241
2454
|
}
|
|
@@ -2252,6 +2465,27 @@ shaka.media.StreamingEngine = class {
|
|
|
2252
2465
|
* @private
|
|
2253
2466
|
*/
|
|
2254
2467
|
dispatchFetch_(reference, stream, streamDataCallback) {
|
|
2468
|
+
goog.asserts.assert(
|
|
2469
|
+
this.playerInterface_.netEngine, 'Must have net engine');
|
|
2470
|
+
return shaka.media.StreamingEngine.dispatchFetch(
|
|
2471
|
+
reference, stream, streamDataCallback || null,
|
|
2472
|
+
this.config_.retryParameters, this.playerInterface_.netEngine);
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
/**
|
|
2476
|
+
* Fetches the given segment.
|
|
2477
|
+
*
|
|
2478
|
+
* @param {!shaka.extern.Stream} stream
|
|
2479
|
+
* @param {(!shaka.media.InitSegmentReference|!shaka.media.SegmentReference)}
|
|
2480
|
+
* reference
|
|
2481
|
+
* @param {?function(BufferSource):!Promise} streamDataCallback
|
|
2482
|
+
* @param {shaka.extern.RetryParameters} retryParameters
|
|
2483
|
+
* @param {!shaka.net.NetworkingEngine} netEngine
|
|
2484
|
+
*
|
|
2485
|
+
* @return {!shaka.net.NetworkingEngine.PendingRequest}
|
|
2486
|
+
*/
|
|
2487
|
+
static dispatchFetch(
|
|
2488
|
+
reference, stream, streamDataCallback, retryParameters, netEngine) {
|
|
2255
2489
|
const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
|
|
2256
2490
|
const segment = reference instanceof shaka.media.SegmentReference ?
|
|
2257
2491
|
reference : undefined;
|
|
@@ -2262,17 +2496,13 @@ shaka.media.StreamingEngine = class {
|
|
|
2262
2496
|
reference.getUris(),
|
|
2263
2497
|
reference.startByte,
|
|
2264
2498
|
reference.endByte,
|
|
2265
|
-
|
|
2499
|
+
retryParameters,
|
|
2266
2500
|
streamDataCallback);
|
|
2501
|
+
request.contentType = stream.type;
|
|
2267
2502
|
|
|
2268
2503
|
shaka.log.v2('fetching: reference=', reference);
|
|
2269
2504
|
|
|
2270
|
-
return
|
|
2271
|
-
requestType, request, {
|
|
2272
|
-
type: type,
|
|
2273
|
-
stream: stream,
|
|
2274
|
-
segment: segment,
|
|
2275
|
-
});
|
|
2505
|
+
return netEngine.request(requestType, request, {type, stream, segment});
|
|
2276
2506
|
}
|
|
2277
2507
|
|
|
2278
2508
|
/**
|
|
@@ -2302,7 +2532,8 @@ shaka.media.StreamingEngine = class {
|
|
|
2302
2532
|
mediaState.segmentIterator = null;
|
|
2303
2533
|
|
|
2304
2534
|
shaka.log.debug(logPrefix, 'clearing buffer');
|
|
2305
|
-
if (mediaState.segmentPrefetch
|
|
2535
|
+
if (mediaState.segmentPrefetch &&
|
|
2536
|
+
!this.audioPrefetchMap_.has(mediaState.stream)) {
|
|
2306
2537
|
mediaState.segmentPrefetch.clearAll();
|
|
2307
2538
|
}
|
|
2308
2539
|
|
|
@@ -2496,6 +2727,31 @@ shaka.media.StreamingEngine = class {
|
|
|
2496
2727
|
return true;
|
|
2497
2728
|
}
|
|
2498
2729
|
|
|
2730
|
+
/**
|
|
2731
|
+
* Update the spatial video info and notify to the app.
|
|
2732
|
+
*
|
|
2733
|
+
* @param {shaka.extern.SpatialVideoInfo} info
|
|
2734
|
+
* @private
|
|
2735
|
+
*/
|
|
2736
|
+
updateSpatialVideoInfo_(info) {
|
|
2737
|
+
if (this.spatialVideoInfo_.projection != info.projection ||
|
|
2738
|
+
this.spatialVideoInfo_.hfov != info.hfov) {
|
|
2739
|
+
const EventName = shaka.util.FakeEvent.EventName;
|
|
2740
|
+
let event;
|
|
2741
|
+
if (info.projection != null || info.hfov != null) {
|
|
2742
|
+
const eventName = EventName.SpatialVideoInfoEvent;
|
|
2743
|
+
const data = (new Map()).set('detail', info);
|
|
2744
|
+
event = new shaka.util.FakeEvent(eventName, data);
|
|
2745
|
+
} else {
|
|
2746
|
+
const eventName = EventName.NoSpatialVideoInfoEvent;
|
|
2747
|
+
event = new shaka.util.FakeEvent(eventName);
|
|
2748
|
+
}
|
|
2749
|
+
event.cancelable = true;
|
|
2750
|
+
this.playerInterface_.onEvent(event);
|
|
2751
|
+
this.spatialVideoInfo_ = info;
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2499
2755
|
/**
|
|
2500
2756
|
* @param {shaka.media.StreamingEngine.MediaState_} mediaState
|
|
2501
2757
|
* @return {string} A log prefix of the form ($CONTENT_TYPE:$STREAM_ID), e.g.,
|