shaka-player 3.2.2 → 3.3.2

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 (188) 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/.github/workflows/build_and_test.yaml +73 -0
  5. package/.github/workflows/release.yaml +53 -0
  6. package/AUTHORS +6 -5
  7. package/CHANGELOG.md +314 -1
  8. package/CONTRIBUTORS +4 -2
  9. package/build/checkversion.py +1 -1
  10. package/build/conformance.textproto +183 -169
  11. package/build/test.py +6 -0
  12. package/build/types/core +3 -0
  13. package/build/types/offline +1 -0
  14. package/build/types/polyfill +1 -0
  15. package/build/types/ui +2 -0
  16. package/demo/common/assets.js +18 -0
  17. package/demo/common/message_ids.js +13 -1
  18. package/demo/config.js +31 -1
  19. package/demo/index.html +1 -1
  20. package/demo/locales/en.json +12 -0
  21. package/demo/locales/source.json +48 -0
  22. package/dist/controls.css +26 -2
  23. package/dist/controls.css.map +1 -1
  24. package/dist/demo.compiled.debug.js +19 -16
  25. package/dist/demo.compiled.debug.map +3 -3
  26. package/dist/demo.compiled.js +19 -16
  27. package/dist/demo.compiled.map +3 -3
  28. package/dist/deps.js +21 -14
  29. package/dist/locales.js +31 -29
  30. package/dist/receiver.compiled.debug.js +11 -9
  31. package/dist/receiver.compiled.debug.map +3 -3
  32. package/dist/receiver.compiled.js +11 -9
  33. package/dist/receiver.compiled.map +3 -3
  34. package/dist/shaka-player.compiled.d.ts +232 -18
  35. package/dist/shaka-player.compiled.debug.d.ts +232 -18
  36. package/dist/shaka-player.compiled.debug.externs.js +293 -17
  37. package/dist/shaka-player.compiled.debug.js +481 -422
  38. package/dist/shaka-player.compiled.debug.map +4 -4
  39. package/dist/shaka-player.compiled.externs.js +293 -17
  40. package/dist/shaka-player.compiled.js +846 -817
  41. package/dist/shaka-player.compiled.map +4 -4
  42. package/dist/shaka-player.ui.d.ts +250 -30
  43. package/dist/shaka-player.ui.debug.d.ts +250 -30
  44. package/dist/shaka-player.ui.debug.externs.js +322 -28
  45. package/dist/shaka-player.ui.debug.js +630 -553
  46. package/dist/shaka-player.ui.debug.map +4 -4
  47. package/dist/shaka-player.ui.externs.js +322 -28
  48. package/dist/shaka-player.ui.js +1048 -1006
  49. package/dist/shaka-player.ui.map +4 -4
  50. package/docs/design/bg-fetch-after.gv +29 -0
  51. package/docs/design/bg-fetch-after.gv.png +0 -0
  52. package/docs/design/bg-fetch-before.gv +18 -0
  53. package/docs/design/bg-fetch-before.gv.png +0 -0
  54. package/docs/design/bg-fetch.md +134 -0
  55. package/docs/tutorials/blob-url.md +15 -0
  56. package/docs/tutorials/index.json +1 -0
  57. package/docs/tutorials/ui-customization.md +66 -0
  58. package/docs/tutorials/ui.md +5 -5
  59. package/externs/cmcd.js +186 -0
  60. package/externs/ima.js +3 -0
  61. package/externs/shaka/ads.js +5 -0
  62. package/externs/shaka/manifest.js +12 -1
  63. package/externs/shaka/manifest_parser.js +10 -1
  64. package/externs/shaka/net.js +20 -4
  65. package/externs/shaka/offline.js +31 -2
  66. package/externs/shaka/player.js +138 -5
  67. package/externs/shaka/text.js +24 -0
  68. package/externs/webcrypto.js +18 -0
  69. package/karma.conf.js +19 -15
  70. package/lib/abr/ewma.js +14 -0
  71. package/lib/abr/ewma_bandwidth_estimator.js +16 -2
  72. package/lib/abr/simple_abr_manager.js +6 -0
  73. package/lib/ads/ad_manager.js +1 -0
  74. package/lib/ads/client_side_ad.js +8 -0
  75. package/lib/ads/client_side_ad_manager.js +8 -4
  76. package/lib/ads/server_side_ad.js +7 -0
  77. package/lib/cast/cast_proxy.js +3 -0
  78. package/lib/cast/cast_receiver.js +3 -0
  79. package/lib/cast/cast_utils.js +1 -0
  80. package/lib/dash/dash_parser.js +79 -33
  81. package/lib/dash/segment_base.js +25 -1
  82. package/lib/dash/segment_list.js +18 -9
  83. package/lib/dash/segment_template.js +23 -10
  84. package/lib/hls/hls_parser.js +116 -23
  85. package/lib/hls/manifest_text_parser.js +1 -0
  86. package/lib/media/quality_observer.js +274 -0
  87. package/lib/media/region_observer.js +39 -33
  88. package/lib/media/region_timeline.js +17 -19
  89. package/lib/media/segment_index.js +8 -1
  90. package/lib/media/segment_reference.js +37 -4
  91. package/lib/media/streaming_engine.js +47 -6
  92. package/lib/net/http_fetch_plugin.js +31 -11
  93. package/lib/net/http_plugin_utils.js +1 -0
  94. package/lib/net/http_xhr_plugin.js +40 -14
  95. package/lib/net/networking_engine.js +85 -18
  96. package/lib/offline/download_info.js +68 -0
  97. package/lib/offline/download_manager.js +13 -5
  98. package/lib/offline/indexeddb/base_storage_cell.js +35 -0
  99. package/lib/offline/indexeddb/storage_mechanism.js +1 -1
  100. package/lib/offline/indexeddb/v5_storage_cell.js +5 -0
  101. package/lib/offline/storage.js +401 -293
  102. package/lib/offline/stored_content_utils.js +2 -0
  103. package/lib/player.js +254 -27
  104. package/lib/polyfill/media_capabilities.js +30 -15
  105. package/lib/polyfill/random_uuid.js +52 -0
  106. package/lib/text/cue.js +18 -0
  107. package/lib/text/ttml_text_parser.js +109 -60
  108. package/lib/text/ui_text_displayer.js +19 -13
  109. package/lib/util/cmcd_manager.js +672 -0
  110. package/lib/util/error.js +6 -0
  111. package/lib/util/fake_event_target.js +21 -1
  112. package/lib/util/language_utils.js +12 -0
  113. package/lib/util/mutex.js +52 -0
  114. package/lib/util/periods.js +98 -64
  115. package/lib/util/player_configuration.js +18 -0
  116. package/lib/util/stream_utils.js +86 -10
  117. package/lib/util/xml_utils.js +19 -0
  118. package/package.json +3 -6
  119. package/shaka-player.uncompiled.js +3 -0
  120. package/test/dash/dash_parser_content_protection_unit.js +2 -0
  121. package/test/dash/dash_parser_live_unit.js +144 -1
  122. package/test/dash/dash_parser_manifest_unit.js +37 -2
  123. package/test/dash/dash_parser_segment_base_unit.js +2 -0
  124. package/test/dash/dash_parser_segment_list_unit.js +2 -1
  125. package/test/dash/dash_parser_segment_template_unit.js +2 -1
  126. package/test/hls/hls_live_unit.js +2 -0
  127. package/test/hls/hls_parser_unit.js +76 -0
  128. package/test/media/drm_engine_unit.js +3 -3
  129. package/test/media/quality_observer_unit.js +142 -0
  130. package/test/media/region_observer_unit.js +40 -5
  131. package/test/media/region_timeline_unit.js +40 -22
  132. package/test/media/segment_reference_unit.js +16 -1
  133. package/test/media/streaming_engine_integration.js +4 -2
  134. package/test/media/streaming_engine_unit.js +147 -0
  135. package/test/net/http_plugin_unit.js +20 -15
  136. package/test/offline/storage_integration.js +108 -4
  137. package/test/player_integration.js +140 -8
  138. package/test/player_src_equals_integration.js +41 -8
  139. package/test/player_unit.js +19 -9
  140. package/test/test/assets/chapters.vtt +2 -2
  141. package/test/test/assets/chapters2.vtt +10 -0
  142. package/test/test/util/dash_parser_util.js +4 -0
  143. package/test/test/util/fake_ad.js +7 -0
  144. package/test/test/util/fake_drm_engine.js +9 -2
  145. package/test/test/util/manifest_generator.js +3 -2
  146. package/test/test/util/test_scheme.js +14 -1
  147. package/test/test/util/ttml_utils.js +81 -0
  148. package/test/test/util/util.js +10 -7
  149. package/test/text/mp4_ttml_parser_unit.js +11 -18
  150. package/test/text/ttml_text_parser_unit.js +544 -228
  151. package/test/ui/text_displayer_layout_unit.js +8 -7
  152. package/test/ui/ui_unit.js +250 -0
  153. package/test/util/cmcd_manager_unit.js +291 -0
  154. package/test/util/stream_utils_unit.js +36 -30
  155. package/third_party/closure-uri/uri.js +21 -0
  156. package/ui/airplay_button.js +1 -0
  157. package/ui/audio_language_selection.js +7 -0
  158. package/ui/cast_button.js +1 -0
  159. package/ui/context_menu.js +109 -0
  160. package/ui/controls.js +44 -7
  161. package/ui/controls.less +1 -0
  162. package/ui/enums.js +4 -0
  163. package/ui/externs/ui.js +28 -1
  164. package/ui/fast_forward_button.js +14 -5
  165. package/ui/fullscreen_button.js +1 -0
  166. package/ui/less/buttons.less +0 -4
  167. package/ui/less/containers.less +74 -0
  168. package/ui/less/overflow_menu.less +0 -10
  169. package/ui/less/tooltip.less +97 -0
  170. package/ui/locales/en.json +1 -0
  171. package/ui/locales/source.json +4 -0
  172. package/ui/localization.js +12 -36
  173. package/ui/loop_button.js +5 -0
  174. package/ui/mute_button.js +2 -1
  175. package/ui/overflow_menu.js +5 -25
  176. package/ui/pip_button.js +5 -0
  177. package/ui/play_button.js +2 -2
  178. package/ui/playback_rate_selection.js +8 -9
  179. package/ui/resolution_selection.js +7 -0
  180. package/ui/rewind_button.js +15 -6
  181. package/ui/seek_bar.js +4 -2
  182. package/ui/settings_menu.js +36 -4
  183. package/ui/small_play_button.js +1 -0
  184. package/ui/statistics_button.js +253 -0
  185. package/ui/text_selection.js +12 -4
  186. package/ui/ui.js +30 -0
  187. package/ui/volume_bar.js +1 -1
  188. package/externs/media_capabilities.js +0 -16
@@ -8,16 +8,19 @@ goog.provide('shaka.media.RegionObserver');
8
8
 
9
9
  goog.require('shaka.media.IPlayheadObserver');
10
10
  goog.require('shaka.media.RegionTimeline');
11
+ goog.require('shaka.util.EventManager');
12
+ goog.require('shaka.util.FakeEvent');
13
+ goog.require('shaka.util.FakeEventTarget');
11
14
 
12
15
 
13
16
  /**
14
17
  * The region observer watches a region timeline and playhead, and fires events
15
- * (onEnter, onExit, and onSkip) as the playhead moves.
18
+ * ('enter', 'exit', 'skip') as the playhead moves.
16
19
  *
17
20
  * @implements {shaka.media.IPlayheadObserver}
18
21
  * @final
19
22
  */
20
- shaka.media.RegionObserver = class {
23
+ shaka.media.RegionObserver = class extends shaka.util.FakeEventTarget {
21
24
  /**
22
25
  * Create a region observer for the given timeline. The observer does not
23
26
  * own the timeline, only uses it. This means that the observer should NOT
@@ -26,6 +29,8 @@ shaka.media.RegionObserver = class {
26
29
  * @param {!shaka.media.RegionTimeline} timeline
27
30
  */
28
31
  constructor(timeline) {
32
+ super();
33
+
29
34
  /** @private {shaka.media.RegionTimeline} */
30
35
  this.timeline_ = timeline;
31
36
 
@@ -39,13 +44,6 @@ shaka.media.RegionObserver = class {
39
44
  */
40
45
  this.oldPosition_ = new Map();
41
46
 
42
- /** @private {shaka.media.RegionObserver.EventListener} */
43
- this.onEnter_ = (region, seeking) => {};
44
- /** @private {shaka.media.RegionObserver.EventListener} */
45
- this.onExit_ = (region, seeking) => {};
46
- /** @private {shaka.media.RegionObserver.EventListener} */
47
- this.onSkip_ = (region, seeking) => {};
48
-
49
47
  // To make the rules easier to read, alias all the relative positions.
50
48
  const RelativePosition = shaka.media.RegionObserver.RelativePosition_;
51
49
  const BEFORE_THE_REGION = RelativePosition.BEFORE_THE_REGION;
@@ -62,39 +60,48 @@ shaka.media.RegionObserver = class {
62
60
  {
63
61
  weWere: null,
64
62
  weAre: IN_THE_REGION,
65
- invoke: (region, seeking) => this.onEnter_(region, seeking),
63
+ invoke: (region, seeking) => this.onEvent_('enter', region, seeking),
66
64
  },
67
65
  {
68
66
  weWere: BEFORE_THE_REGION,
69
67
  weAre: IN_THE_REGION,
70
- invoke: (region, seeking) => this.onEnter_(region, seeking),
68
+ invoke: (region, seeking) => this.onEvent_('enter', region, seeking),
71
69
  },
72
70
  {
73
71
  weWere: AFTER_THE_REGION,
74
72
  weAre: IN_THE_REGION,
75
- invoke: (region, seeking) => this.onEnter_(region, seeking),
73
+ invoke: (region, seeking) => this.onEvent_('enter', region, seeking),
76
74
  },
77
75
  {
78
76
  weWere: IN_THE_REGION,
79
77
  weAre: BEFORE_THE_REGION,
80
- invoke: (region, seeking) => this.onExit_(region, seeking),
78
+ invoke: (region, seeking) => this.onEvent_('exit', region, seeking),
81
79
  },
82
80
  {
83
81
  weWere: IN_THE_REGION,
84
82
  weAre: AFTER_THE_REGION,
85
- invoke: (region, seeking) => this.onExit_(region, seeking),
83
+ invoke: (region, seeking) => this.onEvent_('exit', region, seeking),
86
84
  },
87
85
  {
88
86
  weWere: BEFORE_THE_REGION,
89
87
  weAre: AFTER_THE_REGION,
90
- invoke: (region, seeking) => this.onSkip_(region, seeking),
88
+ invoke: (region, seeking) => this.onEvent_('skip', region, seeking),
91
89
  },
92
90
  {
93
91
  weWere: AFTER_THE_REGION,
94
92
  weAre: BEFORE_THE_REGION,
95
- invoke: (region, seeking) => this.onSkip_(region, seeking),
93
+ invoke: (region, seeking) => this.onEvent_('skip', region, seeking),
96
94
  },
97
95
  ];
96
+
97
+ /** @private {shaka.util.EventManager} */
98
+ this.eventManager_ = new shaka.util.EventManager();
99
+
100
+ this.eventManager_.listen(this.timeline_, 'regionremove', (event) => {
101
+ /** @type {shaka.extern.TimelineRegionInfo} */
102
+ const region = event['region'];
103
+ this.oldPosition_.delete(region);
104
+ });
98
105
  }
99
106
 
100
107
  /** @override */
@@ -105,11 +112,10 @@ shaka.media.RegionObserver = class {
105
112
  // needed.
106
113
  this.oldPosition_.clear();
107
114
 
108
- // Clear the callbacks so that we don't hold onto any references external
109
- // to this class.
110
- this.onEnter_ = (region, seeking) => {};
111
- this.onExit_ = (region, seeking) => {};
112
- this.onSkip_ = (region, seeking) => {};
115
+ this.eventManager_.release();
116
+ this.eventManager_ = null;
117
+
118
+ super.release();
113
119
  }
114
120
 
115
121
  /** @override */
@@ -134,20 +140,20 @@ shaka.media.RegionObserver = class {
134
140
  }
135
141
 
136
142
  /**
137
- * Set all the listeners. This overrides any previous calls to |setListeners|.
143
+ * Dispatch events of the given type. All event types in this class have the
144
+ * same parameters: region and seeking.
138
145
  *
139
- * @param {shaka.media.RegionObserver.EventListener} onEnter
140
- * The callback for when we move from outside a region to inside a region.
141
- * @param {shaka.media.RegionObserver.EventListener} onExit
142
- * The callback for when we move from inside a region to outside a region.
143
- * @param {shaka.media.RegionObserver.EventListener} onSkip
144
- * The callback for when we move from before to after a region or from
145
- * after to before a region.
146
+ * @param {string} eventType
147
+ * @param {shaka.extern.TimelineRegionInfo} region
148
+ * @param {boolean} seeking
149
+ * @private
146
150
  */
147
- setListeners(onEnter, onExit, onSkip) {
148
- this.onEnter_ = onEnter;
149
- this.onExit_ = onExit;
150
- this.onSkip_ = onSkip;
151
+ onEvent_(eventType, region, seeking) {
152
+ const event = new shaka.util.FakeEvent(eventType, new Map([
153
+ ['region', region],
154
+ ['seeking', seeking],
155
+ ]));
156
+ this.dispatchEvent(event);
151
157
  }
152
158
 
153
159
  /**
@@ -6,26 +6,30 @@
6
6
 
7
7
  goog.provide('shaka.media.RegionTimeline');
8
8
 
9
+ goog.require('shaka.util.FakeEvent');
10
+ goog.require('shaka.util.FakeEventTarget');
9
11
  goog.require('shaka.util.IReleasable');
10
12
  goog.require('shaka.util.Timer');
11
13
 
12
14
 
13
15
  /**
14
16
  * The region timeline is a set of unique timeline region info entries. When
15
- * a new entry is added, the |onAddRegion| callback will be called.
17
+ * a new entry is added, the 'regionadd' event will be fired. When an entry is
18
+ * deleted, the 'regionremove' event will be fired.
16
19
  *
17
20
  * @implements {shaka.util.IReleasable}
18
21
  * @final
19
22
  */
20
- shaka.media.RegionTimeline = class {
23
+ shaka.media.RegionTimeline = class extends shaka.util.FakeEventTarget {
21
24
  /**
22
25
  * @param {!function():{start: number, end: number}} getSeekRange
23
26
  */
24
27
  constructor(getSeekRange) {
25
- /** @private {function(shaka.extern.TimelineRegionInfo)} */
26
- this.onAddRegion_ = (region) => {};
28
+ super();
29
+
27
30
  /** @private {!Set.<shaka.extern.TimelineRegionInfo>} */
28
31
  this.regions_ = new Set();
32
+
29
33
  /** @private {!function():{start: number, end: number}} */
30
34
  this.getSeekRange_ = getSeekRange;
31
35
 
@@ -44,22 +48,9 @@ shaka.media.RegionTimeline = class {
44
48
 
45
49
  /** @override */
46
50
  release() {
47
- // Prevent us from holding onto any external references via the callback.
48
- this.onAddRegion_ = (region) => {};
49
51
  this.regions_.clear();
50
52
  this.filterTimer_.stop();
51
- }
52
-
53
- /**
54
- * Set the callbacks for events. This will override any previous calls to
55
- * |setListeners|.
56
- *
57
- * @param {function(shaka.extern.TimelineRegionInfo)} onAddRegion
58
- * Set the callback for when we add a new region. This callback will only
59
- * be called when a region is unique (we reject duplicate regions).
60
- */
61
- setListeners(onAddRegion) {
62
- this.onAddRegion_ = onAddRegion;
53
+ super.release();
63
54
  }
64
55
 
65
56
  /**
@@ -72,7 +63,10 @@ shaka.media.RegionTimeline = class {
72
63
  // instead of making the parser track it.
73
64
  if (similarRegion == null) {
74
65
  this.regions_.add(region);
75
- this.onAddRegion_(region);
66
+ const event = new shaka.util.FakeEvent('regionadd', new Map([
67
+ ['region', region],
68
+ ]));
69
+ this.dispatchEvent(event);
76
70
  }
77
71
  }
78
72
 
@@ -89,6 +83,10 @@ shaka.media.RegionTimeline = class {
89
83
  // reson to store or process them.
90
84
  if (region.endTime < seekRange.start) {
91
85
  this.regions_.delete(region);
86
+ const event = new shaka.util.FakeEvent('regionremove', new Map([
87
+ ['region', region],
88
+ ]));
89
+ this.dispatchEvent(event);
92
90
  }
93
91
  }
94
92
  }
@@ -347,7 +347,8 @@ shaka.media.SegmentIndex = class {
347
347
  lastReference.appendWindowStart,
348
348
  lastReference.appendWindowEnd,
349
349
  lastReference.partialReferences,
350
- lastReference.tilesLayout);
350
+ lastReference.tilesLayout,
351
+ lastReference.tileDuration);
351
352
  }
352
353
 
353
354
 
@@ -427,6 +428,12 @@ shaka.media.SegmentIndex = class {
427
428
  return new shaka.media.SegmentIterator(this, index, partialSegmentIndex);
428
429
  }
429
430
 
431
+ /**
432
+ * @return {boolean}
433
+ */
434
+ isEmpty() {
435
+ return this.references.length == 0;
436
+ }
430
437
 
431
438
  /**
432
439
  * Create a SegmentIndex for a single segment of the given start time and
@@ -23,11 +23,13 @@ shaka.media.InitSegmentReference = class {
23
23
  * of the resource containing the segment.
24
24
  * @param {number} startByte The offset from the start of the resource to the
25
25
  * start of the segment.
26
- * @param {?number} endByte The offset from the start of the resource to the
27
- * end of the segment, inclusive. A value of null indicates that the
26
+ * @param {?number} endByte The offset from the start of the resource
27
+ * to the end of the segment, inclusive. A value of null indicates that the
28
28
  * segment extends to the end of the resource.
29
+ * @param {null|shaka.extern.MediaQualityInfo=} mediaQuality Information about
30
+ * the quality of the media associated with this init segment.
29
31
  */
30
- constructor(uris, startByte, endByte) {
32
+ constructor(uris, startByte, endByte, mediaQuality = null) {
31
33
  /** @type {function():!Array.<string>} */
32
34
  this.getUris = uris;
33
35
 
@@ -36,6 +38,9 @@ shaka.media.InitSegmentReference = class {
36
38
 
37
39
  /** @const {?number} */
38
40
  this.endByte = endByte;
41
+
42
+ /** @const {shaka.extern.MediaQualityInfo|null} */
43
+ this.mediaQuality = mediaQuality;
39
44
  }
40
45
 
41
46
  /**
@@ -73,6 +78,16 @@ shaka.media.InitSegmentReference = class {
73
78
  }
74
79
  }
75
80
 
81
+ /**
82
+ * Returns media quality information for the segments associated with
83
+ * this init segment.
84
+ *
85
+ * @return {?shaka.extern.MediaQualityInfo}
86
+ */
87
+ getMediaQuality() {
88
+ return this.mediaQuality;
89
+ }
90
+
76
91
  /**
77
92
  * Check if two initSegmentReference have all the same values.
78
93
  * @param {?shaka.media.InitSegmentReference} reference1
@@ -140,11 +155,15 @@ shaka.media.SegmentReference = class {
140
155
  * The value is a grid-item-dimension consisting of two positive decimal
141
156
  * integers in the format: column-x-row ('4x3'). It describes the
142
157
  * arrangement of Images in a Grid. The minimum valid LAYOUT is '1x1'.
158
+ * @param {?number=} tileDuration
159
+ * The explicit duration of an individual tile within the tiles grid.
160
+ * If not provided, the duration should be automatically calculated based on
161
+ * the duration of the reference.
143
162
  */
144
163
  constructor(
145
164
  startTime, endTime, uris, startByte, endByte, initSegmentReference,
146
165
  timestampOffset, appendWindowStart, appendWindowEnd,
147
- partialReferences = [], tilesLayout = '') {
166
+ partialReferences = [], tilesLayout = '', tileDuration = null) {
148
167
  // A preload hinted Partial Segment has the same startTime and endTime.
149
168
  goog.asserts.assert(startTime <= endTime,
150
169
  'startTime must be less than or equal to endTime');
@@ -191,6 +210,9 @@ shaka.media.SegmentReference = class {
191
210
 
192
211
  /** @type {?string} */
193
212
  this.tilesLayout = tilesLayout;
213
+
214
+ /** @type {?number} */
215
+ this.tileDuration = tileDuration;
194
216
  }
195
217
 
196
218
  /**
@@ -275,6 +297,17 @@ shaka.media.SegmentReference = class {
275
297
  getTilesLayout() {
276
298
  return this.tilesLayout;
277
299
  }
300
+
301
+ /**
302
+ * Returns the segment's explicit tile duration.
303
+ * Only defined in image segments.
304
+ *
305
+ * @return {?number}
306
+ * @export
307
+ */
308
+ getTileDuration() {
309
+ return this.tileDuration;
310
+ }
278
311
  };
279
312
 
280
313
 
@@ -431,6 +431,11 @@ shaka.media.StreamingEngine = class {
431
431
  this.playerInterface_.mediaSourceEngine.reinitText(fullMimeType);
432
432
  }
433
433
 
434
+ // Releases the segmentIndex of the old stream.
435
+ if (mediaState.stream.closeSegmentIndex) {
436
+ mediaState.stream.closeSegmentIndex();
437
+ }
438
+
434
439
  mediaState.stream = stream;
435
440
  mediaState.segmentIterator = null;
436
441
 
@@ -623,7 +628,7 @@ shaka.media.StreamingEngine = class {
623
628
 
624
629
  // Don't clear the buffer unless something is buffered. This extra
625
630
  // check prevents extra, useless calls to clear the buffer.
626
- if (somethingBuffered) {
631
+ if (somethingBuffered || mediaState.performingUpdate) {
627
632
  this.forceClearBuffer_(mediaState);
628
633
  streamCleared = true;
629
634
  }
@@ -863,7 +868,7 @@ shaka.media.StreamingEngine = class {
863
868
  return;
864
869
  }
865
870
 
866
- // Make sure the segment index exists.
871
+ // Make sure the segment index exists. If not, create the segment index.
867
872
  if (!mediaState.stream.segmentIndex) {
868
873
  const thisStream = mediaState.stream;
869
874
 
@@ -873,6 +878,12 @@ shaka.media.StreamingEngine = class {
873
878
  // We switched streams while in the middle of this async call to
874
879
  // createSegmentIndex. Abandon this update and schedule a new one if
875
880
  // there's not already one pending.
881
+ // Releases the segmentIndex of the old stream.
882
+ if (thisStream.closeSegmentIndex) {
883
+ goog.asserts.assert(!mediaState.stream.segmentIndex,
884
+ 'mediastate.stream should not have segmentIndex yet.');
885
+ thisStream.closeSegmentIndex();
886
+ }
876
887
  if (mediaState.updateTimer == null) {
877
888
  this.scheduleUpdate_(mediaState, 0);
878
889
  }
@@ -977,7 +988,10 @@ shaka.media.StreamingEngine = class {
977
988
  unscaledBufferingGoal * this.bufferingGoalScale_;
978
989
 
979
990
  // Check if we've buffered to the end of the presentation.
980
- if (timeNeeded >= this.manifest_.presentationTimeline.getDuration()) {
991
+ const timeUntilEnd =
992
+ this.manifest_.presentationTimeline.getDuration() - timeNeeded;
993
+ const oneMicrosecond = 1e-6;
994
+ if (timeUntilEnd < oneMicrosecond) {
981
995
  // We shouldn't rebuffer if the playhead is close to the end of the
982
996
  // presentation.
983
997
  shaka.log.debug(logPrefix, 'buffered to end of presentation');
@@ -1530,6 +1544,8 @@ shaka.media.StreamingEngine = class {
1530
1544
  throw error;
1531
1545
  }
1532
1546
  };
1547
+ this.playerInterface_.onInitSegmentAppended(
1548
+ reference.startTime, reference.initSegmentReference);
1533
1549
  operations.push(append());
1534
1550
  }
1535
1551
  }
@@ -1555,7 +1571,9 @@ shaka.media.StreamingEngine = class {
1555
1571
 
1556
1572
  const hasClosedCaptions = stream.closedCaptions &&
1557
1573
  stream.closedCaptions.size > 0;
1558
- if (stream.emsgSchemeIdUris != null && stream.emsgSchemeIdUris.length > 0) {
1574
+ if ((stream.emsgSchemeIdUris != null &&
1575
+ stream.emsgSchemeIdUris.length > 0) ||
1576
+ this.config_.dispatchAllEmsgBoxes) {
1559
1577
  new shaka.util.Mp4Parser()
1560
1578
  .fullBox(
1561
1579
  'emsg',
@@ -1641,7 +1659,8 @@ shaka.media.StreamingEngine = class {
1641
1659
  // See DASH sec. 5.10.3.3.1
1642
1660
  // If a DASH client detects an event message box with a scheme that is not
1643
1661
  // defined in MPD, the client is expected to ignore it.
1644
- if (emsgSchemeIdUris.includes(schemeId)) {
1662
+ if ((emsgSchemeIdUris && emsgSchemeIdUris.includes(schemeId)) ||
1663
+ this.config_.dispatchAllEmsgBoxes) {
1645
1664
  // See DASH sec. 5.10.4.1
1646
1665
  // A special scheme in DASH used to signal manifest updates.
1647
1666
  if (schemeId == 'urn:mpeg:dash:event:2012') {
@@ -1764,6 +1783,19 @@ shaka.media.StreamingEngine = class {
1764
1783
 
1765
1784
  shaka.log.v2('fetching: reference=', reference);
1766
1785
 
1786
+ const stream = mediaState.stream;
1787
+ this.playerInterface_.modifySegmentRequest(
1788
+ request,
1789
+ {
1790
+ type: stream.type,
1791
+ init: reference instanceof shaka.media.InitSegmentReference,
1792
+ duration: reference.endTime - reference.startTime,
1793
+ mimeType: stream.mimeType,
1794
+ codecs: stream.codecs,
1795
+ bandwidth: stream.bandwidth,
1796
+ },
1797
+ );
1798
+
1767
1799
  const op = this.playerInterface_.netEngine.request(requestType, request);
1768
1800
  mediaState.operation = op;
1769
1801
  const response = await op.promise;
@@ -1927,12 +1959,15 @@ shaka.media.StreamingEngine = class {
1927
1959
  * @typedef {{
1928
1960
  * getPresentationTime: function():number,
1929
1961
  * getBandwidthEstimate: function():number,
1962
+ * modifySegmentRequest: function(shaka.extern.Request,
1963
+ * shaka.util.CmcdManager.SegmentInfo),
1930
1964
  * mediaSourceEngine: !shaka.media.MediaSourceEngine,
1931
1965
  * netEngine: shaka.net.NetworkingEngine,
1932
1966
  * onError: function(!shaka.util.Error),
1933
1967
  * onEvent: function(!Event),
1934
1968
  * onManifestUpdate: function(),
1935
- * onSegmentAppended: function()
1969
+ * onSegmentAppended: function(),
1970
+ * onInitSegmentAppended: function(!number,!shaka.media.InitSegmentReference)
1936
1971
  * }}
1937
1972
  *
1938
1973
  * @property {function():number} getPresentationTime
@@ -1940,6 +1975,9 @@ shaka.media.StreamingEngine = class {
1940
1975
  * viewer is seeing on screen right now.
1941
1976
  * @property {function():number} getBandwidthEstimate
1942
1977
  * Get the estimated bandwidth in bits per second.
1978
+ * @property {function(shaka.extern.Request,
1979
+ * shaka.extern.Cmcd.SegmentInfo)} modifySegmentRequest
1980
+ * The request modifier
1943
1981
  * @property {!shaka.media.MediaSourceEngine} mediaSourceEngine
1944
1982
  * The MediaSourceEngine. The caller retains ownership.
1945
1983
  * @property {shaka.net.NetworkingEngine} netEngine
@@ -1954,6 +1992,9 @@ shaka.media.StreamingEngine = class {
1954
1992
  * Called when an embedded 'emsg' box should trigger a manifest update.
1955
1993
  * @property {function()} onSegmentAppended
1956
1994
  * Called after a segment is successfully appended to a MediaSource.
1995
+ * @property
1996
+ * {function(!number, !shaka.media.InitSegmentReference)} onInitSegmentAppended
1997
+ * Called when an init segment is appended to a MediaSource.
1957
1998
  */
1958
1999
  shaka.media.StreamingEngine.PlayerInterface;
1959
2000
 
@@ -27,10 +27,12 @@ shaka.net.HttpFetchPlugin = class {
27
27
  * @param {shaka.net.NetworkingEngine.RequestType} requestType
28
28
  * @param {shaka.extern.ProgressUpdated} progressUpdated Called when a
29
29
  * progress event happened.
30
+ * @param {shaka.extern.HeadersReceived} headersReceived Called when the
31
+ * headers for the download are received, but before the body is.
30
32
  * @return {!shaka.extern.IAbortableOperation.<shaka.extern.Response>}
31
33
  * @export
32
34
  */
33
- static parse(uri, request, requestType, progressUpdated) {
35
+ static parse(uri, request, requestType, progressUpdated, headersReceived) {
34
36
  const headers = new shaka.net.HttpFetchPlugin.Headers_();
35
37
  shaka.util.MapUtils.asMap(request.headers).forEach((value, key) => {
36
38
  headers.append(key, value);
@@ -55,7 +57,7 @@ shaka.net.HttpFetchPlugin = class {
55
57
  };
56
58
 
57
59
  const pendingRequest = shaka.net.HttpFetchPlugin.request_(
58
- uri, requestType, init, abortStatus, progressUpdated,
60
+ uri, requestType, init, abortStatus, progressUpdated, headersReceived,
59
61
  request.streamDataCallback);
60
62
 
61
63
  /** @type {!shaka.util.AbortableOperation} */
@@ -92,12 +94,13 @@ shaka.net.HttpFetchPlugin = class {
92
94
  * @param {!RequestInit} init
93
95
  * @param {shaka.net.HttpFetchPlugin.AbortStatus} abortStatus
94
96
  * @param {shaka.extern.ProgressUpdated} progressUpdated
97
+ * @param {shaka.extern.HeadersReceived} headersReceived
95
98
  * @param {?function(BufferSource):!Promise} streamDataCallback
96
99
  * @return {!Promise<!shaka.extern.Response>}
97
100
  * @private
98
101
  */
99
102
  static async request_(uri, requestType, init, abortStatus, progressUpdated,
100
- streamDataCallback) {
103
+ headersReceived, streamDataCallback) {
101
104
  const fetch = shaka.net.HttpFetchPlugin.fetch_;
102
105
  const ReadableStream = shaka.net.HttpFetchPlugin.ReadableStream_;
103
106
  let response;
@@ -113,6 +116,10 @@ shaka.net.HttpFetchPlugin = class {
113
116
  // headers are available. The download itself isn't done until the promise
114
117
  // for retrieving the data (arrayBuffer, blob, etc) has resolved.
115
118
  response = await fetch(uri, init);
119
+ // At this point in the process, we have the headers of the response, but
120
+ // not the body yet.
121
+ headersReceived(shaka.net.HttpFetchPlugin.headersToGenericObject_(
122
+ response.headers));
116
123
  // Getting the reader in this way allows us to observe the process of
117
124
  // downloading the body, instead of just waiting for an opaque promise to
118
125
  // resolve.
@@ -194,19 +201,28 @@ shaka.net.HttpFetchPlugin = class {
194
201
  }
195
202
  }
196
203
 
197
- const headers = {};
198
- /** @type {Headers} */
199
- const responseHeaders = response.headers;
200
- responseHeaders.forEach((value, key) => {
201
- // Since Edge incorrectly return the header with a leading new line
202
- // character ('\n'), we trim the header here.
203
- headers[key.trim()] = value;
204
- });
204
+ const headers = shaka.net.HttpFetchPlugin.headersToGenericObject_(
205
+ response.headers);
205
206
 
206
207
  return shaka.net.HttpPluginUtils.makeResponse(
207
208
  headers, arrayBuffer, response.status, uri, response.url, requestType);
208
209
  }
209
210
 
211
+ /**
212
+ * @param {!Headers} headers
213
+ * @return {!Object.<string, string>}
214
+ * @private
215
+ */
216
+ static headersToGenericObject_(headers) {
217
+ const headersObj = {};
218
+ headers.forEach((value, key) => {
219
+ // Since Edge incorrectly return the header with a leading new line
220
+ // character ('\n'), we trim the header here.
221
+ headersObj[key.trim()] = value;
222
+ });
223
+ return headersObj;
224
+ }
225
+
210
226
  /**
211
227
  * Determine if the Fetch API is supported in the browser. Note: this is
212
228
  * deliberately exposed as a method to allow the client app to use the same
@@ -290,4 +306,8 @@ if (shaka.net.HttpFetchPlugin.isSupported()) {
290
306
  'https', shaka.net.HttpFetchPlugin.parse,
291
307
  shaka.net.NetworkingEngine.PluginPriority.PREFERRED,
292
308
  /* progressSupport= */ true);
309
+ shaka.net.NetworkingEngine.registerScheme(
310
+ 'blob', shaka.net.HttpFetchPlugin.parse,
311
+ shaka.net.NetworkingEngine.PluginPriority.PREFERRED,
312
+ /* progressSupport= */ true);
293
313
  }
@@ -34,6 +34,7 @@ shaka.net.HttpPluginUtils = class {
34
34
  uri: responseURL || uri,
35
35
  originalUri: uri,
36
36
  data: data,
37
+ status: status,
37
38
  headers: headers,
38
39
  fromCache: !!headers['x-shaka-from-cache'],
39
40
  };
@@ -24,10 +24,12 @@ shaka.net.HttpXHRPlugin = class {
24
24
  * @param {shaka.net.NetworkingEngine.RequestType} requestType
25
25
  * @param {shaka.extern.ProgressUpdated} progressUpdated Called when a
26
26
  * progress event happened.
27
+ * @param {shaka.extern.HeadersReceived} headersReceived Called when the
28
+ * headers for the download are received, but before the body is.
27
29
  * @return {!shaka.extern.IAbortableOperation.<shaka.extern.Response>}
28
30
  * @export
29
31
  */
30
- static parse(uri, request, requestType, progressUpdated) {
32
+ static parse(uri, request, requestType, progressUpdated, headersReceived) {
31
33
  const xhr = new shaka.net.HttpXHRPlugin.Xhr_();
32
34
 
33
35
  // Last time stamp when we got a progress event.
@@ -48,23 +50,25 @@ shaka.net.HttpXHRPlugin = class {
48
50
  shaka.util.Error.Code.OPERATION_ABORTED,
49
51
  uri, requestType));
50
52
  };
51
- xhr.onload = (event) => {
52
- const target = event.target;
53
- goog.asserts.assert(target, 'XHR onload has no target!');
54
- // Since Edge incorrectly return the header with a leading new
55
- // line character ('\n'), we trim the header here.
56
- const headerLines = target.getAllResponseHeaders().trim().split('\r\n');
57
- const headers = {};
58
- for (const header of headerLines) {
59
- /** @type {!Array.<string>} */
60
- const parts = header.split(': ');
61
- headers[parts[0].toLowerCase()] = parts.slice(1).join(': ');
53
+ let calledHeadersReceived = false;
54
+ xhr.onreadystatechange = (event) => {
55
+ // See if the readyState is 2 ("HEADERS_RECEIVED").
56
+ if (xhr.readyState == 2 && !calledHeadersReceived) {
57
+ const headers = shaka.net.HttpXHRPlugin.headersToGenericObject_(xhr);
58
+ headersReceived(headers);
59
+ // Don't send out this event twice.
60
+ calledHeadersReceived = true;
62
61
  }
62
+ };
63
+ xhr.onload = (event) => {
64
+ const headers = shaka.net.HttpXHRPlugin.headersToGenericObject_(xhr);
65
+ goog.asserts.assert(xhr.response instanceof ArrayBuffer,
66
+ 'XHR should have a response by now!');
67
+ const xhrResponse = xhr.response;
63
68
 
64
69
  try {
65
70
  const response = shaka.net.HttpPluginUtils.makeResponse(headers,
66
- target.response, target.status, uri, target.responseURL,
67
- requestType);
71
+ xhrResponse, xhr.status, uri, xhr.responseURL, requestType);
68
72
  resolve(response);
69
73
  } catch (error) {
70
74
  goog.asserts.assert(error instanceof shaka.util.Error,
@@ -116,6 +120,24 @@ shaka.net.HttpXHRPlugin = class {
116
120
  return Promise.resolve();
117
121
  });
118
122
  }
123
+
124
+ /**
125
+ * @param {!XMLHttpRequest} xhr
126
+ * @return {!Object.<string, string>}
127
+ * @private
128
+ */
129
+ static headersToGenericObject_(xhr) {
130
+ // Since Edge incorrectly return the header with a leading new
131
+ // line character ('\n'), we trim the header here.
132
+ const headerLines = xhr.getAllResponseHeaders().trim().split('\r\n');
133
+ const headers = {};
134
+ for (const header of headerLines) {
135
+ /** @type {!Array.<string>} */
136
+ const parts = header.split(': ');
137
+ headers[parts[0].toLowerCase()] = parts.slice(1).join(': ');
138
+ }
139
+ return headers;
140
+ }
119
141
  };
120
142
 
121
143
 
@@ -136,4 +158,8 @@ shaka.net.NetworkingEngine.registerScheme(
136
158
  'https', shaka.net.HttpXHRPlugin.parse,
137
159
  shaka.net.NetworkingEngine.PluginPriority.FALLBACK,
138
160
  /* progressSupport= */ true);
161
+ shaka.net.NetworkingEngine.registerScheme(
162
+ 'blob', shaka.net.HttpXHRPlugin.parse,
163
+ shaka.net.NetworkingEngine.PluginPriority.FALLBACK,
164
+ /* progressSupport= */ true);
139
165