shaka-player 4.2.4 → 4.2.5
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 +24 -0
- package/CONTRIBUTORS +1 -0
- package/demo/index.html +1 -1
- package/dist/demo.compiled.debug.js +64 -64
- package/dist/demo.compiled.debug.map +2 -2
- package/dist/demo.compiled.js +64 -64
- package/dist/demo.compiled.map +2 -2
- package/dist/deps.js +3 -2
- package/dist/locales.js +32 -32
- package/dist/receiver.compiled.debug.js +12 -12
- package/dist/receiver.compiled.debug.map +2 -2
- package/dist/receiver.compiled.js +12 -12
- package/dist/receiver.compiled.map +2 -2
- package/dist/shaka-player.compiled.d.ts +10 -0
- package/dist/shaka-player.compiled.debug.d.ts +10 -0
- package/dist/shaka-player.compiled.debug.externs.js +93 -79
- package/dist/shaka-player.compiled.debug.js +557 -550
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +93 -79
- package/dist/shaka-player.compiled.js +806 -806
- package/dist/shaka-player.compiled.map +3 -3
- package/dist/shaka-player.ui.d.ts +10 -0
- package/dist/shaka-player.ui.debug.d.ts +10 -0
- package/dist/shaka-player.ui.debug.externs.js +93 -79
- package/dist/shaka-player.ui.debug.js +583 -576
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +93 -79
- package/dist/shaka-player.ui.js +953 -953
- package/dist/shaka-player.ui.map +3 -3
- package/docs/tutorials/ui.md +7 -7
- package/docs/tutorials/welcome.md +2 -2
- package/externs/shaka/player.js +2 -1
- package/lib/cea/cea708_window.js +2 -2
- package/lib/dash/segment_template.js +1 -1
- package/lib/hls/hls_parser.js +12 -2
- package/lib/media/content_workarounds.js +12 -0
- package/lib/player.js +1 -1
- package/lib/polyfill/abort_controller.js +143 -0
- package/lib/polyfill/media_capabilities.js +59 -9
- package/lib/text/ttml_text_parser.js +32 -7
- package/lib/text/ui_text_displayer.js +22 -5
- package/lib/util/platform.js +9 -1
- package/lib/util/player_configuration.js +10 -3
- package/lib/util/stream_utils.js +30 -10
- package/package.json +1 -1
- package/shaka-player.uncompiled.js +1 -0
- package/test/dash/dash_parser_manifest_unit.js +1 -0
- package/test/hls/hls_parser_unit.js +1 -0
- package/test/media/drm_engine_unit.js +11 -1
- package/test/media/media_source_engine_integration.js +78 -2
- package/test/polyfill/media_capabilities_unit.js +261 -0
- package/test/test/util/stream_generator.js +212 -8
- package/test/test/util/test_scheme.js +3 -2
- package/test/test/util/util.js +3 -2
- package/test/text/mp4_ttml_parser_unit.js +6 -4
- package/test/text/ttml_text_parser_unit.js +119 -86
- package/test/text/ui_text_displayer_unit.js +76 -0
- package/test/ui/text_displayer_layout_unit.js +14 -0
- package/test/util/stream_utils_unit.js +18 -0
- package/test/util/xml_utils_unit.js +6 -1
- package/ui/controls.js +45 -36
package/lib/util/stream_utils.js
CHANGED
|
@@ -438,15 +438,20 @@ shaka.util.StreamUtils = class {
|
|
|
438
438
|
const video = variant.video;
|
|
439
439
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
440
440
|
if (video) {
|
|
441
|
-
let videoCodecs =
|
|
441
|
+
let videoCodecs =
|
|
442
|
+
shaka.util.StreamUtils.getCorrectVideoCodecs_(video.codecs);
|
|
442
443
|
// For multiplexed streams. Here we must check the audio of the
|
|
443
444
|
// stream to see if it is compatible.
|
|
444
445
|
if (video.codecs.includes(',')) {
|
|
445
446
|
const allCodecs = video.codecs.split(',');
|
|
446
447
|
videoCodecs = shaka.util.ManifestParserUtils.guessCodecs(
|
|
447
448
|
ContentType.VIDEO, allCodecs);
|
|
448
|
-
|
|
449
|
+
videoCodecs =
|
|
450
|
+
shaka.util.StreamUtils.getCorrectVideoCodecs_(videoCodecs);
|
|
451
|
+
let audioCodecs = shaka.util.ManifestParserUtils.guessCodecs(
|
|
449
452
|
ContentType.AUDIO, allCodecs);
|
|
453
|
+
audioCodecs =
|
|
454
|
+
shaka.util.StreamUtils.getCorrectAudioCodecs_(audioCodecs);
|
|
450
455
|
const audioFullType = shaka.util.MimeUtils.getFullOrConvertedType(
|
|
451
456
|
video.mimeType, audioCodecs, ContentType.AUDIO);
|
|
452
457
|
if (!MediaSource.isTypeSupported(audioFullType)) {
|
|
@@ -566,7 +571,8 @@ shaka.util.StreamUtils = class {
|
|
|
566
571
|
const allCodecs = video.codecs.split(',');
|
|
567
572
|
videoCodecs = shaka.util.ManifestParserUtils.guessCodecs(
|
|
568
573
|
ContentType.VIDEO, allCodecs);
|
|
569
|
-
videoCodecs =
|
|
574
|
+
videoCodecs =
|
|
575
|
+
shaka.util.StreamUtils.getCorrectVideoCodecs_(videoCodecs);
|
|
570
576
|
const audioCodecs = shaka.util.ManifestParserUtils.guessCodecs(
|
|
571
577
|
ContentType.AUDIO, allCodecs);
|
|
572
578
|
|
|
@@ -580,7 +586,7 @@ shaka.util.StreamUtils = class {
|
|
|
580
586
|
spatialRendering: false,
|
|
581
587
|
};
|
|
582
588
|
}
|
|
583
|
-
videoCodecs = shaka.util.StreamUtils.
|
|
589
|
+
videoCodecs = shaka.util.StreamUtils.getCorrectVideoCodecs_(videoCodecs);
|
|
584
590
|
const fullType = shaka.util.MimeUtils.getFullOrConvertedType(
|
|
585
591
|
video.mimeType, videoCodecs, ContentType.VIDEO);
|
|
586
592
|
// VideoConfiguration
|
|
@@ -749,15 +755,29 @@ shaka.util.StreamUtils = class {
|
|
|
749
755
|
|
|
750
756
|
|
|
751
757
|
/**
|
|
752
|
-
*
|
|
753
|
-
*
|
|
754
|
-
* mediaCapabilities enabled.
|
|
755
|
-
*
|
|
758
|
+
* Generates the correct video codec for MediaDecodingConfiguration and
|
|
759
|
+
* for MediaSource.isTypeSupported.
|
|
756
760
|
* @param {string} codec
|
|
757
761
|
* @return {string}
|
|
762
|
+
* @private
|
|
758
763
|
*/
|
|
759
|
-
static
|
|
760
|
-
if (codec
|
|
764
|
+
static getCorrectVideoCodecs_(codec) {
|
|
765
|
+
if (codec.includes('avc1')) {
|
|
766
|
+
// Convert avc1 codec string from RFC-4281 to RFC-6381 for
|
|
767
|
+
// MediaSource.isTypeSupported
|
|
768
|
+
// Example, convert avc1.66.30 to avc1.42001e (0x42 == 66 and 0x1e == 30)
|
|
769
|
+
const avcdata = codec.split('.');
|
|
770
|
+
if (avcdata.length == 3) {
|
|
771
|
+
let result = avcdata.shift() + '.';
|
|
772
|
+
result += parseInt(avcdata.shift(), 10).toString(16);
|
|
773
|
+
result +=
|
|
774
|
+
('000' + parseInt(avcdata.shift(), 10).toString(16)).slice(-4);
|
|
775
|
+
return result;
|
|
776
|
+
}
|
|
777
|
+
} else if (codec == 'vp9') {
|
|
778
|
+
// MediaCapabilities supports 'vp09...' codecs, but not 'vp9'. Translate
|
|
779
|
+
// vp9 codec strings into 'vp09...', to allow such content to play with
|
|
780
|
+
// mediaCapabilities enabled.
|
|
761
781
|
// This means profile 0, level 4.1, 8-bit color. This supports 1080p @
|
|
762
782
|
// 60Hz. See https://en.wikipedia.org/wiki/VP9#Levels
|
|
763
783
|
//
|
package/package.json
CHANGED
|
@@ -32,6 +32,7 @@ goog.require('shaka.offline.OfflineManifestParser');
|
|
|
32
32
|
goog.require('shaka.offline.OfflineScheme');
|
|
33
33
|
goog.require('shaka.offline.Storage');
|
|
34
34
|
goog.require('shaka.offline.indexeddb.StorageMechanism');
|
|
35
|
+
goog.require('shaka.polyfill.AbortController');
|
|
35
36
|
goog.require('shaka.polyfill.Aria');
|
|
36
37
|
goog.require('shaka.polyfill.EncryptionScheme');
|
|
37
38
|
goog.require('shaka.polyfill.Fullscreen');
|
|
@@ -2106,6 +2106,7 @@ describe('DashParser Manifest', () => {
|
|
|
2106
2106
|
/** @type {shaka.extern.Manifest} */
|
|
2107
2107
|
const manifest = await parser.start('dummy://foo', playerInterface);
|
|
2108
2108
|
expect(manifest.imageStreams.length).toBe(1);
|
|
2109
|
+
expect(manifest.presentationTimeline.getMaxSegmentDuration()).toBe(1);
|
|
2109
2110
|
const imageStream = manifest.imageStreams[0];
|
|
2110
2111
|
expect(imageStream.width).toBe(1024);
|
|
2111
2112
|
expect(imageStream.height).toBe(1152);
|
|
@@ -120,11 +120,17 @@ describe('DrmEngine', () => {
|
|
|
120
120
|
};
|
|
121
121
|
|
|
122
122
|
drmEngine = new shaka.media.DrmEngine(playerInterface);
|
|
123
|
+
|
|
123
124
|
config = shaka.util.PlayerConfiguration.createDefault().drm;
|
|
124
125
|
config.servers = {
|
|
125
126
|
'drm.abc': 'http://abc.drm/license',
|
|
126
127
|
'drm.def': 'http://def.drm/license',
|
|
127
128
|
};
|
|
129
|
+
// Some platforms, such as Xbox, default to parseInbandPssh: true, which
|
|
130
|
+
// ignores encrypted events. So set it explicitly to false, and let
|
|
131
|
+
// individual tests set it to true where relevant.
|
|
132
|
+
config.parseInbandPsshEnabled = false;
|
|
133
|
+
|
|
128
134
|
drmEngine.configure(config);
|
|
129
135
|
});
|
|
130
136
|
|
|
@@ -2548,7 +2554,11 @@ describe('DrmEngine', () => {
|
|
|
2548
2554
|
*/
|
|
2549
2555
|
async function sendEncryptedEvent(
|
|
2550
2556
|
initDataType = 'cenc', initData = new Uint8Array(1), keyId = null) {
|
|
2551
|
-
|
|
2557
|
+
// For some platforms, such as Xbox, where parseInbandPssh defaults to
|
|
2558
|
+
// true, this listener may never be set.
|
|
2559
|
+
if (mockVideo.on['encrypted']) {
|
|
2560
|
+
mockVideo.on['encrypted']({initDataType, initData, keyId});
|
|
2561
|
+
}
|
|
2552
2562
|
|
|
2553
2563
|
await Util.shortDelay();
|
|
2554
2564
|
}
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
describe('MediaSourceEngine', () => {
|
|
8
8
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
9
|
+
const Cue = shaka.text.Cue;
|
|
10
|
+
const Util = shaka.test.Util;
|
|
9
11
|
const presentationDuration = 840;
|
|
10
12
|
|
|
11
13
|
/** @type {!HTMLVideoElement} */
|
|
@@ -18,6 +20,64 @@ describe('MediaSourceEngine', () => {
|
|
|
18
20
|
let metadata;
|
|
19
21
|
// TODO: add text streams to MSE integration tests
|
|
20
22
|
|
|
23
|
+
const mp4CeaCue0 = jasmine.objectContaining({
|
|
24
|
+
startTime: Util.closeTo(0.067, 0.001),
|
|
25
|
+
endTime: Util.closeTo(1, 0.001),
|
|
26
|
+
textAlign: Cue.textAlign.CENTER,
|
|
27
|
+
nestedCues: [
|
|
28
|
+
jasmine.objectContaining({
|
|
29
|
+
startTime: Util.closeTo(0.067, 0.001),
|
|
30
|
+
endTime: Util.closeTo(1, 0.001),
|
|
31
|
+
payload: 'eng:⠀00:00:00:00',
|
|
32
|
+
textAlign: Cue.textAlign.CENTER,
|
|
33
|
+
}),
|
|
34
|
+
],
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const tsCeaCue0 = jasmine.objectContaining({
|
|
38
|
+
startTime: Util.closeTo(0.767, 0.001),
|
|
39
|
+
endTime: Util.closeTo(4.972, 0.001),
|
|
40
|
+
textAlign: Cue.textAlign.CENTER,
|
|
41
|
+
payload: 'These are 608 captions\n(top left)',
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const tsCeaCue1 = jasmine.objectContaining({
|
|
45
|
+
startTime: Util.closeTo(5.305, 0.001),
|
|
46
|
+
endTime: Util.closeTo(11.979, 0.001),
|
|
47
|
+
textAlign: Cue.textAlign.CENTER,
|
|
48
|
+
payload: 'These are 608 captions\n(middle)',
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const tsCeaCue2 = jasmine.objectContaining({
|
|
52
|
+
startTime: Util.closeTo(12.312, 0.001),
|
|
53
|
+
endTime: Util.closeTo(19.319, 0.001),
|
|
54
|
+
textAlign: Cue.textAlign.CENTER,
|
|
55
|
+
payload: 'These are 608 captions\n(bottom left)',
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// The same segments as above, but offset by 40 seconds (yes, 40), which is
|
|
59
|
+
// also 2 segments.
|
|
60
|
+
const tsCeaCue3 = jasmine.objectContaining({
|
|
61
|
+
startTime: Util.closeTo(40.767, 0.001),
|
|
62
|
+
endTime: Util.closeTo(44.972, 0.001),
|
|
63
|
+
textAlign: Cue.textAlign.CENTER,
|
|
64
|
+
payload: 'These are 608 captions\n(top left)',
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const tsCeaCue4 = jasmine.objectContaining({
|
|
68
|
+
startTime: Util.closeTo(45.305, 0.001),
|
|
69
|
+
endTime: Util.closeTo(51.979, 0.001),
|
|
70
|
+
textAlign: Cue.textAlign.CENTER,
|
|
71
|
+
payload: 'These are 608 captions\n(middle)',
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const tsCeaCue5 = jasmine.objectContaining({
|
|
75
|
+
startTime: Util.closeTo(52.312, 0.001),
|
|
76
|
+
endTime: Util.closeTo(59.319, 0.001),
|
|
77
|
+
textAlign: Cue.textAlign.CENTER,
|
|
78
|
+
payload: 'These are 608 captions\n(bottom left)',
|
|
79
|
+
});
|
|
80
|
+
|
|
21
81
|
/**
|
|
22
82
|
* We use a fake text displayer so that we can check if CEA text is being
|
|
23
83
|
* passed through the system correctly.
|
|
@@ -387,9 +447,14 @@ describe('MediaSourceEngine', () => {
|
|
|
387
447
|
// platforms with native TS support.
|
|
388
448
|
await mediaSourceEngine.init(initObject, /* forceTransmuxTS= */ true);
|
|
389
449
|
mediaSourceEngine.setSelectedClosedCaptionId('CC1');
|
|
450
|
+
|
|
390
451
|
await append(ContentType.VIDEO, 0);
|
|
391
452
|
|
|
392
453
|
expect(textDisplayer.appendSpy).toHaveBeenCalledTimes(3);
|
|
454
|
+
|
|
455
|
+
expect(textDisplayer.appendSpy).toHaveBeenCalledWith([tsCeaCue0]);
|
|
456
|
+
expect(textDisplayer.appendSpy).toHaveBeenCalledWith([tsCeaCue1]);
|
|
457
|
+
expect(textDisplayer.appendSpy).toHaveBeenCalledWith([tsCeaCue2]);
|
|
393
458
|
});
|
|
394
459
|
|
|
395
460
|
it('extracts CEA-708 captions from previous segment from hls', async () => {
|
|
@@ -406,9 +471,19 @@ describe('MediaSourceEngine', () => {
|
|
|
406
471
|
mediaSourceEngine.setSelectedClosedCaptionId('CC1');
|
|
407
472
|
|
|
408
473
|
await append(ContentType.VIDEO, 2);
|
|
474
|
+
|
|
475
|
+
expect(textDisplayer.appendSpy).toHaveBeenCalledTimes(3);
|
|
476
|
+
expect(textDisplayer.appendSpy).toHaveBeenCalledWith([tsCeaCue3]);
|
|
477
|
+
expect(textDisplayer.appendSpy).toHaveBeenCalledWith([tsCeaCue4]);
|
|
478
|
+
expect(textDisplayer.appendSpy).toHaveBeenCalledWith([tsCeaCue5]);
|
|
479
|
+
|
|
480
|
+
textDisplayer.appendSpy.calls.reset();
|
|
409
481
|
await appendWithSeek(ContentType.VIDEO, 0);
|
|
410
482
|
|
|
411
|
-
expect(textDisplayer.appendSpy).toHaveBeenCalledTimes(
|
|
483
|
+
expect(textDisplayer.appendSpy).toHaveBeenCalledTimes(3);
|
|
484
|
+
expect(textDisplayer.appendSpy).toHaveBeenCalledWith([tsCeaCue0]);
|
|
485
|
+
expect(textDisplayer.appendSpy).toHaveBeenCalledWith([tsCeaCue1]);
|
|
486
|
+
expect(textDisplayer.appendSpy).toHaveBeenCalledWith([tsCeaCue2]);
|
|
412
487
|
});
|
|
413
488
|
|
|
414
489
|
it('buffers partial TS video segments in sequence mode', async () => {
|
|
@@ -461,6 +536,7 @@ describe('MediaSourceEngine', () => {
|
|
|
461
536
|
mediaSourceEngine.setSelectedClosedCaptionId('CC1');
|
|
462
537
|
await appendWithClosedCaptions(ContentType.VIDEO, 0);
|
|
463
538
|
|
|
464
|
-
expect(textDisplayer.appendSpy).
|
|
539
|
+
expect(textDisplayer.appendSpy).toHaveBeenCalledTimes(1);
|
|
540
|
+
expect(textDisplayer.appendSpy).toHaveBeenCalledWith([mp4CeaCue0]);
|
|
465
541
|
});
|
|
466
542
|
});
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/*! @license
|
|
2
|
+
* Shaka Player
|
|
3
|
+
* Copyright 2016 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
describe('MediaCapabilities', () => {
|
|
8
|
+
const Util = shaka.test.Util;
|
|
9
|
+
const originalCast = window['cast'];
|
|
10
|
+
const originalVendor = navigator.vendor;
|
|
11
|
+
const originalUserAgent = navigator.userAgent;
|
|
12
|
+
const originalRequestMediaKeySystemAccess =
|
|
13
|
+
navigator.requestMediaKeySystemAccess;
|
|
14
|
+
const originalMediaCapabilities = navigator.mediaCapabilities;
|
|
15
|
+
|
|
16
|
+
/** @type {MediaDecodingConfiguration} */
|
|
17
|
+
let mockDecodingConfig;
|
|
18
|
+
/** @type {!jasmine.Spy} */
|
|
19
|
+
let mockCanDisplayType;
|
|
20
|
+
|
|
21
|
+
beforeAll(() => {
|
|
22
|
+
Object.defineProperty(window['navigator'],
|
|
23
|
+
'userAgent', {
|
|
24
|
+
value: 'unknown', configurable: true,
|
|
25
|
+
writable: true,
|
|
26
|
+
});
|
|
27
|
+
Object.defineProperty(window['navigator'],
|
|
28
|
+
'vendor', {
|
|
29
|
+
value: 'unknown', configurable: true,
|
|
30
|
+
writable: true,
|
|
31
|
+
});
|
|
32
|
+
Object.defineProperty(window['navigator'],
|
|
33
|
+
'requestMediaKeySystemAccess', {
|
|
34
|
+
value: 'unknown', configurable: true,
|
|
35
|
+
writable: true,
|
|
36
|
+
});
|
|
37
|
+
Object.defineProperty(window['navigator'],
|
|
38
|
+
'mediaCapabilities', {
|
|
39
|
+
value: undefined, configurable: true,
|
|
40
|
+
writable: true,
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
beforeEach(() => {
|
|
45
|
+
mockDecodingConfig = {
|
|
46
|
+
audio: {
|
|
47
|
+
bitrate: 100891,
|
|
48
|
+
channels: 2,
|
|
49
|
+
contentType: 'audio/mp4; codecs="mp4a.40.2"',
|
|
50
|
+
samplerate: 48000,
|
|
51
|
+
spatialRendering: false,
|
|
52
|
+
},
|
|
53
|
+
keySystemConfiguration: {
|
|
54
|
+
audio: {robustness: 'SW_SECURE_CRYPTO'},
|
|
55
|
+
distinctiveIdentifier: 'optional',
|
|
56
|
+
initDataType: 'cenc',
|
|
57
|
+
keySystem: 'com.widevine.alpha',
|
|
58
|
+
persistentState: 'optional',
|
|
59
|
+
sessionTypes: ['temporary'],
|
|
60
|
+
video: {robustness: 'SW_SECURE_CRYPTO'},
|
|
61
|
+
},
|
|
62
|
+
type: 'media-source',
|
|
63
|
+
video: {
|
|
64
|
+
bitrate: 349265,
|
|
65
|
+
contentType: 'video/mp4; codecs="avc1.4D4015"',
|
|
66
|
+
framerate: 23.976023976023978,
|
|
67
|
+
height: 288,
|
|
68
|
+
width: 512,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
shaka.polyfill.MediaCapabilities.memoizedMediaKeySystemAccessRequests_ = {};
|
|
72
|
+
|
|
73
|
+
mockCanDisplayType = jasmine.createSpy('canDisplayType');
|
|
74
|
+
mockCanDisplayType.and.returnValue(false);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
afterEach(() => {
|
|
78
|
+
window['cast'] = originalCast;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
afterAll(() => {
|
|
82
|
+
window['cast'] = originalCast;
|
|
83
|
+
Object.defineProperty(window['navigator'],
|
|
84
|
+
'userAgent', {value: originalUserAgent});
|
|
85
|
+
Object.defineProperty(window['navigator'],
|
|
86
|
+
'vendor', {value: originalVendor});
|
|
87
|
+
Object.defineProperty(window['navigator'],
|
|
88
|
+
'requestMediaKeySystemAccess',
|
|
89
|
+
{value: originalRequestMediaKeySystemAccess});
|
|
90
|
+
Object.defineProperty(window['navigator'],
|
|
91
|
+
'mediaCapabilities', {value: originalMediaCapabilities});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('install', () => {
|
|
95
|
+
it('should define decoding info method', () => {
|
|
96
|
+
shaka.polyfill.MediaCapabilities.install();
|
|
97
|
+
|
|
98
|
+
expect(navigator.mediaCapabilities.decodingInfo).toBeDefined();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe('decodingInfo', () => {
|
|
103
|
+
it('should check codec support when MediaDecodingConfiguration.type ' +
|
|
104
|
+
'is "media-source"', () => {
|
|
105
|
+
const isTypeSupportedSpy =
|
|
106
|
+
spyOn(window['MediaSource'], 'isTypeSupported').and.returnValue(true);
|
|
107
|
+
shaka.polyfill.MediaCapabilities.install();
|
|
108
|
+
navigator.mediaCapabilities.decodingInfo(mockDecodingConfig);
|
|
109
|
+
|
|
110
|
+
expect(isTypeSupportedSpy).toHaveBeenCalledTimes(2);
|
|
111
|
+
expect(isTypeSupportedSpy).toHaveBeenCalledWith(
|
|
112
|
+
mockDecodingConfig.video.contentType,
|
|
113
|
+
);
|
|
114
|
+
expect(isTypeSupportedSpy).toHaveBeenCalledWith(
|
|
115
|
+
mockDecodingConfig.audio.contentType,
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should check codec support when MediaDecodingConfiguration.type ' +
|
|
120
|
+
'is "file"', () => {
|
|
121
|
+
const supportsMediaTypeSpy =
|
|
122
|
+
spyOn(shaka['util']['Platform'],
|
|
123
|
+
'supportsMediaType').and.returnValue(true);
|
|
124
|
+
mockDecodingConfig.type = 'file';
|
|
125
|
+
shaka.polyfill.MediaCapabilities.install();
|
|
126
|
+
navigator.mediaCapabilities.decodingInfo(mockDecodingConfig);
|
|
127
|
+
|
|
128
|
+
expect(supportsMediaTypeSpy).toHaveBeenCalledTimes(2);
|
|
129
|
+
expect(supportsMediaTypeSpy).toHaveBeenCalledWith(
|
|
130
|
+
mockDecodingConfig.video.contentType,
|
|
131
|
+
);
|
|
132
|
+
expect(supportsMediaTypeSpy).toHaveBeenCalledWith(
|
|
133
|
+
mockDecodingConfig.audio.contentType,
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should check MediaKeySystem when keySystemConfiguration is present',
|
|
138
|
+
async () => {
|
|
139
|
+
const mockResult = {mockKeySystemAccess: 'mockKeySystemAccess'};
|
|
140
|
+
spyOn(window['MediaSource'], 'isTypeSupported').and.returnValue(true);
|
|
141
|
+
const requestKeySystemAccessSpy =
|
|
142
|
+
spyOn(window['navigator'],
|
|
143
|
+
'requestMediaKeySystemAccess').and.returnValue(mockResult);
|
|
144
|
+
|
|
145
|
+
shaka.polyfill.MediaCapabilities.install();
|
|
146
|
+
const result = await navigator.mediaCapabilities
|
|
147
|
+
.decodingInfo(mockDecodingConfig);
|
|
148
|
+
|
|
149
|
+
expect(requestKeySystemAccessSpy).toHaveBeenCalledWith(
|
|
150
|
+
'com.widevine.alpha',
|
|
151
|
+
[{
|
|
152
|
+
audioCapabilities: [
|
|
153
|
+
{
|
|
154
|
+
robustness: 'SW_SECURE_CRYPTO',
|
|
155
|
+
contentType: 'audio/mp4; codecs="mp4a.40.2"',
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
distinctiveIdentifier: 'optional',
|
|
159
|
+
initDataTypes: ['cenc'],
|
|
160
|
+
persistentState: 'optional',
|
|
161
|
+
sessionTypes: ['temporary'],
|
|
162
|
+
videoCapabilities: [{
|
|
163
|
+
robustness: 'SW_SECURE_CRYPTO',
|
|
164
|
+
contentType: 'video/mp4; codecs="avc1.4D4015"',
|
|
165
|
+
}],
|
|
166
|
+
}],
|
|
167
|
+
);
|
|
168
|
+
expect(result.keySystemAccess).toEqual(mockResult);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('throws when the cast namespace is not available', async () => {
|
|
172
|
+
// Temporarily remove window.cast to trigger error. It's restored after
|
|
173
|
+
// every test.
|
|
174
|
+
delete window['cast'];
|
|
175
|
+
|
|
176
|
+
const isChromecastSpy =
|
|
177
|
+
spyOn(shaka['util']['Platform'],
|
|
178
|
+
'isChromecast').and.returnValue(true);
|
|
179
|
+
const expected = Util.jasmineError(new shaka.util.Error(
|
|
180
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
181
|
+
shaka.util.Error.Category.CAST,
|
|
182
|
+
shaka.util.Error.Code.CAST_API_UNAVAILABLE));
|
|
183
|
+
const isTypeSupportedSpy =
|
|
184
|
+
spyOn(window['MediaSource'], 'isTypeSupported').and.returnValue(true);
|
|
185
|
+
|
|
186
|
+
shaka.polyfill.MediaCapabilities.install();
|
|
187
|
+
await expectAsync(
|
|
188
|
+
navigator.mediaCapabilities.decodingInfo(mockDecodingConfig))
|
|
189
|
+
.toBeRejectedWith(expected);
|
|
190
|
+
|
|
191
|
+
expect(isTypeSupportedSpy).not.toHaveBeenCalled();
|
|
192
|
+
// 1 (during install()) + 1 (for video config check).
|
|
193
|
+
expect(isChromecastSpy).toHaveBeenCalledTimes(2);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('falls back to isTypeSupported() when canDisplayType() missing',
|
|
197
|
+
async () => {
|
|
198
|
+
// We only set the cast namespace, but not the canDisplayType() API.
|
|
199
|
+
window['cast'] = {};
|
|
200
|
+
const isChromecastSpy =
|
|
201
|
+
spyOn(shaka['util']['Platform'],
|
|
202
|
+
'isChromecast').and.returnValue(true);
|
|
203
|
+
const isTypeSupportedSpy =
|
|
204
|
+
spyOn(window['MediaSource'], 'isTypeSupported')
|
|
205
|
+
.and.returnValue(true);
|
|
206
|
+
|
|
207
|
+
shaka.polyfill.MediaCapabilities.install();
|
|
208
|
+
await navigator.mediaCapabilities.decodingInfo(mockDecodingConfig);
|
|
209
|
+
|
|
210
|
+
expect(mockCanDisplayType).not.toHaveBeenCalled();
|
|
211
|
+
// 1 (during install()) + 1 (for video config check).
|
|
212
|
+
expect(isChromecastSpy).toHaveBeenCalledTimes(2);
|
|
213
|
+
// 1 (fallback in canCastDisplayType()) +
|
|
214
|
+
// 1 (mockDecodingConfig.audio).
|
|
215
|
+
expect(isTypeSupportedSpy).toHaveBeenCalledTimes(2);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('should use cast.__platform__.canDisplayType for "supported" field ' +
|
|
219
|
+
'when platform is Cast', async () => {
|
|
220
|
+
// We're using quotes to access window.cast because the compiler
|
|
221
|
+
// knows about lots of Cast-specific APIs we aren't mocking. We
|
|
222
|
+
// don't need this mock strictly type-checked.
|
|
223
|
+
window['cast'] = {
|
|
224
|
+
__platform__: {canDisplayType: mockCanDisplayType},
|
|
225
|
+
};
|
|
226
|
+
const isChromecastSpy =
|
|
227
|
+
spyOn(shaka['util']['Platform'],
|
|
228
|
+
'isChromecast').and.returnValue(true);
|
|
229
|
+
const isTypeSupportedSpy =
|
|
230
|
+
spyOn(window['MediaSource'], 'isTypeSupported').and.returnValue(true);
|
|
231
|
+
|
|
232
|
+
// Tests an HDR stream's extended MIME type is correctly provided.
|
|
233
|
+
mockDecodingConfig.video.transferFunction = 'pq';
|
|
234
|
+
mockDecodingConfig.video.contentType =
|
|
235
|
+
'video/mp4; codecs="hev1.2.4.L153.B0"';
|
|
236
|
+
// Round to a whole number since we can't rely on number => string
|
|
237
|
+
// conversion precision on all devices.
|
|
238
|
+
mockDecodingConfig.video.framerate = 24;
|
|
239
|
+
mockCanDisplayType.and.callFake((type) => {
|
|
240
|
+
expect(type).toBe(
|
|
241
|
+
'video/mp4; ' +
|
|
242
|
+
'codecs="hev1.2.4.L153.B0"; ' +
|
|
243
|
+
'width=512; ' +
|
|
244
|
+
'height=288; ' +
|
|
245
|
+
'framerate=24; ' +
|
|
246
|
+
'eotf=smpte2084');
|
|
247
|
+
return true;
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
shaka.polyfill.MediaCapabilities.install();
|
|
251
|
+
await navigator.mediaCapabilities.decodingInfo(mockDecodingConfig);
|
|
252
|
+
|
|
253
|
+
// 1 (during install()) + 1 (for video config check).
|
|
254
|
+
expect(isChromecastSpy).toHaveBeenCalledTimes(2);
|
|
255
|
+
// 1 (mockDecodingConfig.audio).
|
|
256
|
+
expect(isTypeSupportedSpy).toHaveBeenCalledTimes(1);
|
|
257
|
+
// Called once in canCastDisplayType.
|
|
258
|
+
expect(mockCanDisplayType).toHaveBeenCalledTimes(1);
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
});
|