shaka-player 4.3.13 → 4.3.15
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/ISSUE_TEMPLATE/bug_report.md +1 -1
- package/.github/ISSUE_TEMPLATE/question.md +7 -0
- package/.github/workflows/release-please.yaml +2 -27
- package/.github/workflows/shaka-bot-commands/lib.sh +37 -12
- package/.github/workflows/shaka-bot-commands/main.sh +4 -2
- package/.github/workflows/talk-to-shaka-bot.yaml +1 -0
- package/AUTHORS +5 -0
- package/CHANGELOG.md +96 -0
- package/CODE_OF_CONDUCT.md +1 -1
- package/CONTRIBUTORS +6 -0
- package/README.md +2 -0
- package/demo/common/asset.js +0 -1
- package/demo/common/assets.js +0 -1
- package/demo/custom.js +2 -0
- package/dist/controls.css +1 -1
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +1 -1
- package/dist/demo.compiled.debug.map +1 -1
- package/dist/demo.compiled.js +1 -1
- package/dist/demo.compiled.map +1 -1
- package/dist/deps.js +6 -6
- package/dist/locales.js +30 -30
- package/dist/receiver.compiled.debug.map +1 -1
- package/dist/receiver.compiled.map +1 -1
- package/dist/shaka-player.compiled.d.ts +25 -5
- package/dist/shaka-player.compiled.debug.d.ts +25 -5
- package/dist/shaka-player.compiled.debug.externs.js +73 -45
- package/dist/shaka-player.compiled.debug.js +426 -406
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +73 -45
- package/dist/shaka-player.compiled.js +912 -903
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +37 -6
- package/dist/shaka-player.ui.debug.d.ts +37 -6
- package/dist/shaka-player.ui.debug.externs.js +74 -45
- package/dist/shaka-player.ui.debug.js +449 -428
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +74 -45
- package/dist/shaka-player.ui.js +1060 -1051
- package/dist/shaka-player.ui.map +4 -4
- package/docs/tutorials/ad_monetization.md +54 -0
- package/docs/tutorials/fairplay.md +2 -2
- package/docs/tutorials/index.json +1 -0
- package/docs/tutorials/plugins.md +4 -0
- package/docs/tutorials/text-displayer.md +15 -0
- package/docs/tutorials/ui-customization.md +29 -0
- package/docs/tutorials/ui.md +4 -1
- package/externs/lcevc.js +6 -0
- package/externs/shaka/manifest_parser.js +8 -0
- package/externs/shaka/player.js +1 -1
- package/externs/shaka/text.js +3 -1
- package/lib/abr/simple_abr_manager.js +1 -1
- package/lib/ads/client_side_ad.js +9 -0
- package/lib/ads/client_side_ad_manager.js +7 -2
- package/lib/ads/server_side_ad_manager.js +4 -0
- package/lib/cast/cast_proxy.js +18 -0
- package/lib/cea/cea608_memory.js +1 -1
- package/lib/cea/cea_decoder.js +11 -1
- package/lib/dash/dash_parser.js +75 -41
- package/lib/dash/mpd_utils.js +39 -27
- package/lib/deprecate/deprecate.js +1 -1
- package/lib/hls/hls_parser.js +106 -28
- package/lib/hls/hls_utils.js +5 -5
- package/lib/media/content_workarounds.js +30 -0
- package/lib/media/drm_engine.js +16 -12
- package/lib/media/manifest_parser.js +2 -1
- package/lib/media/media_source_engine.js +4 -2
- package/lib/media/play_rate_controller.js +1 -0
- package/lib/media/presentation_timeline.js +11 -0
- package/lib/media/quality_observer.js +1 -1
- package/lib/media/segment_reference.js +5 -2
- package/lib/media/streaming_engine.js +50 -30
- package/lib/media/transmuxer.js +1 -1
- package/lib/media/video_wrapper.js +4 -3
- package/lib/offline/offline_manifest_parser.js +5 -0
- package/lib/offline/session_deleter.js +0 -8
- package/lib/offline/storage.js +4 -1
- package/lib/player.js +80 -19
- package/lib/polyfill/media_capabilities.js +23 -3
- package/lib/polyfill/mediasource.js +5 -36
- package/lib/text/mp4_ttml_parser.js +2 -2
- package/lib/text/simple_text_displayer.js +7 -6
- package/lib/text/srt_text_parser.js +2 -2
- package/lib/text/text_engine.js +3 -2
- package/lib/text/ttml_text_parser.js +33 -5
- package/lib/text/ui_text_displayer.js +81 -15
- package/lib/text/vtt_text_parser.js +17 -3
- package/lib/util/buffer_utils.js +26 -4
- package/lib/util/cmcd_manager.js +10 -0
- package/lib/util/error.js +1 -0
- package/lib/util/fairplay_utils.js +1 -1
- package/lib/util/manifest_parser_utils.js +14 -8
- package/lib/util/object_utils.js +1 -1
- package/lib/util/periods.js +91 -36
- package/lib/util/platform.js +39 -2
- package/lib/util/player_configuration.js +5 -3
- package/lib/util/stream_utils.js +36 -28
- package/lib/util/xml_utils.js +5 -0
- package/package.json +3 -3
- package/test/cea/cea_decoder_unit.js +40 -0
- package/test/dash/dash_parser_live_unit.js +5 -8
- package/test/hls/hls_parser_unit.js +78 -3
- package/test/media/buffering_observer_unit.js +13 -0
- package/test/media/content_workarounds_unit.js +1 -1
- package/test/media/drm_engine_unit.js +30 -3
- package/test/media/media_source_engine_unit.js +1 -1
- package/test/media/transmuxer_integration.js +2 -2
- package/test/offline/storage_integration.js +3 -0
- package/test/player_integration.js +49 -41
- package/test/player_load_graph_integration.js +13 -7
- package/test/player_src_equals_integration.js +2 -2
- package/test/player_unit.js +40 -3
- package/test/polyfill/media_capabilities_unit.js +12 -6
- package/test/test/util/fake_drm_engine.js +7 -0
- package/test/test/util/layout_tests.js +2 -1
- package/test/test/util/simple_fakes.js +2 -2
- package/test/test/util/test_scheme.js +3 -0
- package/test/test/util/waiter.js +5 -0
- package/test/text/mp4_ttml_parser_unit.js +4 -4
- package/test/text/simple_text_displayer_unit.js +1 -1
- package/test/text/srt_text_parser_unit.js +1 -1
- package/test/text/text_engine_unit.js +6 -0
- package/test/text/ttml_text_parser_unit.js +49 -18
- package/test/text/ui_text_displayer_unit.js +9 -0
- package/test/text/vtt_text_parser_unit.js +31 -0
- package/test/util/buffer_utils_unit.js +1 -1
- package/test/util/periods_unit.js +113 -0
- package/third_party/closure-uri/uri.js +1 -1
- package/ui/externs/ui.js +24 -0
- package/ui/language_utils.js +11 -5
- package/ui/less/containers.less +1 -0
- package/ui/play_button.js +20 -0
- package/ui/playback_rate_selection.js +4 -0
- package/ui/presentation_time.js +5 -5
- package/ui/small_play_button.js +2 -2
- package/ui/volume_bar.js +10 -6
- /package/.github/{config.yml → ISSUE_TEMPLATE/config.yml} +0 -0
package/lib/util/stream_utils.js
CHANGED
|
@@ -434,19 +434,36 @@ shaka.util.StreamUtils = class {
|
|
|
434
434
|
|
|
435
435
|
await shaka.util.StreamUtils.getDecodingInfosForVariants(
|
|
436
436
|
manifest.variants, usePersistentLicenses, /* srcEquals= */ false);
|
|
437
|
+
|
|
438
|
+
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
439
|
+
const Capabilities = shaka.media.Capabilities;
|
|
440
|
+
const ManifestParserUtils = shaka.util.ManifestParserUtils;
|
|
441
|
+
const MimeUtils = shaka.util.MimeUtils;
|
|
442
|
+
const StreamUtils = shaka.util.StreamUtils;
|
|
443
|
+
const isXboxOne = shaka.util.Platform.isXboxOne();
|
|
444
|
+
|
|
445
|
+
const isFirefoxAndroid = shaka.util.Platform.isFirefox() &&
|
|
446
|
+
shaka.util.Platform.isAndroid();
|
|
447
|
+
|
|
437
448
|
manifest.variants = manifest.variants.filter((variant) => {
|
|
438
449
|
// See: https://github.com/shaka-project/shaka-player/issues/3860
|
|
439
450
|
const video = variant.video;
|
|
451
|
+
const videoWidth = (video && video.width) || 0;
|
|
452
|
+
const videoHeight = (video && video.height) || 0;
|
|
440
453
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
454
|
+
// See: https://github.com/shaka-project/shaka-player/issues/3380
|
|
455
|
+
// Note: it makes sense to drop early
|
|
456
|
+
if (isXboxOne && video &&
|
|
457
|
+
(videoWidth > 1920 || videoHeight > 1080) &&
|
|
458
|
+
(video.codecs.includes('avc1.') || video.codecs.includes('avc3.'))
|
|
459
|
+
) {
|
|
460
|
+
shaka.log.debug('Dropping variant - not compatible with platform',
|
|
461
|
+
StreamUtils.getVariantSummaryString_(variant));
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
446
464
|
|
|
447
465
|
if (video) {
|
|
448
466
|
let videoCodecs = StreamUtils.getCorrectVideoCodecs_(video.codecs);
|
|
449
|
-
|
|
450
467
|
// For multiplexed streams. Here we must check the audio of the
|
|
451
468
|
// stream to see if it is compatible.
|
|
452
469
|
if (video.codecs.includes(',')) {
|
|
@@ -455,7 +472,6 @@ shaka.util.StreamUtils = class {
|
|
|
455
472
|
videoCodecs = ManifestParserUtils.guessCodecs(
|
|
456
473
|
ContentType.VIDEO, allCodecs);
|
|
457
474
|
videoCodecs = StreamUtils.getCorrectVideoCodecs_(videoCodecs);
|
|
458
|
-
|
|
459
475
|
let audioCodecs = ManifestParserUtils.guessCodecs(
|
|
460
476
|
ContentType.AUDIO, allCodecs);
|
|
461
477
|
audioCodecs = StreamUtils.getCorrectAudioCodecs_(audioCodecs);
|
|
@@ -484,6 +500,17 @@ shaka.util.StreamUtils = class {
|
|
|
484
500
|
|
|
485
501
|
const audio = variant.audio;
|
|
486
502
|
|
|
503
|
+
// See: https://github.com/shaka-project/shaka-player/issues/6111
|
|
504
|
+
// It seems that Firefox Android reports that it supports
|
|
505
|
+
// Opus + Widevine, but it is not actually supported.
|
|
506
|
+
// It makes sense to drop early.
|
|
507
|
+
if (isFirefoxAndroid && audio && audio.encrypted &&
|
|
508
|
+
audio.codecs.toLowerCase().includes('opus')) {
|
|
509
|
+
shaka.log.debug('Dropping variant - not compatible with platform',
|
|
510
|
+
StreamUtils.getVariantSummaryString_(variant));
|
|
511
|
+
return false;
|
|
512
|
+
}
|
|
513
|
+
|
|
487
514
|
if (audio) {
|
|
488
515
|
const codecs = StreamUtils.getCorrectAudioCodecs_(audio.codecs);
|
|
489
516
|
const fullType = MimeUtils.getFullOrConvertedType(
|
|
@@ -497,17 +524,6 @@ shaka.util.StreamUtils = class {
|
|
|
497
524
|
audio.codecs = codecs;
|
|
498
525
|
}
|
|
499
526
|
|
|
500
|
-
// See: https://github.com/shaka-project/shaka-player/issues/3380
|
|
501
|
-
if (shaka.util.Platform.isXboxOne() && video &&
|
|
502
|
-
((video.width && video.width > 1920) ||
|
|
503
|
-
(video.height && video.height > 1080)) &&
|
|
504
|
-
(video.codecs.includes('avc1.') ||
|
|
505
|
-
video.codecs.includes('avc3.'))) {
|
|
506
|
-
shaka.log.debug('Dropping variant - not compatible with platform',
|
|
507
|
-
StreamUtils.getVariantSummaryString_(variant));
|
|
508
|
-
return false;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
527
|
const supported = variant.decodingInfos.some((decodingInfo) => {
|
|
512
528
|
return decodingInfo.supported;
|
|
513
529
|
});
|
|
@@ -790,7 +806,7 @@ shaka.util.StreamUtils = class {
|
|
|
790
806
|
// currently don't support 'fLaC', while 'flac' is supported by most
|
|
791
807
|
// major browsers.
|
|
792
808
|
// See https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
|
|
793
|
-
if (codecs === 'fLaC') {
|
|
809
|
+
if (codecs === 'fLaC' && !shaka.util.Platform.isSafari()) {
|
|
794
810
|
return 'flac';
|
|
795
811
|
}
|
|
796
812
|
|
|
@@ -799,15 +815,7 @@ shaka.util.StreamUtils = class {
|
|
|
799
815
|
return 'opus';
|
|
800
816
|
}
|
|
801
817
|
|
|
802
|
-
|
|
803
|
-
// report EC-3 support. So query EC-3 as a fallback for AC-3.
|
|
804
|
-
// See https://github.com/shaka-project/shaka-player/issues/2989 for
|
|
805
|
-
// details.
|
|
806
|
-
if (shaka.util.Platform.isTizen()) {
|
|
807
|
-
return codecs.toLowerCase() == 'ac-3' ? 'ec-3' : codecs;
|
|
808
|
-
} else {
|
|
809
|
-
return codecs;
|
|
810
|
-
}
|
|
818
|
+
return codecs;
|
|
811
819
|
}
|
|
812
820
|
|
|
813
821
|
|
package/lib/util/xml_utils.js
CHANGED
|
@@ -384,6 +384,11 @@ shaka.util.XmlUtils = class {
|
|
|
384
384
|
return null;
|
|
385
385
|
}
|
|
386
386
|
|
|
387
|
+
// Cobalt browser doesn't support document.createNodeIterator.
|
|
388
|
+
if (!('createNodeIterator' in document)) {
|
|
389
|
+
return rootElem;
|
|
390
|
+
}
|
|
391
|
+
|
|
387
392
|
// SECURITY: Verify that the document does not contain elements from the
|
|
388
393
|
// HTML or SVG namespaces, which could trigger script execution and XSS.
|
|
389
394
|
const iterator = document.createNodeIterator(
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shaka-player",
|
|
3
3
|
"description": "DASH/EME video player library",
|
|
4
|
-
"version": "4.3.
|
|
4
|
+
"version": "4.3.15",
|
|
5
5
|
"homepage": "https://github.com/shaka-project/shaka-player",
|
|
6
6
|
"author": "Google",
|
|
7
7
|
"maintainers": [
|
|
@@ -42,13 +42,13 @@
|
|
|
42
42
|
"karma-coverage": "^2.2.0",
|
|
43
43
|
"karma-jasmine": "^4.0.1",
|
|
44
44
|
"karma-jasmine-ajax": "^0.1.13",
|
|
45
|
-
"karma-local-wd-launcher": "^1.6.
|
|
45
|
+
"karma-local-wd-launcher": "^1.6.7",
|
|
46
46
|
"karma-opera-launcher": "^1.0.0",
|
|
47
47
|
"karma-sourcemap-loader": "^0.3.8",
|
|
48
48
|
"karma-spec-reporter": "^0.0.34",
|
|
49
49
|
"karma-webdriver-launcher": "^1.0.8",
|
|
50
50
|
"lcevc_dil.js": "^1.1.20",
|
|
51
|
-
"less": "
|
|
51
|
+
"less": "^4.2.0",
|
|
52
52
|
"less-plugin-clean-css": "github:austingardner/less-plugin-clean-css#4e9e77bf",
|
|
53
53
|
"material-design-lite": "^1.3.0",
|
|
54
54
|
"mux.js": "^6.2.0",
|
|
@@ -44,6 +44,39 @@ describe('CeaDecoder', () => {
|
|
|
44
44
|
decoder.clear();
|
|
45
45
|
});
|
|
46
46
|
|
|
47
|
+
it('painton captions on CC4', () => {
|
|
48
|
+
const controlCount = 0x03;
|
|
49
|
+
const captionData = 0xc0 | controlCount;
|
|
50
|
+
const paintonCaptionCC4Packet = new Uint8Array([
|
|
51
|
+
...atscCaptionInitBytes, captionData, /* padding= */ 0xff,
|
|
52
|
+
0xfd, 0x9d, 0x29, // Paint-on mode (RDC control code).
|
|
53
|
+
0xfd, 0xf4, 0xe5, // t, e
|
|
54
|
+
0xfd, 0x73, 0xf4, // s, t
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
const startTimeCaption1 = 1;
|
|
58
|
+
const startTimeCaption2 = 2;
|
|
59
|
+
const expectedText = 'test';
|
|
60
|
+
|
|
61
|
+
const topLevelCue = new shaka.text.Cue(startTimeCaption1,
|
|
62
|
+
startTimeCaption2, '');
|
|
63
|
+
topLevelCue.nestedCues = [
|
|
64
|
+
CeaUtils.createDefaultCue(
|
|
65
|
+
startTimeCaption1, startTimeCaption2, expectedText),
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
const expectedCaptions = [{
|
|
69
|
+
stream: 'CC4',
|
|
70
|
+
cue: topLevelCue,
|
|
71
|
+
}];
|
|
72
|
+
|
|
73
|
+
decoder.extract(paintonCaptionCC4Packet, startTimeCaption1);
|
|
74
|
+
decoder.extract(eraseDisplayedMemory, startTimeCaption2);
|
|
75
|
+
const captions = decoder.decode();
|
|
76
|
+
|
|
77
|
+
expect(captions).toEqual(expectedCaptions);
|
|
78
|
+
});
|
|
79
|
+
|
|
47
80
|
it('green and underlined popon caption data on CC3', () => {
|
|
48
81
|
const controlCount = 0x08;
|
|
49
82
|
const captionData = 0xc0 | controlCount;
|
|
@@ -486,6 +519,13 @@ describe('CeaDecoder', () => {
|
|
|
486
519
|
|
|
487
520
|
expect(decoder.reset).toHaveBeenCalledTimes(1);
|
|
488
521
|
});
|
|
522
|
+
|
|
523
|
+
it('does not attempt to extract SEI packet that is too short', () => {
|
|
524
|
+
const badData = new Uint8Array([0xb5]);
|
|
525
|
+
decoder.extract(badData, 0);
|
|
526
|
+
const captions = decoder.decode();
|
|
527
|
+
expect(captions.length).toBe(0);
|
|
528
|
+
});
|
|
489
529
|
});
|
|
490
530
|
|
|
491
531
|
describe('decodes CEA-708', () => {
|
|
@@ -607,12 +607,12 @@ describe('DashParser Live', () => {
|
|
|
607
607
|
expect(onError).toHaveBeenCalledTimes(1);
|
|
608
608
|
});
|
|
609
609
|
|
|
610
|
-
it('
|
|
610
|
+
it('still updates when @minimumUpdatePeriod is zero', async () => {
|
|
611
611
|
const lines = [
|
|
612
612
|
'<SegmentTemplate startNumber="1" media="s$Number$.mp4" duration="2" />',
|
|
613
613
|
];
|
|
614
614
|
// updateTime parameter sets @minimumUpdatePeriod in the manifest.
|
|
615
|
-
const manifestText = makeSimpleLiveManifestText(lines, updateTime);
|
|
615
|
+
const manifestText = makeSimpleLiveManifestText(lines, /* updateTime= */ 0);
|
|
616
616
|
|
|
617
617
|
/** @type {!jasmine.Spy} */
|
|
618
618
|
const tickAfter = updateTickSpy();
|
|
@@ -622,16 +622,14 @@ describe('DashParser Live', () => {
|
|
|
622
622
|
await parser.start('dummy://foo', playerInterface);
|
|
623
623
|
|
|
624
624
|
expect(tickAfter).toHaveBeenCalledTimes(1);
|
|
625
|
-
const delay = tickAfter.calls.mostRecent().args[0];
|
|
626
|
-
expect(delay).toBe(updateTime);
|
|
627
625
|
});
|
|
628
626
|
|
|
629
|
-
it('
|
|
627
|
+
it('uses @minimumUpdatePeriod', async () => {
|
|
630
628
|
const lines = [
|
|
631
629
|
'<SegmentTemplate startNumber="1" media="s$Number$.mp4" duration="2" />',
|
|
632
630
|
];
|
|
633
631
|
// updateTime parameter sets @minimumUpdatePeriod in the manifest.
|
|
634
|
-
const manifestText = makeSimpleLiveManifestText(lines,
|
|
632
|
+
const manifestText = makeSimpleLiveManifestText(lines, updateTime);
|
|
635
633
|
|
|
636
634
|
/** @type {!jasmine.Spy} */
|
|
637
635
|
const tickAfter = updateTickSpy();
|
|
@@ -640,10 +638,9 @@ describe('DashParser Live', () => {
|
|
|
640
638
|
fakeNetEngine.setResponseText('dummy://foo', manifestText);
|
|
641
639
|
await parser.start('dummy://foo', playerInterface);
|
|
642
640
|
|
|
643
|
-
const waitTime = shaka.dash.DashParser['MIN_UPDATE_PERIOD_'];
|
|
644
641
|
expect(tickAfter).toHaveBeenCalledTimes(1);
|
|
645
642
|
const delay = tickAfter.calls.mostRecent().args[0];
|
|
646
|
-
expect(delay).toBe(
|
|
643
|
+
expect(delay).toBe(updateTime);
|
|
647
644
|
});
|
|
648
645
|
|
|
649
646
|
it('does not update when @minimumUpdatePeriod is missing', async () => {
|
|
@@ -739,6 +739,45 @@ describe('HlsParser', () => {
|
|
|
739
739
|
await testHlsParser(master, media, manifest);
|
|
740
740
|
});
|
|
741
741
|
|
|
742
|
+
it('parses audio+video variant with global closed captions', async () => {
|
|
743
|
+
const master = [
|
|
744
|
+
'#EXTM3U\n',
|
|
745
|
+
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",CHANNELS="2",',
|
|
746
|
+
'URI="audio"\n',
|
|
747
|
+
'#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,GROUP-ID="cap1",LANGUAGE="eng",',
|
|
748
|
+
'INSTREAM-ID="CC1"\n',
|
|
749
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
|
750
|
+
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
|
|
751
|
+
'video\n',
|
|
752
|
+
].join('');
|
|
753
|
+
|
|
754
|
+
const media = [
|
|
755
|
+
'#EXTM3U\n',
|
|
756
|
+
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
|
757
|
+
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
|
758
|
+
'#EXTINF:5,\n',
|
|
759
|
+
'#EXT-X-BYTERANGE:121090@616\n',
|
|
760
|
+
'main.mp4',
|
|
761
|
+
].join('');
|
|
762
|
+
|
|
763
|
+
const closedCaptions = new Map([['CC1', 'en']]);
|
|
764
|
+
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
|
765
|
+
manifest.anyTimeline();
|
|
766
|
+
manifest.addPartialVariant((variant) => {
|
|
767
|
+
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
|
768
|
+
stream.closedCaptions = closedCaptions;
|
|
769
|
+
stream.mime('video/mp4', 'avc1');
|
|
770
|
+
});
|
|
771
|
+
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
|
772
|
+
stream.mime('audio/mp4', 'mp4a');
|
|
773
|
+
});
|
|
774
|
+
});
|
|
775
|
+
manifest.sequenceMode = true;
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
await testHlsParser(master, media, manifest);
|
|
779
|
+
});
|
|
780
|
+
|
|
742
781
|
it('parses audio+video variant with no closed captions', async () => {
|
|
743
782
|
const master = [
|
|
744
783
|
'#EXTM3U\n',
|
|
@@ -2926,7 +2965,8 @@ describe('HlsParser', () => {
|
|
|
2926
2965
|
const media = [
|
|
2927
2966
|
'#EXTM3U\n',
|
|
2928
2967
|
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
|
2929
|
-
'#EXT-X-
|
|
2968
|
+
'#EXT-X-BYTERANGE:616@0\n',
|
|
2969
|
+
'#EXT-X-MAP:URI="init.mp4"\n',
|
|
2930
2970
|
'#EXTINF:5,\n',
|
|
2931
2971
|
'main.mp4\n',
|
|
2932
2972
|
'#EXT-X-MAP:URI="init2.mp4",BYTERANGE="616@0"\n',
|
|
@@ -2954,8 +2994,15 @@ describe('HlsParser', () => {
|
|
|
2954
2994
|
// uri.
|
|
2955
2995
|
const initSegments = Array.from(actualVideo.segmentIndex).map(
|
|
2956
2996
|
(seg) => seg.initSegmentReference);
|
|
2957
|
-
expect(initSegments
|
|
2958
|
-
|
|
2997
|
+
expect(initSegments.length).toBe(2);
|
|
2998
|
+
const firstInitSegment = initSegments[0];
|
|
2999
|
+
expect(firstInitSegment.getUris()[0]).toBe('test:/init.mp4');
|
|
3000
|
+
expect(firstInitSegment.startByte).toBe(0);
|
|
3001
|
+
expect(firstInitSegment.endByte).toBe(615);
|
|
3002
|
+
const secondInitSegment = initSegments[1];
|
|
3003
|
+
expect(secondInitSegment.getUris()[0]).toBe('test:/init2.mp4');
|
|
3004
|
+
expect(secondInitSegment.startByte).toBe(0);
|
|
3005
|
+
expect(secondInitSegment.endByte).toBe(615);
|
|
2959
3006
|
});
|
|
2960
3007
|
|
|
2961
3008
|
it('parses variants encrypted with AES-128', async () => {
|
|
@@ -3696,6 +3743,34 @@ describe('HlsParser', () => {
|
|
|
3696
3743
|
await verifyError(master, media, error, /* onCreateSegmentIndex= */ true);
|
|
3697
3744
|
});
|
|
3698
3745
|
|
|
3746
|
+
it('if SAMPLE-AES encryption with MSE and mp2t content', async () => {
|
|
3747
|
+
const master = [
|
|
3748
|
+
'#EXTM3U\n',
|
|
3749
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
|
|
3750
|
+
'RESOLUTION=960x540,FRAME-RATE=60\n',
|
|
3751
|
+
'video\n',
|
|
3752
|
+
].join('');
|
|
3753
|
+
|
|
3754
|
+
const media = [
|
|
3755
|
+
'#EXTM3U\n',
|
|
3756
|
+
'#EXT-X-TARGETDURATION:6\n',
|
|
3757
|
+
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
|
3758
|
+
'#EXT-X-KEY:METHOD=SAMPLE-AES,',
|
|
3759
|
+
'URI="fake",\n',
|
|
3760
|
+
'#EXTINF:5,\n',
|
|
3761
|
+
'#EXT-X-BYTERANGE:121090@616\n',
|
|
3762
|
+
'main.ts',
|
|
3763
|
+
].join('');
|
|
3764
|
+
|
|
3765
|
+
const error = new shaka.util.Error(
|
|
3766
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
3767
|
+
shaka.util.Error.Category.MANIFEST,
|
|
3768
|
+
Code.HLS_MSE_ENCRYPTED_MP2T_NOT_SUPPORTED);
|
|
3769
|
+
|
|
3770
|
+
await verifyError(master, media, error, /* onCreateSegmentIndex= */ true);
|
|
3771
|
+
});
|
|
3772
|
+
|
|
3773
|
+
|
|
3699
3774
|
describe('if required tags are missing', () => {
|
|
3700
3775
|
/**
|
|
3701
3776
|
* @param {string} master
|
|
@@ -11,15 +11,28 @@ describe('BufferingObserver', () => {
|
|
|
11
11
|
const thresholdAfterStarving = 5;
|
|
12
12
|
const thresholdAfterSatisfied = 2;
|
|
13
13
|
|
|
14
|
+
const originalDateNow = Date.now;
|
|
15
|
+
|
|
16
|
+
/** @type {!Date} */
|
|
17
|
+
let baseTime;
|
|
18
|
+
|
|
14
19
|
/** @type {!shaka.media.BufferingObserver} */
|
|
15
20
|
let controller;
|
|
16
21
|
|
|
17
22
|
beforeEach(() => {
|
|
23
|
+
// Any test using Date needs to have Date mocked to avoid flake.
|
|
24
|
+
baseTime = new Date(2015, 11, 30);
|
|
25
|
+
Date.now = () => baseTime.getTime();
|
|
26
|
+
|
|
18
27
|
controller = new BufferingObserver(
|
|
19
28
|
thresholdAfterStarving,
|
|
20
29
|
thresholdAfterSatisfied);
|
|
21
30
|
});
|
|
22
31
|
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
Date.now = originalDateNow;
|
|
34
|
+
});
|
|
35
|
+
|
|
23
36
|
describe('when satisfied', () => {
|
|
24
37
|
beforeEach(() => {
|
|
25
38
|
controller.setState(State.SATISFIED);
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
describe('ContentWorkarounds', () => {
|
|
8
8
|
const encryptionBoxes = {
|
|
9
9
|
'encv': ['hev1', 'hvc1', 'avc1', 'avc3'],
|
|
10
|
-
'enca': ['ac-3', 'ec-3', 'mp4a'],
|
|
10
|
+
'enca': ['ac-3', 'ec-3', 'ac-4', 'mp4a'],
|
|
11
11
|
};
|
|
12
12
|
for (const encryptionBox of Object.keys(encryptionBoxes)) {
|
|
13
13
|
for (const box of encryptionBoxes[encryptionBox]) {
|
|
@@ -2100,7 +2100,7 @@ describe('DrmEngine', () => {
|
|
|
2100
2100
|
videoRobustness: 'really_really_ridiculously_good',
|
|
2101
2101
|
serverCertificate: serverCert,
|
|
2102
2102
|
serverCertificateUri: '',
|
|
2103
|
-
initData: ['
|
|
2103
|
+
initData: [{keyId: 'a'}],
|
|
2104
2104
|
keyIds: new Set(['deadbeefdeadbeefdeadbeefdeadbeef']),
|
|
2105
2105
|
};
|
|
2106
2106
|
const drmInfoAudio = {
|
|
@@ -2111,7 +2111,7 @@ describe('DrmEngine', () => {
|
|
|
2111
2111
|
audioRobustness: 'good',
|
|
2112
2112
|
serverCertificate: undefined,
|
|
2113
2113
|
serverCertificateUri: '',
|
|
2114
|
-
initData: [
|
|
2114
|
+
initData: [{keyId: 'b'}],
|
|
2115
2115
|
keyIds: new Set(['eadbeefdeadbeefdeadbeefdeadbeefd']),
|
|
2116
2116
|
};
|
|
2117
2117
|
const drmInfoDesired = {
|
|
@@ -2123,7 +2123,7 @@ describe('DrmEngine', () => {
|
|
|
2123
2123
|
videoRobustness: 'really_really_ridiculously_good',
|
|
2124
2124
|
serverCertificate: serverCert,
|
|
2125
2125
|
serverCertificateUri: '',
|
|
2126
|
-
initData: ['
|
|
2126
|
+
initData: [{keyId: 'a'}, {keyId: 'b'}],
|
|
2127
2127
|
keyIds: new Set([
|
|
2128
2128
|
'deadbeefdeadbeefdeadbeefdeadbeef',
|
|
2129
2129
|
'eadbeefdeadbeefdeadbeefdeadbeefd',
|
|
@@ -2177,6 +2177,33 @@ describe('DrmEngine', () => {
|
|
|
2177
2177
|
[drmInfoAudio]);
|
|
2178
2178
|
expect(returned).toEqual([drmInfoDesired]);
|
|
2179
2179
|
});
|
|
2180
|
+
|
|
2181
|
+
it('does not match incompatible drmInfos', () => {
|
|
2182
|
+
// Different key systems do not match.
|
|
2183
|
+
const drmInfo1 = {
|
|
2184
|
+
keySystem: 'drm.abc',
|
|
2185
|
+
licenseServerUri: undefined,
|
|
2186
|
+
distinctiveIdentifierRequired: false,
|
|
2187
|
+
persistentStateRequired: false,
|
|
2188
|
+
serverCertificate: undefined,
|
|
2189
|
+
serverCertificateUri: '',
|
|
2190
|
+
initData: [],
|
|
2191
|
+
keyIds: new Set(),
|
|
2192
|
+
};
|
|
2193
|
+
const drmInfo2 = {
|
|
2194
|
+
keySystem: 'drm.foobar',
|
|
2195
|
+
licenseServerUri: undefined,
|
|
2196
|
+
distinctiveIdentifierRequired: false,
|
|
2197
|
+
persistentStateRequired: false,
|
|
2198
|
+
serverCertificate: undefined,
|
|
2199
|
+
serverCertificateUri: '',
|
|
2200
|
+
initData: [],
|
|
2201
|
+
keyIds: new Set(),
|
|
2202
|
+
};
|
|
2203
|
+
const returned1 = shaka.media.DrmEngine.getCommonDrmInfos(
|
|
2204
|
+
[drmInfo1], [drmInfo2]);
|
|
2205
|
+
expect(returned1).toEqual([]);
|
|
2206
|
+
});
|
|
2180
2207
|
}); // describe('getCommonDrmInfos')
|
|
2181
2208
|
|
|
2182
2209
|
describe('configure', () => {
|
|
@@ -560,7 +560,7 @@ describe('MediaSourceEngine', () => {
|
|
|
560
560
|
await mediaSourceEngine.appendBuffer(
|
|
561
561
|
ContentType.TEXT, data, reference, /* hasClosedCaptions= */ false);
|
|
562
562
|
expect(mockTextEngine.appendBuffer).toHaveBeenCalledWith(
|
|
563
|
-
data, 0, 10);
|
|
563
|
+
data, 0, 10, 'foo://bar');
|
|
564
564
|
});
|
|
565
565
|
|
|
566
566
|
it('appends transmuxed data and captions', async () => {
|
|
@@ -102,8 +102,8 @@ describe('Transmuxer', () => {
|
|
|
102
102
|
});
|
|
103
103
|
});
|
|
104
104
|
|
|
105
|
-
it('
|
|
106
|
-
expect(transmuxer.
|
|
105
|
+
it('getOriginalMimeType returns the correct mimeType', () => {
|
|
106
|
+
expect(transmuxer.getOriginalMimeType()).toBe(transmuxerMimeType);
|
|
107
107
|
});
|
|
108
108
|
|
|
109
109
|
describe('transmuxing', () => {
|
|
@@ -59,6 +59,46 @@ describe('Player', () => {
|
|
|
59
59
|
document.body.removeChild(video);
|
|
60
60
|
});
|
|
61
61
|
|
|
62
|
+
describe('seek range', () => {
|
|
63
|
+
// Regression test for Issue #3675.
|
|
64
|
+
it('has a reasonable seek range after going from live to VOD', async () => {
|
|
65
|
+
const netEngine = player.getNetworkingEngine();
|
|
66
|
+
const startTime = Date.now();
|
|
67
|
+
netEngine.registerRequestFilter((type, request) => {
|
|
68
|
+
if (type != shaka.net.NetworkingEngine.RequestType.MANIFEST) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
// Simulate a live stream by providing different manifests over time.
|
|
72
|
+
const time = (Date.now() - startTime) / 1000;
|
|
73
|
+
const manifestNumber = Math.min(5, Math.floor(0.5 + time / 2));
|
|
74
|
+
request.uris = [
|
|
75
|
+
'/base/test/test/assets/3675/dash_' + manifestNumber + '.mpd',
|
|
76
|
+
];
|
|
77
|
+
console.log('getting manifest', request.uris);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Play the stream.
|
|
81
|
+
await player.load('/base/test/test/assets/3675/dash_0.mpd');
|
|
82
|
+
await video.play();
|
|
83
|
+
|
|
84
|
+
// Wait for the stream to be over.
|
|
85
|
+
eventManager.listen(player, 'error', Util.spyFunc(onErrorSpy));
|
|
86
|
+
/** @type {shaka.test.Waiter} */
|
|
87
|
+
const waiter = new shaka.test.Waiter(eventManager)
|
|
88
|
+
.setPlayer(player)
|
|
89
|
+
.timeoutAfter(40)
|
|
90
|
+
.failOnTimeout(true);
|
|
91
|
+
await waiter.waitForEnd(video);
|
|
92
|
+
|
|
93
|
+
// The stream should have transitioned to VOD by now.
|
|
94
|
+
expect(player.isLive()).toBe(false);
|
|
95
|
+
|
|
96
|
+
// Check that the final seek range is as expected.
|
|
97
|
+
const seekRange = player.seekRange();
|
|
98
|
+
expect(seekRange.end).toBeCloseTo(14);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
62
102
|
describe('attach', () => {
|
|
63
103
|
beforeEach(async () => {
|
|
64
104
|
// To test attach, we want to construct a player without a video element
|
|
@@ -903,46 +943,6 @@ describe('Player', () => {
|
|
|
903
943
|
});
|
|
904
944
|
});
|
|
905
945
|
|
|
906
|
-
describe('seek range', () => {
|
|
907
|
-
// Regression test for Issue #3675.
|
|
908
|
-
it('has a reasonable seek range after going from live to VOD', async () => {
|
|
909
|
-
const netEngine = player.getNetworkingEngine();
|
|
910
|
-
const startTime = Date.now();
|
|
911
|
-
netEngine.registerRequestFilter((type, request) => {
|
|
912
|
-
if (type != shaka.net.NetworkingEngine.RequestType.MANIFEST) {
|
|
913
|
-
return;
|
|
914
|
-
}
|
|
915
|
-
// Simulate a live stream by providing different manifests over time.
|
|
916
|
-
const time = (Date.now() - startTime) / 1000;
|
|
917
|
-
const manifestNumber = Math.min(5, Math.floor(0.5 + time / 2));
|
|
918
|
-
request.uris = [
|
|
919
|
-
'/base/test/test/assets/3675/dash_' + manifestNumber + '.mpd',
|
|
920
|
-
];
|
|
921
|
-
console.log('getting manifest', request.uris);
|
|
922
|
-
});
|
|
923
|
-
|
|
924
|
-
// Play the stream.
|
|
925
|
-
await player.load('/base/test/test/assets/3675/dash_0.mpd');
|
|
926
|
-
await video.play();
|
|
927
|
-
|
|
928
|
-
// Wait for the stream to be over.
|
|
929
|
-
eventManager.listen(player, 'error', Util.spyFunc(onErrorSpy));
|
|
930
|
-
/** @type {shaka.test.Waiter} */
|
|
931
|
-
const waiter = new shaka.test.Waiter(eventManager)
|
|
932
|
-
.setPlayer(player)
|
|
933
|
-
.timeoutAfter(40)
|
|
934
|
-
.failOnTimeout(true);
|
|
935
|
-
await waiter.waitForEnd(video);
|
|
936
|
-
|
|
937
|
-
// The stream should have transitioned to VOD by now.
|
|
938
|
-
expect(player.isLive()).toBe(false);
|
|
939
|
-
|
|
940
|
-
// Check that the final seek range is as expected.
|
|
941
|
-
const seekRange = player.seekRange();
|
|
942
|
-
expect(seekRange.end).toBeCloseTo(14);
|
|
943
|
-
});
|
|
944
|
-
});
|
|
945
|
-
|
|
946
946
|
describe('buffering', () => {
|
|
947
947
|
const startBuffering = jasmine.objectContaining({buffering: true});
|
|
948
948
|
const endBuffering = jasmine.objectContaining({buffering: false});
|
|
@@ -1197,13 +1197,21 @@ describe('Player', () => {
|
|
|
1197
1197
|
'com.widevine.alpha': bogusUrl,
|
|
1198
1198
|
'com.microsoft.playready': bogusUrl,
|
|
1199
1199
|
});
|
|
1200
|
-
|
|
1200
|
+
|
|
1201
|
+
// This load may be interrupted, so ignore errors and don't wait.
|
|
1202
|
+
const loadPromise =
|
|
1203
|
+
player.load('test:sintel-enc_compiled').catch(() => {});
|
|
1201
1204
|
|
|
1202
1205
|
await errorPromise;
|
|
1203
1206
|
expect(unloadPromise).not.toBeNull();
|
|
1207
|
+
|
|
1204
1208
|
if (unloadPromise) {
|
|
1205
1209
|
await unloadPromise;
|
|
1206
1210
|
}
|
|
1211
|
+
|
|
1212
|
+
// This should be done, and errors ignored. But don't leave any Promise
|
|
1213
|
+
// unresolved.
|
|
1214
|
+
await loadPromise;
|
|
1207
1215
|
});
|
|
1208
1216
|
}); // describe('unloading')
|
|
1209
1217
|
|
|
@@ -464,11 +464,11 @@ describe('Player Load Graph', () => {
|
|
|
464
464
|
|
|
465
465
|
// Make the two requests one-after-another so that we don't have any idle
|
|
466
466
|
// time between them.
|
|
467
|
-
const
|
|
468
|
-
const
|
|
467
|
+
const attach = player.attach(video);
|
|
468
|
+
const load = player.load('test:sintel');
|
|
469
469
|
|
|
470
|
-
await
|
|
471
|
-
await expectAsync(
|
|
470
|
+
await attach;
|
|
471
|
+
await expectAsync(load).toBeRejected();
|
|
472
472
|
|
|
473
473
|
// Wait a couple interrupter cycles to allow the player to enter idle
|
|
474
474
|
// state.
|
|
@@ -506,10 +506,12 @@ describe('Player Load Graph', () => {
|
|
|
506
506
|
// Normally the player would initialize media source after attaching to
|
|
507
507
|
// the media element, however since we don't support media source, it
|
|
508
508
|
// should stop at the attach state.
|
|
509
|
-
player.attach(video, /* initMediaSource= */ true);
|
|
509
|
+
const attach = player.attach(video, /* initMediaSource= */ true);
|
|
510
510
|
|
|
511
511
|
const event = await spyIsCalled(stateIdleSpy);
|
|
512
512
|
expect(event.state).toBe('attach');
|
|
513
|
+
|
|
514
|
+
await attach;
|
|
513
515
|
});
|
|
514
516
|
|
|
515
517
|
it('loading ignores media source path', async () => {
|
|
@@ -518,10 +520,12 @@ describe('Player Load Graph', () => {
|
|
|
518
520
|
// Normally the player would load content like this with the media source
|
|
519
521
|
// path, but since we don't have media source support, it should use the
|
|
520
522
|
// src= path.
|
|
521
|
-
player.load(SMALL_MP4_CONTENT_URI);
|
|
523
|
+
const load = player.load(SMALL_MP4_CONTENT_URI);
|
|
522
524
|
|
|
523
525
|
const event = await spyIsCalled(stateIdleSpy);
|
|
524
526
|
expect(event.state).toBe('src-equals');
|
|
527
|
+
|
|
528
|
+
await load;
|
|
525
529
|
});
|
|
526
530
|
|
|
527
531
|
it('unloading ignores init media source flag', async () => {
|
|
@@ -531,10 +535,12 @@ describe('Player Load Graph', () => {
|
|
|
531
535
|
// Normally the player would try to go to the media source state because
|
|
532
536
|
// we are saying to initialize media source after unloading, but since we
|
|
533
537
|
// don't have media source, it should stop at the attach state.
|
|
534
|
-
player.unload(/* initMediaSource= */ true);
|
|
538
|
+
const unload = player.unload(/* initMediaSource= */ true);
|
|
535
539
|
|
|
536
540
|
const event = await spyIsCalled(stateIdleSpy);
|
|
537
541
|
expect(event.state).toBe('attach');
|
|
542
|
+
|
|
543
|
+
await unload;
|
|
538
544
|
});
|
|
539
545
|
});
|
|
540
546
|
|