(function () { function chromecastPlayer() { var self = this; var PlayerName = "Chromecast"; var ApplicationID = "2D4B1DA3"; var currentWebAppSession; var currentDevice; var currentDeviceId; // MediaController needs this self.name = PlayerName; self.getItemsForPlayback = function (query) { var userId = Dashboard.getCurrentUserId(); if (query.Ids && query.Ids.split(',').length == 1) { var deferred = DeferredBuilder.Deferred(); ApiClient.getItem(userId, query.Ids.split(',')).done(function (item) { deferred.resolveWith(null, [ { Items: [item], TotalRecordCount: 1 }]); }); return deferred.promise(); } else { query.Limit = query.Limit || 100; query.ExcludeLocationTypes = "Virtual"; return ApiClient.getItems(userId, query); } }; var castPlayer = {}; $(castPlayer).on("playbackstart", function (e, data) { Logger.log('cc: playbackstart'); var state = self.getPlayerStateInternal(data); $(self).trigger("playbackstart", [state]); }); $(castPlayer).on("playbackstop", function (e, data) { Logger.log('cc: playbackstop'); var state = self.getPlayerStateInternal(data); $(self).trigger("playbackstop", [state]); // Reset this so the next query doesn't make it appear like content is playing. self.lastPlayerData = {}; }); $(castPlayer).on("playbackprogress", function (e, data) { Logger.log('cc: positionchange'); var state = self.getPlayerStateInternal(data); $(self).trigger("positionchange", [state]); }); var endpointInfo; function getEndpointInfo() { if (endpointInfo) { var deferred = $.Deferred(); deferred.resolveWith(null, [endpointInfo]); return deferred.promise(); } return ApiClient.getJSON(ApiClient.getUrl('System/Endpoint')).done(function (info) { endpointInfo = info; }); } function sendMessageToDevice(message) { var bitrateSetting = AppSettings.maxChromecastBitrate(); message = $.extend(message, { userId: Dashboard.getCurrentUserId(), deviceId: ApiClient.deviceId(), accessToken: ApiClient.accessToken(), serverAddress: ApiClient.serverAddress(), maxBitrate: bitrateSetting, receiverName: currentDevice.getFriendlyName(), supportsAc3: AppSettings.enableChromecastAc3() }); getEndpointInfo().done(function (endpoint) { if (endpoint.IsInNetwork) { ApiClient.getPublicSystemInfo().done(function (info) { message.serverAddress = info.LocalAddress; sendMessageInternal(message); }); } else { sendMessageInternal(message); } }); } function sendMessageInternal(message) { currentWebAppSession.sendText(JSON.stringify(message)); } self.play = function (options) { Dashboard.getCurrentUser().done(function (user) { if (options.items) { self.playWithCommand(options, 'PlayNow'); } else { self.getItemsForPlayback({ Ids: options.ids.join(',') }).done(function (result) { options.items = result.Items; self.playWithCommand(options, 'PlayNow'); }); } }); }; self.playWithCommand = function (options, command) { if (!options.items) { ApiClient.getItem(Dashboard.getCurrentUserId(), options.ids[0]).done(function (item) { options.items = [item]; self.playWithCommand(options, command); }); return; } // Convert the items to smaller stubs to send the minimal amount of information options.items = options.items.map(function (i) { return { Id: i.Id, Name: i.Name, Type: i.Type, MediaType: i.MediaType, IsFolder: i.IsFolder }; }); sendMessageToDevice({ options: options, command: command }); }; self.unpause = function () { sendMessageToDevice({ command: 'Unpause' }); }; self.pause = function () { sendMessageToDevice({ command: 'Pause' }); }; self.shuffle = function (id) { var userId = Dashboard.getCurrentUserId(); ApiClient.getItem(userId, id).done(function (item) { self.playWithCommand({ items: [item] }, 'Shuffle'); }); }; self.instantMix = function (id) { var userId = Dashboard.getCurrentUserId(); ApiClient.getItem(userId, id).done(function (item) { self.playWithCommand({ items: [item] }, 'InstantMix'); }); }; self.canQueueMediaType = function (mediaType) { return mediaType == "Audio"; }; self.queue = function (options) { self.playWithCommnd(options, 'PlayLast'); }; self.queueNext = function (options) { self.playWithCommand(options, 'PlayNext'); }; self.stop = function () { sendMessageToDevice({ command: 'Stop' }); }; self.displayContent = function (options) { sendMessageToDevice({ options: options, command: 'DisplayContent' }); }; self.mute = function () { sendMessageToDevice({ command: 'Mute' }); }; self.unMute = function () { self.setVolume(getCurrentVolume() + 2); }; self.toggleMute = function () { var state = self.lastPlayerData || {}; state = state.PlayState || {}; if (state.IsMuted) { self.unMute(); } else { self.mute(); } }; function getBaseTargetInfo() { var target = {}; target.playerName = PlayerName; target.playableMediaTypes = ["Audio", "Video"]; target.isLocalPlayer = false; target.appName = PlayerName; target.supportedCommands = [ "VolumeUp", "VolumeDown", "Mute", "Unmute", "ToggleMute", "SetVolume", "SetAudioStreamIndex", "SetSubtitleStreamIndex", "DisplayContent", "SetRepeatMode", "EndSession" ]; return target; } function convertDeviceToTarget(device) { var target = getBaseTargetInfo(); target.name = target.deviceName = device.getFriendlyName(); target.id = device.getId(); return target; } function isChromecastName(name) { name = (name || '').toLowerCase(); var validTokens = ['nexusplayer']; validTokens.push('chromecast'); validTokens.push('eurekadongle'); return validTokens.filter(function (t) { return name.replace(' ', '').indexOf(t) != -1; }).length > 0; } self.getTargets = function () { return ConnectSDKHelper.getDeviceList().filter(function (d) { return d.hasService('Chromecast') || d.hasService('ChromeCast') || isChromecastName(d.getModelName()) || isChromecastName(d.getFriendlyName()); }).map(convertDeviceToTarget); }; self.seek = function (position) { position = parseInt(position); position = position / 10000000; sendMessageToDevice({ options: { position: position }, command: 'Seek' }); }; self.setAudioStreamIndex = function (index) { sendMessageToDevice({ options: { index: index }, command: 'SetAudioStreamIndex' }); }; self.setSubtitleStreamIndex = function (index) { sendMessageToDevice({ options: { index: index }, command: 'SetSubtitleStreamIndex' }); }; self.nextTrack = function () { sendMessageToDevice({ options: {}, command: 'NextTrack' }); }; self.previousTrack = function () { sendMessageToDevice({ options: {}, command: 'PreviousTrack' }); }; self.beginPlayerUpdates = function () { // Setup polling here }; self.endPlayerUpdates = function () { // Stop polling here }; function getCurrentVolume() { var state = self.lastPlayerData || {}; state = state.PlayState || {}; return state.VolumeLevel == null ? 100 : state.VolumeLevel; } self.volumeDown = function () { sendMessageToDevice({ options: {}, command: 'VolumeDown' }); }; self.setRepeatMode = function (mode) { sendMessageToDevice({ options: { RepeatMode: mode }, command: 'SetRepeatMode' }); }; self.volumeUp = function () { sendMessageToDevice({ options: {}, command: 'VolumeUp' }); }; self.setVolume = function (vol) { vol = Math.min(vol, 100); vol = Math.max(vol, 0); sendMessageToDevice({ options: { volume: vol }, command: 'SetVolume' }); }; self.getPlayerState = function () { var deferred = $.Deferred(); var result = self.getPlayerStateInternal(); deferred.resolveWith(null, [result]); return deferred.promise(); }; self.lastPlayerData = {}; self.getPlayerStateInternal = function (data) { data = data || self.lastPlayerData; self.lastPlayerData = data; Logger.log(JSON.stringify(data)); return data; }; function onMessage(message) { if (message.type == 'playbackerror') { var errorCode = message.data; setTimeout(function () { Dashboard.alert({ message: Globalize.translate('MessagePlaybackError' + errorCode), title: Globalize.translate('HeaderPlaybackError') }); }, 300); } else if (message.type == 'connectionerror') { setTimeout(function () { Dashboard.alert({ message: Globalize.translate('MessageChromecastConnectionError'), title: Globalize.translate('HeaderError') }); }, 300); } else if (message.type && message.type.indexOf('playback') == 0) { $(castPlayer).trigger(message.type, [message.data]); } } function handleMessage(message) { // message could be either a string or an object if (typeof message === 'string') { onMessage(JSON.parse(message)); } else { onMessage(message); } } function handleSessionDisconnect() { Logger.log("session disconnected"); // We can't trust this because we might receive events of other devices disconnecting //cleanupSession(); //MediaController.removeActivePlayer(PlayerName); } function onWebAppSessionConnect(webAppSession, device) { currentWebAppSession = webAppSession; Logger.log('session.connect succeeded'); webAppSession.setWebAppSessionListener(); currentDevice = device; currentDeviceId = device.getId(); self.lastPlayerData = {}; MediaController.setActivePlayer(PlayerName, convertDeviceToTarget(device)); sendIdentifyMessage(); } function setupWebAppSession(device, session, connectToSession) { // hold on to a reference var currentSession = session.acquire(); currentSession.on('message', handleMessage); currentSession.on('disconnect', handleSessionDisconnect); if (connectToSession || $.browser.safari) { currentSession.connect().success(function () { onWebAppSessionConnect(currentSession, device); }).error(handleSessionError); } else { onWebAppSessionConnect(currentSession, device); } } function sendIdentifyMessage() { sendMessageToDevice({ options: {}, command: 'Identify' }); } function handleSessionError() { Logger.log('chromecast session connect error'); cleanupSession(); } function cleanupSession() { var session = currentWebAppSession; if (session) { // Clean up listeners session.off("message"); session.off("disconnect"); // Release session to free up memory session.disconnect(); session.release(); } self.lastPlayerData = {}; currentWebAppSession = null; } function tryLaunchWebSession(device) { Logger.log('calling launchWebApp'); device.getWebAppLauncher().launchWebApp(ApplicationID).success(function (session) { Logger.log('launchWebApp success. calling onSessionConnected'); if ($.browser.android) { //tryJoinWebSession(device, true, false); setupWebAppSession(device, session, true); } else { setupWebAppSession(device, session, true); } }).error(function (err1) { Logger.log('launchWebApp error:' + JSON.stringify(err1)); }); } function tryJoinWebSession(device, enableRetry, enableLaunch) { // First try to join existing session. If it fails, launch a new one Logger.log('calling joinWebApp'); device.getWebAppLauncher().joinWebApp(ApplicationID).success(function (session) { Logger.log('joinWebApp success. calling onSessionConnected'); setupWebAppSession(device, session, false); }).error(function (err) { Logger.log('joinWebApp error: ' + JSON.stringify(err)); if (enableRetry) { tryJoinWebSession(device, false, true); return; } if (enableLaunch) { Logger.log('calling launchWebApp'); tryLaunchWebSession(device); } }); } function launchWebApp(device) { if (currentWebAppSession) { cleanupSession(); } tryJoinWebSession(device, true, true); } function onDeviceReady(device) { device.off("ready"); Logger.log('creating webAppSession'); self.lastPlayerData = {}; launchWebApp(device); } self.tryPair = function (target) { var deferred = $.Deferred(); var device = ConnectSDKHelper.getDeviceList().filter(function (d) { return d.getId() == target.id; })[0]; if (device) { self.tryPairWithDevice(device, deferred); } else { deferred.reject(); } return deferred.promise(); }; self.tryPairWithDevice = function (device, deferred) { Logger.log('Will attempt to connect to Chromecast'); device.on("disconnect", function () { device.off("ready"); device.off("disconnect"); }); if (device.isReady()) { Logger.log('Device is already ready, calling onDeviceReady'); onDeviceReady(device); } else { Logger.log('Binding device ready handler'); device.on("ready", function () { Logger.log('device.ready fired'); onDeviceReady(device); }); Logger.log('Calling device.connect'); device.connect(); } }; self.endSession = function () { if (currentDevice) { currentDevice.disconnect(); } cleanupSession(); currentDevice = null; currentDeviceId = null; }; $(MediaController).on('playerchange', function (e, newPlayer, newTarget) { if (newTarget.id != currentDeviceId) { if (currentWebAppSession) { Logger.log('Disconnecting from chromecast'); Logger.log('New target info: ' + JSON.stringify(newTarget)); Logger.log('currentDeviceId: ' + currentDeviceId); //currentDevice.disconnect(); cleanupSession(); currentDevice = null; currentDeviceId = null; } } }); function onResume() { var deviceId = currentDeviceId; if (deviceId) { self.tryPair({ id: deviceId }); } } document.addEventListener("resume", onResume, false); } MediaController.registerPlayer(new chromecastPlayer()); })();