UNPKG

shaka-player

Version:
1,165 lines (1,057 loc) 41.5 kB
/*! @license * Shaka Player * Copyright 2016 Google LLC * SPDX-License-Identifier: Apache-2.0 */ describe('StreamUtils', () => { const StreamUtils = shaka.util.StreamUtils; let manifest; /** @type {!jasmine.Spy} */ let decodingInfoSpy; const originalDecodingInfo = navigator.mediaCapabilities.decodingInfo; beforeEach(() => { decodingInfoSpy = jasmine.createSpy('decodingInfo'); }); afterEach(() => { navigator.mediaCapabilities.decodingInfo = originalDecodingInfo; }); describe('filterStreamsByLanguageAndRole', () => { it('chooses text streams in user\'s preferred language', () => { manifest = shaka.test.ManifestGenerator.generate((manifest) => { manifest.addTextStream(1, (stream) => { stream.language = 'en'; }); manifest.addTextStream(2, (stream) => { stream.language = 'es'; }); manifest.addTextStream(3, (stream) => { stream.language = 'en'; }); }); const chosen = StreamUtils.filterStreamsByLanguageAndRole( manifest.textStreams, 'en', '', false); expect(chosen.length).toBe(2); expect(chosen[0]).toBe(manifest.textStreams[0]); expect(chosen[1]).toBe(manifest.textStreams[2]); }); it('chooses text streams in user\'s preferred forced language', () => { manifest = shaka.test.ManifestGenerator.generate((manifest) => { manifest.addTextStream(1, (stream) => { stream.language = 'en'; }); manifest.addTextStream(2, (stream) => { stream.language = 'es'; }); manifest.addTextStream(3, (stream) => { stream.language = 'en'; stream.forced = true; }); }); const chosen = StreamUtils.filterStreamsByLanguageAndRole( manifest.textStreams, 'en', '', true); expect(chosen.length).toBe(1); expect(chosen[0]).toBe(manifest.textStreams[2]); }); it('no chooses text streams if there are not forced language', () => { manifest = shaka.test.ManifestGenerator.generate((manifest) => { manifest.addTextStream(1, (stream) => { stream.language = 'en'; }); manifest.addTextStream(2, (stream) => { stream.language = 'es'; }); }); const chosen = StreamUtils.filterStreamsByLanguageAndRole( manifest.textStreams, 'es', '', true); expect(chosen.length).toBe(0); }); it('chooses primary text streams', () => { manifest = shaka.test.ManifestGenerator.generate((manifest) => { manifest.addTextStream(1); manifest.addTextStream(2, (stream) => { stream.primary = true; }); manifest.addTextStream(3, (stream) => { stream.primary = true; }); }); const chosen = StreamUtils.filterStreamsByLanguageAndRole( manifest.textStreams, 'en', '', false); expect(chosen.length).toBe(2); expect(chosen[0]).toBe(manifest.textStreams[1]); expect(chosen[1]).toBe(manifest.textStreams[2]); }); it('chooses text streams in preferred language and role', () => { manifest = shaka.test.ManifestGenerator.generate((manifest) => { manifest.addTextStream(1, (stream) => { stream.language = 'en'; stream.roles = ['main', 'commentary']; }); manifest.addTextStream(2, (stream) => { stream.language = 'es'; }); manifest.addTextStream(3, (stream) => { stream.language = 'en'; stream.roles = ['caption']; }); }); const chosen = StreamUtils.filterStreamsByLanguageAndRole( manifest.textStreams, 'en', 'main', false); expect(chosen.length).toBe(1); expect(chosen[0]).toBe(manifest.textStreams[0]); }); it('prefers no-role streams if there is no preferred role', () => { manifest = shaka.test.ManifestGenerator.generate((manifest) => { manifest.addTextStream(0, (stream) => { stream.language = 'en'; stream.roles = ['commentary']; }); manifest.addTextStream(1, (stream) => { stream.language = 'en'; }); manifest.addTextStream(2, (stream) => { stream.language = 'en'; stream.roles = ['secondary']; }); }); const chosen = StreamUtils.filterStreamsByLanguageAndRole( manifest.textStreams, 'en', '', false); expect(chosen.length).toBe(1); expect(chosen[0].roles.length).toBe(0); // Pick a stream with no role. }); it('ignores no-role streams if there is a preferred role', () => { manifest = shaka.test.ManifestGenerator.generate((manifest) => { manifest.addTextStream(0, (stream) => { stream.language = 'en'; stream.roles = ['commentary']; }); manifest.addTextStream(1, (stream) => { stream.language = 'en'; }); manifest.addTextStream(2, (stream) => { stream.language = 'en'; stream.roles = ['secondary']; }); }); const chosen = StreamUtils.filterStreamsByLanguageAndRole( manifest.textStreams, 'en', 'main', false); // A role that is not present. expect(chosen.length).toBe(1); expect(chosen[0].roles.length).toBe(1); // Pick a stream with a role. }); it('chooses only one role, even if none is preferred', () => { // Regression test for https://github.com/shaka-project/shaka-player/issues/949 manifest = shaka.test.ManifestGenerator.generate((manifest) => { manifest.addTextStream(0, (stream) => { stream.language = 'en'; stream.roles = ['commentary']; }); manifest.addTextStream(1, (stream) => { stream.language = 'en'; stream.roles = ['commentary']; }); manifest.addTextStream(2, (stream) => { stream.language = 'en'; stream.roles = ['secondary']; }); manifest.addTextStream(3, (stream) => { stream.language = 'en'; stream.roles = ['secondary']; }); manifest.addTextStream(4, (stream) => { stream.language = 'en'; stream.roles = ['main']; }); manifest.addTextStream(5, (stream) => { stream.language = 'en'; stream.roles = ['main']; }); }); const chosen = StreamUtils.filterStreamsByLanguageAndRole( manifest.textStreams, 'en', '', false); // Which role is chosen is an implementation detail. // Each role is found on two text streams, so we should have two. expect(chosen.length).toBe(2); expect(chosen[0].roles[0]).toBe(chosen[1].roles[0]); }); it('chooses only one role, even if all are primary', () => { // Regression test for https://github.com/shaka-project/shaka-player/issues/949 manifest = shaka.test.ManifestGenerator.generate((manifest) => { manifest.addTextStream(0, (stream) => { stream.language = 'en'; stream.primary = true; stream.roles = ['commentary']; }); manifest.addTextStream(1, (stream) => { stream.language = 'en'; stream.primary = true; stream.roles = ['commentary']; }); manifest.addTextStream(2, (stream) => { stream.language = 'en'; stream.primary = true; stream.roles = ['secondary']; }); manifest.addTextStream(3, (stream) => { stream.language = 'en'; stream.primary = true; stream.roles = ['secondary']; }); manifest.addTextStream(4, (stream) => { stream.language = 'en'; stream.primary = true; stream.roles = ['main']; }); manifest.addTextStream(5, (stream) => { stream.language = 'en'; stream.primary = true; stream.roles = ['main']; }); }); const chosen = StreamUtils.filterStreamsByLanguageAndRole( manifest.textStreams, 'zh', '', false); // Which role is chosen is an implementation detail. // Each role is found on two text streams, so we should have two. expect(chosen.length).toBe(2); expect(chosen[0].roles[0]).toBe(chosen[1].roles[0]); }); it('chooses only one language, even if all are primary', () => { // Regression test for https://github.com/shaka-project/shaka-player/issues/918 manifest = shaka.test.ManifestGenerator.generate((manifest) => { manifest.addTextStream(0, (stream) => { stream.language = 'en'; stream.primary = true; }); manifest.addTextStream(1, (stream) => { stream.language = 'en'; stream.primary = true; }); manifest.addTextStream(2, (stream) => { stream.language = 'es'; stream.primary = true; }); manifest.addTextStream(3, (stream) => { stream.language = 'es'; stream.primary = true; }); }); const chosen = StreamUtils.filterStreamsByLanguageAndRole( manifest.textStreams, 'zh', '', false); // Which language is chosen is an implementation detail. // Each role is found on two variants, so we should have two. expect(chosen.length).toBe(2); expect(chosen[0].language).toBe(chosen[1].language); }); it('chooses a role from among primary streams without language match', () => { manifest = shaka.test.ManifestGenerator.generate((manifest) => { manifest.addTextStream(0, (stream) => { stream.language = 'en'; stream.primary = true; stream.roles = ['commentary']; }); manifest.addTextStream(1, (stream) => { stream.language = 'en'; stream.primary = true; stream.roles = ['commentary']; }); manifest.addTextStream(2, (stream) => { stream.language = 'en'; stream.roles = ['secondary']; }); manifest.addTextStream(3, (stream) => { stream.language = 'en'; stream.roles = ['secondary']; }); manifest.addTextStream(4, (stream) => { stream.language = 'en'; stream.primary = true; stream.roles = ['main']; }); manifest.addTextStream(5, (stream) => { stream.language = 'en'; stream.primary = true; stream.roles = ['main']; }); }); const chosen = StreamUtils.filterStreamsByLanguageAndRole( manifest.textStreams, 'zh', '', false); // Which role is chosen is an implementation detail. // Each role is found on two text streams, so we should have two. expect(chosen.length).toBe(2); expect(chosen[0].roles[0]).toBe(chosen[1].roles[0]); // Since nothing matches our language preference, we chose primary // text streams. expect(chosen[0].primary).toBe(true); expect(chosen[1].primary).toBe(true); }); it('chooses a role from best language match, in spite of primary', () => { manifest = shaka.test.ManifestGenerator.generate((manifest) => { manifest.addTextStream(0, (stream) => { stream.language = 'en'; stream.primary = true; stream.roles = ['commentary']; }); manifest.addTextStream(1, (stream) => { stream.language = 'en'; stream.primary = true; stream.roles = ['commentary']; }); manifest.addTextStream(2, (stream) => { stream.language = 'zh'; stream.roles = ['secondary']; }); manifest.addTextStream(3, (stream) => { stream.language = 'zh'; stream.roles = ['secondary']; }); manifest.addTextStream(4, (stream) => { stream.language = 'en'; stream.primary = true; stream.roles = ['main']; }); manifest.addTextStream(5, (stream) => { stream.language = 'en'; stream.primary = true; stream.roles = ['main']; }); }); const chosen = StreamUtils.filterStreamsByLanguageAndRole( manifest.textStreams, 'zh', '', false); expect(chosen.length).toBe(2); expect(chosen[0].language).toBe('zh'); expect(chosen[1].language).toBe('zh'); expect(chosen[0].primary).toBe(false); expect(chosen[1].primary).toBe(false); }); }); describe('filterVariantsByAudioChannelCount', () => { it('chooses variants with preferred audio channels count', () => { manifest = shaka.test.ManifestGenerator.generate((manifest) => { manifest.addVariant(0, (variant) => { variant.addAudio(0, (stream) => { stream.channelsCount = 2; }); }); manifest.addVariant(1, (variant) => { variant.addAudio(1, (stream) => { stream.channelsCount = 6; }); }); manifest.addVariant(2, (variant) => { variant.addAudio(2, (stream) => { stream.channelsCount = 2; }); }); }); const chosen = StreamUtils.filterVariantsByAudioChannelCount( manifest.variants, 2); expect(chosen.length).toBe(2); expect(chosen[0]).toBe(manifest.variants[0]); expect(chosen[1]).toBe(manifest.variants[2]); }); it('chooses variants with largest audio channel count less than config' + ' when no exact audio channel count match is possible', () => { manifest = shaka.test.ManifestGenerator.generate((manifest) => { manifest.addVariant(0, (variant) => { variant.addAudio(0, (stream) => { stream.channelsCount = 2; }); }); manifest.addVariant(1, (variant) => { variant.addAudio(1, (stream) => { stream.channelsCount = 8; }); }); manifest.addVariant(2, (variant) => { variant.addAudio(2, (stream) => { stream.channelsCount = 2; }); }); }); const chosen = StreamUtils.filterVariantsByAudioChannelCount( manifest.variants, 6); expect(chosen.length).toBe(2); expect(chosen[0]).toBe(manifest.variants[0]); expect(chosen[1]).toBe(manifest.variants[2]); }); it('chooses variants with fewest audio channels when none fit in the ' + 'config', () => { manifest = shaka.test.ManifestGenerator.generate((manifest) => { manifest.addVariant(0, (variant) => { variant.addAudio(0, (stream) => { stream.channelsCount = 6; }); }); manifest.addVariant(1, (variant) => { variant.addAudio(1, (stream) => { stream.channelsCount = 8; }); }); manifest.addVariant(2, (variant) => { variant.addAudio(2, (stream) => { stream.channelsCount = 6; }); }); }); const chosen = StreamUtils.filterVariantsByAudioChannelCount( manifest.variants, 2); expect(chosen.length).toBe(2); expect(chosen[0]).toBe(manifest.variants[0]); expect(chosen[1]).toBe(manifest.variants[2]); }); }); describe('getDecodingInfosForVariants', () => { it('for multiplexd content', async () => { manifest = shaka.test.ManifestGenerator.generate((manifest) => { manifest.addVariant(0, (variant) => { variant.addVideo(1, (stream) => { stream.mime('video/mp2t', 'avc1.4d400d,mp4a.40.2'); }); }); }); await StreamUtils.getDecodingInfosForVariants(manifest.variants, /* usePersistentLicenses= */false, /* srcEquals= */ false, /* preferredKeySystems= */ []); expect(manifest.variants.length).toBeTruthy(); expect(manifest.variants[0].decodingInfos.length).toBe(1); expect(manifest.variants[0].decodingInfos[0].supported).toBeTruthy(); }); it('for srcEquals content', async () => { manifest = shaka.test.ManifestGenerator.generate((manifest) => { manifest.addVariant(0, (variant) => { variant.addVideo(1, (stream) => { stream.mime('video/mp4', 'avc1.4d400d'); }); }); }); await StreamUtils.getDecodingInfosForVariants(manifest.variants, /* usePersistentLicenses= */false, /* srcEquals= */ true, /* preferredKeySystems= */ []); expect(manifest.variants.length).toBeTruthy(); expect(manifest.variants[0].decodingInfos.length).toBe(1); expect(manifest.variants[0].decodingInfos[0].supported).toBeTruthy(); }); it('handles decodingInfo exception', async () => { navigator.mediaCapabilities.decodingInfo = shaka.test.Util.spyFunc(decodingInfoSpy); // If decodingInfo() fails, setDecodingInfo should finish without throwing // an exception, and the variant should have no decodingInfo result. decodingInfoSpy.and.throwError('MediaCapabilties.decodingInfo failed.'); manifest = shaka.test.ManifestGenerator.generate((manifest) => { manifest.addVariant(0, (variant) => { variant.addVideo(1, (stream) => { stream.mime('video/mp4', 'avc1'); stream.encrypted = true; stream.mime('video/mp4', 'avc1.4d400d'); }); variant.addAudio(2, (stream) => { stream.mime('audio/mp4', 'mp4a.40.2'); stream.encrypted = true; stream.addDrmInfo('com.widevine.alpha'); }); }); }); await StreamUtils.getDecodingInfosForVariants(manifest.variants, /* usePersistentLicenses= */false, /* srcEquals= */ false, /* preferredKeySystems= */ []); expect(manifest.variants.length).toBe(1); expect(manifest.variants[0].decodingInfos.length).toBe(0); }); it('includes transferFunction in config when hdr', async () => { const originalDecodingInfo = navigator.mediaCapabilities.decodingInfo; try { navigator.mediaCapabilities.decodingInfo = shaka.test.Util.spyFunc(decodingInfoSpy); manifest = shaka.test.ManifestGenerator.generate((manifest) => { manifest.addVariant(0, (variant) => { variant.addVideo(0, (stream) => { stream.mime('video/mp4', 'avc1.640028'); stream.hdr = 'SDR'; }); }); manifest.addVariant(1, (variant) => { variant.addVideo(1, (stream) => { stream.mime('video/mp4', 'hvc1.2.4.L150.90'); stream.hdr = 'PQ'; }); }); manifest.addVariant(2, (variant) => { variant.addVideo(2, (stream) => { stream.mime('video/mp4', 'hvc1.2.4.L153.B0'); stream.hdr = 'HLG'; }); }); }); await StreamUtils.getDecodingInfosForVariants(manifest.variants, /* usePersistentLicenses= */ false, /* srcEquals= */ false, /* preferredKeySystems= */ []); expect(decodingInfoSpy.calls.argsFor(0)[0].video.transferFunction) .toBe('srgb'); expect(decodingInfoSpy.calls.argsFor(1)[0].video.transferFunction) .toBe('pq'); expect(decodingInfoSpy.calls.argsFor(2)[0].video.transferFunction) .toBe('hlg'); } finally { navigator.mediaCapabilities.decodingInfo = originalDecodingInfo; } }); it('includes streams only with preferred key system', async () => { const originalDecodingInfo = navigator.mediaCapabilities.decodingInfo; try { navigator.mediaCapabilities.decodingInfo = shaka.test.Util.spyFunc(decodingInfoSpy); manifest = shaka.test.ManifestGenerator.generate((manifest) => { manifest.addVariant(0, (variant) => { variant.addVideo(1, (stream) => { stream.mime('video/mp4', 'avc1.4d400d'); stream.encrypted = true; stream.addDrmInfo('com.widevine.alpha'); stream.addDrmInfo('com.microsoft.playready'); }); }); }); await StreamUtils.getDecodingInfosForVariants(manifest.variants, /* usePersistentLicenses= */ false, /* srcEquals= */ false, /* preferredKeySystems= */ ['com.microsoft.playready']); // if preferred key system satisfies us, we shouldn't check other ones. expect(decodingInfoSpy).toHaveBeenCalledTimes(1); expect(decodingInfoSpy.calls.argsFor(0)[0].keySystemConfiguration .keySystem) .toBe('com.microsoft.playready'); } finally { navigator.mediaCapabilities.decodingInfo = originalDecodingInfo; } }); }); describe('filterManifest', () => { let fakeDrmEngine; beforeAll(() => { fakeDrmEngine = new shaka.test.FakeDrmEngine(); }); it('filters text streams with the full MIME type', async () => { manifest = shaka.test.ManifestGenerator.generate((manifest) => { manifest.addTextStream(1, (stream) => { stream.mimeType = 'text/vtt'; }); manifest.addTextStream(2, (stream) => { stream.mime('application/mp4', 'wvtt'); }); manifest.addTextStream(3, (stream) => { stream.mimeType = 'text/bogus'; }); manifest.addTextStream(4, (stream) => { stream.mime('application/mp4', 'bogus'); }); }); const noVariant = null; await shaka.util.StreamUtils.filterManifest( fakeDrmEngine, noVariant, manifest, shaka.config.CodecSwitchingStrategy.RELOAD); // Covers a regression in which we would remove streams with codecs. // The last two streams should be removed because their full MIME types // are bogus. expect(manifest.textStreams.length).toBe(2); expect(manifest.textStreams[0].id).toBe(1); expect(manifest.textStreams[1].id).toBe(2); }); it('filters image streams', async () => { manifest = shaka.test.ManifestGenerator.generate((manifest) => { manifest.addImageStream(1, (stream) => { stream.mimeType = 'image/svg+xml'; }); manifest.addImageStream(2, (stream) => { stream.mimeType = 'image/png'; }); manifest.addImageStream(3, (stream) => { stream.mimeType = 'image/jpg'; }); manifest.addImageStream(4, (stream) => { stream.mimeType = 'image/jpeg'; }); manifest.addImageStream(5, (stream) => { stream.mimeType = 'image/bogus'; }); manifest.addImageStream(6, (stream) => { stream.mimeType = 'image/avif'; }); manifest.addImageStream(7, (stream) => { stream.mimeType = 'image/webp'; }); }); const noVariant = null; await shaka.util.StreamUtils.filterManifest( fakeDrmEngine, noVariant, manifest, shaka.config.CodecSwitchingStrategy.RELOAD); // Covers a regression in which we would remove streams with codecs. // The first 4 streams should be there because they are always supported. // The 5th stream should be removed because the MIME type is bogus. // The 6th and 7th streams may be there, based on platform support. expect(manifest.imageStreams).toContain( jasmine.objectContaining({id: 1})); expect(manifest.imageStreams).toContain( jasmine.objectContaining({id: 2})); expect(manifest.imageStreams).toContain( jasmine.objectContaining({id: 3})); expect(manifest.imageStreams).toContain( jasmine.objectContaining({id: 4})); expect(manifest.imageStreams).not.toContain( jasmine.objectContaining({id: 5})); }); it('does not filter manifest when codec switching is enabled', async () => { manifest = shaka.test.ManifestGenerator.generate((manifest) => { manifest.addVariant(1, (variant) => { variant.addAudio(10, (stream) => { stream.codecs = 'mp4a.69'; }); variant.addVideo(11, (stream) => { stream.codecs = 'avc1'; }); }); }); const originalFilterManifestByCurrentVariant = shaka.util.StreamUtils.filterManifestByCurrentVariant; try { const filterManifestByCurrentVariantSpy = jasmine.createSpy('filterManifestByCurrentVariant'); shaka.util.StreamUtils.filterManifestByCurrentVariant = shaka.test.Util.spyFunc(filterManifestByCurrentVariantSpy); await shaka.util.StreamUtils.filterManifest( fakeDrmEngine, /* currentVariant= */ null, manifest, shaka.config.CodecSwitchingStrategy.RELOAD); expect(filterManifestByCurrentVariantSpy).not.toHaveBeenCalled(); } finally { shaka.util.StreamUtils.filterManifestByCurrentVariant = originalFilterManifestByCurrentVariant; } }); it('filters transport streams', async () => { manifest = shaka.test.ManifestGenerator.generate((manifest) => { manifest.addVariant(0, (variant) => { variant.language = 'en'; variant.addVideo(1, (stream) => { stream.mime('video/mp2t', 'avc1.42c00d'); }); variant.addAudio(2, (stream) => { stream.mime('video/mp2t', 'mp4a.40.2'); }); }); }); await shaka.util.StreamUtils.filterManifest( fakeDrmEngine, /* currentVariant= */ null, manifest, shaka.config.CodecSwitchingStrategy.RELOAD); // Covers a regression in which we would remove streams with codecs. // The last two streams should be removed because their full MIME types // are bogus. expect(manifest.variants.length).toBe(1); expect(manifest.variants[0].video.id).toBe(1); expect(manifest.variants[0].audio.id).toBe(2); }); // MediaCapabilities decodingInfo requires valid bandwidth, frameRate, // width, height as part of the input. Fill in default values if those info // are not available from the manifest. it('tolerates empty bandwidth, frameRate, width, height', async () => { manifest = shaka.test.ManifestGenerator.generate((manifest) => { manifest.addVariant(0, (variant) => { variant.language = 'en'; variant.addVideo(1, (stream) => { stream.codecs = 'avc1.4d401f'; }); variant.addAudio(2, (stream) => { stream.codecs = 'mp4a.40.2'; }); }); }); await shaka.util.StreamUtils.filterManifest( fakeDrmEngine, /* currentVariant= */ null, manifest, shaka.config.CodecSwitchingStrategy.RELOAD); expect(manifest.variants.length).toBe(1); }); it('supports VP9 codec', async () => { if (!MediaSource.isTypeSupported('video/webm; codecs="vp9"')) { pending('Codec VP9 is not supported by the platform.'); } manifest = shaka.test.ManifestGenerator.generate((manifest) => { manifest.addVariant(0, (variant) => { variant.addVideo(1, (stream) => { stream.mime('video/webm', 'vp9'); }); }); }); await shaka.util.StreamUtils.filterManifest( fakeDrmEngine, /* currentVariant= */ null, manifest, shaka.config.CodecSwitchingStrategy.RELOAD); expect(manifest.variants.length).toBe(1); }); it('supports fLaC codec', async () => { if (!MediaSource.isTypeSupported('audio/mp4; codecs="flac"')) { pending('Codec fLaC is not supported by the platform.'); } manifest = shaka.test.ManifestGenerator.generate((manifest) => { manifest.addVariant(0, (variant) => { variant.addAudio(1, (stream) => { stream.mime('audio/mp4', 'fLaC'); }); }); manifest.addVariant(2, (variant) => { variant.addAudio(3, (stream) => { stream.mime('audio/mp4', 'flac'); }); }); }); await shaka.util.StreamUtils.filterManifest( fakeDrmEngine, /* currentVariant= */ null, manifest); expect(manifest.variants.length).toBe(2); }); it('supports Opus codec', async () => { if (!MediaSource.isTypeSupported('audio/mp4; codecs="opus"')) { pending('Codec Opus is not supported by the platform.'); } manifest = shaka.test.ManifestGenerator.generate((manifest) => { manifest.addVariant(0, (variant) => { variant.addAudio(1, (stream) => { stream.mime('audio/mp4', 'Opus'); }); }); manifest.addVariant(2, (variant) => { variant.addAudio(3, (stream) => { stream.mime('audio/mp4', 'opus'); }); }); }); await shaka.util.StreamUtils.filterManifest( fakeDrmEngine, /* currentVariant= */ null, manifest); expect(manifest.variants.length).toBe(2); }); it('supports legacy AVC1 codec', async () => { if (!MediaSource.isTypeSupported('video/mp4; codecs="avc1.42001e"')) { pending('Codec avc1.42001e is not supported by the platform.'); } manifest = shaka.test.ManifestGenerator.generate((manifest) => { manifest.addVariant(0, (variant) => { variant.addVideo(1, (stream) => { stream.mime('video/mp4', 'avc1.66.30'); }); }); }); await shaka.util.StreamUtils.filterManifest( fakeDrmEngine, /* currentVariant= */ null, manifest, shaka.config.CodecSwitchingStrategy.RELOAD); expect(manifest.variants.length).toBe(1); }); }); describe('chooseCodecsAndFilterManifest', () => { const addVariant720Avc1 = (manifest) => { manifest.addVariant(0, (variant) => { variant.bandwidth = 5058558; variant.addAudio(1, (stream) => { stream.bandwidth = 129998; stream.mime('audio/mp4', 'mp4a.40.2'); }); variant.addVideo(2, (stream) => { stream.bandwidth = 4928560; stream.size(1280, 720); stream.mime('video/mp4', 'avc1.640028'); }); }); }; const addVariant720Vp9 = (manifest) => { manifest.addVariant(3, (variant) => { variant.bandwidth = 4911000; variant.addAudio(4, (stream) => { stream.bandwidth = 129998; stream.mime('audio/webm', 'vorbis'); }); variant.addVideo(5, (stream) => { stream.bandwidth = 4781002; stream.size(1280, 720); stream.mime('video/webm', 'vp9'); }); }); }; const addVariant1080Vp9 = (manifest) => { manifest.addVariant(6, (variant) => { variant.bandwidth = 10850316; variant.addAudio(1, (stream) => { stream.bandwidth = 129998; stream.mime('audio/mp4', 'mp4a.40.2'); }); variant.addVideo(8, (stream) => { stream.bandwidth = 10784324; stream.size(1920, 1080); stream.mime('video/webm', 'vp9'); }); }); }; it('should filter variants by the best available bandwidth' + ' for video resolution', () => { manifest = shaka.test.ManifestGenerator.generate((manifest) => { manifest.addVariant(0, (variant) => { variant.bandwidth = 4058558; variant.addVideo(1, (stream) => { stream.bandwidth = 300000; stream.size(10, 10); }); }); manifest.addVariant(2, (variant) => { variant.bandwidth = 4781002; variant.addVideo(3, (stream) => { stream.bandwidth = 400000; stream.size(10, 10); }); }); manifest.addVariant(4, (variant) => { variant.addVideo(5, (stream) => { stream.bandwidth = 500000; stream.size(20, 20); }); }); manifest.addVariant(6, (variant) => { variant.addVideo(7, (stream) => { stream.bandwidth = 600000; stream.size(20, 20); }); }); }); shaka.util.StreamUtils.chooseCodecsAndFilterManifest(manifest, /* preferredVideoCodecs= */[], /* preferredAudioCodecs= */[], /* preferredAudioChannelCount= */2, /* preferredDecodingAttributes= */[]); expect(manifest.variants.length).toBe(2); expect(manifest.variants.every((v) => [300000, 500000].includes( v.video.bandwidth))).toBeTruthy(); }); it('should filter variants by the best available bandwidth' + ' for audio language', () => { manifest = shaka.test.ManifestGenerator.generate((manifest) => { manifest.addVariant(0, (variant) => { variant.bandwidth = 4058558; variant.addAudio(1, (stream) => { stream.bandwidth = 100000; stream.language = 'en'; }); }); manifest.addVariant(2, (variant) => { variant.bandwidth = 4781002; variant.addAudio(3, (stream) => { stream.bandwidth = 200000; stream.language = 'en'; }); }); manifest.addVariant(4, (variant) => { variant.addAudio(5, (stream) => { stream.bandwidth = 100000; stream.language = 'es'; }); }); manifest.addVariant(6, (variant) => { variant.addAudio(7, (stream) => { stream.bandwidth = 500000; stream.language = 'es'; }); }); }); shaka.util.StreamUtils.chooseCodecsAndFilterManifest(manifest, /* preferredVideoCodecs= */[], /* preferredAudioCodecs= */[], /* preferredAudioChannelCount= */2, /* preferredDecodingAttributes= */[]); expect(manifest.variants.length).toBe(2); expect(manifest.variants.every((v) => v.audio.bandwidth == 100000)) .toBeTruthy(); }); it('should allow multiple codecs for codec switching', () => { if (!MediaSource.isTypeSupported('video/webm; codecs="vp9"')) { pending('Codec VP9 is not supported by the platform.'); } if (!MediaSource.isTypeSupported('video/webm; codecs="vorbis"')) { pending('Codec vorbis is not supported by the platform.'); } // This test is flaky in some Tizen devices, due to codec restrictions. if (shaka.util.Platform.isTizen()) { pending('Skip flaky test in Tizen'); } manifest = shaka.test.ManifestGenerator.generate((manifest) => { addVariant720Avc1(manifest); addVariant720Vp9(manifest); addVariant1080Vp9(manifest); }); manifest.variants[0].video.bandwidth = 1; shaka.util.StreamUtils.chooseCodecsAndFilterManifest(manifest, /* preferredVideoCodecs= */[], /* preferredAudioCodecs= */[], /* preferredAudioChannelCount= */2, /* preferredDecodingAttributes= */[]); expect(manifest.variants.length).toBe(2); expect(manifest.variants[0].video.codecs) .not.toBe(manifest.variants[1].video.codecs); }); it('chooses preferred audio and video codecs', () => { if (!MediaSource.isTypeSupported('video/webm; codecs="vp9"')) { pending('Codec VP9 is not supported by the platform.'); } if (!MediaSource.isTypeSupported('video/webm; codecs="vorbis"')) { pending('Codec vorbis is not supported by the platform.'); } manifest = shaka.test.ManifestGenerator.generate((manifest) => { addVariant720Avc1(manifest); addVariant720Vp9(manifest); addVariant1080Vp9(manifest); }); const variants = shaka.util.StreamUtils.choosePreferredCodecs(manifest.variants, /* preferredVideoCodecs= */['vp9'], /* preferredAudioCodecs= */['mp4a']); expect(variants.length).toBe(1); expect(variants[0].video.codecs).toBe('vp9'); expect(variants[0].audio.codecs).toBe('mp4a.40.2'); }); it('chooses preferred video codecs', () => { if (!MediaSource.isTypeSupported('video/webm; codecs="vp9"')) { pending('Codec VP9 is not supported by the platform.'); } if (!MediaSource.isTypeSupported('video/webm; codecs="vorbis"')) { pending('Codec vorbis is not supported by the platform.'); } // If no preferred audio codecs is specified or can be found, choose the // variants with preferred video codecs. manifest = shaka.test.ManifestGenerator.generate((manifest) => { addVariant720Avc1(manifest); addVariant720Vp9(manifest); addVariant1080Vp9(manifest); }); const variants = shaka.util.StreamUtils.choosePreferredCodecs(manifest.variants, /* preferredVideoCodecs= */['vp9'], /* preferredAudioCodecs= */[]); expect(variants.length).toBe(2); expect(variants[0].video.codecs).toBe('vp9'); expect(variants[0].audio.codecs).toBe('vorbis'); expect(variants[1].video.codecs).toBe('vp9'); expect(variants[1].audio.codecs).toBe('mp4a.40.2'); }); it('chooses preferred audio codecs', () => { if (!MediaSource.isTypeSupported('video/webm; codecs="vp9"')) { pending('Codec VP9 is not supported by the platform.'); } if (!MediaSource.isTypeSupported('video/webm; codecs="vorbis"')) { pending('Codec vorbis is not supported by the platform.'); } // If no preferred video codecs is specified or can be found, choose the // variants with preferred audio codecs. manifest = shaka.test.ManifestGenerator.generate((manifest) => { addVariant720Avc1(manifest); addVariant720Vp9(manifest); addVariant1080Vp9(manifest); }); const variants = shaka.util.StreamUtils.choosePreferredCodecs(manifest.variants, /* preferredVideoCodecs= */['foo'], /* preferredAudioCodecs= */['mp4a.40.2']); expect(variants.length).toBe(2); expect(variants[0].video.codecs).toBe('avc1.640028'); expect(variants[0].audio.codecs).toBe('mp4a.40.2'); expect(variants[1].video.codecs).toBe('vp9'); expect(variants[1].audio.codecs).toBe('mp4a.40.2'); }); it('chooses variants by decoding attributes', async () => { manifest = shaka.test.ManifestGenerator.generate((manifest) => { manifest.addVariant(0, (variant) => { variant.bandwidth = 4058558; variant.addVideo(1, (stream) => { stream.mime('video', 'notsmooth'); }); }); manifest.addVariant(1, (variant) => { variant.bandwidth = 4781002; variant.addVideo(2, (stream) => { stream.mime('video', 'smooth'); }); }); manifest.addVariant(3, (variant) => { variant.addVideo(4, (stream) => { variant.bandwidth = 5058558; stream.mime('video', 'smooth-2'); }); }); }); navigator.mediaCapabilities.decodingInfo = shaka.test.Util.spyFunc(decodingInfoSpy); decodingInfoSpy.and.callFake((config) => { const res = config.video.contentType.includes('notsmooth') ? {supported: true, smooth: false} : {supported: true, smooth: true}; return Promise.resolve(res); }); await StreamUtils.getDecodingInfosForVariants(manifest.variants, /* usePersistentLicenses= */false, /* srcEquals= */ false, /* preferredKeySystems= */ []); shaka.util.StreamUtils.chooseCodecsAndFilterManifest(manifest, /* preferredVideoCodecs= */[], /* preferredAudioCodecs= */[], /* preferredAudioChannelCount= */2, /* preferredDecodingAttributes= */ [shaka.util.StreamUtils.DecodingAttributes.SMOOTH]); // 2 video codecs are smooth. Choose the one with the lowest bandwidth. expect(manifest.variants.length).toBe(1); expect(manifest.variants[0].id).toBe(1); expect(manifest.variants[0].video.id).toBe(2); }); }); describe('isPlayable', () => { /** @type {shaka.extern.Variant} */ const variant = { id: 1, language: 'es', disabledUntilTime: 0, video: null, audio: null, primary: false, bandwidth: 2000, allowedByApplication: true, allowedByKeySystem: true, decodingInfos: [], }; it('returns false if variant is disabled', () => { variant.allowedByApplication = true; variant.allowedByKeySystem = true; variant.disabledUntilTime = 1234; expect(shaka.util.StreamUtils.isPlayable(variant)).toBe(false); }); }); });