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
@@ -0,0 +1,291 @@
1
+ /*! @license
2
+ * Shaka Player
3
+ * Copyright 2016 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ goog.provide('shaka.media.QualityObserver');
8
+
9
+ goog.require('shaka.media.IPlayheadObserver');
10
+ goog.require('shaka.log');
11
+
12
+ /**
13
+ * Monitors the quality of content being appended to the source
14
+ * buffers and fires onQualityChange events when the media quality
15
+ * at the playhead changes.
16
+ *
17
+ * @implements {shaka.media.IPlayheadObserver}
18
+ * @final
19
+ */
20
+ shaka.media.QualityObserver = class {
21
+ /**
22
+ * Creates a new QualityObserver.
23
+ *
24
+ * @param {!function():!shaka.extern.BufferedInfo} getBufferedInfo
25
+ * Buffered info is needed to purge QualityChanges that are no
26
+ * longer relevant.
27
+ */
28
+ constructor(getBufferedInfo) {
29
+ /**
30
+ * @private {!Map.<string,!shaka.media.QualityObserver.ContentTypeState>}
31
+ */
32
+ this.contentTypeStates_ = new Map();
33
+
34
+ /** @private {shaka.media.QualityObserver.EventListener} */
35
+ this.onQualityChange_ = (mediaQuality, position) => {};
36
+
37
+ /** @private function():!shaka.extern.BufferedInfo */
38
+ this.getBufferedInfo_ = getBufferedInfo;
39
+ }
40
+
41
+ /**
42
+ * Set all the listeners. This overrides any previous calls to |setListeners|.
43
+ *
44
+ * @param {shaka.media.QualityObserver.EventListener} onQualityChange
45
+ */
46
+ setListeners(onQualityChange) {
47
+ this.onQualityChange_ = onQualityChange;
48
+ }
49
+
50
+ /** @override */
51
+ release() {
52
+ this.contentTypeStates_.clear();
53
+ }
54
+
55
+ /**
56
+ * Get the ContenTypeState for a contentType, creating a new
57
+ * one if necessary.
58
+ *
59
+ * @param {!string} contentType
60
+ * The contend type e.g. "video" or "audio".
61
+ * @return {!shaka.media.QualityObserver.ContentTypeState}
62
+ * @private
63
+ */
64
+ getContentTypeState_(contentType) {
65
+ let contentTypeState = this.contentTypeStates_.get(contentType);
66
+ if (!contentTypeState) {
67
+ contentTypeState = {
68
+ qualityChangePositions: [],
69
+ currentQuality: null,
70
+ contentType: contentType,
71
+ };
72
+ this.contentTypeStates_.set(contentType, contentTypeState);
73
+ }
74
+ return contentTypeState;
75
+ }
76
+
77
+ /**
78
+ * Adds a QualityChangePosition for the contentType identified by
79
+ * the mediaQuality.contentType.
80
+ *
81
+ * @param {!shaka.extern.MediaQualityInfo} mediaQuality
82
+ * @param {!number} position
83
+ * Position in seconds of the quality change.
84
+ */
85
+ addMediaQualityChange(mediaQuality, position) {
86
+ const contentTypeState =
87
+ this.getContentTypeState_(mediaQuality.contentType);
88
+
89
+ // Remove unneeded QualityChangePosition(s) before adding the new one
90
+ this.purgeQualityChangePositions_(contentTypeState);
91
+
92
+ const newChangePosition = {
93
+ mediaQuality: mediaQuality,
94
+ position: position,
95
+ };
96
+
97
+ const changePositions = contentTypeState.qualityChangePositions;
98
+ const insertBeforeIndex = changePositions.findIndex(
99
+ (qualityChange) => (qualityChange.position >= position));
100
+
101
+ if (insertBeforeIndex >= 0) {
102
+ const duplicatePositions =
103
+ (changePositions[insertBeforeIndex].position == position) ? 1 : 0;
104
+ changePositions.splice(
105
+ insertBeforeIndex, duplicatePositions, newChangePosition);
106
+ } else {
107
+ changePositions.push(newChangePosition);
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Determines the media quality at a specific position in the source buffer.
113
+ *
114
+ * @param {!number} position
115
+ * Position in seconds
116
+ * @param {!shaka.media.QualityObserver.ContentTypeState} contentTypeState
117
+ * @return {?shaka.extern.MediaQualityInfo}
118
+ * @private
119
+ */
120
+ static getMediaQualityAtPosition_(position, contentTypeState) {
121
+ // The qualityChangePositions must be ordered by position ascending
122
+ // Find the last QualityChangePosition prior to the position
123
+ const changePositions = contentTypeState.qualityChangePositions;
124
+ for (let i = changePositions.length - 1; i >= 0; i--) {
125
+ const qualityChange = changePositions[i];
126
+ if (qualityChange.position <= position) {
127
+ return qualityChange.mediaQuality;
128
+ }
129
+ }
130
+ return null;
131
+ }
132
+
133
+ /**
134
+ * Determines if two MediaQualityInfo objects are the same or not.
135
+ *
136
+ * @param {?shaka.extern.MediaQualityInfo} mq1
137
+ * @param {?shaka.extern.MediaQualityInfo} mq2
138
+ * @return {boolean}
139
+ * @private
140
+ */
141
+ static mediaQualitiesAreTheSame_(mq1, mq2) {
142
+ if (mq1 === mq2) {
143
+ return true;
144
+ }
145
+ if (!mq1 || !mq2) {
146
+ return false;
147
+ }
148
+ return (mq1.bandwidth == mq2.bandwidth) &&
149
+ (mq1.audioSamplingRate == mq2.audioSamplingRate) &&
150
+ (mq1.codecs == mq2.codecs) &&
151
+ (mq1.contentType == mq2.contentType) &&
152
+ (mq1.frameRate == mq2.frameRate) &&
153
+ (mq1.height == mq2.height) &&
154
+ (mq1.mimeType == mq2.mimeType) &&
155
+ (mq1.channelsCount == mq2.channelsCount) &&
156
+ (mq1.pixelAspectRatio == mq2.pixelAspectRatio) &&
157
+ (mq1.width == mq2.width);
158
+ }
159
+
160
+ /** @override */
161
+ poll(positionInSeconds, wasSeeking) {
162
+ for (const contentTypeState of this.contentTypeStates_.values()) {
163
+ const qualityAtPosition =
164
+ shaka.media.QualityObserver.getMediaQualityAtPosition_(
165
+ positionInSeconds, contentTypeState);
166
+ if (qualityAtPosition &&
167
+ !shaka.media.QualityObserver.mediaQualitiesAreTheSame_(
168
+ contentTypeState.currentQuality, qualityAtPosition)) {
169
+ if (this.positionIsBuffered_(
170
+ positionInSeconds, qualityAtPosition.contentType)) {
171
+ contentTypeState.currentQuality = qualityAtPosition;
172
+ shaka.log.debug('Media quality changed at position ' +
173
+ positionInSeconds + ' ' + JSON.stringify(qualityAtPosition));
174
+ this.onQualityChange_(
175
+ qualityAtPosition, positionInSeconds);
176
+ }
177
+ }
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Determine if a position is buffered for a given content type.
183
+ *
184
+ * @param {!number} position
185
+ * @param {!string} contentType
186
+ * @private
187
+ */
188
+ positionIsBuffered_(position, contentType) {
189
+ const bufferedInfo = this.getBufferedInfo_();
190
+ const bufferedRanges = bufferedInfo[contentType];
191
+ if (bufferedRanges && bufferedRanges.length > 0) {
192
+ const bufferStart = bufferedRanges[0].start;
193
+ const bufferEnd = bufferedRanges[bufferedRanges.length - 1].end;
194
+ if (position >= bufferStart && position < bufferEnd) {
195
+ return true;
196
+ }
197
+ }
198
+ return false;
199
+ }
200
+
201
+ /**
202
+ * Removes the QualityChangePosition(s) that are not relevant to the buffered
203
+ * content of the specified contentType. Note that this function is
204
+ * invoked just before adding the quality change info associated with
205
+ * the next media segment to be appended.
206
+ *
207
+ * @param {!shaka.media.QualityObserver.ContentTypeState} contentTypeState
208
+ * @private
209
+ */
210
+ purgeQualityChangePositions_(contentTypeState) {
211
+ const bufferedInfo = this.getBufferedInfo_();
212
+ const bufferedRanges = bufferedInfo[contentTypeState.contentType];
213
+
214
+ if (bufferedRanges && bufferedRanges.length > 0) {
215
+ const bufferStart = bufferedRanges[0].start;
216
+ const bufferEnd = bufferedRanges[bufferedRanges.length - 1].end;
217
+ const oldChangePositions = contentTypeState.qualityChangePositions;
218
+ contentTypeState.qualityChangePositions =
219
+ oldChangePositions.filter(
220
+ (qualityChange, index) => {
221
+ // Remove all but last quality change before bufferStart.
222
+ if ((qualityChange.position <= bufferStart) &&
223
+ (index + 1 < oldChangePositions.length) &&
224
+ (oldChangePositions[index + 1].position <= bufferStart)) {
225
+ return false;
226
+ }
227
+ // Remove all quality changes after bufferEnd.
228
+ if (qualityChange.position >= bufferEnd) {
229
+ return false;
230
+ }
231
+ return true;
232
+ });
233
+ } else {
234
+ // Nothing is buffered; so remove all quality changes.
235
+ contentTypeState.qualityChangePositions = [];
236
+ }
237
+ }
238
+ };
239
+
240
+ /**
241
+ * @typedef {function(shaka.extern.MediaQualityInfo, number)}
242
+ *
243
+ * @description
244
+ * A callback function used to notify the player when media quality changes
245
+ * are detected at the playhead.
246
+ *
247
+ * The first argument is information about media quality at the playhead
248
+ * position.
249
+ *
250
+ * The second argument is the playhead position in seconds.
251
+ */
252
+ shaka.media.QualityObserver.EventListener;
253
+
254
+ /**
255
+ * @typedef {{
256
+ * mediaQuality: !shaka.extern.MediaQualityInfo,
257
+ * position: !number
258
+ * }}
259
+ *
260
+ * @description
261
+ * Identifies the position of a media quality change in the
262
+ * source buffer.
263
+ *
264
+ * @property {shaka.extern.MediaQualityInfo} !mediaQuality
265
+ * The new media quality for content after position in the source buffer.
266
+ * @property {number} !position
267
+ * A position in seconds in the source buffer
268
+ */
269
+ shaka.media.QualityObserver.QualityChangePosition;
270
+
271
+ /**
272
+ * @typedef {{
273
+ * qualityChangePositions:
274
+ * !Array.<shaka.media.QualityObserver.QualityChangePosition>,
275
+ * currentQuality: ?shaka.extern.MediaQualityInfo,
276
+ * contentType: !string
277
+ * }}
278
+ *
279
+ * @description
280
+ * Contains media quality information for a specific content type
281
+ * e.g video or audio.
282
+ *
283
+ * @property {!Array.<shaka.media.QualityObserver.QualityChangePosition>}
284
+ * qualityChangePositions
285
+ * Quality changes ordered by position ascending.
286
+ * @property {?shaka.media.MediaQualityInfo} currentMediaQuality
287
+ * The media quality at the playhead position.
288
+ * @property {string} contentType
289
+ * The contentType e.g. 'video' or 'audio'
290
+ */
291
+ shaka.media.QualityObserver.ContentTypeState;
@@ -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.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') {
@@ -1662,7 +1681,8 @@ shaka.media.StreamingEngine = class {
1662
1681
 
1663
1682
  // Dispatch an event to notify the application about the emsg box.
1664
1683
  const eventName = shaka.Player.EventName.Emsg;
1665
- const event = new shaka.util.FakeEvent(eventName, {'detail': emsg});
1684
+ const data = (new Map()).set('detail', emsg);
1685
+ const event = new shaka.util.FakeEvent(eventName, data);
1666
1686
  this.playerInterface_.onEvent(event);
1667
1687
  }
1668
1688
  }
@@ -1763,6 +1783,19 @@ shaka.media.StreamingEngine = class {
1763
1783
 
1764
1784
  shaka.log.v2('fetching: reference=', reference);
1765
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
+
1766
1799
  const op = this.playerInterface_.netEngine.request(requestType, request);
1767
1800
  mediaState.operation = op;
1768
1801
  const response = await op.promise;
@@ -1926,12 +1959,15 @@ shaka.media.StreamingEngine = class {
1926
1959
  * @typedef {{
1927
1960
  * getPresentationTime: function():number,
1928
1961
  * getBandwidthEstimate: function():number,
1962
+ * modifySegmentRequest: function(shaka.extern.Request,
1963
+ * shaka.util.CmcdManager.SegmentInfo),
1929
1964
  * mediaSourceEngine: !shaka.media.MediaSourceEngine,
1930
1965
  * netEngine: shaka.net.NetworkingEngine,
1931
1966
  * onError: function(!shaka.util.Error),
1932
1967
  * onEvent: function(!Event),
1933
1968
  * onManifestUpdate: function(),
1934
- * onSegmentAppended: function()
1969
+ * onSegmentAppended: function(),
1970
+ * onInitSegmentAppended: function(!number,!shaka.media.InitSegmentReference)
1935
1971
  * }}
1936
1972
  *
1937
1973
  * @property {function():number} getPresentationTime
@@ -1939,6 +1975,9 @@ shaka.media.StreamingEngine = class {
1939
1975
  * viewer is seeing on screen right now.
1940
1976
  * @property {function():number} getBandwidthEstimate
1941
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
1942
1981
  * @property {!shaka.media.MediaSourceEngine} mediaSourceEngine
1943
1982
  * The MediaSourceEngine. The caller retains ownership.
1944
1983
  * @property {shaka.net.NetworkingEngine} netEngine
@@ -1953,6 +1992,9 @@ shaka.media.StreamingEngine = class {
1953
1992
  * Called when an embedded 'emsg' box should trigger a manifest update.
1954
1993
  * @property {function()} onSegmentAppended
1955
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.
1956
1998
  */
1957
1999
  shaka.media.StreamingEngine.PlayerInterface;
1958
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
  };