shaka-player 4.0.2 → 4.1.1
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/AUTHORS +1 -0
- package/CHANGELOG.md +36 -16
- package/CONTRIBUTORS +2 -0
- package/build/test.py +1 -1
- package/demo/common/assets.js +1 -1
- package/demo/common/message_ids.js +1 -0
- package/demo/config.js +3 -1
- package/demo/locales/en.json +1 -0
- package/demo/locales/source.json +4 -0
- package/demo/main.js +3 -3
- package/dist/demo.compiled.debug.js +7 -7
- package/dist/demo.compiled.debug.map +2 -2
- package/dist/demo.compiled.js +7 -7
- package/dist/demo.compiled.map +2 -2
- package/dist/deps.js +2 -2
- package/dist/locales.js +30 -30
- package/dist/receiver.compiled.debug.js +3 -3
- package/dist/receiver.compiled.debug.map +2 -2
- package/dist/receiver.compiled.js +3 -3
- package/dist/receiver.compiled.map +2 -2
- package/dist/shaka-player.compiled.d.ts +14 -8
- package/dist/shaka-player.compiled.debug.d.ts +14 -8
- package/dist/shaka-player.compiled.debug.externs.js +13 -4
- package/dist/shaka-player.compiled.debug.js +296 -282
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +13 -4
- package/dist/shaka-player.compiled.js +781 -773
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +15 -9
- package/dist/shaka-player.ui.debug.d.ts +15 -9
- package/dist/shaka-player.ui.debug.externs.js +13 -4
- package/dist/shaka-player.ui.debug.js +345 -326
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +13 -4
- package/dist/shaka-player.ui.js +955 -949
- package/dist/shaka-player.ui.map +4 -4
- package/docs/design/chromecast.md +3 -1
- package/docs/tutorials/drm-config.md +16 -0
- package/docs/tutorials/ui.md +26 -0
- package/externs/chromecast.js +9 -2
- package/externs/shaka/manifest.js +7 -0
- package/externs/shaka/player.js +25 -4
- package/externs/shaka/text.js +7 -0
- 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/hls/hls_parser.js +189 -69
- package/lib/media/drm_engine.js +17 -8
- package/lib/media/gap_jumping_controller.js +23 -1
- package/lib/media/playhead.js +47 -4
- package/lib/media/segment_index.js +26 -1
- package/lib/media/segment_reference.js +10 -3
- package/lib/media/stall_detector.js +19 -2
- package/lib/media/streaming_engine.js +9 -2
- package/lib/offline/manifest_converter.js +1 -0
- package/lib/player.js +175 -69
- package/lib/text/cue.js +6 -0
- package/lib/text/ui_text_displayer.js +1 -0
- package/lib/text/vtt_text_parser.js +4 -0
- package/lib/util/error.js +6 -0
- package/lib/util/fake_event.js +39 -0
- package/lib/util/player_configuration.js +2 -0
- package/lib/util/stats.js +28 -1
- package/lib/util/stream_utils.js +8 -0
- package/package.json +2 -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_parser_unit.js +102 -8
- 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/player_integration.js +3 -0
- package/test/player_unit.js +142 -1
- package/test/test/util/manifest_generator.js +2 -0
- package/test/test/util/simple_fakes.js +14 -0
- package/test/test/util/streaming_engine_util.js +1 -0
- package/test/text/vtt_text_parser_unit.js +4 -0
- package/test/ui/ui_unit.js +18 -5
- package/test/util/stream_utils_unit.js +108 -0
- package/ui/controls.js +12 -8
- package/ui/externs/ui.js +10 -1
- package/ui/statistics_button.js +10 -0
- package/ui/ui.js +12 -1
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,10 +131,14 @@ 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
143
|
player.createPlayhead = (startTime) => {
|
|
128
144
|
const callableSetStartTime =
|
|
@@ -321,6 +337,131 @@ describe('Player', () => {
|
|
|
321
337
|
});
|
|
322
338
|
});
|
|
323
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
|
+
|
|
324
465
|
describe('setTextTrackVisibility', () => {
|
|
325
466
|
beforeEach(() => {
|
|
326
467
|
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
|
@@ -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} */
|
|
@@ -333,6 +333,12 @@ shaka.test.FakePlayhead = class {
|
|
|
333
333
|
/** @private {number} */
|
|
334
334
|
this.startTime_ = 0;
|
|
335
335
|
|
|
336
|
+
/** @private {number} */
|
|
337
|
+
this.gapsJumped_ = 0;
|
|
338
|
+
|
|
339
|
+
/** @private {number} */
|
|
340
|
+
this.stallsDetected_ = 0;
|
|
341
|
+
|
|
336
342
|
/** @type {!jasmine.Spy} */
|
|
337
343
|
this.setStartTime = jasmine.createSpy('setStartTime')
|
|
338
344
|
.and.callFake((value) => {
|
|
@@ -343,6 +349,14 @@ shaka.test.FakePlayhead = class {
|
|
|
343
349
|
this.getTime = jasmine.createSpy('getTime')
|
|
344
350
|
.and.callFake(() => this.startTime_);
|
|
345
351
|
|
|
352
|
+
/** @type {!jasmine.Spy} */
|
|
353
|
+
this.getGapsJumped = jasmine.createSpy('getGapsJumped')
|
|
354
|
+
.and.callFake(() => this.gapsJumped_);
|
|
355
|
+
|
|
356
|
+
/** @type {!jasmine.Spy} */
|
|
357
|
+
this.getStallsDetected = jasmine.createSpy('getTime')
|
|
358
|
+
.and.callFake(() => this.stallsDetected_);
|
|
359
|
+
|
|
346
360
|
/** @type {!jasmine.Spy} */
|
|
347
361
|
this.setBuffering = jasmine.createSpy('setBuffering');
|
|
348
362
|
|
|
@@ -624,6 +624,7 @@ describe('VttTextParser', () => {
|
|
|
624
624
|
});
|
|
625
625
|
|
|
626
626
|
it('supports global style blocks', () => {
|
|
627
|
+
const textShadow = '-1px 0 black, 0 1px black, 1px 0 black, 0 -1px black';
|
|
627
628
|
verifyHelper(
|
|
628
629
|
[
|
|
629
630
|
{
|
|
@@ -632,6 +633,7 @@ describe('VttTextParser', () => {
|
|
|
632
633
|
payload: 'Test',
|
|
633
634
|
color: 'cyan',
|
|
634
635
|
fontSize: '10px',
|
|
636
|
+
textShadow: textShadow,
|
|
635
637
|
},
|
|
636
638
|
{
|
|
637
639
|
startTime: 40,
|
|
@@ -639,6 +641,7 @@ describe('VttTextParser', () => {
|
|
|
639
641
|
payload: 'Test2',
|
|
640
642
|
color: 'cyan',
|
|
641
643
|
fontSize: '10px',
|
|
644
|
+
textShadow: textShadow,
|
|
642
645
|
},
|
|
643
646
|
],
|
|
644
647
|
'WEBVTT\n\n' +
|
|
@@ -646,6 +649,7 @@ describe('VttTextParser', () => {
|
|
|
646
649
|
'::cue {\n' +
|
|
647
650
|
'color: cyan;\n'+
|
|
648
651
|
'font-size: 10px;\n'+
|
|
652
|
+
`text-shadow: ${textShadow};\n`+
|
|
649
653
|
'}\n\n' +
|
|
650
654
|
'00:00:20.000 --> 00:00:40.000\n' +
|
|
651
655
|
'Test\n\n' +
|
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} */
|
|
@@ -902,4 +902,112 @@ describe('StreamUtils', () => {
|
|
|
902
902
|
expect(manifest.variants[0].video.id).toBe(2);
|
|
903
903
|
});
|
|
904
904
|
});
|
|
905
|
+
|
|
906
|
+
describe('meetsRestrictions', () => {
|
|
907
|
+
const oldDateNow = Date.now;
|
|
908
|
+
/* @param {shaka.extern.Restrictions} */
|
|
909
|
+
const restrictions = {
|
|
910
|
+
minWidth: 10,
|
|
911
|
+
maxWidth: 20,
|
|
912
|
+
minHeight: 10,
|
|
913
|
+
maxHeight: 20,
|
|
914
|
+
minPixels: 10,
|
|
915
|
+
maxPixels: 20,
|
|
916
|
+
minFrameRate: 21,
|
|
917
|
+
maxFrameRate: 25,
|
|
918
|
+
minBandwidth: 1000,
|
|
919
|
+
maxBandwidth: 3000,
|
|
920
|
+
};
|
|
921
|
+
/* @param {{width: number, height: number}} */
|
|
922
|
+
const maxHwRes = {width: 123, height: 456};
|
|
923
|
+
/* @param {shaka.extern.Variant} */
|
|
924
|
+
let variant;
|
|
925
|
+
/* @param {boolean} */
|
|
926
|
+
let meetsRestrictionsResult;
|
|
927
|
+
|
|
928
|
+
beforeEach(() => {
|
|
929
|
+
Date.now = () => 123 * 1000;
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
afterEach(() => {
|
|
933
|
+
Date.now = oldDateNow;
|
|
934
|
+
});
|
|
935
|
+
|
|
936
|
+
/*
|
|
937
|
+
* @param {number} disabledUntilTime
|
|
938
|
+
*/
|
|
939
|
+
const checkRestrictions = ({disabledUntilTime = 0}) => {
|
|
940
|
+
/* @param {shaka.extern.Variant} */
|
|
941
|
+
variant = {
|
|
942
|
+
id: 1,
|
|
943
|
+
language: 'es',
|
|
944
|
+
disabledUntilTime,
|
|
945
|
+
video: null,
|
|
946
|
+
audio: null,
|
|
947
|
+
primary: false,
|
|
948
|
+
bandwidth: 2000,
|
|
949
|
+
allowedByApplication: true,
|
|
950
|
+
allowedByKeySystem: true,
|
|
951
|
+
decodingInfos: [],
|
|
952
|
+
};
|
|
953
|
+
meetsRestrictionsResult = StreamUtils.meetsRestrictions(variant,
|
|
954
|
+
restrictions, maxHwRes);
|
|
955
|
+
};
|
|
956
|
+
|
|
957
|
+
describe('when disabledUntilTime > now', () => {
|
|
958
|
+
beforeEach(() => {
|
|
959
|
+
checkRestrictions({disabledUntilTime: 124});
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
it('does not meet the restrictions', () => {
|
|
963
|
+
expect(meetsRestrictionsResult).toBeFalsy();
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
it('does not reset disabledUntilTime', () => {
|
|
967
|
+
expect(variant.disabledUntilTime).toBe(124);
|
|
968
|
+
});
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
describe('when disabledUntilTime == now', () => {
|
|
972
|
+
beforeEach(() => {
|
|
973
|
+
checkRestrictions({disabledUntilTime: 123});
|
|
974
|
+
});
|
|
975
|
+
|
|
976
|
+
it('meets the restrictions', () => {
|
|
977
|
+
expect(meetsRestrictionsResult).toBeTruthy();
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
it('resets disabledUntilTime', () => {
|
|
981
|
+
expect(variant.disabledUntilTime).toBe(0);
|
|
982
|
+
});
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
describe('when disabledUntilTime == now', () => {
|
|
986
|
+
beforeEach(() => {
|
|
987
|
+
checkRestrictions({disabledUntilTime: 122});
|
|
988
|
+
});
|
|
989
|
+
|
|
990
|
+
it('meets the restrictions', () => {
|
|
991
|
+
expect(meetsRestrictionsResult).toBeTruthy();
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
it('resets disabledUntilTime', () => {
|
|
995
|
+
expect(variant.disabledUntilTime).toBe(0);
|
|
996
|
+
});
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
describe('when disabledUntilTime == 0', () => {
|
|
1000
|
+
beforeEach(() => {
|
|
1001
|
+
checkRestrictions({disabledUntilTime: 0});
|
|
1002
|
+
});
|
|
1003
|
+
|
|
1004
|
+
it('meets the restrictions', () => {
|
|
1005
|
+
expect(meetsRestrictionsResult).toBeTruthy();
|
|
1006
|
+
});
|
|
1007
|
+
|
|
1008
|
+
it('leaves disabledUntilTime = 0', () => {
|
|
1009
|
+
expect(variant.disabledUntilTime).toBe(0);
|
|
1010
|
+
});
|
|
1011
|
+
});
|
|
1012
|
+
});
|
|
905
1013
|
});
|
package/ui/controls.js
CHANGED
|
@@ -53,7 +53,8 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
|
|
|
53
53
|
|
|
54
54
|
/** @private {shaka.cast.CastProxy} */
|
|
55
55
|
this.castProxy_ = new shaka.cast.CastProxy(
|
|
56
|
-
video, player, this.config_.castReceiverAppId
|
|
56
|
+
video, player, this.config_.castReceiverAppId,
|
|
57
|
+
this.config_.castAndroidReceiverCompatible);
|
|
57
58
|
|
|
58
59
|
/** @private {boolean} */
|
|
59
60
|
this.castAllowed_ = true;
|
|
@@ -315,7 +316,8 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
|
|
|
315
316
|
configure(config) {
|
|
316
317
|
this.config_ = config;
|
|
317
318
|
|
|
318
|
-
this.castProxy_.changeReceiverId(config.castReceiverAppId
|
|
319
|
+
this.castProxy_.changeReceiverId(config.castReceiverAppId,
|
|
320
|
+
config.castAndroidReceiverCompatible);
|
|
319
321
|
|
|
320
322
|
// Deconstruct the old layout if applicable
|
|
321
323
|
if (this.seekBar_) {
|
|
@@ -1258,21 +1260,23 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
|
|
|
1258
1260
|
return;
|
|
1259
1261
|
}
|
|
1260
1262
|
|
|
1263
|
+
const keyboardSeekDistance = this.config_.keyboardSeekDistance;
|
|
1264
|
+
|
|
1261
1265
|
switch (event.key) {
|
|
1262
1266
|
case 'ArrowLeft':
|
|
1263
1267
|
// If it's not focused on the volume bar, move the seek time backward
|
|
1264
|
-
// for
|
|
1265
|
-
if (this.seekBar_ && !isVolumeBar) {
|
|
1268
|
+
// for a few sec. Otherwise, the volume will be adjusted automatically.
|
|
1269
|
+
if (this.seekBar_ && !isVolumeBar && keyboardSeekDistance > 0) {
|
|
1266
1270
|
event.preventDefault();
|
|
1267
|
-
this.seek_(this.seekBar_.getValue() -
|
|
1271
|
+
this.seek_(this.seekBar_.getValue() - keyboardSeekDistance);
|
|
1268
1272
|
}
|
|
1269
1273
|
break;
|
|
1270
1274
|
case 'ArrowRight':
|
|
1271
1275
|
// If it's not focused on the volume bar, move the seek time forward
|
|
1272
|
-
// for
|
|
1273
|
-
if (this.seekBar_ && !isVolumeBar) {
|
|
1276
|
+
// for a few sec. Otherwise, the volume will be adjusted automatically.
|
|
1277
|
+
if (this.seekBar_ && !isVolumeBar && keyboardSeekDistance > 0) {
|
|
1274
1278
|
event.preventDefault();
|
|
1275
|
-
this.seek_(this.seekBar_.getValue() +
|
|
1279
|
+
this.seek_(this.seekBar_.getValue() + keyboardSeekDistance);
|
|
1276
1280
|
}
|
|
1277
1281
|
break;
|
|
1278
1282
|
// Jump to the beginning of the video's seek range.
|
package/ui/externs/ui.js
CHANGED
|
@@ -72,6 +72,7 @@ shaka.extern.UIVolumeBarColors;
|
|
|
72
72
|
* addBigPlayButton: boolean,
|
|
73
73
|
* customContextMenu: boolean,
|
|
74
74
|
* castReceiverAppId: string,
|
|
75
|
+
* castAndroidReceiverCompatible: boolean,
|
|
75
76
|
* clearBufferOnQualityChange: boolean,
|
|
76
77
|
* showUnbufferedStart: boolean,
|
|
77
78
|
* seekBarColors: shaka.extern.UISeekBarColors,
|
|
@@ -83,7 +84,8 @@ shaka.extern.UIVolumeBarColors;
|
|
|
83
84
|
* enableKeyboardPlaybackControls: boolean,
|
|
84
85
|
* enableFullscreenOnRotation: boolean,
|
|
85
86
|
* forceLandscapeOnFullscreen: boolean,
|
|
86
|
-
* enableTooltips: boolean
|
|
87
|
+
* enableTooltips: boolean,
|
|
88
|
+
* keyboardSeekDistance: number
|
|
87
89
|
* }}
|
|
88
90
|
*
|
|
89
91
|
* @property {!Array.<string>} controlPanelElements
|
|
@@ -109,6 +111,8 @@ shaka.extern.UIVolumeBarColors;
|
|
|
109
111
|
* Whether or not a custom context menu replaces the default.
|
|
110
112
|
* @property {string} castReceiverAppId
|
|
111
113
|
* Receiver app id to use for the Chromecast support.
|
|
114
|
+
* @property {boolean} castAndroidReceiverCompatible
|
|
115
|
+
* Indicates if the app is compatible with an Android Cast Receiver.
|
|
112
116
|
* @property {boolean} clearBufferOnQualityChange
|
|
113
117
|
* Only applicable if the resolution selection is part of the UI.
|
|
114
118
|
* Whether buffer should be cleared when changing resolution
|
|
@@ -174,6 +178,11 @@ shaka.extern.UIVolumeBarColors;
|
|
|
174
178
|
* Whether or not buttons in the control panel display tooltips that contain
|
|
175
179
|
* information about their function.
|
|
176
180
|
* Defaults to false.
|
|
181
|
+
* @property {number} keyboardSeekDistance
|
|
182
|
+
* The time interval, in seconds, to seek when the user presses the left or
|
|
183
|
+
* right keyboard keys when the video is selected. If less than or equal to 0,
|
|
184
|
+
* no seeking will occur.
|
|
185
|
+
* Defaults to 5 seconds.
|
|
177
186
|
* @exportDoc
|
|
178
187
|
*/
|
|
179
188
|
shaka.extern.UIConfiguration;
|
package/ui/statistics_button.js
CHANGED
|
@@ -109,6 +109,14 @@ shaka.ui.StatisticsButton = class extends shaka.ui.Element {
|
|
|
109
109
|
this.currentStats_[name], false) + ' (m)';
|
|
110
110
|
};
|
|
111
111
|
|
|
112
|
+
const parseGaps = (name) => {
|
|
113
|
+
return this.currentStats_[name] + ' (gaps)';
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const parseStalls = (name) => {
|
|
117
|
+
return this.currentStats_[name] + ' (stalls)';
|
|
118
|
+
};
|
|
119
|
+
|
|
112
120
|
/** @private {!Object.<string, function(string):string>} */
|
|
113
121
|
this.parseFrom_ = {
|
|
114
122
|
'width': parsePx,
|
|
@@ -128,6 +136,8 @@ shaka.ui.StatisticsButton = class extends shaka.ui.Element {
|
|
|
128
136
|
'corruptedFrames': parseFrames,
|
|
129
137
|
'decodedFrames': parseFrames,
|
|
130
138
|
'droppedFrames': parseFrames,
|
|
139
|
+
'stallsDetected': parseStalls,
|
|
140
|
+
'gapsJumped': parseGaps,
|
|
131
141
|
};
|
|
132
142
|
|
|
133
143
|
/** @private {shaka.util.Timer} */
|
package/ui/ui.js
CHANGED
|
@@ -218,6 +218,7 @@ shaka.ui.Overlay = class {
|
|
|
218
218
|
addBigPlayButton: false,
|
|
219
219
|
customContextMenu: false,
|
|
220
220
|
castReceiverAppId: '',
|
|
221
|
+
castAndroidReceiverCompatible: false,
|
|
221
222
|
clearBufferOnQualityChange: true,
|
|
222
223
|
showUnbufferedStart: false,
|
|
223
224
|
seekBarColors: {
|
|
@@ -238,6 +239,7 @@ shaka.ui.Overlay = class {
|
|
|
238
239
|
enableFullscreenOnRotation: true,
|
|
239
240
|
forceLandscapeOnFullscreen: true,
|
|
240
241
|
enableTooltips: false,
|
|
242
|
+
keyboardSeekDistance: 5,
|
|
241
243
|
};
|
|
242
244
|
|
|
243
245
|
// Check AirPlay support
|
|
@@ -388,19 +390,28 @@ shaka.ui.Overlay = class {
|
|
|
388
390
|
// Get and configure cast app id.
|
|
389
391
|
let castAppId = '';
|
|
390
392
|
|
|
393
|
+
// Get and configure cast Android Receiver Compatibility
|
|
394
|
+
let castAndroidReceiverCompatible = false;
|
|
395
|
+
|
|
391
396
|
// Cast receiver id can be specified on either container or video.
|
|
392
397
|
// It should not be provided on both. If it was, we will use the last
|
|
393
398
|
// one we saw.
|
|
394
399
|
if (container['dataset'] &&
|
|
395
400
|
container['dataset']['shakaPlayerCastReceiverId']) {
|
|
396
401
|
castAppId = container['dataset']['shakaPlayerCastReceiverId'];
|
|
402
|
+
castAndroidReceiverCompatible =
|
|
403
|
+
container['dataset']['shakaPlayerCastAndroidReceiverCompatible'] ===
|
|
404
|
+
'true';
|
|
397
405
|
} else if (video['dataset'] &&
|
|
398
406
|
video['dataset']['shakaPlayerCastReceiverId']) {
|
|
399
407
|
castAppId = video['dataset']['shakaPlayerCastReceiverId'];
|
|
408
|
+
castAndroidReceiverCompatible =
|
|
409
|
+
video['dataset']['shakaPlayerCastAndroidReceiverCompatible'] === 'true';
|
|
400
410
|
}
|
|
401
411
|
|
|
402
412
|
if (castAppId.length) {
|
|
403
|
-
ui.configure({castReceiverAppId: castAppId
|
|
413
|
+
ui.configure({castReceiverAppId: castAppId,
|
|
414
|
+
castAndroidReceiverCompatible: castAndroidReceiverCompatible});
|
|
404
415
|
}
|
|
405
416
|
|
|
406
417
|
if (shaka.util.Dom.asHTMLMediaElement(video).controls) {
|