shaka-player 3.1.4 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) 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 +6 -5
  5. package/CHANGELOG.md +363 -0
  6. package/CONTRIBUTORS +4 -2
  7. package/README.md +4 -4
  8. package/build/types/core +3 -0
  9. package/build/types/offline +1 -0
  10. package/build/types/polyfill +2 -0
  11. package/build/types/ui +2 -0
  12. package/demo/common/asset.js +9 -7
  13. package/demo/common/assets.js +28 -1
  14. package/demo/common/message_ids.js +15 -2
  15. package/demo/config.js +35 -2
  16. package/demo/index.html +1 -1
  17. package/demo/locales/en.json +14 -1
  18. package/demo/locales/source.json +56 -4
  19. package/demo/main.js +2 -1
  20. package/dist/controls.css +26 -2
  21. package/dist/controls.css.map +1 -1
  22. package/dist/demo.compiled.debug.js +24 -21
  23. package/dist/demo.compiled.debug.map +3 -3
  24. package/dist/demo.compiled.js +24 -21
  25. package/dist/demo.compiled.map +3 -3
  26. package/dist/deps.js +33 -25
  27. package/dist/locales.js +31 -29
  28. package/dist/receiver.compiled.debug.js +14 -12
  29. package/dist/receiver.compiled.debug.map +3 -3
  30. package/dist/receiver.compiled.js +14 -12
  31. package/dist/receiver.compiled.map +3 -3
  32. package/dist/shaka-player.compiled.d.ts +258 -20
  33. package/dist/shaka-player.compiled.debug.d.ts +258 -20
  34. package/dist/shaka-player.compiled.debug.externs.js +1005 -695
  35. package/dist/shaka-player.compiled.debug.js +720 -640
  36. package/dist/shaka-player.compiled.debug.map +4 -4
  37. package/dist/shaka-player.compiled.externs.js +1005 -695
  38. package/dist/shaka-player.compiled.js +850 -817
  39. package/dist/shaka-player.compiled.map +4 -4
  40. package/dist/shaka-player.ui.d.ts +274 -21
  41. package/dist/shaka-player.ui.debug.d.ts +274 -21
  42. package/dist/shaka-player.ui.debug.externs.js +766 -429
  43. package/dist/shaka-player.ui.debug.js +863 -765
  44. package/dist/shaka-player.ui.debug.map +4 -4
  45. package/dist/shaka-player.ui.externs.js +766 -429
  46. package/dist/shaka-player.ui.js +1042 -996
  47. package/dist/shaka-player.ui.map +4 -4
  48. package/docs/design/bg-fetch-after.gv +29 -0
  49. package/docs/design/bg-fetch-after.gv.png +0 -0
  50. package/docs/design/bg-fetch-before.gv +18 -0
  51. package/docs/design/bg-fetch-before.gv.png +0 -0
  52. package/docs/design/bg-fetch.md +134 -0
  53. package/docs/tutorials/blob-url.md +15 -0
  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/aria.js +6 -0
  58. package/externs/ima.js +3 -0
  59. package/externs/shaka/ads.js +29 -1
  60. package/externs/shaka/manifest.js +18 -2
  61. package/externs/shaka/manifest_parser.js +10 -1
  62. package/externs/shaka/net.js +20 -4
  63. package/externs/shaka/offline.js +36 -2
  64. package/externs/shaka/player.js +182 -13
  65. package/externs/shaka/text.js +24 -10
  66. package/externs/texttrack.js +3 -0
  67. package/externs/webcrypto.js +18 -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/ad_manager.js +16 -15
  72. package/lib/ads/client_side_ad.js +8 -0
  73. package/lib/ads/client_side_ad_manager.js +14 -6
  74. package/lib/ads/server_side_ad.js +7 -0
  75. package/lib/ads/server_side_ad_manager.js +19 -2
  76. package/lib/cast/cast_proxy.js +8 -0
  77. package/lib/cast/cast_utils.js +7 -1
  78. package/lib/cea/cea608_data_channel.js +56 -56
  79. package/lib/cea/cea608_memory.js +33 -33
  80. package/lib/cea/cea708_service.js +43 -43
  81. package/lib/cea/cea708_window.js +38 -38
  82. package/lib/cea/cea_decoder.js +13 -14
  83. package/lib/cea/cea_utils.js +23 -23
  84. package/lib/cea/dtvcc_packet_builder.js +7 -7
  85. package/lib/cea/i_caption_decoder.js +2 -3
  86. package/lib/cea/i_cea_parser.js +2 -2
  87. package/lib/cea/mp4_cea_parser.js +6 -6
  88. package/lib/dash/dash_parser.js +53 -14
  89. package/lib/dash/segment_base.js +25 -1
  90. package/lib/dash/segment_list.js +18 -9
  91. package/lib/dash/segment_template.js +23 -10
  92. package/lib/hls/hls_parser.js +210 -30
  93. package/lib/media/closed_caption_parser.js +1 -2
  94. package/lib/media/drm_engine.js +162 -192
  95. package/lib/media/quality_observer.js +291 -0
  96. package/lib/media/segment_index.js +9 -1
  97. package/lib/media/segment_reference.js +37 -5
  98. package/lib/media/streaming_engine.js +48 -10
  99. package/lib/net/http_fetch_plugin.js +31 -11
  100. package/lib/net/http_plugin_utils.js +1 -0
  101. package/lib/net/http_xhr_plugin.js +40 -14
  102. package/lib/net/networking_engine.js +82 -18
  103. package/lib/offline/download_info.js +68 -0
  104. package/lib/offline/download_manager.js +14 -7
  105. package/lib/offline/indexeddb/base_storage_cell.js +35 -0
  106. package/lib/offline/indexeddb/storage_mechanism.js +1 -1
  107. package/lib/offline/indexeddb/v1_storage_cell.js +1 -0
  108. package/lib/offline/indexeddb/v2_storage_cell.js +1 -0
  109. package/lib/offline/indexeddb/v5_storage_cell.js +5 -0
  110. package/lib/offline/manifest_converter.js +3 -1
  111. package/lib/offline/storage.js +404 -297
  112. package/lib/offline/stored_content_utils.js +2 -0
  113. package/lib/player.js +622 -153
  114. package/lib/polyfill/aria.js +82 -0
  115. package/lib/polyfill/random_uuid.js +52 -0
  116. package/lib/routing/walker.js +2 -3
  117. package/lib/text/cue.js +12 -0
  118. package/lib/text/srt_text_parser.js +4 -4
  119. package/lib/text/text_engine.js +2 -2
  120. package/lib/text/ttml_text_parser.js +25 -2
  121. package/lib/text/ui_text_displayer.js +2 -2
  122. package/lib/text/vtt_text_parser.js +2 -2
  123. package/lib/text/web_vtt_generator.js +55 -12
  124. package/lib/util/cmcd_manager.js +826 -0
  125. package/lib/util/error.js +12 -0
  126. package/lib/util/language_utils.js +12 -0
  127. package/lib/util/manifest_parser_utils.js +1 -0
  128. package/lib/util/mp4_box_parsers.js +20 -20
  129. package/lib/util/multi_map.js +8 -0
  130. package/lib/util/mutex.js +52 -0
  131. package/lib/util/periods.js +98 -64
  132. package/lib/util/player_configuration.js +31 -1
  133. package/lib/util/stream_utils.js +140 -85
  134. package/lib/util/xml_utils.js +31 -12
  135. package/package.json +1 -1
  136. package/roadmap.md +24 -22
  137. package/shaka-player.uncompiled.js +4 -0
  138. package/test/cast/cast_utils_unit.js +1 -0
  139. package/test/cea/cea608_memory_unit.js +1 -1
  140. package/test/cea/cea708_service_unit.js +8 -8
  141. package/test/cea/cea708_window_unit.js +6 -6
  142. package/test/cea/cea_decoder_unit.js +1 -1
  143. package/test/cea/dtvcc_packet_builder_unit.js +2 -2
  144. package/test/dash/dash_parser_content_protection_unit.js +2 -0
  145. package/test/dash/dash_parser_live_unit.js +101 -1
  146. package/test/dash/dash_parser_manifest_unit.js +78 -2
  147. package/test/dash/dash_parser_segment_base_unit.js +2 -0
  148. package/test/dash/dash_parser_segment_list_unit.js +2 -1
  149. package/test/dash/dash_parser_segment_template_unit.js +2 -1
  150. package/test/hls/hls_live_unit.js +2 -0
  151. package/test/hls/hls_parser_unit.js +252 -0
  152. package/test/media/drm_engine_integration.js +86 -12
  153. package/test/media/drm_engine_unit.js +310 -450
  154. package/test/media/quality_observer_unit.js +139 -0
  155. package/test/media/segment_reference_unit.js +16 -1
  156. package/test/media/streaming_engine_integration.js +2 -1
  157. package/test/media/streaming_engine_unit.js +2 -0
  158. package/test/net/http_plugin_unit.js +20 -15
  159. package/test/offline/manifest_convert_unit.js +2 -0
  160. package/test/offline/storage_integration.js +117 -23
  161. package/test/player_integration.js +151 -0
  162. package/test/player_src_equals_integration.js +52 -0
  163. package/test/player_unit.js +11 -33
  164. package/test/test/assets/chapters.srt +11 -0
  165. package/test/test/assets/chapters.vtt +10 -0
  166. package/test/test/util/cea_utils.js +12 -12
  167. package/test/test/util/dash_parser_util.js +4 -0
  168. package/test/test/util/fake_ad.js +7 -0
  169. package/test/test/util/fake_ad_manager.js +7 -0
  170. package/test/test/util/fake_drm_engine.js +9 -2
  171. package/test/test/util/manifest_generator.js +5 -2
  172. package/test/test/util/manifest_parser_util.js +3 -1
  173. package/test/test/util/test_scheme.js +14 -1
  174. package/test/text/ttml_text_parser_unit.js +47 -0
  175. package/test/text/web_vtt_generator_unit.js +81 -7
  176. package/test/ui/ui_unit.js +250 -0
  177. package/test/util/cmcd_manager_unit.js +277 -0
  178. package/test/util/stream_utils_unit.js +118 -10
  179. package/ui/airplay_button.js +2 -1
  180. package/ui/audio_language_selection.js +9 -5
  181. package/ui/cast_button.js +5 -6
  182. package/ui/constants.js +0 -3
  183. package/ui/context_menu.js +109 -0
  184. package/ui/controls.js +41 -7
  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 +16 -8
  189. package/ui/fullscreen_button.js +2 -3
  190. package/ui/language_utils.js +1 -1
  191. package/ui/less/buttons.less +0 -4
  192. package/ui/less/containers.less +74 -0
  193. package/ui/less/overflow_menu.less +0 -10
  194. package/ui/less/tooltip.less +97 -0
  195. package/ui/locales/en.json +1 -0
  196. package/ui/locales/source.json +4 -0
  197. package/ui/loop_button.js +6 -3
  198. package/ui/mute_button.js +3 -4
  199. package/ui/overflow_menu.js +7 -28
  200. package/ui/pip_button.js +10 -7
  201. package/ui/play_button.js +2 -2
  202. package/ui/playback_rate_selection.js +11 -15
  203. package/ui/presentation_time.js +1 -3
  204. package/ui/resolution_selection.js +11 -7
  205. package/ui/rewind_button.js +17 -9
  206. package/ui/seek_bar.js +6 -6
  207. package/ui/settings_menu.js +36 -4
  208. package/ui/small_play_button.js +1 -0
  209. package/ui/spacer.js +1 -1
  210. package/ui/statistics_button.js +253 -0
  211. package/ui/text_selection.js +19 -15
  212. package/ui/ui.js +30 -0
  213. package/ui/ui_utils.js +1 -1
  214. package/ui/volume_bar.js +2 -4
@@ -0,0 +1,826 @@
1
+ /*! @license
2
+ * Shaka Player
3
+ * Copyright 2016 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ goog.provide('shaka.util.CmcdManager');
8
+
9
+ goog.require('shaka.log');
10
+
11
+
12
+ /**
13
+ * @summary
14
+ * A CmcdManager maintains CMCD state as well as a collection of utility
15
+ * functions.
16
+ */
17
+ shaka.util.CmcdManager = class {
18
+ /**
19
+ * @param {shaka.util.CmcdManager.PlayerInterface} playerInterface
20
+ * @param {shaka.extern.CmcdConfiguration} config
21
+ */
22
+ constructor(playerInterface, config) {
23
+ /** @private {shaka.util.CmcdManager.PlayerInterface} */
24
+ this.playerInterface_ = playerInterface;
25
+
26
+ /** @private {?shaka.extern.CmcdConfiguration} */
27
+ this.config_ = config;
28
+
29
+ /**
30
+ * Session ID
31
+ *
32
+ * @private {string}
33
+ */
34
+ this.sid_ = config.sessionId || window.crypto.randomUUID();
35
+
36
+ /**
37
+ * Streaming format
38
+ *
39
+ * @private {(shaka.util.CmcdManager.StreamingFormat|undefined)}
40
+ */
41
+ this.sf_ = undefined;
42
+
43
+ /**
44
+ * @private {boolean}
45
+ */
46
+ this.playbackStarted_ = false;
47
+
48
+ /**
49
+ * @private {boolean}
50
+ */
51
+ this.buffering_ = true;
52
+
53
+ /**
54
+ * @private {boolean}
55
+ */
56
+ this.starved_ = false;
57
+ }
58
+
59
+ /**
60
+ * Set the buffering state
61
+ *
62
+ * @param {boolean} buffering
63
+ */
64
+ setBuffering(buffering) {
65
+ if (!buffering && !this.playbackStarted_) {
66
+ this.playbackStarted_ = true;
67
+ }
68
+
69
+ if (this.playbackStarted_ && buffering) {
70
+ this.starved_ = true;
71
+ }
72
+
73
+ this.buffering_ = buffering;
74
+ }
75
+
76
+ /**
77
+ * Apply CMCD data to a manifest request.
78
+ *
79
+ * @param {!shaka.extern.Request} request
80
+ * The request to apply CMCD data to
81
+ * @param {shaka.util.CmcdManager.ManifestInfo} manifestInfo
82
+ * The manifest format
83
+ */
84
+ applyManifestData(request, manifestInfo) {
85
+ try {
86
+ if (!this.config_.enabled) {
87
+ return;
88
+ }
89
+
90
+ this.sf_ = manifestInfo.format;
91
+
92
+ this.apply_(request, {
93
+ ot: shaka.util.CmcdManager.ObjectType.MANIFEST,
94
+ su: !this.playbackStarted_,
95
+ });
96
+ } catch (error) {
97
+ shaka.log.warnOnce('CMCD_MANIFEST_ERROR',
98
+ 'Could not generate manifest CMCD data.', error);
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Apply CMCD data to a segment request
104
+ *
105
+ * @param {!shaka.extern.Request} request
106
+ * @param {shaka.util.CmcdManager.SegmentInfo} segmentInfo
107
+ */
108
+ applySegmentData(request, segmentInfo) {
109
+ try {
110
+ if (!this.config_.enabled) {
111
+ return;
112
+ }
113
+
114
+ const data = {
115
+ d: segmentInfo.duration * 1000,
116
+ st: this.getStreamType_(),
117
+ };
118
+
119
+ data.ot = this.getObjectType_(segmentInfo);
120
+
121
+ const isMedia = data.ot === shaka.util.CmcdManager.ObjectType.VIDEO ||
122
+ data.ot === shaka.util.CmcdManager.ObjectType.AUDIO ||
123
+ data.ot === shaka.util.CmcdManager.ObjectType.MUXED ||
124
+ data.ot === shaka.util.CmcdManager.ObjectType.TIMED_TEXT;
125
+
126
+ if (isMedia) {
127
+ data.bl = this.getBufferLength_(segmentInfo.type);
128
+ }
129
+
130
+ if (segmentInfo.bandwidth) {
131
+ data.br = segmentInfo.bandwidth / 1000;
132
+ }
133
+
134
+ data.tb = this.getTopBandwidth_(segmentInfo.type) / 1000;
135
+
136
+ this.apply_(request, data);
137
+ } catch (error) {
138
+ shaka.log.warnOnce('CMCD_SEGMENT_ERROR',
139
+ 'Could not generate segment CMCD data.', error);
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Apply CMCD data to a text request
145
+ *
146
+ * @param {!shaka.extern.Request} request
147
+ */
148
+ applyTextData(request) {
149
+ try {
150
+ this.apply_(request, {
151
+ ot: shaka.util.CmcdManager.ObjectType.CAPTION,
152
+ su: true,
153
+ });
154
+ } catch (error) {
155
+ shaka.log.warnOnce('CMCD_TEXT_ERROR',
156
+ 'Could not generate text CMCD data.', error);
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Apply CMCD data to streams loaded via src=.
162
+ *
163
+ * @param {string} uri
164
+ * @param {string} mimeType
165
+ * @return {string}
166
+ */
167
+ appendSrcData(uri, mimeType) {
168
+ try {
169
+ if (!this.config_.enabled) {
170
+ return uri;
171
+ }
172
+
173
+ const data = this.createData_();
174
+ data.ot = this.getObjectTypeFromMimeType_(mimeType);
175
+ data.su = true;
176
+
177
+ const query = shaka.util.CmcdManager.toQuery(data);
178
+
179
+ return shaka.util.CmcdManager.appendQueryToUri(uri, query);
180
+ } catch (error) {
181
+ shaka.log.warnOnce('CMCD_SRC_ERROR',
182
+ 'Could not generate src CMCD data.', error);
183
+ return uri;
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Apply CMCD data to side car text track uri.
189
+ *
190
+ * @param {string} uri
191
+ * @return {string}
192
+ */
193
+ appendTextTrackData(uri) {
194
+ try {
195
+ if (!this.config_.enabled) {
196
+ return uri;
197
+ }
198
+
199
+ const data = this.createData_();
200
+ data.ot = shaka.util.CmcdManager.ObjectType.CAPTION;
201
+ data.su = true;
202
+
203
+ const query = shaka.util.CmcdManager.toQuery(data);
204
+
205
+ return shaka.util.CmcdManager.appendQueryToUri(uri, query);
206
+ } catch (error) {
207
+ shaka.log.warnOnce('CMCD_TEXT_TRACK_ERROR',
208
+ 'Could not generate text track CMCD data.', error);
209
+ return uri;
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Create baseline CMCD data
215
+ *
216
+ * @return {shaka.util.CmcdManager.Data}
217
+ * @private
218
+ */
219
+ createData_() {
220
+ return {
221
+ v: shaka.util.CmcdManager.Version,
222
+ sf: this.sf_,
223
+ sid: this.sid_,
224
+ cid: this.config_.contentId,
225
+ mtp: this.playerInterface_.getBandwidthEstimate() / 1000,
226
+ };
227
+ }
228
+
229
+ /**
230
+ * Apply CMCD data to a request.
231
+ *
232
+ * @param {!shaka.extern.Request} request The request to apply CMCD data to
233
+ * @param {!shaka.util.CmcdManager.Data} data The data object
234
+ * @param {boolean} useHeaders Send data via request headers
235
+ * @private
236
+ */
237
+ apply_(request, data = {}, useHeaders = this.config_.useHeaders) {
238
+ if (!this.config_.enabled) {
239
+ return;
240
+ }
241
+
242
+ // apply baseline data
243
+ Object.assign(data, this.createData_());
244
+
245
+ data.pr = this.playerInterface_.getPlaybackRate();
246
+
247
+ const isVideo = data.ot === shaka.util.CmcdManager.ObjectType.VIDEO ||
248
+ data.ot === shaka.util.CmcdManager.ObjectType.MUXED;
249
+
250
+ if (this.starved_ && isVideo) {
251
+ data.bs = true;
252
+ data.su = true;
253
+ this.starved_ = false;
254
+ }
255
+
256
+ if (data.su == null) {
257
+ data.su = this.buffering_;
258
+ }
259
+
260
+ // TODO: Implement rtp, nrr, nor, dl
261
+
262
+ if (useHeaders) {
263
+ const headers = shaka.util.CmcdManager.toHeaders(data);
264
+ if (!Object.keys(headers).length) {
265
+ return;
266
+ }
267
+
268
+ Object.assign(request.headers, headers);
269
+ } else {
270
+ const query = shaka.util.CmcdManager.toQuery(data);
271
+ if (!query) {
272
+ return;
273
+ }
274
+
275
+ request.uris = request.uris.map((uri) => {
276
+ return shaka.util.CmcdManager.appendQueryToUri(uri, query);
277
+ });
278
+ }
279
+ }
280
+
281
+ /**
282
+ * The CMCD object type.
283
+ *
284
+ * @param {shaka.util.CmcdManager.SegmentInfo} segmentInfo
285
+ * @private
286
+ */
287
+ getObjectType_(segmentInfo) {
288
+ const type = segmentInfo.type;
289
+
290
+ if (segmentInfo.init) {
291
+ return shaka.util.CmcdManager.ObjectType.INIT;
292
+ }
293
+
294
+ if (type == 'video') {
295
+ if (segmentInfo.codecs.includes(',')) {
296
+ return shaka.util.CmcdManager.ObjectType.MUXED;
297
+ }
298
+ return shaka.util.CmcdManager.ObjectType.VIDEO;
299
+ }
300
+
301
+ if (type == 'audio') {
302
+ return shaka.util.CmcdManager.ObjectType.AUDIO;
303
+ }
304
+
305
+ if (type == 'text') {
306
+ if (segmentInfo.mimeType === 'application/mp4') {
307
+ return shaka.util.CmcdManager.ObjectType.TIMED_TEXT;
308
+ }
309
+ return shaka.util.CmcdManager.ObjectType.CAPTION;
310
+ }
311
+
312
+ return undefined;
313
+ }
314
+
315
+ /**
316
+ * The CMCD object type from mimeType.
317
+ *
318
+ * @param {!string} mimeType
319
+ * @return {(shaka.util.CmcdManager.ObjectType|undefined)}
320
+ * @private
321
+ */
322
+ getObjectTypeFromMimeType_(mimeType) {
323
+ switch (mimeType) {
324
+ case 'video/webm':
325
+ case 'video/mp4':
326
+ return shaka.util.CmcdManager.ObjectType.MUXED;
327
+
328
+ case 'application/x-mpegurl':
329
+ return shaka.util.CmcdManager.ObjectType.MANIFEST;
330
+
331
+ default:
332
+ return undefined;
333
+ }
334
+ }
335
+
336
+ /**
337
+ * Get the buffer length for a media type in milliseconds
338
+ *
339
+ * @param {string} type
340
+ * @return {number}
341
+ * @private
342
+ */
343
+ getBufferLength_(type) {
344
+ const ranges = this.playerInterface_.getBufferedInfo()[type];
345
+
346
+ if (!ranges.length) {
347
+ return NaN;
348
+ }
349
+
350
+ const start = this.playerInterface_.getCurrentTime();
351
+ const range = ranges.find((r) => r.start <= start && r.end >= start);
352
+
353
+ if (!range) {
354
+ return NaN;
355
+ }
356
+
357
+ return (range.end - start) * 1000;
358
+ }
359
+
360
+ /**
361
+ * Get the stream type
362
+ *
363
+ * @return {shaka.util.CmcdManager.StreamType}
364
+ * @private
365
+ */
366
+ getStreamType_() {
367
+ const isLive = this.playerInterface_.isLive();
368
+ if (isLive) {
369
+ return shaka.util.CmcdManager.StreamType.LIVE;
370
+ } else {
371
+ return shaka.util.CmcdManager.StreamType.VOD;
372
+ }
373
+ }
374
+
375
+ /**
376
+ * Get the highest bandwidth for a given type.
377
+ *
378
+ * @param {string} type
379
+ * @return {number}
380
+ * @private
381
+ */
382
+ getTopBandwidth_(type) {
383
+ const manifest = this.playerInterface_.getManifest();
384
+ if (!manifest) {
385
+ return NaN;
386
+ }
387
+
388
+ const variants = (type === 'text') ?
389
+ manifest.textStreams : manifest.variants;
390
+ let top = variants[0][type] || variants[0];
391
+
392
+ for (const variant of variants) {
393
+ const stream = variant[type] || variant;
394
+ if (stream.bandwidth > top.bandwidth) {
395
+ top = stream;
396
+ }
397
+ }
398
+
399
+ return top.bandwidth;
400
+ }
401
+
402
+ /**
403
+ * Serialize a CMCD data object according to the rules defined in the
404
+ * section 3.2 of
405
+ * [CTA-5004](https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf).
406
+ *
407
+ * @param {shaka.util.CmcdManager.Data} data The CMCD data object
408
+ * @return {string}
409
+ */
410
+ static serialize(data) {
411
+ const results = [];
412
+ const isValid = (value) =>
413
+ !Number.isNaN(value) && value != null && value !== '' && value !== false;
414
+ const toRounded = (value) => Math.round(value);
415
+ const toHundred = (value) => toRounded(value / 100) * 100;
416
+ const toUrlSafe = (value) => encodeURIComponent(value);
417
+ const formatters = {
418
+ br: toRounded,
419
+ d: toRounded,
420
+ bl: toHundred,
421
+ dl: toHundred,
422
+ mtp: toHundred,
423
+ nor: toUrlSafe,
424
+ rtp: toHundred,
425
+ tb: toRounded,
426
+ };
427
+
428
+ const keys = Object.keys(data || {}).sort();
429
+
430
+ for (const key of keys) {
431
+ let value = data[key];
432
+
433
+ // ignore invalid values
434
+ if (!isValid(value)) {
435
+ continue;
436
+ }
437
+
438
+ // Version should only be reported if not equal to 1.
439
+ if (key === 'v' && value === 1) {
440
+ continue;
441
+ }
442
+
443
+ // Playback rate should only be sent if not equal to 1.
444
+ if (key == 'pr' && value === 1) {
445
+ continue;
446
+ }
447
+
448
+ // Certain values require special formatting
449
+ const formatter = formatters[key];
450
+ if (formatter) {
451
+ value = formatter(value);
452
+ }
453
+
454
+ // Serialize the key/value pair
455
+ const type = typeof value;
456
+ let result;
457
+
458
+ if (type === 'string' && key !== 'ot' && key !== 'sf' && key !== 'st') {
459
+ result = `${key}="${value.replace(/"/g, '"')}"`;
460
+ } else if (type === 'boolean') {
461
+ result = key;
462
+ } else if (type === 'symbol') {
463
+ result = `${key}=${value.description}`;
464
+ } else {
465
+ result = `${key}=${value}`;
466
+ }
467
+
468
+ results.push(result);
469
+ }
470
+
471
+ return results.join(',');
472
+ }
473
+
474
+ /**
475
+ * Convert a CMCD data object to request headers according to the rules
476
+ * defined in the section 2.1 and 3.2 of
477
+ * [CTA-5004](https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf).
478
+ *
479
+ * @param {shaka.util.CmcdManager.Data} data The CMCD data object
480
+ * @return {!Object}
481
+ */
482
+ static toHeaders(data) {
483
+ const keys = Object.keys(data);
484
+ const headers = {};
485
+ const headerNames = ['Object', 'Request', 'Session', 'Status'];
486
+ const headerGroups = [{}, {}, {}, {}];
487
+ const headerMap = {
488
+ br: 0, d: 0, ot: 0, tb: 0,
489
+ bl: 1, dl: 1, mtp: 1, nor: 1, nrr: 1, su: 1,
490
+ cid: 2, pr: 2, sf: 2, sid: 2, st: 2, v: 2,
491
+ bs: 3, rtp: 3,
492
+ };
493
+
494
+ for (const key of keys) {
495
+ // Unmapped fields are mapped to the Request header
496
+ const index = (headerMap[key] != null) ? headerMap[key] : 1;
497
+ headerGroups[index][key] = data[key];
498
+ }
499
+
500
+ for (let i = 0; i < headerGroups.length; i++) {
501
+ const value = shaka.util.CmcdManager.serialize(headerGroups[i]);
502
+ if (value) {
503
+ headers[`CMCD-${headerNames[i]}`] = value;
504
+ }
505
+ }
506
+
507
+ return headers;
508
+ }
509
+
510
+ /**
511
+ * Convert a CMCD data object to query args according to the rules
512
+ * defined in the section 2.2 and 3.2 of
513
+ * [CTA-5004](https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf).
514
+ *
515
+ * @param {shaka.util.CmcdManager.Data} data The CMCD data object
516
+ * @return {string}
517
+ */
518
+ static toQuery(data) {
519
+ return `CMCD=${encodeURIComponent(shaka.util.CmcdManager.serialize(data))}`;
520
+ }
521
+
522
+ /**
523
+ * Append query args to a uri.
524
+ *
525
+ * @param {string} uri
526
+ * @param {string} query
527
+ * @return {string}
528
+ */
529
+ static appendQueryToUri(uri, query) {
530
+ if (!query) {
531
+ return uri;
532
+ }
533
+
534
+ if (uri.includes('offline:')) {
535
+ return uri;
536
+ }
537
+
538
+ const separator = uri.includes('?') ? '&' : '?';
539
+ return `${uri}${separator}${query}`;
540
+ }
541
+ };
542
+
543
+
544
+ /**
545
+ * @typedef {{
546
+ * getBandwidthEstimate: function():number,
547
+ * getBufferedInfo: function():shaka.extern.BufferedInfo,
548
+ * getCurrentTime: function():number,
549
+ * getManifest: function():shaka.extern.Manifest,
550
+ * getPlaybackRate: function():number,
551
+ * isLive: function():boolean
552
+ * }}
553
+ *
554
+ * @property {function():number} getBandwidthEstimate
555
+ * Get the estimated bandwidth in bits per second.
556
+ * @property {function():shaka.extern.BufferedInfo} getBufferedInfo
557
+ * Get information about what the player has buffered.
558
+ * @property {function():number} getCurrentTime
559
+ * Get the current time
560
+ * @property {function():shaka.extern.Manifest} getManifest
561
+ * Get the manifest
562
+ * @property {function():number} getPlaybackRate
563
+ * Get the playback rate
564
+ * @property {function():boolean} isLive
565
+ * Get if the player is playing live content.
566
+ */
567
+ shaka.util.CmcdManager.PlayerInterface;
568
+
569
+
570
+ /**
571
+ * @typedef {{
572
+ * type: string,
573
+ * init: boolean,
574
+ * duration: number,
575
+ * mimeType: string,
576
+ * codecs: string,
577
+ * bandwidth: (number|undefined)
578
+ * }}
579
+ *
580
+ * @property {string} type
581
+ * The media type
582
+ * @property {boolean} init
583
+ * Flag indicating whether the segment is an init segment
584
+ * @property {number} duration
585
+ * The duration of the segment in seconds
586
+ * @property {string} mimeType
587
+ * The segment's mime type
588
+ * @property {string} codecs
589
+ * The segment's codecs
590
+ * @property {(number|undefined)} bandwidth
591
+ * The segment's variation bandwidth
592
+ *
593
+ * @export
594
+ */
595
+ shaka.util.CmcdManager.SegmentInfo;
596
+
597
+
598
+ /**
599
+ * @typedef {{
600
+ * format: shaka.util.CmcdManager.StreamingFormat
601
+ * }}
602
+ *
603
+ * @property {shaka.util.CmcdManager.StreamingFormat} format
604
+ * The manifest's stream format
605
+ *
606
+ * @export
607
+ */
608
+ shaka.util.CmcdManager.ManifestInfo;
609
+
610
+
611
+ /**
612
+ * @typedef {{
613
+ * br: (number|undefined),
614
+ * d: (number|undefined),
615
+ * ot: (shaka.util.CmcdManager.ObjectType|undefined),
616
+ * tb: (number|undefined),
617
+ * bl: (number|undefined),
618
+ * dl: (number|undefined),
619
+ * mtp: (number|undefined),
620
+ * nor: (string|undefined),
621
+ * nrr: (string|undefined),
622
+ * su: (boolean|undefined),
623
+ * cid: (string|undefined),
624
+ * pr: (number|undefined),
625
+ * sf: (shaka.util.CmcdManager.StreamingFormat|undefined),
626
+ * sid: (string|undefined),
627
+ * st: (shaka.util.CmcdManager.StreamType|undefined),
628
+ * v: (number|undefined),
629
+ * bs: (boolean|undefined),
630
+ * rtp: (number|undefined)
631
+ * }}
632
+ *
633
+ * @description
634
+ * Client Media Common Data (CMCD) data.
635
+ *
636
+ * @property {number} br
637
+ * The encoded bitrate of the audio or video object being requested. This may
638
+ * not be known precisely by the player; however, it MAY be estimated based
639
+ * upon playlist/manifest declarations. If the playlist declares both peak and
640
+ * average bitrate values, the peak value should be transmitted.
641
+ *
642
+ * @property {number} d
643
+ * The playback duration in milliseconds of the object being requested. If a
644
+ * partial segment is being requested, then this value MUST indicate the
645
+ * playback duration of that part and not that of its parent segment. This
646
+ * value can be an approximation of the estimated duration if the explicit
647
+ * value is not known.
648
+ *
649
+ * @property {shaka.util.CmcdManager.ObjectType} ot
650
+ * The media type of the current object being requested:
651
+ * - `m` = text file, such as a manifest or playlist
652
+ * - `a` = audio only
653
+ * - `v` = video only
654
+ * - `av` = muxed audio and video
655
+ * - `i` = init segment
656
+ * - `c` = caption or subtitle
657
+ * - `tt` = ISOBMFF timed text track
658
+ * - `k` = cryptographic key, license or certificate.
659
+ * - `o` = other
660
+ *
661
+ * If the object type being requested is unknown, then this key MUST NOT be
662
+ * used.
663
+ *
664
+ * @property {number} tb
665
+ * The highest bitrate rendition in the manifest or playlist that the client
666
+ * is allowed to play, given current codec, licensing and sizing constraints.
667
+ *
668
+ * @property {number} bl
669
+ * The buffer length associated with the media object being requested. This
670
+ * value MUST be rounded to the nearest 100 ms. This key SHOULD only be sent
671
+ * with an object type of ‘a’, ‘v’ or ‘av’.
672
+ *
673
+ * @property {number} dl
674
+ * Deadline from the request time until the first sample of this
675
+ * Segment/Object needs to be available in order to not create a buffer
676
+ * underrun or any other playback problems. This value MUST be rounded to the
677
+ * nearest 100ms. For a playback rate of 1, this may be equivalent to the
678
+ * player’s remaining buffer length.
679
+ *
680
+ * @property {number} mtp
681
+ * The throughput between client and server, as measured by the client and
682
+ * MUST be rounded to the nearest 100 kbps. This value, however derived,
683
+ * SHOULD be the value that the client is using to make its next Adaptive
684
+ * Bitrate switching decision. If the client is connected to multiple
685
+ * servers concurrently, it must take care to report only the throughput
686
+ * measured against the receiving server. If the client has multiple
687
+ * concurrent connections to the server, then the intent is that this value
688
+ * communicates the aggregate throughput the client sees across all those
689
+ * connections.
690
+ *
691
+ * @property {string} nor
692
+ * Relative path of the next object to be requested. This can be used to
693
+ * trigger pre-fetching by the CDN. This MUST be a path relative to the
694
+ * current request. This string MUST be URLEncoded. The client SHOULD NOT
695
+ * depend upon any pre-fetch action being taken - it is merely a request for
696
+ * such a pre-fetch to take place.
697
+ *
698
+ * @property {string} nrr
699
+ * If the next request will be a partial object request, then this string
700
+ * denotes the byte range to be requested. If the ‘nor’ field is not set, then
701
+ * the object is assumed to match the object currently being requested. The
702
+ * client SHOULD NOT depend upon any pre-fetch action being taken – it is
703
+ * merely a request for such a pre-fetch to take place. Formatting is similar
704
+ * to the HTTP Range header, except that the unit MUST be ‘byte’, the ‘Range:’
705
+ * prefix is NOT required and specifying multiple ranges is NOT allowed. Valid
706
+ * combinations are:
707
+ *
708
+ * - `"\<range-start\>-"`
709
+ * - `"\<range-start\>-\<range-end\>"`
710
+ * - `"-\<suffix-length\>"`
711
+ *
712
+ * @property {boolean} su
713
+ * Key is included without a value if the object is needed urgently due to
714
+ * startup, seeking or recovery after a buffer-empty event. The media SHOULD
715
+ * not be rendering when this request is made. This key MUST not be sent if it
716
+ * is FALSE.
717
+ *
718
+ * @property {string} cid
719
+ * A unique string identifying the current content. Maximum length is 64
720
+ * characters. This value is consistent across multiple different sessions and
721
+ * devices and is defined and updated at the discretion of the service
722
+ * provider.
723
+ *
724
+ * @property {number} pr
725
+ * The playback rate. `1` if real-time, `2` if double speed, `0` if not
726
+ * playing. SHOULD only be sent if not equal to `1`.
727
+ *
728
+ * @property {shaka.util.CmcdManager.StreamingFormat} sf
729
+ * The streaming format that defines the current request.
730
+ *
731
+ * - `d` = MPEG DASH
732
+ * - `h` = HTTP Live Streaming (HLS)
733
+ * - `s` = Smooth Streaming
734
+ * - `o` = other
735
+ *
736
+ * If the streaming format being requested is unknown, then this key MUST NOT
737
+ * be used.
738
+ *
739
+ * @property {string} sid
740
+ * A GUID identifying the current playback session. A playback session
741
+ * typically ties together segments belonging to a single media asset. Maximum
742
+ * length is 64 characters. It is RECOMMENDED to conform to the UUID
743
+ * specification.
744
+ *
745
+ * @property {shaka.util.CmcdManager.StreamType} st
746
+ * Stream type
747
+ * - `v` = all segments are available – e.g., VOD
748
+ * - `l` = segments become available over time – e.g., LIVE
749
+ *
750
+ * @property {number} v
751
+ * The version of this specification used for interpreting the defined key
752
+ * names and values. If this key is omitted, the client and server MUST
753
+ * interpret the values as being defined by version 1. Client SHOULD omit this
754
+ * field if the version is 1.
755
+ *
756
+ * @property {boolean} bs
757
+ * Buffer starvation key is included without a value if the buffer was starved
758
+ * at some point between the prior request and this object request, resulting
759
+ * in the player being in a rebuffering state and the video or audio playback
760
+ * being stalled. This key MUST NOT be sent if the buffer was not starved
761
+ * since the prior request.
762
+ *
763
+ * If the object type `ot` key is sent along with this key, then the `bs` key
764
+ * refers to the buffer associated with the particular object type. If no
765
+ * object type is communicated, then the buffer state applies to the current
766
+ * session.
767
+ *
768
+ * @property {number} rtp
769
+ * Requested maximum throughput
770
+ *
771
+ * The requested maximum throughput that the client considers sufficient for
772
+ * delivery of the asset. Values MUST be rounded to the nearest 100kbps. For
773
+ * example, a client would indicate that the current segment, encoded at
774
+ * 2Mbps, is to be delivered at no more than 10Mbps, by using rtp=10000.
775
+ *
776
+ * Note: This can benefit clients by preventing buffer saturation through
777
+ * over-delivery and can also deliver a community benefit through fair-share
778
+ * delivery. The concept is that each client receives the throughput necessary
779
+ * for great performance, but no more. The CDN may not support the rtp
780
+ * feature.
781
+ */
782
+ shaka.util.CmcdManager.Data;
783
+
784
+
785
+ /**
786
+ * @enum {string}
787
+ */
788
+ shaka.util.CmcdManager.ObjectType = {
789
+ MANIFEST: 'm',
790
+ AUDIO: 'a',
791
+ VIDEO: 'v',
792
+ MUXED: 'av',
793
+ INIT: 'i',
794
+ CAPTION: 'c',
795
+ TIMED_TEXT: 'tt',
796
+ KEY: 'k',
797
+ OTHER: 'o',
798
+ };
799
+
800
+
801
+ /**
802
+ * @enum {string}
803
+ */
804
+ shaka.util.CmcdManager.StreamType = {
805
+ VOD: 'v',
806
+ LIVE: 'l',
807
+ };
808
+
809
+
810
+ /**
811
+ * @enum {string}
812
+ * @export
813
+ */
814
+ shaka.util.CmcdManager.StreamingFormat = {
815
+ DASH: 'd',
816
+ HLS: 'h',
817
+ SMOOTH: 's',
818
+ OTHER: 'o',
819
+ };
820
+
821
+
822
+ /**
823
+ * The CMCD spec version
824
+ * @const {number}
825
+ */
826
+ shaka.util.CmcdManager.Version = 1;