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.
Files changed (114) hide show
  1. package/AUTHORS +2 -0
  2. package/CONTRIBUTORS +2 -0
  3. package/LICENSE +54 -0
  4. package/README.md +22 -1
  5. package/dist/controls.css +1 -1
  6. package/dist/controls.css.map +1 -1
  7. package/dist/deps.js +34 -23
  8. package/dist/locales.js +41 -31
  9. package/dist/shaka-player.compiled.d.ts +196 -45
  10. package/dist/shaka-player.compiled.debug.d.ts +196 -45
  11. package/dist/shaka-player.compiled.debug.externs.js +491 -348
  12. package/dist/shaka-player.compiled.debug.js +969 -847
  13. package/dist/shaka-player.compiled.debug.map +4 -4
  14. package/dist/shaka-player.compiled.externs.js +491 -348
  15. package/dist/shaka-player.compiled.js +1238 -1171
  16. package/dist/shaka-player.compiled.map +4 -4
  17. package/dist/shaka-player.ui.d.ts +259 -48
  18. package/dist/shaka-player.ui.debug.d.ts +259 -48
  19. package/dist/shaka-player.ui.debug.externs.js +598 -377
  20. package/dist/shaka-player.ui.debug.js +1185 -980
  21. package/dist/shaka-player.ui.debug.map +4 -4
  22. package/dist/shaka-player.ui.externs.js +598 -377
  23. package/dist/shaka-player.ui.js +1526 -1408
  24. package/dist/shaka-player.ui.map +4 -4
  25. package/externs/device_sensor_event.js +18 -0
  26. package/externs/hisense.js +16 -0
  27. package/externs/playstation.js +31 -0
  28. package/externs/shaka/abr_manager.js +3 -1
  29. package/externs/shaka/ads.js +5 -0
  30. package/externs/shaka/codecs.js +13 -0
  31. package/externs/shaka/manifest.js +58 -6
  32. package/externs/shaka/net.js +13 -1
  33. package/externs/shaka/player.js +184 -13
  34. package/externs/shaka/text.js +19 -0
  35. package/externs/tizen.js +41 -0
  36. package/externs/webos.js +19 -0
  37. package/externs/xbox.js +43 -0
  38. package/lib/abr/simple_abr_manager.js +9 -1
  39. package/lib/ads/ad_manager.js +19 -2
  40. package/lib/ads/client_side_ad_manager.js +5 -3
  41. package/lib/ads/media_tailor_ad_manager.js +9 -0
  42. package/lib/cast/cast_receiver.js +0 -14
  43. package/lib/cast/cast_utils.js +1 -0
  44. package/lib/cea/mp4_cea_parser.js +6 -1
  45. package/lib/dash/content_protection.js +97 -53
  46. package/lib/dash/dash_parser.js +237 -177
  47. package/lib/dash/mpd_utils.js +72 -65
  48. package/lib/dash/segment_base.js +27 -21
  49. package/lib/dash/segment_list.js +18 -16
  50. package/lib/dash/segment_template.js +45 -22
  51. package/lib/hls/hls_parser.js +358 -164
  52. package/lib/media/buffering_observer.js +25 -1
  53. package/lib/media/drm_engine.js +91 -71
  54. package/lib/media/manifest_filterer.js +220 -0
  55. package/lib/media/media_source_engine.js +34 -5
  56. package/lib/media/preload_manager.js +827 -0
  57. package/lib/media/presentation_timeline.js +11 -0
  58. package/lib/media/segment_index.js +93 -47
  59. package/lib/media/segment_prefetch.js +140 -43
  60. package/lib/media/segment_reference.js +43 -8
  61. package/lib/media/segment_utils.js +208 -25
  62. package/lib/media/streaming_engine.js +355 -66
  63. package/lib/mss/content_protection.js +31 -25
  64. package/lib/mss/mss_parser.js +79 -55
  65. package/lib/net/networking_engine.js +49 -4
  66. package/lib/offline/storage.js +3 -3
  67. package/lib/player.js +882 -672
  68. package/lib/polyfill/media_capabilities.js +16 -1
  69. package/lib/text/simple_text_displayer.js +9 -0
  70. package/lib/text/stub_text_displayer.js +7 -0
  71. package/lib/text/text_engine.js +16 -3
  72. package/lib/text/ttml_text_parser.js +116 -114
  73. package/lib/text/ui_text_displayer.js +28 -11
  74. package/lib/text/vtt_text_parser.js +54 -115
  75. package/lib/transmuxer/opus.js +91 -0
  76. package/lib/transmuxer/ts_transmuxer.js +115 -0
  77. package/lib/util/cmcd_manager.js +153 -3
  78. package/lib/util/dom_utils.js +11 -0
  79. package/lib/util/error.js +5 -0
  80. package/lib/util/fake_event.js +2 -0
  81. package/lib/util/manifest_parser_utils.js +97 -3
  82. package/lib/util/mp4_box_parsers.js +346 -17
  83. package/lib/util/mp4_generator.js +23 -0
  84. package/lib/util/periods.js +19 -0
  85. package/lib/util/platform.js +161 -9
  86. package/lib/util/player_configuration.js +79 -13
  87. package/lib/util/stats.js +16 -0
  88. package/lib/util/stream_utils.js +22 -8
  89. package/lib/util/string_utils.js +36 -0
  90. package/lib/util/tXml.js +814 -0
  91. package/lib/util/ts_parser.js +50 -0
  92. package/lib/util/xml_utils.js +10 -338
  93. package/package.json +2 -2
  94. package/shaka-player.uncompiled.js +8 -0
  95. package/ui/controls.js +187 -5
  96. package/ui/enums.js +2 -0
  97. package/ui/externs/ui.js +30 -3
  98. package/ui/gl_matrix/matrix_4x4.js +568 -0
  99. package/ui/gl_matrix/matrix_quaternion.js +45 -0
  100. package/ui/less/containers.less +11 -0
  101. package/ui/locales/en-GB.json +2 -0
  102. package/ui/locales/en.json +2 -0
  103. package/ui/locales/es-419.json +2 -0
  104. package/ui/locales/es.json +2 -0
  105. package/ui/locales/source.json +8 -0
  106. package/ui/recenter_vr.js +126 -0
  107. package/ui/resolution_selection.js +25 -5
  108. package/ui/statistics_button.js +12 -0
  109. package/ui/text_selection.js +1 -1
  110. package/ui/toggle_stereoscopic.js +127 -0
  111. package/ui/ui.js +74 -38
  112. package/ui/vr_manager.js +557 -0
  113. package/ui/vr_utils.js +232 -0
  114. 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 (mediaState.segmentPrefetch) {
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
- if (mediaState.stream.closeSegmentIndex) {
491
- mediaState.stream.closeSegmentIndex();
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
- if (
899
- stream.type !== shaka.util.ManifestParserUtils.ContentType.VIDEO &&
900
- stream.type !== shaka.util.ManifestParserUtils.ContentType.AUDIO
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(Math.pow(2, 32));
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
- const initSegmentReference = reference.initSegmentReference;
1179
- if (initSegmentReference && (!mediaState.lastSegmentReference ||
1180
- !shaka.media.InitSegmentReference.equal(
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(time);
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(lookupTime);
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(presentationTime);
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.prefetchSegments(
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.prefetchSegments(
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.aes128Key} aes128Key
1713
+ * @param {shaka.extern.aesKey} aesKey
1532
1714
  * @param {number} position
1533
1715
  * @return {!Promise.<!BufferSource>} finalResult
1534
1716
  * @private
1535
1717
  */
1536
- async aes128Decrypt_(rawResult, aes128Key, position) {
1537
- const key = aes128Key;
1718
+ async aesDecrypt_(rawResult, aesKey, position) {
1719
+ const key = aesKey;
1538
1720
  if (!key.cryptoKey) {
1539
- goog.asserts.assert(key.fetchKey, 'If AES-128 cryptoKey was not ' +
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-128 cryptoKey should now be set');
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
- return window.crypto.subtle.decrypt(
1554
- {name: 'AES-CBC', iv}, key.cryptoKey, rawResult);
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 = shaka.util.Mp4BoxParsers.parseMDHD(
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 = shaka.util.Mp4BoxParsers.parseHDLR(
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 <= 1) {
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
- if (reference instanceof shaka.media.InitSegmentReference) {
2213
- const segmentData = reference.getSegmentData();
2214
- if (segmentData) {
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.aes128Key) {
2238
- result = await this.aes128Decrypt_(result, reference.aes128Key, position);
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
- this.config_.retryParameters,
2506
+ retryParameters,
2266
2507
  streamDataCallback);
2508
+ request.contentType = stream.type;
2267
2509
 
2268
2510
  shaka.log.v2('fetching: reference=', reference);
2269
2511
 
2270
- return this.playerInterface_.netEngine.request(
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