shaka-player 3.2.2 → 3.2.3

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 (51) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/controls.css +2 -2
  3. package/dist/controls.css.map +1 -1
  4. package/dist/locales.js +30 -30
  5. package/dist/shaka-player.compiled.d.ts +21 -2
  6. package/dist/shaka-player.compiled.debug.d.ts +21 -2
  7. package/dist/shaka-player.compiled.debug.externs.js +22 -1
  8. package/dist/shaka-player.compiled.debug.js +205 -202
  9. package/dist/shaka-player.compiled.debug.map +3 -3
  10. package/dist/shaka-player.compiled.externs.js +22 -1
  11. package/dist/shaka-player.compiled.js +727 -723
  12. package/dist/shaka-player.compiled.map +4 -4
  13. package/dist/shaka-player.ui.d.ts +21 -2
  14. package/dist/shaka-player.ui.debug.d.ts +21 -2
  15. package/dist/shaka-player.ui.debug.externs.js +22 -1
  16. package/dist/shaka-player.ui.debug.js +221 -218
  17. package/dist/shaka-player.ui.debug.map +3 -3
  18. package/dist/shaka-player.ui.externs.js +22 -1
  19. package/dist/shaka-player.ui.js +829 -825
  20. package/dist/shaka-player.ui.map +4 -4
  21. package/externs/shaka/player.js +8 -0
  22. package/externs/shaka/text.js +10 -0
  23. package/lib/dash/dash_parser.js +4 -2
  24. package/lib/hls/hls_parser.js +30 -0
  25. package/lib/media/segment_index.js +2 -1
  26. package/lib/media/segment_reference.js +19 -1
  27. package/lib/media/streaming_engine.js +5 -2
  28. package/lib/player.js +20 -13
  29. package/lib/polyfill/media_capabilities.js +30 -15
  30. package/lib/text/cue.js +6 -0
  31. package/lib/text/ttml_text_parser.js +84 -58
  32. package/lib/text/ui_text_displayer.js +17 -11
  33. package/lib/util/stream_utils.js +86 -10
  34. package/lib/util/xml_utils.js +19 -0
  35. package/package.json +2 -2
  36. package/test/dash/dash_parser_manifest_unit.js +33 -1
  37. package/test/media/drm_engine_unit.js +3 -3
  38. package/test/media/streaming_engine_integration.js +2 -1
  39. package/test/media/streaming_engine_unit.js +145 -0
  40. package/test/player_integration.js +41 -8
  41. package/test/player_src_equals_integration.js +41 -8
  42. package/test/player_unit.js +19 -9
  43. package/test/test/assets/chapters.vtt +2 -2
  44. package/test/test/assets/chapters2.vtt +10 -0
  45. package/test/test/util/ttml_utils.js +81 -0
  46. package/test/text/mp4_ttml_parser_unit.js +11 -18
  47. package/test/text/ttml_text_parser_unit.js +495 -228
  48. package/test/util/stream_utils_unit.js +36 -30
  49. package/ui/less/buttons.less +0 -4
  50. package/ui/text_selection.js +1 -0
  51. package/externs/media_capabilities.js +0 -16
@@ -352,19 +352,22 @@ shaka.text.UITextDisplayer = class {
352
352
  type = 'br';
353
353
  }
354
354
 
355
+ const needWrapper = !isNested && cue.nestedCues.length > 0;
356
+
355
357
  // Nested cues are inline elements. Top-level cues are block elements.
356
358
  const cueElement = shaka.util.Dom.createHTMLElement(type);
357
359
  if (type != 'br') {
358
- this.setCaptionStyles_(cueElement, cue, parents);
360
+ this.setCaptionStyles_(cueElement, cue, parents, needWrapper);
359
361
  }
360
362
 
361
363
  let wrapper = cueElement;
362
- if (!isNested && cue.nestedCues.length) {
364
+ if (needWrapper) {
363
365
  // Create a wrapper element which will serve to contain all children into
364
366
  // a single item. This ensures that nested span elements appear
365
367
  // horizontally and br elements occupy no vertical space.
366
368
  wrapper = shaka.util.Dom.createHTMLElement('span');
367
369
  wrapper.classList.add('shaka-text-wrapper');
370
+ wrapper.style.backgroundColor = cue.backgroundColor;
368
371
  cueElement.appendChild(wrapper);
369
372
  }
370
373
 
@@ -375,9 +378,10 @@ shaka.text.UITextDisplayer = class {
375
378
  * @param {!HTMLElement} cueElement
376
379
  * @param {!shaka.extern.Cue} cue
377
380
  * @param {!Array.<!shaka.extern.Cue>} parents
381
+ * @param {boolean} hasWrapper
378
382
  * @private
379
383
  */
380
- setCaptionStyles_(cueElement, cue, parents) {
384
+ setCaptionStyles_(cueElement, cue, parents, hasWrapper) {
381
385
  const Cue = shaka.text.Cue;
382
386
  const inherit =
383
387
  (cb) => shaka.text.UITextDisplayer.inheritProperty_(parents, cb);
@@ -434,13 +438,15 @@ shaka.text.UITextDisplayer = class {
434
438
  if (cue.border) {
435
439
  elem.style.border = cue.border;
436
440
  }
437
- const bgColor = inherit((c) => c.backgroundColor);
438
- if (bgColor) {
439
- elem.style.backgroundColor = bgColor;
440
- } else if (text) {
441
- // If there is no background, default to a semi-transparent black.
442
- // Only do this for the text itself.
443
- elem.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
441
+ if (!hasWrapper) {
442
+ const bgColor = inherit((c) => c.backgroundColor);
443
+ if (bgColor) {
444
+ elem.style.backgroundColor = bgColor;
445
+ } else if (text) {
446
+ // If there is no background, default to a semi-transparent black.
447
+ // Only do this for the text itself.
448
+ elem.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
449
+ }
444
450
  }
445
451
  if (text) {
446
452
  elem.textContent = text;
@@ -450,7 +456,7 @@ shaka.text.UITextDisplayer = class {
450
456
  // The displayAlign attribute specifies the vertical alignment of the
451
457
  // captions inside the text container. Before means at the top of the
452
458
  // text container, and after means at the bottom.
453
- if (isNested) {
459
+ if (isNested && !parents[parents.length - 1].isContainer) {
454
460
  style.display = 'inline';
455
461
  } else {
456
462
  style.display = 'flex';
@@ -419,13 +419,48 @@ shaka.util.StreamUtils = class {
419
419
  */
420
420
  static async filterManifestByMediaCapabilities(
421
421
  manifest, usePersistentLicenses) {
422
- goog.asserts.assert(window.shakaMediaCapabilities,
422
+ goog.asserts.assert(navigator.mediaCapabilities,
423
423
  'MediaCapabilities should be valid.');
424
424
 
425
425
  await shaka.util.StreamUtils.getDecodingInfosForVariants(
426
426
  manifest.variants, usePersistentLicenses, /* srcEquals= */ false);
427
427
  manifest.variants = manifest.variants.filter((variant) => {
428
+ // See: https://github.com/google/shaka-player/issues/3860
428
429
  const video = variant.video;
430
+ const ContentType = shaka.util.ManifestParserUtils.ContentType;
431
+ if (video) {
432
+ let videoCodecs = shaka.util.StreamUtils.patchVp9(video.codecs);
433
+ // For multiplexed streams. Here we must check the audio of the
434
+ // stream to see if it is compatible.
435
+ if (video.codecs.includes(',')) {
436
+ const allCodecs = video.codecs.split(',');
437
+ videoCodecs = shaka.util.ManifestParserUtils.guessCodecs(
438
+ ContentType.VIDEO, allCodecs);
439
+ const audioCodecs = shaka.util.ManifestParserUtils.guessCodecs(
440
+ ContentType.AUDIO, allCodecs);
441
+ const audioFullType = shaka.util.MimeUtils.getFullOrConvertedType(
442
+ video.mimeType, audioCodecs, ContentType.AUDIO);
443
+ if (!MediaSource.isTypeSupported(audioFullType)) {
444
+ return false;
445
+ }
446
+ }
447
+ const fullType = shaka.util.MimeUtils.getFullOrConvertedType(
448
+ video.mimeType, videoCodecs, ContentType.VIDEO);
449
+ if (!MediaSource.isTypeSupported(fullType)) {
450
+ return false;
451
+ }
452
+ }
453
+ const audio = variant.audio;
454
+ if (audio) {
455
+ const codecs =
456
+ shaka.util.StreamUtils.getCorrectAudioCodecs_(audio.codecs);
457
+ const fullType = shaka.util.MimeUtils.getFullOrConvertedType(
458
+ audio.mimeType, codecs, ContentType.AUDIO);
459
+ if (!MediaSource.isTypeSupported(fullType)) {
460
+ return false;
461
+ }
462
+ }
463
+
429
464
  // See: https://github.com/google/shaka-player/issues/3380
430
465
  if (shaka.util.Platform.isXboxOne() && video &&
431
466
  ((video.width && video.width > 1920) ||
@@ -468,7 +503,7 @@ shaka.util.StreamUtils = class {
468
503
  return;
469
504
  }
470
505
 
471
- const mediaCapabilities = window.shakaMediaCapabilities;
506
+ const mediaCapabilities = navigator.mediaCapabilities;
472
507
 
473
508
  const operations = [];
474
509
  const getVariantDecodingInfos = (async (variant, decodingConfig) => {
@@ -562,12 +597,8 @@ shaka.util.StreamUtils = class {
562
597
  }
563
598
  }
564
599
  if (audio) {
565
- // Some Tizen devices seem to misreport AC-3 support, but correctly
566
- // report EC-3 support. So query EC-3 as a fallback for AC-3.
567
- // See https://github.com/google/shaka-player/issues/2989 for details.
568
600
  const codecs =
569
- (audio.codecs.toLowerCase() == 'ac-3' &&
570
- shaka.util.Platform.isTizen()) ? 'ec-3' : audio.codecs;
601
+ shaka.util.StreamUtils.getCorrectAudioCodecs_(audio.codecs);
571
602
  const fullType = shaka.util.MimeUtils.getFullOrConvertedType(
572
603
  audio.mimeType, codecs, ContentType.AUDIO);
573
604
 
@@ -678,6 +709,26 @@ shaka.util.StreamUtils = class {
678
709
  return configs;
679
710
  }
680
711
 
712
+
713
+ /**
714
+ * Generates the correct audio codec for MediaDecodingConfiguration and
715
+ * for MediaSource.isTypeSupported.
716
+ * @param {string} codecs
717
+ * @return {string}
718
+ * @private
719
+ */
720
+ static getCorrectAudioCodecs_(codecs) {
721
+ // Some Tizen devices seem to misreport AC-3 support, but correctly
722
+ // report EC-3 support. So query EC-3 as a fallback for AC-3.
723
+ // See https://github.com/google/shaka-player/issues/2989 for details.
724
+ if (shaka.util.Platform.isTizen()) {
725
+ return codecs.toLowerCase() == 'ac-3' ? 'ec-3' : codecs;
726
+ } else {
727
+ return codecs;
728
+ }
729
+ }
730
+
731
+
681
732
  /**
682
733
  * MediaCapabilities supports 'vp09...' codecs, but not 'vp9'. Translate vp9
683
734
  * codec strings into 'vp09...', to allow such content to play with
@@ -989,6 +1040,31 @@ shaka.util.StreamUtils = class {
989
1040
  static imageStreamToTrack(stream) {
990
1041
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
991
1042
 
1043
+ let width = stream.width || null;
1044
+ let height = stream.height || null;
1045
+
1046
+ // The stream width and height represent the size of the entire thumbnail
1047
+ // sheet, so divide by the layout.
1048
+ let reference = null;
1049
+ // Note: segmentIndex is built by default for HLS, but not for DASH, but
1050
+ // in DASH this information comes at the stream level and not at the
1051
+ // segment level.
1052
+ if (stream.segmentIndex) {
1053
+ reference = stream.segmentIndex.get(0);
1054
+ }
1055
+ let layout = stream.tilesLayout;
1056
+ if (reference) {
1057
+ layout = reference.getTilesLayout() || layout;
1058
+ }
1059
+ if (layout && width != null) {
1060
+ width /= Number(layout.split('x')[0]);
1061
+ }
1062
+ if (layout && height != null) {
1063
+ height /= Number(layout.split('x')[1]);
1064
+ }
1065
+ // TODO: What happens if there are multiple grids, with different
1066
+ // layout sizes, inside this image stream?
1067
+
992
1068
  /** @type {shaka.extern.Track} */
993
1069
  const track = {
994
1070
  id: stream.id,
@@ -998,8 +1074,8 @@ shaka.util.StreamUtils = class {
998
1074
  language: '',
999
1075
  label: null,
1000
1076
  kind: null,
1001
- width: stream.width || null,
1002
- height: stream.height || null,
1077
+ width,
1078
+ height,
1003
1079
  frameRate: null,
1004
1080
  pixelAspectRatio: null,
1005
1081
  hdr: null,
@@ -1016,7 +1092,7 @@ shaka.util.StreamUtils = class {
1016
1092
  channelsCount: null,
1017
1093
  audioSamplingRate: null,
1018
1094
  spatialAudio: false,
1019
- tilesLayout: stream.tilesLayout || null,
1095
+ tilesLayout: layout || null,
1020
1096
  audioBandwidth: null,
1021
1097
  videoBandwidth: null,
1022
1098
  originalVideoId: null,
@@ -103,6 +103,25 @@ shaka.util.XmlUtils = class {
103
103
  }
104
104
 
105
105
 
106
+ /**
107
+ * Gets a namespace-qualified attribute.
108
+ * @param {!Element} elem The element to get from.
109
+ * @param {!Array.<string>} nsList The lis of namespace URIs.
110
+ * @param {string} name The local name of the attribute.
111
+ * @return {?string} The attribute's value, or null if not present.
112
+ */
113
+ static getAttributeNSList(elem, nsList, name) {
114
+ // Some browsers return the empty string when the attribute is missing,
115
+ // so check if it exists first. See: https://mzl.la/2L7F0UK
116
+ for (const ns of nsList) {
117
+ if (elem.hasAttributeNS(ns, name)) {
118
+ return elem.getAttributeNS(ns, name);
119
+ }
120
+ }
121
+ return null;
122
+ }
123
+
124
+
106
125
  /**
107
126
  * Gets the text contents of a node.
108
127
  * @param {!Node} elem The XML element.
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.2.2",
4
+ "version": "3.2.3",
5
5
  "homepage": "https://github.com/google/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -12,6 +12,7 @@
12
12
  ],
13
13
  "devDependencies": {
14
14
  "@chiragrupani/karma-chromium-edge-launcher": "^2.1.0",
15
+ "@onslip/karma-safari-launcher": "^1.0.1",
15
16
  "@teppeis/clutz": "^1.0.29-4c95e12.v20190929",
16
17
  "awesomplete": "^1.1.4",
17
18
  "babel-core": "^6.26.3",
@@ -44,7 +45,6 @@
44
45
  "karma-jasmine": "^3.3.1",
45
46
  "karma-jasmine-ajax": "^0.1.13",
46
47
  "karma-opera-launcher": "^1.0.0",
47
- "karma-safari-launcher": "^1.0.0",
48
48
  "karma-sourcemap-loader": "^0.3.7",
49
49
  "karma-spec-reporter": "0.0.32",
50
50
  "karma-webdriver-launcher": "^1.0.8",
@@ -1872,7 +1872,7 @@ describe('DashParser Manifest', () => {
1872
1872
  expect(textStream.kind).toBe('caption');
1873
1873
  });
1874
1874
 
1875
- it('converts Roles element to "forced"', async () => {
1875
+ it('converts Roles element to "forced" (old role)', async () => {
1876
1876
  const manifestText = [
1877
1877
  '<MPD minBufferTime="PT75S">',
1878
1878
  ' <Period id="1" duration="PT30S">',
@@ -1904,6 +1904,38 @@ describe('DashParser Manifest', () => {
1904
1904
  expect(textStream.forced).toBe(true);
1905
1905
  });
1906
1906
 
1907
+ it('converts Roles element to "forced"', async () => {
1908
+ const manifestText = [
1909
+ '<MPD minBufferTime="PT75S">',
1910
+ ' <Period id="1" duration="PT30S">',
1911
+ ' <AdaptationSet id="1" contentType="text">',
1912
+ ' <Accessibility schemeIdUri="urn:mpeg:dash:role:2011" ',
1913
+ ' value="captions" />',
1914
+ ' <Accessibility schemeIdUri="urn:mpeg:dash:role:2011" ',
1915
+ ' value="forced-subtitle" />',
1916
+ ' <Accessibility schemeIdUri="foobar" value="bar" />',
1917
+ ' <Representation id="text-en" mimeType="text/webvtt">',
1918
+ ' <BaseURL>t-en.vtt</BaseURL>',
1919
+ ' </Representation>',
1920
+ ' </AdaptationSet>',
1921
+ ' <AdaptationSet id="1" mimeType="video/mp4">',
1922
+ ' <Representation id="video-sd" width="640" height="480">',
1923
+ ' <BaseURL>v-sd.mp4</BaseURL>',
1924
+ ' <SegmentBase indexRange="100-200" />',
1925
+ ' </Representation>',
1926
+ ' </AdaptationSet>',
1927
+ ' </Period>',
1928
+ '</MPD>',
1929
+ ].join('\n');
1930
+
1931
+ fakeNetEngine.setResponseText('dummy://foo', manifestText);
1932
+ /** @type {shaka.extern.Manifest} */
1933
+ const manifest = await parser.start('dummy://foo', playerInterface);
1934
+ const textStream = manifest.textStreams[0];
1935
+ expect(textStream.roles).toEqual(['captions', 'forced-subtitle']);
1936
+ expect(textStream.forced).toBe(true);
1937
+ });
1938
+
1907
1939
  it('supports HDR signaling via profiles', async () => {
1908
1940
  // (DASH-IF IOP v4.3 10.3.3.)
1909
1941
  const hdrProfile =
@@ -31,7 +31,7 @@ describe('DrmEngine', () => {
31
31
  navigator.requestMediaKeySystemAccess;
32
32
  const originalLogError = shaka.log.error;
33
33
  const originalBatchTime = shaka.media.DrmEngine.KEY_STATUS_BATCH_TIME;
34
- const originalDecodingInfo = window.shakaMediaCapabilities.decodingInfo;
34
+ const originalDecodingInfo = navigator.mediaCapabilities.decodingInfo;
35
35
 
36
36
  /** @type {!jasmine.Spy} */
37
37
  let decodingInfoSpy;
@@ -76,7 +76,7 @@ describe('DrmEngine', () => {
76
76
 
77
77
  beforeEach(() => {
78
78
  decodingInfoSpy = jasmine.createSpy('decodingInfo');
79
- window.shakaMediaCapabilities.decodingInfo =
79
+ navigator.mediaCapabilities.decodingInfo =
80
80
  shaka.test.Util.spyFunc(decodingInfoSpy);
81
81
 
82
82
  logErrorSpy = jasmine.createSpy('shaka.log.error');
@@ -153,7 +153,7 @@ describe('DrmEngine', () => {
153
153
 
154
154
  navigator.requestMediaKeySystemAccess =
155
155
  originalRequestMediaKeySystemAccess;
156
- window.shakaMediaCapabilities.decodingInfo = originalDecodingInfo;
156
+ navigator.mediaCapabilities.decodingInfo = originalDecodingInfo;
157
157
  shaka.log.error = originalLogError;
158
158
  });
159
159
 
@@ -419,8 +419,9 @@ describe('StreamingEngine', () => {
419
419
  // 3. Playhead seeks to force us back inside the window
420
420
  // 4. (maybe) seek if there is a gap at the period boundary
421
421
  // 5. (maybe) seek to flush a pipeline stall
422
+ // 6. (maybe) on slower platforms (e.g. GitHub actions)
422
423
  expect(seekCount).toBeGreaterThan(2);
423
- expect(seekCount).toBeLessThan(6);
424
+ expect(seekCount).toBeLessThan(7);
424
425
  });
425
426
  });
426
427
 
@@ -97,6 +97,9 @@ describe('StreamingEngine', () => {
97
97
  /** @type {!shaka.media.StreamingEngine} */
98
98
  let streamingEngine;
99
99
 
100
+ /** @type {function(function(), number)} */
101
+ let realSetTimeout;
102
+
100
103
  /**
101
104
  * Runs the fake event loop.
102
105
  * @param {function()=} callback An optional callback that is executed
@@ -116,6 +119,7 @@ describe('StreamingEngine', () => {
116
119
  }
117
120
 
118
121
  beforeAll(() => {
122
+ realSetTimeout = window.setTimeout;
119
123
  jasmine.clock().install();
120
124
  jasmine.clock().mockDate();
121
125
  });
@@ -822,6 +826,53 @@ describe('StreamingEngine', () => {
822
826
  .toHaveBeenCalledWith('video', 0, lt20, gt40);
823
827
  });
824
828
 
829
+ // Regression test for https://github.com/google/shaka-player/issues/3717
830
+ it('applies fudge factors for the duration', async () => {
831
+ setupVod();
832
+
833
+ // In #3717, the duration was just barely large enough to encompass an
834
+ // additional segment, but that segment didn't exist, so playback never
835
+ // completed. Here, we set the duration to just beyond the 3rd segment, and
836
+ // we make the 4th segment fail when requested.
837
+ const duration = 30.000000005;
838
+ timeline.getDuration.and.returnValue(duration);
839
+
840
+ const targetUri = '1_video_3'; // The URI of the 4th video segment.
841
+ failRequestsForTarget(netEngine, targetUri);
842
+
843
+ mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
844
+ createStreamingEngine();
845
+
846
+ // Here we go!
847
+ streamingEngine.switchVariant(variant);
848
+ streamingEngine.switchTextStream(textStream);
849
+ await streamingEngine.start();
850
+ playing = true;
851
+ await runTest();
852
+
853
+ // The end of the stream should have been reached, and the 4th segment from
854
+ // each type should never have been requested.
855
+ expect(mediaSourceEngine.endOfStream).toHaveBeenCalled();
856
+
857
+ const segmentType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
858
+
859
+ netEngine.expectRequest('0_audio_0', segmentType);
860
+ netEngine.expectRequest('0_video_0', segmentType);
861
+ netEngine.expectRequest('0_text_0', segmentType);
862
+
863
+ netEngine.expectRequest('0_audio_1', segmentType);
864
+ netEngine.expectRequest('0_video_1', segmentType);
865
+ netEngine.expectRequest('0_text_1', segmentType);
866
+
867
+ netEngine.expectRequest('1_audio_2', segmentType);
868
+ netEngine.expectRequest('1_video_2', segmentType);
869
+ netEngine.expectRequest('1_text_2', segmentType);
870
+
871
+ netEngine.expectNoRequest('1_audio_3', segmentType);
872
+ netEngine.expectNoRequest('1_video_3', segmentType);
873
+ netEngine.expectNoRequest('1_text_3', segmentType);
874
+ });
875
+
825
876
  it('does not buffer one media type ahead of another', async () => {
826
877
  setupVod();
827
878
  mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
@@ -1389,6 +1440,66 @@ describe('StreamingEngine', () => {
1389
1440
  });
1390
1441
  });
1391
1442
 
1443
+ it('into unbuffered regions when nothing is buffered ' +
1444
+ 'and mediaState is performing an update', async () => {
1445
+ // Here we go!
1446
+ streamingEngine.switchVariant(variant);
1447
+ streamingEngine.switchTextStream(textStream);
1448
+ // ensure init source buffer promise does not resolve before seeked()
1449
+ // so mediaState remains in "performingUpdate" state
1450
+ const initSourceBufferPromise = new shaka.util.PublicPromise();
1451
+ mediaSourceEngine.setStreamProperties.and
1452
+ .returnValue(initSourceBufferPromise);
1453
+ await streamingEngine.start();
1454
+ playing = true;
1455
+
1456
+ // tick to trigger mediaState updates
1457
+ jasmine.clock().tick(1);
1458
+ // give a chance for fetchAndAppend_ to be invoked
1459
+ // by async onUpdate_ callback
1460
+ await Util.shortDelay(realSetTimeout);
1461
+
1462
+ // Nothing is buffered yet.
1463
+ expect(mediaSourceEngine.segments).toEqual({
1464
+ audio: [false, false, false, false],
1465
+ video: [false, false, false, false],
1466
+ text: [false, false, false, false],
1467
+ });
1468
+
1469
+ // Seek forward to an unbuffered region in the first Period.
1470
+ presentationTimeInSeconds = 15;
1471
+ streamingEngine.seeked();
1472
+
1473
+ // resolve initSourceBufferPromise after seeked(), waitingToClearBuffer
1474
+ // should have been set, so this will now trigger the actual flush of
1475
+ // the buffer
1476
+ initSourceBufferPromise.resolve();
1477
+ mediaSourceEngine.setStreamProperties.and
1478
+ .returnValue(Promise.resolve());
1479
+
1480
+ // allow mediaState update to resolve
1481
+ await Util.shortDelay(realSetTimeout);
1482
+
1483
+ onTick.and.callFake(() => {
1484
+ expect(mediaSourceEngine.clear).toHaveBeenCalled();
1485
+ onTick.and.stub();
1486
+ });
1487
+
1488
+ await runTest(Util.spyFunc(onTick));
1489
+
1490
+ // Verify buffers.
1491
+ expect(mediaSourceEngine.initSegments).toEqual({
1492
+ audio: [false, true],
1493
+ video: [false, true],
1494
+ text: [],
1495
+ });
1496
+ expect(mediaSourceEngine.segments).toEqual({
1497
+ audio: [false, true, true, true],
1498
+ video: [false, true, true, true],
1499
+ text: [false, true, true, true],
1500
+ });
1501
+ });
1502
+
1392
1503
  it('into unbuffered regions near segment start', async () => {
1393
1504
  // Here we go!
1394
1505
  streamingEngine.switchVariant(variant);
@@ -3388,6 +3499,40 @@ describe('StreamingEngine', () => {
3388
3499
  segmentAvailability.end++;
3389
3500
  }
3390
3501
 
3502
+ /**
3503
+ * @param {!shaka.test.FakeNetworkingEngine} netEngine A NetworkingEngine
3504
+ * look-alike.
3505
+ * @param {string} targetUri
3506
+ * @param {shaka.util.Error.Code=} errorCode
3507
+ */
3508
+ function failRequestsForTarget(
3509
+ netEngine, targetUri, errorCode=shaka.util.Error.Code.BAD_HTTP_STATUS) {
3510
+ // eslint-disable-next-line no-restricted-syntax
3511
+ const originalNetEngineRequest = netEngine.request.bind(netEngine);
3512
+
3513
+ netEngine.request = jasmine.createSpy('request').and.callFake(
3514
+ (requestType, request) => {
3515
+ if (request.uris[0] == targetUri) {
3516
+ const data = [targetUri];
3517
+
3518
+ if (errorCode == shaka.util.Error.Code.BAD_HTTP_STATUS) {
3519
+ data.push(404);
3520
+ data.push('');
3521
+ }
3522
+
3523
+ // The compiler still sees the error code parameter as potentially
3524
+ // undefined, even though we gave it a default value.
3525
+ goog.asserts.assert(errorCode != undefined, 'Undefined error code');
3526
+
3527
+ return shaka.util.AbortableOperation.failed(new shaka.util.Error(
3528
+ shaka.util.Error.Severity.CRITICAL,
3529
+ shaka.util.Error.Category.NETWORK,
3530
+ errorCode, data));
3531
+ }
3532
+ return originalNetEngineRequest(requestType, request);
3533
+ });
3534
+ }
3535
+
3391
3536
  /**
3392
3537
  * @param {!shaka.test.FakeNetworkingEngine} netEngine A NetworkingEngine
3393
3538
  * look-alike.
@@ -964,9 +964,9 @@ describe('Player', () => {
964
964
  it('add external chapters in vtt format', async () => {
965
965
  await player.load('test:sintel_no_text_compiled');
966
966
  const locationUri = new goog.Uri(location.href);
967
- const partialUri = new goog.Uri('/base/test/test/assets/chapters.vtt');
968
- const absoluteUri = locationUri.resolve(partialUri);
969
- await player.addChaptersTrack(absoluteUri.toString(), 'en');
967
+ const partialUri1 = new goog.Uri('/base/test/test/assets/chapters.vtt');
968
+ const absoluteUri1 = locationUri.resolve(partialUri1);
969
+ await player.addChaptersTrack(absoluteUri1.toString(), 'en');
970
970
 
971
971
  await shaka.test.Util.delay(1.5);
972
972
 
@@ -979,11 +979,44 @@ describe('Player', () => {
979
979
  const chapter2 = chapters[1];
980
980
  expect(chapter2.title).toBe('Chapter 2');
981
981
  expect(chapter2.startTime).toBe(5);
982
- expect(chapter2.endTime).toBe(30);
982
+ expect(chapter2.endTime).toBe(10);
983
983
  const chapter3 = chapters[2];
984
984
  expect(chapter3.title).toBe('Chapter 3');
985
- expect(chapter3.startTime).toBe(30);
986
- expect(chapter3.endTime).toBe(61.349);
985
+ expect(chapter3.startTime).toBe(10);
986
+ expect(chapter3.endTime).toBe(20);
987
+
988
+ const partialUri2 = new goog.Uri('/base/test/test/assets/chapters2.vtt');
989
+ const absoluteUri2 = locationUri.resolve(partialUri2);
990
+ await player.addChaptersTrack(absoluteUri2.toString(), 'en');
991
+
992
+ await shaka.test.Util.delay(1.5);
993
+
994
+ const chaptersUpdated = player.getChapters('en');
995
+ expect(chaptersUpdated.length).toBe(6);
996
+ const chapterUpdated1 = chaptersUpdated[0];
997
+ expect(chapterUpdated1.title).toBe('Chapter 1');
998
+ expect(chapterUpdated1.startTime).toBe(0);
999
+ expect(chapterUpdated1.endTime).toBe(5);
1000
+ const chapterUpdated2 = chaptersUpdated[1];
1001
+ expect(chapterUpdated2.title).toBe('Chapter 2');
1002
+ expect(chapterUpdated2.startTime).toBe(5);
1003
+ expect(chapterUpdated2.endTime).toBe(10);
1004
+ const chapterUpdated3 = chaptersUpdated[2];
1005
+ expect(chapterUpdated3.title).toBe('Chapter 3');
1006
+ expect(chapterUpdated3.startTime).toBe(10);
1007
+ expect(chapterUpdated3.endTime).toBe(20);
1008
+ const chapterUpdated4 = chaptersUpdated[3];
1009
+ expect(chapterUpdated4.title).toBe('Chapter 4');
1010
+ expect(chapterUpdated4.startTime).toBe(20);
1011
+ expect(chapterUpdated4.endTime).toBe(30);
1012
+ const chapterUpdated5 = chaptersUpdated[4];
1013
+ expect(chapterUpdated5.title).toBe('Chapter 5');
1014
+ expect(chapterUpdated5.startTime).toBe(30);
1015
+ expect(chapterUpdated5.endTime).toBe(40);
1016
+ const chapterUpdated6 = chaptersUpdated[5];
1017
+ expect(chapterUpdated6.title).toBe('Chapter 6');
1018
+ expect(chapterUpdated6.startTime).toBe(40);
1019
+ expect(chapterUpdated6.endTime).toBe(61.349);
987
1020
  });
988
1021
 
989
1022
  it('add external chapters in srt format', async () => {
@@ -991,11 +1024,11 @@ describe('Player', () => {
991
1024
  const locationUri = new goog.Uri(location.href);
992
1025
  const partialUri = new goog.Uri('/base/test/test/assets/chapters.srt');
993
1026
  const absoluteUri = locationUri.resolve(partialUri);
994
- await player.addChaptersTrack(absoluteUri.toString(), 'en');
1027
+ await player.addChaptersTrack(absoluteUri.toString(), 'es');
995
1028
 
996
1029
  await shaka.test.Util.delay(1.5);
997
1030
 
998
- const chapters = player.getChapters('en');
1031
+ const chapters = player.getChapters('es');
999
1032
  expect(chapters.length).toBe(3);
1000
1033
  const chapter1 = chapters[0];
1001
1034
  expect(chapter1.title).toBe('Chapter 1');