shaka-player 4.0.0 → 4.1.0
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/CHANGELOG.md +33 -0
- package/CONTRIBUTORS +1 -0
- package/README.md +4 -4
- package/build/misspellings.txt +1 -0
- package/demo/common/message_ids.js +1 -0
- package/demo/config.js +3 -1
- package/demo/demo.less +0 -1
- package/demo/locales/en.json +1 -0
- package/demo/locales/source.json +4 -0
- package/dist/controls.css +3 -3
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +4 -4
- package/dist/demo.compiled.debug.map +2 -2
- package/dist/demo.compiled.js +4 -4
- package/dist/demo.compiled.map +2 -2
- package/dist/demo.css +1 -1
- package/dist/demo.css.map +1 -1
- package/dist/deps.js +3 -3
- package/dist/locales.js +32 -32
- package/dist/receiver.compiled.debug.js +2 -2
- package/dist/receiver.compiled.debug.map +2 -2
- package/dist/receiver.compiled.js +2 -2
- package/dist/receiver.compiled.map +2 -2
- package/dist/shaka-player.compiled.d.ts +15 -8
- package/dist/shaka-player.compiled.debug.d.ts +15 -8
- package/dist/shaka-player.compiled.debug.externs.js +11 -2
- package/dist/shaka-player.compiled.debug.js +266 -253
- package/dist/shaka-player.compiled.debug.map +3 -3
- package/dist/shaka-player.compiled.externs.js +11 -2
- package/dist/shaka-player.compiled.js +792 -784
- package/dist/shaka-player.compiled.map +3 -3
- package/dist/shaka-player.ui.d.ts +16 -9
- package/dist/shaka-player.ui.debug.d.ts +16 -9
- package/dist/shaka-player.ui.debug.externs.js +11 -2
- package/dist/shaka-player.ui.debug.js +299 -282
- package/dist/shaka-player.ui.debug.map +3 -3
- package/dist/shaka-player.ui.externs.js +11 -2
- package/dist/shaka-player.ui.js +948 -941
- package/dist/shaka-player.ui.map +3 -3
- package/docs/design/chromecast.md +3 -1
- package/docs/tutorials/drm-config.md +16 -0
- package/docs/tutorials/ui.md +26 -0
- package/docs/tutorials/upgrade.md +4 -2
- package/externs/chromecast.js +9 -2
- package/externs/shaka/manifest.js +7 -0
- package/externs/shaka/player.js +32 -2
- package/externs/shaka/text.js +7 -0
- package/karma.conf.js +4 -0
- package/lib/abr/simple_abr_manager.js +2 -1
- package/lib/cast/cast_proxy.js +19 -8
- package/lib/cast/cast_receiver.js +3 -3
- package/lib/cast/cast_sender.js +12 -2
- package/lib/dash/dash_parser.js +2 -2
- package/lib/hls/hls_parser.js +46 -15
- package/lib/media/drm_engine.js +36 -8
- package/lib/media/gap_jumping_controller.js +23 -1
- package/lib/media/media_source_engine.js +91 -37
- package/lib/media/playhead.js +47 -4
- package/lib/media/stall_detector.js +19 -2
- package/lib/media/streaming_engine.js +9 -3
- package/lib/offline/indexeddb/storage_mechanism.js +4 -2
- package/lib/offline/manifest_converter.js +1 -0
- package/lib/player.js +339 -175
- package/lib/text/cue.js +6 -0
- package/lib/text/ui_text_displayer.js +1 -0
- package/lib/text/vtt_text_parser.js +11 -7
- package/lib/util/config_utils.js +25 -0
- package/lib/util/error.js +13 -0
- package/lib/util/fake_event.js +39 -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 +16 -14
- package/lib/util/stats.js +28 -1
- package/lib/util/stream_utils.js +11 -2
- package/package.json +5 -2
- package/test/cast/cast_proxy_unit.js +3 -1
- package/test/cast/cast_sender_unit.js +21 -6
- package/test/demo/demo_unit.js +2 -1
- package/test/hls/hls_live_unit.js +79 -7
- package/test/hls/manifest_text_parser_unit.js +19 -0
- package/test/media/adaptation_set_unit.js +1 -0
- package/test/media/playhead_unit.js +114 -25
- package/test/media/stall_detector_unit.js +20 -2
- package/test/media/streaming_engine_integration.js +2 -1
- package/test/media/streaming_engine_unit.js +1 -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 +9 -10
- package/test/player_src_equals_integration.js +81 -83
- package/test/player_unit.js +240 -2
- package/test/test/boot.js +9 -0
- package/test/test/externs/filters.js +4 -0
- package/test/test/util/manifest_generator.js +2 -0
- package/test/test/util/simple_fakes.js +45 -2
- package/test/test/util/streaming_engine_util.js +1 -0
- package/test/text/vtt_text_parser_unit.js +68 -5
- package/test/ui/ui_unit.js +18 -5
- package/test/util/mime_utils_unit.js +54 -0
- package/test/util/periods_unit.js +79 -0
- package/test/util/stream_utils_unit.js +108 -0
- package/ui/controls.js +12 -8
- package/ui/externs/ui.js +10 -1
- 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/ui/statistics_button.js +10 -0
- package/ui/ui.js +12 -1
- package/.github/pull_request_template.md +0 -10
- package/docs/tutorials/gsoc-ideas-list.md +0 -3
package/test/player_unit.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
describe('Player', () => {
|
|
8
8
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
9
|
+
const StreamUtils = shaka.util.StreamUtils;
|
|
9
10
|
const Util = shaka.test.Util;
|
|
10
11
|
|
|
11
12
|
const originalLogError = shaka.log.error;
|
|
@@ -29,6 +30,8 @@ describe('Player', () => {
|
|
|
29
30
|
let player;
|
|
30
31
|
/** @type {!shaka.test.FakeAbrManager} */
|
|
31
32
|
let abrManager;
|
|
33
|
+
/** @type {function(!shaka.util.Error)} */
|
|
34
|
+
let onErrorCallback;
|
|
32
35
|
|
|
33
36
|
/** @type {!shaka.test.FakeNetworkingEngine} */
|
|
34
37
|
let networkingEngine;
|
|
@@ -40,6 +43,8 @@ describe('Player', () => {
|
|
|
40
43
|
let playhead;
|
|
41
44
|
/** @type {!shaka.test.FakeTextDisplayer} */
|
|
42
45
|
let textDisplayer;
|
|
46
|
+
/** @type {shaka.extern.BufferedInfo} */
|
|
47
|
+
let bufferedInfo;
|
|
43
48
|
|
|
44
49
|
let mediaSourceEngine;
|
|
45
50
|
|
|
@@ -101,6 +106,13 @@ describe('Player', () => {
|
|
|
101
106
|
abrManager = new shaka.test.FakeAbrManager();
|
|
102
107
|
textDisplayer = createTextDisplayer();
|
|
103
108
|
|
|
109
|
+
bufferedInfo = {
|
|
110
|
+
total: [],
|
|
111
|
+
audio: [],
|
|
112
|
+
video: [{start: 12, end: 26}],
|
|
113
|
+
text: [],
|
|
114
|
+
};
|
|
115
|
+
|
|
104
116
|
function dependencyInjector(player) {
|
|
105
117
|
// Create a networking engine that always returns an empty buffer.
|
|
106
118
|
networkingEngine = new shaka.test.FakeNetworkingEngine();
|
|
@@ -119,12 +131,22 @@ describe('Player', () => {
|
|
|
119
131
|
setSegmentRelativeVttTiming:
|
|
120
132
|
jasmine.createSpy('setSegmentRelativeVttTiming'),
|
|
121
133
|
getTextDisplayer: () => textDisplayer,
|
|
134
|
+
getBufferedInfo: () => bufferedInfo,
|
|
122
135
|
ended: jasmine.createSpy('ended').and.returnValue(false),
|
|
123
136
|
};
|
|
124
137
|
|
|
125
|
-
player.createDrmEngine = () =>
|
|
138
|
+
player.createDrmEngine = ({onError}) => {
|
|
139
|
+
onErrorCallback = onError;
|
|
140
|
+
return drmEngine;
|
|
141
|
+
};
|
|
126
142
|
player.createNetworkingEngine = () => networkingEngine;
|
|
127
|
-
player.createPlayhead = () =>
|
|
143
|
+
player.createPlayhead = (startTime) => {
|
|
144
|
+
const callableSetStartTime =
|
|
145
|
+
shaka.test.Util.spyFunc(playhead.setStartTime);
|
|
146
|
+
callableSetStartTime(startTime);
|
|
147
|
+
playhead.setStartTime.calls.reset();
|
|
148
|
+
return playhead;
|
|
149
|
+
};
|
|
128
150
|
player.createMediaSourceEngine = () => mediaSourceEngine;
|
|
129
151
|
player.createStreamingEngine = () => streamingEngine;
|
|
130
152
|
}
|
|
@@ -156,6 +178,7 @@ describe('Player', () => {
|
|
|
156
178
|
window.MediaSource.isTypeSupported = originalIsTypeSupported;
|
|
157
179
|
shaka.media.ManifestParser.unregisterParserByMime(fakeMimeType);
|
|
158
180
|
navigator.mediaCapabilities.decodingInfo = originalDecodingInfo;
|
|
181
|
+
onError.calls.reset();
|
|
159
182
|
}
|
|
160
183
|
});
|
|
161
184
|
|
|
@@ -314,6 +337,131 @@ describe('Player', () => {
|
|
|
314
337
|
});
|
|
315
338
|
});
|
|
316
339
|
|
|
340
|
+
describe('onError and tryToRecoverFromError', () => {
|
|
341
|
+
const httpError = new shaka.util.Error(
|
|
342
|
+
shaka.util.Error.Severity.RECOVERABLE,
|
|
343
|
+
shaka.util.Error.Category.NETWORK,
|
|
344
|
+
shaka.util.Error.Code.HTTP_ERROR);
|
|
345
|
+
const nonHttpError = new shaka.util.Error(
|
|
346
|
+
shaka.util.Error.Severity.RECOVERABLE,
|
|
347
|
+
shaka.util.Error.Category.NETWORK,
|
|
348
|
+
shaka.util.Error.Code.TIMEOUT);
|
|
349
|
+
/** @type {?jasmine.Spy} */
|
|
350
|
+
let dispatchEventSpy;
|
|
351
|
+
|
|
352
|
+
beforeEach(async () => {
|
|
353
|
+
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
|
354
|
+
manifest.addVariant(11, (variant) => {
|
|
355
|
+
variant.addAudio(2);
|
|
356
|
+
variant.addVideo(3);
|
|
357
|
+
});
|
|
358
|
+
manifest.addVariant(12, (variant) => {
|
|
359
|
+
variant.addAudio(4);
|
|
360
|
+
variant.addVideo(5);
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
await player.load(fakeManifestUri, 0, fakeMimeType);
|
|
364
|
+
dispatchEventSpy = spyOn(player, 'dispatchEvent').and.returnValue(true);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
afterEach(() => {
|
|
368
|
+
dispatchEventSpy.calls.reset();
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it('does not handle non NETWORK HTTP_ERROR', () => {
|
|
372
|
+
onErrorCallback(nonHttpError);
|
|
373
|
+
expect(nonHttpError.handled).toBeFalsy();
|
|
374
|
+
expect(player.dispatchEvent).toHaveBeenCalled();
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
describe('when config.streaming.maxDisabledTime is 0', () => {
|
|
378
|
+
it('does not handle NETWORK HTTP_ERROR', () => {
|
|
379
|
+
player.configure({streaming: {maxDisabledTime: 0}});
|
|
380
|
+
httpError.handled = false;
|
|
381
|
+
onErrorCallback(httpError);
|
|
382
|
+
expect(httpError.handled).toBeFalsy();
|
|
383
|
+
expect(player.dispatchEvent).toHaveBeenCalled();
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
describe('when config.streaming.maxDisabledTime is 30', () => {
|
|
388
|
+
/** @type {number} */
|
|
389
|
+
const maxDisabledTime = 30;
|
|
390
|
+
/** @type {number} */
|
|
391
|
+
const currentTime = 123;
|
|
392
|
+
const chosenVariant = {
|
|
393
|
+
id: 1,
|
|
394
|
+
language: 'es',
|
|
395
|
+
disabledUntilTime: 0,
|
|
396
|
+
primary: false,
|
|
397
|
+
bandwidth: 2100,
|
|
398
|
+
allowedByApplication: true,
|
|
399
|
+
allowedByKeySystem: true,
|
|
400
|
+
decodingInfos: [],
|
|
401
|
+
};
|
|
402
|
+
/** @type {?jasmine.Spy} */
|
|
403
|
+
let applyRestrictionsSpy;
|
|
404
|
+
/** @type {?jasmine.Spy} */
|
|
405
|
+
let chooseVariantSpy;
|
|
406
|
+
/** @type {?jasmine.Spy} */
|
|
407
|
+
let getBufferedInfoSpy;
|
|
408
|
+
/** @type {?jasmine.Spy} */
|
|
409
|
+
let switchVariantSpy;
|
|
410
|
+
|
|
411
|
+
describe('and there is new variant', () => {
|
|
412
|
+
const oldDateNow = Date.now;
|
|
413
|
+
beforeEach(() => {
|
|
414
|
+
Date.now = () => currentTime * 1000;
|
|
415
|
+
chooseVariantSpy = spyOn(player, 'chooseVariant_')
|
|
416
|
+
.and.returnValue(chosenVariant);
|
|
417
|
+
getBufferedInfoSpy = spyOn(player, 'getBufferedInfo')
|
|
418
|
+
.and.returnValue(bufferedInfo);
|
|
419
|
+
switchVariantSpy = spyOn(player, 'switchVariant_');
|
|
420
|
+
applyRestrictionsSpy = spyOn(StreamUtils, 'applyRestrictions');
|
|
421
|
+
httpError.handled = false;
|
|
422
|
+
dispatchEventSpy.calls.reset();
|
|
423
|
+
player.configure({streaming: {maxDisabledTime}});
|
|
424
|
+
player.setMaxHardwareResolution(123, 456);
|
|
425
|
+
onErrorCallback(httpError);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
afterEach(() => {
|
|
429
|
+
Date.now = oldDateNow;
|
|
430
|
+
chooseVariantSpy.calls.reset();
|
|
431
|
+
getBufferedInfoSpy.calls.reset();
|
|
432
|
+
switchVariantSpy.calls.reset();
|
|
433
|
+
applyRestrictionsSpy.calls.reset();
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
it('handles HTTP_ERROR', () => {
|
|
437
|
+
expect(httpError.handled).toBeTruthy();
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it('does not dispatch any error', () => {
|
|
441
|
+
expect(dispatchEventSpy).not.toHaveBeenCalled();
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
it('disables the current variant and applies restrictions', () => {
|
|
445
|
+
const foundDisabledVariant =
|
|
446
|
+
manifest.variants.some(({disabledUntilTime}) =>
|
|
447
|
+
disabledUntilTime == currentTime + maxDisabledTime);
|
|
448
|
+
expect(foundDisabledVariant).toBeTruthy();
|
|
449
|
+
expect(applyRestrictionsSpy).toHaveBeenCalledWith(
|
|
450
|
+
manifest.variants,
|
|
451
|
+
player.getConfiguration().restrictions,
|
|
452
|
+
{width: 123, height: 456});
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
it('switches the variant', () => {
|
|
456
|
+
expect(chooseVariantSpy).toHaveBeenCalled();
|
|
457
|
+
expect(getBufferedInfoSpy).toHaveBeenCalled();
|
|
458
|
+
expect(switchVariantSpy)
|
|
459
|
+
.toHaveBeenCalledWith(chosenVariant, false, true, 14);
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
});
|
|
463
|
+
});
|
|
464
|
+
|
|
317
465
|
describe('setTextTrackVisibility', () => {
|
|
318
466
|
beforeEach(() => {
|
|
319
467
|
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
|
@@ -2658,6 +2806,51 @@ describe('Player', () => {
|
|
|
2658
2806
|
expect(activeVariant.id).toBe(1);
|
|
2659
2807
|
});
|
|
2660
2808
|
|
|
2809
|
+
// Regression test for https://github.com/shaka-project/shaka-player/issues/4190
|
|
2810
|
+
it('throws only one RESTRICTIONS_CANNOT_BE_MET error', async () => {
|
|
2811
|
+
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
|
2812
|
+
manifest.addVariant(0, (variant) => {
|
|
2813
|
+
variant.addVideo(1, (stream) => {
|
|
2814
|
+
stream.keyIds = new Set(['abc']);
|
|
2815
|
+
});
|
|
2816
|
+
});
|
|
2817
|
+
});
|
|
2818
|
+
|
|
2819
|
+
// Check that error is RESTRICTIONS_CANNOT_BE_MET
|
|
2820
|
+
onError.and.callFake((e) => {
|
|
2821
|
+
const error = e.detail;
|
|
2822
|
+
shaka.test.Util.expectToEqualError(
|
|
2823
|
+
error,
|
|
2824
|
+
new shaka.util.Error(
|
|
2825
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
2826
|
+
shaka.util.Error.Category.MANIFEST,
|
|
2827
|
+
shaka.util.Error.Code.RESTRICTIONS_CANNOT_BE_MET, {
|
|
2828
|
+
hasAppRestrictions: false,
|
|
2829
|
+
missingKeys: ['abc'],
|
|
2830
|
+
restrictedKeyStatuses: [],
|
|
2831
|
+
}));
|
|
2832
|
+
});
|
|
2833
|
+
|
|
2834
|
+
await player.load(fakeManifestUri, 0, fakeMimeType);
|
|
2835
|
+
const activeVariant = getActiveVariantTrack();
|
|
2836
|
+
expect(activeVariant.id).toBe(0);
|
|
2837
|
+
|
|
2838
|
+
abrManager.chooseIndex = 0;
|
|
2839
|
+
abrManager.chooseVariant.calls.reset();
|
|
2840
|
+
|
|
2841
|
+
onKeyStatus({'abc': 'output-restricted'});
|
|
2842
|
+
|
|
2843
|
+
// Ensure that RESTRICTIONS_CANNOT_BE_MET is thrown once
|
|
2844
|
+
expect(onError).toHaveBeenCalledTimes(1);
|
|
2845
|
+
|
|
2846
|
+
// It does not call chooseVariant
|
|
2847
|
+
expect(abrManager.chooseVariant).not.toHaveBeenCalled();
|
|
2848
|
+
|
|
2849
|
+
// The first variant is disallowed.
|
|
2850
|
+
expect(manifest.variants[0].id).toBe(0);
|
|
2851
|
+
expect(manifest.variants[0].allowedByKeySystem).toBe(false);
|
|
2852
|
+
});
|
|
2853
|
+
|
|
2661
2854
|
it('doesn\'t switch if the active stream isn\'t restricted', async () => {
|
|
2662
2855
|
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
|
2663
2856
|
manifest.addVariant(0, (variant) => {
|
|
@@ -3664,6 +3857,51 @@ describe('Player', () => {
|
|
|
3664
3857
|
});
|
|
3665
3858
|
});
|
|
3666
3859
|
|
|
3860
|
+
describe('config streaming.startAtSegmentBoundary', () => {
|
|
3861
|
+
// Test for https://github.com/shaka-project/shaka-player/issues/4188
|
|
3862
|
+
// In that issue, using streaming.startAtSegmentBoundary in v4.0.0 caused
|
|
3863
|
+
// an exception due to the use of a removed method.
|
|
3864
|
+
it('adjusts start time', async () => {
|
|
3865
|
+
player.configure('streaming.startAtSegmentBoundary', true);
|
|
3866
|
+
|
|
3867
|
+
// In order to adjust the start time to a segment boundary, we need
|
|
3868
|
+
// segment descriptions in the segment index. So create a non-default
|
|
3869
|
+
// fake manifest.
|
|
3870
|
+
const timeline = new shaka.media.PresentationTimeline(300, 0);
|
|
3871
|
+
timeline.setStatic(true);
|
|
3872
|
+
// This duration is used by useSegmentTemplate below to decide how many
|
|
3873
|
+
// references to generate.
|
|
3874
|
+
timeline.setDuration(300);
|
|
3875
|
+
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
|
3876
|
+
manifest.presentationTimeline = timeline;
|
|
3877
|
+
manifest.addVariant(0, (variant) => {
|
|
3878
|
+
variant.addVideo(1, (stream) => {
|
|
3879
|
+
stream.useSegmentTemplate(
|
|
3880
|
+
'$Number$.mp4', /* segmentDuration= */ 10);
|
|
3881
|
+
});
|
|
3882
|
+
});
|
|
3883
|
+
});
|
|
3884
|
+
|
|
3885
|
+
await player.load(fakeManifestUri, 12, fakeMimeType);
|
|
3886
|
+
|
|
3887
|
+
expect(playhead.setStartTime).toHaveBeenCalledWith(10);
|
|
3888
|
+
});
|
|
3889
|
+
|
|
3890
|
+
it('does not fail with no segments', async () => {
|
|
3891
|
+
player.configure('streaming.startAtSegmentBoundary', true);
|
|
3892
|
+
|
|
3893
|
+
// Without useSegmentTemplate in the fake manifest, the call to
|
|
3894
|
+
// getIteratorForTime() in Player produces a null iterator. Using the
|
|
3895
|
+
// default fake manifest should cover that case. But just to make sure,
|
|
3896
|
+
// verify that we have no segments before calling load().
|
|
3897
|
+
const variant = manifest.variants[0];
|
|
3898
|
+
expect(variant.video.segmentIndex.getIteratorForTime(0)).toBeNull();
|
|
3899
|
+
expect(variant.audio.segmentIndex.getIteratorForTime(0)).toBeNull();
|
|
3900
|
+
|
|
3901
|
+
await player.load(fakeManifestUri, 0, fakeMimeType);
|
|
3902
|
+
});
|
|
3903
|
+
});
|
|
3904
|
+
|
|
3667
3905
|
/**
|
|
3668
3906
|
* Gets the currently active variant track.
|
|
3669
3907
|
* @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}
|
|
@@ -265,6 +265,8 @@ shaka.test.ManifestGenerator.Variant = class {
|
|
|
265
265
|
this.language = 'und';
|
|
266
266
|
/** @type {number} */
|
|
267
267
|
this.bandwidth = 0;
|
|
268
|
+
/** @type {number} */
|
|
269
|
+
this.disabledUntilTime = 0;
|
|
268
270
|
/** @type {boolean} */
|
|
269
271
|
this.primary = false;
|
|
270
272
|
/** @type {boolean} */
|
|
@@ -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,32 @@ 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
|
+
|
|
336
|
+
/** @private {number} */
|
|
337
|
+
this.gapsJumped_ = 0;
|
|
338
|
+
|
|
339
|
+
/** @private {number} */
|
|
340
|
+
this.stallsDetected_ = 0;
|
|
341
|
+
|
|
342
|
+
/** @type {!jasmine.Spy} */
|
|
343
|
+
this.setStartTime = jasmine.createSpy('setStartTime')
|
|
344
|
+
.and.callFake((value) => {
|
|
345
|
+
this.startTime_ = value;
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
/** @type {!jasmine.Spy} */
|
|
349
|
+
this.getTime = jasmine.createSpy('getTime')
|
|
350
|
+
.and.callFake(() => this.startTime_);
|
|
351
|
+
|
|
311
352
|
/** @type {!jasmine.Spy} */
|
|
312
|
-
this.
|
|
353
|
+
this.getGapsJumped = jasmine.createSpy('getGapsJumped')
|
|
354
|
+
.and.callFake(() => this.gapsJumped_);
|
|
313
355
|
|
|
314
356
|
/** @type {!jasmine.Spy} */
|
|
315
|
-
this.
|
|
357
|
+
this.getStallsDetected = jasmine.createSpy('getTime')
|
|
358
|
+
.and.callFake(() => this.stallsDetected_);
|
|
316
359
|
|
|
317
360
|
/** @type {!jasmine.Spy} */
|
|
318
361
|
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,10 +619,12 @@ 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', () => {
|
|
627
|
+
const textShadow = '-1px 0 black, 0 1px black, 1px 0 black, 0 -1px black';
|
|
571
628
|
verifyHelper(
|
|
572
629
|
[
|
|
573
630
|
{
|
|
@@ -576,6 +633,7 @@ describe('VttTextParser', () => {
|
|
|
576
633
|
payload: 'Test',
|
|
577
634
|
color: 'cyan',
|
|
578
635
|
fontSize: '10px',
|
|
636
|
+
textShadow: textShadow,
|
|
579
637
|
},
|
|
580
638
|
{
|
|
581
639
|
startTime: 40,
|
|
@@ -583,6 +641,7 @@ describe('VttTextParser', () => {
|
|
|
583
641
|
payload: 'Test2',
|
|
584
642
|
color: 'cyan',
|
|
585
643
|
fontSize: '10px',
|
|
644
|
+
textShadow: textShadow,
|
|
586
645
|
},
|
|
587
646
|
],
|
|
588
647
|
'WEBVTT\n\n' +
|
|
@@ -590,6 +649,7 @@ describe('VttTextParser', () => {
|
|
|
590
649
|
'::cue {\n' +
|
|
591
650
|
'color: cyan;\n'+
|
|
592
651
|
'font-size: 10px;\n'+
|
|
652
|
+
`text-shadow: ${textShadow};\n`+
|
|
593
653
|
'}\n\n' +
|
|
594
654
|
'00:00:20.000 --> 00:00:40.000\n' +
|
|
595
655
|
'Test\n\n' +
|
|
@@ -978,11 +1038,14 @@ describe('VttTextParser', () => {
|
|
|
978
1038
|
* @param {!Array} cues
|
|
979
1039
|
* @param {string} text
|
|
980
1040
|
* @param {shaka.extern.TextParser.TimeContext} time
|
|
1041
|
+
* @param {boolean=} sequenceMode
|
|
981
1042
|
*/
|
|
982
|
-
function verifyHelper(cues, text, time) {
|
|
1043
|
+
function verifyHelper(cues, text, time, sequenceMode = false) {
|
|
983
1044
|
const data =
|
|
984
1045
|
shaka.util.BufferUtils.toUint8(shaka.util.StringUtils.toUTF8(text));
|
|
985
|
-
const
|
|
1046
|
+
const parser = new shaka.text.VttTextParser();
|
|
1047
|
+
parser.setSequenceMode(sequenceMode);
|
|
1048
|
+
const result = parser.parseMedia(data, time);
|
|
986
1049
|
|
|
987
1050
|
const expected = cues.map((cue) => {
|
|
988
1051
|
if (cue.nestedCues) {
|
package/test/ui/ui_unit.js
CHANGED
|
@@ -809,12 +809,25 @@ describe('UI', () => {
|
|
|
809
809
|
}
|
|
810
810
|
});
|
|
811
811
|
it('is updated periodically', async () => {
|
|
812
|
-
|
|
812
|
+
// There is no guaranteed ordering, so fetch by the stat name.
|
|
813
|
+
function getStatsElementByName(name) {
|
|
813
814
|
const nodes = statisticsContainer.childNodes;
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
815
|
+
for (const node of nodes) {
|
|
816
|
+
if (node.hasChildNodes() &&
|
|
817
|
+
node.childNodes[0].textContent.includes(name)) {
|
|
818
|
+
return node;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
return null;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
function getStatsFromContainer() {
|
|
825
|
+
width = getStatsElementByName(
|
|
826
|
+
'width').childNodes[1].textContent.replace(' (px)', '');
|
|
827
|
+
height = getStatsElementByName(
|
|
828
|
+
'height').childNodes[1].textContent.replace(' (px)', '');
|
|
829
|
+
bufferingTime = getStatsElementByName(
|
|
830
|
+
'bufferingTime').childNodes[1].textContent.replace(' (s)', '');
|
|
818
831
|
}
|
|
819
832
|
|
|
820
833
|
/** @type {!string} */
|
|
@@ -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
|
+
});
|