shaka-player 3.2.1 → 3.3.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 (213) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +1 -1
  3. package/.github/ISSUE_TEMPLATE/question.md +1 -1
  4. package/AUTHORS +9 -5
  5. package/CHANGELOG.md +358 -0
  6. package/CONTRIBUTORS +7 -2
  7. package/build/conformance.textproto +183 -169
  8. package/build/generateExterns.js +1 -1
  9. package/build/types/core +3 -0
  10. package/build/types/offline +1 -0
  11. package/build/types/polyfill +1 -0
  12. package/build/types/ui +2 -0
  13. package/demo/common/assets.js +19 -1
  14. package/demo/common/message_ids.js +13 -1
  15. package/demo/config.js +31 -1
  16. package/demo/index.html +1 -1
  17. package/demo/locales/en.json +12 -0
  18. package/demo/locales/source.json +48 -0
  19. package/dist/controls.css +26 -2
  20. package/dist/controls.css.map +1 -1
  21. package/dist/demo.compiled.debug.js +20 -17
  22. package/dist/demo.compiled.debug.map +3 -3
  23. package/dist/demo.compiled.js +20 -17
  24. package/dist/demo.compiled.map +3 -3
  25. package/dist/deps.js +19 -12
  26. package/dist/locales.js +39 -37
  27. package/dist/receiver.compiled.debug.js +12 -10
  28. package/dist/receiver.compiled.debug.map +3 -3
  29. package/dist/receiver.compiled.js +12 -10
  30. package/dist/receiver.compiled.map +3 -3
  31. package/dist/shaka-player.compiled.d.ts +231 -18
  32. package/dist/shaka-player.compiled.debug.d.ts +231 -18
  33. package/dist/shaka-player.compiled.debug.externs.js +279 -8
  34. package/dist/shaka-player.compiled.debug.js +512 -449
  35. package/dist/shaka-player.compiled.debug.map +4 -4
  36. package/dist/shaka-player.compiled.externs.js +279 -8
  37. package/dist/shaka-player.compiled.js +854 -821
  38. package/dist/shaka-player.compiled.map +4 -4
  39. package/dist/shaka-player.ui.d.ts +247 -19
  40. package/dist/shaka-player.ui.debug.d.ts +247 -19
  41. package/dist/shaka-player.ui.debug.externs.js +307 -9
  42. package/dist/shaka-player.ui.debug.js +662 -580
  43. package/dist/shaka-player.ui.debug.map +4 -4
  44. package/dist/shaka-player.ui.externs.js +307 -9
  45. package/dist/shaka-player.ui.js +1047 -1000
  46. package/dist/shaka-player.ui.map +4 -4
  47. package/docs/design/bg-fetch-after.gv +29 -0
  48. package/docs/design/bg-fetch-after.gv.png +0 -0
  49. package/docs/design/bg-fetch-before.gv +18 -0
  50. package/docs/design/bg-fetch-before.gv.png +0 -0
  51. package/docs/design/bg-fetch.md +134 -0
  52. package/docs/tutorials/blob-url.md +15 -0
  53. package/docs/tutorials/fairplay.md +1 -1
  54. package/docs/tutorials/index.json +1 -0
  55. package/docs/tutorials/ui-customization.md +66 -0
  56. package/docs/tutorials/ui.md +5 -5
  57. package/externs/cmcd.js +186 -0
  58. package/externs/ima.js +3 -0
  59. package/externs/shaka/ads.js +5 -0
  60. package/externs/shaka/manifest.js +12 -1
  61. package/externs/shaka/manifest_parser.js +10 -1
  62. package/externs/shaka/net.js +20 -4
  63. package/externs/shaka/offline.js +31 -2
  64. package/externs/shaka/player.js +138 -5
  65. package/externs/shaka/text.js +24 -0
  66. package/externs/webcrypto.js +18 -0
  67. package/karma.conf.js +1 -0
  68. package/lib/abr/ewma.js +14 -0
  69. package/lib/abr/ewma_bandwidth_estimator.js +16 -2
  70. package/lib/abr/simple_abr_manager.js +6 -0
  71. package/lib/ads/client_side_ad.js +8 -0
  72. package/lib/ads/client_side_ad_manager.js +54 -90
  73. package/lib/ads/server_side_ad.js +7 -0
  74. package/lib/ads/server_side_ad_manager.js +7 -10
  75. package/lib/cast/cast_proxy.js +3 -2
  76. package/lib/cast/cast_receiver.js +2 -1
  77. package/lib/cast/cast_sender.js +2 -2
  78. package/lib/cast/cast_utils.js +1 -0
  79. package/lib/dash/dash_parser.js +141 -16
  80. package/lib/dash/segment_base.js +25 -1
  81. package/lib/dash/segment_list.js +19 -10
  82. package/lib/dash/segment_template.js +24 -11
  83. package/lib/hls/hls_parser.js +146 -40
  84. package/lib/media/drm_engine.js +7 -0
  85. package/lib/media/gap_jumping_controller.js +4 -2
  86. package/lib/media/media_source_engine.js +1 -2
  87. package/lib/media/quality_observer.js +291 -0
  88. package/lib/media/segment_index.js +8 -1
  89. package/lib/media/segment_reference.js +37 -4
  90. package/lib/media/streaming_engine.js +49 -7
  91. package/lib/net/http_fetch_plugin.js +31 -11
  92. package/lib/net/http_plugin_utils.js +1 -0
  93. package/lib/net/http_xhr_plugin.js +40 -14
  94. package/lib/net/networking_engine.js +83 -19
  95. package/lib/offline/download_info.js +68 -0
  96. package/lib/offline/download_manager.js +13 -5
  97. package/lib/offline/indexeddb/base_storage_cell.js +35 -0
  98. package/lib/offline/indexeddb/storage_mechanism.js +1 -1
  99. package/lib/offline/indexeddb/v5_storage_cell.js +5 -0
  100. package/lib/offline/storage.js +393 -284
  101. package/lib/offline/stored_content_utils.js +2 -0
  102. package/lib/player.js +249 -43
  103. package/lib/polyfill/media_capabilities.js +36 -15
  104. package/lib/polyfill/mediasource.js +6 -0
  105. package/lib/polyfill/orientation.js +1 -1
  106. package/lib/polyfill/patchedmediakeys_apple.js +4 -4
  107. package/lib/polyfill/patchedmediakeys_ms.js +4 -4
  108. package/lib/polyfill/patchedmediakeys_webkit.js +4 -4
  109. package/lib/polyfill/random_uuid.js +52 -0
  110. package/lib/text/cue.js +18 -0
  111. package/lib/text/mp4_ttml_parser.js +3 -0
  112. package/lib/text/text_engine.js +14 -2
  113. package/lib/text/ttml_text_parser.js +114 -60
  114. package/lib/text/ui_text_displayer.js +95 -41
  115. package/lib/util/cmcd_manager.js +672 -0
  116. package/lib/util/error.js +6 -0
  117. package/lib/util/fake_event.js +53 -7
  118. package/lib/util/language_utils.js +12 -0
  119. package/lib/util/mutex.js +52 -0
  120. package/lib/util/periods.js +102 -65
  121. package/lib/util/platform.js +13 -1
  122. package/lib/util/player_configuration.js +18 -0
  123. package/lib/util/stream_utils.js +99 -10
  124. package/lib/util/xml_utils.js +19 -0
  125. package/package.json +2 -2
  126. package/shaka-player.uncompiled.js +3 -0
  127. package/test/cast/cast_proxy_unit.js +12 -6
  128. package/test/cast/cast_utils_unit.js +1 -1
  129. package/test/dash/dash_parser_content_protection_unit.js +2 -0
  130. package/test/dash/dash_parser_live_unit.js +101 -1
  131. package/test/dash/dash_parser_manifest_unit.js +222 -2
  132. package/test/dash/dash_parser_segment_base_unit.js +2 -0
  133. package/test/dash/dash_parser_segment_list_unit.js +2 -1
  134. package/test/dash/dash_parser_segment_template_unit.js +2 -1
  135. package/test/hls/hls_live_unit.js +2 -0
  136. package/test/hls/hls_parser_unit.js +7 -1
  137. package/test/media/closed_caption_parser_unit.js +6 -1
  138. package/test/media/drm_engine_unit.js +47 -3
  139. package/test/media/quality_observer_unit.js +139 -0
  140. package/test/media/segment_reference_unit.js +16 -1
  141. package/test/media/streaming_engine_integration.js +4 -2
  142. package/test/media/streaming_engine_unit.js +147 -0
  143. package/test/net/http_plugin_unit.js +20 -15
  144. package/test/offline/storage_integration.js +108 -4
  145. package/test/player_integration.js +178 -8
  146. package/test/player_src_equals_integration.js +41 -8
  147. package/test/player_unit.js +19 -9
  148. package/test/test/assets/3675/chunk-stream0-00001.m4s +0 -0
  149. package/test/test/assets/3675/chunk-stream0-00002.m4s +0 -0
  150. package/test/test/assets/3675/chunk-stream0-00003.m4s +0 -0
  151. package/test/test/assets/3675/chunk-stream0-00004.m4s +0 -0
  152. package/test/test/assets/3675/chunk-stream0-00005.m4s +0 -0
  153. package/test/test/assets/3675/chunk-stream0-00006.m4s +0 -0
  154. package/test/test/assets/3675/chunk-stream0-00007.m4s +0 -0
  155. package/test/test/assets/3675/dash_0.mpd +30 -0
  156. package/test/test/assets/3675/dash_1.mpd +30 -0
  157. package/test/test/assets/3675/dash_2.mpd +30 -0
  158. package/test/test/assets/3675/dash_3.mpd +30 -0
  159. package/test/test/assets/3675/dash_4.mpd +30 -0
  160. package/test/test/assets/3675/dash_5.mpd +26 -0
  161. package/test/test/assets/3675/init-stream0.m4s +0 -0
  162. package/test/test/assets/chapters.vtt +2 -2
  163. package/test/test/assets/chapters2.vtt +10 -0
  164. package/test/test/util/dash_parser_util.js +4 -0
  165. package/test/test/util/fake_ad.js +7 -0
  166. package/test/test/util/fake_ad_manager.js +1 -1
  167. package/test/test/util/fake_drm_engine.js +9 -2
  168. package/test/test/util/manifest_generator.js +3 -2
  169. package/test/test/util/test_scheme.js +14 -1
  170. package/test/test/util/ttml_utils.js +81 -0
  171. package/test/text/mp4_ttml_parser_unit.js +11 -18
  172. package/test/text/ttml_text_parser_unit.js +552 -227
  173. package/test/text/ui_text_displayer_unit.js +29 -17
  174. package/test/ui/localization_unit.js +15 -14
  175. package/test/ui/text_displayer_layout_unit.js +13 -1
  176. package/test/ui/ui_unit.js +250 -0
  177. package/test/util/cmcd_manager_unit.js +291 -0
  178. package/test/util/stream_utils_unit.js +45 -4
  179. package/third_party/closure-uri/uri.js +21 -0
  180. package/ui/airplay_button.js +1 -0
  181. package/ui/audio_language_selection.js +7 -0
  182. package/ui/cast_button.js +3 -3
  183. package/ui/context_menu.js +109 -0
  184. package/ui/controls.js +45 -13
  185. package/ui/controls.less +1 -0
  186. package/ui/enums.js +4 -0
  187. package/ui/externs/ui.js +28 -1
  188. package/ui/fast_forward_button.js +14 -5
  189. package/ui/fullscreen_button.js +1 -0
  190. package/ui/less/buttons.less +0 -4
  191. package/ui/less/containers.less +77 -7
  192. package/ui/less/overflow_menu.less +0 -10
  193. package/ui/less/tooltip.less +97 -0
  194. package/ui/locales/en.json +1 -0
  195. package/ui/locales/ru.json +6 -6
  196. package/ui/locales/source.json +4 -0
  197. package/ui/localization.js +28 -51
  198. package/ui/loop_button.js +5 -0
  199. package/ui/mute_button.js +2 -1
  200. package/ui/overflow_menu.js +5 -25
  201. package/ui/pip_button.js +7 -3
  202. package/ui/play_button.js +2 -2
  203. package/ui/playback_rate_selection.js +8 -9
  204. package/ui/resolution_selection.js +7 -0
  205. package/ui/rewind_button.js +15 -6
  206. package/ui/seek_bar.js +4 -2
  207. package/ui/settings_menu.js +36 -4
  208. package/ui/small_play_button.js +1 -0
  209. package/ui/statistics_button.js +253 -0
  210. package/ui/text_selection.js +12 -4
  211. package/ui/ui.js +30 -0
  212. package/ui/volume_bar.js +1 -1
  213. package/externs/media_capabilities.js +0 -16
@@ -19,6 +19,7 @@ goog.require('shaka.media.PresentationTimeline');
19
19
  goog.require('shaka.media.SegmentIndex');
20
20
  goog.require('shaka.net.NetworkingEngine');
21
21
  goog.require('shaka.text.TextEngine');
22
+ goog.require('shaka.util.CmcdManager');
22
23
  goog.require('shaka.util.Error');
23
24
  goog.require('shaka.util.Functional');
24
25
  goog.require('shaka.util.Iterables');
@@ -58,12 +59,12 @@ shaka.dash.DashParser = class {
58
59
  this.globalId_ = 1;
59
60
 
60
61
  /**
61
- * A map of IDs to SegmentIndex objects.
62
+ * A map of IDs to Stream objects.
62
63
  * ID: Period@id,AdaptationSet@id,@Representation@id
63
64
  * e.g.: '1,5,23'
64
- * @private {!Object.<string, !shaka.media.SegmentIndex>}
65
+ * @private {!Object.<string, !shaka.extern.Stream>}
65
66
  */
66
- this.segmentIndexMap_ = {};
67
+ this.streamMap_ = {};
67
68
 
68
69
  /**
69
70
  * A map of period ids to their durations
@@ -95,6 +96,18 @@ shaka.dash.DashParser = class {
95
96
  /** @private {!shaka.util.OperationManager} */
96
97
  this.operationManager_ = new shaka.util.OperationManager();
97
98
 
99
+ /**
100
+ * Largest period start time seen.
101
+ * @private {?number}
102
+ */
103
+ this.largestPeriodStartTime_ = null;
104
+
105
+ /**
106
+ * Period IDs seen in previous manifest.
107
+ * @private {!Array.<number>}
108
+ */
109
+ this.lastManifestUpdatePeriodIds_ = [];
110
+
98
111
  /**
99
112
  * The minimum of the availabilityTimeOffset values among the adaptation
100
113
  * sets.
@@ -152,8 +165,10 @@ shaka.dash.DashParser = class {
152
165
  stop() {
153
166
  // When the parser stops, release all segment indexes, which stops their
154
167
  // timers, as well.
155
- for (const segmentIndex of Object.values(this.segmentIndexMap_)) {
156
- segmentIndex.release();
168
+ for (const stream of Object.values(this.streamMap_)) {
169
+ if (stream.segmentIndex) {
170
+ stream.segmentIndex.release();
171
+ }
157
172
  }
158
173
 
159
174
  if (this.periodCombiner_) {
@@ -164,7 +179,7 @@ shaka.dash.DashParser = class {
164
179
  this.config_ = null;
165
180
  this.manifestUris_ = [];
166
181
  this.manifest_ = null;
167
- this.segmentIndexMap_ = {};
182
+ this.streamMap_ = {};
168
183
  this.periodCombiner_ = null;
169
184
 
170
185
  if (this.updateTimer_ != null) {
@@ -212,6 +227,9 @@ shaka.dash.DashParser = class {
212
227
  this.manifestUris_, this.config_.retryParameters);
213
228
  const networkingEngine = this.playerInterface_.networkingEngine;
214
229
 
230
+ const format = shaka.util.CmcdManager.StreamingFormat.DASH;
231
+ this.playerInterface_.modifyManifestRequest(request, {format: format});
232
+
215
233
  const startTime = Date.now();
216
234
  const operation = networkingEngine.request(requestType, request);
217
235
  this.operationManager_.manage(operation);
@@ -366,8 +384,11 @@ shaka.dash.DashParser = class {
366
384
  // that the complex logic of multi-Period eviction and period-flattening
367
385
  // is correct. See also:
368
386
  // https://github.com/google/shaka-player/issues/3169#issuecomment-823580634
369
- for (const segmentIndex of Object.values(this.segmentIndexMap_)) {
370
- segmentIndex.evict(presentationTimeline.getSegmentAvailabilityStart());
387
+ for (const stream of Object.values(this.streamMap_)) {
388
+ if (stream.segmentIndex) {
389
+ stream.segmentIndex.evict(
390
+ presentationTimeline.getSegmentAvailabilityStart());
391
+ }
371
392
  }
372
393
  } else {
373
394
  // DASH IOP v3.0 suggests using a default delay between minBufferTime
@@ -538,6 +559,7 @@ shaka.dash.DashParser = class {
538
559
  for (const {i, item: elem, next} of enumerate(periodNodes)) {
539
560
  const start = /** @type {number} */ (
540
561
  XmlUtils.parseAttr(elem, 'start', XmlUtils.parseDuration, prevEnd));
562
+ const periodId = elem.id;
541
563
  const givenDuration =
542
564
  XmlUtils.parseAttr(elem, 'duration', XmlUtils.parseDuration);
543
565
 
@@ -571,6 +593,42 @@ shaka.dash.DashParser = class {
571
593
  periodDuration = givenDuration;
572
594
  }
573
595
 
596
+ /**
597
+ * This is to improve robustness when the player observes manifest with
598
+ * past periods that are inconsistent to previous ones.
599
+ *
600
+ * This may happen when a CDN or proxy server switches its upstream from
601
+ * one encoder to another redundant encoder.
602
+ *
603
+ * Skip periods that match all of the following criteria:
604
+ * - Start time is earlier than latest period start time ever seen
605
+ * - Period ID is never seen in the previous manifest
606
+ * - Not the last period in the manifest
607
+ *
608
+ * Periods that meet the aforementioned criteria are considered invalid
609
+ * and should be safe to discard.
610
+ */
611
+
612
+ if (this.largestPeriodStartTime_ !== null &&
613
+ periodId !== null && start !== null &&
614
+ start < this.largestPeriodStartTime_ &&
615
+ !this.lastManifestUpdatePeriodIds_.includes(periodId) &&
616
+ i + 1 != periodNodes.length) {
617
+ shaka.log.debug(
618
+ `Skipping Period with ID ${periodId} as its start time is smaller` +
619
+ ' than the largest period start time that has been seen, and ID ' +
620
+ 'is unseen before');
621
+ continue;
622
+ }
623
+
624
+
625
+ // Save maximum period start time if it is the last period
626
+ if (start !== null &&
627
+ (this.largestPeriodStartTime_ === null ||
628
+ start > this.largestPeriodStartTime_)) {
629
+ this.largestPeriodStartTime_ = start;
630
+ }
631
+
574
632
  // Parse child nodes.
575
633
  const info = {
576
634
  start: start,
@@ -602,6 +660,9 @@ shaka.dash.DashParser = class {
602
660
  prevEnd = start + periodDuration;
603
661
  } // end of period parsing loop
604
662
 
663
+ // Replace previous seen periods with the current one.
664
+ this.lastManifestUpdatePeriodIds_ = periods.map((el) => el.id);
665
+
605
666
  if (presentationDuration != null) {
606
667
  if (prevEnd != presentationDuration) {
607
668
  shaka.log.warning(
@@ -813,6 +874,25 @@ shaka.dash.DashParser = class {
813
874
  }
814
875
  }
815
876
 
877
+ // Parallel for HLS VIDEO-RANGE as defined in DASH-IF IOP v4.3 6.2.5.1.
878
+ let videoRange;
879
+ const videoRangeScheme = 'urn:mpeg:mpegB:cicp:TransferCharacteristics';
880
+ const getVideoRangeFromTransferCharacteristicCICP = (cicp) => {
881
+ switch (cicp) {
882
+ case 1:
883
+ case 6:
884
+ case 13:
885
+ case 14:
886
+ case 15:
887
+ return 'SDR';
888
+ case 16:
889
+ return 'PQ';
890
+ case 18:
891
+ return 'HLG';
892
+ }
893
+ return undefined;
894
+ };
895
+
816
896
  const essentialProperties =
817
897
  XmlUtils.findChildren(elem, 'EssentialProperty');
818
898
  // ID of real AdaptationSet if this is a trick mode set:
@@ -822,11 +902,26 @@ shaka.dash.DashParser = class {
822
902
  const schemeId = prop.getAttribute('schemeIdUri');
823
903
  if (schemeId == 'http://dashif.org/guidelines/trickmode') {
824
904
  trickModeFor = prop.getAttribute('value');
905
+ } else if (schemeId == videoRangeScheme) {
906
+ videoRange = getVideoRangeFromTransferCharacteristicCICP(
907
+ parseInt(prop.getAttribute('value'), 10),
908
+ );
825
909
  } else {
826
910
  unrecognizedEssentialProperty = true;
827
911
  }
828
912
  }
829
913
 
914
+ const supplementalProperties =
915
+ XmlUtils.findChildren(elem, 'SupplementalProperty');
916
+ for (const prop of supplementalProperties) {
917
+ const schemeId = prop.getAttribute('schemeIdUri');
918
+ if (schemeId == videoRangeScheme) {
919
+ videoRange = getVideoRangeFromTransferCharacteristicCICP(
920
+ parseInt(prop.getAttribute('value'), 10),
921
+ );
922
+ }
923
+ }
924
+
830
925
  const accessibilities = XmlUtils.findChildren(elem, 'Accessibility');
831
926
  const LanguageUtils = shaka.util.LanguageUtils;
832
927
  const closedCaptions = new Map();
@@ -952,8 +1047,13 @@ shaka.dash.DashParser = class {
952
1047
  // Parse Representations into Streams.
953
1048
  const representations = XmlUtils.findChildren(elem, 'Representation');
954
1049
  const streams = representations.map((representation) => {
955
- return this.parseRepresentation_(context, contentProtection, kind,
956
- language, label, main, roleValues, closedCaptions, representation);
1050
+ const parsedRepresentation = this.parseRepresentation_(context,
1051
+ contentProtection, kind, language, label, main, roleValues,
1052
+ closedCaptions, representation);
1053
+ if (parsedRepresentation) {
1054
+ parsedRepresentation.hdr = parsedRepresentation.hdr || videoRange;
1055
+ }
1056
+ return parsedRepresentation;
957
1057
  }).filter((s) => !!s);
958
1058
 
959
1059
  if (streams.length == 0) {
@@ -1072,11 +1172,12 @@ shaka.dash.DashParser = class {
1072
1172
  context, requestInitSegment);
1073
1173
  } else if (context.representation.segmentList) {
1074
1174
  streamInfo = shaka.dash.SegmentList.createStreamInfo(
1075
- context, this.segmentIndexMap_);
1175
+ context, this.streamMap_);
1076
1176
  } else if (context.representation.segmentTemplate) {
1077
1177
  const hasManifest = !!this.manifest_;
1178
+
1078
1179
  streamInfo = shaka.dash.SegmentTemplate.createStreamInfo(
1079
- context, requestInitSegment, this.segmentIndexMap_, hasManifest,
1180
+ context, requestInitSegment, this.streamMap_, hasManifest,
1080
1181
  this.config_.dash.initialSegmentLimit, this.periodDurations_);
1081
1182
  } else {
1082
1183
  goog.asserts.assert(isText,
@@ -1129,8 +1230,10 @@ shaka.dash.DashParser = class {
1129
1230
 
1130
1231
  let forced = false;
1131
1232
  if (isText) {
1132
- // See: https://github.com/google/shaka-player/issues/2122
1133
- forced = roles.includes('forced_subtitle');
1233
+ // See: https://github.com/google/shaka-player/issues/2122 and
1234
+ // https://github.com/Dash-Industry-Forum/DASH-IF-IOP/issues/165
1235
+ forced = roles.includes('forced_subtitle') ||
1236
+ roles.includes('forced-subtitle');
1134
1237
  }
1135
1238
 
1136
1239
  let tilesLayout;
@@ -1163,13 +1266,29 @@ shaka.dash.DashParser = class {
1163
1266
  hdr = 'PQ';
1164
1267
  }
1165
1268
 
1269
+ const contextId = context.representation.id ?
1270
+ context.period.id + ',' + context.representation.id : '';
1271
+
1166
1272
  /** @type {shaka.extern.Stream} */
1167
1273
  const stream = {
1168
1274
  id: this.globalId_++,
1169
1275
  originalId: context.representation.id,
1170
1276
  createSegmentIndex: async () => {
1171
- if (!stream.segmentIndex) {
1172
- stream.segmentIndex = await streamInfo.generateSegmentIndex();
1277
+ // If we have a stream with the same context id stored in the map
1278
+ // that has no segmentIndex, we should set the segmentIndex for it.
1279
+ const storedInMap = contextId && context.dynamic &&
1280
+ this.streamMap_[contextId];
1281
+
1282
+ const currentStream = storedInMap ? this.streamMap_[contextId] : stream;
1283
+ if (!currentStream.segmentIndex) {
1284
+ currentStream.segmentIndex = await streamInfo.generateSegmentIndex();
1285
+ }
1286
+ },
1287
+
1288
+ closeSegmentIndex: () => {
1289
+ if (stream.segmentIndex) {
1290
+ stream.segmentIndex.release();
1291
+ stream.segmentIndex = null;
1173
1292
  }
1174
1293
  },
1175
1294
  segmentIndex: null,
@@ -1199,7 +1318,13 @@ shaka.dash.DashParser = class {
1199
1318
  closedCaptions,
1200
1319
  hdr,
1201
1320
  tilesLayout,
1321
+ matchedStreams: [],
1202
1322
  };
1323
+
1324
+ if (contextId && context.dynamic && !this.streamMap_[contextId]) {
1325
+ this.streamMap_[contextId] = stream;
1326
+ }
1327
+
1203
1328
  return stream;
1204
1329
  }
1205
1330
 
@@ -61,7 +61,9 @@ shaka.dash.SegmentBase = class {
61
61
  }
62
62
 
63
63
  const getUris = () => resolvedUris;
64
- return new shaka.media.InitSegmentReference(getUris, startByte, endByte);
64
+ const qualityInfo = shaka.dash.SegmentBase.createQualityInfo(context);
65
+ return new shaka.media.InitSegmentReference(
66
+ getUris, startByte, endByte, qualityInfo);
65
67
  }
66
68
 
67
69
  /**
@@ -346,4 +348,26 @@ shaka.dash.SegmentBase = class {
346
348
  indexRange.start, indexRange.end,
347
349
  scaledPresentationTimeOffset);
348
350
  }
351
+
352
+ /**
353
+ * Create a MediaQualityInfo object from a Context object.
354
+ *
355
+ * @param {!shaka.dash.DashParser.Context} context
356
+ * @return {!shaka.extern.MediaQualityInfo}
357
+ */
358
+ static createQualityInfo(context) {
359
+ const representation = context.representation;
360
+ return {
361
+ bandwidth: context.bandwidth,
362
+ audioSamplingRate: representation.audioSamplingRate,
363
+ codecs: representation.codecs,
364
+ contentType: representation.contentType,
365
+ frameRate: representation.frameRate || null,
366
+ height: representation.height || null,
367
+ mimeType: representation.mimeType,
368
+ channelsCount: representation.numChannels,
369
+ pixelAspectRatio: representation.pixelAspectRatio || null,
370
+ width: representation.width || null,
371
+ };
372
+ }
349
373
  };
@@ -30,10 +30,10 @@ shaka.dash.SegmentList = class {
30
30
  * Updates the existing SegmentIndex, if any.
31
31
  *
32
32
  * @param {shaka.dash.DashParser.Context} context
33
- * @param {!Object.<string, !shaka.media.SegmentIndex>} segmentIndexMap
33
+ * @param {!Object.<string, !shaka.extern.Stream>} streamMap
34
34
  * @return {shaka.dash.DashParser.StreamInfo}
35
35
  */
36
- static createStreamInfo(context, segmentIndexMap) {
36
+ static createStreamInfo(context, streamMap) {
37
37
  goog.asserts.assert(context.representation.segmentList,
38
38
  'Should only be called with SegmentList');
39
39
  const SegmentList = shaka.dash.SegmentList;
@@ -46,11 +46,14 @@ shaka.dash.SegmentList = class {
46
46
 
47
47
  /** @type {shaka.media.SegmentIndex} */
48
48
  let segmentIndex = null;
49
- let id = null;
49
+ let stream = null;
50
50
  if (context.period.id && context.representation.id) {
51
51
  // Only check/store the index if period and representation IDs are set.
52
- id = context.period.id + ',' + context.representation.id;
53
- segmentIndex = segmentIndexMap[id];
52
+ const id = context.period.id + ',' + context.representation.id;
53
+ stream = streamMap[id];
54
+ if (stream) {
55
+ segmentIndex = stream.segmentIndex;
56
+ }
54
57
  }
55
58
 
56
59
  const references = SegmentList.createSegmentReferences_(
@@ -63,12 +66,9 @@ shaka.dash.SegmentList = class {
63
66
  const start = context.presentationTimeline.getSegmentAvailabilityStart();
64
67
  segmentIndex.mergeAndEvict(references, start);
65
68
  } else {
66
- context.presentationTimeline.notifySegments(references);
67
69
  segmentIndex = new shaka.media.SegmentIndex(references);
68
- if (id && context.dynamic) {
69
- segmentIndexMap[id] = segmentIndex;
70
- }
71
70
  }
71
+ context.presentationTimeline.notifySegments(references);
72
72
 
73
73
  if (!context.dynamic || !context.periodInfo.isLastPeriod) {
74
74
  const periodStart = context.periodInfo.start;
@@ -77,8 +77,17 @@ shaka.dash.SegmentList = class {
77
77
  segmentIndex.fit(periodStart, periodEnd, isNew);
78
78
  }
79
79
 
80
+ if (stream) {
81
+ stream.segmentIndex = segmentIndex;
82
+ }
83
+
80
84
  return {
81
- generateSegmentIndex: () => Promise.resolve(segmentIndex),
85
+ generateSegmentIndex: () => {
86
+ if (!segmentIndex || segmentIndex.isEmpty()) {
87
+ segmentIndex.merge(references);
88
+ }
89
+ return Promise.resolve(segmentIndex);
90
+ },
82
91
  };
83
92
  }
84
93
 
@@ -31,7 +31,7 @@ shaka.dash.SegmentTemplate = class {
31
31
  * @param {shaka.dash.DashParser.Context} context
32
32
  * @param {shaka.dash.DashParser.RequestInitSegmentCallback}
33
33
  * requestInitSegment
34
- * @param {!Object.<string, !shaka.media.SegmentIndex>} segmentIndexMap
34
+ * @param {!Object.<string, !shaka.extern.Stream>} streamMap
35
35
  * @param {boolean} isUpdate True if the manifest is being updated.
36
36
  * @param {number} segmentLimit The maximum number of segments to generate for
37
37
  * a SegmentTemplate with fixed duration.
@@ -39,8 +39,8 @@ shaka.dash.SegmentTemplate = class {
39
39
  * @return {shaka.dash.DashParser.StreamInfo}
40
40
  */
41
41
  static createStreamInfo(
42
- context, requestInitSegment, segmentIndexMap, isUpdate,
43
- segmentLimit, periodDurationMap) {
42
+ context, requestInitSegment, streamMap, isUpdate, segmentLimit,
43
+ periodDurationMap) {
44
44
  goog.asserts.assert(context.representation.segmentTemplate,
45
45
  'Should only be called with SegmentTemplate');
46
46
  const SegmentTemplate = shaka.dash.SegmentTemplate;
@@ -86,10 +86,14 @@ shaka.dash.SegmentTemplate = class {
86
86
  /** @type {shaka.media.SegmentIndex} */
87
87
  let segmentIndex = null;
88
88
  let id = null;
89
+ let stream = null;
89
90
  if (context.period.id && context.representation.id) {
90
91
  // Only check/store the index if period and representation IDs are set.
91
92
  id = context.period.id + ',' + context.representation.id;
92
- segmentIndex = segmentIndexMap[id];
93
+ stream = streamMap[id];
94
+ if (stream) {
95
+ segmentIndex = stream.segmentIndex;
96
+ }
93
97
  }
94
98
 
95
99
  const references = SegmentTemplate.createFromTimeline_(
@@ -116,19 +120,28 @@ shaka.dash.SegmentTemplate = class {
116
120
  segmentIndex.mergeAndEvict(references,
117
121
  context.presentationTimeline.getSegmentAvailabilityStart());
118
122
  } else {
119
- context.presentationTimeline.notifySegments(references);
120
123
  segmentIndex = new shaka.media.SegmentIndex(references);
121
- if (id && context.dynamic) {
122
- segmentIndexMap[id] = segmentIndex;
123
- }
124
124
  }
125
+ context.presentationTimeline.notifySegments(references);
125
126
 
126
127
  if (shouldFit) {
127
128
  segmentIndex.fit(periodStart, periodEnd);
128
129
  }
129
130
 
131
+ if (stream && context.dynamic) {
132
+ stream.segmentIndex = segmentIndex;
133
+ }
134
+
130
135
  return {
131
- generateSegmentIndex: () => Promise.resolve(segmentIndex),
136
+ generateSegmentIndex: () => {
137
+ // If segmentIndex is deleted, or segmentIndex's references are
138
+ // released by closeSegmentIndex(), we should set the value of
139
+ // segmentIndex again.
140
+ if (!segmentIndex || segmentIndex.isEmpty()) {
141
+ segmentIndex.merge(references);
142
+ }
143
+ return Promise.resolve(segmentIndex);
144
+ },
132
145
  };
133
146
  }
134
147
  }
@@ -569,8 +582,8 @@ shaka.dash.SegmentTemplate = class {
569
582
  baseUris, [filledTemplate]);
570
583
  return resolvedUris;
571
584
  };
572
-
573
- return new shaka.media.InitSegmentReference(getUris, 0, null);
585
+ const qualityInfo = shaka.dash.SegmentBase.createQualityInfo(context);
586
+ return new shaka.media.InitSegmentReference(getUris, 0, null, qualityInfo);
574
587
  }
575
588
  };
576
589