shaka-player
Version:
DASH/EME video player library
739 lines (631 loc) • 27.4 kB
JavaScript
/**
* @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('CastProxy', function() {
const CastProxy = shaka.cast.CastProxy;
const FakeEvent = shaka.util.FakeEvent;
const Util = shaka.test.Util;
const originalCastSender = shaka.cast.CastSender;
const fakeAppId = 'fake app ID';
let mockPlayer;
let mockSender;
/** @type {!shaka.test.FakeVideo} */
let mockVideo;
/** @type {!jasmine.Spy} */
let mockCastSenderConstructor;
/** @type {shaka.cast.CastProxy} */
let proxy;
beforeEach(() => {
mockCastSenderConstructor = jasmine.createSpy('CastSender constructor');
mockCastSenderConstructor.and.callFake(createMockCastSender);
shaka.cast.CastSender = Util.spyFunc(mockCastSenderConstructor);
mockVideo = new shaka.test.FakeVideo();
mockPlayer = createMockPlayer();
mockSender = null;
proxy = new CastProxy(mockVideo, mockPlayer, fakeAppId);
});
afterEach(async () => {
try {
await proxy.destroy();
} finally {
shaka.cast.CastSender = originalCastSender;
}
});
describe('constructor', function() {
it('creates and initializes a CastSender', function() {
expect(mockCastSenderConstructor).toHaveBeenCalled();
expect(mockSender).toBeTruthy();
expect(mockSender.init).toHaveBeenCalled();
});
it('listens for video and player events', function() {
expect(Object.keys(mockVideo.on).length).toBeGreaterThan(0);
expect(Object.keys(mockPlayer.listeners).length).toBeGreaterThan(0);
});
it('creates proxies for video and player', function() {
expect(proxy.getVideo()).toBeTruthy();
expect(proxy.getVideo()).not.toBe(mockVideo);
expect(proxy.getPlayer()).toBeTruthy();
expect(proxy.getPlayer()).not.toBe(mockPlayer);
});
});
describe('canCast', function() {
it('is true if the API is ready and we have receivers', function() {
mockSender.apiReady.and.returnValue(false);
mockSender.hasReceivers.and.returnValue(false);
expect(proxy.canCast()).toBe(false);
mockSender.apiReady.and.returnValue(true);
expect(proxy.canCast()).toBe(false);
mockSender.hasReceivers.and.returnValue(true);
expect(proxy.canCast()).toBe(true);
});
});
describe('isCasting', function() {
it('delegates directly to the sender', function() {
mockSender.isCasting.and.returnValue(false);
expect(proxy.isCasting()).toBe(false);
mockSender.isCasting.and.returnValue(true);
expect(proxy.isCasting()).toBe(true);
});
});
describe('receiverName', function() {
it('delegates directly to the sender', function() {
mockSender.receiverName.and.returnValue('abc');
expect(proxy.receiverName()).toBe('abc');
mockSender.receiverName.and.returnValue('xyz');
expect(proxy.receiverName()).toBe('xyz');
});
});
describe('setAppData', function() {
it('delegates directly to the sender', function() {
let fakeAppData = {key: 'value'};
expect(mockSender.setAppData).not.toHaveBeenCalled();
proxy.setAppData(fakeAppData);
expect(mockSender.setAppData).toHaveBeenCalledWith(fakeAppData);
});
});
describe('disconnect', function() {
it('delegates directly to the sender', function() {
expect(mockSender.showDisconnectDialog).not.toHaveBeenCalled();
proxy.suggestDisconnect();
expect(mockSender.showDisconnectDialog).toHaveBeenCalled();
});
});
describe('cast', function() {
it('pauses the local video', function() {
proxy.cast();
expect(mockVideo.pause).toHaveBeenCalled();
});
it('passes initial state to sender', function() {
mockVideo.loop = true;
mockVideo.playbackRate = 3;
mockVideo.currentTime = 12;
let fakeConfig = {key: 'value'};
mockPlayer.getConfiguration.and.returnValue(fakeConfig);
mockPlayer.isTextTrackVisible.and.returnValue(false);
let fakeManifestUri = 'foo://bar';
mockPlayer.getAssetUri.and.returnValue(fakeManifestUri);
proxy.cast();
let calls = mockSender.cast.calls;
expect(calls.count()).toBe(1);
if (calls.count()) {
let state = calls.argsFor(0)[0];
// Video state goes directly:
expect(state.video.loop).toEqual(mockVideo.loop);
expect(state.video.playbackRate).toEqual(mockVideo.playbackRate);
// Player state uses corresponding setter names:
expect(state.player.configure).toEqual(fakeConfig);
expect(state['playerAfterLoad'].setTextTrackVisibility).toBe(false);
// Manifest URI:
expect(state.manifest).toEqual(fakeManifestUri);
// Start time:
expect(state.startTime).toEqual(mockVideo.currentTime);
}
});
it('does not provide a start time if the video has ended', function() {
mockVideo.ended = true;
mockVideo.currentTime = 12;
proxy.cast();
let calls = mockSender.cast.calls;
expect(calls.count()).toBe(1);
if (calls.count()) {
let state = calls.argsFor(0)[0];
expect(state.startTime).toBe(null);
}
});
it('unloads the local player after casting is complete', function(done) {
let p = new shaka.util.PublicPromise();
mockSender.cast.and.returnValue(p);
proxy.cast();
shaka.test.Util.delay(0.1).then(function() {
// unload() has not been called yet.
expect(mockPlayer.unload).not.toHaveBeenCalled();
// Resolve the cast() promise.
p.resolve();
return shaka.test.Util.delay(0.1);
}).then(function() {
// unload() has now been called.
expect(mockPlayer.unload).toHaveBeenCalled();
}).catch(fail).then(done);
});
});
describe('video proxy', function() {
describe('get', function() {
it('returns local values when we are playing back locally', function() {
mockVideo.currentTime = 12;
mockVideo.paused = true;
expect(proxy.getVideo().currentTime).toEqual(mockVideo.currentTime);
expect(proxy.getVideo().paused).toEqual(mockVideo.paused);
expect(mockVideo.play).not.toHaveBeenCalled();
proxy.getVideo().play();
expect(mockVideo.play).toHaveBeenCalled();
// The local method call was properly bound:
expect(mockVideo.play.calls.mostRecent().object).toBe(mockVideo);
});
it('returns cached remote values when we are casting', function() {
// Local values that will be ignored:
mockVideo.currentTime = 12;
mockVideo.paused = true;
// Set up the sender in casting mode:
mockSender.isCasting.and.returnValue(true);
mockSender.hasRemoteProperties.and.returnValue(true);
// Simulate remote values:
let cache = {video: {
currentTime: 24,
paused: false,
play: jasmine.createSpy('play'),
}};
mockSender.get.and.callFake(function(targetName, property) {
expect(targetName).toEqual('video');
return cache.video[property];
});
expect(proxy.getVideo().currentTime).not.toEqual(mockVideo.currentTime);
expect(proxy.getVideo().currentTime).toEqual(cache.video.currentTime);
expect(proxy.getVideo().paused).not.toEqual(mockVideo.paused);
expect(proxy.getVideo().paused).toEqual(cache.video.paused);
// Call a method:
expect(mockVideo.play).not.toHaveBeenCalled();
proxy.getVideo().play();
// The call was routed to the remote video.
expect(mockVideo.play).not.toHaveBeenCalled();
expect(cache.video.play).toHaveBeenCalled();
});
it('returns local values when we have no remote values yet', function() {
mockVideo.currentTime = 12;
mockVideo.paused = true;
// Set up the sender in casting mode, but without any remote values:
mockSender.isCasting.and.returnValue(true);
mockSender.hasRemoteProperties.and.returnValue(false);
// Simulate remote method:
let playSpy = jasmine.createSpy('play');
mockSender.get.and.callFake(function(targetName, property) {
expect(targetName).toEqual('video');
expect(property).toEqual('play');
return playSpy;
});
// Without remote values, we should still return the local ones.
expect(proxy.getVideo().currentTime).toEqual(mockVideo.currentTime);
expect(proxy.getVideo().paused).toEqual(mockVideo.paused);
// Call a method:
expect(mockVideo.play).not.toHaveBeenCalled();
proxy.getVideo().play();
// The call was still routed to the remote video.
expect(mockVideo.play).not.toHaveBeenCalled();
expect(playSpy).toHaveBeenCalled();
});
});
describe('set', function() {
it('writes local values when we are playing back locally', function() {
mockVideo.currentTime = 12;
expect(proxy.getVideo().currentTime).toEqual(12);
// Writes to the proxy are reflected immediately in both the proxy and
// the local video.
proxy.getVideo().currentTime = 24;
expect(proxy.getVideo().currentTime).toEqual(24);
expect(mockVideo.currentTime).toEqual(24);
});
it('writes values remotely when we are casting', function() {
mockVideo.currentTime = 12;
// Set up the sender in casting mode:
mockSender.isCasting.and.returnValue(true);
mockSender.hasRemoteProperties.and.returnValue(true);
// Set the value of currentTime:
expect(mockSender.set).not.toHaveBeenCalled();
proxy.getVideo().currentTime = 24;
expect(mockSender.set).toHaveBeenCalledWith('video', 'currentTime', 24);
// The local value was unaffected.
expect(mockVideo.currentTime).toEqual(12);
});
});
describe('local events', function() {
it('forward to the proxy when we are playing back locally', function() {
let proxyListener = jasmine.createSpy('listener');
proxy.getVideo().addEventListener(
'timeupdate', Util.spyFunc(proxyListener));
expect(proxyListener).not.toHaveBeenCalled();
let fakeEvent = new FakeEvent('timeupdate', {detail: 8675309});
mockVideo.on['timeupdate'](fakeEvent);
expect(proxyListener).toHaveBeenCalledWith(jasmine.objectContaining({
type: 'timeupdate',
detail: 8675309,
}));
});
it('are ignored when we are casting', function() {
let proxyListener = jasmine.createSpy('listener');
proxy.getVideo().addEventListener(
'timeupdate', Util.spyFunc(proxyListener));
// Set up the sender in casting mode:
mockSender.isCasting.and.returnValue(true);
mockSender.hasRemoteProperties.and.returnValue(true);
expect(proxyListener).not.toHaveBeenCalled();
let fakeEvent = new FakeEvent('timeupdate', {detail: 8675309});
mockVideo.on['timeupdate'](fakeEvent);
expect(proxyListener).not.toHaveBeenCalled();
});
});
describe('remote events', function() {
it('forward to the proxy when we are casting', function() {
let proxyListener = jasmine.createSpy('listener');
proxy.getVideo().addEventListener(
'timeupdate', Util.spyFunc(proxyListener));
// Set up the sender in casting mode:
mockSender.isCasting.and.returnValue(true);
mockSender.hasRemoteProperties.and.returnValue(true);
expect(proxyListener).not.toHaveBeenCalled();
let fakeEvent = new FakeEvent('timeupdate', {detail: 8675309});
mockSender.onRemoteEvent('video', fakeEvent);
expect(proxyListener).toHaveBeenCalledWith(jasmine.objectContaining({
type: 'timeupdate',
detail: 8675309,
}));
});
});
});
describe('player proxy', function() {
describe('get', function() {
it('returns local values when we are playing back locally', function() {
let fakeConfig = {key: 'value'};
mockPlayer.getConfiguration.and.returnValue(fakeConfig);
expect(proxy.getPlayer().getConfiguration()).toEqual(fakeConfig);
expect(mockPlayer.trickPlay).not.toHaveBeenCalled();
proxy.getPlayer().trickPlay(5);
expect(mockPlayer.trickPlay).toHaveBeenCalledWith(5);
// The local method call was properly bound:
expect(mockPlayer.trickPlay.calls.mostRecent().object).toBe(mockPlayer);
});
it('returns cached remote values when we are casting', function() {
// Local values that will be ignored:
let fakeConfig = {key: 'value'};
mockPlayer.getConfiguration.and.returnValue(fakeConfig);
mockPlayer.isTextTrackVisible.and.returnValue(false);
// Set up the sender in casting mode:
mockSender.isCasting.and.returnValue(true);
mockSender.hasRemoteProperties.and.returnValue(true);
// Simulate remote values:
let fakeConfig2 = {key2: 'value2'};
let cache = {player: {
getConfiguration: fakeConfig2,
isTextTrackVisible: true,
trickPlay: jasmine.createSpy('trickPlay'),
}};
mockSender.get.and.callFake(function(targetName, property) {
expect(targetName).toEqual('player');
let value = cache.player[property];
// methods:
if (typeof value == 'function') return value;
// getters:
else return function() { return value; };
});
expect(proxy.getPlayer().getConfiguration()).toEqual(fakeConfig2);
expect(proxy.getPlayer().isTextTrackVisible()).toBe(true);
// Call a method:
expect(mockPlayer.trickPlay).not.toHaveBeenCalled();
proxy.getPlayer().trickPlay(5);
// The call was routed to the remote player.
expect(mockPlayer.trickPlay).not.toHaveBeenCalled();
expect(cache.player.trickPlay).toHaveBeenCalledWith(5);
});
it('returns local values when we have no remote values yet', function() {
let fakeConfig = {key: 'value'};
mockPlayer.getConfiguration.and.returnValue(fakeConfig);
mockPlayer.isTextTrackVisible.and.returnValue(true);
// Set up the sender in casting mode, but without any remote values:
mockSender.isCasting.and.returnValue(true);
mockSender.hasRemoteProperties.and.returnValue(false);
// Simulate remote method:
let trickPlaySpy = jasmine.createSpy('trickPlay');
mockSender.get.and.callFake(function(targetName, property) {
expect(targetName).toEqual('player');
expect(property).toEqual('trickPlay');
return trickPlaySpy;
});
// Without remote values, we should still return the local ones.
expect(proxy.getPlayer().getConfiguration()).toEqual(fakeConfig);
expect(proxy.getPlayer().isTextTrackVisible()).toBe(true);
// Call a method:
expect(mockPlayer.trickPlay).not.toHaveBeenCalled();
proxy.getPlayer().trickPlay(5);
// The call was still routed to the remote player.
expect(mockPlayer.trickPlay).not.toHaveBeenCalled();
expect(trickPlaySpy).toHaveBeenCalledWith(5);
});
it('always returns a local NetworkingEngine', function() {
// Set up the sender in casting mode:
mockSender.isCasting.and.returnValue(true);
mockSender.hasRemoteProperties.and.returnValue(true);
expect(mockPlayer.getNetworkingEngine).not.toHaveBeenCalled();
proxy.getPlayer().getNetworkingEngine();
expect(mockPlayer.getNetworkingEngine).toHaveBeenCalled();
// The local method call was properly bound:
expect(mockPlayer.getNetworkingEngine.calls.mostRecent().object).toBe(
mockPlayer);
});
});
describe('local events', function() {
it('forward to the proxy when we are playing back locally', function() {
let proxyListener = jasmine.createSpy('listener');
proxy.getPlayer().addEventListener(
'buffering', Util.spyFunc(proxyListener));
expect(proxyListener).not.toHaveBeenCalled();
let fakeEvent = new FakeEvent('buffering', {detail: 8675309});
mockPlayer.listeners['buffering'](fakeEvent);
expect(proxyListener).toHaveBeenCalledWith(jasmine.objectContaining({
type: 'buffering',
detail: 8675309,
}));
});
it('are ignored when we are casting', function() {
let proxyListener = jasmine.createSpy('listener');
proxy.getPlayer().addEventListener(
'buffering', Util.spyFunc(proxyListener));
// Set up the sender in casting mode:
mockSender.isCasting.and.returnValue(true);
mockSender.hasRemoteProperties.and.returnValue(true);
expect(proxyListener).not.toHaveBeenCalled();
let fakeEvent = new FakeEvent('buffering', {detail: 8675309});
mockPlayer.listeners['buffering'](fakeEvent);
expect(proxyListener).not.toHaveBeenCalled();
});
});
describe('remote events', function() {
it('forward to the proxy when we are casting', function() {
let proxyListener = jasmine.createSpy('listener');
proxy.getPlayer().addEventListener(
'buffering', Util.spyFunc(proxyListener));
// Set up the sender in casting mode:
mockSender.isCasting.and.returnValue(true);
mockSender.hasRemoteProperties.and.returnValue(true);
expect(proxyListener).not.toHaveBeenCalled();
let fakeEvent = new FakeEvent('buffering', {detail: 8675309});
mockSender.onRemoteEvent('player', fakeEvent);
expect(proxyListener).toHaveBeenCalledWith(jasmine.objectContaining({
type: 'buffering',
detail: 8675309,
}));
});
});
});
describe('"caststatuschanged" event', function() {
it('is triggered by the sender', function() {
let listener = jasmine.createSpy('listener');
proxy.addEventListener('caststatuschanged', Util.spyFunc(listener));
expect(listener).not.toHaveBeenCalled();
mockSender.onCastStatusChanged();
expect(listener).toHaveBeenCalledWith(jasmine.objectContaining({
type: 'caststatuschanged',
}));
});
});
describe('synthetic video events from onFirstCastStateUpdate', function() {
it('sends a pause event if the video is paused', function() {
mockVideo.paused = true;
let proxyListener = jasmine.createSpy('listener');
proxy.getVideo().addEventListener('pause', Util.spyFunc(proxyListener));
expect(proxyListener).not.toHaveBeenCalled();
mockSender.onFirstCastStateUpdate();
expect(proxyListener).toHaveBeenCalled();
});
it('sends a play event if the video is playing', function() {
mockVideo.paused = false;
let proxyListener = jasmine.createSpy('listener');
proxy.getVideo().addEventListener('play', Util.spyFunc(proxyListener));
expect(proxyListener).not.toHaveBeenCalled();
mockSender.onFirstCastStateUpdate();
expect(proxyListener).toHaveBeenCalled();
});
});
describe('resume local playback', function() {
let cache;
beforeEach(function() {
// Simulate cached remote state:
cache = {
video: {
loop: true,
playbackRate: 5,
},
player: {
getConfiguration: {key: 'value'},
isTextTrackVisisble: true,
},
};
mockSender.get.and.callFake(function(targetName, property) {
if (targetName == 'player') {
return function() { return cache[targetName][property]; };
} else {
return cache[targetName][property];
}
});
});
it('transfers remote state back to local objects', function(done) {
// Nothing has been set yet:
expect(mockPlayer.configure).not.toHaveBeenCalled();
expect(mockPlayer.setTextTrackVisibility).not.toHaveBeenCalled();
expect(mockVideo.loop).toBe(false);
expect(mockVideo.playbackRate).toBe(1);
// Resume local playback.
mockSender.onResumeLocal();
// Initial Player state first:
expect(mockPlayer.configure).toHaveBeenCalledWith(
cache.player.getConfiguration);
// 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(
cache.player.isTextTrackVisible);
expect(mockVideo.loop).toEqual(cache.video.loop);
expect(mockVideo.playbackRate).toEqual(cache.video.playbackRate);
}).catch(fail).then(done);
});
it('loads the manifest', function() {
cache.video.currentTime = 12;
cache.player.getAssetUri = 'foo://bar';
expect(mockPlayer.load).not.toHaveBeenCalled();
mockSender.onResumeLocal();
expect(mockPlayer.load).toHaveBeenCalledWith('foo://bar', 12);
});
it('does not provide a start time if the video has ended', function() {
cache.video.currentTime = 12;
cache.video.ended = true;
cache.player.getAssetUri = 'foo://bar';
expect(mockPlayer.load).not.toHaveBeenCalled();
mockSender.onResumeLocal();
expect(mockPlayer.load).toHaveBeenCalledWith('foo://bar', null);
});
it('plays the video after loading', function(done) {
cache.player.getAssetUri = 'foo://bar';
// Should play even if the video was paused remotely.
cache.video.paused = true;
mockVideo.autoplay = true;
mockSender.onResumeLocal();
// 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', function(done) {
cache.player.getAssetUri = null;
mockSender.onResumeLocal();
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(
cache.player.isTextTrackVisible);
expect(mockVideo.loop).toEqual(cache.video.loop);
expect(mockVideo.playbackRate).toEqual(cache.video.playbackRate);
}).catch(fail).then(done);
});
it('triggers an "error" event if load fails', function(done) {
cache.player.getAssetUri = '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));
mockSender.onResumeLocal();
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('destroy', function() {
it('destroys the local player and the sender', function(done) {
expect(mockPlayer.destroy).not.toHaveBeenCalled();
expect(mockSender.destroy).not.toHaveBeenCalled();
expect(mockSender.forceDisconnect).not.toHaveBeenCalled();
proxy.destroy().catch(fail).then(done);
expect(mockPlayer.destroy).toHaveBeenCalled();
expect(mockSender.destroy).toHaveBeenCalled();
expect(mockSender.forceDisconnect).not.toHaveBeenCalled();
});
it('optionally forces the sender to disconnect', function(done) {
expect(mockSender.destroy).not.toHaveBeenCalled();
expect(mockSender.forceDisconnect).not.toHaveBeenCalled();
proxy.destroy(true).catch(fail).then(done);
expect(mockSender.destroy).toHaveBeenCalled();
expect(mockSender.forceDisconnect).toHaveBeenCalled();
});
});
/**
* @param {string} appId
* @param {Function} onCastStatusChanged
* @param {Function} onFirstCastStateUpdate
* @param {Function} onRemoteEvent
* @param {Function} onResumeLocal
* @return {!Object}
*/
function createMockCastSender(
appId, onCastStatusChanged, onFirstCastStateUpdate,
onRemoteEvent, onResumeLocal) {
expect(appId).toEqual(fakeAppId);
mockSender = {
init: jasmine.createSpy('init'),
destroy: jasmine.createSpy('destroy'),
apiReady: jasmine.createSpy('apiReady'),
hasReceivers: jasmine.createSpy('hasReceivers'),
isCasting: jasmine.createSpy('isCasting'),
receiverName: jasmine.createSpy('receiverName'),
hasRemoteProperties: jasmine.createSpy('hasRemoteProperties'),
setAppData: jasmine.createSpy('setAppData'),
forceDisconnect: jasmine.createSpy('forceDisconnect'),
showDisconnectDialog: jasmine.createSpy('showDisconnectDialog'),
cast: jasmine.createSpy('cast'),
get: jasmine.createSpy('get'),
set: jasmine.createSpy('set'),
// For convenience:
onCastStatusChanged: onCastStatusChanged,
onFirstCastStateUpdate: onFirstCastStateUpdate,
onRemoteEvent: onRemoteEvent,
onResumeLocal: onResumeLocal,
};
mockSender.cast.and.returnValue(Promise.resolve());
return mockSender;
}
function createMockPlayer() {
let player = {
load: jasmine.createSpy('load'),
unload: jasmine.createSpy('unload'),
getNetworkingEngine: jasmine.createSpy('getNetworkingEngine'),
getAssetUri: jasmine.createSpy('getAssetUri'),
getConfiguration: jasmine.createSpy('getConfiguration'),
configure: jasmine.createSpy('configure'),
isTextTrackVisible: jasmine.createSpy('isTextTrackVisible'),
setTextTrackVisibility: jasmine.createSpy('setTextTrackVisibility'),
trickPlay: jasmine.createSpy('trickPlay'),
destroy: jasmine.createSpy('destroy'),
addEventListener: function(eventName, listener) {
player.listeners[eventName] = listener;
},
removeEventListener: function(eventName, listener) {
delete player.listeners[eventName];
},
dispatchEvent: jasmine.createSpy('dispatchEvent'),
// For convenience:
listeners: {},
};
player.load.and.returnValue(Promise.resolve());
player.unload.and.returnValue(Promise.resolve());
return player;
}
});