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.
- package/.github/config.yml +1 -0
- package/.github/workflows/release-please.yaml +6 -11
- package/AUTHORS +1 -0
- package/CHANGELOG.md +39 -0
- package/CONTRIBUTORS +1 -0
- package/README.md +4 -4
- package/build/misspellings.txt +1 -0
- package/build/test.py +1 -1
- package/demo/common/assets.js +1 -1
- package/demo/demo.less +0 -1
- package/demo/main.js +3 -3
- package/dist/controls.css +3 -3
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +3 -3
- package/dist/demo.compiled.debug.map +2 -2
- package/dist/demo.compiled.js +3 -3
- package/dist/demo.compiled.map +2 -2
- package/dist/demo.css +1 -1
- package/dist/demo.css.map +1 -1
- package/dist/deps.js +1 -1
- package/dist/locales.js +33 -33
- package/dist/receiver.compiled.debug.js +1 -1
- package/dist/receiver.compiled.debug.map +1 -1
- package/dist/receiver.compiled.js +1 -1
- package/dist/receiver.compiled.map +1 -1
- package/dist/shaka-player.compiled.d.ts +2 -1
- package/dist/shaka-player.compiled.debug.d.ts +2 -1
- package/dist/shaka-player.compiled.debug.externs.js +4 -2
- package/dist/shaka-player.compiled.debug.js +278 -267
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +4 -2
- package/dist/shaka-player.compiled.js +815 -806
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +2 -1
- package/dist/shaka-player.ui.debug.d.ts +2 -1
- package/dist/shaka-player.ui.debug.externs.js +4 -2
- package/dist/shaka-player.ui.debug.js +305 -293
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +4 -2
- package/dist/shaka-player.ui.js +966 -959
- package/dist/shaka-player.ui.map +4 -4
- package/docs/tutorials/upgrade.md +4 -2
- package/externs/shaka/player.js +15 -2
- package/karma.conf.js +4 -0
- package/lib/abr/simple_abr_manager.js +2 -1
- package/lib/dash/dash_parser.js +2 -2
- package/lib/hls/hls_parser.js +211 -82
- package/lib/media/drm_engine.js +19 -0
- package/lib/media/media_source_engine.js +91 -37
- package/lib/media/segment_index.js +26 -1
- package/lib/media/segment_reference.js +10 -3
- package/lib/media/streaming_engine.js +2 -3
- package/lib/offline/indexeddb/storage_mechanism.js +4 -2
- package/lib/player.js +165 -107
- package/lib/text/vtt_text_parser.js +7 -7
- package/lib/util/config_utils.js +25 -0
- package/lib/util/error.js +7 -0
- package/lib/util/manifest_parser_utils.js +1 -1
- package/lib/util/mime_utils.js +62 -6
- package/lib/util/periods.js +5 -4
- package/lib/util/player_configuration.js +14 -14
- package/lib/util/stream_utils.js +5 -2
- package/package.json +6 -3
- package/test/hls/hls_live_unit.js +79 -7
- package/test/hls/hls_parser_unit.js +102 -8
- package/test/hls/manifest_text_parser_unit.js +19 -0
- package/test/offline/indexeddb_storage_unit.js +1 -1
- package/test/offline/storage_compatibility_unit.js +1 -1
- package/test/player_external.js +7 -1
- package/test/player_integration.js +6 -10
- package/test/player_src_equals_integration.js +81 -83
- package/test/player_unit.js +98 -1
- package/test/test/boot.js +9 -0
- package/test/test/externs/filters.js +4 -0
- package/test/test/util/simple_fakes.js +31 -2
- package/test/text/vtt_text_parser_unit.js +64 -5
- package/test/util/mime_utils_unit.js +54 -0
- package/test/util/periods_unit.js +79 -0
- package/ui/less/containers.less +1 -1
- package/ui/less/general.less +1 -1
- package/ui/less/range_elements.less +1 -0
- package/ui/resolution_selection.js +1 -1
- package/.github/pull_request_template.md +0 -10
- 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
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
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
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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.
|
package/test/player_unit.js
CHANGED
|
@@ -124,7 +124,13 @@ describe('Player', () => {
|
|
|
124
124
|
|
|
125
125
|
player.createDrmEngine = () => drmEngine;
|
|
126
126
|
player.createNetworkingEngine = () => networkingEngine;
|
|
127
|
-
player.createPlayhead = () =>
|
|
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}
|
|
@@ -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')
|
|
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
|
|
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) => {
|
package/ui/less/containers.less
CHANGED
|
@@ -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. */
|
package/ui/less/general.less
CHANGED
|
@@ -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
|
|
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');
|