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
@@ -0,0 +1,672 @@
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('goog.Uri');
10
+ goog.require('shaka.log');
11
+
12
+
13
+ /**
14
+ * @summary
15
+ * A CmcdManager maintains CMCD state as well as a collection of utility
16
+ * functions.
17
+ */
18
+ shaka.util.CmcdManager = class {
19
+ /**
20
+ * @param {shaka.util.CmcdManager.PlayerInterface} playerInterface
21
+ * @param {shaka.extern.CmcdConfiguration} config
22
+ */
23
+ constructor(playerInterface, config) {
24
+ /** @private {shaka.util.CmcdManager.PlayerInterface} */
25
+ this.playerInterface_ = playerInterface;
26
+
27
+ /** @private {?shaka.extern.CmcdConfiguration} */
28
+ this.config_ = config;
29
+
30
+ /**
31
+ * Session ID
32
+ *
33
+ * @private {string}
34
+ */
35
+ this.sid_ = '';
36
+
37
+ /**
38
+ * Streaming format
39
+ *
40
+ * @private {(shaka.util.CmcdManager.StreamingFormat|undefined)}
41
+ */
42
+ this.sf_ = undefined;
43
+
44
+ /**
45
+ * @private {boolean}
46
+ */
47
+ this.playbackStarted_ = false;
48
+
49
+ /**
50
+ * @private {boolean}
51
+ */
52
+ this.buffering_ = true;
53
+
54
+ /**
55
+ * @private {boolean}
56
+ */
57
+ this.starved_ = false;
58
+ }
59
+
60
+ /**
61
+ * Set the buffering state
62
+ *
63
+ * @param {boolean} buffering
64
+ */
65
+ setBuffering(buffering) {
66
+ if (!buffering && !this.playbackStarted_) {
67
+ this.playbackStarted_ = true;
68
+ }
69
+
70
+ if (this.playbackStarted_ && buffering) {
71
+ this.starved_ = true;
72
+ }
73
+
74
+ this.buffering_ = buffering;
75
+ }
76
+
77
+ /**
78
+ * Apply CMCD data to a manifest request.
79
+ *
80
+ * @param {!shaka.extern.Request} request
81
+ * The request to apply CMCD data to
82
+ * @param {shaka.util.CmcdManager.ManifestInfo} manifestInfo
83
+ * The manifest format
84
+ */
85
+ applyManifestData(request, manifestInfo) {
86
+ try {
87
+ if (!this.config_.enabled) {
88
+ return;
89
+ }
90
+
91
+ this.sf_ = manifestInfo.format;
92
+
93
+ this.apply_(request, {
94
+ ot: shaka.util.CmcdManager.ObjectType.MANIFEST,
95
+ su: !this.playbackStarted_,
96
+ });
97
+ } catch (error) {
98
+ shaka.log.warnOnce('CMCD_MANIFEST_ERROR',
99
+ 'Could not generate manifest CMCD data.', error);
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Apply CMCD data to a segment request
105
+ *
106
+ * @param {!shaka.extern.Request} request
107
+ * @param {shaka.util.CmcdManager.SegmentInfo} segmentInfo
108
+ */
109
+ applySegmentData(request, segmentInfo) {
110
+ try {
111
+ if (!this.config_.enabled) {
112
+ return;
113
+ }
114
+
115
+ const data = {
116
+ d: segmentInfo.duration * 1000,
117
+ st: this.getStreamType_(),
118
+ };
119
+
120
+ data.ot = this.getObjectType_(segmentInfo);
121
+
122
+ const ObjectType = shaka.util.CmcdManager.ObjectType;
123
+ const isMedia = data.ot === ObjectType.VIDEO ||
124
+ data.ot === ObjectType.AUDIO ||
125
+ data.ot === ObjectType.MUXED ||
126
+ data.ot === ObjectType.TIMED_TEXT;
127
+
128
+ if (isMedia) {
129
+ data.bl = this.getBufferLength_(segmentInfo.type);
130
+ }
131
+
132
+ if (segmentInfo.bandwidth) {
133
+ data.br = segmentInfo.bandwidth / 1000;
134
+ }
135
+
136
+ if (isMedia && data.ot !== ObjectType.TIMED_TEXT) {
137
+ data.tb = this.getTopBandwidth_(data.ot) / 1000;
138
+ }
139
+
140
+ this.apply_(request, data);
141
+ } catch (error) {
142
+ shaka.log.warnOnce('CMCD_SEGMENT_ERROR',
143
+ 'Could not generate segment CMCD data.', error);
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Apply CMCD data to a text request
149
+ *
150
+ * @param {!shaka.extern.Request} request
151
+ */
152
+ applyTextData(request) {
153
+ try {
154
+ if (!this.config_.enabled) {
155
+ return;
156
+ }
157
+
158
+ this.apply_(request, {
159
+ ot: shaka.util.CmcdManager.ObjectType.CAPTION,
160
+ su: true,
161
+ });
162
+ } catch (error) {
163
+ shaka.log.warnOnce('CMCD_TEXT_ERROR',
164
+ 'Could not generate text CMCD data.', error);
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Apply CMCD data to streams loaded via src=.
170
+ *
171
+ * @param {string} uri
172
+ * @param {string} mimeType
173
+ * @return {string}
174
+ */
175
+ appendSrcData(uri, mimeType) {
176
+ try {
177
+ if (!this.config_.enabled) {
178
+ return uri;
179
+ }
180
+
181
+ const data = this.createData_();
182
+ data.ot = this.getObjectTypeFromMimeType_(mimeType);
183
+ data.su = true;
184
+
185
+ const query = shaka.util.CmcdManager.toQuery(data);
186
+
187
+ return shaka.util.CmcdManager.appendQueryToUri(uri, query);
188
+ } catch (error) {
189
+ shaka.log.warnOnce('CMCD_SRC_ERROR',
190
+ 'Could not generate src CMCD data.', error);
191
+ return uri;
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Apply CMCD data to side car text track uri.
197
+ *
198
+ * @param {string} uri
199
+ * @return {string}
200
+ */
201
+ appendTextTrackData(uri) {
202
+ try {
203
+ if (!this.config_.enabled) {
204
+ return uri;
205
+ }
206
+
207
+ const data = this.createData_();
208
+ data.ot = shaka.util.CmcdManager.ObjectType.CAPTION;
209
+ data.su = true;
210
+
211
+ const query = shaka.util.CmcdManager.toQuery(data);
212
+
213
+ return shaka.util.CmcdManager.appendQueryToUri(uri, query);
214
+ } catch (error) {
215
+ shaka.log.warnOnce('CMCD_TEXT_TRACK_ERROR',
216
+ 'Could not generate text track CMCD data.', error);
217
+ return uri;
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Create baseline CMCD data
223
+ *
224
+ * @return {CmcdData}
225
+ * @private
226
+ */
227
+ createData_() {
228
+ if (!this.sid_) {
229
+ this.sid_ = this.config_.sessionId || window.crypto.randomUUID();
230
+ }
231
+ return {
232
+ v: shaka.util.CmcdManager.Version,
233
+ sf: this.sf_,
234
+ sid: this.sid_,
235
+ cid: this.config_.contentId,
236
+ mtp: this.playerInterface_.getBandwidthEstimate() / 1000,
237
+ };
238
+ }
239
+
240
+ /**
241
+ * Apply CMCD data to a request.
242
+ *
243
+ * @param {!shaka.extern.Request} request The request to apply CMCD data to
244
+ * @param {!CmcdData} data The data object
245
+ * @param {boolean} useHeaders Send data via request headers
246
+ * @private
247
+ */
248
+ apply_(request, data = {}, useHeaders = this.config_.useHeaders) {
249
+ if (!this.config_.enabled) {
250
+ return;
251
+ }
252
+
253
+ // apply baseline data
254
+ Object.assign(data, this.createData_());
255
+
256
+ data.pr = this.playerInterface_.getPlaybackRate();
257
+
258
+ const isVideo = data.ot === shaka.util.CmcdManager.ObjectType.VIDEO ||
259
+ data.ot === shaka.util.CmcdManager.ObjectType.MUXED;
260
+
261
+ if (this.starved_ && isVideo) {
262
+ data.bs = true;
263
+ data.su = true;
264
+ this.starved_ = false;
265
+ }
266
+
267
+ if (data.su == null) {
268
+ data.su = this.buffering_;
269
+ }
270
+
271
+ // TODO: Implement rtp, nrr, nor, dl
272
+
273
+ if (useHeaders) {
274
+ const headers = shaka.util.CmcdManager.toHeaders(data);
275
+ if (!Object.keys(headers).length) {
276
+ return;
277
+ }
278
+
279
+ Object.assign(request.headers, headers);
280
+ } else {
281
+ const query = shaka.util.CmcdManager.toQuery(data);
282
+ if (!query) {
283
+ return;
284
+ }
285
+
286
+ request.uris = request.uris.map((uri) => {
287
+ return shaka.util.CmcdManager.appendQueryToUri(uri, query);
288
+ });
289
+ }
290
+ }
291
+
292
+ /**
293
+ * The CMCD object type.
294
+ *
295
+ * @param {shaka.util.CmcdManager.SegmentInfo} segmentInfo
296
+ * @private
297
+ */
298
+ getObjectType_(segmentInfo) {
299
+ const type = segmentInfo.type;
300
+
301
+ if (segmentInfo.init) {
302
+ return shaka.util.CmcdManager.ObjectType.INIT;
303
+ }
304
+
305
+ if (type == 'video') {
306
+ if (segmentInfo.codecs.includes(',')) {
307
+ return shaka.util.CmcdManager.ObjectType.MUXED;
308
+ }
309
+ return shaka.util.CmcdManager.ObjectType.VIDEO;
310
+ }
311
+
312
+ if (type == 'audio') {
313
+ return shaka.util.CmcdManager.ObjectType.AUDIO;
314
+ }
315
+
316
+ if (type == 'text') {
317
+ if (segmentInfo.mimeType === 'application/mp4') {
318
+ return shaka.util.CmcdManager.ObjectType.TIMED_TEXT;
319
+ }
320
+ return shaka.util.CmcdManager.ObjectType.CAPTION;
321
+ }
322
+
323
+ return undefined;
324
+ }
325
+
326
+ /**
327
+ * The CMCD object type from mimeType.
328
+ *
329
+ * @param {!string} mimeType
330
+ * @return {(shaka.util.CmcdManager.ObjectType|undefined)}
331
+ * @private
332
+ */
333
+ getObjectTypeFromMimeType_(mimeType) {
334
+ switch (mimeType) {
335
+ case 'video/webm':
336
+ case 'video/mp4':
337
+ return shaka.util.CmcdManager.ObjectType.MUXED;
338
+
339
+ case 'application/x-mpegurl':
340
+ return shaka.util.CmcdManager.ObjectType.MANIFEST;
341
+
342
+ default:
343
+ return undefined;
344
+ }
345
+ }
346
+
347
+ /**
348
+ * Get the buffer length for a media type in milliseconds
349
+ *
350
+ * @param {string} type
351
+ * @return {number}
352
+ * @private
353
+ */
354
+ getBufferLength_(type) {
355
+ const ranges = this.playerInterface_.getBufferedInfo()[type];
356
+
357
+ if (!ranges.length) {
358
+ return NaN;
359
+ }
360
+
361
+ const start = this.playerInterface_.getCurrentTime();
362
+ const range = ranges.find((r) => r.start <= start && r.end >= start);
363
+
364
+ if (!range) {
365
+ return NaN;
366
+ }
367
+
368
+ return (range.end - start) * 1000;
369
+ }
370
+
371
+ /**
372
+ * Get the stream type
373
+ *
374
+ * @return {shaka.util.CmcdManager.StreamType}
375
+ * @private
376
+ */
377
+ getStreamType_() {
378
+ const isLive = this.playerInterface_.isLive();
379
+ if (isLive) {
380
+ return shaka.util.CmcdManager.StreamType.LIVE;
381
+ } else {
382
+ return shaka.util.CmcdManager.StreamType.VOD;
383
+ }
384
+ }
385
+
386
+ /**
387
+ * Get the highest bandwidth for a given type.
388
+ *
389
+ * @param {string} type
390
+ * @return {number}
391
+ * @private
392
+ */
393
+ getTopBandwidth_(type) {
394
+ const variants = this.playerInterface_.getVariantTracks();
395
+ if (!variants.length) {
396
+ return NaN;
397
+ }
398
+
399
+ let top = variants[0];
400
+
401
+ for (const variant of variants) {
402
+ if (variant.type === 'variant' && variant.bandwidth > top.bandwidth) {
403
+ top = variant;
404
+ }
405
+ }
406
+
407
+ const ObjectType = shaka.util.CmcdManager.ObjectType;
408
+
409
+ switch (type) {
410
+ case ObjectType.VIDEO:
411
+ return top.videoBandwidth || NaN;
412
+
413
+ case ObjectType.AUDIO:
414
+ return top.audioBandwidth || NaN;
415
+
416
+ default:
417
+ return top.bandwidth;
418
+ }
419
+ }
420
+
421
+ /**
422
+ * Serialize a CMCD data object according to the rules defined in the
423
+ * section 3.2 of
424
+ * [CTA-5004](https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf).
425
+ *
426
+ * @param {CmcdData} data The CMCD data object
427
+ * @return {string}
428
+ */
429
+ static serialize(data) {
430
+ const results = [];
431
+ const isValid = (value) =>
432
+ !Number.isNaN(value) && value != null && value !== '' && value !== false;
433
+ const toRounded = (value) => Math.round(value);
434
+ const toHundred = (value) => toRounded(value / 100) * 100;
435
+ const toUrlSafe = (value) => encodeURIComponent(value);
436
+ const formatters = {
437
+ br: toRounded,
438
+ d: toRounded,
439
+ bl: toHundred,
440
+ dl: toHundred,
441
+ mtp: toHundred,
442
+ nor: toUrlSafe,
443
+ rtp: toHundred,
444
+ tb: toRounded,
445
+ };
446
+
447
+ const keys = Object.keys(data || {}).sort();
448
+
449
+ for (const key of keys) {
450
+ let value = data[key];
451
+
452
+ // ignore invalid values
453
+ if (!isValid(value)) {
454
+ continue;
455
+ }
456
+
457
+ // Version should only be reported if not equal to 1.
458
+ if (key === 'v' && value === 1) {
459
+ continue;
460
+ }
461
+
462
+ // Playback rate should only be sent if not equal to 1.
463
+ if (key == 'pr' && value === 1) {
464
+ continue;
465
+ }
466
+
467
+ // Certain values require special formatting
468
+ const formatter = formatters[key];
469
+ if (formatter) {
470
+ value = formatter(value);
471
+ }
472
+
473
+ // Serialize the key/value pair
474
+ const type = typeof value;
475
+ let result;
476
+
477
+ if (type === 'string' && key !== 'ot' && key !== 'sf' && key !== 'st') {
478
+ result = `${key}=${JSON.stringify(value)}`;
479
+ } else if (type === 'boolean') {
480
+ result = key;
481
+ } else if (type === 'symbol') {
482
+ result = `${key}=${value.description}`;
483
+ } else {
484
+ result = `${key}=${value}`;
485
+ }
486
+
487
+ results.push(result);
488
+ }
489
+
490
+ return results.join(',');
491
+ }
492
+
493
+ /**
494
+ * Convert a CMCD data object to request headers according to the rules
495
+ * defined in the section 2.1 and 3.2 of
496
+ * [CTA-5004](https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf).
497
+ *
498
+ * @param {CmcdData} data The CMCD data object
499
+ * @return {!Object}
500
+ */
501
+ static toHeaders(data) {
502
+ const keys = Object.keys(data);
503
+ const headers = {};
504
+ const headerNames = ['Object', 'Request', 'Session', 'Status'];
505
+ const headerGroups = [{}, {}, {}, {}];
506
+ const headerMap = {
507
+ br: 0, d: 0, ot: 0, tb: 0,
508
+ bl: 1, dl: 1, mtp: 1, nor: 1, nrr: 1, su: 1,
509
+ cid: 2, pr: 2, sf: 2, sid: 2, st: 2, v: 2,
510
+ bs: 3, rtp: 3,
511
+ };
512
+
513
+ for (const key of keys) {
514
+ // Unmapped fields are mapped to the Request header
515
+ const index = (headerMap[key] != null) ? headerMap[key] : 1;
516
+ headerGroups[index][key] = data[key];
517
+ }
518
+
519
+ for (let i = 0; i < headerGroups.length; i++) {
520
+ const value = shaka.util.CmcdManager.serialize(headerGroups[i]);
521
+ if (value) {
522
+ headers[`CMCD-${headerNames[i]}`] = value;
523
+ }
524
+ }
525
+
526
+ return headers;
527
+ }
528
+
529
+ /**
530
+ * Convert a CMCD data object to query args according to the rules
531
+ * defined in the section 2.2 and 3.2 of
532
+ * [CTA-5004](https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf).
533
+ *
534
+ * @param {CmcdData} data The CMCD data object
535
+ * @return {string}
536
+ */
537
+ static toQuery(data) {
538
+ return shaka.util.CmcdManager.serialize(data);
539
+ }
540
+
541
+ /**
542
+ * Append query args to a uri.
543
+ *
544
+ * @param {string} uri
545
+ * @param {string} query
546
+ * @return {string}
547
+ */
548
+ static appendQueryToUri(uri, query) {
549
+ if (!query) {
550
+ return uri;
551
+ }
552
+
553
+ if (uri.includes('offline:')) {
554
+ return uri;
555
+ }
556
+
557
+ const url = new goog.Uri(uri);
558
+ url.getQueryData().set('CMCD', query);
559
+ return url.toString();
560
+ }
561
+ };
562
+
563
+
564
+ /**
565
+ * @typedef {{
566
+ * getBandwidthEstimate: function():number,
567
+ * getBufferedInfo: function():shaka.extern.BufferedInfo,
568
+ * getCurrentTime: function():number,
569
+ * getVariantTracks: function():Array.<shaka.extern.Track>,
570
+ * getPlaybackRate: function():number,
571
+ * isLive: function():boolean
572
+ * }}
573
+ *
574
+ * @property {function():number} getBandwidthEstimate
575
+ * Get the estimated bandwidth in bits per second.
576
+ * @property {function():shaka.extern.BufferedInfo} getBufferedInfo
577
+ * Get information about what the player has buffered.
578
+ * @property {function():number} getCurrentTime
579
+ * Get the current time
580
+ * @property {function():Array.<shaka.extern.Track>} getVariantTracks
581
+ * Get the variant tracks
582
+ * @property {function():number} getPlaybackRate
583
+ * Get the playback rate
584
+ * @property {function():boolean} isLive
585
+ * Get if the player is playing live content.
586
+ */
587
+ shaka.util.CmcdManager.PlayerInterface;
588
+
589
+
590
+ /**
591
+ * @typedef {{
592
+ * type: string,
593
+ * init: boolean,
594
+ * duration: number,
595
+ * mimeType: string,
596
+ * codecs: string,
597
+ * bandwidth: (number|undefined)
598
+ * }}
599
+ *
600
+ * @property {string} type
601
+ * The media type
602
+ * @property {boolean} init
603
+ * Flag indicating whether the segment is an init segment
604
+ * @property {number} duration
605
+ * The duration of the segment in seconds
606
+ * @property {string} mimeType
607
+ * The segment's mime type
608
+ * @property {string} codecs
609
+ * The segment's codecs
610
+ * @property {(number|undefined)} bandwidth
611
+ * The segment's variation bandwidth
612
+ *
613
+ * @export
614
+ */
615
+ shaka.util.CmcdManager.SegmentInfo;
616
+
617
+
618
+ /**
619
+ * @typedef {{
620
+ * format: shaka.util.CmcdManager.StreamingFormat
621
+ * }}
622
+ *
623
+ * @property {shaka.util.CmcdManager.StreamingFormat} format
624
+ * The manifest's stream format
625
+ *
626
+ * @export
627
+ */
628
+ shaka.util.CmcdManager.ManifestInfo;
629
+
630
+
631
+ /**
632
+ * @enum {string}
633
+ */
634
+ shaka.util.CmcdManager.ObjectType = {
635
+ MANIFEST: 'm',
636
+ AUDIO: 'a',
637
+ VIDEO: 'v',
638
+ MUXED: 'av',
639
+ INIT: 'i',
640
+ CAPTION: 'c',
641
+ TIMED_TEXT: 'tt',
642
+ KEY: 'k',
643
+ OTHER: 'o',
644
+ };
645
+
646
+
647
+ /**
648
+ * @enum {string}
649
+ */
650
+ shaka.util.CmcdManager.StreamType = {
651
+ VOD: 'v',
652
+ LIVE: 'l',
653
+ };
654
+
655
+
656
+ /**
657
+ * @enum {string}
658
+ * @export
659
+ */
660
+ shaka.util.CmcdManager.StreamingFormat = {
661
+ DASH: 'd',
662
+ HLS: 'h',
663
+ SMOOTH: 's',
664
+ OTHER: 'o',
665
+ };
666
+
667
+
668
+ /**
669
+ * The CMCD spec version
670
+ * @const {number}
671
+ */
672
+ shaka.util.CmcdManager.Version = 1;
package/lib/util/error.js CHANGED
@@ -986,6 +986,12 @@ shaka.util.Error.Code = {
986
986
  */
987
987
  'DOWNLOAD_SIZE_CALLBACK_ERROR': 9015,
988
988
 
989
+ /**
990
+ * The storage cell does not allow new operations that significantly change
991
+ * existing data.
992
+ */
993
+ 'MODIFY_OPERATION_NOT_SUPPORTED': 9016,
994
+
989
995
  /**
990
996
  * CS IMA SDK, required for ad insertion, has not been included on the page.
991
997
  */