shaka-player 4.7.12 → 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 (120) 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 +36 -25
  8. package/dist/locales.js +40 -30
  9. package/dist/shaka-player.compiled.d.ts +196 -46
  10. package/dist/shaka-player.compiled.debug.d.ts +196 -46
  11. package/dist/shaka-player.compiled.debug.externs.js +474 -337
  12. package/dist/shaka-player.compiled.debug.js +992 -871
  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 +1248 -1174
  16. package/dist/shaka-player.compiled.map +4 -4
  17. package/dist/shaka-player.ui.d.ts +259 -49
  18. package/dist/shaka-player.ui.debug.d.ts +259 -49
  19. package/dist/shaka-player.ui.debug.externs.js +591 -376
  20. package/dist/shaka-player.ui.debug.js +1208 -1003
  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 +1533 -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 +69 -7
  32. package/externs/shaka/net.js +13 -1
  33. package/externs/shaka/offline.js +7 -1
  34. package/externs/shaka/player.js +184 -13
  35. package/externs/shaka/text.js +19 -0
  36. package/externs/tizen.js +41 -0
  37. package/externs/webos.js +19 -0
  38. package/externs/xbox.js +43 -0
  39. package/lib/abr/simple_abr_manager.js +9 -1
  40. package/lib/ads/ad_manager.js +19 -2
  41. package/lib/ads/client_side_ad_manager.js +5 -3
  42. package/lib/ads/media_tailor_ad_manager.js +9 -0
  43. package/lib/cast/cast_receiver.js +0 -14
  44. package/lib/cast/cast_utils.js +1 -0
  45. package/lib/cea/mp4_cea_parser.js +6 -1
  46. package/lib/dash/content_protection.js +97 -53
  47. package/lib/dash/dash_parser.js +240 -179
  48. package/lib/dash/mpd_utils.js +72 -65
  49. package/lib/dash/segment_base.js +35 -22
  50. package/lib/dash/segment_list.js +39 -31
  51. package/lib/dash/segment_template.js +47 -23
  52. package/lib/hls/hls_parser.js +376 -165
  53. package/lib/media/buffering_observer.js +25 -1
  54. package/lib/media/drm_engine.js +91 -71
  55. package/lib/media/manifest_filterer.js +220 -0
  56. package/lib/media/media_source_engine.js +108 -29
  57. package/lib/media/playhead.js +3 -3
  58. package/lib/media/preload_manager.js +827 -0
  59. package/lib/media/presentation_timeline.js +11 -0
  60. package/lib/media/segment_index.js +60 -49
  61. package/lib/media/segment_prefetch.js +140 -43
  62. package/lib/media/segment_reference.js +31 -8
  63. package/lib/media/segment_utils.js +208 -25
  64. package/lib/media/streaming_engine.js +321 -77
  65. package/lib/mss/content_protection.js +31 -25
  66. package/lib/mss/mss_parser.js +78 -55
  67. package/lib/net/networking_engine.js +49 -4
  68. package/lib/offline/indexeddb/v1_storage_cell.js +2 -0
  69. package/lib/offline/indexeddb/v2_storage_cell.js +2 -0
  70. package/lib/offline/manifest_converter.js +10 -3
  71. package/lib/offline/storage.js +5 -3
  72. package/lib/player.js +896 -674
  73. package/lib/polyfill/media_capabilities.js +16 -1
  74. package/lib/text/simple_text_displayer.js +9 -0
  75. package/lib/text/stub_text_displayer.js +7 -0
  76. package/lib/text/text_engine.js +16 -3
  77. package/lib/text/ttml_text_parser.js +116 -114
  78. package/lib/text/ui_text_displayer.js +28 -11
  79. package/lib/text/vtt_text_parser.js +54 -115
  80. package/lib/transmuxer/opus.js +91 -0
  81. package/lib/transmuxer/ts_transmuxer.js +115 -0
  82. package/lib/util/cmcd_manager.js +152 -3
  83. package/lib/util/dom_utils.js +11 -0
  84. package/lib/util/error.js +5 -0
  85. package/lib/util/fake_event.js +2 -0
  86. package/lib/util/manifest_parser_utils.js +97 -3
  87. package/lib/util/mime_utils.js +10 -5
  88. package/lib/util/mp4_box_parsers.js +346 -17
  89. package/lib/util/mp4_generator.js +23 -0
  90. package/lib/util/periods.js +24 -37
  91. package/lib/util/platform.js +161 -9
  92. package/lib/util/player_configuration.js +61 -3
  93. package/lib/util/stats.js +16 -0
  94. package/lib/util/stream_utils.js +350 -277
  95. package/lib/util/string_utils.js +36 -0
  96. package/lib/util/tXml.js +814 -0
  97. package/lib/util/ts_parser.js +50 -0
  98. package/lib/util/xml_utils.js +10 -338
  99. package/package.json +2 -2
  100. package/shaka-player.uncompiled.js +8 -0
  101. package/ui/controls.js +187 -5
  102. package/ui/enums.js +2 -0
  103. package/ui/externs/ui.js +30 -3
  104. package/ui/gl_matrix/matrix_4x4.js +568 -0
  105. package/ui/gl_matrix/matrix_quaternion.js +45 -0
  106. package/ui/less/containers.less +11 -0
  107. package/ui/locales/en-GB.json +2 -0
  108. package/ui/locales/en.json +2 -0
  109. package/ui/locales/es-419.json +2 -0
  110. package/ui/locales/es.json +2 -0
  111. package/ui/locales/source.json +8 -0
  112. package/ui/recenter_vr.js +126 -0
  113. package/ui/resolution_selection.js +25 -5
  114. package/ui/statistics_button.js +12 -0
  115. package/ui/text_selection.js +1 -1
  116. package/ui/toggle_stereoscopic.js +127 -0
  117. package/ui/ui.js +74 -38
  118. package/ui/vr_manager.js +557 -0
  119. package/ui/vr_utils.js +232 -0
  120. 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
  }
@@ -881,7 +953,6 @@ shaka.media.StreamingEngine = class {
881
953
  // The playhead might be seeking on startup, if a start time is set, so
882
954
  // start "seeked" as true.
883
955
  seeked: true,
884
- needsResync: false,
885
956
  recovering: false,
886
957
  hasError: false,
887
958
  operation: null,
@@ -896,12 +967,37 @@ shaka.media.StreamingEngine = class {
896
967
  * @private
897
968
  */
898
969
  createSegmentPrefetch_(stream) {
899
- if (
900
- stream.type !== shaka.util.ManifestParserUtils.ContentType.VIDEO &&
901
- stream.type !== shaka.util.ManifestParserUtils.ContentType.AUDIO
902
- ) {
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) {
903
977
  return null;
904
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
+ }
905
1001
  if (this.config_.segmentPrefetchLimit > 0) {
906
1002
  return new shaka.media.SegmentPrefetch(
907
1003
  this.config_.segmentPrefetchLimit,
@@ -914,6 +1010,75 @@ shaka.media.StreamingEngine = class {
914
1010
  return null;
915
1011
  }
916
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
+
917
1082
  /**
918
1083
  * Sets the MediaSource's duration.
919
1084
  */
@@ -922,9 +1087,13 @@ shaka.media.StreamingEngine = class {
922
1087
  if (duration < Infinity) {
923
1088
  this.playerInterface_.mediaSourceEngine.setDuration(duration);
924
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);
925
1094
  // Not all platforms support infinite durations, so set a finite duration
926
1095
  // so we can append segments and so the user agent can seek.
927
- this.playerInterface_.mediaSourceEngine.setDuration(Math.pow(2, 32));
1096
+ this.playerInterface_.mediaSourceEngine.setDuration(duration);
928
1097
  }
929
1098
  }
930
1099
 
@@ -1072,6 +1241,15 @@ shaka.media.StreamingEngine = class {
1072
1241
  // Compute how far we've buffered ahead of the playhead.
1073
1242
  const presentationTime = this.playerInterface_.getPresentationTime();
1074
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
+
1075
1253
  // Get the next timestamp we need.
1076
1254
  const timeNeeded = this.getTimeNeeded_(mediaState, presentationTime);
1077
1255
  shaka.log.v2(logPrefix, 'timeNeeded=' + timeNeeded);
@@ -1175,14 +1353,10 @@ shaka.media.StreamingEngine = class {
1175
1353
  return this.config_.updateIntervalSeconds;
1176
1354
  }
1177
1355
 
1178
- if (mediaState.segmentPrefetch && mediaState.segmentIterator) {
1179
- const initSegmentReference = reference.initSegmentReference;
1180
- if (initSegmentReference && (!mediaState.lastSegmentReference ||
1181
- !shaka.media.InitSegmentReference.equal(
1182
- initSegmentReference, mediaState.lastInitSegmentReference))) {
1183
- mediaState.segmentPrefetch.prefetchInitSegment(initSegmentReference);
1184
- }
1185
- 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);
1186
1360
  }
1187
1361
 
1188
1362
  const p = this.fetchAndAppend_(mediaState, presentationTime, reference);
@@ -1248,11 +1422,6 @@ shaka.media.StreamingEngine = class {
1248
1422
  shaka.log.v1(
1249
1423
  logPrefix, 'looking up segment from new stream endTime:', time);
1250
1424
 
1251
- // Using a new iterator means we need to resync the stream in sequence
1252
- // mode. The buffered range might not align perfectly with the last
1253
- // segment end time, so we may end up repeating a segment. Resyncing
1254
- // makes this safe to do.
1255
- mediaState.needsResync = true;
1256
1425
  mediaState.segmentIterator =
1257
1426
  mediaState.stream.segmentIndex.getIteratorForTime(time);
1258
1427
  const ref = mediaState.segmentIterator &&
@@ -1396,8 +1565,8 @@ shaka.media.StreamingEngine = class {
1396
1565
  /* isChunkedData= */ true);
1397
1566
 
1398
1567
  if (mediaState.segmentPrefetch && mediaState.segmentIterator) {
1399
- mediaState.segmentPrefetch.prefetchSegments(
1400
- reference, /* skipFirst= */ true);
1568
+ mediaState.segmentPrefetch.prefetchSegmentsByTime(
1569
+ reference.startTime, /* skipFirst= */ true);
1401
1570
  }
1402
1571
  }
1403
1572
  } catch (error) {
@@ -1437,8 +1606,8 @@ shaka.media.StreamingEngine = class {
1437
1606
  }
1438
1607
 
1439
1608
  if (mediaState.segmentPrefetch && mediaState.segmentIterator) {
1440
- mediaState.segmentPrefetch.prefetchSegments(
1441
- reference, /* skipFirst= */ true);
1609
+ mediaState.segmentPrefetch.prefetchSegmentsByTime(
1610
+ reference.startTime, /* skipFirst= */ true);
1442
1611
  }
1443
1612
  } else {
1444
1613
  if (this.config_.lowLatencyMode && !isReadableStreamSupported) {
@@ -1534,18 +1703,18 @@ shaka.media.StreamingEngine = class {
1534
1703
 
1535
1704
  /**
1536
1705
  * @param {!BufferSource} rawResult
1537
- * @param {shaka.extern.aes128Key} aes128Key
1706
+ * @param {shaka.extern.aesKey} aesKey
1538
1707
  * @param {number} position
1539
1708
  * @return {!Promise.<!BufferSource>} finalResult
1540
1709
  * @private
1541
1710
  */
1542
- async aes128Decrypt_(rawResult, aes128Key, position) {
1543
- const key = aes128Key;
1711
+ async aesDecrypt_(rawResult, aesKey, position) {
1712
+ const key = aesKey;
1544
1713
  if (!key.cryptoKey) {
1545
- goog.asserts.assert(key.fetchKey, 'If AES-128 cryptoKey was not ' +
1714
+ goog.asserts.assert(key.fetchKey, 'If AES cryptoKey was not ' +
1546
1715
  'preloaded, fetchKey function should be provided');
1547
1716
  await key.fetchKey();
1548
- goog.asserts.assert(key.cryptoKey, 'AES-128 cryptoKey should now be set');
1717
+ goog.asserts.assert(key.cryptoKey, 'AES cryptoKey should now be set');
1549
1718
  }
1550
1719
  let iv = key.iv;
1551
1720
  if (!iv) {
@@ -1556,8 +1725,22 @@ shaka.media.StreamingEngine = class {
1556
1725
  sequence >>= 8;
1557
1726
  }
1558
1727
  }
1559
- return window.crypto.subtle.decrypt(
1560
- {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);
1561
1744
  }
1562
1745
 
1563
1746
 
@@ -1730,7 +1913,7 @@ shaka.media.StreamingEngine = class {
1730
1913
  const isResetMediaSourceNecessary =
1731
1914
  mediaState.lastCodecs && mediaState.lastMimeType &&
1732
1915
  this.playerInterface_.mediaSourceEngine.isResetMediaSourceNecessary(
1733
- mediaState.type, mediaState.stream);
1916
+ mediaState.type, mediaState.stream, mimeType, codecs);
1734
1917
  if (isResetMediaSourceNecessary) {
1735
1918
  let otherState = null;
1736
1919
  if (mediaState.type === ContentType.VIDEO) {
@@ -1739,9 +1922,16 @@ shaka.media.StreamingEngine = class {
1739
1922
  otherState = this.mediaStates_.get(ContentType.VIDEO);
1740
1923
  }
1741
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.
1742
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.
1743
1934
  this.forceClearBuffer_(otherState);
1744
- this.abortOperations_(otherState).catch(() => {});
1745
1935
  }
1746
1936
  }
1747
1937
 
@@ -1772,19 +1962,6 @@ shaka.media.StreamingEngine = class {
1772
1962
  appendWindowEnd, ignoreTimestampOffset,
1773
1963
  reference.mimeType || mediaState.stream.mimeType,
1774
1964
  reference.codecs || mediaState.stream.codecs, streamsByType);
1775
- if (isResetMediaSourceNecessary) {
1776
- let otherState = null;
1777
- if (mediaState.type === ContentType.VIDEO) {
1778
- otherState = this.mediaStates_.get(ContentType.AUDIO);
1779
- } else if (mediaState.type === ContentType.AUDIO) {
1780
- otherState = this.mediaStates_.get(ContentType.VIDEO);
1781
- }
1782
- if (otherState) {
1783
- otherState.waitingToClearBuffer = false;
1784
- otherState.performingUpdate = false;
1785
- this.forceClearBuffer_(otherState);
1786
- }
1787
- }
1788
1965
  } catch (error) {
1789
1966
  mediaState.lastAppendWindowStart = null;
1790
1967
  mediaState.lastAppendWindowEnd = null;
@@ -1817,8 +1994,15 @@ shaka.media.StreamingEngine = class {
1817
1994
  let lastTimescale = null;
1818
1995
  const timescaleMap = new Map();
1819
1996
 
1997
+ /** @type {!shaka.extern.SpatialVideoInfo} */
1998
+ const spatialVideoInfo = {
1999
+ projection: null,
2000
+ hfov: null,
2001
+ };
2002
+
1820
2003
  const parser = new shaka.util.Mp4Parser();
1821
2004
  const Mp4Parser = shaka.util.Mp4Parser;
2005
+ const Mp4BoxParsers = shaka.util.Mp4BoxParsers;
1822
2006
  parser.box('moov', Mp4Parser.children)
1823
2007
  .box('trak', Mp4Parser.children)
1824
2008
  .box('mdia', Mp4Parser.children)
@@ -1826,13 +2010,12 @@ shaka.media.StreamingEngine = class {
1826
2010
  goog.asserts.assert(
1827
2011
  box.version != null,
1828
2012
  'MDHD is a full box and should have a valid version.');
1829
- const parsedMDHDBox = shaka.util.Mp4BoxParsers.parseMDHD(
2013
+ const parsedMDHDBox = Mp4BoxParsers.parseMDHD(
1830
2014
  box.reader, box.version);
1831
2015
  lastTimescale = parsedMDHDBox.timescale;
1832
2016
  })
1833
2017
  .box('hdlr', (box) => {
1834
- const parsedHDLR = shaka.util.Mp4BoxParsers.parseHDLR(
1835
- box.reader);
2018
+ const parsedHDLR = Mp4BoxParsers.parseHDLR(box.reader);
1836
2019
  switch (parsedHDLR.handlerType) {
1837
2020
  case 'soun':
1838
2021
  timescaleMap.set(ContentType.AUDIO, lastTimescale);
@@ -1843,8 +2026,32 @@ shaka.media.StreamingEngine = class {
1843
2026
  }
1844
2027
  lastTimescale = null;
1845
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
+ })
1846
2051
  .parse(initSegment);
1847
2052
 
2053
+ this.updateSpatialVideoInfo_(spatialVideoInfo);
2054
+
1848
2055
  if (timescaleMap.has(mediaState.type)) {
1849
2056
  reference.initSegmentReference.timescale =
1850
2057
  timescaleMap.get(mediaState.type);
@@ -1879,9 +2086,7 @@ shaka.media.StreamingEngine = class {
1879
2086
  // Across discontinuity bounds, we should resync timestamps for
1880
2087
  // sequence mode playbacks. The next segment appended should
1881
2088
  // land at its theoretical timestamp from the segment index.
1882
- if (reference.discontinuitySequence != lastDiscontinuitySequence ||
1883
- mediaState.needsResync) {
1884
- mediaState.needsResync = false;
2089
+ if (reference.discontinuitySequence != lastDiscontinuitySequence) {
1885
2090
  operations.push(this.playerInterface_.mediaSourceEngine.resync(
1886
2091
  mediaState.type, reference.startTime));
1887
2092
  }
@@ -2164,12 +2369,13 @@ shaka.media.StreamingEngine = class {
2164
2369
 
2165
2370
  const overflow = bufferedBehind - bufferBehind;
2166
2371
  // See: https://github.com/shaka-project/shaka-player/issues/6240
2167
- if (overflow <= 1) {
2372
+ if (overflow <= this.config_.evictionGoal) {
2168
2373
  shaka.log.v2(logPrefix,
2169
2374
  'buffer behind okay:',
2170
2375
  'presentationTime=' + presentationTime,
2171
2376
  'bufferedBehind=' + bufferedBehind,
2172
2377
  'bufferBehind=' + bufferBehind,
2378
+ 'evictionGoal=' + this.config_.evictionGoal,
2173
2379
  'underflow=' + Math.abs(overflow));
2174
2380
  return;
2175
2381
  }
@@ -2179,6 +2385,7 @@ shaka.media.StreamingEngine = class {
2179
2385
  'presentationTime=' + presentationTime,
2180
2386
  'bufferedBehind=' + bufferedBehind,
2181
2387
  'bufferBehind=' + bufferBehind,
2388
+ 'evictionGoal=' + this.config_.evictionGoal,
2182
2389
  'overflow=' + overflow);
2183
2390
 
2184
2391
  await this.playerInterface_.mediaSourceEngine.remove(mediaState.type,
@@ -2217,11 +2424,9 @@ shaka.media.StreamingEngine = class {
2217
2424
  * @private
2218
2425
  */
2219
2426
  async fetch_(mediaState, reference, streamDataCallback) {
2220
- if (reference instanceof shaka.media.InitSegmentReference) {
2221
- const segmentData = reference.getSegmentData();
2222
- if (segmentData) {
2223
- return segmentData;
2224
- }
2427
+ const segmentData = reference.getSegmentData();
2428
+ if (segmentData) {
2429
+ return segmentData;
2225
2430
  }
2226
2431
  let op = null;
2227
2432
  if (mediaState.segmentPrefetch) {
@@ -2242,8 +2447,8 @@ shaka.media.StreamingEngine = class {
2242
2447
  const response = await op.promise;
2243
2448
  mediaState.operation = null;
2244
2449
  let result = response.data;
2245
- if (reference.aes128Key) {
2246
- result = await this.aes128Decrypt_(result, reference.aes128Key, position);
2450
+ if (reference.aesKey) {
2451
+ result = await this.aesDecrypt_(result, reference.aesKey, position);
2247
2452
  }
2248
2453
  return result;
2249
2454
  }
@@ -2260,6 +2465,27 @@ shaka.media.StreamingEngine = class {
2260
2465
  * @private
2261
2466
  */
2262
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) {
2263
2489
  const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
2264
2490
  const segment = reference instanceof shaka.media.SegmentReference ?
2265
2491
  reference : undefined;
@@ -2270,17 +2496,13 @@ shaka.media.StreamingEngine = class {
2270
2496
  reference.getUris(),
2271
2497
  reference.startByte,
2272
2498
  reference.endByte,
2273
- this.config_.retryParameters,
2499
+ retryParameters,
2274
2500
  streamDataCallback);
2501
+ request.contentType = stream.type;
2275
2502
 
2276
2503
  shaka.log.v2('fetching: reference=', reference);
2277
2504
 
2278
- return this.playerInterface_.netEngine.request(
2279
- requestType, request, {
2280
- type: type,
2281
- stream: stream,
2282
- segment: segment,
2283
- });
2505
+ return netEngine.request(requestType, request, {type, stream, segment});
2284
2506
  }
2285
2507
 
2286
2508
  /**
@@ -2310,7 +2532,8 @@ shaka.media.StreamingEngine = class {
2310
2532
  mediaState.segmentIterator = null;
2311
2533
 
2312
2534
  shaka.log.debug(logPrefix, 'clearing buffer');
2313
- if (mediaState.segmentPrefetch) {
2535
+ if (mediaState.segmentPrefetch &&
2536
+ !this.audioPrefetchMap_.has(mediaState.stream)) {
2314
2537
  mediaState.segmentPrefetch.clearAll();
2315
2538
  }
2316
2539
 
@@ -2504,6 +2727,31 @@ shaka.media.StreamingEngine = class {
2504
2727
  return true;
2505
2728
  }
2506
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
+
2507
2755
  /**
2508
2756
  * @param {shaka.media.StreamingEngine.MediaState_} mediaState
2509
2757
  * @return {string} A log prefix of the form ($CONTENT_TYPE:$STREAM_ID), e.g.,
@@ -2591,7 +2839,6 @@ shaka.media.StreamingEngine.PlayerInterface;
2591
2839
  * clearBufferSafeMargin: number,
2592
2840
  * clearingBuffer: boolean,
2593
2841
  * seeked: boolean,
2594
- * needsResync: boolean,
2595
2842
  * adaptation: boolean,
2596
2843
  * recovering: boolean,
2597
2844
  * hasError: boolean,
@@ -2646,9 +2893,6 @@ shaka.media.StreamingEngine.PlayerInterface;
2646
2893
  * True indicates that the presentation just seeked.
2647
2894
  * @property {boolean} adaptation
2648
2895
  * True indicates that the presentation just automatically switched variants.
2649
- * @property {boolean} needsResync
2650
- * True indicates that the stream needs to be resynced in sequence mode,
2651
- * regardless of discontinuity sequence.
2652
2896
  * @property {boolean} recovering
2653
2897
  * True indicates that the last segment was not appended because it could not
2654
2898
  * fit in the buffer.