shaka-player 4.7.13 → 4.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AUTHORS +2 -0
- package/CONTRIBUTORS +2 -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 +41 -31
- package/dist/shaka-player.compiled.d.ts +196 -45
- package/dist/shaka-player.compiled.debug.d.ts +196 -45
- package/dist/shaka-player.compiled.debug.externs.js +491 -348
- package/dist/shaka-player.compiled.debug.js +969 -847
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +491 -348
- package/dist/shaka-player.compiled.js +1238 -1171
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +259 -48
- package/dist/shaka-player.ui.debug.d.ts +259 -48
- package/dist/shaka-player.ui.debug.externs.js +598 -377
- package/dist/shaka-player.ui.debug.js +1185 -980
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +598 -377
- package/dist/shaka-player.ui.js +1526 -1408
- 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 +237 -177
- 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 +45 -22
- 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 +93 -47
- package/lib/media/segment_prefetch.js +140 -43
- package/lib/media/segment_reference.js +43 -8
- package/lib/media/segment_utils.js +208 -25
- package/lib/media/streaming_engine.js +355 -66
- package/lib/mss/content_protection.js +31 -25
- package/lib/mss/mss_parser.js +79 -55
- package/lib/net/networking_engine.js +49 -4
- package/lib/offline/storage.js +3 -3
- package/lib/player.js +882 -672
- 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 +153 -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 +79 -13
- package/lib/util/stats.js +16 -0
- package/lib/util/stream_utils.js +22 -8
- 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');
|
|
@@ -303,6 +360,8 @@ shaka.media.StreamingEngine = class {
|
|
|
303
360
|
setTrickPlay(on) {
|
|
304
361
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
305
362
|
|
|
363
|
+
this.updateSegmentIteratorReverse_();
|
|
364
|
+
|
|
306
365
|
const mediaState = this.mediaStates_.get(ContentType.VIDEO);
|
|
307
366
|
if (!mediaState) {
|
|
308
367
|
return;
|
|
@@ -472,7 +531,9 @@ shaka.media.StreamingEngine = class {
|
|
|
472
531
|
return;
|
|
473
532
|
}
|
|
474
533
|
|
|
475
|
-
if (
|
|
534
|
+
if (this.audioPrefetchMap_.has(stream)) {
|
|
535
|
+
mediaState.segmentPrefetch = this.audioPrefetchMap_.get(stream);
|
|
536
|
+
} else if (mediaState.segmentPrefetch) {
|
|
476
537
|
mediaState.segmentPrefetch.switchStream(stream);
|
|
477
538
|
}
|
|
478
539
|
|
|
@@ -487,8 +548,11 @@ shaka.media.StreamingEngine = class {
|
|
|
487
548
|
}
|
|
488
549
|
|
|
489
550
|
// Releases the segmentIndex of the old stream.
|
|
490
|
-
|
|
491
|
-
|
|
551
|
+
// Do not close segment indexes we are prefetching.
|
|
552
|
+
if (!this.audioPrefetchMap_.has(mediaState.stream)) {
|
|
553
|
+
if (mediaState.stream.closeSegmentIndex) {
|
|
554
|
+
mediaState.stream.closeSegmentIndex();
|
|
555
|
+
}
|
|
492
556
|
}
|
|
493
557
|
|
|
494
558
|
mediaState.stream = stream;
|
|
@@ -789,10 +853,11 @@ shaka.media.StreamingEngine = class {
|
|
|
789
853
|
* Initializes the initial streams and media states. This will schedule
|
|
790
854
|
* updates for the given types.
|
|
791
855
|
*
|
|
856
|
+
* @param {!Map.<number, shaka.media.SegmentPrefetch>} segmentPrefetchById
|
|
792
857
|
* @return {!Promise}
|
|
793
858
|
* @private
|
|
794
859
|
*/
|
|
795
|
-
async initStreams_() {
|
|
860
|
+
async initStreams_(segmentPrefetchById) {
|
|
796
861
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
797
862
|
|
|
798
863
|
goog.asserts.assert(this.config_,
|
|
@@ -845,6 +910,15 @@ shaka.media.StreamingEngine = class {
|
|
|
845
910
|
const stream = streamsByType.get(type);
|
|
846
911
|
if (!this.mediaStates_.has(type)) {
|
|
847
912
|
const mediaState = this.createMediaState_(stream);
|
|
913
|
+
if (segmentPrefetchById.has(stream.id)) {
|
|
914
|
+
const segmentPrefetch = segmentPrefetchById.get(stream.id);
|
|
915
|
+
segmentPrefetch.replaceFetchDispatcher(
|
|
916
|
+
(reference, stream, streamDataCallback) => {
|
|
917
|
+
return this.dispatchFetch_(
|
|
918
|
+
reference, stream, streamDataCallback);
|
|
919
|
+
});
|
|
920
|
+
mediaState.segmentPrefetch = segmentPrefetch;
|
|
921
|
+
}
|
|
848
922
|
this.mediaStates_.set(type, mediaState);
|
|
849
923
|
this.scheduleUpdate_(mediaState, 0);
|
|
850
924
|
}
|
|
@@ -895,12 +969,37 @@ shaka.media.StreamingEngine = class {
|
|
|
895
969
|
* @private
|
|
896
970
|
*/
|
|
897
971
|
createSegmentPrefetch_(stream) {
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
972
|
+
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
973
|
+
if (stream.type === ContentType.VIDEO &&
|
|
974
|
+
this.config_.disableVideoPrefetch) {
|
|
975
|
+
return null;
|
|
976
|
+
}
|
|
977
|
+
if (stream.type === ContentType.AUDIO &&
|
|
978
|
+
this.config_.disableAudioPrefetch) {
|
|
979
|
+
return null;
|
|
980
|
+
}
|
|
981
|
+
const MimeUtils = shaka.util.MimeUtils;
|
|
982
|
+
const CEA608_MIME = MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE;
|
|
983
|
+
const CEA708_MIME = MimeUtils.CEA708_CLOSED_CAPTION_MIMETYPE;
|
|
984
|
+
if (stream.type === ContentType.TEXT &&
|
|
985
|
+
(stream.mimeType == CEA608_MIME || stream.mimeType == CEA708_MIME)) {
|
|
986
|
+
return null;
|
|
987
|
+
}
|
|
988
|
+
if (stream.type === ContentType.TEXT &&
|
|
989
|
+
this.config_.disableTextPrefetch) {
|
|
902
990
|
return null;
|
|
903
991
|
}
|
|
992
|
+
if (this.audioPrefetchMap_.has(stream)) {
|
|
993
|
+
return this.audioPrefetchMap_.get(stream);
|
|
994
|
+
}
|
|
995
|
+
const type = /** @type {!shaka.util.ManifestParserUtils.ContentType} */
|
|
996
|
+
(stream.type);
|
|
997
|
+
const mediaState = this.mediaStates_.get(type);
|
|
998
|
+
const currentSegmentPrefetch = mediaState && mediaState.segmentPrefetch;
|
|
999
|
+
if (currentSegmentPrefetch &&
|
|
1000
|
+
stream === currentSegmentPrefetch.getStream()) {
|
|
1001
|
+
return currentSegmentPrefetch;
|
|
1002
|
+
}
|
|
904
1003
|
if (this.config_.segmentPrefetchLimit > 0) {
|
|
905
1004
|
return new shaka.media.SegmentPrefetch(
|
|
906
1005
|
this.config_.segmentPrefetchLimit,
|
|
@@ -913,6 +1012,75 @@ shaka.media.StreamingEngine = class {
|
|
|
913
1012
|
return null;
|
|
914
1013
|
}
|
|
915
1014
|
|
|
1015
|
+
/**
|
|
1016
|
+
* Populates the prefetch map depending on the configuration
|
|
1017
|
+
* @private
|
|
1018
|
+
*/
|
|
1019
|
+
updatePrefetchMapForAudio_() {
|
|
1020
|
+
const prefetchLimit = this.config_.segmentPrefetchLimit;
|
|
1021
|
+
const prefetchLanguages = this.config_.prefetchAudioLanguages;
|
|
1022
|
+
const LanguageUtils = shaka.util.LanguageUtils;
|
|
1023
|
+
|
|
1024
|
+
for (const variant of this.manifest_.variants) {
|
|
1025
|
+
if (!variant.audio) {
|
|
1026
|
+
continue;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
if (this.audioPrefetchMap_.has(variant.audio)) {
|
|
1030
|
+
// if we already have a segment prefetch,
|
|
1031
|
+
// update it's prefetch limit and if the new limit isn't positive,
|
|
1032
|
+
// remove the segment prefetch from our prefetch map.
|
|
1033
|
+
const prefetch = this.audioPrefetchMap_.get(variant.audio);
|
|
1034
|
+
prefetch.resetLimit(prefetchLimit);
|
|
1035
|
+
if (!(prefetchLimit > 0) ||
|
|
1036
|
+
!prefetchLanguages.some(
|
|
1037
|
+
(lang) => LanguageUtils.areLanguageCompatible(
|
|
1038
|
+
variant.audio.language, lang))
|
|
1039
|
+
) {
|
|
1040
|
+
const type = /** @type {!shaka.util.ManifestParserUtils.ContentType}*/
|
|
1041
|
+
(variant.audio.type);
|
|
1042
|
+
const mediaState = this.mediaStates_.get(type);
|
|
1043
|
+
const currentSegmentPrefetch = mediaState &&
|
|
1044
|
+
mediaState.segmentPrefetch;
|
|
1045
|
+
// if this prefetch isn't the current one, we want to clear it
|
|
1046
|
+
if (prefetch !== currentSegmentPrefetch) {
|
|
1047
|
+
prefetch.clearAll();
|
|
1048
|
+
}
|
|
1049
|
+
this.audioPrefetchMap_.delete(variant.audio);
|
|
1050
|
+
}
|
|
1051
|
+
continue;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
// don't try to create a new segment prefetch if the limit isn't positive.
|
|
1055
|
+
if (prefetchLimit <= 0) {
|
|
1056
|
+
continue;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
// only create a segment prefetch if its language is configured
|
|
1060
|
+
// to be prefetched
|
|
1061
|
+
if (!prefetchLanguages.some(
|
|
1062
|
+
(lang) => LanguageUtils.areLanguageCompatible(
|
|
1063
|
+
variant.audio.language, lang))) {
|
|
1064
|
+
continue;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
// use the helper to create a segment prefetch to ensure that existing
|
|
1068
|
+
// objects are reused.
|
|
1069
|
+
const segmentPrefetch = this.createSegmentPrefetch_(variant.audio);
|
|
1070
|
+
|
|
1071
|
+
// if a segment prefetch wasn't created, skip the rest
|
|
1072
|
+
if (!segmentPrefetch) {
|
|
1073
|
+
continue;
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
if (!variant.audio.segmentIndex) {
|
|
1077
|
+
variant.audio.createSegmentIndex();
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
this.audioPrefetchMap_.set(variant.audio, segmentPrefetch);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
|
|
916
1084
|
/**
|
|
917
1085
|
* Sets the MediaSource's duration.
|
|
918
1086
|
*/
|
|
@@ -921,9 +1089,13 @@ shaka.media.StreamingEngine = class {
|
|
|
921
1089
|
if (duration < Infinity) {
|
|
922
1090
|
this.playerInterface_.mediaSourceEngine.setDuration(duration);
|
|
923
1091
|
} else {
|
|
1092
|
+
// To set the media source live duration as Infinity
|
|
1093
|
+
// If infiniteLiveStreamDuration as true
|
|
1094
|
+
const duration =
|
|
1095
|
+
this.config_.infiniteLiveStreamDuration ? Infinity : Math.pow(2, 32);
|
|
924
1096
|
// Not all platforms support infinite durations, so set a finite duration
|
|
925
1097
|
// so we can append segments and so the user agent can seek.
|
|
926
|
-
this.playerInterface_.mediaSourceEngine.setDuration(
|
|
1098
|
+
this.playerInterface_.mediaSourceEngine.setDuration(duration);
|
|
927
1099
|
}
|
|
928
1100
|
}
|
|
929
1101
|
|
|
@@ -1071,6 +1243,15 @@ shaka.media.StreamingEngine = class {
|
|
|
1071
1243
|
// Compute how far we've buffered ahead of the playhead.
|
|
1072
1244
|
const presentationTime = this.playerInterface_.getPresentationTime();
|
|
1073
1245
|
|
|
1246
|
+
if (mediaState.type === ContentType.AUDIO) {
|
|
1247
|
+
// evict all prefetched segments that are before the presentationTime
|
|
1248
|
+
for (const stream of this.audioPrefetchMap_.keys()) {
|
|
1249
|
+
const prefetch = this.audioPrefetchMap_.get(stream);
|
|
1250
|
+
prefetch.evict(presentationTime, /* clearInitSegments= */ true);
|
|
1251
|
+
prefetch.prefetchSegmentsByTime(presentationTime);
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1074
1255
|
// Get the next timestamp we need.
|
|
1075
1256
|
const timeNeeded = this.getTimeNeeded_(mediaState, presentationTime);
|
|
1076
1257
|
shaka.log.v2(logPrefix, 'timeNeeded=' + timeNeeded);
|
|
@@ -1174,14 +1355,10 @@ shaka.media.StreamingEngine = class {
|
|
|
1174
1355
|
return this.config_.updateIntervalSeconds;
|
|
1175
1356
|
}
|
|
1176
1357
|
|
|
1177
|
-
if (mediaState.segmentPrefetch && mediaState.segmentIterator
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
initSegmentReference, mediaState.lastInitSegmentReference))) {
|
|
1182
|
-
mediaState.segmentPrefetch.prefetchInitSegment(initSegmentReference);
|
|
1183
|
-
}
|
|
1184
|
-
mediaState.segmentPrefetch.prefetchSegments(reference);
|
|
1358
|
+
if (mediaState.segmentPrefetch && mediaState.segmentIterator &&
|
|
1359
|
+
!this.audioPrefetchMap_.has(mediaState.stream)) {
|
|
1360
|
+
mediaState.segmentPrefetch.evict(presentationTime);
|
|
1361
|
+
mediaState.segmentPrefetch.prefetchSegmentsByTime(reference.startTime);
|
|
1185
1362
|
}
|
|
1186
1363
|
|
|
1187
1364
|
const p = this.fetchAndAppend_(mediaState, presentationTime, reference);
|
|
@@ -1247,8 +1424,10 @@ shaka.media.StreamingEngine = class {
|
|
|
1247
1424
|
shaka.log.v1(
|
|
1248
1425
|
logPrefix, 'looking up segment from new stream endTime:', time);
|
|
1249
1426
|
|
|
1427
|
+
const reverse = this.playerInterface_.getPlaybackRate() < 0;
|
|
1250
1428
|
mediaState.segmentIterator =
|
|
1251
|
-
mediaState.stream.segmentIndex.getIteratorForTime(
|
|
1429
|
+
mediaState.stream.segmentIndex.getIteratorForTime(
|
|
1430
|
+
time, /* allowNonIndepedent= */ false, reverse);
|
|
1252
1431
|
const ref = mediaState.segmentIterator &&
|
|
1253
1432
|
mediaState.segmentIterator.next().value;
|
|
1254
1433
|
if (ref == null) {
|
|
@@ -1268,10 +1447,12 @@ shaka.media.StreamingEngine = class {
|
|
|
1268
1447
|
'lookupTime:', lookupTime,
|
|
1269
1448
|
'presentationTime:', presentationTime);
|
|
1270
1449
|
|
|
1450
|
+
const reverse = this.playerInterface_.getPlaybackRate() < 0;
|
|
1271
1451
|
let ref = null;
|
|
1272
1452
|
if (inaccurateTolerance) {
|
|
1273
1453
|
mediaState.segmentIterator =
|
|
1274
|
-
mediaState.stream.segmentIndex.getIteratorForTime(
|
|
1454
|
+
mediaState.stream.segmentIndex.getIteratorForTime(
|
|
1455
|
+
lookupTime, /* allowNonIndepedent= */ false, reverse);
|
|
1275
1456
|
ref = mediaState.segmentIterator &&
|
|
1276
1457
|
mediaState.segmentIterator.next().value;
|
|
1277
1458
|
}
|
|
@@ -1279,7 +1460,8 @@ shaka.media.StreamingEngine = class {
|
|
|
1279
1460
|
// If we can't find a valid segment with the drifted time, look for a
|
|
1280
1461
|
// segment with the presentation time.
|
|
1281
1462
|
mediaState.segmentIterator =
|
|
1282
|
-
mediaState.stream.segmentIndex.getIteratorForTime(
|
|
1463
|
+
mediaState.stream.segmentIndex.getIteratorForTime(
|
|
1464
|
+
presentationTime, /* allowNonIndepedent= */ false, reverse);
|
|
1283
1465
|
ref = mediaState.segmentIterator &&
|
|
1284
1466
|
mediaState.segmentIterator.next().value;
|
|
1285
1467
|
}
|
|
@@ -1390,8 +1572,8 @@ shaka.media.StreamingEngine = class {
|
|
|
1390
1572
|
/* isChunkedData= */ true);
|
|
1391
1573
|
|
|
1392
1574
|
if (mediaState.segmentPrefetch && mediaState.segmentIterator) {
|
|
1393
|
-
mediaState.segmentPrefetch.
|
|
1394
|
-
reference, /* skipFirst= */ true);
|
|
1575
|
+
mediaState.segmentPrefetch.prefetchSegmentsByTime(
|
|
1576
|
+
reference.startTime, /* skipFirst= */ true);
|
|
1395
1577
|
}
|
|
1396
1578
|
}
|
|
1397
1579
|
} catch (error) {
|
|
@@ -1431,8 +1613,8 @@ shaka.media.StreamingEngine = class {
|
|
|
1431
1613
|
}
|
|
1432
1614
|
|
|
1433
1615
|
if (mediaState.segmentPrefetch && mediaState.segmentIterator) {
|
|
1434
|
-
mediaState.segmentPrefetch.
|
|
1435
|
-
reference, /* skipFirst= */ true);
|
|
1616
|
+
mediaState.segmentPrefetch.prefetchSegmentsByTime(
|
|
1617
|
+
reference.startTime, /* skipFirst= */ true);
|
|
1436
1618
|
}
|
|
1437
1619
|
} else {
|
|
1438
1620
|
if (this.config_.lowLatencyMode && !isReadableStreamSupported) {
|
|
@@ -1528,18 +1710,18 @@ shaka.media.StreamingEngine = class {
|
|
|
1528
1710
|
|
|
1529
1711
|
/**
|
|
1530
1712
|
* @param {!BufferSource} rawResult
|
|
1531
|
-
* @param {shaka.extern.
|
|
1713
|
+
* @param {shaka.extern.aesKey} aesKey
|
|
1532
1714
|
* @param {number} position
|
|
1533
1715
|
* @return {!Promise.<!BufferSource>} finalResult
|
|
1534
1716
|
* @private
|
|
1535
1717
|
*/
|
|
1536
|
-
async
|
|
1537
|
-
const key =
|
|
1718
|
+
async aesDecrypt_(rawResult, aesKey, position) {
|
|
1719
|
+
const key = aesKey;
|
|
1538
1720
|
if (!key.cryptoKey) {
|
|
1539
|
-
goog.asserts.assert(key.fetchKey, 'If AES
|
|
1721
|
+
goog.asserts.assert(key.fetchKey, 'If AES cryptoKey was not ' +
|
|
1540
1722
|
'preloaded, fetchKey function should be provided');
|
|
1541
1723
|
await key.fetchKey();
|
|
1542
|
-
goog.asserts.assert(key.cryptoKey, 'AES
|
|
1724
|
+
goog.asserts.assert(key.cryptoKey, 'AES cryptoKey should now be set');
|
|
1543
1725
|
}
|
|
1544
1726
|
let iv = key.iv;
|
|
1545
1727
|
if (!iv) {
|
|
@@ -1550,8 +1732,22 @@ shaka.media.StreamingEngine = class {
|
|
|
1550
1732
|
sequence >>= 8;
|
|
1551
1733
|
}
|
|
1552
1734
|
}
|
|
1553
|
-
|
|
1554
|
-
|
|
1735
|
+
let algorithm;
|
|
1736
|
+
if (aesKey.blockCipherMode == 'CBC') {
|
|
1737
|
+
algorithm = {
|
|
1738
|
+
name: 'AES-CBC',
|
|
1739
|
+
iv,
|
|
1740
|
+
};
|
|
1741
|
+
} else {
|
|
1742
|
+
algorithm = {
|
|
1743
|
+
name: 'AES-CTR',
|
|
1744
|
+
counter: iv,
|
|
1745
|
+
// NIST SP800-38A standard suggests that the counter should occupy half
|
|
1746
|
+
// of the counter block
|
|
1747
|
+
length: 64,
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
return window.crypto.subtle.decrypt(algorithm, key.cryptoKey, rawResult);
|
|
1555
1751
|
}
|
|
1556
1752
|
|
|
1557
1753
|
|
|
@@ -1733,9 +1929,16 @@ shaka.media.StreamingEngine = class {
|
|
|
1733
1929
|
otherState = this.mediaStates_.get(ContentType.VIDEO);
|
|
1734
1930
|
}
|
|
1735
1931
|
if (otherState) {
|
|
1932
|
+
// First, abort all operations in progress on the other stream.
|
|
1933
|
+
await this.abortOperations_(otherState).catch(() => {});
|
|
1934
|
+
// Then clear our cache of the last init segment, since MSE will be
|
|
1935
|
+
// reloaded and no init segment will be there post-reload.
|
|
1736
1936
|
otherState.lastInitSegmentReference = null;
|
|
1937
|
+
// Now force the existing buffer to be cleared. It is not necessary
|
|
1938
|
+
// to perform the MSE clear operation, but this has the side-effect
|
|
1939
|
+
// that our state for that stream will then match MSE's post-reload
|
|
1940
|
+
// state.
|
|
1737
1941
|
this.forceClearBuffer_(otherState);
|
|
1738
|
-
this.abortOperations_(otherState).catch(() => {});
|
|
1739
1942
|
}
|
|
1740
1943
|
}
|
|
1741
1944
|
|
|
@@ -1766,19 +1969,6 @@ shaka.media.StreamingEngine = class {
|
|
|
1766
1969
|
appendWindowEnd, ignoreTimestampOffset,
|
|
1767
1970
|
reference.mimeType || mediaState.stream.mimeType,
|
|
1768
1971
|
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
1972
|
} catch (error) {
|
|
1783
1973
|
mediaState.lastAppendWindowStart = null;
|
|
1784
1974
|
mediaState.lastAppendWindowEnd = null;
|
|
@@ -1811,8 +2001,15 @@ shaka.media.StreamingEngine = class {
|
|
|
1811
2001
|
let lastTimescale = null;
|
|
1812
2002
|
const timescaleMap = new Map();
|
|
1813
2003
|
|
|
2004
|
+
/** @type {!shaka.extern.SpatialVideoInfo} */
|
|
2005
|
+
const spatialVideoInfo = {
|
|
2006
|
+
projection: null,
|
|
2007
|
+
hfov: null,
|
|
2008
|
+
};
|
|
2009
|
+
|
|
1814
2010
|
const parser = new shaka.util.Mp4Parser();
|
|
1815
2011
|
const Mp4Parser = shaka.util.Mp4Parser;
|
|
2012
|
+
const Mp4BoxParsers = shaka.util.Mp4BoxParsers;
|
|
1816
2013
|
parser.box('moov', Mp4Parser.children)
|
|
1817
2014
|
.box('trak', Mp4Parser.children)
|
|
1818
2015
|
.box('mdia', Mp4Parser.children)
|
|
@@ -1820,13 +2017,12 @@ shaka.media.StreamingEngine = class {
|
|
|
1820
2017
|
goog.asserts.assert(
|
|
1821
2018
|
box.version != null,
|
|
1822
2019
|
'MDHD is a full box and should have a valid version.');
|
|
1823
|
-
const parsedMDHDBox =
|
|
2020
|
+
const parsedMDHDBox = Mp4BoxParsers.parseMDHD(
|
|
1824
2021
|
box.reader, box.version);
|
|
1825
2022
|
lastTimescale = parsedMDHDBox.timescale;
|
|
1826
2023
|
})
|
|
1827
2024
|
.box('hdlr', (box) => {
|
|
1828
|
-
const parsedHDLR =
|
|
1829
|
-
box.reader);
|
|
2025
|
+
const parsedHDLR = Mp4BoxParsers.parseHDLR(box.reader);
|
|
1830
2026
|
switch (parsedHDLR.handlerType) {
|
|
1831
2027
|
case 'soun':
|
|
1832
2028
|
timescaleMap.set(ContentType.AUDIO, lastTimescale);
|
|
@@ -1837,8 +2033,32 @@ shaka.media.StreamingEngine = class {
|
|
|
1837
2033
|
}
|
|
1838
2034
|
lastTimescale = null;
|
|
1839
2035
|
})
|
|
2036
|
+
.box('minf', Mp4Parser.children)
|
|
2037
|
+
.box('stbl', Mp4Parser.children)
|
|
2038
|
+
.fullBox('stsd', Mp4Parser.sampleDescription)
|
|
2039
|
+
.box('encv', Mp4Parser.visualSampleEntry)
|
|
2040
|
+
.box('avc1', Mp4Parser.visualSampleEntry)
|
|
2041
|
+
.box('avc3', Mp4Parser.visualSampleEntry)
|
|
2042
|
+
.box('hev1', Mp4Parser.visualSampleEntry)
|
|
2043
|
+
.box('hvc1', Mp4Parser.visualSampleEntry)
|
|
2044
|
+
.box('dvav', Mp4Parser.visualSampleEntry)
|
|
2045
|
+
.box('dva1', Mp4Parser.visualSampleEntry)
|
|
2046
|
+
.box('dvh1', Mp4Parser.visualSampleEntry)
|
|
2047
|
+
.box('dvhe', Mp4Parser.visualSampleEntry)
|
|
2048
|
+
.box('vexu', Mp4Parser.children)
|
|
2049
|
+
.box('proj', Mp4Parser.children)
|
|
2050
|
+
.fullBox('prji', (box) => {
|
|
2051
|
+
const parsedPRJIBox = Mp4BoxParsers.parsePRJI(box.reader);
|
|
2052
|
+
spatialVideoInfo.projection = parsedPRJIBox.projection;
|
|
2053
|
+
})
|
|
2054
|
+
.box('hfov', (box) => {
|
|
2055
|
+
const parsedHFOVBox = Mp4BoxParsers.parseHFOV(box.reader);
|
|
2056
|
+
spatialVideoInfo.hfov = parsedHFOVBox.hfov;
|
|
2057
|
+
})
|
|
1840
2058
|
.parse(initSegment);
|
|
1841
2059
|
|
|
2060
|
+
this.updateSpatialVideoInfo_(spatialVideoInfo);
|
|
2061
|
+
|
|
1842
2062
|
if (timescaleMap.has(mediaState.type)) {
|
|
1843
2063
|
reference.initSegmentReference.timescale =
|
|
1844
2064
|
timescaleMap.get(mediaState.type);
|
|
@@ -2156,12 +2376,13 @@ shaka.media.StreamingEngine = class {
|
|
|
2156
2376
|
|
|
2157
2377
|
const overflow = bufferedBehind - bufferBehind;
|
|
2158
2378
|
// See: https://github.com/shaka-project/shaka-player/issues/6240
|
|
2159
|
-
if (overflow <=
|
|
2379
|
+
if (overflow <= this.config_.evictionGoal) {
|
|
2160
2380
|
shaka.log.v2(logPrefix,
|
|
2161
2381
|
'buffer behind okay:',
|
|
2162
2382
|
'presentationTime=' + presentationTime,
|
|
2163
2383
|
'bufferedBehind=' + bufferedBehind,
|
|
2164
2384
|
'bufferBehind=' + bufferBehind,
|
|
2385
|
+
'evictionGoal=' + this.config_.evictionGoal,
|
|
2165
2386
|
'underflow=' + Math.abs(overflow));
|
|
2166
2387
|
return;
|
|
2167
2388
|
}
|
|
@@ -2171,6 +2392,7 @@ shaka.media.StreamingEngine = class {
|
|
|
2171
2392
|
'presentationTime=' + presentationTime,
|
|
2172
2393
|
'bufferedBehind=' + bufferedBehind,
|
|
2173
2394
|
'bufferBehind=' + bufferBehind,
|
|
2395
|
+
'evictionGoal=' + this.config_.evictionGoal,
|
|
2174
2396
|
'overflow=' + overflow);
|
|
2175
2397
|
|
|
2176
2398
|
await this.playerInterface_.mediaSourceEngine.remove(mediaState.type,
|
|
@@ -2209,11 +2431,9 @@ shaka.media.StreamingEngine = class {
|
|
|
2209
2431
|
* @private
|
|
2210
2432
|
*/
|
|
2211
2433
|
async fetch_(mediaState, reference, streamDataCallback) {
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
return segmentData;
|
|
2216
|
-
}
|
|
2434
|
+
const segmentData = reference.getSegmentData();
|
|
2435
|
+
if (segmentData) {
|
|
2436
|
+
return segmentData;
|
|
2217
2437
|
}
|
|
2218
2438
|
let op = null;
|
|
2219
2439
|
if (mediaState.segmentPrefetch) {
|
|
@@ -2234,8 +2454,8 @@ shaka.media.StreamingEngine = class {
|
|
|
2234
2454
|
const response = await op.promise;
|
|
2235
2455
|
mediaState.operation = null;
|
|
2236
2456
|
let result = response.data;
|
|
2237
|
-
if (reference.
|
|
2238
|
-
result = await this.
|
|
2457
|
+
if (reference.aesKey) {
|
|
2458
|
+
result = await this.aesDecrypt_(result, reference.aesKey, position);
|
|
2239
2459
|
}
|
|
2240
2460
|
return result;
|
|
2241
2461
|
}
|
|
@@ -2252,6 +2472,27 @@ shaka.media.StreamingEngine = class {
|
|
|
2252
2472
|
* @private
|
|
2253
2473
|
*/
|
|
2254
2474
|
dispatchFetch_(reference, stream, streamDataCallback) {
|
|
2475
|
+
goog.asserts.assert(
|
|
2476
|
+
this.playerInterface_.netEngine, 'Must have net engine');
|
|
2477
|
+
return shaka.media.StreamingEngine.dispatchFetch(
|
|
2478
|
+
reference, stream, streamDataCallback || null,
|
|
2479
|
+
this.config_.retryParameters, this.playerInterface_.netEngine);
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
/**
|
|
2483
|
+
* Fetches the given segment.
|
|
2484
|
+
*
|
|
2485
|
+
* @param {!shaka.extern.Stream} stream
|
|
2486
|
+
* @param {(!shaka.media.InitSegmentReference|!shaka.media.SegmentReference)}
|
|
2487
|
+
* reference
|
|
2488
|
+
* @param {?function(BufferSource):!Promise} streamDataCallback
|
|
2489
|
+
* @param {shaka.extern.RetryParameters} retryParameters
|
|
2490
|
+
* @param {!shaka.net.NetworkingEngine} netEngine
|
|
2491
|
+
*
|
|
2492
|
+
* @return {!shaka.net.NetworkingEngine.PendingRequest}
|
|
2493
|
+
*/
|
|
2494
|
+
static dispatchFetch(
|
|
2495
|
+
reference, stream, streamDataCallback, retryParameters, netEngine) {
|
|
2255
2496
|
const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
|
|
2256
2497
|
const segment = reference instanceof shaka.media.SegmentReference ?
|
|
2257
2498
|
reference : undefined;
|
|
@@ -2262,17 +2503,13 @@ shaka.media.StreamingEngine = class {
|
|
|
2262
2503
|
reference.getUris(),
|
|
2263
2504
|
reference.startByte,
|
|
2264
2505
|
reference.endByte,
|
|
2265
|
-
|
|
2506
|
+
retryParameters,
|
|
2266
2507
|
streamDataCallback);
|
|
2508
|
+
request.contentType = stream.type;
|
|
2267
2509
|
|
|
2268
2510
|
shaka.log.v2('fetching: reference=', reference);
|
|
2269
2511
|
|
|
2270
|
-
return
|
|
2271
|
-
requestType, request, {
|
|
2272
|
-
type: type,
|
|
2273
|
-
stream: stream,
|
|
2274
|
-
segment: segment,
|
|
2275
|
-
});
|
|
2512
|
+
return netEngine.request(requestType, request, {type, stream, segment});
|
|
2276
2513
|
}
|
|
2277
2514
|
|
|
2278
2515
|
/**
|
|
@@ -2302,7 +2539,8 @@ shaka.media.StreamingEngine = class {
|
|
|
2302
2539
|
mediaState.segmentIterator = null;
|
|
2303
2540
|
|
|
2304
2541
|
shaka.log.debug(logPrefix, 'clearing buffer');
|
|
2305
|
-
if (mediaState.segmentPrefetch
|
|
2542
|
+
if (mediaState.segmentPrefetch &&
|
|
2543
|
+
!this.audioPrefetchMap_.has(mediaState.stream)) {
|
|
2306
2544
|
mediaState.segmentPrefetch.clearAll();
|
|
2307
2545
|
}
|
|
2308
2546
|
|
|
@@ -2496,6 +2734,54 @@ shaka.media.StreamingEngine = class {
|
|
|
2496
2734
|
return true;
|
|
2497
2735
|
}
|
|
2498
2736
|
|
|
2737
|
+
/**
|
|
2738
|
+
* Update the spatial video info and notify to the app.
|
|
2739
|
+
*
|
|
2740
|
+
* @param {shaka.extern.SpatialVideoInfo} info
|
|
2741
|
+
* @private
|
|
2742
|
+
*/
|
|
2743
|
+
updateSpatialVideoInfo_(info) {
|
|
2744
|
+
if (this.spatialVideoInfo_.projection != info.projection ||
|
|
2745
|
+
this.spatialVideoInfo_.hfov != info.hfov) {
|
|
2746
|
+
const EventName = shaka.util.FakeEvent.EventName;
|
|
2747
|
+
let event;
|
|
2748
|
+
if (info.projection != null || info.hfov != null) {
|
|
2749
|
+
const eventName = EventName.SpatialVideoInfoEvent;
|
|
2750
|
+
const data = (new Map()).set('detail', info);
|
|
2751
|
+
event = new shaka.util.FakeEvent(eventName, data);
|
|
2752
|
+
} else {
|
|
2753
|
+
const eventName = EventName.NoSpatialVideoInfoEvent;
|
|
2754
|
+
event = new shaka.util.FakeEvent(eventName);
|
|
2755
|
+
}
|
|
2756
|
+
event.cancelable = true;
|
|
2757
|
+
this.playerInterface_.onEvent(event);
|
|
2758
|
+
this.spatialVideoInfo_ = info;
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2761
|
+
|
|
2762
|
+
/**
|
|
2763
|
+
* Update the segment iterator direction.
|
|
2764
|
+
*
|
|
2765
|
+
* @private
|
|
2766
|
+
*/
|
|
2767
|
+
updateSegmentIteratorReverse_() {
|
|
2768
|
+
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
2769
|
+
|
|
2770
|
+
const reverse = this.playerInterface_.getPlaybackRate() < 0;
|
|
2771
|
+
const videoState = this.mediaStates_.get(ContentType.VIDEO);
|
|
2772
|
+
if (videoState && videoState.segmentIterator) {
|
|
2773
|
+
videoState.segmentIterator.setReverse(reverse);
|
|
2774
|
+
}
|
|
2775
|
+
const audioState = this.mediaStates_.get(ContentType.AUDIO);
|
|
2776
|
+
if (audioState && audioState.segmentIterator) {
|
|
2777
|
+
audioState.segmentIterator.setReverse(reverse);
|
|
2778
|
+
}
|
|
2779
|
+
const textState = this.mediaStates_.get(ContentType.TEXT);
|
|
2780
|
+
if (textState && textState.segmentIterator) {
|
|
2781
|
+
textState.segmentIterator.setReverse(reverse);
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
|
|
2499
2785
|
/**
|
|
2500
2786
|
* @param {shaka.media.StreamingEngine.MediaState_} mediaState
|
|
2501
2787
|
* @return {string} A log prefix of the form ($CONTENT_TYPE:$STREAM_ID), e.g.,
|
|
@@ -2512,6 +2798,7 @@ shaka.media.StreamingEngine = class {
|
|
|
2512
2798
|
* @typedef {{
|
|
2513
2799
|
* getPresentationTime: function():number,
|
|
2514
2800
|
* getBandwidthEstimate: function():number,
|
|
2801
|
+
* getPlaybackRate: function():number,
|
|
2515
2802
|
* mediaSourceEngine: !shaka.media.MediaSourceEngine,
|
|
2516
2803
|
* netEngine: shaka.net.NetworkingEngine,
|
|
2517
2804
|
* onError: function(!shaka.util.Error),
|
|
@@ -2531,6 +2818,8 @@ shaka.media.StreamingEngine = class {
|
|
|
2531
2818
|
* viewer is seeing on screen right now.
|
|
2532
2819
|
* @property {function():number} getBandwidthEstimate
|
|
2533
2820
|
* Get the estimated bandwidth in bits per second.
|
|
2821
|
+
* @property {function():number} getPlaybackRate
|
|
2822
|
+
* Get the playback rate
|
|
2534
2823
|
* @property {!shaka.media.MediaSourceEngine} mediaSourceEngine
|
|
2535
2824
|
* The MediaSourceEngine. The caller retains ownership.
|
|
2536
2825
|
* @property {shaka.net.NetworkingEngine} netEngine
|