UNPKG

shaka-player

Version:
1,078 lines (940 loc) 34.9 kB
/** * @license * Copyright 2016 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ describe('CastReceiver', function() { const CastReceiver = shaka.cast.CastReceiver; const CastUtils = shaka.cast.CastUtils; const Util = shaka.test.Util; const originalCast = window['cast']; const originalUserAgent = navigator.userAgent; /** @type {!shaka.test.FakeVideo} */ let mockVideo; /** @type {!jasmine.Spy} */ let mockAppDataCallback; let mockPlayer; let mockReceiverManager; let mockReceiverApi; let mockShakaMessageBus; let mockGenericMessageBus; /** @type {!jasmine.Spy} */ let mockCanDisplayType; /** @type {shaka.cast.CastReceiver} */ let receiver; /** @type {boolean} */ let isChrome; /** @type {boolean} */ let isChromecast; /** * Before running the test, check if this is Chrome or Chromecast. * @param {function(function()=)} test * @return {function(function())} */ function checkAndRun(test) { let check = function(done) { if (!isChromecast && !isChrome) { pending( 'Skipping CastReceiver tests for non-Chrome and non-Chromecast'); } else { test(done); } }; // Account for tests with a done argument, and tests without. if (test.length == 1) { return (done) => check(done); } return () => check(undefined); } beforeAll(function() { // The receiver is only meant to run on the Chromecast, so we have the // ability to use modern APIs there that may not be available on all of the // browsers our library supports. Because of this, CastReceiver tests will // only be run on Chrome and Chromecast. isChromecast = navigator.userAgent.includes('CrKey'); let isEdge = navigator.userAgent.includes('Edge/'); // Edge also has "Chrome/" in its user agent string. isChrome = navigator.userAgent.includes('Chrome/') && !isEdge; // Don't do any more work here if the tests will not end up running. if (!isChromecast && !isChrome) return; // In uncompiled mode, there is a UA check for Chromecast in order to make // manual testing easier. For these automated tests, we want to act as if // we are running on the Chromecast, even in Chrome. // Since we can't write to window.navigator or navigator.userAgent, we use // Object.defineProperty. Object.defineProperty(window['navigator'], 'userAgent', {value: 'CrKey', configurable: true}); }); beforeEach(checkAndRun(() => { mockReceiverApi = createMockReceiverApi(); mockCanDisplayType = jasmine.createSpy('canDisplayType'); mockCanDisplayType.and.returnValue(false); // We're using quotes to access window.cast because the compiler // knows about lots of Cast-specific APIs we aren't mocking. We // don't need this mock strictly type-checked. window['cast'] = { receiver: mockReceiverApi, __platform__: {canDisplayType: mockCanDisplayType}, }; mockReceiverManager = createMockReceiverManager(); mockShakaMessageBus = createMockMessageBus(); mockGenericMessageBus = createMockMessageBus(); mockVideo = new shaka.test.FakeVideo(); mockPlayer = createMockPlayer(); mockAppDataCallback = jasmine.createSpy('appDataCallback'); })); afterEach(function(done) { if (receiver) { receiver.destroy().catch(fail).then(done); } else { done(); } }); afterAll(function() { if (originalUserAgent) { window['cast'] = originalCast; Object.defineProperty(window['navigator'], 'userAgent', {value: originalUserAgent}); } }); describe('constructor', function() { it('starts the receiver manager', checkAndRun(() => { receiver = new CastReceiver( mockVideo, mockPlayer, Util.spyFunc(mockAppDataCallback)); expect(mockReceiverManager.start).toHaveBeenCalled(); })); it('listens for video and player events', checkAndRun(() => { receiver = new CastReceiver( mockVideo, mockPlayer, Util.spyFunc(mockAppDataCallback)); expect(Object.keys(mockVideo.on).length).toBeGreaterThan(0); expect(Object.keys(mockPlayer.listeners).length).toBeGreaterThan(0); })); it('limits streams to 1080p on Chromecast v1 and v2', checkAndRun(() => { // Simulate the canDisplayType reponse of Chromecast v1 or v2 mockCanDisplayType.and.callFake(function(type) { let matches = /height=(\d+)/.exec(type); let height = matches[1]; if (height && height > 1080) return false; return true; }); receiver = new CastReceiver( mockVideo, mockPlayer, Util.spyFunc(mockAppDataCallback)); expect(mockCanDisplayType).toHaveBeenCalled(); expect(mockPlayer.setMaxHardwareResolution). toHaveBeenCalledWith(1920, 1080); })); it('limits streams to 4k on Chromecast Ultra', checkAndRun(() => { // Simulate the canDisplayType reponse of Chromecast Ultra mockCanDisplayType.and.callFake(function(type) { let matches = /height=(\d+)/.exec(type); let height = matches[1]; if (height && height > 2160) return false; return true; }); receiver = new CastReceiver( mockVideo, mockPlayer, Util.spyFunc(mockAppDataCallback)); expect(mockCanDisplayType).toHaveBeenCalled(); expect(mockPlayer.setMaxHardwareResolution). toHaveBeenCalledWith(3840, 2160); })); it('does not start polling', checkAndRun(() => { receiver = new CastReceiver( mockVideo, mockPlayer, Util.spyFunc(mockAppDataCallback)); expect(mockPlayer.getConfiguration).not.toHaveBeenCalled(); expect(mockShakaMessageBus.messages.length).toBe(0); })); }); describe('isConnected', function() { beforeEach(function() { receiver = new CastReceiver( mockVideo, mockPlayer, Util.spyFunc(mockAppDataCallback)); }); it('is true when there are senders', checkAndRun(() => { expect(receiver.isConnected()).toBe(false); fakeConnectedSenders(1); expect(receiver.isConnected()).toBe(true); fakeConnectedSenders(2); expect(receiver.isConnected()).toBe(true); fakeConnectedSenders(99); expect(receiver.isConnected()).toBe(true); fakeConnectedSenders(0); expect(receiver.isConnected()).toBe(false); })); }); describe('"caststatuschanged" event', function() { beforeEach(function() { receiver = new CastReceiver( mockVideo, mockPlayer, Util.spyFunc(mockAppDataCallback)); }); it('triggers when senders connect or disconnect', checkAndRun((done) => { let listener = jasmine.createSpy('listener'); receiver.addEventListener('caststatuschanged', Util.spyFunc(listener)); shaka.test.Util.delay(0.2).then(function() { expect(listener).not.toHaveBeenCalled(); fakeConnectedSenders(1); return shaka.test.Util.delay(0.2); }).then(function() { expect(listener).toHaveBeenCalled(); listener.calls.reset(); mockReceiverManager.onSenderDisconnected(); return shaka.test.Util.delay(0.2); }).then(function() { expect(listener).toHaveBeenCalled(); }).catch(fail).then(done); })); it('triggers when idle state changes', checkAndRun((done) => { let listener = jasmine.createSpy('listener'); receiver.addEventListener('caststatuschanged', Util.spyFunc(listener)); let fakeLoadingEvent = {type: 'loading'}; let fakeUnloadingEvent = {type: 'unloading'}; let fakeEndedEvent = {type: 'ended'}; let fakePlayingEvent = {type: 'playing'}; shaka.test.Util.delay(0.2).then(function() { expect(listener).not.toHaveBeenCalled(); expect(receiver.isIdle()).toBe(true); mockPlayer.listeners['loading'](fakeLoadingEvent); return shaka.test.Util.delay(0.2); }).then(function() { expect(listener).toHaveBeenCalled(); expect(receiver.isIdle()).toBe(false); listener.calls.reset(); mockPlayer.listeners['unloading'](fakeUnloadingEvent); return shaka.test.Util.delay(0.2); }).then(function() { expect(listener).toHaveBeenCalled(); expect(receiver.isIdle()).toBe(true); listener.calls.reset(); mockVideo.ended = true; mockVideo.on['ended'](fakeEndedEvent); return shaka.test.Util.delay(5.2); // There is a long delay for 'ended' }).then(function() { expect(listener).toHaveBeenCalled(); listener.calls.reset(); expect(receiver.isIdle()).toBe(true); mockVideo.ended = false; mockVideo.on['playing'](fakePlayingEvent); }).then(function() { expect(listener).toHaveBeenCalled(); expect(receiver.isIdle()).toBe(false); }).catch(fail).then(done); })); }); describe('local events', function() { beforeEach(function() { receiver = new CastReceiver( mockVideo, mockPlayer, Util.spyFunc(mockAppDataCallback)); }); it('trigger "update" and "event" messages', checkAndRun(() => { fakeConnectedSenders(1); // No messages yet. expect(mockShakaMessageBus.messages).toEqual([]); let fakeEvent = {type: 'timeupdate'}; mockVideo.on['timeupdate'](fakeEvent); // There are now "update" and "event" messages, in that order. expect(mockShakaMessageBus.messages).toEqual([ { type: 'update', update: jasmine.any(Object), }, { type: 'event', targetName: 'video', event: jasmine.objectContaining(fakeEvent), }, ]); })); }); describe('"init" message', function() { /** @const */ let fakeConfig = {key: 'value'}; /** @const */ let fakeAppData = {myFakeAppData: 1234}; let fakeInitState; beforeEach(function() { receiver = new CastReceiver( mockVideo, mockPlayer, Util.spyFunc(mockAppDataCallback)); fakeInitState = { player: { configure: fakeConfig, }, playerAfterLoad: { setTextTrackVisibility: true, }, video: { loop: true, playbackRate: 5, }, }; }); it('sets initial state', checkAndRun((done) => { expect(mockVideo.loop).toBe(false); expect(mockVideo.playbackRate).toBe(1); expect(mockPlayer.configure).not.toHaveBeenCalled(); fakeIncomingMessage({ type: 'init', initState: fakeInitState, appData: fakeAppData, }, mockShakaMessageBus); // Initial Player state first: expect(mockPlayer.configure).toHaveBeenCalledWith(fakeConfig); // App data next: expect(mockAppDataCallback).toHaveBeenCalledWith(fakeAppData); // Nothing else yet: expect(mockPlayer.setTextTrackVisibility).not.toHaveBeenCalled(); expect(mockVideo.loop).toBe(false); expect(mockVideo.playbackRate).toBe(1); // The rest is done async: shaka.test.Util.delay(0.1).then(function() { expect(mockPlayer.setTextTrackVisibility).toHaveBeenCalledWith( fakeInitState['playerAfterLoad'].setTextTrackVisibility); expect(mockVideo.loop).toEqual(fakeInitState.video.loop); expect(mockVideo.playbackRate).toEqual( fakeInitState.video.playbackRate); }).catch(fail).then(done); })); it('starts polling', checkAndRun(() => { let fakeConfig = {key: 'value'}; mockPlayer.getConfiguration.and.returnValue(fakeConfig); fakeConnectedSenders(1); mockPlayer.getConfiguration.calls.reset(); expect(mockShakaMessageBus.messages.length).toBe(0); fakeIncomingMessage({ type: 'init', initState: fakeInitState, appData: fakeAppData, }, mockShakaMessageBus); expect(mockPlayer.getConfiguration).toHaveBeenCalled(); expect(mockShakaMessageBus.messages).toContain(jasmine.objectContaining({ type: 'update', update: jasmine.objectContaining({ player: jasmine.objectContaining({ getConfiguration: fakeConfig, }), }), })); })); it('doesn\'t poll live methods while loading a VOD', checkAndRun(() => { mockPlayer.getConfiguration.and.returnValue({key: 'value'}); mockPlayer.isLive.and.returnValue(false); fakeConnectedSenders(1); expect(mockShakaMessageBus.messages.length).toBe(0); fakeIncomingMessage({ type: 'init', initState: fakeInitState, appData: fakeAppData, }, mockShakaMessageBus); expect(mockPlayer.getPlayheadTimeAsDate).not.toHaveBeenCalled(); })); it('does poll live methods while loading a livestream', checkAndRun(() => { mockPlayer.getConfiguration.and.returnValue({key: 'value'}); mockPlayer.isLive.and.returnValue(true); fakeConnectedSenders(1); expect(mockShakaMessageBus.messages.length).toBe(0); fakeIncomingMessage({ type: 'init', initState: fakeInitState, appData: fakeAppData, }, mockShakaMessageBus); expect(mockPlayer.getPlayheadTimeAsDate).toHaveBeenCalled(); })); it('loads the manifest', checkAndRun(() => { fakeInitState.startTime = 12; fakeInitState.manifest = 'foo://bar'; expect(mockPlayer.load).not.toHaveBeenCalled(); fakeIncomingMessage({ type: 'init', initState: fakeInitState, appData: fakeAppData, }, mockShakaMessageBus); expect(mockPlayer.load).toHaveBeenCalledWith('foo://bar', 12); })); it('plays the video after loading', checkAndRun((done) => { fakeInitState.manifest = 'foo://bar'; mockVideo.autoplay = true; fakeIncomingMessage({ type: 'init', initState: fakeInitState, appData: fakeAppData, }, mockShakaMessageBus); // Video autoplay inhibited: expect(mockVideo.autoplay).toBe(false); shaka.test.Util.delay(0.1).then(function() { expect(mockVideo.play).toHaveBeenCalled(); // Video autoplay restored: expect(mockVideo.autoplay).toBe(true); }).catch(fail).then(done); })); it('does not load or play without a manifest URI', checkAndRun((done) => { fakeInitState.manifest = null; fakeIncomingMessage({ type: 'init', initState: fakeInitState, appData: fakeAppData, }, mockShakaMessageBus); shaka.test.Util.delay(0.1).then(function() { // Nothing loaded or played: expect(mockPlayer.load).not.toHaveBeenCalled(); expect(mockVideo.play).not.toHaveBeenCalled(); // State was still transferred, though: expect(mockPlayer.setTextTrackVisibility).toHaveBeenCalledWith( fakeInitState['playerAfterLoad'].setTextTrackVisibility); expect(mockVideo.loop).toEqual(fakeInitState.video.loop); expect(mockVideo.playbackRate).toEqual( fakeInitState.video.playbackRate); }).catch(fail).then(done); })); it('triggers an "error" event if load fails', checkAndRun((done) => { fakeInitState.manifest = 'foo://bar'; let fakeError = new shaka.util.Error( shaka.util.Error.Severity.CRITICAL, shaka.util.Error.Category.MANIFEST, shaka.util.Error.Code.UNABLE_TO_GUESS_MANIFEST_TYPE); mockPlayer.load.and.returnValue(Promise.reject(fakeError)); let listener = jasmine.createSpy('listener'); mockPlayer.addEventListener('error', listener); expect(listener).not.toHaveBeenCalled(); fakeIncomingMessage({ type: 'init', initState: fakeInitState, appData: fakeAppData, }, mockShakaMessageBus); shaka.test.Util.delay(0.1).then(function() { expect(mockPlayer.load).toHaveBeenCalled(); expect(mockPlayer.dispatchEvent).toHaveBeenCalledWith( jasmine.objectContaining({type: 'error', detail: fakeError})); }).catch(fail).then(done); })); }); describe('"appData" message', function() { beforeEach(function() { receiver = new CastReceiver( mockVideo, mockPlayer, Util.spyFunc(mockAppDataCallback)); }); it('triggers the app data callback', checkAndRun(() => { expect(mockAppDataCallback).not.toHaveBeenCalled(); let fakeAppData = {myFakeAppData: 1234}; fakeIncomingMessage({ type: 'appData', appData: fakeAppData, }, mockShakaMessageBus); expect(mockAppDataCallback).toHaveBeenCalledWith(fakeAppData); })); }); describe('"set" message', function() { beforeEach(function() { receiver = new CastReceiver( mockVideo, mockPlayer, Util.spyFunc(mockAppDataCallback)); }); it('sets local properties', checkAndRun(() => { expect(mockVideo.currentTime).toBe(0); fakeIncomingMessage({ type: 'set', targetName: 'video', property: 'currentTime', value: 12, }, mockShakaMessageBus); expect(mockVideo.currentTime).toEqual(12); expect(mockPlayer['arbitraryName']).toBe(undefined); fakeIncomingMessage({ type: 'set', targetName: 'player', property: 'arbitraryName', value: 'arbitraryValue', }, mockShakaMessageBus); expect(mockPlayer['arbitraryName']).toEqual('arbitraryValue'); })); it('routes volume properties to the receiver manager', checkAndRun(() => { expect(mockVideo.volume).toBe(1); expect(mockVideo.muted).toBe(false); expect(mockReceiverManager.setSystemVolumeLevel).not.toHaveBeenCalled(); expect(mockReceiverManager.setSystemVolumeMuted).not.toHaveBeenCalled(); fakeIncomingMessage({ type: 'set', targetName: 'video', property: 'volume', value: 0.5, }, mockShakaMessageBus); fakeIncomingMessage({ type: 'set', targetName: 'video', property: 'muted', value: true, }, mockShakaMessageBus); expect(mockVideo.volume).toBe(1); expect(mockVideo.muted).toBe(false); expect(mockReceiverManager.setSystemVolumeLevel). toHaveBeenCalledWith(0.5); expect(mockReceiverManager.setSystemVolumeMuted). toHaveBeenCalledWith(true); })); }); describe('"call" message', function() { beforeEach(function() { receiver = new CastReceiver( mockVideo, mockPlayer, Util.spyFunc(mockAppDataCallback)); }); it('calls local methods', checkAndRun(() => { expect(mockVideo.play).not.toHaveBeenCalled(); fakeIncomingMessage({ type: 'call', targetName: 'video', methodName: 'play', args: [1, 2, 3], }, mockShakaMessageBus); expect(mockVideo.play).toHaveBeenCalledWith(1, 2, 3); expect(mockPlayer.configure).not.toHaveBeenCalled(); fakeIncomingMessage({ type: 'call', targetName: 'player', methodName: 'configure', args: [42], }, mockShakaMessageBus); expect(mockPlayer.configure).toHaveBeenCalledWith(42); })); }); describe('"asyncCall" message', function() { /** @const */ let fakeSenderId = 'senderId'; /** @const */ let fakeCallId = '5'; /** @type {!shaka.util.PublicPromise} */ let p; beforeEach(function() { receiver = new CastReceiver( mockVideo, mockPlayer, Util.spyFunc(mockAppDataCallback)); fakeConnectedSenders(1); p = new shaka.util.PublicPromise(); mockPlayer.load.and.returnValue(p); expect(mockPlayer.load).not.toHaveBeenCalled(); fakeIncomingMessage({ type: 'asyncCall', id: fakeCallId, targetName: 'player', methodName: 'load', args: ['foo://bar', 12], }, mockShakaMessageBus, fakeSenderId); }); it('calls local async methods', checkAndRun(() => { expect(mockPlayer.load).toHaveBeenCalledWith('foo://bar', 12); p.resolve(); })); it('sends "asyncComplete" replies when resolved', checkAndRun((done) => { // No messages have been sent, either broadcast or privately. expect(mockShakaMessageBus.broadcast).not.toHaveBeenCalled(); expect(mockShakaMessageBus.getCastChannel).not.toHaveBeenCalled(); p.resolve(); shaka.test.Util.delay(0.1).then(function() { // No broadcast messages have been sent, but a private message has // been sent to the sender who started the async call. expect(mockShakaMessageBus.broadcast).not.toHaveBeenCalled(); expect(mockShakaMessageBus.getCastChannel).toHaveBeenCalledWith( fakeSenderId); let senderChannel = mockShakaMessageBus.getCastChannel(); expect(senderChannel.messages).toEqual([{ type: 'asyncComplete', id: fakeCallId, error: null, }]); }).catch(fail).then(done); })); it('sends "asyncComplete" replies when rejected', checkAndRun((done) => { // No messages have been sent, either broadcast or privately. expect(mockShakaMessageBus.broadcast).not.toHaveBeenCalled(); expect(mockShakaMessageBus.getCastChannel).not.toHaveBeenCalled(); let fakeError = new shaka.util.Error( shaka.util.Error.Severity.CRITICAL, shaka.util.Error.Category.MANIFEST, shaka.util.Error.Code.UNABLE_TO_GUESS_MANIFEST_TYPE); p.reject(fakeError); shaka.test.Util.delay(0.1).then(function() { // No broadcast messages have been sent, but a private message has // been sent to the sender who started the async call. expect(mockShakaMessageBus.broadcast).not.toHaveBeenCalled(); expect(mockShakaMessageBus.getCastChannel).toHaveBeenCalledWith( fakeSenderId); let senderChannel = mockShakaMessageBus.getCastChannel(); expect(senderChannel.messages).toEqual([{ type: 'asyncComplete', id: fakeCallId, error: jasmine.any(Object), }]); if (senderChannel.messages.length) { let error = senderChannel.messages[0].error; shaka.test.Util.expectToEqualError(fakeError, error); } }).catch(fail).then(done); })); }); describe('sends duration', function() { beforeEach(checkAndRun((done) => { receiver = new CastReceiver( mockVideo, mockPlayer, Util.spyFunc(mockAppDataCallback)); fakeConnectedSenders(1); mockPlayer.load.and.callFake(() => { mockVideo.duration = 1; mockPlayer.getAssetUri = function() { return 'URI A'; }; return Promise.resolve(); }); fakeIncomingMessage({ type: 'init', initState: {manifest: 'URI A'}, appData: {}, }, mockShakaMessageBus); // The messages will show up asychronously: Util.delay(0.1).then(function() { expectMediaInfo('URI A', 1); mockGenericMessageBus.messages = []; }).then(done); })); it('only once, if nothing else changes', checkAndRun((done) => { Util.delay(0.5).then(function() { expect(mockGenericMessageBus.messages.length).toBe(0); }).then(done); })); it('after new sender connects', checkAndRun((done) => { fakeConnectedSenders(1); Util.delay(0.5).then(function() { expectMediaInfo('URI A', 1); expect(mockGenericMessageBus.messages.length).toBe(0); }).then(done); })); it('for correct manifest after loading new', checkAndRun((done) => { // Change media information, but only after half a second. mockPlayer.load.and.callFake(() => { return Util.delay(0.5).then(function() { mockVideo.duration = 2; mockPlayer.getAssetUri = function() { return 'URI B'; }; }); }); fakeIncomingMessage({ type: 'asyncCall', id: '5', targetName: 'player', methodName: 'load', args: ['URI B'], }, mockShakaMessageBus, 'senderId'); // Wait for the mockPlayer to finish 'loading' before checking again. Util.delay(1.0).then(function() { expectMediaInfo('URI B', 2); // pollAttributes_ expect(mockGenericMessageBus.messages.length).toBe(0); }).then(done); })); it('after LOAD system message', checkAndRun((done) => { mockPlayer.load.and.callFake(() => { mockVideo.duration = 2; mockPlayer.getAssetUri = function() { return 'URI B'; }; return Promise.resolve(); }); let message = { // Arbitrary number 'requestId': 0, 'type': 'LOAD', 'autoplay': false, 'currentTime': 10, 'media': { 'contentId': 'URI B', 'contentType': 'video/mp4', 'streamType': 'BUFFERED', }, }; fakeIncomingMessage(message, mockGenericMessageBus); Util.delay(0.5).then(function() { expectMediaInfo('URI B', 2); expect(mockGenericMessageBus.messages.length).toBe(0); }).then(done); })); }); describe('respects generic control messages', function() { beforeEach(async () => { receiver = new CastReceiver( mockVideo, mockPlayer, Util.spyFunc(mockAppDataCallback)); fakeConnectedSenders(1); mockPlayer.load.and.callFake(() => { mockVideo.duration = 1; mockPlayer.getAssetUri = () => 'URI A'; return Promise.resolve(); }); fakeIncomingMessage({ type: 'init', initState: {manifest: 'URI A'}, appData: {}, }, mockShakaMessageBus); // The messages will show up asychronously: await Util.delay(0.1); expectMediaInfo('URI A', 1); mockGenericMessageBus.messages = []; mockGenericMessageBus.broadcast.calls.reset(); }); it('get status', checkAndRun(() => { let message = { // Arbitrary number 'requestId': 0, 'type': 'GET_STATUS', }; fakeIncomingMessage(message, mockGenericMessageBus); expect(mockGenericMessageBus.broadcast.calls.count()).toEqual(1); // This covers the lack of scrubber in the Google Home app, as described // in https://github.com/google/shaka-player/issues/2606 expectMediaInfo('URI A', 1); })); it('play', checkAndRun(() => { let message = { // Arbitrary number 'requestId': 0, 'type': 'PLAY', }; fakeIncomingMessage(message, mockGenericMessageBus); expect(mockVideo.play).toHaveBeenCalled(); })); it('pause', checkAndRun(() => { let message = { // Arbitrary number 'requestId': 0, 'type': 'PAUSE', }; fakeIncomingMessage(message, mockGenericMessageBus); expect(mockVideo.pause).toHaveBeenCalled(); })); it('seek', checkAndRun(() => { let message = { // Arbitrary number 'requestId': 0, 'type': 'SEEK', 'resumeState': 'PLAYBACK_START', 'currentTime': 10, }; fakeIncomingMessage(message, mockGenericMessageBus); expect(mockVideo.play).toHaveBeenCalled(); expect(mockVideo.currentTime).toBe(10); })); it('stop', checkAndRun(() => { let message = { // Arbitrary number 'requestId': 0, 'type': 'STOP', }; fakeIncomingMessage(message, mockGenericMessageBus); expect(mockPlayer.unload).toHaveBeenCalled(); })); it('volume', checkAndRun(() => { let message = { // Arbitrary number 'requestId': 0, 'type': 'VOLUME', 'volume': { 'level': 0.5, 'muted': true, }, }; fakeIncomingMessage(message, mockGenericMessageBus); expect(mockVideo.volume).toBe(0.5); expect(mockVideo.muted).toBe(true); })); it('load', checkAndRun(() => { let message = { // Arbitrary number 'requestId': 0, 'type': 'LOAD', 'autoplay': false, 'currentTime': 10, 'media': { 'contentId': 'manifestUri', 'contentType': 'video/mp4', 'streamType': 'BUFFERED', }, }; fakeIncomingMessage(message, mockGenericMessageBus); expect(mockPlayer.load).toHaveBeenCalled(); })); it('dispatches error on unrecognized request type', checkAndRun(() => { let message = { // Arbitrary number 'requestId': 0, 'type': 'UNKNOWN_TYPE', }; fakeIncomingMessage(message, mockGenericMessageBus); expect(mockGenericMessageBus.broadcast.calls.count()).toEqual(1); expect(mockGenericMessageBus.messages).toEqual(jasmine.arrayContaining([ jasmine.objectContaining({ requestId: 0, type: 'INVALID_REQUEST', reason: 'INVALID_COMMAND', }), ])); })); }); describe('destroy', function() { beforeEach(function() { receiver = new CastReceiver( mockVideo, mockPlayer, Util.spyFunc(mockAppDataCallback)); }); it('destroys the local player', checkAndRun((done) => { expect(mockPlayer.destroy).not.toHaveBeenCalled(); receiver.destroy().then(function() { expect(mockPlayer.destroy).toHaveBeenCalled(); }).catch(fail).then(done); })); it('stops polling', checkAndRun((done) => { // Start polling: fakeIncomingMessage({ type: 'init', initState: {}, appData: {}, }, mockShakaMessageBus); mockPlayer.getConfiguration.calls.reset(); shaka.test.Util.delay(1).then(function() { // We have polled at least once, so this getter has been called. expect(mockPlayer.getConfiguration).toHaveBeenCalled(); mockPlayer.getConfiguration.calls.reset(); // Destroy the receiver. return receiver.destroy(); }).then(function() { // Wait another second. return shaka.test.Util.delay(1); }).then(function() { // We have not polled again since destruction. expect(mockPlayer.getConfiguration).not.toHaveBeenCalled(); }).catch(fail).then(done); })); it('stops the receiver manager', checkAndRun((done) => { expect(mockReceiverManager.stop).not.toHaveBeenCalled(); receiver.destroy().then(function() { expect(mockReceiverManager.stop).toHaveBeenCalled(); }).catch(fail).then(done); })); }); function createMockReceiverApi() { return { CastReceiverManager: { getInstance: function() { return mockReceiverManager; }, }, }; } function createMockReceiverManager() { return { start: jasmine.createSpy('CastReceiverManager.start'), stop: jasmine.createSpy('CastReceiverManager.stop'), setSystemVolumeLevel: jasmine.createSpy('CastReceiverManager.setSystemVolumeLevel'), setSystemVolumeMuted: jasmine.createSpy('CastReceiverManager.setSystemVolumeMuted'), getSenders: jasmine.createSpy('CastReceiverManager.getSenders'), getSystemVolume: function() { return {level: 1, muted: false}; }, getCastMessageBus: function(namespace) { if (namespace == shaka.cast.CastUtils.SHAKA_MESSAGE_NAMESPACE) { return mockShakaMessageBus; } return mockGenericMessageBus; }, }; } function createMockMessageBus() { let bus = { messages: [], broadcast: jasmine.createSpy('CastMessageBus.broadcast'), getCastChannel: jasmine.createSpy('CastMessageBus.getCastChannel'), }; // For convenience, deserialize and store sent messages. bus.broadcast.and.callFake(function(message) { bus.messages.push(CastUtils.deserialize(message)); }); let channel = { messages: [], send: function(message) { channel.messages.push(CastUtils.deserialize(message)); }, }; bus.getCastChannel.and.returnValue(channel); return bus; } function createMockPlayer() { let player = { destroy: jasmine.createSpy('destroy').and.returnValue(Promise.resolve()), setMaxHardwareResolution: jasmine.createSpy('setMaxHardwareResolution'), addEventListener: function(eventName, listener) { player.listeners[eventName] = listener; }, removeEventListener: function(eventName, listener) { player.listeners[eventName] = null; }, dispatchEvent: jasmine.createSpy('dispatchEvent'), // For convenience: listeners: {}, }; CastUtils.PlayerVoidMethods.forEach(function(name) { player[name] = jasmine.createSpy(name); }); for (let name in CastUtils.PlayerGetterMethods) { player[name] = jasmine.createSpy(name); } for (let name in CastUtils.PlayerGetterMethodsThatRequireLive) { player[name] = jasmine.createSpy(name); } CastUtils.PlayerPromiseMethods.forEach(function(name) { player[name] = jasmine.createSpy(name).and.returnValue(Promise.resolve()); }); return player; } /** * @param {number} num */ function fakeConnectedSenders(num) { let senderArray = []; while (num--) { senderArray.push('senderId'); } mockReceiverManager.getSenders.and.returnValue(senderArray); mockReceiverManager.onSenderConnected(); } /** * @param {?} message * @param {!Object} bus * @param {string=} senderId */ function fakeIncomingMessage(message, bus, senderId) { let serialized = CastUtils.serialize(message); let messageEvent = { senderId: senderId, data: serialized, }; bus.onMessage(messageEvent); } function expectMediaInfo(expectedUri, expectedDuration) { expect(mockGenericMessageBus.messages.length).toBeGreaterThan(0); if (mockGenericMessageBus.messages.length == 0) { return; } expect(mockGenericMessageBus.messages[0]).toEqual( { requestId: 0, type: 'MEDIA_STATUS', status: [jasmine.objectContaining({ media: { contentId: expectedUri, streamType: 'BUFFERED', duration: expectedDuration, contentType: '', }, })], } ); mockGenericMessageBus.messages.shift(); } });