shaka-player 4.3.12 → 4.3.14

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 (140) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  2. package/.github/ISSUE_TEMPLATE/question.md +7 -0
  3. package/.github/workflows/release-please.yaml +2 -27
  4. package/AUTHORS +5 -0
  5. package/CHANGELOG.md +108 -0
  6. package/CODE_OF_CONDUCT.md +1 -1
  7. package/CONTRIBUTORS +6 -0
  8. package/README.md +2 -0
  9. package/demo/common/asset.js +5 -1
  10. package/demo/common/assets.js +16 -2
  11. package/demo/common/message_ids.js +1 -0
  12. package/demo/custom.js +2 -0
  13. package/demo/locales/en.json +1 -0
  14. package/demo/locales/source.json +4 -0
  15. package/demo/main.js +1 -1
  16. package/dist/demo.compiled.debug.js +56 -54
  17. package/dist/demo.compiled.debug.map +3 -3
  18. package/dist/demo.compiled.js +56 -54
  19. package/dist/demo.compiled.map +3 -3
  20. package/dist/deps.js +6 -6
  21. package/dist/locales.js +31 -31
  22. package/dist/receiver.compiled.debug.js +24 -22
  23. package/dist/receiver.compiled.debug.map +3 -3
  24. package/dist/receiver.compiled.js +24 -22
  25. package/dist/receiver.compiled.map +3 -3
  26. package/dist/shaka-player.compiled.d.ts +26 -6
  27. package/dist/shaka-player.compiled.debug.d.ts +26 -6
  28. package/dist/shaka-player.compiled.debug.externs.js +79 -49
  29. package/dist/shaka-player.compiled.debug.js +426 -410
  30. package/dist/shaka-player.compiled.debug.map +4 -4
  31. package/dist/shaka-player.compiled.externs.js +79 -49
  32. package/dist/shaka-player.compiled.js +912 -901
  33. package/dist/shaka-player.compiled.map +4 -4
  34. package/dist/shaka-player.ui.d.ts +38 -7
  35. package/dist/shaka-player.ui.debug.d.ts +38 -7
  36. package/dist/shaka-player.ui.debug.externs.js +80 -49
  37. package/dist/shaka-player.ui.debug.js +460 -442
  38. package/dist/shaka-player.ui.debug.map +4 -4
  39. package/dist/shaka-player.ui.externs.js +80 -49
  40. package/dist/shaka-player.ui.js +1060 -1049
  41. package/dist/shaka-player.ui.map +4 -4
  42. package/docs/tutorials/ad_monetization.md +54 -0
  43. package/docs/tutorials/fairplay.md +2 -2
  44. package/docs/tutorials/index.json +1 -0
  45. package/docs/tutorials/offline.md +1 -1
  46. package/docs/tutorials/plugins.md +4 -0
  47. package/docs/tutorials/text-displayer.md +15 -0
  48. package/docs/tutorials/ui-customization.md +29 -0
  49. package/docs/tutorials/ui.md +4 -1
  50. package/externs/lcevc.js +6 -0
  51. package/externs/shaka/manifest_parser.js +8 -0
  52. package/externs/shaka/player.js +1 -1
  53. package/externs/shaka/text.js +3 -1
  54. package/lib/abr/simple_abr_manager.js +1 -1
  55. package/lib/ads/client_side_ad.js +9 -0
  56. package/lib/ads/client_side_ad_manager.js +7 -2
  57. package/lib/ads/server_side_ad_manager.js +4 -0
  58. package/lib/cast/cast_proxy.js +18 -0
  59. package/lib/cea/cea608_memory.js +1 -1
  60. package/lib/cea/cea_decoder.js +11 -1
  61. package/lib/dash/dash_parser.js +79 -42
  62. package/lib/dash/mpd_utils.js +39 -27
  63. package/lib/hls/hls_parser.js +148 -49
  64. package/lib/hls/hls_utils.js +5 -5
  65. package/lib/media/content_workarounds.js +30 -0
  66. package/lib/media/drm_engine.js +16 -12
  67. package/lib/media/media_source_engine.js +4 -2
  68. package/lib/media/play_rate_controller.js +1 -0
  69. package/lib/media/presentation_timeline.js +11 -0
  70. package/lib/media/quality_observer.js +1 -1
  71. package/lib/media/segment_reference.js +16 -7
  72. package/lib/media/streaming_engine.js +64 -54
  73. package/lib/media/transmuxer.js +1 -1
  74. package/lib/media/video_wrapper.js +4 -3
  75. package/lib/offline/offline_manifest_parser.js +5 -0
  76. package/lib/offline/session_deleter.js +0 -8
  77. package/lib/offline/storage.js +4 -1
  78. package/lib/player.js +79 -18
  79. package/lib/polyfill/media_capabilities.js +23 -3
  80. package/lib/text/mp4_ttml_parser.js +2 -2
  81. package/lib/text/simple_text_displayer.js +7 -6
  82. package/lib/text/srt_text_parser.js +2 -2
  83. package/lib/text/text_engine.js +3 -2
  84. package/lib/text/ttml_text_parser.js +33 -5
  85. package/lib/text/ui_text_displayer.js +81 -15
  86. package/lib/text/vtt_text_parser.js +13 -0
  87. package/lib/util/buffer_utils.js +26 -4
  88. package/lib/util/cmcd_manager.js +16 -1
  89. package/lib/util/fairplay_utils.js +1 -1
  90. package/lib/util/manifest_parser_utils.js +20 -8
  91. package/lib/util/object_utils.js +1 -1
  92. package/lib/util/periods.js +91 -36
  93. package/lib/util/platform.js +39 -2
  94. package/lib/util/player_configuration.js +5 -3
  95. package/lib/util/stream_utils.js +36 -28
  96. package/lib/util/ts_parser.js +3 -3
  97. package/lib/util/xml_utils.js +5 -0
  98. package/package.json +2 -2
  99. package/test/cea/cea_decoder_unit.js +40 -0
  100. package/test/dash/dash_parser_live_unit.js +5 -8
  101. package/test/dash/dash_parser_manifest_unit.js +5 -0
  102. package/test/dash/dash_parser_segment_base_unit.js +5 -0
  103. package/test/dash/dash_parser_segment_list_unit.js +21 -17
  104. package/test/dash/dash_parser_segment_template_unit.js +5 -0
  105. package/test/hls/hls_live_unit.js +33 -0
  106. package/test/hls/hls_parser_unit.js +52 -60
  107. package/test/media/buffering_observer_unit.js +13 -0
  108. package/test/media/content_workarounds_unit.js +1 -1
  109. package/test/media/drm_engine_unit.js +30 -3
  110. package/test/media/media_source_engine_unit.js +1 -1
  111. package/test/media/transmuxer_integration.js +2 -2
  112. package/test/offline/storage_integration.js +3 -0
  113. package/test/player_integration.js +40 -40
  114. package/test/player_unit.js +31 -0
  115. package/test/polyfill/media_capabilities_unit.js +12 -6
  116. package/test/test/util/dash_parser_util.js +16 -7
  117. package/test/test/util/fake_drm_engine.js +7 -0
  118. package/test/test/util/layout_tests.js +2 -1
  119. package/test/test/util/simple_fakes.js +2 -2
  120. package/test/test/util/test_scheme.js +3 -0
  121. package/test/test/util/waiter.js +5 -0
  122. package/test/text/mp4_ttml_parser_unit.js +4 -4
  123. package/test/text/simple_text_displayer_unit.js +1 -1
  124. package/test/text/srt_text_parser_unit.js +1 -1
  125. package/test/text/text_engine_unit.js +6 -0
  126. package/test/text/ttml_text_parser_unit.js +49 -18
  127. package/test/text/ui_text_displayer_unit.js +9 -0
  128. package/test/text/vtt_text_parser_unit.js +2 -17
  129. package/test/util/buffer_utils_unit.js +1 -1
  130. package/test/util/periods_unit.js +113 -0
  131. package/test/util/ts_parser_unit.js +1 -1
  132. package/third_party/closure-uri/uri.js +1 -1
  133. package/ui/externs/ui.js +24 -0
  134. package/ui/language_utils.js +11 -5
  135. package/ui/play_button.js +20 -0
  136. package/ui/playback_rate_selection.js +4 -0
  137. package/ui/presentation_time.js +5 -5
  138. package/ui/small_play_button.js +2 -2
  139. package/ui/volume_bar.js +10 -6
  140. /package/.github/{config.yml → ISSUE_TEMPLATE/config.yml} +0 -0
@@ -199,6 +199,15 @@ shaka.util.Platform = class {
199
199
  !shaka.util.Platform.isEdge();
200
200
  }
201
201
 
202
+ /**
203
+ * Check if the current platform is Firefox.
204
+ *
205
+ * @return {boolean}
206
+ */
207
+ static isFirefox() {
208
+ return shaka.util.Platform.userAgentContains_('Firefox');
209
+ }
210
+
202
211
  /**
203
212
  * Check if the current platform is from Apple.
204
213
  *
@@ -213,10 +222,12 @@ shaka.util.Platform = class {
213
222
  return !!navigator.vendor && navigator.vendor.includes('Apple') &&
214
223
  !shaka.util.Platform.isTizen() &&
215
224
  !shaka.util.Platform.isEOS() &&
225
+ !shaka.util.Platform.isAPL() &&
216
226
  !shaka.util.Platform.isVirginMedia() &&
217
227
  !shaka.util.Platform.isOrange() &&
218
228
  !shaka.util.Platform.isPS4() &&
219
- !shaka.util.Platform.isAmazonFireTV();
229
+ !shaka.util.Platform.isAmazonFireTV() &&
230
+ !shaka.util.Platform.isWPE();
220
231
  }
221
232
 
222
233
  /**
@@ -271,6 +282,14 @@ shaka.util.Platform = class {
271
282
  return shaka.util.Platform.userAgentContains_('AFT');
272
283
  }
273
284
 
285
+ /**
286
+ * Check if the current platform is Comcast X1.
287
+ * @return {boolean}
288
+ */
289
+ static isWPE() {
290
+ return shaka.util.Platform.userAgentContains_('WPE');
291
+ }
292
+
274
293
  /**
275
294
  * Returns a major version number for Safari, or Safari-based iOS browsers.
276
295
  *
@@ -327,6 +346,15 @@ shaka.util.Platform = class {
327
346
  return shaka.util.Platform.userAgentContains_('PC=EOS');
328
347
  }
329
348
 
349
+ /**
350
+ * Check if the current platform is an APL set-top box.
351
+ *
352
+ * @return {boolean}
353
+ */
354
+ static isAPL() {
355
+ return shaka.util.Platform.userAgentContains_('PC=APL');
356
+ }
357
+
330
358
  /**
331
359
  * Guesses if the platform is a mobile one (iOS or Android).
332
360
  *
@@ -368,6 +396,15 @@ shaka.util.Platform = class {
368
396
  return navigator.platform.toLowerCase().includes('mac');
369
397
  }
370
398
 
399
+ /**
400
+ * Return true if the platform is a Android, regardless of the browser.
401
+ *
402
+ * @return {boolean}
403
+ */
404
+ static isAndroid() {
405
+ return shaka.util.Platform.userAgentContains_('Android');
406
+ }
407
+
371
408
  /**
372
409
  * Check if the user agent contains a key. This is the best way we know of
373
410
  * right now to detect platforms. If there is a better way, please send a
@@ -429,7 +466,7 @@ shaka.util.Platform = class {
429
466
  */
430
467
  static requiresEncryptionInfoInAllInitSegments() {
431
468
  const Platform = shaka.util.Platform;
432
- return Platform.isTizen() || Platform.isXboxOne();
469
+ return Platform.isTizen() || Platform.isXboxOne() || Platform.isOrange();
433
470
  }
434
471
 
435
472
  /**
@@ -189,13 +189,15 @@ shaka.util.PlayerConfiguration = class {
189
189
  parsePrftBox: false,
190
190
  };
191
191
 
192
- // WebOS, Tizen, and Chromecast have long hardware pipelines that respond
193
- // slowly to seeking. Therefore we should not seek when we detect a stall
192
+ // WebOS, Tizen, Chromecast and Hisense have long hardware pipelines
193
+ // that respond slowly to seeking.
194
+ // Therefore we should not seek when we detect a stall
194
195
  // on one of these platforms. Instead, default stallSkip to 0 to force the
195
196
  // stall detector to pause and play instead.
196
197
  if (shaka.util.Platform.isWebOS() ||
197
198
  shaka.util.Platform.isTizen() ||
198
- shaka.util.Platform.isChromecast()) {
199
+ shaka.util.Platform.isChromecast() ||
200
+ shaka.util.Platform.isHisense()) {
199
201
  streaming.stallSkip = 0;
200
202
  }
201
203
 
@@ -434,19 +434,36 @@ shaka.util.StreamUtils = class {
434
434
 
435
435
  await shaka.util.StreamUtils.getDecodingInfosForVariants(
436
436
  manifest.variants, usePersistentLicenses, /* srcEquals= */ false);
437
+
438
+ const ContentType = shaka.util.ManifestParserUtils.ContentType;
439
+ const Capabilities = shaka.media.Capabilities;
440
+ const ManifestParserUtils = shaka.util.ManifestParserUtils;
441
+ const MimeUtils = shaka.util.MimeUtils;
442
+ const StreamUtils = shaka.util.StreamUtils;
443
+ const isXboxOne = shaka.util.Platform.isXboxOne();
444
+
445
+ const isFirefoxAndroid = shaka.util.Platform.isFirefox() &&
446
+ shaka.util.Platform.isAndroid();
447
+
437
448
  manifest.variants = manifest.variants.filter((variant) => {
438
449
  // See: https://github.com/shaka-project/shaka-player/issues/3860
439
450
  const video = variant.video;
451
+ const videoWidth = (video && video.width) || 0;
452
+ const videoHeight = (video && video.height) || 0;
440
453
 
441
- const ContentType = shaka.util.ManifestParserUtils.ContentType;
442
- const Capabilities = shaka.media.Capabilities;
443
- const ManifestParserUtils = shaka.util.ManifestParserUtils;
444
- const MimeUtils = shaka.util.MimeUtils;
445
- const StreamUtils = shaka.util.StreamUtils;
454
+ // See: https://github.com/shaka-project/shaka-player/issues/3380
455
+ // Note: it makes sense to drop early
456
+ if (isXboxOne && video &&
457
+ (videoWidth > 1920 || videoHeight > 1080) &&
458
+ (video.codecs.includes('avc1.') || video.codecs.includes('avc3.'))
459
+ ) {
460
+ shaka.log.debug('Dropping variant - not compatible with platform',
461
+ StreamUtils.getVariantSummaryString_(variant));
462
+ return false;
463
+ }
446
464
 
447
465
  if (video) {
448
466
  let videoCodecs = StreamUtils.getCorrectVideoCodecs_(video.codecs);
449
-
450
467
  // For multiplexed streams. Here we must check the audio of the
451
468
  // stream to see if it is compatible.
452
469
  if (video.codecs.includes(',')) {
@@ -455,7 +472,6 @@ shaka.util.StreamUtils = class {
455
472
  videoCodecs = ManifestParserUtils.guessCodecs(
456
473
  ContentType.VIDEO, allCodecs);
457
474
  videoCodecs = StreamUtils.getCorrectVideoCodecs_(videoCodecs);
458
-
459
475
  let audioCodecs = ManifestParserUtils.guessCodecs(
460
476
  ContentType.AUDIO, allCodecs);
461
477
  audioCodecs = StreamUtils.getCorrectAudioCodecs_(audioCodecs);
@@ -484,6 +500,17 @@ shaka.util.StreamUtils = class {
484
500
 
485
501
  const audio = variant.audio;
486
502
 
503
+ // See: https://github.com/shaka-project/shaka-player/issues/6111
504
+ // It seems that Firefox Android reports that it supports
505
+ // Opus + Widevine, but it is not actually supported.
506
+ // It makes sense to drop early.
507
+ if (isFirefoxAndroid && audio && audio.encrypted &&
508
+ audio.codecs.toLowerCase().includes('opus')) {
509
+ shaka.log.debug('Dropping variant - not compatible with platform',
510
+ StreamUtils.getVariantSummaryString_(variant));
511
+ return false;
512
+ }
513
+
487
514
  if (audio) {
488
515
  const codecs = StreamUtils.getCorrectAudioCodecs_(audio.codecs);
489
516
  const fullType = MimeUtils.getFullOrConvertedType(
@@ -497,17 +524,6 @@ shaka.util.StreamUtils = class {
497
524
  audio.codecs = codecs;
498
525
  }
499
526
 
500
- // See: https://github.com/shaka-project/shaka-player/issues/3380
501
- if (shaka.util.Platform.isXboxOne() && video &&
502
- ((video.width && video.width > 1920) ||
503
- (video.height && video.height > 1080)) &&
504
- (video.codecs.includes('avc1.') ||
505
- video.codecs.includes('avc3.'))) {
506
- shaka.log.debug('Dropping variant - not compatible with platform',
507
- StreamUtils.getVariantSummaryString_(variant));
508
- return false;
509
- }
510
-
511
527
  const supported = variant.decodingInfos.some((decodingInfo) => {
512
528
  return decodingInfo.supported;
513
529
  });
@@ -790,7 +806,7 @@ shaka.util.StreamUtils = class {
790
806
  // currently don't support 'fLaC', while 'flac' is supported by most
791
807
  // major browsers.
792
808
  // See https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
793
- if (codecs === 'fLaC') {
809
+ if (codecs === 'fLaC' && !shaka.util.Platform.isSafari()) {
794
810
  return 'flac';
795
811
  }
796
812
 
@@ -799,15 +815,7 @@ shaka.util.StreamUtils = class {
799
815
  return 'opus';
800
816
  }
801
817
 
802
- // Some Tizen devices seem to misreport AC-3 support, but correctly
803
- // report EC-3 support. So query EC-3 as a fallback for AC-3.
804
- // See https://github.com/shaka-project/shaka-player/issues/2989 for
805
- // details.
806
- if (shaka.util.Platform.isTizen()) {
807
- return codecs.toLowerCase() == 'ac-3' ? 'ec-3' : codecs;
808
- } else {
809
- return codecs;
810
- }
818
+ return codecs;
811
819
  }
812
820
 
813
821
 
@@ -122,15 +122,15 @@ shaka.util.TsParser = class {
122
122
  // for example
123
123
  // NOTE this is only the PID of the track as found in TS,
124
124
  // but we are not using this for MP4 track IDs.
125
- if (this.videoPid_ == null) {
125
+ if (parsedPIDs.video != -1) {
126
126
  this.videoPid_ = parsedPIDs.video;
127
127
  this.videoCodec_ = parsedPIDs.videoCodec;
128
128
  }
129
- if (this.audioPid_ == null) {
129
+ if (parsedPIDs.audio != -1) {
130
130
  this.audioPid_ = parsedPIDs.audio;
131
131
  this.audioCodec_ = parsedPIDs.audioCodec;
132
132
  }
133
- if (this.id3Pid_ == null) {
133
+ if (parsedPIDs.id3 != -1) {
134
134
  this.id3Pid_ = parsedPIDs.id3;
135
135
  }
136
136
 
@@ -384,6 +384,11 @@ shaka.util.XmlUtils = class {
384
384
  return null;
385
385
  }
386
386
 
387
+ // Cobalt browser doesn't support document.createNodeIterator.
388
+ if (!('createNodeIterator' in document)) {
389
+ return rootElem;
390
+ }
391
+
387
392
  // SECURITY: Verify that the document does not contain elements from the
388
393
  // HTML or SVG namespaces, which could trigger script execution and XSS.
389
394
  const iterator = document.createNodeIterator(
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": "4.3.12",
4
+ "version": "4.3.14",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -42,7 +42,7 @@
42
42
  "karma-coverage": "^2.2.0",
43
43
  "karma-jasmine": "^4.0.1",
44
44
  "karma-jasmine-ajax": "^0.1.13",
45
- "karma-local-wd-launcher": "^1.6.6",
45
+ "karma-local-wd-launcher": "^1.6.7",
46
46
  "karma-opera-launcher": "^1.0.0",
47
47
  "karma-sourcemap-loader": "^0.3.8",
48
48
  "karma-spec-reporter": "^0.0.34",
@@ -44,6 +44,39 @@ describe('CeaDecoder', () => {
44
44
  decoder.clear();
45
45
  });
46
46
 
47
+ it('painton captions on CC4', () => {
48
+ const controlCount = 0x03;
49
+ const captionData = 0xc0 | controlCount;
50
+ const paintonCaptionCC4Packet = new Uint8Array([
51
+ ...atscCaptionInitBytes, captionData, /* padding= */ 0xff,
52
+ 0xfd, 0x9d, 0x29, // Paint-on mode (RDC control code).
53
+ 0xfd, 0xf4, 0xe5, // t, e
54
+ 0xfd, 0x73, 0xf4, // s, t
55
+ ]);
56
+
57
+ const startTimeCaption1 = 1;
58
+ const startTimeCaption2 = 2;
59
+ const expectedText = 'test';
60
+
61
+ const topLevelCue = new shaka.text.Cue(startTimeCaption1,
62
+ startTimeCaption2, '');
63
+ topLevelCue.nestedCues = [
64
+ CeaUtils.createDefaultCue(
65
+ startTimeCaption1, startTimeCaption2, expectedText),
66
+ ];
67
+
68
+ const expectedCaptions = [{
69
+ stream: 'CC4',
70
+ cue: topLevelCue,
71
+ }];
72
+
73
+ decoder.extract(paintonCaptionCC4Packet, startTimeCaption1);
74
+ decoder.extract(eraseDisplayedMemory, startTimeCaption2);
75
+ const captions = decoder.decode();
76
+
77
+ expect(captions).toEqual(expectedCaptions);
78
+ });
79
+
47
80
  it('green and underlined popon caption data on CC3', () => {
48
81
  const controlCount = 0x08;
49
82
  const captionData = 0xc0 | controlCount;
@@ -486,6 +519,13 @@ describe('CeaDecoder', () => {
486
519
 
487
520
  expect(decoder.reset).toHaveBeenCalledTimes(1);
488
521
  });
522
+
523
+ it('does not attempt to extract SEI packet that is too short', () => {
524
+ const badData = new Uint8Array([0xb5]);
525
+ decoder.extract(badData, 0);
526
+ const captions = decoder.decode();
527
+ expect(captions.length).toBe(0);
528
+ });
489
529
  });
490
530
 
491
531
  describe('decodes CEA-708', () => {
@@ -607,12 +607,12 @@ describe('DashParser Live', () => {
607
607
  expect(onError).toHaveBeenCalledTimes(1);
608
608
  });
609
609
 
610
- it('uses @minimumUpdatePeriod', async () => {
610
+ it('still updates when @minimumUpdatePeriod is zero', async () => {
611
611
  const lines = [
612
612
  '<SegmentTemplate startNumber="1" media="s$Number$.mp4" duration="2" />',
613
613
  ];
614
614
  // updateTime parameter sets @minimumUpdatePeriod in the manifest.
615
- const manifestText = makeSimpleLiveManifestText(lines, updateTime);
615
+ const manifestText = makeSimpleLiveManifestText(lines, /* updateTime= */ 0);
616
616
 
617
617
  /** @type {!jasmine.Spy} */
618
618
  const tickAfter = updateTickSpy();
@@ -622,16 +622,14 @@ describe('DashParser Live', () => {
622
622
  await parser.start('dummy://foo', playerInterface);
623
623
 
624
624
  expect(tickAfter).toHaveBeenCalledTimes(1);
625
- const delay = tickAfter.calls.mostRecent().args[0];
626
- expect(delay).toBe(updateTime);
627
625
  });
628
626
 
629
- it('still updates when @minimumUpdatePeriod is zero', async () => {
627
+ it('uses @minimumUpdatePeriod', async () => {
630
628
  const lines = [
631
629
  '<SegmentTemplate startNumber="1" media="s$Number$.mp4" duration="2" />',
632
630
  ];
633
631
  // updateTime parameter sets @minimumUpdatePeriod in the manifest.
634
- const manifestText = makeSimpleLiveManifestText(lines, /* updateTime= */ 0);
632
+ const manifestText = makeSimpleLiveManifestText(lines, updateTime);
635
633
 
636
634
  /** @type {!jasmine.Spy} */
637
635
  const tickAfter = updateTickSpy();
@@ -640,10 +638,9 @@ describe('DashParser Live', () => {
640
638
  fakeNetEngine.setResponseText('dummy://foo', manifestText);
641
639
  await parser.start('dummy://foo', playerInterface);
642
640
 
643
- const waitTime = shaka.dash.DashParser['MIN_UPDATE_PERIOD_'];
644
641
  expect(tickAfter).toHaveBeenCalledTimes(1);
645
642
  const delay = tickAfter.calls.mostRecent().args[0];
646
- expect(delay).toBe(waitTime);
643
+ expect(delay).toBe(updateTime);
647
644
  });
648
645
 
649
646
  it('does not update when @minimumUpdatePeriod is missing', async () => {
@@ -57,6 +57,11 @@ describe('DashParser Manifest', () => {
57
57
  };
58
58
  });
59
59
 
60
+ afterEach(() => {
61
+ // Dash parser stop is synchronous.
62
+ parser.stop();
63
+ });
64
+
60
65
  /**
61
66
  * Makes a series of tests for the given manifest type.
62
67
  *
@@ -43,6 +43,11 @@ describe('DashParser SegmentBase', () => {
43
43
  };
44
44
  });
45
45
 
46
+ afterEach(() => {
47
+ // Dash parser stop is synchronous.
48
+ parser.stop();
49
+ });
50
+
46
51
  it('requests init data for WebM', async () => {
47
52
  const source = [
48
53
  '<MPD mediaPresentationDuration="PT75S">',
@@ -351,24 +351,28 @@ describe('DashParser SegmentList', () => {
351
351
  updateDuration: () => {},
352
352
  newDrmInfo: (stream) => {},
353
353
  };
354
- const manifest = await dashParser.start('dummy://foo', playerInterface);
355
- const stream = manifest.variants[0].video;
356
- await stream.createSegmentIndex();
357
- goog.asserts.assert(stream.segmentIndex, 'Expected index to be created');
354
+ try {
355
+ const manifest = await dashParser.start('dummy://foo', playerInterface);
356
+ const stream = manifest.variants[0].video;
357
+ await stream.createSegmentIndex();
358
+ goog.asserts.assert(stream.segmentIndex, 'Expected index to be created');
358
359
 
359
- // Don't use Dash.testSegmentIndex since it uses SegmentIndex.find, which
360
- // doesn't reproduce this issue. We want to use Array.from which uses the
361
- // iterator.
362
- const expected = [
363
- ManifestParser.makeReference('s1.mp4', 0, 10, baseUri),
364
- ManifestParser.makeReference('s2.mp4', 10, 20, baseUri),
365
- ManifestParser.makeReference('s3.mp4', 20, 30, baseUri),
360
+ // Don't use Dash.testSegmentIndex since it uses SegmentIndex.find, which
361
+ // doesn't reproduce this issue. We want to use Array.from which uses
362
+ // the iterator.
363
+ const expected = [
364
+ ManifestParser.makeReference('s1.mp4', 0, 10, baseUri),
365
+ ManifestParser.makeReference('s2.mp4', 10, 20, baseUri),
366
+ ManifestParser.makeReference('s3.mp4', 20, 30, baseUri),
366
367
 
367
- ManifestParser.makeReference('s3.mp4', 30, 40, baseUri),
368
- ManifestParser.makeReference('s4.mp4', 40, 50, baseUri),
369
- ManifestParser.makeReference('s5.mp4', 50, 60, baseUri),
370
- ];
371
- const actual = Array.from(stream.segmentIndex);
372
- expect(actual).toEqual(expected);
368
+ ManifestParser.makeReference('s3.mp4', 30, 40, baseUri),
369
+ ManifestParser.makeReference('s4.mp4', 40, 50, baseUri),
370
+ ManifestParser.makeReference('s5.mp4', 50, 60, baseUri),
371
+ ];
372
+ const actual = Array.from(stream.segmentIndex);
373
+ expect(actual).toEqual(expected);
374
+ } finally {
375
+ dashParser.stop();
376
+ }
373
377
  });
374
378
  });
@@ -52,6 +52,11 @@ describe('DashParser SegmentTemplate', () => {
52
52
  };
53
53
  });
54
54
 
55
+ afterEach(() => {
56
+ // Dash parser stop is synchronous.
57
+ parser.stop();
58
+ });
59
+
55
60
  shaka.test.Dash.makeTimelineTests(
56
61
  'SegmentTemplate', 'media="s$Number$.mp4"', []);
57
62
 
@@ -501,10 +501,43 @@ describe('HlsParser live', () => {
501
501
 
502
502
  it('sets 3 times target duration as presentation delay if not configured',
503
503
  async () => {
504
+ const media = [
505
+ '#EXTM3U\n',
506
+ '#EXT-X-TARGETDURATION:5\n',
507
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
508
+ '#EXT-X-MEDIA-SEQUENCE:0\n',
509
+ '#EXTINF:2,\n',
510
+ 'main.mp4\n',
511
+ '#EXTINF:2,\n',
512
+ 'main.mp4\n',
513
+ '#EXTINF:2,\n',
514
+ 'main.mp4\n',
515
+ '#EXTINF:2,\n',
516
+ 'main.mp4\n',
517
+ '#EXTINF:2,\n',
518
+ 'main.mp4\n',
519
+ '#EXTINF:2,\n',
520
+ 'main.mp4\n',
521
+ ].join('');
504
522
  const manifest = await testInitialManifest(master, media);
505
523
  expect(manifest.presentationTimeline.getDelay()).toBe(15);
506
524
  });
507
525
 
526
+ it('sets 1 times target duration as presentation delay if there are not enough segments', async () => { // eslint-disable-line max-len
527
+ const media = [
528
+ '#EXTM3U\n',
529
+ '#EXT-X-TARGETDURATION:5\n',
530
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
531
+ '#EXT-X-MEDIA-SEQUENCE:0\n',
532
+ '#EXTINF:2,\n',
533
+ 'main.mp4\n',
534
+ '#EXTINF:2,\n',
535
+ 'main.mp4\n',
536
+ ].join('');
537
+ const manifest = await testInitialManifest(master, media);
538
+ expect(manifest.presentationTimeline.getDelay()).toBe(5);
539
+ });
540
+
508
541
  it('sets presentation delay for low latency mode', async () => {
509
542
  const mediaWithLowLatency = [
510
543
  '#EXTM3U\n',
@@ -739,6 +739,45 @@ describe('HlsParser', () => {
739
739
  await testHlsParser(master, media, manifest);
740
740
  });
741
741
 
742
+ it('parses audio+video variant with global closed captions', async () => {
743
+ const master = [
744
+ '#EXTM3U\n',
745
+ '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",CHANNELS="2",',
746
+ 'URI="audio"\n',
747
+ '#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,GROUP-ID="cap1",LANGUAGE="eng",',
748
+ 'INSTREAM-ID="CC1"\n',
749
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
750
+ 'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
751
+ 'video\n',
752
+ ].join('');
753
+
754
+ const media = [
755
+ '#EXTM3U\n',
756
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
757
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
758
+ '#EXTINF:5,\n',
759
+ '#EXT-X-BYTERANGE:121090@616\n',
760
+ 'main.mp4',
761
+ ].join('');
762
+
763
+ const closedCaptions = new Map([['CC1', 'en']]);
764
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
765
+ manifest.anyTimeline();
766
+ manifest.addPartialVariant((variant) => {
767
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
768
+ stream.closedCaptions = closedCaptions;
769
+ stream.mime('video/mp4', 'avc1');
770
+ });
771
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
772
+ stream.mime('audio/mp4', 'mp4a');
773
+ });
774
+ });
775
+ manifest.sequenceMode = true;
776
+ });
777
+
778
+ await testHlsParser(master, media, manifest);
779
+ });
780
+
742
781
  it('parses audio+video variant with no closed captions', async () => {
743
782
  const master = [
744
783
  '#EXTM3U\n',
@@ -1114,63 +1153,6 @@ describe('HlsParser', () => {
1114
1153
  expect(references[6].discontinuitySequence).toBe(3);
1115
1154
  });
1116
1155
 
1117
- it('sets reference timetampOffset based on discontinuity start time',
1118
- async () => {
1119
- const master = [
1120
- '#EXTM3U\n',
1121
- '#EXT-X-STREAM-INF:BANDWIDTH=2000000,CODECS="avc1"\n',
1122
- 'video\n',
1123
- ].join('');
1124
-
1125
- const media = [
1126
- '#EXTM3U\n',
1127
- '#EXT-X-VERSION:3\n',
1128
- '#EXT-X-TARGETDURATION:5\n',
1129
- '#EXT-X-MEDIA-SEQUENCE:0\n',
1130
- '#EXTINF:3,\n',
1131
- 'clip0-video-0.ts\n',
1132
- '#EXTINF:1,\n',
1133
- 'clip0-video-1.ts\n',
1134
- '#EXT-X-DISCONTINUITY\n',
1135
- '#EXTINF:2,\n',
1136
- 'clip1-video-1.ts\n',
1137
- '#EXTINF:3,\n',
1138
- 'clip1-video-2.ts\n',
1139
- '#EXT-X-DISCONTINUITY\n',
1140
- '#EXTINF:1,\n',
1141
- 'media-clip2-video-0.ts\n',
1142
- '#EXTINF:1,\n',
1143
- 'media-clip2-video-1.ts\n',
1144
- '#EXT-X-DISCONTINUITY\n',
1145
- '#EXTINF:4,\n',
1146
- 'media-clip3-video-1.ts\n',
1147
- '#EXT-X-ENDLIST\n',
1148
- ].join('');
1149
-
1150
- fakeNetEngine
1151
- .setResponseText('test:/master', master)
1152
- .setResponseText('test:/video', media);
1153
-
1154
- const manifest = await parser.start('test:/master', playerInterface);
1155
- await manifest.variants[0].video.createSegmentIndex();
1156
-
1157
- const segmentIndex = manifest.variants[0].video.segmentIndex;
1158
- const references = [];
1159
-
1160
- for (let i = 0; i < 7; i++) {
1161
- references.push(segmentIndex.get(i));
1162
- }
1163
-
1164
- expect(references[0].timestampOffset).toBe(0);
1165
- expect(references[1].timestampOffset).toBe(0);
1166
- expect(references[2].timestampOffset).toBe(4);
1167
- expect(references[3].timestampOffset).toBe(4);
1168
- expect(references[4].timestampOffset).toBe(9);
1169
- expect(references[5].timestampOffset).toBe(9);
1170
- expect(references[6].timestampOffset).toBe(11);
1171
- },
1172
- );
1173
-
1174
1156
  it('parses characteristics from audio tags', async () => {
1175
1157
  const master = [
1176
1158
  '#EXTM3U\n',
@@ -2983,7 +2965,8 @@ describe('HlsParser', () => {
2983
2965
  const media = [
2984
2966
  '#EXTM3U\n',
2985
2967
  '#EXT-X-PLAYLIST-TYPE:VOD\n',
2986
- '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
2968
+ '#EXT-X-BYTERANGE:616@0\n',
2969
+ '#EXT-X-MAP:URI="init.mp4"\n',
2987
2970
  '#EXTINF:5,\n',
2988
2971
  'main.mp4\n',
2989
2972
  '#EXT-X-MAP:URI="init2.mp4",BYTERANGE="616@0"\n',
@@ -3011,8 +2994,15 @@ describe('HlsParser', () => {
3011
2994
  // uri.
3012
2995
  const initSegments = Array.from(actualVideo.segmentIndex).map(
3013
2996
  (seg) => seg.initSegmentReference);
3014
- expect(initSegments[0].getUris()[0]).toBe('test:/init.mp4');
3015
- expect(initSegments[1].getUris()[0]).toBe('test:/init2.mp4');
2997
+ expect(initSegments.length).toBe(2);
2998
+ const firstInitSegment = initSegments[0];
2999
+ expect(firstInitSegment.getUris()[0]).toBe('test:/init.mp4');
3000
+ expect(firstInitSegment.startByte).toBe(0);
3001
+ expect(firstInitSegment.endByte).toBe(615);
3002
+ const secondInitSegment = initSegments[1];
3003
+ expect(secondInitSegment.getUris()[0]).toBe('test:/init2.mp4');
3004
+ expect(secondInitSegment.startByte).toBe(0);
3005
+ expect(secondInitSegment.endByte).toBe(615);
3016
3006
  });
3017
3007
 
3018
3008
  it('parses variants encrypted with AES-128', async () => {
@@ -3132,8 +3122,10 @@ describe('HlsParser', () => {
3132
3122
 
3133
3123
  const firstMp4Segment = mp4AesEncryptionVideo.segmentIndex.get(0);
3134
3124
  expect(firstMp4Segment.hlsAes128Key).toBeDefined();
3125
+ expect(firstMp4Segment.initSegmentReference.hlsAes128Key).toBeDefined();
3135
3126
  const secondMp4Segment = mp4AesEncryptionVideo.segmentIndex.get(1);
3136
3127
  expect(secondMp4Segment.hlsAes128Key).toBeNull();
3128
+ expect(secondMp4Segment.initSegmentReference.hlsAes128Key).toBeDefined();
3137
3129
 
3138
3130
  const tsAesEncryptionVideo = actual.variants[2].video;
3139
3131
  await tsAesEncryptionVideo.createSegmentIndex();
@@ -11,15 +11,28 @@ describe('BufferingObserver', () => {
11
11
  const thresholdAfterStarving = 5;
12
12
  const thresholdAfterSatisfied = 2;
13
13
 
14
+ const originalDateNow = Date.now;
15
+
16
+ /** @type {!Date} */
17
+ let baseTime;
18
+
14
19
  /** @type {!shaka.media.BufferingObserver} */
15
20
  let controller;
16
21
 
17
22
  beforeEach(() => {
23
+ // Any test using Date needs to have Date mocked to avoid flake.
24
+ baseTime = new Date(2015, 11, 30);
25
+ Date.now = () => baseTime.getTime();
26
+
18
27
  controller = new BufferingObserver(
19
28
  thresholdAfterStarving,
20
29
  thresholdAfterSatisfied);
21
30
  });
22
31
 
32
+ afterEach(() => {
33
+ Date.now = originalDateNow;
34
+ });
35
+
23
36
  describe('when satisfied', () => {
24
37
  beforeEach(() => {
25
38
  controller.setState(State.SATISFIED);