shaka-player 3.1.3 → 3.2.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 (182) hide show
  1. package/AUTHORS +3 -0
  2. package/CHANGELOG.md +175 -3
  3. package/CONTRIBUTORS +3 -0
  4. package/README.md +4 -4
  5. package/build/generateExterns.js +1 -1
  6. package/build/types/polyfill +1 -0
  7. package/demo/common/asset.js +9 -7
  8. package/demo/common/assets.js +11 -2
  9. package/demo/common/message_ids.js +2 -1
  10. package/demo/config.js +5 -2
  11. package/demo/index.html +1 -1
  12. package/demo/locales/en.json +2 -1
  13. package/demo/locales/source.json +8 -4
  14. package/demo/main.js +2 -1
  15. package/dist/controls.css +2 -2
  16. package/dist/controls.css.map +1 -1
  17. package/dist/demo.compiled.debug.js +15 -15
  18. package/dist/demo.compiled.debug.map +2 -2
  19. package/dist/demo.compiled.js +15 -15
  20. package/dist/demo.compiled.map +2 -2
  21. package/dist/deps.js +22 -21
  22. package/dist/locales.js +36 -36
  23. package/dist/receiver.compiled.debug.js +9 -9
  24. package/dist/receiver.compiled.debug.map +2 -2
  25. package/dist/receiver.compiled.js +9 -9
  26. package/dist/receiver.compiled.map +2 -2
  27. package/dist/shaka-player.compiled.d.ts +58 -10
  28. package/dist/shaka-player.compiled.debug.d.ts +58 -10
  29. package/dist/shaka-player.compiled.debug.externs.js +777 -717
  30. package/dist/shaka-player.compiled.debug.js +639 -607
  31. package/dist/shaka-player.compiled.debug.map +4 -4
  32. package/dist/shaka-player.compiled.externs.js +777 -717
  33. package/dist/shaka-player.compiled.js +826 -819
  34. package/dist/shaka-player.compiled.map +4 -4
  35. package/dist/shaka-player.ui.d.ts +58 -10
  36. package/dist/shaka-player.ui.debug.d.ts +58 -10
  37. package/dist/shaka-player.ui.debug.externs.js +529 -469
  38. package/dist/shaka-player.ui.debug.js +676 -645
  39. package/dist/shaka-player.ui.debug.map +4 -4
  40. package/dist/shaka-player.ui.externs.js +529 -469
  41. package/dist/shaka-player.ui.js +963 -955
  42. package/dist/shaka-player.ui.map +4 -4
  43. package/docs/tutorials/fairplay.md +1 -1
  44. package/docs/tutorials/ui.md +5 -5
  45. package/externs/aria.js +6 -0
  46. package/externs/shaka/ads.js +24 -1
  47. package/externs/shaka/manifest.js +6 -1
  48. package/externs/shaka/offline.js +5 -0
  49. package/externs/shaka/player.js +53 -9
  50. package/externs/shaka/text.js +10 -10
  51. package/externs/texttrack.js +3 -0
  52. package/karma.conf.js +1 -0
  53. package/lib/ads/ad_manager.js +16 -15
  54. package/lib/ads/client_side_ad_manager.js +52 -88
  55. package/lib/ads/server_side_ad_manager.js +26 -12
  56. package/lib/cast/cast_proxy.js +11 -2
  57. package/lib/cast/cast_receiver.js +2 -1
  58. package/lib/cast/cast_sender.js +2 -2
  59. package/lib/cast/cast_utils.js +6 -1
  60. package/lib/cea/cea608_data_channel.js +56 -56
  61. package/lib/cea/cea608_memory.js +33 -33
  62. package/lib/cea/cea708_service.js +43 -43
  63. package/lib/cea/cea708_window.js +38 -38
  64. package/lib/cea/cea_decoder.js +13 -14
  65. package/lib/cea/cea_utils.js +23 -23
  66. package/lib/cea/dtvcc_packet_builder.js +7 -7
  67. package/lib/cea/i_caption_decoder.js +2 -3
  68. package/lib/cea/i_cea_parser.js +2 -2
  69. package/lib/cea/mp4_cea_parser.js +6 -6
  70. package/lib/dash/dash_parser.js +98 -2
  71. package/lib/dash/segment_list.js +1 -1
  72. package/lib/dash/segment_template.js +1 -1
  73. package/lib/hls/hls_parser.js +159 -29
  74. package/lib/media/closed_caption_parser.js +1 -2
  75. package/lib/media/drm_engine.js +169 -192
  76. package/lib/media/gap_jumping_controller.js +4 -2
  77. package/lib/media/media_source_engine.js +1 -2
  78. package/lib/media/segment_index.js +3 -1
  79. package/lib/media/segment_reference.js +19 -2
  80. package/lib/media/streaming_engine.js +8 -7
  81. package/lib/net/networking_engine.js +3 -1
  82. package/lib/offline/download_manager.js +1 -2
  83. package/lib/offline/indexeddb/v1_storage_cell.js +1 -0
  84. package/lib/offline/indexeddb/v2_storage_cell.js +1 -0
  85. package/lib/offline/manifest_converter.js +3 -1
  86. package/lib/offline/storage.js +12 -14
  87. package/lib/player.js +437 -167
  88. package/lib/polyfill/aria.js +82 -0
  89. package/lib/polyfill/media_capabilities.js +7 -1
  90. package/lib/polyfill/mediasource.js +6 -0
  91. package/lib/polyfill/orientation.js +1 -1
  92. package/lib/polyfill/patchedmediakeys_apple.js +4 -4
  93. package/lib/polyfill/patchedmediakeys_ms.js +4 -4
  94. package/lib/polyfill/patchedmediakeys_webkit.js +4 -4
  95. package/lib/routing/walker.js +2 -3
  96. package/lib/text/mp4_ttml_parser.js +3 -0
  97. package/lib/text/srt_text_parser.js +4 -4
  98. package/lib/text/text_engine.js +16 -4
  99. package/lib/text/ttml_text_parser.js +5 -0
  100. package/lib/text/ui_text_displayer.js +85 -37
  101. package/lib/text/vtt_text_parser.js +2 -2
  102. package/lib/text/web_vtt_generator.js +55 -12
  103. package/lib/util/error.js +6 -0
  104. package/lib/util/fake_event.js +53 -7
  105. package/lib/util/manifest_parser_utils.js +1 -0
  106. package/lib/util/mp4_box_parsers.js +20 -20
  107. package/lib/util/multi_map.js +8 -0
  108. package/lib/util/periods.js +4 -1
  109. package/lib/util/platform.js +13 -1
  110. package/lib/util/player_configuration.js +13 -1
  111. package/lib/util/stream_utils.js +153 -85
  112. package/lib/util/xml_utils.js +31 -12
  113. package/package.json +1 -1
  114. package/roadmap.md +24 -22
  115. package/shaka-player.uncompiled.js +1 -0
  116. package/test/cast/cast_proxy_unit.js +12 -6
  117. package/test/cast/cast_utils_unit.js +2 -1
  118. package/test/cea/cea608_memory_unit.js +1 -1
  119. package/test/cea/cea708_service_unit.js +8 -8
  120. package/test/cea/cea708_window_unit.js +6 -6
  121. package/test/cea/cea_decoder_unit.js +1 -1
  122. package/test/cea/dtvcc_packet_builder_unit.js +2 -2
  123. package/test/dash/dash_parser_manifest_unit.js +230 -2
  124. package/test/hls/hls_parser_unit.js +255 -1
  125. package/test/media/closed_caption_parser_unit.js +6 -1
  126. package/test/media/drm_engine_integration.js +86 -12
  127. package/test/media/drm_engine_unit.js +353 -450
  128. package/test/offline/manifest_convert_unit.js +2 -0
  129. package/test/offline/storage_integration.js +9 -19
  130. package/test/player_integration.js +90 -0
  131. package/test/player_src_equals_integration.js +52 -0
  132. package/test/player_unit.js +11 -33
  133. package/test/test/assets/3675/chunk-stream0-00001.m4s +0 -0
  134. package/test/test/assets/3675/chunk-stream0-00002.m4s +0 -0
  135. package/test/test/assets/3675/chunk-stream0-00003.m4s +0 -0
  136. package/test/test/assets/3675/chunk-stream0-00004.m4s +0 -0
  137. package/test/test/assets/3675/chunk-stream0-00005.m4s +0 -0
  138. package/test/test/assets/3675/chunk-stream0-00006.m4s +0 -0
  139. package/test/test/assets/3675/chunk-stream0-00007.m4s +0 -0
  140. package/test/test/assets/3675/dash_0.mpd +30 -0
  141. package/test/test/assets/3675/dash_1.mpd +30 -0
  142. package/test/test/assets/3675/dash_2.mpd +30 -0
  143. package/test/test/assets/3675/dash_3.mpd +30 -0
  144. package/test/test/assets/3675/dash_4.mpd +30 -0
  145. package/test/test/assets/3675/dash_5.mpd +26 -0
  146. package/test/test/assets/3675/init-stream0.m4s +0 -0
  147. package/test/test/assets/chapters.srt +11 -0
  148. package/test/test/assets/chapters.vtt +10 -0
  149. package/test/test/util/cea_utils.js +12 -12
  150. package/test/test/util/fake_ad_manager.js +8 -1
  151. package/test/test/util/manifest_generator.js +2 -0
  152. package/test/test/util/manifest_parser_util.js +3 -1
  153. package/test/text/ttml_text_parser_unit.js +9 -0
  154. package/test/text/ui_text_displayer_unit.js +29 -17
  155. package/test/text/web_vtt_generator_unit.js +81 -7
  156. package/test/ui/localization_unit.js +15 -14
  157. package/test/ui/text_displayer_layout_unit.js +13 -1
  158. package/test/util/stream_utils_unit.js +153 -10
  159. package/ui/airplay_button.js +1 -1
  160. package/ui/audio_language_selection.js +2 -5
  161. package/ui/cast_button.js +6 -9
  162. package/ui/constants.js +0 -3
  163. package/ui/controls.js +4 -6
  164. package/ui/fast_forward_button.js +2 -3
  165. package/ui/fullscreen_button.js +1 -3
  166. package/ui/language_utils.js +1 -1
  167. package/ui/less/containers.less +3 -7
  168. package/ui/locales/ru.json +6 -6
  169. package/ui/localization.js +28 -51
  170. package/ui/loop_button.js +1 -3
  171. package/ui/mute_button.js +1 -3
  172. package/ui/overflow_menu.js +2 -3
  173. package/ui/pip_button.js +7 -10
  174. package/ui/playback_rate_selection.js +3 -6
  175. package/ui/presentation_time.js +1 -3
  176. package/ui/resolution_selection.js +4 -7
  177. package/ui/rewind_button.js +2 -3
  178. package/ui/seek_bar.js +2 -4
  179. package/ui/spacer.js +1 -1
  180. package/ui/text_selection.js +8 -11
  181. package/ui/ui_utils.js +1 -1
  182. package/ui/volume_bar.js +1 -3
@@ -6,6 +6,7 @@
6
6
 
7
7
  goog.provide('shaka.util.XmlUtils');
8
8
 
9
+ goog.require('goog.asserts');
9
10
  goog.require('shaka.log');
10
11
  goog.require('shaka.util.StringUtils');
11
12
 
@@ -312,29 +313,46 @@ shaka.util.XmlUtils = class {
312
313
 
313
314
 
314
315
  /**
315
- * Parse a string and return the resulting root element if
316
- * it was valid XML.
316
+ * Parse a string and return the resulting root element if it was valid XML.
317
+ *
317
318
  * @param {string} xmlString
318
319
  * @param {string} expectedRootElemName
319
320
  * @return {Element}
320
321
  */
321
322
  static parseXmlString(xmlString, expectedRootElemName) {
322
323
  const parser = new DOMParser();
323
- let rootElem = null;
324
324
  let xml = null;
325
325
  try {
326
326
  xml = parser.parseFromString(xmlString, 'text/xml');
327
- } catch (exception) {}
328
- if (xml) {
329
- // The top-level element in the loaded xml should have the
330
- // same type as the element linking.
331
- if (xml.documentElement.tagName == expectedRootElemName) {
332
- rootElem = xml.documentElement;
333
- }
327
+ } catch (exception) {
328
+ shaka.log.error('XML parsing exception:', exception);
329
+ return null;
330
+ }
331
+
332
+ // According to MDN, parseFromString never returns null.
333
+ goog.asserts.assert(xml, 'Parsed XML document cannot be null!');
334
+
335
+ // Check for empty documents.
336
+ const rootElem = xml.documentElement;
337
+ if (!rootElem) {
338
+ shaka.log.error('XML document was empty!');
339
+ return null;
340
+ }
341
+
342
+ // Check for parser errors.
343
+ const parserErrorElements = rootElem.getElementsByTagName('parsererror');
344
+ if (parserErrorElements.length) {
345
+ shaka.log.error('XML parser error found:', parserErrorElements[0]);
346
+ return null;
334
347
  }
335
- if (rootElem && rootElem.getElementsByTagName('parsererror').length > 0) {
348
+
349
+ // The top-level element in the loaded XML should have the name we expect.
350
+ if (xml.documentElement.tagName != expectedRootElemName) {
351
+ shaka.log.error(
352
+ `XML tag name does not match expected "${expectedRootElemName}":`,
353
+ xml.documentElement.tagName);
336
354
  return null;
337
- } // It had a parser error in it.
355
+ }
338
356
 
339
357
  return rootElem;
340
358
  }
@@ -352,6 +370,7 @@ shaka.util.XmlUtils = class {
352
370
  const string = shaka.util.StringUtils.fromUTF8(data);
353
371
  return shaka.util.XmlUtils.parseXmlString(string, expectedRootElemName);
354
372
  } catch (exception) {
373
+ shaka.log.error('parseXmlString threw!', exception);
355
374
  return null;
356
375
  }
357
376
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shaka-player",
3
3
  "description": "DASH/EME video player library",
4
- "version": "3.1.3",
4
+ "version": "3.2.2",
5
5
  "homepage": "https://github.com/google/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
package/roadmap.md CHANGED
@@ -8,49 +8,51 @@ CHANGELOG.md.
8
8
  The goals of future milestones are fluid until we begin that development cycle,
9
9
  so the exact milestone for future features is not pre-determined.
10
10
 
11
- Candidate features for future release cycles
12
- - HLS performance improvements
13
- https://github.com/google/shaka-player/issues/3363
14
- - HLS interstitial support
15
- https://github.com/google/shaka-player/issues/3364
16
- - Integration with Google Video ML/Cloud AI
11
+ Priority features up next:
17
12
  - Support containerless formats
18
13
  https://github.com/google/shaka-player/issues/2337
14
+ - Preload API
15
+ https://github.com/google/shaka-player/issues/880
16
+ - Codec-switching and order preference
17
+ https://github.com/google/shaka-player/issues/1528
18
+ https://github.com/google/shaka-player/issues/2179
19
+
20
+ Candidate features for future release cycles:
21
+ - HLS interstitial support
22
+ https://github.com/google/shaka-player/issues/3364
19
23
  - Support ID3 tags in mp4
20
24
  https://github.com/google/shaka-player/issues/3351
21
25
  - MSE support for FairPlay
22
26
  https://github.com/google/shaka-player/issues/3346
23
- - Codec-switching and order preference
24
- https://github.com/google/shaka-player/issues/1528
25
- https://github.com/google/shaka-player/issues/2179
26
- - Configurable key-system priority
27
- https://github.com/google/shaka-player/issues/3002
28
- - Preload API
29
- https://github.com/google/shaka-player/issues/880
30
- - Thumbnail tracks
31
- DASH: https://github.com/google/shaka-player/issues/559
32
- HLS: https://github.com/google/shaka-player/issues/2429
33
27
  - Smaller, more modular binary
34
28
  - Official TypeScript defs, generated from source
35
29
  https://github.com/google/shaka-player/issues/1030
30
+ - Shaka Analytics Component and an integration with BigQuery Analytics
31
+ https://github.com/google/shaka-player/issues/3359
32
+ - Integration with Google Video ML/Cloud AI
36
33
 
37
- v3.3 - 2021 Q3
34
+ v3.3 - 2021 Q3 or Q4
38
35
  - Background fetch for offline storage
39
36
  https://github.com/google/shaka-player/issues/879
40
- - Shaka Analytics Component and an integration with BigQuery Analytics
41
- https://github.com/google/shaka-player/issues/3359
37
+ - HLS performance improvements
38
+ https://github.com/google/shaka-player/issues/3363
42
39
 
43
- v3.2 - 2021 Q2
40
+ =====
41
+
42
+ v3.2 - 2021 Q3
44
43
  - MediaCapabilities to make performance-based decisions
45
44
  https://github.com/google/shaka-player/issues/1391
45
+ - Configurable key-system priority
46
+ https://github.com/google/shaka-player/issues/3002
46
47
 
47
48
  v3.1 - 2021 Q2
48
49
  - Low-latency live (LL-HLS, LL-DASH)
49
50
  https://github.com/google/shaka-player/issues/1525
50
51
  - Own CEA parser, use mux.js only for TS
51
52
  https://github.com/google/shaka-player/issues/2648
52
-
53
- =====
53
+ - Thumbnail tracks
54
+ DASH: https://github.com/google/shaka-player/issues/559
55
+ HLS: https://github.com/google/shaka-player/issues/2429
54
56
 
55
57
  v3.0 - 2020 Q2
56
58
  - Code health improvements
@@ -32,6 +32,7 @@ goog.require('shaka.offline.OfflineManifestParser');
32
32
  goog.require('shaka.offline.OfflineScheme');
33
33
  goog.require('shaka.offline.Storage');
34
34
  goog.require('shaka.offline.indexeddb.StorageMechanism');
35
+ goog.require('shaka.polyfill.Aria');
35
36
  goog.require('shaka.polyfill.EncryptionScheme');
36
37
  goog.require('shaka.polyfill.Fullscreen');
37
38
  goog.require('shaka.polyfill.MathRound');
@@ -297,7 +297,8 @@ describe('CastProxy', () => {
297
297
  'timeupdate', Util.spyFunc(proxyListener));
298
298
 
299
299
  expect(proxyListener).not.toHaveBeenCalled();
300
- const fakeEvent = new FakeEvent('timeupdate', {detail: 8675309});
300
+ const fakeEvent = new FakeEvent(
301
+ 'timeupdate', (new Map()).set('detail', 8675309));
301
302
  mockVideo.on['timeupdate'](fakeEvent);
302
303
  expect(proxyListener).toHaveBeenCalledWith(jasmine.objectContaining({
303
304
  type: 'timeupdate',
@@ -315,7 +316,8 @@ describe('CastProxy', () => {
315
316
  mockSender.hasRemoteProperties.and.returnValue(true);
316
317
 
317
318
  expect(proxyListener).not.toHaveBeenCalled();
318
- const fakeEvent = new FakeEvent('timeupdate', {detail: 8675309});
319
+ const fakeEvent = new FakeEvent(
320
+ 'timeupdate', (new Map()).set('detail', 8675309));
319
321
  mockVideo.on['timeupdate'](fakeEvent);
320
322
  expect(proxyListener).not.toHaveBeenCalled();
321
323
  });
@@ -332,7 +334,8 @@ describe('CastProxy', () => {
332
334
  mockSender.hasRemoteProperties.and.returnValue(true);
333
335
 
334
336
  expect(proxyListener).not.toHaveBeenCalled();
335
- const fakeEvent = new FakeEvent('timeupdate', {detail: 8675309});
337
+ const fakeEvent = new FakeEvent(
338
+ 'timeupdate', (new Map()).set('detail', 8675309));
336
339
  mockSender.onRemoteEvent('video', fakeEvent);
337
340
  expect(proxyListener).toHaveBeenCalledWith(jasmine.objectContaining({
338
341
  type: 'timeupdate',
@@ -446,7 +449,8 @@ describe('CastProxy', () => {
446
449
  'buffering', Util.spyFunc(proxyListener));
447
450
 
448
451
  expect(proxyListener).not.toHaveBeenCalled();
449
- const fakeEvent = new FakeEvent('buffering', {detail: 8675309});
452
+ const fakeEvent = new FakeEvent(
453
+ 'buffering', (new Map()).set('detail', 8675309));
450
454
  mockPlayer.listeners['buffering'](fakeEvent);
451
455
  expect(proxyListener).toHaveBeenCalledWith(jasmine.objectContaining({
452
456
  type: 'buffering',
@@ -464,7 +468,8 @@ describe('CastProxy', () => {
464
468
  mockSender.hasRemoteProperties.and.returnValue(true);
465
469
 
466
470
  expect(proxyListener).not.toHaveBeenCalled();
467
- const fakeEvent = new FakeEvent('buffering', {detail: 8675309});
471
+ const fakeEvent = new FakeEvent(
472
+ 'buffering', (new Map()).set('detail', 8675309));
468
473
  mockPlayer.listeners['buffering'](fakeEvent);
469
474
  expect(proxyListener).not.toHaveBeenCalled();
470
475
  });
@@ -481,7 +486,8 @@ describe('CastProxy', () => {
481
486
  mockSender.hasRemoteProperties.and.returnValue(true);
482
487
 
483
488
  expect(proxyListener).not.toHaveBeenCalled();
484
- const fakeEvent = new FakeEvent('buffering', {detail: 8675309});
489
+ const fakeEvent = new FakeEvent(
490
+ 'buffering', (new Map()).set('detail', 8675309));
485
491
  mockSender.onRemoteEvent('player', fakeEvent);
486
492
  expect(proxyListener).toHaveBeenCalledWith(jasmine.objectContaining({
487
493
  type: 'buffering',
@@ -27,6 +27,7 @@ describe('CastUtils', () => {
27
27
  'getAdManager', // Handled specially
28
28
  'getSharedConfiguration', // Handled specially
29
29
  'getNetworkingEngine', // Handled specially
30
+ 'getDrmEngine', // Handled specially
30
31
  'getMediaElement', // Handled specially
31
32
  'setMaxHardwareResolution',
32
33
  'destroy', // Should use CastProxy.destroy instead
@@ -122,7 +123,7 @@ describe('CastUtils', () => {
122
123
  expect(typeof deserialized).toBe('object');
123
124
 
124
125
  // The object can be used to construct a FakeEvent.
125
- const fakeEvent = new FakeEvent(deserialized['type'], deserialized);
126
+ const fakeEvent = FakeEvent.fromRealEvent(deserialized);
126
127
 
127
128
  // The fake event has the same type and properties as the original.
128
129
  const asObj = /** @type {!Object} */ (fakeEvent);
@@ -17,7 +17,7 @@ describe('Cea608Memory', () => {
17
17
  /** @type {!shaka.cea.Cea608Memory} */
18
18
  let memory;
19
19
 
20
- /** @type {!string} */
20
+ /** @type {string} */
21
21
  const stream = 'CC1';
22
22
 
23
23
  beforeEach(() => {
@@ -19,39 +19,39 @@ describe('Cea708Service', () => {
19
19
 
20
20
  /**
21
21
  * Hide window (2 bytes), with a bitmap provided to indicate all windows.
22
- * @type {!Array<!number>}
22
+ * @type {!Array<number>}
23
23
  */
24
24
  const hideWindow = [0x8a, 0xff];
25
25
 
26
26
  /**
27
27
  * Define window (7 bytes), defines window #0 to be a visible window
28
28
  * with 32 rows and 32 columns. (We specify 31 for each since decoder adds 1).
29
- * @type {!Array<!number>}
29
+ * @type {!Array<number>}
30
30
  */
31
31
  const defineWindow = [
32
32
  0x98, 0x38, 0x00, 0x00, 0x1f, 0x1f, 0x00,
33
33
  ];
34
34
 
35
- /** @type {!number} */
35
+ /** @type {number} */
36
36
  const startTime = 1;
37
37
 
38
- /** @type {!number} */
38
+ /** @type {number} */
39
39
  const endTime = 2;
40
40
 
41
41
  /**
42
42
  * We arbitrarily pick service 1 for all of these tests.
43
- * @type {!number}
43
+ * @type {number}
44
44
  */
45
45
  const serviceNumber = 1;
46
46
 
47
- /** @type {!string} */
47
+ /** @type {string} */
48
48
  const stream = `svc${serviceNumber}`;
49
49
 
50
50
  /**
51
51
  * Takes in a array of bytes and a presentation timestamp (in seconds),
52
52
  * and converts it into a CEA-708 DTVCC Packet.
53
- * @param {!Array<!number>} bytes
54
- * @param {!number} pts
53
+ * @param {!Array<number>} bytes
54
+ * @param {number} pts
55
55
  */
56
56
  const createCea708PacketFromBytes = (bytes, pts) => {
57
57
  const cea708Bytes = bytes.map((code, i) => {
@@ -15,22 +15,22 @@ describe('Cea708Window', () => {
15
15
  /** @type {!shaka.cea.Cea708Window} */
16
16
  let window;
17
17
 
18
- /** @type {!number} */
18
+ /** @type {number} */
19
19
  const serviceNumber = 1; // We will arbitrarily pick service 1 for all tests.
20
20
 
21
- /** @type {!string} */
21
+ /** @type {string} */
22
22
  const stream = `svc${serviceNumber}`;
23
23
 
24
- /** @type {!number} */
24
+ /** @type {number} */
25
25
  const rowCount = 10;
26
26
 
27
- /** @type {!number} */
27
+ /** @type {number} */
28
28
  const colCount = 32;
29
29
 
30
- /** @type {!number} */
30
+ /** @type {number} */
31
31
  const startTime = 1;
32
32
 
33
- /** @type {!number} */
33
+ /** @type {number} */
34
34
  const endTime = 2;
35
35
 
36
36
  beforeEach(() => {
@@ -13,7 +13,7 @@ goog.require('shaka.text.Cue');
13
13
  describe('CeaDecoder', () => {
14
14
  const CeaUtils = shaka.test.CeaUtils;
15
15
 
16
- /** @type {!string} */
16
+ /** @type {string} */
17
17
  const DEFAULT_BG_COLOR = shaka.cea.CeaUtils.DEFAULT_BG_COLOR;
18
18
 
19
19
  /**
@@ -11,10 +11,10 @@ describe('DtvccPacketBuilder', () => {
11
11
  /** @type {!shaka.cea.DtvccPacketBuilder} */
12
12
  let dtvccPacketBuilder;
13
13
 
14
- /** @type {!number} */
14
+ /** @type {number} */
15
15
  const DTVCC_PACKET_START = shaka.cea.DtvccPacketBuilder.DTVCC_PACKET_START;
16
16
 
17
- /** @type {!number} */
17
+ /** @type {number} */
18
18
  const DTVCC_PACKET_DATA = shaka.cea.DtvccPacketBuilder.DTVCC_PACKET_DATA;
19
19
 
20
20
  beforeEach(() => {
@@ -36,8 +36,16 @@ describe('DashParser Manifest', () => {
36
36
  /** @type {!ArrayBuffer} */
37
37
  let mp4Index;
38
38
 
39
- /** @type {!string} */
39
+ /** @type {string} */
40
40
  const thumbnailScheme = 'http://dashif.org/guidelines/thumbnail_tile';
41
+ /**
42
+ * CICP scheme. parameter must be one of the following: "ColourPrimaries",
43
+ * "TransferCharacteristics", or "MatrixCoefficients".
44
+ *
45
+ * @param {string} parameter
46
+ * @return {string}
47
+ */
48
+ const cicpScheme = (parameter) => `urn:mpeg:mpegB:cicp:${parameter}`;
41
49
 
42
50
  beforeAll(async () => {
43
51
  mp4Index = await shaka.test.Util.fetch(mp4IndexSegmentUri);
@@ -1790,6 +1798,48 @@ describe('DashParser Manifest', () => {
1790
1798
  expect(manifest.presentationTimeline).toBeTruthy();
1791
1799
  });
1792
1800
 
1801
+ it('Invokes manifestPreprocessor in config', async () => {
1802
+ const manifestText = [
1803
+ '<MPD minBufferTime="PT75S">',
1804
+ ' <Period id="1" duration="PT30S">',
1805
+ ' <AdaptationSet id="2" mimeType="video/mp4">',
1806
+ ' <Representation id="video-sd" width="640" height="480">',
1807
+ ' <BaseURL>v-sd.mp4</BaseURL>',
1808
+ ' <SegmentBase indexRange="100-200" />',
1809
+ ' </Representation>',
1810
+ ' </AdaptationSet>',
1811
+ ' <AdaptationSet id="3" mimeType="audio/mp4">',
1812
+ ' <Representation id="audio-en">',
1813
+ ' <BaseURL>a-en.mp4</BaseURL>',
1814
+ ' <SegmentBase indexRange="100-200" />',
1815
+ ' </Representation>',
1816
+ ' </AdaptationSet>',
1817
+ ' <AdaptationSet mimeType="text/vtt" lang="de">',
1818
+ ' <Representation>',
1819
+ ' <BaseURL>http://example.com/de.vtt</BaseURL>',
1820
+ ' </Representation>',
1821
+ ' </AdaptationSet>',
1822
+ ' </Period>',
1823
+ '</MPD>',
1824
+ ].join('\n');
1825
+
1826
+ fakeNetEngine.setResponseText('dummy://foo', manifestText);
1827
+ const config = shaka.util.PlayerConfiguration.createDefault().manifest;
1828
+ config.dash.manifestPreprocessor = (mpd) => {
1829
+ const selector = 'AdaptationSet[mimeType="text/vtt"';
1830
+ const vttElements = mpd.querySelectorAll(selector);
1831
+ for (const element of vttElements) {
1832
+ element.parentNode.removeChild(element);
1833
+ }
1834
+ };
1835
+ parser.configure(config);
1836
+
1837
+ /** @type {shaka.extern.Manifest} */
1838
+ const manifest = await parser.start('dummy://foo', playerInterface);
1839
+ const stream = manifest.textStreams[0];
1840
+ expect(stream).toBeUndefined();
1841
+ });
1842
+
1793
1843
  it('converts Accessibility element to "kind"', async () => {
1794
1844
  const manifestText = [
1795
1845
  '<MPD minBufferTime="PT75S">',
@@ -1854,7 +1904,8 @@ describe('DashParser Manifest', () => {
1854
1904
  expect(textStream.forced).toBe(true);
1855
1905
  });
1856
1906
 
1857
- it('supports HDR signaling', async () => {
1907
+ it('supports HDR signaling via profiles', async () => {
1908
+ // (DASH-IF IOP v4.3 10.3.3.)
1858
1909
  const hdrProfile =
1859
1910
  'http://dashif.org/guidelines/dash-if-uhd#hevc-hdr-pq10';
1860
1911
  const manifestText = [
@@ -1885,6 +1936,110 @@ describe('DashParser Manifest', () => {
1885
1936
  expect(stream.hdr).toBe('PQ');
1886
1937
  });
1887
1938
 
1939
+ it('supports HDR signaling via SupplementalProperty', async () => {
1940
+ // (DASH-IF IOP v4.3 6.2.5.1.)
1941
+ const hdrScheme = cicpScheme('TransferCharacteristics');
1942
+ const pq = 16;
1943
+ const manifestText = [
1944
+ '<MPD minBufferTime="PT75S">',
1945
+ ' <Period id="1" duration="PT30S">',
1946
+ ' <AdaptationSet id="2" mimeType="video/mp4">',
1947
+ ` <SupplementalProperty schemeIdUri="${hdrScheme}" value="${pq}" />`,
1948
+ ' <Representation codecs="hvc1.2.4.L150.90">',
1949
+ ' <BaseURL>v-sd.mp4</BaseURL>',
1950
+ ' <SegmentBase indexRange="100-200" />',
1951
+ ' </Representation>',
1952
+ ' </AdaptationSet>',
1953
+ ' <AdaptationSet id="3" mimeType="audio/mp4">',
1954
+ ' <Representation id="audio-en">',
1955
+ ' <BaseURL>a-en.mp4</BaseURL>',
1956
+ ' <SegmentBase indexRange="100-200" />',
1957
+ ' </Representation>',
1958
+ ' </AdaptationSet>',
1959
+ ' </Period>',
1960
+ '</MPD>',
1961
+ ].join('\n');
1962
+
1963
+ fakeNetEngine.setResponseText('dummy://foo', manifestText);
1964
+
1965
+ /** @type {shaka.extern.Manifest} */
1966
+ const manifest = await parser.start('dummy://foo', playerInterface);
1967
+ expect(manifest.variants.length).toBe(1);
1968
+ const stream = manifest.variants[0].video;
1969
+ expect(stream.hdr).toBe('PQ');
1970
+ });
1971
+
1972
+ it('supports HDR signaling via EssentialProperty', async () => {
1973
+ // (DASH-IF IOP v4.3 6.2.5.1.)
1974
+ const hdrScheme = cicpScheme('TransferCharacteristics');
1975
+ const hlg = 18;
1976
+ const manifestText = [
1977
+ '<MPD minBufferTime="PT75S">',
1978
+ ' <Period id="1" duration="PT30S">',
1979
+ ' <AdaptationSet id="2" mimeType="video/mp4">',
1980
+ ` <EssentialProperty schemeIdUri="${hdrScheme}" value="${hlg}" />`,
1981
+ ' <Representation codecs="hvc1.2.4.L153.B0">',
1982
+ ' <BaseURL>v-sd.mp4</BaseURL>',
1983
+ ' <SegmentBase indexRange="100-200" />',
1984
+ ' </Representation>',
1985
+ ' </AdaptationSet>',
1986
+ ' <AdaptationSet id="3" mimeType="audio/mp4">',
1987
+ ' <Representation id="audio-en">',
1988
+ ' <BaseURL>a-en.mp4</BaseURL>',
1989
+ ' <SegmentBase indexRange="100-200" />',
1990
+ ' </Representation>',
1991
+ ' </AdaptationSet>',
1992
+ ' </Period>',
1993
+ '</MPD>',
1994
+ ].join('\n');
1995
+
1996
+ fakeNetEngine.setResponseText('dummy://foo', manifestText);
1997
+
1998
+ /** @type {shaka.extern.Manifest} */
1999
+ const manifest = await parser.start('dummy://foo', playerInterface);
2000
+ expect(manifest.variants.length).toBe(1);
2001
+ const stream = manifest.variants[0].video;
2002
+ expect(stream.hdr).toBe('HLG');
2003
+ });
2004
+
2005
+ it('supports SDR signalling via EssentialProperty', async () => {
2006
+ // (DASH-IF IOP v4.3 6.2.5.1.)
2007
+ const scheme = cicpScheme('TransferCharacteristics');
2008
+ const sdrValues = [1, 6, 13, 14, 15];
2009
+ const manifestPromises = [];
2010
+ for (const value of sdrValues) {
2011
+ const manifestText = [
2012
+ '<MPD minBufferTime="PT75S">',
2013
+ ' <Period id="1" duration="PT30S">',
2014
+ ' <AdaptationSet id="2" mimeType="video/mp4">',
2015
+ ` <EssentialProperty schemeIdUri="${scheme}" value="${value}" />`,
2016
+ ' <Representation codecs="avc1.640028">',
2017
+ ' <BaseURL>v-sd.mp4</BaseURL>',
2018
+ ' <SegmentBase indexRange="100-200" />',
2019
+ ' </Representation>',
2020
+ ' </AdaptationSet>',
2021
+ ' <AdaptationSet id="3" mimeType="audio/mp4">',
2022
+ ' <Representation id="audio-en">',
2023
+ ' <BaseURL>a-en.mp4</BaseURL>',
2024
+ ' <SegmentBase indexRange="100-200" />',
2025
+ ' </Representation>',
2026
+ ' </AdaptationSet>',
2027
+ ' </Period>',
2028
+ '</MPD>',
2029
+ ].join('\n');
2030
+
2031
+ fakeNetEngine.setResponseText('dummy://foo', manifestText);
2032
+
2033
+ manifestPromises.push(parser.start('dummy://foo', playerInterface));
2034
+ }
2035
+ const manifests = await Promise.all(manifestPromises);
2036
+ for (const manifest of manifests) {
2037
+ expect(manifest.variants.length).toBe(1);
2038
+ const stream = manifest.variants[0].video;
2039
+ expect(stream.hdr).toBe('SDR');
2040
+ }
2041
+ });
2042
+
1888
2043
  it('Does not error when image adaptation sets are present', async () => {
1889
2044
  const manifestText = [
1890
2045
  '<MPD minBufferTime="PT75S">',
@@ -2108,4 +2263,77 @@ describe('DashParser Manifest', () => {
2108
2263
  expect(uri).not.toContain('/p2/');
2109
2264
  }
2110
2265
  });
2266
+
2267
+ /**
2268
+ * @param {!Array.<number>} periods Start time of multiple periods
2269
+ * @return {string}
2270
+ */
2271
+ function buildManifestWithPeriodStartTime(periods) {
2272
+ const mpdTemplate = [
2273
+ `<MPD type="dynamic"`,
2274
+ 'availabilityStartTime="1970-01-01T00:00:00Z"',
2275
+ 'timeShiftBufferDepth="PT10H">',
2276
+ ' %(periods)s',
2277
+ '</MPD>',
2278
+ ].join('\n');
2279
+ const periodTemplate = (id, period, duration) => {
2280
+ return [
2281
+ ` <Period id="${id}" start="PT${period}S">`,
2282
+ ' <AdaptationSet mimeType="video/mp4" lang="en" group="1">',
2283
+ ' <SegmentTemplate startNumber="1" media="l-$Number$.mp4">',
2284
+ ' <SegmentTimeline>',
2285
+ ` <S t="0" d="${duration}" />`,
2286
+ ' </SegmentTimeline>',
2287
+ ' </SegmentTemplate>',
2288
+ ' <Representation id="1"/>',
2289
+ ' </AdaptationSet>',
2290
+ ' </Period>',
2291
+ ].join('\n');
2292
+ };
2293
+ const periodXmls = periods.map((period, i) => {
2294
+ const duration = i+1 === periods.length ? 10 : periods[i+1] - period;
2295
+ // Period start time as ID here. If we use index then there will be
2296
+ // periods with same period ID and different start time which are invalid.
2297
+ return periodTemplate(period, period, duration);
2298
+ });
2299
+ return sprintf(mpdTemplate, {
2300
+ periods: periodXmls.join('\n'),
2301
+ });
2302
+ }
2303
+
2304
+ // Bug description: Inconsistent period start time in the manifests due
2305
+ // to failover triggered in backend servers
2306
+
2307
+ // When one of the servers is down, the manifest will be served by other
2308
+ // redundant servers. The period start time might become out of sync
2309
+ // during the switch-over/recovery.
2310
+
2311
+ it('skip periods that are earlier than max period start time', async () => {
2312
+ const sources = [
2313
+ buildManifestWithPeriodStartTime([5, 15]),
2314
+ buildManifestWithPeriodStartTime([6, 15]), // simulate out-of-sync of +1s
2315
+ buildManifestWithPeriodStartTime([4, 15]), // simulate out-of-sync of -1s
2316
+ ];
2317
+ const segments = [];
2318
+
2319
+ for (const source of sources) {
2320
+ fakeNetEngine.setResponseText('dummy://foo', source);
2321
+ /** @type {shaka.extern.Manifest} */
2322
+ // eslint-disable-next-line no-await-in-loop
2323
+ const manifest = await parser.start('dummy://foo', playerInterface);
2324
+ const video = manifest.variants[0].video;
2325
+ // eslint-disable-next-line no-await-in-loop
2326
+ await video.createSegmentIndex();
2327
+ goog.asserts.assert(video.segmentIndex, 'Null segmentIndex!');
2328
+ segments.push(Array.from(video.segmentIndex));
2329
+ }
2330
+
2331
+ // Expect identical segments
2332
+ expect(segments[0].length).toBe(2);
2333
+ expect(segments[1].length).toBe(2);
2334
+ expect(segments[0][0].startTime).toBe(5);
2335
+ expect(segments[1][0].startTime).toBe(5);
2336
+ expect(segments[0][1].startTime).toBe(15);
2337
+ expect(segments[1][1].startTime).toBe(15);
2338
+ });
2111
2339
  });