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.
Files changed (114) hide show
  1. package/AUTHORS +1 -0
  2. package/CONTRIBUTORS +1 -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 +44 -34
  9. package/dist/shaka-player.compiled.d.ts +194 -44
  10. package/dist/shaka-player.compiled.debug.d.ts +194 -44
  11. package/dist/shaka-player.compiled.debug.externs.js +474 -337
  12. package/dist/shaka-player.compiled.debug.js +949 -832
  13. package/dist/shaka-player.compiled.debug.map +4 -4
  14. package/dist/shaka-player.compiled.externs.js +474 -337
  15. package/dist/shaka-player.compiled.js +1237 -1171
  16. package/dist/shaka-player.compiled.map +4 -4
  17. package/dist/shaka-player.ui.d.ts +257 -47
  18. package/dist/shaka-player.ui.debug.d.ts +257 -47
  19. package/dist/shaka-player.ui.debug.externs.js +591 -376
  20. package/dist/shaka-player.ui.debug.js +1165 -965
  21. package/dist/shaka-player.ui.debug.map +4 -4
  22. package/dist/shaka-player.ui.externs.js +591 -376
  23. package/dist/shaka-player.ui.js +1521 -1404
  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 +230 -176
  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 +26 -21
  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 +40 -30
  59. package/lib/media/segment_prefetch.js +140 -43
  60. package/lib/media/segment_reference.js +31 -8
  61. package/lib/media/segment_utils.js +208 -25
  62. package/lib/media/streaming_engine.js +319 -63
  63. package/lib/mss/content_protection.js +31 -25
  64. package/lib/mss/mss_parser.js +74 -55
  65. package/lib/net/networking_engine.js +49 -4
  66. package/lib/offline/storage.js +3 -3
  67. package/lib/player.js +882 -673
  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 +152 -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 +61 -3
  87. package/lib/util/stats.js +16 -0
  88. package/lib/util/stream_utils.js +16 -6
  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');
@@ -472,7 +529,9 @@ shaka.media.StreamingEngine = class {
472
529
  return;
473
530
  }
474
531
 
475
- if (mediaState.segmentPrefetch) {
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
- if (mediaState.stream.closeSegmentIndex) {
491
- mediaState.stream.closeSegmentIndex();
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
- if (
899
- stream.type !== shaka.util.ManifestParserUtils.ContentType.VIDEO &&
900
- stream.type !== shaka.util.ManifestParserUtils.ContentType.AUDIO
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(Math.pow(2, 32));
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
- 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);
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.prefetchSegments(
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.prefetchSegments(
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.aes128Key} aes128Key
1706
+ * @param {shaka.extern.aesKey} aesKey
1532
1707
  * @param {number} position
1533
1708
  * @return {!Promise.<!BufferSource>} finalResult
1534
1709
  * @private
1535
1710
  */
1536
- async aes128Decrypt_(rawResult, aes128Key, position) {
1537
- const key = aes128Key;
1711
+ async aesDecrypt_(rawResult, aesKey, position) {
1712
+ const key = aesKey;
1538
1713
  if (!key.cryptoKey) {
1539
- goog.asserts.assert(key.fetchKey, 'If AES-128 cryptoKey was not ' +
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-128 cryptoKey should now be set');
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
- return window.crypto.subtle.decrypt(
1554
- {name: 'AES-CBC', iv}, key.cryptoKey, rawResult);
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 = shaka.util.Mp4BoxParsers.parseMDHD(
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 = shaka.util.Mp4BoxParsers.parseHDLR(
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 <= 1) {
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
- if (reference instanceof shaka.media.InitSegmentReference) {
2213
- const segmentData = reference.getSegmentData();
2214
- if (segmentData) {
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.aes128Key) {
2238
- result = await this.aes128Decrypt_(result, reference.aes128Key, position);
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
- this.config_.retryParameters,
2499
+ retryParameters,
2266
2500
  streamDataCallback);
2501
+ request.contentType = stream.type;
2267
2502
 
2268
2503
  shaka.log.v2('fetching: reference=', reference);
2269
2504
 
2270
- return this.playerInterface_.netEngine.request(
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.,