shaka-player 4.0.0 → 4.0.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 (84) hide show
  1. package/.github/config.yml +1 -0
  2. package/.github/workflows/release-please.yaml +6 -11
  3. package/AUTHORS +1 -0
  4. package/CHANGELOG.md +39 -0
  5. package/CONTRIBUTORS +1 -0
  6. package/README.md +4 -4
  7. package/build/misspellings.txt +1 -0
  8. package/build/test.py +1 -1
  9. package/demo/common/assets.js +1 -1
  10. package/demo/demo.less +0 -1
  11. package/demo/main.js +3 -3
  12. package/dist/controls.css +3 -3
  13. package/dist/controls.css.map +1 -1
  14. package/dist/demo.compiled.debug.js +3 -3
  15. package/dist/demo.compiled.debug.map +2 -2
  16. package/dist/demo.compiled.js +3 -3
  17. package/dist/demo.compiled.map +2 -2
  18. package/dist/demo.css +1 -1
  19. package/dist/demo.css.map +1 -1
  20. package/dist/deps.js +1 -1
  21. package/dist/locales.js +33 -33
  22. package/dist/receiver.compiled.debug.js +1 -1
  23. package/dist/receiver.compiled.debug.map +1 -1
  24. package/dist/receiver.compiled.js +1 -1
  25. package/dist/receiver.compiled.map +1 -1
  26. package/dist/shaka-player.compiled.d.ts +2 -1
  27. package/dist/shaka-player.compiled.debug.d.ts +2 -1
  28. package/dist/shaka-player.compiled.debug.externs.js +4 -2
  29. package/dist/shaka-player.compiled.debug.js +278 -267
  30. package/dist/shaka-player.compiled.debug.map +4 -4
  31. package/dist/shaka-player.compiled.externs.js +4 -2
  32. package/dist/shaka-player.compiled.js +815 -806
  33. package/dist/shaka-player.compiled.map +4 -4
  34. package/dist/shaka-player.ui.d.ts +2 -1
  35. package/dist/shaka-player.ui.debug.d.ts +2 -1
  36. package/dist/shaka-player.ui.debug.externs.js +4 -2
  37. package/dist/shaka-player.ui.debug.js +305 -293
  38. package/dist/shaka-player.ui.debug.map +4 -4
  39. package/dist/shaka-player.ui.externs.js +4 -2
  40. package/dist/shaka-player.ui.js +966 -959
  41. package/dist/shaka-player.ui.map +4 -4
  42. package/docs/tutorials/upgrade.md +4 -2
  43. package/externs/shaka/player.js +15 -2
  44. package/karma.conf.js +4 -0
  45. package/lib/abr/simple_abr_manager.js +2 -1
  46. package/lib/dash/dash_parser.js +2 -2
  47. package/lib/hls/hls_parser.js +211 -82
  48. package/lib/media/drm_engine.js +19 -0
  49. package/lib/media/media_source_engine.js +91 -37
  50. package/lib/media/segment_index.js +26 -1
  51. package/lib/media/segment_reference.js +10 -3
  52. package/lib/media/streaming_engine.js +2 -3
  53. package/lib/offline/indexeddb/storage_mechanism.js +4 -2
  54. package/lib/player.js +165 -107
  55. package/lib/text/vtt_text_parser.js +7 -7
  56. package/lib/util/config_utils.js +25 -0
  57. package/lib/util/error.js +7 -0
  58. package/lib/util/manifest_parser_utils.js +1 -1
  59. package/lib/util/mime_utils.js +62 -6
  60. package/lib/util/periods.js +5 -4
  61. package/lib/util/player_configuration.js +14 -14
  62. package/lib/util/stream_utils.js +5 -2
  63. package/package.json +6 -3
  64. package/test/hls/hls_live_unit.js +79 -7
  65. package/test/hls/hls_parser_unit.js +102 -8
  66. package/test/hls/manifest_text_parser_unit.js +19 -0
  67. package/test/offline/indexeddb_storage_unit.js +1 -1
  68. package/test/offline/storage_compatibility_unit.js +1 -1
  69. package/test/player_external.js +7 -1
  70. package/test/player_integration.js +6 -10
  71. package/test/player_src_equals_integration.js +81 -83
  72. package/test/player_unit.js +98 -1
  73. package/test/test/boot.js +9 -0
  74. package/test/test/externs/filters.js +4 -0
  75. package/test/test/util/simple_fakes.js +31 -2
  76. package/test/text/vtt_text_parser_unit.js +64 -5
  77. package/test/util/mime_utils_unit.js +54 -0
  78. package/test/util/periods_unit.js +79 -0
  79. package/ui/less/containers.less +1 -1
  80. package/ui/less/general.less +1 -1
  81. package/ui/less/range_elements.less +1 -0
  82. package/ui/resolution_selection.js +1 -1
  83. package/.github/pull_request_template.md +0 -10
  84. package/docs/tutorials/gsoc-ideas-list.md +0 -3
@@ -319,90 +319,88 @@ describe('Player Src Equals', () => {
319
319
  expect(newTrack).toBeTruthy();
320
320
  });
321
321
 
322
- it('add external chapters in vtt format', async () => {
323
- await loadWithSrcEquals(SMALL_MP4_CONTENT_URI, /* startTime= */ null);
324
-
325
- const locationUri = new goog.Uri(location.href);
326
- const partialUri1 = new goog.Uri('/base/test/test/assets/chapters.vtt');
327
- const absoluteUri1 = locationUri.resolve(partialUri1);
328
- await player.addChaptersTrack(absoluteUri1.toString(), 'en');
329
-
330
- await shaka.test.Util.delay(1.5);
331
-
332
- const chapters = player.getChapters('en');
333
- expect(chapters.length).toBe(3);
334
- const chapter1 = chapters[0];
335
- expect(chapter1.title).toBe('Chapter 1');
336
- expect(chapter1.startTime).toBe(0);
337
- expect(chapter1.endTime).toBe(5);
338
- const chapter2 = chapters[1];
339
- expect(chapter2.title).toBe('Chapter 2');
340
- expect(chapter2.startTime).toBe(5);
341
- expect(chapter2.endTime).toBe(10);
342
- const chapter3 = chapters[2];
343
- expect(chapter3.title).toBe('Chapter 3');
344
- expect(chapter3.startTime).toBe(10);
345
- expect(chapter3.endTime).toBe(20);
346
-
347
- const partialUri2 = new goog.Uri('/base/test/test/assets/chapters2.vtt');
348
- const absoluteUri2 = locationUri.resolve(partialUri2);
349
- await player.addChaptersTrack(absoluteUri2.toString(), 'en');
350
-
351
- await shaka.test.Util.delay(1.5);
352
-
353
- const chaptersUpdated = player.getChapters('en');
354
- expect(chaptersUpdated.length).toBe(6);
355
- const chapterUpdated1 = chaptersUpdated[0];
356
- expect(chapterUpdated1.title).toBe('Chapter 1');
357
- expect(chapterUpdated1.startTime).toBe(0);
358
- expect(chapterUpdated1.endTime).toBe(5);
359
- const chapterUpdated2 = chaptersUpdated[1];
360
- expect(chapterUpdated2.title).toBe('Chapter 2');
361
- expect(chapterUpdated2.startTime).toBe(5);
362
- expect(chapterUpdated2.endTime).toBe(10);
363
- const chapterUpdated3 = chaptersUpdated[2];
364
- expect(chapterUpdated3.title).toBe('Chapter 3');
365
- expect(chapterUpdated3.startTime).toBe(10);
366
- expect(chapterUpdated3.endTime).toBe(20);
367
- const chapterUpdated4 = chaptersUpdated[3];
368
- expect(chapterUpdated4.title).toBe('Chapter 4');
369
- expect(chapterUpdated4.startTime).toBe(20);
370
- expect(chapterUpdated4.endTime).toBe(30);
371
- const chapterUpdated5 = chaptersUpdated[4];
372
- expect(chapterUpdated5.title).toBe('Chapter 5');
373
- expect(chapterUpdated5.startTime).toBe(30);
374
- expect(chapterUpdated5.endTime).toBe(40);
375
- const chapterUpdated6 = chaptersUpdated[5];
376
- expect(chapterUpdated6.title).toBe('Chapter 6');
377
- expect(chapterUpdated6.startTime).toBe(40);
378
- expect(chapterUpdated6.endTime).toBe(61.349);
379
- });
380
-
381
- it('add external chapters in srt format', async () => {
382
- await loadWithSrcEquals(SMALL_MP4_CONTENT_URI, /* startTime= */ null);
383
-
384
- const locationUri = new goog.Uri(location.href);
385
- const partialUri = new goog.Uri('/base/test/test/assets/chapters.srt');
386
- const absoluteUri = locationUri.resolve(partialUri);
387
- await player.addChaptersTrack(absoluteUri.toString(), 'es');
388
-
389
- await shaka.test.Util.delay(1.5);
322
+ describe('addChaptersTrack', () => {
323
+ it('adds external chapters in vtt format', async () => {
324
+ await loadWithSrcEquals(SMALL_MP4_CONTENT_URI, /* startTime= */ null);
325
+
326
+ const locationUri = new goog.Uri(location.href);
327
+ const partialUri1 = new goog.Uri('/base/test/test/assets/chapters.vtt');
328
+ const absoluteUri1 = locationUri.resolve(partialUri1);
329
+ await player.addChaptersTrack(absoluteUri1.toString(), 'en');
330
+
331
+ // Data should be available as soon as addChaptersTrack resolves.
332
+ // See https://github.com/shaka-project/shaka-player/issues/4186
333
+ const chapters = player.getChapters('en');
334
+ expect(chapters.length).toBe(3);
335
+ const chapter1 = chapters[0];
336
+ expect(chapter1.title).toBe('Chapter 1');
337
+ expect(chapter1.startTime).toBe(0);
338
+ expect(chapter1.endTime).toBe(5);
339
+ const chapter2 = chapters[1];
340
+ expect(chapter2.title).toBe('Chapter 2');
341
+ expect(chapter2.startTime).toBe(5);
342
+ expect(chapter2.endTime).toBe(10);
343
+ const chapter3 = chapters[2];
344
+ expect(chapter3.title).toBe('Chapter 3');
345
+ expect(chapter3.startTime).toBe(10);
346
+ expect(chapter3.endTime).toBe(20);
347
+
348
+ const partialUri2 = new goog.Uri('/base/test/test/assets/chapters2.vtt');
349
+ const absoluteUri2 = locationUri.resolve(partialUri2);
350
+ await player.addChaptersTrack(absoluteUri2.toString(), 'en');
351
+
352
+ const chaptersUpdated = player.getChapters('en');
353
+ expect(chaptersUpdated.length).toBe(6);
354
+ const chapterUpdated1 = chaptersUpdated[0];
355
+ expect(chapterUpdated1.title).toBe('Chapter 1');
356
+ expect(chapterUpdated1.startTime).toBe(0);
357
+ expect(chapterUpdated1.endTime).toBe(5);
358
+ const chapterUpdated2 = chaptersUpdated[1];
359
+ expect(chapterUpdated2.title).toBe('Chapter 2');
360
+ expect(chapterUpdated2.startTime).toBe(5);
361
+ expect(chapterUpdated2.endTime).toBe(10);
362
+ const chapterUpdated3 = chaptersUpdated[2];
363
+ expect(chapterUpdated3.title).toBe('Chapter 3');
364
+ expect(chapterUpdated3.startTime).toBe(10);
365
+ expect(chapterUpdated3.endTime).toBe(20);
366
+ const chapterUpdated4 = chaptersUpdated[3];
367
+ expect(chapterUpdated4.title).toBe('Chapter 4');
368
+ expect(chapterUpdated4.startTime).toBe(20);
369
+ expect(chapterUpdated4.endTime).toBe(30);
370
+ const chapterUpdated5 = chaptersUpdated[4];
371
+ expect(chapterUpdated5.title).toBe('Chapter 5');
372
+ expect(chapterUpdated5.startTime).toBe(30);
373
+ expect(chapterUpdated5.endTime).toBe(40);
374
+ const chapterUpdated6 = chaptersUpdated[5];
375
+ expect(chapterUpdated6.title).toBe('Chapter 6');
376
+ expect(chapterUpdated6.startTime).toBe(40);
377
+ expect(chapterUpdated6.endTime).toBe(61.349);
378
+ });
390
379
 
391
- const chapters = player.getChapters('es');
392
- expect(chapters.length).toBe(3);
393
- const chapter1 = chapters[0];
394
- expect(chapter1.title).toBe('Chapter 1');
395
- expect(chapter1.startTime).toBe(0);
396
- expect(chapter1.endTime).toBe(5);
397
- const chapter2 = chapters[1];
398
- expect(chapter2.title).toBe('Chapter 2');
399
- expect(chapter2.startTime).toBe(5);
400
- expect(chapter2.endTime).toBe(30);
401
- const chapter3 = chapters[2];
402
- expect(chapter3.title).toBe('Chapter 3');
403
- expect(chapter3.startTime).toBe(30);
404
- expect(chapter3.endTime).toBe(61.349);
405
- });
380
+ it('add external chapters in srt format', async () => {
381
+ await loadWithSrcEquals(SMALL_MP4_CONTENT_URI, /* startTime= */ null);
382
+
383
+ const locationUri = new goog.Uri(location.href);
384
+ const partialUri = new goog.Uri('/base/test/test/assets/chapters.srt');
385
+ const absoluteUri = locationUri.resolve(partialUri);
386
+ await player.addChaptersTrack(absoluteUri.toString(), 'es');
387
+
388
+ const chapters = player.getChapters('es');
389
+ expect(chapters.length).toBe(3);
390
+ const chapter1 = chapters[0];
391
+ expect(chapter1.title).toBe('Chapter 1');
392
+ expect(chapter1.startTime).toBe(0);
393
+ expect(chapter1.endTime).toBe(5);
394
+ const chapter2 = chapters[1];
395
+ expect(chapter2.title).toBe('Chapter 2');
396
+ expect(chapter2.startTime).toBe(5);
397
+ expect(chapter2.endTime).toBe(30);
398
+ const chapter3 = chapters[2];
399
+ expect(chapter3.title).toBe('Chapter 3');
400
+ expect(chapter3.startTime).toBe(30);
401
+ expect(chapter3.endTime).toBe(61.349);
402
+ });
403
+ }); // describe('addChaptersTrack')
406
404
 
407
405
  // Since we are not in-charge of streaming, calling |retryStreaming| should
408
406
  // have no effect.
@@ -124,7 +124,13 @@ describe('Player', () => {
124
124
 
125
125
  player.createDrmEngine = () => drmEngine;
126
126
  player.createNetworkingEngine = () => networkingEngine;
127
- player.createPlayhead = () => playhead;
127
+ player.createPlayhead = (startTime) => {
128
+ const callableSetStartTime =
129
+ shaka.test.Util.spyFunc(playhead.setStartTime);
130
+ callableSetStartTime(startTime);
131
+ playhead.setStartTime.calls.reset();
132
+ return playhead;
133
+ };
128
134
  player.createMediaSourceEngine = () => mediaSourceEngine;
129
135
  player.createStreamingEngine = () => streamingEngine;
130
136
  }
@@ -156,6 +162,7 @@ describe('Player', () => {
156
162
  window.MediaSource.isTypeSupported = originalIsTypeSupported;
157
163
  shaka.media.ManifestParser.unregisterParserByMime(fakeMimeType);
158
164
  navigator.mediaCapabilities.decodingInfo = originalDecodingInfo;
165
+ onError.calls.reset();
159
166
  }
160
167
  });
161
168
 
@@ -2658,6 +2665,51 @@ describe('Player', () => {
2658
2665
  expect(activeVariant.id).toBe(1);
2659
2666
  });
2660
2667
 
2668
+ // Regression test for https://github.com/shaka-project/shaka-player/issues/4190
2669
+ it('throws only one RESTRICTIONS_CANNOT_BE_MET error', async () => {
2670
+ manifest = shaka.test.ManifestGenerator.generate((manifest) => {
2671
+ manifest.addVariant(0, (variant) => {
2672
+ variant.addVideo(1, (stream) => {
2673
+ stream.keyIds = new Set(['abc']);
2674
+ });
2675
+ });
2676
+ });
2677
+
2678
+ // Check that error is RESTRICTIONS_CANNOT_BE_MET
2679
+ onError.and.callFake((e) => {
2680
+ const error = e.detail;
2681
+ shaka.test.Util.expectToEqualError(
2682
+ error,
2683
+ new shaka.util.Error(
2684
+ shaka.util.Error.Severity.CRITICAL,
2685
+ shaka.util.Error.Category.MANIFEST,
2686
+ shaka.util.Error.Code.RESTRICTIONS_CANNOT_BE_MET, {
2687
+ hasAppRestrictions: false,
2688
+ missingKeys: ['abc'],
2689
+ restrictedKeyStatuses: [],
2690
+ }));
2691
+ });
2692
+
2693
+ await player.load(fakeManifestUri, 0, fakeMimeType);
2694
+ const activeVariant = getActiveVariantTrack();
2695
+ expect(activeVariant.id).toBe(0);
2696
+
2697
+ abrManager.chooseIndex = 0;
2698
+ abrManager.chooseVariant.calls.reset();
2699
+
2700
+ onKeyStatus({'abc': 'output-restricted'});
2701
+
2702
+ // Ensure that RESTRICTIONS_CANNOT_BE_MET is thrown once
2703
+ expect(onError).toHaveBeenCalledTimes(1);
2704
+
2705
+ // It does not call chooseVariant
2706
+ expect(abrManager.chooseVariant).not.toHaveBeenCalled();
2707
+
2708
+ // The first variant is disallowed.
2709
+ expect(manifest.variants[0].id).toBe(0);
2710
+ expect(manifest.variants[0].allowedByKeySystem).toBe(false);
2711
+ });
2712
+
2661
2713
  it('doesn\'t switch if the active stream isn\'t restricted', async () => {
2662
2714
  manifest = shaka.test.ManifestGenerator.generate((manifest) => {
2663
2715
  manifest.addVariant(0, (variant) => {
@@ -3664,6 +3716,51 @@ describe('Player', () => {
3664
3716
  });
3665
3717
  });
3666
3718
 
3719
+ describe('config streaming.startAtSegmentBoundary', () => {
3720
+ // Test for https://github.com/shaka-project/shaka-player/issues/4188
3721
+ // In that issue, using streaming.startAtSegmentBoundary in v4.0.0 caused
3722
+ // an exception due to the use of a removed method.
3723
+ it('adjusts start time', async () => {
3724
+ player.configure('streaming.startAtSegmentBoundary', true);
3725
+
3726
+ // In order to adjust the start time to a segment boundary, we need
3727
+ // segment descriptions in the segment index. So create a non-default
3728
+ // fake manifest.
3729
+ const timeline = new shaka.media.PresentationTimeline(300, 0);
3730
+ timeline.setStatic(true);
3731
+ // This duration is used by useSegmentTemplate below to decide how many
3732
+ // references to generate.
3733
+ timeline.setDuration(300);
3734
+ manifest = shaka.test.ManifestGenerator.generate((manifest) => {
3735
+ manifest.presentationTimeline = timeline;
3736
+ manifest.addVariant(0, (variant) => {
3737
+ variant.addVideo(1, (stream) => {
3738
+ stream.useSegmentTemplate(
3739
+ '$Number$.mp4', /* segmentDuration= */ 10);
3740
+ });
3741
+ });
3742
+ });
3743
+
3744
+ await player.load(fakeManifestUri, 12, fakeMimeType);
3745
+
3746
+ expect(playhead.setStartTime).toHaveBeenCalledWith(10);
3747
+ });
3748
+
3749
+ it('does not fail with no segments', async () => {
3750
+ player.configure('streaming.startAtSegmentBoundary', true);
3751
+
3752
+ // Without useSegmentTemplate in the fake manifest, the call to
3753
+ // getIteratorForTime() in Player produces a null iterator. Using the
3754
+ // default fake manifest should cover that case. But just to make sure,
3755
+ // verify that we have no segments before calling load().
3756
+ const variant = manifest.variants[0];
3757
+ expect(variant.video.segmentIndex.getIteratorForTime(0)).toBeNull();
3758
+ expect(variant.audio.segmentIndex.getIteratorForTime(0)).toBeNull();
3759
+
3760
+ await player.load(fakeManifestUri, 0, fakeMimeType);
3761
+ });
3762
+ });
3763
+
3667
3764
  /**
3668
3765
  * Gets the currently active variant track.
3669
3766
  * @return {shaka.extern.Track}
package/test/test/boot.js CHANGED
@@ -242,6 +242,15 @@ window.xfilterDescribe = (describeName, cond, describeBody) => {
242
242
  window['describe'] = oldDescribe;
243
243
  };
244
244
 
245
+ /**
246
+ * A very short alias for filtering offline integration tests.
247
+ *
248
+ * @return {boolean}
249
+ */
250
+ window.offlineSupported = () => {
251
+ return shaka.offline.StorageMuxer.support();
252
+ };
253
+
245
254
  /**
246
255
  * Load node modules used in testing and install them into window.
247
256
  * @return {!Promise}
@@ -38,3 +38,7 @@ var filterDescribe = function(name, cond, callback) {};
38
38
  * @param {function()} callback
39
39
  */
40
40
  var xfilterDescribe = function(name, cond, callback) {};
41
+
42
+
43
+ /** @return {boolean} */
44
+ var offlineSupported = function() {};
@@ -152,9 +152,27 @@ shaka.test.FakeVideo = class {
152
152
  constructor(currentTime) {
153
153
  /** @const {!Object.<string, !Function>} */
154
154
  this.on = {}; // event listeners
155
+
155
156
  /** @type {!Array.<!TextTrack>} */
156
157
  this.textTracks = [];
157
158
 
159
+ // In a real video element, textTracks is an event target.
160
+ // Since Player listens to events on textTracks, we need to fake that
161
+ // interface.
162
+ this.textTracksEventTarget = new shaka.util.FakeEventTarget();
163
+ this.textTracks.addEventListener =
164
+ // eslint-disable-next-line no-restricted-syntax
165
+ this.textTracksEventTarget.addEventListener.bind(
166
+ this.textTracksEventTarget);
167
+ this.textTracks.removeEventListener =
168
+ // eslint-disable-next-line no-restricted-syntax
169
+ this.textTracksEventTarget.removeEventListener.bind(
170
+ this.textTracksEventTarget);
171
+ this.textTracks.dispatchEvent =
172
+ // eslint-disable-next-line no-restricted-syntax
173
+ this.textTracksEventTarget.dispatchEvent.bind(
174
+ this.textTracksEventTarget);
175
+
158
176
  this.currentTime = currentTime || 0;
159
177
  this.readyState = 0;
160
178
  this.playbackRate = 1;
@@ -173,6 +191,10 @@ shaka.test.FakeVideo = class {
173
191
  jasmine.createSpy('addTextTrack').and.callFake((kind, id) => {
174
192
  const track = new shaka.test.FakeTextTrack();
175
193
  this.textTracks.push(track);
194
+
195
+ const trackEvent = new shaka.util.FakeEvent('addtrack', {track});
196
+ this.textTracksEventTarget.dispatchEvent(trackEvent);
197
+
176
198
  return track;
177
199
  });
178
200
 
@@ -308,11 +330,18 @@ shaka.test.FakePlayhead = class {
308
330
  /** @type {!jasmine.Spy} */
309
331
  this.setRebufferingGoal = jasmine.createSpy('setRebufferingGoal');
310
332
 
333
+ /** @private {number} */
334
+ this.startTime_ = 0;
335
+
311
336
  /** @type {!jasmine.Spy} */
312
- this.setStartTime = jasmine.createSpy('setStartTime');
337
+ this.setStartTime = jasmine.createSpy('setStartTime')
338
+ .and.callFake((value) => {
339
+ this.startTime_ = value;
340
+ });
313
341
 
314
342
  /** @type {!jasmine.Spy} */
315
- this.getTime = jasmine.createSpy('getTime').and.returnValue(0);
343
+ this.getTime = jasmine.createSpy('getTime')
344
+ .and.callFake(() => this.startTime_);
316
345
 
317
346
  /** @type {!jasmine.Spy} */
318
347
  this.setBuffering = jasmine.createSpy('setBuffering');
@@ -535,7 +535,61 @@ describe('VttTextParser', () => {
535
535
  'Test\n\n' +
536
536
  '00:00:40.000 --> 00:00:50.000 line:-1\n' +
537
537
  'Test2',
538
- {periodStart: 0, segmentStart: 25, segmentEnd: 65, vttOffset: 0});
538
+ {periodStart: 0, segmentStart: 25, segmentEnd: 65, vttOffset: 0},
539
+ /* sequenceMode= */ true);
540
+ });
541
+
542
+ it('ignores X-TIMESTAMP-MAP header if not in sequence mode', () => {
543
+ verifyHelper(
544
+ [
545
+ {startTime: 20, endTime: 40, payload: 'Test'},
546
+ {startTime: 40, endTime: 50, payload: 'Test2'},
547
+ ],
548
+ 'WEBVTT\n' +
549
+ 'X-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:01:00:00.000\n\n' +
550
+ '00:00:20.000 --> 00:00:40.000 line:0\n' +
551
+ 'Test\n\n' +
552
+ '00:00:40.000 --> 00:00:50.000 line:-1\n' +
553
+ 'Test2',
554
+ {periodStart: 0, segmentStart: 25, segmentEnd: 65, vttOffset: 0},
555
+ /* sequenceMode= */ false);
556
+ });
557
+
558
+ it('parses X-TIMESTAMP-MAP header with non-zero local base', () => {
559
+ verifyHelper(
560
+ [
561
+ {startTime: 1800, endTime: 1810, payload: 'Test'},
562
+ {startTime: 1820, endTime: 1830, payload: 'Test2'},
563
+ ],
564
+ // 162000000 = 30 * 60 * 90k = 30 minutes for the TS part of the map.
565
+ // The local (VTT) part of the map is 1 hour.
566
+ // So text times of 1 hour map to media times of 30 minutes.
567
+ 'WEBVTT\n' +
568
+ 'X-TIMESTAMP-MAP=MPEGTS:162000000,LOCAL:01:00:00.000\n\n' +
569
+ '01:00:00.000 --> 01:00:10.000 line:0\n' +
570
+ 'Test\n\n' +
571
+ '01:00:20.000 --> 01:00:30.000 line:-1\n' +
572
+ 'Test2',
573
+ {periodStart: 0, segmentStart: 25, segmentEnd: 65, vttOffset: 0},
574
+ /* sequenceMode= */ true);
575
+ });
576
+
577
+ it('combines X-TIMESTAMP-MAP header with periodStart', () => {
578
+ verifyHelper(
579
+ [
580
+ {startTime: 130, endTime: 150, payload: 'Test'},
581
+ {startTime: 150, endTime: 160, payload: 'Test2'},
582
+ ],
583
+ // 900000 = 10 sec, so expect every timestamp to be 10
584
+ // seconds ahead of what is specified.
585
+ 'WEBVTT\n' +
586
+ 'X-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000\n\n' +
587
+ '00:00:20.000 --> 00:00:40.000 line:0\n' +
588
+ 'Test\n\n' +
589
+ '00:00:40.000 --> 00:00:50.000 line:-1\n' +
590
+ 'Test2',
591
+ {periodStart: 100, segmentStart: 25, segmentEnd: 65, vttOffset: 0},
592
+ /* sequenceMode= */ true);
539
593
  });
540
594
 
541
595
  it('handles timestamp rollover with X-TIMESTAMP-MAP header', () => {
@@ -551,7 +605,8 @@ describe('VttTextParser', () => {
551
605
  'Test',
552
606
  // Non-null segmentStart takes precedence over X-TIMESTAMP-MAP.
553
607
  // This protects us from rollover in the MPEGTS field.
554
- {periodStart: 0, segmentStart: 95440, segmentEnd: 95550, vttOffset: 0});
608
+ {periodStart: 0, segmentStart: 95440, segmentEnd: 95550, vttOffset: 0},
609
+ /* sequenceMode= */ true);
555
610
 
556
611
  verifyHelper(
557
612
  [
@@ -564,7 +619,8 @@ describe('VttTextParser', () => {
564
619
  'X-TIMESTAMP-MAP=MPEGTS:9745408,LOCAL:00:00:00.000\n\n' +
565
620
  '00:00:00.000 --> 00:00:02.000 line:0\n' +
566
621
  'Test2',
567
- {periodStart: 0, segmentStart: 95550, segmentEnd: 95560, vttOffset: 0});
622
+ {periodStart: 0, segmentStart: 95550, segmentEnd: 95560, vttOffset: 0},
623
+ /* sequenceMode= */ true);
568
624
  });
569
625
 
570
626
  it('supports global style blocks', () => {
@@ -978,11 +1034,14 @@ describe('VttTextParser', () => {
978
1034
  * @param {!Array} cues
979
1035
  * @param {string} text
980
1036
  * @param {shaka.extern.TextParser.TimeContext} time
1037
+ * @param {boolean=} sequenceMode
981
1038
  */
982
- function verifyHelper(cues, text, time) {
1039
+ function verifyHelper(cues, text, time, sequenceMode = false) {
983
1040
  const data =
984
1041
  shaka.util.BufferUtils.toUint8(shaka.util.StringUtils.toUTF8(text));
985
- const result = new shaka.text.VttTextParser().parseMedia(data, time);
1042
+ const parser = new shaka.text.VttTextParser();
1043
+ parser.setSequenceMode(sequenceMode);
1044
+ const result = parser.parseMedia(data, time);
986
1045
 
987
1046
  const expected = cues.map((cue) => {
988
1047
  if (cue.nestedCues) {
@@ -0,0 +1,54 @@
1
+ /*! @license
2
+ * Shaka Player
3
+ * Copyright 2016 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ describe('MimeUtils', () => {
8
+ const getNormalizedCodec = (codecs) =>
9
+ shaka.util.MimeUtils.getNormalizedCodec(codecs);
10
+
11
+ it('normalizes codecs', () => {
12
+ expect(getNormalizedCodec('mp4a.66')).toBe('aac');
13
+ expect(getNormalizedCodec('mp4a.67')).toBe('aac');
14
+ expect(getNormalizedCodec('mp4a.68')).toBe('aac');
15
+
16
+ expect(getNormalizedCodec('mp3')).toBe('mp3');
17
+ expect(getNormalizedCodec('mp4a.69')).toBe('mp3');
18
+ expect(getNormalizedCodec('mp4a.6B')).toBe('mp3');
19
+ expect(getNormalizedCodec('mp4a.6b')).toBe('mp3');
20
+
21
+ expect(getNormalizedCodec('mp4a.40.2')).toBe('aac');
22
+ expect(getNormalizedCodec('mp4a.40.02')).toBe('aac');
23
+ expect(getNormalizedCodec('mp4a.40.5')).toBe('aac');
24
+ expect(getNormalizedCodec('mp4a.40.05')).toBe('aac');
25
+ expect(getNormalizedCodec('mp4a.40.29')).toBe('aac');
26
+ expect(getNormalizedCodec('mp4a.40.42')).toBe('aac');
27
+
28
+ expect(getNormalizedCodec('ac-3')).toBe('ac-3');
29
+ expect(getNormalizedCodec('mp4a.a5')).toBe('ac-3');
30
+ expect(getNormalizedCodec('mp4a.A5')).toBe('ac-3');
31
+
32
+ expect(getNormalizedCodec('ec-3')).toBe('ec-3');
33
+ expect(getNormalizedCodec('mp4a.a6')).toBe('ec-3');
34
+ expect(getNormalizedCodec('mp4a.A6')).toBe('ec-3');
35
+
36
+ expect(getNormalizedCodec('dtsc')).toBe('dtsc');
37
+ expect(getNormalizedCodec('mp4a.a9')).toBe('dtsc');
38
+
39
+ expect(getNormalizedCodec('dtsx')).toBe('dtsx');
40
+ expect(getNormalizedCodec('mp4a.b2')).toBe('dtsx');
41
+
42
+ expect(getNormalizedCodec('vp8')).toBe('vp8');
43
+ expect(getNormalizedCodec('vp8.0')).toBe('vp8');
44
+
45
+ expect(getNormalizedCodec('avc1')).toBe('avc');
46
+ expect(getNormalizedCodec('avc3')).toBe('avc');
47
+
48
+ expect(getNormalizedCodec('hvc1')).toBe('hevc');
49
+ expect(getNormalizedCodec('hev1')).toBe('hevc');
50
+
51
+ expect(getNormalizedCodec('dvh1.05')).toBe('dovi');
52
+ expect(getNormalizedCodec('dvhe.05')).toBe('dovi');
53
+ });
54
+ });
@@ -996,6 +996,85 @@ describe('PeriodCombiner', () => {
996
996
  expect(audio2.originalId).toBe('2,4');
997
997
  });
998
998
 
999
+ it('Matches streams with related codecs', async () => {
1000
+ const stream1 = makeVideoStream(1080);
1001
+ stream1.originalId = '1';
1002
+ stream1.bandwidth = 120000;
1003
+ stream1.codecs = 'hvc1.1.4.L126.B0';
1004
+
1005
+ const stream2 = makeVideoStream(1080);
1006
+ stream2.originalId = '2';
1007
+ stream2.bandwidth = 120000;
1008
+ stream2.codecs = 'hev1.2.4.L123.B0';
1009
+
1010
+ const stream3 = makeVideoStream(1080);
1011
+ stream3.originalId = '3';
1012
+ stream3.bandwidth = 120000;
1013
+ stream3.codecs = 'dvhe.05.01';
1014
+
1015
+ const stream4 = makeVideoStream(1080);
1016
+ stream4.originalId = '4';
1017
+ stream4.bandwidth = 120000;
1018
+ stream4.codecs = 'dvh1.05.01';
1019
+
1020
+ const stream5 = makeVideoStream(1080);
1021
+ stream5.originalId = '5';
1022
+ stream5.bandwidth = 120000;
1023
+ stream5.codecs = 'avc1.42001f';
1024
+
1025
+ const stream6 = makeVideoStream(1080);
1026
+ stream6.originalId = '6';
1027
+ stream6.bandwidth = 120000;
1028
+ stream6.codecs = 'avc3.42001f';
1029
+
1030
+ const stream7 = makeVideoStream(1080);
1031
+ stream7.originalId = '7';
1032
+ stream7.bandwidth = 120000;
1033
+ stream7.codecs = 'vp09.00.10.08';
1034
+
1035
+ const stream8 = makeVideoStream(1080);
1036
+ stream8.originalId = '8';
1037
+ stream8.bandwidth = 120000;
1038
+ stream8.codecs = 'vp09.01.20.08.01';
1039
+
1040
+ /** @type {!Array.<shaka.util.PeriodCombiner.Period>} */
1041
+ const periods = [
1042
+ {
1043
+ id: '0',
1044
+ videoStreams: [
1045
+ stream1, stream3, stream5, stream7,
1046
+ ],
1047
+ audioStreams: [],
1048
+ textStreams: [],
1049
+ imageStreams: [],
1050
+ },
1051
+ {
1052
+ id: '1',
1053
+ videoStreams: [
1054
+ stream2, stream4, stream6, stream8,
1055
+ ],
1056
+ audioStreams: [],
1057
+ textStreams: [],
1058
+ imageStreams: [],
1059
+ },
1060
+ ];
1061
+
1062
+ await combiner.combinePeriods(periods, /* isDynamic= */ true);
1063
+ const variants = combiner.getVariants();
1064
+ expect(variants.length).toBe(4);
1065
+ // We can use the originalId field to see what each track is composed of.
1066
+ const video1 = variants[0].video;
1067
+ expect(video1.originalId).toBe('1,2');
1068
+
1069
+ const video2 = variants[1].video;
1070
+ expect(video2.originalId).toBe('3,4');
1071
+
1072
+ const video3 = variants[2].video;
1073
+ expect(video3.originalId).toBe('5,6');
1074
+
1075
+ const video4 = variants[3].video;
1076
+ expect(video4.originalId).toBe('7,8');
1077
+ });
999
1078
 
1000
1079
  it('Matches streams with most roles in common', async () => {
1001
1080
  const makeAudioStreamWithRoles = (roles) => {
@@ -161,7 +161,7 @@
161
161
  /* Consistent margins (external) and padding (internal) between controls. */
162
162
  .bottom-panels-elements-margin();
163
163
 
164
- padding: 0;
164
+ padding: 0 5px;
165
165
 
166
166
  /* Transparent backgrounds, no borders, and a pointer when you mouse over
167
167
  * them. */
@@ -37,7 +37,7 @@
37
37
  }
38
38
 
39
39
  .bottom-panels-elements-margin() {
40
- margin: 1px 6px;
40
+ margin: 1px;
41
41
  }
42
42
 
43
43
  /* For containers which host elements overlaying other things. */
@@ -140,6 +140,7 @@
140
140
 
141
141
  .shaka-volume-bar-container {
142
142
  width: 100px;
143
+ padding: 0;
143
144
  }
144
145
 
145
146
  .shaka-range-element {
@@ -90,7 +90,7 @@ shaka.ui.ResolutionSelection = class extends shaka.ui.SettingsMenu {
90
90
  return otherIdx == idx;
91
91
  });
92
92
 
93
- // Sort the tracks by height or bandwith depending on content type.
93
+ // Sort the tracks by height or bandwidth depending on content type.
94
94
  if (this.player.isAudioOnly()) {
95
95
  tracks.sort((t1, t2) => {
96
96
  goog.asserts.assert(t1.bandwidth != null, 'Null bandwidth');