diff --git a/karma.conf.js b/karma.conf.js index 3d33b94863..639de1bc8b 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -431,12 +431,14 @@ module.exports = (config) => { clientArgs.testFiles.push('test/player_external.js'); } else { // In a normal test run, we serve unit tests. - clientArgs.testFiles.push('test/**/*_unit.js'); - - if (!settings.quick) { - // If --quick is present, we don't serve integration tests. - clientArgs.testFiles.push('test/**/*_integration.js'); - } + clientArgs.testFiles.push('test/**/ads_integration.js'); + clientArgs.testFiles.push('test/**/player_integration.js'); + clientArgs.testFiles.push('test/**/hls_parser_integration.js'); + + // if (!settings.quick) { + // // If --quick is present, we don't serve integration tests. + // clientArgs.testFiles.push('test/**/*_integration.js'); + // } if (settings.external) { // If --external is present, we serve external asset tests. clientArgs.testFiles.push('demo/common/asset.js'); diff --git a/package-lock.json b/package-lock.json index 0bc10c7622..5c4aa825b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "4.8.0", "license": "Apache-2.0", "dependencies": { - "eme-encryption-scheme-polyfill": "^2.1.2" + "eme-encryption-scheme-polyfill": "^2.1.3" }, "devDependencies": { "@babel/core": "^7.17.5", @@ -3668,9 +3668,9 @@ "dev": true }, "node_modules/eme-encryption-scheme-polyfill": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/eme-encryption-scheme-polyfill/-/eme-encryption-scheme-polyfill-2.1.2.tgz", - "integrity": "sha512-ccDEgbk5+hJbzEDcUkID0chK+vy/pXi1oQIIADGpyYdZThAbLLod7WZKryU32IWmFX4BAWmzZtf4LeUnnNzcQQ==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/eme-encryption-scheme-polyfill/-/eme-encryption-scheme-polyfill-2.1.3.tgz", + "integrity": "sha512-MLF/qy30Byiay963acUmnCRVYlUnep5C3XjY+XKQR6kmYEEEM/KMf1ZnvmFXwALsxLIYBibziiHneDQObTH7Tw==" }, "node_modules/emoji-regex": { "version": "8.0.0", @@ -11508,9 +11508,9 @@ "dev": true }, "eme-encryption-scheme-polyfill": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/eme-encryption-scheme-polyfill/-/eme-encryption-scheme-polyfill-2.1.2.tgz", - "integrity": "sha512-ccDEgbk5+hJbzEDcUkID0chK+vy/pXi1oQIIADGpyYdZThAbLLod7WZKryU32IWmFX4BAWmzZtf4LeUnnNzcQQ==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/eme-encryption-scheme-polyfill/-/eme-encryption-scheme-polyfill-2.1.3.tgz", + "integrity": "sha512-MLF/qy30Byiay963acUmnCRVYlUnep5C3XjY+XKQR6kmYEEEM/KMf1ZnvmFXwALsxLIYBibziiHneDQObTH7Tw==" }, "emoji-regex": { "version": "8.0.0", diff --git a/package.json b/package.json index c3045b511f..252eda27bb 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,7 @@ "prepublishOnly": "python build/checkversion.py && python build/all.py --force" }, "dependencies": { - "eme-encryption-scheme-polyfill": "^2.1.2" + "eme-encryption-scheme-polyfill": "^2.1.3" }, "engines": { "node": ">=14" diff --git a/test/ads_integration.js b/test/ads_integration.js new file mode 100644 index 0000000000..cc98de363c --- /dev/null +++ b/test/ads_integration.js @@ -0,0 +1,116 @@ +/*! @license + * Shaka Player + * Copyright 2016 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +describe('Ads', () => { + const Util = shaka.test.Util; + + /** @type {!jasmine.Spy} */ + let onErrorSpy; + + /** @type {!HTMLScriptElement} */ + let imaScript; + /** @type {!HTMLVideoElement} */ + let video; + /** @type {!HTMLElement} */ + let adContainer; + /** @type {shaka.Player} */ + let player; + /** @type {shaka.extern.IAdManager} */ + let adManager; + /** @type {!shaka.util.EventManager} */ + let eventManager; + + let compiledShaka; + + /** @type {!shaka.test.Waiter} */ + let waiter; + + beforeAll(async () => { + imaScript = shaka.test.UiUtils.createImaSdkScript(); + const loadImaScript = new Promise((resolve, reject) => { + imaScript.onload = resolve; + imaScript.onerror= reject; + }); + document.head.appendChild(imaScript); + await loadImaScript; + video = shaka.test.UiUtils.createVideoElement(); + document.body.appendChild(video); + adContainer = + /** @type {!HTMLElement} */ (document.createElement('div')); + document.body.appendChild(adContainer); + compiledShaka = + await shaka.test.Loader.loadShaka(getClientArg('uncompiled')); + }); + + beforeEach(async () => { + await shaka.test.TestScheme.createManifests(compiledShaka, '_compiled'); + player = new compiledShaka.Player(); + adManager = player.getAdManager(); + await player.attach(video); + + player.configure('streaming.useNativeHlsOnSafari', false); + + // Disable stall detection, which can interfere with playback tests. + player.configure('streaming.stallEnabled', false); + + // Grab event manager from the uncompiled library: + eventManager = new shaka.util.EventManager(); + waiter = new shaka.test.Waiter(eventManager); + waiter.setPlayer(player); + + onErrorSpy = jasmine.createSpy('onError'); + onErrorSpy.and.callFake((event) => fail(event.detail)); + eventManager.listen(player, 'error', Util.spyFunc(onErrorSpy)); + eventManager.listen(adManager, shaka.ads.AdManager.AD_ERROR, + Util.spyFunc(onErrorSpy)); + }); + + afterEach(async () => { + eventManager.release(); + await player.destroy(); + }); + + afterAll(() => { + document.head.removeChild(imaScript); + document.body.removeChild(video); + }); + + it('supports IMA SDK with vast', async () => { + adManager.initClientSide( + adContainer, video, /** adsRenderingSettings= **/ null); + + await player.load('/base/test/test/assets/dash-aes-128/dash.mpd'); + await video.play(); + expect(player.isLive()).toBe(false); + + // Wait for the video to start playback. If it takes longer than 10 + // seconds, fail the test. + await waiter.waitForMovementOrFailOnTimeout(video, 10); + + // Play for 5 seconds, but stop early if the video ends. If it takes + // longer than 20 seconds, fail the test. + await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 5, 20); + + const adRequest = new google.ima.AdsRequest(); + adRequest.adTagUrl = 'https://pubads.g.doubleclick.net/gampad/ads?' + + 'sz=640x480&iu=/124319096/external/single_ad_samples&' + + 'ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&' + + 'unviewed_position_start=1&' + + 'cust_params=deployment%3Ddevsite%26sample_ct%3Dlinear&correlator='; + adManager.requestClientSideAds(adRequest); + + // The ad lasts 10 seconds. If it takes longer than 300 seconds, fail the + // test. + await waiter.timeoutAfter(30) + .waitForEvent(adManager, shaka.ads.AdManager.AD_STOPPED); + + // Play for 10 seconds, but stop early if the video ends. If it takes + // longer than 30 seconds, fail the test. + await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 10, 30); + + await player.unload(); + }); +}); diff --git a/test/hls/hls_parser_integration.js b/test/hls/hls_parser_integration.js index 03bb6a1015..3a60429757 100644 --- a/test/hls/hls_parser_integration.js +++ b/test/hls/hls_parser_integration.js @@ -70,32 +70,6 @@ describe('HlsParser', () => { document.body.removeChild(video); }); - it('supports AES-256 streaming', async () => { - let keyRequests = 0; - const netEngine = player.getNetworkingEngine(); - netEngine.registerRequestFilter((type, request, context) => { - if (type == shaka.net.NetworkingEngine.RequestType.KEY) { - keyRequests++; - } - }); - await player.load('/base/test/test/assets/hls-aes-256/index.m3u8'); - await video.play(); - expect(player.isLive()).toBe(false); - - // Wait for the video to start playback. If it takes longer than 10 - // seconds, fail the test. - await waiter.waitForMovementOrFailOnTimeout(video, 10); - - // Play for 10 seconds, but stop early if the video ends. If it takes - // longer than 30 seconds, fail the test. - await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 10, 30); - - await player.unload(); - - // The stream has 6 #EXT-X-KEY but only 5 different keys. - expect(keyRequests).toBe(5); - }); - drmIt('supports SAMPLE-AES identity streaming', async () => { if (!checkClearKeySupport()) { pending('ClearKey is not supported'); @@ -115,30 +89,4 @@ describe('HlsParser', () => { await player.unload(); }); - - it('supports text discontinuity', async () => { - if (!shaka.util.Platform.supportsSequenceMode()) { - pending('Sequence mode is not supported by the platform.'); - } - - player.configure('manifest.hls.ignoreManifestProgramDateTime', true); - player.setTextTrackVisibility(true); - - await player.load('/base/test/test/assets/hls-text-offset/index.m3u8'); - await video.play(); - - // Wait for last cue - await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 7, 30); - - const cues = video.textTracks[0].cues; - expect(cues.length).toBe(3); - expect(cues[0].startTime).toBeCloseTo(0, 0); - expect(cues[0].endTime).toBeCloseTo(2, 0); - expect(cues[1].startTime).toBeCloseTo(2, 0); - expect(cues[1].endTime).toBeCloseTo(4, 0); - expect(cues[2].startTime).toBeCloseTo(6, 0); - expect(cues[2].endTime).toBeCloseTo(8, 0); - - await player.unload(); - }); }); diff --git a/test/player_integration.js b/test/player_integration.js index d80f765c31..df7209d5ea 100644 --- a/test/player_integration.js +++ b/test/player_integration.js @@ -61,1131 +61,6 @@ describe('Player', () => { document.body.removeChild(video); }); - describe('seek range', () => { - // Regression test for Issue #3675. - it('has a reasonable seek range after going from live to VOD', async () => { - const netEngine = player.getNetworkingEngine(); - const startTime = Date.now(); - netEngine.registerRequestFilter((type, request) => { - if (type != shaka.net.NetworkingEngine.RequestType.MANIFEST) { - return; - } - // Simulate a live stream by providing different manifests over time. - const time = (Date.now() - startTime) / 1000; - const manifestNumber = Math.min(5, Math.floor(0.5 + time / 2)); - request.uris = [ - '/base/test/test/assets/3675/dash_' + manifestNumber + '.mpd', - ]; - console.log('getting manifest', request.uris); - }); - - // Play the stream. - await player.load('/base/test/test/assets/3675/dash_0.mpd'); - await video.play(); - - // Wait for the stream to be over. - eventManager.listen(player, 'error', Util.spyFunc(onErrorSpy)); - /** @type {shaka.test.Waiter} */ - const waiter = new shaka.test.Waiter(eventManager) - .setPlayer(player) - .timeoutAfter(40) - .failOnTimeout(true); - await waiter.waitForEnd(video); - - // The stream should have transitioned to VOD by now. - expect(player.isLive()).toBe(false); - - // Check that the final seek range is as expected. - const seekRange = player.seekRange(); - expect(seekRange.end).toBeCloseTo(14); - }); - }); - - describe('attach', () => { - beforeEach(async () => { - // To test attach, we want to construct a player without a video element - // attached in advance. To do that, we destroy the player that was - // constructed in the outermost beforeEach(), then construct a new one - // without a video element. - await player.destroy(); - player = new compiledShaka.Player(); - }); - - it('can be used before load()', async () => { - await player.attach(video); - await player.load('test:sintel_compiled'); - }); - }); // describe('attach') - - describe('destroy', () => { - // Regression test for: - // https://github.com/shaka-project/shaka-player/issues/4850 - it('does not leave any lingering timers', async () => { - shaka.util.Timer.activeTimers.clear(); - - // Unlike the other tests in this file, this uses an uncompiled build of - // Shaka, so that we don't need to expose shaka.util.Timer.activeTimers. - player = new shaka.Player(); - await player.attach(video); - - // Disable stall detection, which can interfere with playback tests. - player.configure('streaming.stallEnabled', false); - - waiter.setPlayer(player); - - // Play the video for a little while. - await player.load('test:sintel'); - await video.play(); - await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 1, 10); - - // Destroy the player. - await player.destroy(); - - // Are there any timers left? - for (const timer of shaka.util.Timer.activeTimers.keys()) { - const stackTrace = shaka.util.Timer.activeTimers.get(timer); - fail('Lingering timer exists! Stack trace of creation: ' + stackTrace); - } - }); - }); - - describe('updateStartTime() in manifestparsed event handler', () => { - it('does not get segments prior to startTime', async () => { - player.addEventListener('manifestparsed', () => { - player.updateStartTime(24); - }); - const results = { - requestedVideoSegment0: false, - requestedVideoSegment1: false, - requestedVideoSegment2: false, - }; - const expected = { - requestedVideoSegment0: false, - requestedVideoSegment1: false, - requestedVideoSegment2: true, - }; - player.getNetworkingEngine().registerRequestFilter( - (type, request) => { - if (request.uris[0] == 'test:sintel/video/0') { - results.requestedVideoSegment0 = true; - } - if (request.uris[0] == 'test:sintel/video/1') { - results.requestedVideoSegment1 = true; - } - if (request.uris[0] == 'test:sintel/video/2') { - results.requestedVideoSegment2 = true; - } - }); - await player.load('test:sintel_compiled'); - await video.play(); - await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 25, 30); - expect(results).toEqual(expected); - }); - }); - - - describe('getStats', () => { - it('gives stats about current stream', async () => { - // This is tested more in player_unit.js. This is here to test the public - // API and to check for renaming. - await player.load('test:sintel_compiled'); - await video.play(); - await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 1, 10); - - const stats = player.getStats(); - const expected = { - width: jasmine.any(Number), - height: jasmine.any(Number), - streamBandwidth: jasmine.any(Number), - - decodedFrames: jasmine.any(Number), - droppedFrames: jasmine.any(Number), - corruptedFrames: jasmine.any(Number), - estimatedBandwidth: jasmine.any(Number), - - gapsJumped: jasmine.any(Number), - stallsDetected: jasmine.any(Number), - - completionPercent: jasmine.any(Number), - loadLatency: jasmine.any(Number), - manifestTimeSeconds: jasmine.any(Number), - drmTimeSeconds: jasmine.any(Number), - playTime: jasmine.any(Number), - pauseTime: jasmine.any(Number), - bufferingTime: jasmine.any(Number), - licenseTime: jasmine.any(Number), - liveLatency: jasmine.any(Number), - - maxSegmentDuration: jasmine.any(Number), - - bytesDownloaded: jasmine.any(Number), - - // We should have loaded the first Period by now, so we should have a - // history. - switchHistory: jasmine.arrayContaining([{ - timestamp: jasmine.any(Number), - id: jasmine.any(Number), - type: 'variant', - fromAdaptation: true, - bandwidth: 0, - }]), - - stateHistory: jasmine.arrayContaining([{ - state: 'playing', - timestamp: jasmine.any(Number), - duration: jasmine.any(Number), - }]), - }; - expect(stats).toEqual(expected); - }); - - // Regression test for Issue #968 where trying to get the stats before - // calling load would fail because not all components had been initialized. - it('can get stats before loading content', async () => { - // Destroy Player created in beforeEach. - await player.destroy(); - - // We are opting not to initialize the player with a video element so that - // it is in the least loaded state possible. - player = new compiledShaka.Player(); - - const stats = player.getStats(); - expect(stats).toBeTruthy(); - await player.destroy(); - }); - }); // describe('getStats') - - describe('setTextTrackVisibility', () => { - // Using mode='disabled' on TextTrack causes cues to go null, which leads - // to a crash in TextEngine. This validates that we do not trigger this - // behavior when changing visibility of text. - - it('does not cause cues to be null', async () => { - await player.load('test:sintel_compiled'); - await video.play(); - await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 1, 10); - - // This TextTrack was created as part of load() when we set up the - // TextDisplayer. - const textTrack = video.textTracks[0]; - expect(textTrack).not.toBe(null); - - if (textTrack) { - // This should not be null initially. - expect(textTrack.cues).not.toBe(null); - - await player.setTextTrackVisibility(true); - // This should definitely not be null when visible. - expect(textTrack.cues).not.toBe(null); - - await player.setTextTrackVisibility(false); - // This should not transition to null when invisible. - expect(textTrack.cues).not.toBe(null); - } - }); - - // Repro for https://github.com/shaka-project/shaka-player/issues/1879. - it('appends cues when enabled initially', async () => { - let cues = []; - /** @const {!shaka.test.FakeTextDisplayer} */ - const displayer = new shaka.test.FakeTextDisplayer(); - displayer.appendSpy.and.callFake((added) => { - cues = cues.concat(added); - }); - - player.configure('textDisplayFactory', () => displayer); - - const preferredTextLanguage = 'fa'; // The same as in the content itself - player.configure({preferredTextLanguage: preferredTextLanguage}); - - await player.load('test:sintel_realistic_compiled'); - - // Play until a time at which the external cues would be on screen. - await video.play(); - await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 4, 20); - - expect(player.isTextTrackVisible()).toBe(true); - expect(displayer.isTextVisible()).toBe(true); - expect(cues.length).toBeGreaterThan(0); - }); - - it('appends cues for external text', async () => { - let cues = []; - /** @const {!shaka.test.FakeTextDisplayer} */ - const displayer = new shaka.test.FakeTextDisplayer(); - displayer.appendSpy.and.callFake((added) => { - cues = cues.concat(added); - }); - - player.configure('textDisplayFactory', () => displayer); - - await player.load('test:sintel_no_text_compiled'); - const locationUri = new goog.Uri(location.href); - const partialUri = new goog.Uri('/base/test/test/assets/text-clip.vtt'); - const absoluteUri = locationUri.resolve(partialUri); - const newTrack = await player.addTextTrackAsync( - absoluteUri.toString(), 'en', 'subtitles', 'text/vtt'); - - expect(player.getTextTracks()).toEqual([newTrack]); - - player.selectTextTrack(newTrack); - player.setTextTrackVisibility(true); - await waiter.waitForEvent(player, 'texttrackvisibility'); - - // Play until a time at which the external cues would be on screen. - await video.play(); - await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 4, 20); - - expect(player.isTextTrackVisible()).toBe(true); - expect(displayer.isTextVisible()).toBe(true); - expect(cues.length).toBeGreaterThan(0); - }); - - // https://github.com/shaka-project/shaka-player/issues/2553 - it('does not change the selected track', async () => { - player.configure('streaming.alwaysStreamText', false); - await player.load('test:forced_subs_simulation_compiled'); - - // In this content, both text tracks have the same language and role, and - // so should look identical in terms of choosing one to match a - // preference. This is important to the test, so verify it first. - const tracks = player.getTextTracks(); - expect(tracks[0].language).toBe(tracks[1].language); - expect(tracks[0].roles).toEqual(tracks[1].roles); - - const getTracksActive = () => player.getTextTracks().map((t) => t.active); - - // If we choose a track first, then turn on text, the track should not - // change. Try this with both tracks. - player.setTextTrackVisibility(false); - - player.selectTextTrack(tracks[0]); - expect(getTracksActive()).toEqual([true, false]); - player.setTextTrackVisibility(true); - expect(getTracksActive()).toEqual([true, false]); - - player.setTextTrackVisibility(false); - - player.selectTextTrack(tracks[1]); - expect(getTracksActive()).toEqual([false, true]); - player.setTextTrackVisibility(true); - expect(getTracksActive()).toEqual([false, true]); - }); - - // https://github.com/shaka-project/shaka-player/issues/4821 - it('loads a single text stream', async () => { - player.configure({preferredTextLanguage: 'en'}); - await player.load('test:sintel_no_text_compiled'); - - // Add preferred language text track. - const locationUri = new goog.Uri(location.href); - const partialUri = new goog.Uri('/base/test/test/assets/text-clip.vtt'); - const absoluteUri = locationUri.resolve(partialUri); - await player.addTextTrackAsync( - absoluteUri.toString(), 'en', 'subtitles', 'text/vtt'); - - // Add alternate language text track. - // Two text tracks with same timings but different text - // are necessary for test. - const partialUri2 = - new goog.Uri('/base/test/test/assets/text-clip-alt.vtt'); - const absoluteUri2 = locationUri.resolve(partialUri2); - await player.addTextTrackAsync( - absoluteUri2.toString(), 'fr', 'subtitles', 'text/vtt'); - - const textTracks = player.getTextTracks(); - expect(textTracks.length).toBe(2); - expect(textTracks[0].language).toBe('en'); - expect(textTracks[1].language).toBe('fr'); - - // Enable text visibilty and immediately change language. - // Only one set of cues should be active. - // Cues should be of the selected language track. - player.setTextTrackVisibility(true); - player.selectTextLanguage('fr'); - video.currentTime = 5; - await video.play(); - await waiter.waitForMovementOrFailOnTimeout(video, 10); - - expect(video.textTracks[0].activeCues.length).toBe(1); - expect(player.getTextTracks()[1].active).toBe(true); - }); - }); // describe('setTextTrackVisibility') - - describe('autoShowText', () => { - async function textMatchesAudioDoesNot() { - const preferredTextLanguage = 'fa'; // The same as in the content - player.configure({preferredTextLanguage: preferredTextLanguage}); - - // NOTE: This is also a regression test for #1696, in which a change to - // this feature broke StreamingEngine initialization. - - // Now load a version of Sintel with delayed setup of video & audio - // streams and wait for completion. - await player.load('test:sintel_realistic_compiled'); - // By this point, a MediaSource error would be thrown in a repro of bug - // #1696. - - // Make sure the content we tested with has text tracks, that the config - // we used matches the text language, and that the audio language differs. - // These will catch any changes to the underlying content that would - // invalidate the test setup. - expect(player.getTextTracks().length).not.toBe(0); - const textTrack = player.getTextTracks()[0]; - expect(textTrack.language).toBe(preferredTextLanguage); - - const variantTrack = player.getVariantTracks()[0]; - expect(variantTrack.language).not.toBe(textTrack.language); - } - - async function textDoesNotMatch() { - const preferredTextLanguage = 'xx'; // Differs from the content - player.configure({preferredTextLanguage: preferredTextLanguage}); - - // Now load the content and wait for completion. - await player.load('test:sintel_realistic_compiled'); - - // Make sure the content we tested with has text tracks, that the config - // we used does not match the text language, and that the text and audio - // languages do not match each other (to keep this distinct from the next - // test case). This will catch any changes to the underlying content that - // would invalidate the test setup. - expect(player.getTextTracks().length).not.toBe(0); - const textTrack = player.getTextTracks()[0]; - expect(textTrack.language).not.toBe(preferredTextLanguage); - - const variantTrack = player.getVariantTracks()[0]; - expect(variantTrack.language).not.toBe(textTrack.language); - } - - async function textAndAudioMatch() { - const preferredTextLanguage = 'und'; // The same as in the content - player.configure({preferredTextLanguage: preferredTextLanguage}); - - // Now load the content and wait for completion. - await player.load('test:sintel_compiled'); - - // Make sure the content we tested with has text tracks, that the config - // we used matches the content, and that the text and audio languages - // match each other. This will catch any changes to the underlying - // content that would invalidate the test setup. - expect(player.getTextTracks().length).not.toBe(0); - const textTrack = player.getTextTracks()[0]; - expect(textTrack.language).toBe(preferredTextLanguage); - - const variantTrack = player.getVariantTracks()[0]; - expect(variantTrack.language).toBe(textTrack.language); - } - - describe('IF_SUBTITLES_MAY_BE_NEEDED', () => { - beforeEach(() => { - player.configure( - 'autoShowText', - shaka.config.AutoShowText.IF_SUBTITLES_MAY_BE_NEEDED); - }); - - it('enables text if text matches and audio does not', async () => { - await textMatchesAudioDoesNot(); - expect(player.isTextTrackVisible()).toBe(true); - }); - - it('disables text if text does not match', async () => { - await textDoesNotMatch(); - expect(player.isTextTrackVisible()).toBe(false); - }); - - it('disables text if both text and audio match', async () => { - await textAndAudioMatch(); - expect(player.isTextTrackVisible()).toBe(false); - }); - }); // IF_SUBTITLES_MAY_BE_NEEDED - - describe('IF_PREFERRED_TEXT_LANGUAGE', () => { - beforeEach(() => { - player.configure( - 'autoShowText', - shaka.config.AutoShowText.IF_PREFERRED_TEXT_LANGUAGE); - }); - - it('enables text if text matches and audio does not', async () => { - await textMatchesAudioDoesNot(); - expect(player.isTextTrackVisible()).toBe(true); - }); - - it('disables text if text does not match', async () => { - await textDoesNotMatch(); - expect(player.isTextTrackVisible()).toBe(false); - }); - - it('enables text if both text and audio match', async () => { - await textAndAudioMatch(); - expect(player.isTextTrackVisible()).toBe(true); - }); - }); // IF_PREFERRED_TEXT_LANGUAGE - - describe('ALWAYS', () => { - beforeEach(() => { - player.configure('autoShowText', shaka.config.AutoShowText.ALWAYS); - }); - - it('enables text if text matches and audio does not', async () => { - await textMatchesAudioDoesNot(); - expect(player.isTextTrackVisible()).toBe(true); - }); - - it('enables text if text does not match', async () => { - await textDoesNotMatch(); - expect(player.isTextTrackVisible()).toBe(true); - }); - - it('enables text if both text and audio match', async () => { - await textAndAudioMatch(); - expect(player.isTextTrackVisible()).toBe(true); - }); - }); // ALWAYS - - describe('NEVER', () => { - beforeEach(() => { - player.configure('autoShowText', shaka.config.AutoShowText.NEVER); - }); - - it('disables text if text matches and audio does not', async () => { - await textMatchesAudioDoesNot(); - expect(player.isTextTrackVisible()).toBe(false); - }); - - it('disables text if text does not match', async () => { - await textDoesNotMatch(); - expect(player.isTextTrackVisible()).toBe(false); - }); - - it('disables text if both text and audio match', async () => { - await textAndAudioMatch(); - expect(player.isTextTrackVisible()).toBe(false); - }); - }); // NEVER - }); // AutoShowText - - describe('plays', () => { - it('with external text tracks', async () => { - await player.load('test:sintel_no_text_compiled'); - - // For some reason, using path-absolute URLs (i.e. without the hostname) - // like this doesn't work on Safari. So manually resolve the URL. - const locationUri = new goog.Uri(location.href); - const partialUri = new goog.Uri('/base/test/test/assets/text-clip.vtt'); - const absoluteUri = locationUri.resolve(partialUri); - const newTrack = await player.addTextTrackAsync( - absoluteUri.toString(), 'en', 'subtitles', 'text/vtt'); - - expect(newTrack.language).toBe('en'); - expect(player.getTextTracks()).toEqual([newTrack]); - - player.selectTextTrack(newTrack); - expect(player.getTextTracks()[0].active).toBe(true); - }); - - it('with cea closed captions', async () => { - await player.load('test:cea-708_mp4_compiled'); - - const textTracks = player.getTextTracks(); - expect(textTracks).toBeTruthy(); - expect(textTracks.length).toBe(1); - expect(textTracks[0].language).toBe('en'); - }); - - it('at higher playback rates', async () => { - await player.load('test:sintel_compiled'); - await video.play(); - await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 1, 10); - - // Enabling trick play should change our playback rate to the same rate. - player.trickPlay(2); - expect(video.playbackRate).toBe(2); - - // Let playback continue playing for a bit longer. - await shaka.test.Util.delay(2); - - // Cancelling trick play should return our playback rate to normal. - player.cancelTrickPlay(); - expect(video.playbackRate).toBe(1); - }); - - it('in sequence mode', async () => { - await player.load('test:sintel_sequence_compiled'); - expect(player.getManifest().sequenceMode).toBe(true); - - // Ensure the video plays. - await video.play(); - await waiter.timeoutAfter(20).waitUntilPlayheadReaches(video, 5); - - // Seek the video, and see if it can continue playing from that point. - // If this is a multiple of 10, it tends to flake on Tizen with stall - // detection disabled. So use 25. - video.currentTime = 25; - - // Expect that we can then reach the end of the video. - await waiter.timeoutAfter(40).waitForEnd(video); - }); - - // Regression test for #2326. - // - // 1. Construct an instance with a video element. - // 2. Don't call or await attach(). - // 3. Call load() with a MIME type, which triggers a check for native - // playback support. - // - // Note that a real playback may use a HEAD request to fetch a MIME type, - // even if one is not specified in load(). - it('immediately after construction with MIME type', async () => { - const testSchemeMimeType = 'application/x-test-manifest'; - await player.load('test:sintel_compiled', 0, testSchemeMimeType); - await video.play(); - await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 1, 10); - }); - - /** - * Gets the language of the active Variant. - * @return {string} - */ - function getActiveLanguage() { - const tracks = player.getVariantTracks().filter((t) => { - return t.active; - }); - expect(tracks.length).toBeGreaterThan(0); - return tracks[0].language; - } - }); // describe('plays') - - describe('TextDisplayer plugin', () => { - // Simulate the use of an external TextDisplayer plugin. - /** @type {shaka.test.FakeTextDisplayer} */ - let textDisplayer; - beforeEach(() => { - textDisplayer = new shaka.test.FakeTextDisplayer(); - - textDisplayer.isTextVisibleSpy.and.callFake(() => { - return false; - }); - textDisplayer.destroySpy.and.returnValue(Promise.resolve()); - player.configure('textDisplayFactory', () => textDisplayer); - - // Make sure the configuration was taken. - const configuredFactory = player.getConfiguration().textDisplayFactory; - const configuredTextDisplayer = configuredFactory(); - expect(configuredTextDisplayer).toBe(textDisplayer); - }); - - // Regression test for https://github.com/shaka-project/shaka-player/issues/1187 - it('does not throw on destroy', async () => { - await player.load('test:sintel_compiled'); - await video.play(); - await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 1, 10); - await player.unload(); - // Before we fixed #1187, the call to destroy() on textDisplayer was - // renamed in the compiled version and could not be called. - expect(textDisplayer.destroySpy).toHaveBeenCalled(); - }); - }); // describe('TextDisplayer plugin') - - describe('TextAndRoles', () => { - // Regression Test. Makes sure that the language and role fields have been - // properly exported from the player. - it('exports language and roles fields', async () => { - await player.load('test:sintel_compiled'); - const languagesAndRoles = player.getTextLanguagesAndRoles(); - expect(languagesAndRoles.length).toBeTruthy(); - for (const languageAndRole of languagesAndRoles) { - expect(languageAndRole.language).not.toBeUndefined(); - expect(languageAndRole.role).not.toBeUndefined(); - } - }); - }); // describe('TextAndRoles') - - describe('streaming event', () => { - // Calling switch early during load() caused a failed assertion in Player - // and the track selection was ignored. Because this bug involved - // interactions between Player and StreamingEngine, it is an integration - // test and not a unit test. - // https://github.com/shaka-project/shaka-player/issues/1119 - it('allows early selection of specific tracks', async () => { - /** @type {!jasmine.Spy} */ - const streamingListener = jasmine.createSpy('listener'); - - // Because this is an issue with failed assertions, destroy the existing - // player from the compiled version, and create a new one using the - // uncompiled version. Then we will get assertions. - eventManager.unlisten(player, 'error'); - await player.destroy(); - player = new shaka.Player(); // NOTE: MUST BE UNCOMPILED - await player.attach(video); - player.configure({abr: {enabled: false}}); - eventManager.listen(player, 'error', Util.spyFunc(onErrorSpy)); - - // When 'streaming' fires, select the first track explicitly. - player.addEventListener('streaming', Util.spyFunc(streamingListener)); - streamingListener.and.callFake(() => { - const tracks = player.getVariantTracks(); - player.selectVariantTrack(tracks[0]); - }); - - // Now load the content. - await player.load('test:sintel'); - - // When the bug triggers, we fail assertions in Player. - // Make sure the listener was triggered, so that it could trigger the - // code path in this bug. - expect(streamingListener).toHaveBeenCalled(); - }); - - // After fixing the issue above, calling switch early during a second load() - // caused a failed assertion in StreamingEngine, because we did not reset - // switchingPeriods_ in Player. Because this bug involved interactions - // between Player and StreamingEngine, it is an integration test and not a - // unit test. - // https://github.com/shaka-project/shaka-player/issues/1119 - it('allows selection of tracks in subsequent loads', async () => { - /** @type {!jasmine.Spy} */ - const streamingListener = jasmine.createSpy('listener'); - - // Because this is an issue with failed assertions, destroy the existing - // player from the compiled version, and create a new one using the - // uncompiled version. Then we will get assertions. - eventManager.unlisten(player, 'error'); - await player.destroy(); - player = new shaka.Player(); // NOTE: MUST BE UNCOMPILED - await player.attach(video); - player.configure({abr: {enabled: false}}); - eventManager.listen(player, 'error', Util.spyFunc(onErrorSpy)); - - // This bug only triggers when you do this on the second load. - // So we load one piece of content, then set up the streaming listener - // to change tracks, then we load a second piece of content. - await player.load('test:sintel'); - - // Give StreamingEngine time to complete all setup and to call back into - // the Player with canSwitch_. If you move on too quickly to the next - // load(), the bug does not reproduce. - await shaka.test.Util.delay(1); - - player.addEventListener('streaming', Util.spyFunc(streamingListener)); - - streamingListener.and.callFake(() => { - const track = player.getVariantTracks()[0]; - player.selectVariantTrack(track); - }); - - // Now load again to trigger the failed assertion. - await player.load('test:sintel'); - - // When the bug triggers, we fail assertions in StreamingEngine. - // So just make sure the listener was triggered, so that it could - // trigger the code path in this bug. - expect(streamingListener).toHaveBeenCalled(); - }); - }); // describe('streaming event') - - describe('tracks', () => { - // This is a regression test for b/138941217, in which tracks briefly - // vanished during the loading process. On Chromecast devices, where the - // timing is very different from on desktop, this could occur such that - // there were no tracks after load() is resolved. - // This is an integration test so that we can check the behavior of the - // Player against actual platform behavior on all supported platforms. - it('remain available at every stage of loading', async () => { - let tracksFound = false; - - /** - * @param {string} when When the check takes place. - * - * Will fail the test if tracks disappear after they first become - * available. - */ - const checkTracks = (when) => { - // If tracks have already been found, expect them to still be found. - const tracksNow = player.getVariantTracks().length != 0; - if (tracksFound) { - expect(tracksNow).withContext(when).toBe(true); - } else { - // If tracks are now found, they should not, at any point during - // the loading process, disappear again. - if (tracksNow) { - tracksFound = true; - } - } - shaka.log.debug( - 'checkTracks', when, - 'tracksFound=', tracksFound, - 'tracksNow=', tracksNow); - }; - - /** @param {Event} event */ - const checkOnEvent = (event) => { - checkTracks(event.type + ' event'); - }; - - // On each of these events, we will notice when tracks first appear, and - // verify that they never disappear at any point in the loading sequence. - eventManager.listen(video, 'canplay', checkOnEvent); - eventManager.listen(video, 'canplaythrough', checkOnEvent); - eventManager.listen(video, 'durationchange', checkOnEvent); - eventManager.listen(video, 'emptied', checkOnEvent); - eventManager.listen(video, 'loadeddata', checkOnEvent); - eventManager.listen(video, 'loadedmetadata', checkOnEvent); - eventManager.listen(video, 'loadstart', checkOnEvent); - eventManager.listen(video, 'pause', checkOnEvent); - eventManager.listen(video, 'play', checkOnEvent); - eventManager.listen(video, 'playing', checkOnEvent); - eventManager.listen(video, 'seeked', checkOnEvent); - eventManager.listen(video, 'seeking', checkOnEvent); - eventManager.listen(video, 'stalled', checkOnEvent); - eventManager.listen(video, 'waiting', checkOnEvent); - eventManager.listen(player, 'trackschanged', checkOnEvent); - - const waiter = new shaka.test.Waiter(eventManager) - .setPlayer(player) - .timeoutAfter(10); - const canPlayThrough = waiter.waitForEvent(video, 'canplaythrough'); - - await player.load('test:sintel_compiled', 5); - shaka.log.debug('load resolved'); - - // When load is resolved(), tracks should definitely exist. - expect(tracksFound).toBe(true); - - // Let the test keep running until we can play through. In the original - // bug, tracks would disappear _after_ load() on some platforms. - await canPlayThrough; - }); - }); // describe('tracks') - - describe('loading', () => { - // A regression test for Issue #2433. - it('can load very large files', async () => { - // Reset the lazy function, so that it does not remember any chunk size - // that was detected beforehand. - compiledShaka.util.StringUtils.resetFromCharCode(); - const oldFromCharCode = String.fromCharCode; - try { - // Replace String.fromCharCode with a version that can only handle very - // small chunks. - // This has to be an old-style function, to use the "arguments" object. - // eslint-disable-next-line no-restricted-syntax - String.fromCharCode = function() { - if (arguments.length > 2000) { - throw new RangeError('Synthetic Range Error'); - } - // eslint-disable-next-line prefer-spread - return oldFromCharCode.apply(null, arguments); - }; - await player.load('/base/test/test/assets/large_file.mpd'); - } finally { - String.fromCharCode = oldFromCharCode; - } - }); - }); - - describe('mediaQualityChanges', () => { - /** @type {!jasmine.Spy} */ - let onQualityChange; - /** @type {!shaka.test.Waiter} */ - let waiter; - - const qualityChange1 = jasmine.objectContaining({ - mediaQuality: jasmine.objectContaining({ - bandwidth: 1, - codecs: 'mp4a.40.2', - contentType: 'audio', - mimeType: 'audio/mp4', - }), - type: 'mediaqualitychanged', - position: jasmine.any(Number), - }); - const qualityChange2 = jasmine.objectContaining({ - mediaQuality: jasmine.objectContaining({ - bandwidth: 1, - codecs: 'avc1.42c01e', - contentType: 'video', - mimeType: 'video/mp4', - }), - type: 'mediaqualitychanged', - position: jasmine.any(Number), - }); - - beforeEach(() => { - onQualityChange = jasmine.createSpy('onQualityChange'); - // const spyFunc = Util.spyFunc(onQualityChange); - player.addEventListener('mediaqualitychanged', - Util.spyFunc(onQualityChange)); - waiter = new shaka.test.Waiter(eventManager) - .setPlayer(player) - .timeoutAfter(10) - .failOnTimeout(true); - }); - - it('emits audio/video quality changes at start when enabled', async () => { - player.configure('streaming.observeQualityChanges', true); - - await player.load('test:sintel_compiled'); - await video.play(); - - // Wait for the video to start playback. If it takes longer than 10 - // seconds, fail the test. - await waiter.waitForMovementOrFailOnTimeout(video, 10); - expect(onQualityChange).toHaveBeenCalledTimes(2); - expect(onQualityChange).toHaveBeenCalledWith(qualityChange1); - expect(onQualityChange).toHaveBeenCalledWith(qualityChange2); - }); - - it('does not emit quality changes at start when disabled', async () => { - player.configure('streaming.observeQualityChanges', false); - - await player.load('test:sintel_compiled'); - await video.play(); - - // Wait for the video to start playback. If it takes longer than 10 - // seconds, fail the test. - await waiter.waitForMovementOrFailOnTimeout(video, 10); - expect(onQualityChange).not.toHaveBeenCalled(); - }); - }); - - describe('buffering', () => { - const startBuffering = jasmine.objectContaining({buffering: true}); - const endBuffering = jasmine.objectContaining({buffering: false}); - /** @type {!jasmine.Spy} */ - let onBuffering; - /** @type {!shaka.test.Waiter} */ - let waiter; - - beforeEach(() => { - onBuffering = jasmine.createSpy('onBuffering'); - player.addEventListener('buffering', Util.spyFunc(onBuffering)); - - waiter = new shaka.test.Waiter(eventManager) - .setPlayer(player) - .timeoutAfter(10) - .failOnTimeout(true); - }); - - it('enters/exits buffering state at start', async () => { - // Set a large rebuffer goal to ensure we can see the buffering before - // we start playing. - player.configure('streaming.rebufferingGoal', 30); - - await player.load('test:sintel_long_compiled'); - video.pause(); - expect(onBuffering).toHaveBeenCalledTimes(1); - expect(onBuffering).toHaveBeenCalledWith(startBuffering); - onBuffering.calls.reset(); - - await waiter.waitForEvent(player, 'buffering'); - expect(onBuffering).toHaveBeenCalledTimes(1); - expect(onBuffering).toHaveBeenCalledWith(endBuffering); - - expect(getBufferedAhead()).toBeGreaterThanOrEqual(30); - }); - - it('enters/exits buffering state while playing', async () => { - player.configure('streaming.rebufferingGoal', 1); - player.configure('streaming.bufferingGoal', 10); - - await player.load('test:sintel_long_compiled'); - video.pause(); - if (player.isBuffering()) { - await waiter.waitForEvent(player, 'buffering'); - } - onBuffering.calls.reset(); - - player.configure('streaming.rebufferingGoal', 30); - video.currentTime = 70; - await waiter.waitForEvent(player, 'buffering'); - expect(onBuffering).toHaveBeenCalledTimes(1); - expect(onBuffering).toHaveBeenCalledWith(startBuffering); - onBuffering.calls.reset(); - - expect(getBufferedAhead()).toBeLessThan(30); - - await waiter.waitForEvent(player, 'buffering'); - expect(onBuffering).toHaveBeenCalledTimes(1); - expect(onBuffering).toHaveBeenCalledWith(endBuffering); - - expect(getBufferedAhead()).toBeGreaterThanOrEqual(30); - }); - - it('buffers ahead of the playhead', async () => { - if (window.ManagedMediaSource) { - pending('ManagedMediaSource has buffer control signals.'); - } - player.configure('streaming.bufferingGoal', 10); - - await player.load('test:sintel_long_compiled'); - video.pause(); - await waiter.waitUntilBuffered(video, 10); - - player.configure('streaming.bufferingGoal', 30); - await waiter.waitUntilBuffered(video, 30); - - player.configure('streaming.bufferingGoal', 60); - await waiter.waitUntilBuffered(video, 60); - await Util.delay(1); - expect(getBufferedAhead()).toBeLessThan(70); // 60 + segment_size - - // We don't remove buffered content ahead of the playhead, so seek to - // clear the buffer. - player.configure('streaming.bufferingGoal', 10); - video.currentTime = 120; - await waiter.waitUntilBuffered(video, 10); - await Util.delay(1); - expect(getBufferedAhead()).toBeLessThan(20); // 10 + segment_size - }); - - it('clears buffer behind playhead', async () => { - if (window.ManagedMediaSource) { - pending('ManagedMediaSource has buffer control signals.'); - } - player.configure('streaming.bufferingGoal', 30); - player.configure('streaming.bufferBehind', 30); - - await player.load('test:sintel_long_compiled'); - video.pause(); - await waiter.waitUntilBuffered(video, 30); - video.currentTime = 20; - await waiter.waitUntilBuffered(video, 30); - - expect(getBufferedBehind()).toBe(20); // Buffered to start still. - video.currentTime = 50; - await waiter.waitUntilBuffered(video, 30); - await Util.delay(1); - expect(getBufferedBehind()).toBeLessThan(40); // 30 + segment_size - - player.configure('streaming.bufferBehind', 10); - // We only evict content when we append a segment, so increase the - // buffering goal so we append another segment. - player.configure('streaming.bufferingGoal', 40); - await waiter.waitUntilBuffered(video, 40); - await Util.delay(1); - expect(getBufferedBehind()).toBeLessThanOrEqual(10); - }); - - function getBufferedAhead() { - const end = shaka.media.TimeRangesUtils.bufferEnd(video.buffered); - if (end == null) { - return 0; - } - return end - video.currentTime; - } - - function getBufferedBehind() { - const start = shaka.media.TimeRangesUtils.bufferStart(video.buffered); - if (start == null) { - return 0; - } - return video.currentTime - start; - } - }); // describe('buffering') - - describe('configuration', () => { - it('has the correct number of arguments in compiled callbacks', () => { - // Get the default configuration for both the compiled & uncompiled - // versions for comparison. - const compiledConfig = (new compiledShaka.Player()).getConfiguration(); - const uncompiledConfig = (new shaka.Player()).getConfiguration(); - - compareConfigFunctions(compiledConfig, uncompiledConfig); - - /** - * Find all the callbacks in the configuration recursively and compare - * their lengths (number of arguments). We warn the app developer when a - * configured callback has the wrong number of arguments, so our own - * compiled versions must be correct. - * - * @param {Object} compiled - * @param {Object} uncompiled - * @param {string=} basePath The path to this point in the config, for - * logging purposes. - */ - function compareConfigFunctions(compiled, uncompiled, basePath = '') { - for (const key in uncompiled) { - const uncompiledValue = uncompiled[key]; - const compiledValue = compiled[key]; - const path = basePath + '.' + key; - - if (uncompiledValue && uncompiledValue.constructor == Object) { - // This is an anonymous Object, so recurse on it. - compareConfigFunctions(compiledValue, uncompiledValue, path); - } else if (typeof uncompiledValue == 'function') { - // This is a function, so check its length. The uncompiled version - // is considered canonically correct, so we use the uncompiled - // length as the expectation. - shaka.log.debug('[' + path + ']', - compiledValue.length, 'should be', uncompiledValue.length); - expect(compiledValue.length).withContext(path) - .toBe(uncompiledValue.length); - } - } - } - }); - }); // describe('configuration') - - describe('adaptation', () => { - /** @type {!shaka.test.FakeAbrManager} */ - let abrManager; - - beforeEach(() => { - abrManager = new shaka.test.FakeAbrManager(); - player.configure('abrFactory', () => abrManager); - }); - - it('fires "adaptation" event', async () => { - const abrEnabled = new Promise((resolve) => { - abrManager.enable.and.callFake(resolve); - }); - - await player.load('test:sintel_multi_lingual_multi_res_compiled'); - - expect(abrManager.switchCallback).toBeTruthy(); - expect(abrManager.variants.length).toBeGreaterThan(1); - expect(abrManager.chooseIndex).toBe(0); - - /** @type {shaka.test.Waiter} */ - const waiter = new shaka.test.Waiter(eventManager) - .setPlayer(player) - .timeoutAfter(1) - .failOnTimeout(true); - - await waiter.waitForPromise(abrEnabled, 'AbrManager enabled'); - - const p = waiter.waitForEvent(player, 'adaptation'); - abrManager.switchCallback(abrManager.variants[1]); - await expectAsync(p).toBeResolved(); - }); - - it('doesn\'t fire "adaptation" when not changing streams', async () => { - const abrEnabled = new Promise((resolve) => { - abrManager.enable.and.callFake(resolve); - }); - - await player.load('test:sintel_multi_lingual_multi_res_compiled'); - - expect(abrManager.switchCallback).toBeTruthy(); - - /** @type {shaka.test.Waiter} */ - const waiter = new shaka.test.Waiter(eventManager) - .setPlayer(player) - .timeoutAfter(1) - .failOnTimeout(true); - - await waiter.waitForPromise(abrEnabled, 'AbrManager enabled'); - - const p = waiter.waitForEvent(player, 'adaptation'); - for (let i = 0; i < 3; i++) { - abrManager.switchCallback(abrManager.variants[abrManager.chooseIndex]); - } - await expectAsync(p).toBeRejected(); // Timeout - }); - }); // describe('adaptation') - /** Regression test for Issue #2741 */ describe('unloading', () => { drmIt('unloads properly after DRM error', async () => { @@ -1227,191 +102,4 @@ describe('Player', () => { await loadPromise; }); }); // describe('unloading') - - describe('addChaptersTrack', () => { - it('adds external chapters in vtt format', async () => { - await player.load('test:sintel_no_text_compiled'); - const locationUri = new goog.Uri(location.href); - const partialUri1 = new goog.Uri('/base/test/test/assets/chapters.vtt'); - const absoluteUri1 = locationUri.resolve(partialUri1); - await player.addChaptersTrack(absoluteUri1.toString(), 'en'); - - // Data should be available as soon as addChaptersTrack resolves. - // See https://github.com/shaka-project/shaka-player/issues/4186 - const chapters = player.getChapters('en'); - expect(chapters.length).toBe(3); - const chapter1 = chapters[0]; - expect(chapter1.title).toBe('Chapter 1'); - expect(chapter1.startTime).toBe(0); - expect(chapter1.endTime).toBe(5); - const chapter2 = chapters[1]; - expect(chapter2.title).toBe('Chapter 2'); - expect(chapter2.startTime).toBe(5); - expect(chapter2.endTime).toBe(10); - const chapter3 = chapters[2]; - expect(chapter3.title).toBe('Chapter 3'); - expect(chapter3.startTime).toBe(10); - expect(chapter3.endTime).toBe(20); - - const partialUri2 = new goog.Uri('/base/test/test/assets/chapters2.vtt'); - const absoluteUri2 = locationUri.resolve(partialUri2); - await player.addChaptersTrack(absoluteUri2.toString(), 'en'); - - const chaptersUpdated = player.getChapters('en'); - expect(chaptersUpdated.length).toBe(6); - const chapterUpdated1 = chaptersUpdated[0]; - expect(chapterUpdated1.title).toBe('Chapter 1'); - expect(chapterUpdated1.startTime).toBe(0); - expect(chapterUpdated1.endTime).toBe(5); - const chapterUpdated2 = chaptersUpdated[1]; - expect(chapterUpdated2.title).toBe('Chapter 2'); - expect(chapterUpdated2.startTime).toBe(5); - expect(chapterUpdated2.endTime).toBe(10); - const chapterUpdated3 = chaptersUpdated[2]; - expect(chapterUpdated3.title).toBe('Chapter 3'); - expect(chapterUpdated3.startTime).toBe(10); - expect(chapterUpdated3.endTime).toBe(20); - const chapterUpdated4 = chaptersUpdated[3]; - expect(chapterUpdated4.title).toBe('Chapter 4'); - expect(chapterUpdated4.startTime).toBe(20); - expect(chapterUpdated4.endTime).toBe(30); - const chapterUpdated5 = chaptersUpdated[4]; - expect(chapterUpdated5.title).toBe('Chapter 5'); - expect(chapterUpdated5.startTime).toBe(30); - expect(chapterUpdated5.endTime).toBe(40); - const chapterUpdated6 = chaptersUpdated[5]; - expect(chapterUpdated6.title).toBe('Chapter 6'); - expect(chapterUpdated6.startTime).toBe(40); - expect(chapterUpdated6.endTime).toBe(61.349); - }); - - it('adds external chapters in srt format', async () => { - await player.load('test:sintel_no_text_compiled'); - const locationUri = new goog.Uri(location.href); - const partialUri = new goog.Uri('/base/test/test/assets/chapters.srt'); - const absoluteUri = locationUri.resolve(partialUri); - await player.addChaptersTrack(absoluteUri.toString(), 'es'); - - const chapters = player.getChapters('es'); - expect(chapters.length).toBe(3); - const chapter1 = chapters[0]; - expect(chapter1.title).toBe('Chapter 1'); - expect(chapter1.startTime).toBe(0); - expect(chapter1.endTime).toBe(5); - const chapter2 = chapters[1]; - expect(chapter2.title).toBe('Chapter 2'); - expect(chapter2.startTime).toBe(5); - expect(chapter2.endTime).toBe(30); - const chapter3 = chapters[2]; - expect(chapter3.title).toBe('Chapter 3'); - expect(chapter3.startTime).toBe(30); - expect(chapter3.endTime).toBe(61.349); - }); - }); // describe('addChaptersTrack') - - it('requires a license server for HLS ClearKey content', async () => { - const expectedError = Util.jasmineError(new shaka.util.Error( - shaka.util.Error.Severity.CRITICAL, - shaka.util.Error.Category.DRM, - shaka.util.Error.Code.NO_LICENSE_SERVER_GIVEN, - 'org.w3.clearkey')); - await expectAsync(player.load('test:sintel-hls-clearkey')) - .toBeRejectedWith(expectedError); - }); - - describe('addThumbnailsTrack', () => { - it('appends thumbnails for external thumbnails with sprites', - async () => { - await player.load('test:sintel_no_text_compiled'); - const locationUri = new goog.Uri(location.href); - const partialUri = - new goog.Uri('/base/test/test/assets/thumbnails-sprites.vtt'); - const absoluteUri = locationUri.resolve(partialUri); - const newTrack = - await player.addThumbnailsTrack(absoluteUri.toString()); - - expect(player.getImageTracks()).toEqual([newTrack]); - - const thumbnail1 = await player.getThumbnails(newTrack.id, 0); - expect(thumbnail1.startTime).toBe(0); - expect(thumbnail1.duration).toBe(5); - expect(thumbnail1.height).toBe(90); - expect(thumbnail1.positionX).toBe(0); - expect(thumbnail1.positionY).toBe(0); - expect(thumbnail1.width).toBe(160); - const thumbnail2 = await player.getThumbnails(newTrack.id, 10); - expect(thumbnail2.startTime).toBe(5); - expect(thumbnail2.duration).toBe(25); - expect(thumbnail2.height).toBe(90); - expect(thumbnail2.positionX).toBe(160); - expect(thumbnail2.positionY).toBe(0); - expect(thumbnail2.width).toBe(160); - const thumbnail3 = await player.getThumbnails(newTrack.id, 40); - expect(thumbnail3.startTime).toBe(30); - expect(thumbnail3.duration).toBe(30); - expect(thumbnail3.height).toBe(90); - expect(thumbnail3.positionX).toBe(160); - expect(thumbnail3.positionY).toBe(90); - expect(thumbnail3.width).toBe(160); - - const thumbnails = await player.getAllThumbnails(newTrack.id); - expect(thumbnails.length).toBe(3); - }); - - it('appends thumbnails for external thumbnails without sprites', - async () => { - await player.load('test:sintel_no_text_compiled'); - const locationUri = new goog.Uri(location.href); - const partialUri = - new goog.Uri('/base/test/test/assets/thumbnails.vtt'); - const absoluteUri = locationUri.resolve(partialUri); - const newTrack = - await player.addThumbnailsTrack(absoluteUri.toString()); - - expect(player.getImageTracks()).toEqual([newTrack]); - - const thumbnail1 = await player.getThumbnails(newTrack.id, 0); - expect(thumbnail1.startTime).toBe(0); - expect(thumbnail1.duration).toBe(5); - const thumbnail2 = await player.getThumbnails(newTrack.id, 10); - expect(thumbnail2.startTime).toBe(5); - expect(thumbnail2.duration).toBe(25); - const thumbnail3 = await player.getThumbnails(newTrack.id, 40); - expect(thumbnail3.startTime).toBe(30); - expect(thumbnail3.duration).toBe(30); - - const thumbnails = await player.getAllThumbnails(newTrack.id); - expect(thumbnails.length).toBe(3); - }); - }); // describe('addThumbnailsTrack') - - it('preload', async () => { - const preloadManager = await player.preload('test:sintel_compiled'); - await preloadManager.waitForFinish(); - await player.load(preloadManager); - await video.play(); - await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 1, 10); - }); - - it('unloadAndSavePreload', async () => { - await player.load('test:sintel_compiled'); - await video.play(); - const preloadManager = await player.unloadAndSavePreload(); - await player.load(preloadManager); - await video.play(); - await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 1, 10); - }); - - describe('buffer gap', () => { - // Regression test for issue #6339. - it('skip initial buffer gap', async () => { - if (window.ManagedMediaSource) { - pending('Skipping test, only runs on MSE'); - } - // Ensure the video has loaded. - await player.load('/base/test/test/assets/6339/master.mpd'); - await waiter.timeoutAfter(10).waitForEvent(video, 'loadeddata'); - expect(video.currentTime).toBeGreaterThanOrEqual(0); - }); - }); }); diff --git a/test/test/util/ui_utils.js b/test/test/util/ui_utils.js index 9e1f70232a..2d5e7a1fc1 100644 --- a/test/test/util/ui_utils.js +++ b/test/test/util/ui_utils.js @@ -195,4 +195,18 @@ shaka.test.UiUtils = class { return canvas; } + + /** + * Creates a IMA SDK script element for testing. + * + * @return {!HTMLScriptElement} + */ + static createImaSdkScript() { + const script = /** @type {!HTMLScriptElement} */(document.createElement( + 'script')); + + script.src = 'https://imasdk.googleapis.com/js/sdkloader/ima3.js'; + + return script; + } };