diff --git a/static/js/background.js b/static/js/background.js
index 14a3a06..664eb98 100644
--- a/static/js/background.js
+++ b/static/js/background.js
@@ -1,33 +1,35 @@
function createWindow(state) {
- chrome.app.window.create('index.html', {
- state: state,
- bounds: {
- width: 960,
- height: 540
- }
- }, function(window) {
- // workaround:
- // state = 'normal' in some cases not work (e.g. starting app from 'chrome://extensions' always open window in fullscreen mode)
- // it requires manually restoring window state to 'normal'
- if (state == 'normal') {
- setTimeout(function() { window.restore(); }, 1000);
- }
- });
+ chrome.app.window.create('index.html', {
+ state: state,
+ bounds: {
+ width: 960,
+ height: 540
+ }
+ }, function(window) {
+ // workaround:
+ // state = 'normal' in some cases not work (e.g. starting app from 'chrome://extensions' always open window in fullscreen mode)
+ // it requires manually restoring window state to 'normal'
+ if (state == 'normal') {
+ setTimeout(function() {
+ window.restore();
+ }, 1000);
+ }
+ });
}
chrome.app.runtime.onLaunched.addListener(function() {
- console.log('Chrome app runtime launched.');
- var windowState = 'normal';
+ console.log('Chrome app runtime launched.');
+ var windowState = 'normal';
- if (chrome.storage) {
- // load stored window state
- chrome.storage.sync.get('windowState', function(item) {
- windowState = (item && item.windowState)
- ? item.windowState
- : windowState;
- createWindow(windowState);
- });
- } else {
- createWindow(windowState);
- }
+ if (chrome.storage) {
+ // load stored window state
+ chrome.storage.sync.get('windowState', function(item) {
+ windowState = (item && item.windowState) ?
+ item.windowState :
+ windowState;
+ createWindow(windowState);
+ });
+ } else {
+ createWindow(windowState);
+ }
});
diff --git a/static/js/common.js b/static/js/common.js
index 6b5a5c0..1fd86d0 100644
--- a/static/js/common.js
+++ b/static/js/common.js
@@ -35,7 +35,7 @@ var common = (function() {
} else if (tool == 'pnacl') {
mimetype = 'application/x-pnacl';
}
- console.log('%c[updateStatus, common.js]', 'color: gray;', 'mimetype: ' + mimetype);
+ console.log('%c[updateStatus, common.js]', 'color: gray;', 'mimetype: ' + mimetype);
return mimetype;
}
@@ -72,7 +72,7 @@ var common = (function() {
*/
function createNaClModule(name, tool, path, width, height, attrs) {
console.log('%c[createNaClModule, common.js]', 'color: gray;', "name: " + name + ", tool: " + tool + ", path: " + path + ", width: " + width +
- ", height: " + height + ", attrs: " + JSON.stringify(attrs));
+ ", height: " + height + ", attrs: " + JSON.stringify(attrs));
var moduleEl = document.createElement('embed');
moduleEl.setAttribute('name', 'nacl_module');
moduleEl.setAttribute('id', 'nacl_module');
@@ -115,7 +115,7 @@ var common = (function() {
moduleEl.readyState = 4;
moduleEl.dispatchEvent(new CustomEvent('load'));
moduleEl.dispatchEvent(new CustomEvent('loadend'));
- }, 100); // 100 ms
+ }, 100); // 100 ms
}
}
@@ -158,7 +158,7 @@ var common = (function() {
* This event listener is registered in attachDefaultListeners above.
*/
function handleCrash(event) {
- console.log('%c[handleCrash, common.js]', 'color: red;', event);
+ console.log('%c[handleCrash, common.js]', 'color: red;', event);
if (common.naclModule.exitStatus == -1) {
updateStatus('CRASHED');
} else {
@@ -373,7 +373,7 @@ document.addEventListener('DOMContentLoaded', function() {
for (var key_ix = 0; key_ix < pairs.length; key_ix++) {
var keyValue = pairs[key_ix].split('=');
searchVars[unescape(keyValue[0])] =
- keyValue.length > 1 ? unescape(keyValue[1]) : '';
+ keyValue.length > 1 ? unescape(keyValue[1]) : '';
}
}
@@ -393,7 +393,7 @@ document.addEventListener('DOMContentLoaded', function() {
}
var tc = toolchains.indexOf(searchVars.tc) !== -1 ?
- searchVars.tc : toolchains[0];
+ searchVars.tc : toolchains[0];
// If the config value is included in the search vars, use that.
// Otherwise default to Release if it is valid, or the first value if
@@ -411,7 +411,7 @@ document.addEventListener('DOMContentLoaded', function() {
isRelease = path.toLowerCase().indexOf('release') != -1;
loadFunction(body.dataset.name, tc, path, body.dataset.width,
- body.dataset.height, attrs);
+ body.dataset.height, attrs);
}
}
});
diff --git a/static/js/index.js b/static/js/index.js
index e5ac380..2f80823 100644
--- a/static/js/index.js
+++ b/static/js/index.js
@@ -1,158 +1,160 @@
-var hosts = {}; // hosts is an associative array of NvHTTP objects, keyed by server UID
-var activePolls = {}; // hosts currently being polled. An associated array of polling IDs, keyed by server UID
+var hosts = {}; // hosts is an associative array of NvHTTP objects, keyed by server UID
+var activePolls = {}; // hosts currently being polled. An associated array of polling IDs, keyed by server UID
var pairingCert;
var myUniqueid;
-var api; // `api` should only be set if we're in a host-specific screen. on the initial screen it should always be null.
+var api; // `api` should only be set if we're in a host-specific screen. on the initial screen it should always be null.
var isInGame = false; // flag indicating whether the game stream started
var windowState = 'normal'; // chrome's windowState, possible values: 'normal' or 'fullscreen'
// Called by the common.js module.
function attachListeners() {
- changeUiModeForNaClLoad();
+ changeUiModeForNaClLoad();
- $('.resolutionMenu li').on('click', saveResolution);
- $('.framerateMenu li').on('click', saveFramerate);
- $('#bitrateSlider').on('input', updateBitrateField); // input occurs every notch you slide
- //$('#bitrateSlider').on('change', saveBitrate); //FIXME: it seems not working
- $("#remoteAudioEnabledSwitch").on('click', saveRemoteAudio);
- $('#optimizeGamesSwitch').on('click', saveOptimize);
- $('#addHostCell').on('click', addHost);
- $('#backIcon').on('click', showHostsAndSettingsMode);
- $('#quitCurrentApp').on('click', stopGameWithConfirmation);
- $(window).resize(fullscreenNaclModule);
- chrome.app.window.current().onMaximized.addListener(fullscreenChromeWindow);
+ $('.resolutionMenu li').on('click', saveResolution);
+ $('.framerateMenu li').on('click', saveFramerate);
+ $('#bitrateSlider').on('input', updateBitrateField); // input occurs every notch you slide
+ //$('#bitrateSlider').on('change', saveBitrate); //FIXME: it seems not working
+ $("#remoteAudioEnabledSwitch").on('click', saveRemoteAudio);
+ $('#optimizeGamesSwitch').on('click', saveOptimize);
+ $('#addHostCell').on('click', addHost);
+ $('#backIcon').on('click', showHostsAndSettingsMode);
+ $('#quitCurrentApp').on('click', stopGameWithConfirmation);
+ $(window).resize(fullscreenNaclModule);
+ chrome.app.window.current().onMaximized.addListener(fullscreenChromeWindow);
}
function fullscreenChromeWindow() {
- // when the user clicks the maximize button on the window,
- // FIRST restore it to the previous size, then fullscreen it to the whole screen
- // this prevents the previous window size from being 'maximized',
- // and allows us to functionally retain two window sizes
- // so that when the user hits `esc`, they go back to the "restored" size,
- // instead of "maximized", which would immediately go to fullscreen
- chrome.app.window.current().restore();
- chrome.app.window.current().fullscreen();
+ // when the user clicks the maximize button on the window,
+ // FIRST restore it to the previous size, then fullscreen it to the whole screen
+ // this prevents the previous window size from being 'maximized',
+ // and allows us to functionally retain two window sizes
+ // so that when the user hits `esc`, they go back to the "restored" size,
+ // instead of "maximized", which would immediately go to fullscreen
+ chrome.app.window.current().restore();
+ chrome.app.window.current().fullscreen();
}
function loadWindowState() {
- if (!chrome.storage) { return; }
+ if (!chrome.storage) {
+ return;
+ }
- chrome.storage.sync.get('windowState', function(item) {
- // load stored window state
- windowState = (item && item.windowState)
- ? item.windowState
- : windowState;
+ chrome.storage.sync.get('windowState', function(item) {
+ // load stored window state
+ windowState = (item && item.windowState) ?
+ item.windowState :
+ windowState;
- // subscribe to chrome's windowState events
- chrome.app.window.current().onFullscreened.addListener(onFullscreened);
- chrome.app.window.current().onBoundsChanged.addListener(onBoundsChanged);
- });
+ // subscribe to chrome's windowState events
+ chrome.app.window.current().onFullscreened.addListener(onFullscreened);
+ chrome.app.window.current().onBoundsChanged.addListener(onBoundsChanged);
+ });
}
function onFullscreened() {
- if (!isInGame && windowState == 'normal') {
- storeData('windowState', 'fullscreen', null);
- windowState = 'fullscreen';
- }
+ if (!isInGame && windowState == 'normal') {
+ storeData('windowState', 'fullscreen', null);
+ windowState = 'fullscreen';
+ }
}
function onBoundsChanged() {
- if (!isInGame && windowState == 'fullscreen') {
- storeData('windowState', 'normal', null);
- windowState = 'normal';
- }
+ if (!isInGame && windowState == 'fullscreen') {
+ storeData('windowState', 'normal', null);
+ windowState = 'normal';
+ }
}
function changeUiModeForNaClLoad() {
- $('#main-navigation').children().hide();
- $("#main-content").children().not("#listener, #naclSpinner").hide();
- $('#naclSpinnerMessage').text('Loading Moonlight plugin...');
- $('#naclSpinner').css('display', 'inline-block');
+ $('#main-navigation').children().hide();
+ $("#main-content").children().not("#listener, #naclSpinner").hide();
+ $('#naclSpinnerMessage').text('Loading Moonlight plugin...');
+ $('#naclSpinner').css('display', 'inline-block');
}
function startPollingHosts() {
- for(var hostUID in hosts) {
- beginBackgroundPollingOfHost(hosts[hostUID]);
- }
+ for (var hostUID in hosts) {
+ beginBackgroundPollingOfHost(hosts[hostUID]);
+ }
}
function stopPollingHosts() {
- for(var hostUID in hosts) {
- stopBackgroundPollingOfHost(hosts[hostUID]);
- }
+ for (var hostUID in hosts) {
+ stopBackgroundPollingOfHost(hosts[hostUID]);
+ }
}
function restoreUiAfterNaClLoad() {
- $('#main-navigation').children().not("#quitCurrentApp").show();
- $("#main-content").children().not("#listener, #naclSpinner, #game-grid").show();
- $('#naclSpinner').hide();
- $('#loadingSpinner').css('display', 'none');
- showHostsAndSettingsMode();
+ $('#main-navigation').children().not("#quitCurrentApp").show();
+ $("#main-content").children().not("#listener, #naclSpinner, #game-grid").show();
+ $('#naclSpinner').hide();
+ $('#loadingSpinner').css('display', 'none');
+ showHostsAndSettingsMode();
- findNvService(function (finder, opt_error) {
- if (finder.byService_['_nvstream._tcp']) {
- var ips = Object.keys(finder.byService_['_nvstream._tcp']);
- for (var i in ips) {
- var ip = ips[i];
- if (finder.byService_['_nvstream._tcp'][ip]) {
- var mDnsDiscoveredHost = new NvHTTP(ip, myUniqueid);
- mDnsDiscoveredHost.pollServer(function(returneMdnsDiscoveredHost) {
- // Just drop this if the host doesn't respond
- if (!returneMdnsDiscoveredHost.online) {
- return;
- }
-
- if (hosts[returneMdnsDiscoveredHost.serverUid] != null) {
- // if we're seeing a host we've already seen before, update it for the current local IP.
- hosts[returneMdnsDiscoveredHost.serverUid].address = returneMdnsDiscoveredHost.address;
- } else {
- beginBackgroundPollingOfHost(returneMdnsDiscoveredHost);
- addHostToGrid(returneMdnsDiscoveredHost, true);
- }
- });
- }
+ findNvService(function(finder, opt_error) {
+ if (finder.byService_['_nvstream._tcp']) {
+ var ips = Object.keys(finder.byService_['_nvstream._tcp']);
+ for (var i in ips) {
+ var ip = ips[i];
+ if (finder.byService_['_nvstream._tcp'][ip]) {
+ var mDnsDiscoveredHost = new NvHTTP(ip, myUniqueid);
+ mDnsDiscoveredHost.pollServer(function(returneMdnsDiscoveredHost) {
+ // Just drop this if the host doesn't respond
+ if (!returneMdnsDiscoveredHost.online) {
+ return;
}
+
+ if (hosts[returneMdnsDiscoveredHost.serverUid] != null) {
+ // if we're seeing a host we've already seen before, update it for the current local IP.
+ hosts[returneMdnsDiscoveredHost.serverUid].address = returneMdnsDiscoveredHost.address;
+ } else {
+ beginBackgroundPollingOfHost(returneMdnsDiscoveredHost);
+ addHostToGrid(returneMdnsDiscoveredHost, true);
+ }
+ });
}
- });
+ }
+ }
+ });
}
function beginBackgroundPollingOfHost(host) {
- if (host.online) {
+ if (host.online) {
+ $("#hostgrid-" + host.serverUid).removeClass('host-cell-inactive');
+ // The host was already online. Just start polling in the background now.
+ activePolls[host.serverUid] = window.setInterval(function() {
+ // every 5 seconds, poll at the address we know it was live at
+ host.pollServer(function() {
+ if (host.online) {
+ $("#hostgrid-" + host.serverUid).removeClass('host-cell-inactive');
+ } else {
+ $("#hostgrid-" + host.serverUid).addClass('host-cell-inactive');
+ }
+ });
+ }, 5000);
+ } else {
+ $("#hostgrid-" + host.serverUid).addClass('host-cell-inactive');
+ // The host was offline, so poll immediately.
+ host.pollServer(function() {
+ if (host.online) {
$("#hostgrid-" + host.serverUid).removeClass('host-cell-inactive');
- // The host was already online. Just start polling in the background now.
- activePolls[host.serverUid] = window.setInterval(function() {
- // every 5 seconds, poll at the address we know it was live at
- host.pollServer(function () {
- if (host.online) {
- $("#hostgrid-" + host.serverUid).removeClass('host-cell-inactive');
- } else {
- $("#hostgrid-" + host.serverUid).addClass('host-cell-inactive');
- }
- });
- }, 5000);
- } else {
+ } else {
$("#hostgrid-" + host.serverUid).addClass('host-cell-inactive');
- // The host was offline, so poll immediately.
- host.pollServer(function () {
- if (host.online) {
- $("#hostgrid-" + host.serverUid).removeClass('host-cell-inactive');
- } else {
- $("#hostgrid-" + host.serverUid).addClass('host-cell-inactive');
- }
+ }
- // Now start background polling
- activePolls[host.serverUid] = window.setInterval(function() {
- // every 5 seconds, poll at the address we know it was live at
- host.pollServer(function () {
- if (host.online) {
- $("#hostgrid-" + host.serverUid).removeClass('host-cell-inactive');
- } else {
- $("#hostgrid-" + host.serverUid).addClass('host-cell-inactive');
- }
- });
- }, 5000);
+ // Now start background polling
+ activePolls[host.serverUid] = window.setInterval(function() {
+ // every 5 seconds, poll at the address we know it was live at
+ host.pollServer(function() {
+ if (host.online) {
+ $("#hostgrid-" + host.serverUid).removeClass('host-cell-inactive');
+ } else {
+ $("#hostgrid-" + host.serverUid).addClass('host-cell-inactive');
+ }
});
- }
+ }, 5000);
+ });
+ }
}
function stopBackgroundPollingOfHost(host) {
@@ -162,254 +164,270 @@ function stopBackgroundPollingOfHost(host) {
}
function snackbarLog(givenMessage) {
- console.log('%c[index.js, snackbarLog]', 'color: green;', givenMessage);
- var data = {
- message: givenMessage,
- timeout: 2000
- };
- document.querySelector('#snackbar').MaterialSnackbar.showSnackbar(data);
+ console.log('%c[index.js, snackbarLog]', 'color: green;', givenMessage);
+ var data = {
+ message: givenMessage,
+ timeout: 2000
+ };
+ document.querySelector('#snackbar').MaterialSnackbar.showSnackbar(data);
}
function snackbarLogLong(givenMessage) {
- console.log('%c[index.js, snackbarLog]', 'color: green;', givenMessage);
- var data = {
- message: givenMessage,
- timeout: 5000
- };
- document.querySelector('#snackbar').MaterialSnackbar.showSnackbar(data);
+ console.log('%c[index.js, snackbarLog]', 'color: green;', givenMessage);
+ var data = {
+ message: givenMessage,
+ timeout: 5000
+ };
+ document.querySelector('#snackbar').MaterialSnackbar.showSnackbar(data);
}
function updateBitrateField() {
- $('#bitrateField').html($('#bitrateSlider').val() + " Mbps");
- saveBitrate();
+ $('#bitrateField').html($('#bitrateSlider').val() + " Mbps");
+ saveBitrate();
}
function moduleDidLoad() {
- // load the HTTP cert and unique ID if we have one.
- chrome.storage.sync.get('cert', function(savedCert) {
- if (savedCert.cert != null) { // we have a saved cert
- pairingCert = savedCert.cert;
- }
+ // load the HTTP cert and unique ID if we have one.
+ chrome.storage.sync.get('cert', function(savedCert) {
+ if (savedCert.cert != null) { // we have a saved cert
+ pairingCert = savedCert.cert;
+ }
- chrome.storage.sync.get('uniqueid', function(savedUniqueid) {
- if (savedUniqueid.uniqueid != null) { // we have a saved uniqueid
- myUniqueid = savedUniqueid.uniqueid;
- } else {
- myUniqueid = uniqueid();
- storeData('uniqueid', myUniqueid, null);
- }
+ chrome.storage.sync.get('uniqueid', function(savedUniqueid) {
+ if (savedUniqueid.uniqueid != null) { // we have a saved uniqueid
+ myUniqueid = savedUniqueid.uniqueid;
+ } else {
+ myUniqueid = uniqueid();
+ storeData('uniqueid', myUniqueid, null);
+ }
- if (!pairingCert) { // we couldn't load a cert. Make one.
- console.warn('%c[index.js, moduleDidLoad]', 'color: green;', 'Failed to load local cert. Generating new one');
- sendMessage('makeCert', []).then(function (cert) {
- storeData('cert', cert, null);
- pairingCert = cert;
- console.info('%c[index.js, moduleDidLoad]', 'color: green;', 'Generated new cert:', cert);
- }, function (failedCert) {
- console.error('%c[index.js, moduleDidLoad]', 'color: green;', 'Failed to generate new cert! Returned error was: \n', failedCert);
- }).then(function (ret) {
- sendMessage('httpInit', [pairingCert.cert, pairingCert.privateKey, myUniqueid]).then(function (ret) {
- restoreUiAfterNaClLoad();
- }, function (failedInit) {
- console.error('%c[index.js, moduleDidLoad]', 'color: green;', 'Failed httpInit! Returned error was: ', failedInit);
- });
- });
- }
- else {
- sendMessage('httpInit', [pairingCert.cert, pairingCert.privateKey, myUniqueid]).then(function (ret) {
- restoreUiAfterNaClLoad();
- }, function (failedInit) {
- console.error('%c[index.js, moduleDidLoad]', 'color: green;', 'Failed httpInit! Returned error was: ', failedInit);
- });
- }
-
- // load previously connected hosts, which have been killed into an object, and revive them back into a class
- chrome.storage.sync.get('hosts', function(previousValue) {
- hosts = previousValue.hosts != null ? previousValue.hosts : {};
- for(var hostUID in hosts) { // programmatically add each new host.
- var revivedHost = new NvHTTP(hosts[hostUID].address, myUniqueid, hosts[hostUID].userEnteredAddress);
- revivedHost.serverUid = hosts[hostUID].serverUid;
- revivedHost.externalIP = hosts[hostUID].externalIP;
- revivedHost.hostname = hosts[hostUID].hostname;
- addHostToGrid(revivedHost);
- }
- console.log('%c[index.js]', 'color: green;', 'Loaded previously connected hosts');
- });
+ if (!pairingCert) { // we couldn't load a cert. Make one.
+ console.warn('%c[index.js, moduleDidLoad]', 'color: green;', 'Failed to load local cert. Generating new one');
+ sendMessage('makeCert', []).then(function(cert) {
+ storeData('cert', cert, null);
+ pairingCert = cert;
+ console.info('%c[index.js, moduleDidLoad]', 'color: green;', 'Generated new cert:', cert);
+ }, function(failedCert) {
+ console.error('%c[index.js, moduleDidLoad]', 'color: green;', 'Failed to generate new cert! Returned error was: \n', failedCert);
+ }).then(function(ret) {
+ sendMessage('httpInit', [pairingCert.cert, pairingCert.privateKey, myUniqueid]).then(function(ret) {
+ restoreUiAfterNaClLoad();
+ }, function(failedInit) {
+ console.error('%c[index.js, moduleDidLoad]', 'color: green;', 'Failed httpInit! Returned error was: ', failedInit);
+ });
});
+ } else {
+ sendMessage('httpInit', [pairingCert.cert, pairingCert.privateKey, myUniqueid]).then(function(ret) {
+ restoreUiAfterNaClLoad();
+ }, function(failedInit) {
+ console.error('%c[index.js, moduleDidLoad]', 'color: green;', 'Failed httpInit! Returned error was: ', failedInit);
+ });
+ }
+
+ // load previously connected hosts, which have been killed into an object, and revive them back into a class
+ chrome.storage.sync.get('hosts', function(previousValue) {
+ hosts = previousValue.hosts != null ? previousValue.hosts : {};
+ for (var hostUID in hosts) { // programmatically add each new host.
+ var revivedHost = new NvHTTP(hosts[hostUID].address, myUniqueid, hosts[hostUID].userEnteredAddress);
+ revivedHost.serverUid = hosts[hostUID].serverUid;
+ revivedHost.externalIP = hosts[hostUID].externalIP;
+ revivedHost.hostname = hosts[hostUID].hostname;
+ addHostToGrid(revivedHost);
+ }
+ console.log('%c[index.js]', 'color: green;', 'Loaded previously connected hosts');
+ });
});
+ });
}
// pair to the given NvHTTP host object. Returns whether pairing was successful.
function pairTo(nvhttpHost, onSuccess, onFailure) {
- if(!pairingCert) {
- snackbarLog('ERROR: cert has not been generated yet. Is NaCl initialized?');
- console.warn('%c[index.js]', 'color: green;', 'User wants to pair, and we still have no cert. Problem = very yes.');
- onFailure();
- return;
+ if (!pairingCert) {
+ snackbarLog('ERROR: cert has not been generated yet. Is NaCl initialized?');
+ console.warn('%c[index.js]', 'color: green;', 'User wants to pair, and we still have no cert. Problem = very yes.');
+ onFailure();
+ return;
+ }
+
+ nvhttpHost.pollServer(function(ret) {
+ if (!nvhttpHost.online) {
+ snackbarLog('Failed to connect to ' + nvhttpHost.hostname + '! Are you sure the host is on?');
+ console.error('%c[index.js]', 'color: green;', 'Host declared as offline:', nvhttpHost, nvhttpHost.toString()); //Logging both the object and the toString version for text logs
+ onFailure();
+ return;
}
- nvhttpHost.pollServer(function (ret) {
- if (!nvhttpHost.online) {
- snackbarLog('Failed to connect to ' + nvhttpHost.hostname + '! Are you sure the host is on?');
- console.error('%c[index.js]', 'color: green;', 'Host declared as offline:', nvhttpHost, nvhttpHost.toString()); //Logging both the object and the toString version for text logs
- onFailure();
- return;
- }
+ if (nvhttpHost.paired) {
+ onSuccess();
+ return;
+ }
- if (nvhttpHost.paired) {
- onSuccess();
- return;
- }
+ var randomNumber = String("0000" + (Math.random() * 10000 | 0)).slice(-4);
+ var pairingDialog = document.querySelector('#pairingDialog');
+ $('#pairingDialogText').html('Please enter the number ' + randomNumber + ' on the GFE dialog on the computer. This dialog will be dismissed once complete');
+ pairingDialog.showModal();
- var randomNumber = String("0000" + (Math.random()*10000|0)).slice(-4);
- var pairingDialog = document.querySelector('#pairingDialog');
- $('#pairingDialogText').html('Please enter the number ' + randomNumber + ' on the GFE dialog on the computer. This dialog will be dismissed once complete');
- pairingDialog.showModal();
-
- $('#cancelPairingDialog').off('click');
- $('#cancelPairingDialog').on('click', function () {
- pairingDialog.close();
- });
-
- console.log('%c[index.js]', 'color: green;', 'Sending pairing request to ' + nvhttpHost.hostname + ' with random number' + randomNumber);
- nvhttpHost.pair(randomNumber).then(function (paired) {
- if (!paired) {
- if (nvhttpHost.currentGame != 0) {
- $('#pairingDialogText').html('Error: ' + nvhttpHost.hostname + ' is busy. Stop streaming to pair.');
- } else {
- $('#pairingDialogText').html('Error: failed to pair with ' + nvhttpHost.hostname + '.');
- }
- console.log('%c[index.js]', 'color: green;', 'Failed API object:', nvhttpHost, nvhttpHost.toString()); //Logging both the object and the toString version for text logs
- onFailure();
- return;
- }
-
- snackbarLog('Pairing successful');
- pairingDialog.close();
- onSuccess();
- }, function (failedPairing) {
- snackbarLog('Failed pairing to: ' + nvhttpHost.hostname);
- console.error('%c[index.js]', 'color: green;', 'Pairing failed, and returned:', failedPairing);
- console.error('%c[index.js]', 'color: green;', 'Failed API object:', nvhttpHost, nvhttpHost.toString()); //Logging both the object and the toString version for text logs
- onFailure();
- });
+ $('#cancelPairingDialog').off('click');
+ $('#cancelPairingDialog').on('click', function() {
+ pairingDialog.close();
});
+
+ console.log('%c[index.js]', 'color: green;', 'Sending pairing request to ' + nvhttpHost.hostname + ' with random number' + randomNumber);
+ nvhttpHost.pair(randomNumber).then(function(paired) {
+ if (!paired) {
+ if (nvhttpHost.currentGame != 0) {
+ $('#pairingDialogText').html('Error: ' + nvhttpHost.hostname + ' is busy. Stop streaming to pair.');
+ } else {
+ $('#pairingDialogText').html('Error: failed to pair with ' + nvhttpHost.hostname + '.');
+ }
+ console.log('%c[index.js]', 'color: green;', 'Failed API object:', nvhttpHost, nvhttpHost.toString()); //Logging both the object and the toString version for text logs
+ onFailure();
+ return;
+ }
+
+ snackbarLog('Pairing successful');
+ pairingDialog.close();
+ onSuccess();
+ }, function(failedPairing) {
+ snackbarLog('Failed pairing to: ' + nvhttpHost.hostname);
+ console.error('%c[index.js]', 'color: green;', 'Pairing failed, and returned:', failedPairing);
+ console.error('%c[index.js]', 'color: green;', 'Failed API object:', nvhttpHost, nvhttpHost.toString()); //Logging both the object and the toString version for text logs
+ onFailure();
+ });
+ });
}
function hostChosen(host) {
- if (!host.online) {
- return;
- }
+ if (!host.online) {
+ return;
+ }
- // Avoid delay from other polling during pairing
- stopPollingHosts();
+ // Avoid delay from other polling during pairing
+ stopPollingHosts();
- api = host;
- if (!host.paired) {
- // Still not paired; go to the pairing flow
- pairTo(host, function() {
- showApps(host);
- saveHosts();
- },
- function(){
- startPollingHosts();
- });
- } else {
- // When we queried again, it was paired, so show apps.
+ api = host;
+ if (!host.paired) {
+ // Still not paired; go to the pairing flow
+ pairTo(host, function() {
showApps(host);
- }
+ saveHosts();
+ },
+ function() {
+ startPollingHosts();
+ });
+ } else {
+ // When we queried again, it was paired, so show apps.
+ showApps(host);
+ }
}
// the `+` was selected on the host grid.
// give the user a dialog to input connection details for the PC
function addHost() {
- var modal = document.querySelector('#addHostDialog');
- modal.showModal();
+ var modal = document.querySelector('#addHostDialog');
+ modal.showModal();
- // drop the dialog if they cancel
- $('#cancelAddHost').off('click');
- $('#cancelAddHost').on('click', function() {
- modal.close();
- });
-
- // try to pair if they continue
- $('#continueAddHost').off('click');
- $('#continueAddHost').on('click', function () {
- var inputHost = $('#dialogInputHost').val();
- var _nvhttpHost = new NvHTTP(inputHost, myUniqueid, inputHost);
-
- pairTo(_nvhttpHost, function() {
- // Check if we already have record of this host
- if (hosts[_nvhttpHost.serverUid] != null) {
- // Just update the addresses
- hosts[_nvhttpHost.serverUid].address = _nvhttpHost.address;
- hosts[_nvhttpHost.serverUid].userEnteredAddress = _nvhttpHost.userEnteredAddress;
- }
- else {
- beginBackgroundPollingOfHost(_nvhttpHost);
- addHostToGrid(_nvhttpHost);
- }
- saveHosts();
- }, function() {
- snackbarLog('pairing to ' + inputHost + ' failed!');
- });
- modal.close();
+ // drop the dialog if they cancel
+ $('#cancelAddHost').off('click');
+ $('#cancelAddHost').on('click', function() {
+ modal.close();
+ });
+
+ // try to pair if they continue
+ $('#continueAddHost').off('click');
+ $('#continueAddHost').on('click', function() {
+ var inputHost = $('#dialogInputHost').val();
+ var _nvhttpHost = new NvHTTP(inputHost, myUniqueid, inputHost);
+
+ pairTo(_nvhttpHost, function() {
+ // Check if we already have record of this host
+ if (hosts[_nvhttpHost.serverUid] != null) {
+ // Just update the addresses
+ hosts[_nvhttpHost.serverUid].address = _nvhttpHost.address;
+ hosts[_nvhttpHost.serverUid].userEnteredAddress = _nvhttpHost.userEnteredAddress;
+ } else {
+ beginBackgroundPollingOfHost(_nvhttpHost);
+ addHostToGrid(_nvhttpHost);
+ }
+ saveHosts();
+ }, function() {
+ snackbarLog('pairing to ' + inputHost + ' failed!');
});
+ modal.close();
+ });
}
// host is an NvHTTP object
function addHostToGrid(host, ismDNSDiscovered) {
- var outerDiv = $("
", {class: 'host-container mdl-card mdl-shadow--4dp', id: 'host-container-' + host.serverUid, role: 'link', tabindex: 0, 'aria-label': host.hostname });
- var cell = $("
", {class: 'mdl-card__title mdl-card--expand', id: 'hostgrid-' + host.serverUid });
- $(cell).prepend($("
", {class: "mdl-card__title-text", html: host.hostname}));
- var removalButton = $("", {class: "remove-host", id: "removeHostButton-" + host.serverUid, role: 'button', tabindex: 0, 'aria-label': 'Remove host ' + host.hostname});
- removalButton.off('click');
- removalButton.click(function () {
- removeClicked(host);
- });
- cell.off('click');
- cell.click(function () {
- hostChosen(host);
- });
- outerDiv.keypress(function(e){
- if(e.keyCode == 13) {
- hostChosen(host);
- }
- });
- $(outerDiv).append(cell);
- if (!ismDNSDiscovered) {
- // we don't have the option to delete mDNS hosts. So don't show it to the user.
- $(outerDiv).append(removalButton);
+ var outerDiv = $("
", {
+ class: 'host-container mdl-card mdl-shadow--4dp',
+ id: 'host-container-' + host.serverUid,
+ role: 'link',
+ tabindex: 0,
+ 'aria-label': host.hostname
+ });
+ var cell = $("
", {
+ class: 'mdl-card__title mdl-card--expand',
+ id: 'hostgrid-' + host.serverUid
+ });
+ $(cell).prepend($("
", {
+ class: "mdl-card__title-text",
+ html: host.hostname
+ }));
+ var removalButton = $("", {
+ class: "remove-host",
+ id: "removeHostButton-" + host.serverUid,
+ role: 'button',
+ tabindex: 0,
+ 'aria-label': 'Remove host ' + host.hostname
+ });
+ removalButton.off('click');
+ removalButton.click(function() {
+ removeClicked(host);
+ });
+ cell.off('click');
+ cell.click(function() {
+ hostChosen(host);
+ });
+ outerDiv.keypress(function(e) {
+ if (e.keyCode == 13) {
+ hostChosen(host);
}
- $('#host-grid').append(outerDiv);
- hosts[host.serverUid] = host;
+ });
+ $(outerDiv).append(cell);
+ if (!ismDNSDiscovered) {
+ // we don't have the option to delete mDNS hosts. So don't show it to the user.
+ $(outerDiv).append(removalButton);
+ }
+ $('#host-grid').append(outerDiv);
+ hosts[host.serverUid] = host;
}
function removeClicked(host) {
- var deleteHostDialog = document.querySelector('#deleteHostDialog');
- document.getElementById('deleteHostDialogText').innerHTML =
+ var deleteHostDialog = document.querySelector('#deleteHostDialog');
+ document.getElementById('deleteHostDialogText').innerHTML =
' Are you sure you want to delete ' + host.hostname + '?';
- deleteHostDialog.showModal();
+ deleteHostDialog.showModal();
- $('#cancelDeleteHost').off('click');
- $('#cancelDeleteHost').on('click', function () {
- deleteHostDialog.close();
- });
+ $('#cancelDeleteHost').off('click');
+ $('#cancelDeleteHost').on('click', function() {
+ deleteHostDialog.close();
+ });
- // locally remove the hostname/ip from the saved `hosts` array.
- // note: this does not make the host forget the pairing to us.
- // this means we can re-add the host, and will still be paired.
- $('#continueDeleteHost').off('click');
- $('#continueDeleteHost').on('click', function () {
- var deleteHostDialog = document.querySelector('#deleteHostDialog');
- $('#host-container-' + host.serverUid).remove();
- delete hosts[host.serverUid]; // remove the host from the array;
- saveHosts();
- deleteHostDialog.close();
- });
+ // locally remove the hostname/ip from the saved `hosts` array.
+ // note: this does not make the host forget the pairing to us.
+ // this means we can re-add the host, and will still be paired.
+ $('#continueDeleteHost').off('click');
+ $('#continueDeleteHost').on('click', function() {
+ var deleteHostDialog = document.querySelector('#deleteHostDialog');
+ $('#host-container-' + host.serverUid).remove();
+ delete hosts[host.serverUid]; // remove the host from the array;
+ saveHosts();
+ deleteHostDialog.close();
+ });
}
// puts the CSS style for current app on the app that's currently running
@@ -418,17 +436,17 @@ function removeClicked(host) {
// the function was made like this so that we can remove duplicated code, but
// not do N*N stylizations of the box art, or make the code not flow very well
function stylizeBoxArt(freshApi, appIdToStylize) {
- if (freshApi.currentGame === appIdToStylize){ // stylize the currently running game
- // destylize it, if it has the not-current-game style
- if ($('#game-'+ appIdToStylize).hasClass("not-current-game")) $('#game-'+ appIdToStylize).removeClass("not-current-game");
- // add the current-game style
- $('#game-'+ appIdToStylize).addClass("current-game");
- } else {
- // destylize it, if it has the current-game style
- if ($('#game-'+ appIdToStylize).hasClass("current-game")) $('#game-'+ appIdToStylize).removeClass("current-game");
- // add the not-current-game style
- $('#game-'+ appIdToStylize).addClass('not-current-game');
- }
+ if (freshApi.currentGame === appIdToStylize) { // stylize the currently running game
+ // destylize it, if it has the not-current-game style
+ if ($('#game-' + appIdToStylize).hasClass("not-current-game")) $('#game-' + appIdToStylize).removeClass("not-current-game");
+ // add the current-game style
+ $('#game-' + appIdToStylize).addClass("current-game");
+ } else {
+ // destylize it, if it has the current-game style
+ if ($('#game-' + appIdToStylize).hasClass("current-game")) $('#game-' + appIdToStylize).removeClass("current-game");
+ // add the not-current-game style
+ $('#game-' + appIdToStylize).addClass('not-current-game');
+ }
}
function sortTitles(list, sortOrder) {
@@ -438,329 +456,358 @@ function sortTitles(list, sortOrder) {
// A - Z
if (sortOrder === 'ASC') {
- if (titleA < titleB) { return -1; }
- if (titleA > titleB) { return 1; }
+ if (titleA < titleB) {
+ return -1;
+ }
+ if (titleA > titleB) {
+ return 1;
+ }
return 0;
}
// Z - A
if (sortOrder === 'DESC') {
- if (titleA < titleB) { return 1; }
- if (titleA > titleB) { return -1; }
- return 0; }
+ if (titleA < titleB) {
+ return 1;
+ }
+ if (titleA > titleB) {
+ return -1;
+ }
+ return 0;
+ }
});
}
// show the app list
function showApps(host) {
- if(!host || !host.paired) { // safety checking. shouldn't happen.
- console.log('%c[index.js, showApps]', 'color: green;', 'Moved into showApps, but `host` did not initialize properly! Failing.');
- return;
- }
- console.log('%c[index.js, showApps]', 'color: green;', 'Current host object:', host, host.toString()); //Logging both object (for console) and toString-ed object (for text logs)
- $('#quitCurrentApp').show();
- $("#gameList .game-container").remove();
+ if (!host || !host.paired) { // safety checking. shouldn't happen.
+ console.log('%c[index.js, showApps]', 'color: green;', 'Moved into showApps, but `host` did not initialize properly! Failing.');
+ return;
+ }
+ console.log('%c[index.js, showApps]', 'color: green;', 'Current host object:', host, host.toString()); //Logging both object (for console) and toString-ed object (for text logs)
+ $('#quitCurrentApp').show();
+ $("#gameList .game-container").remove();
- // Show a spinner while the applist loads
- $('#naclSpinnerMessage').text('Loading apps...');
- $('#naclSpinner').css('display', 'inline-block');
+ // Show a spinner while the applist loads
+ $('#naclSpinnerMessage').text('Loading apps...');
+ $('#naclSpinner').css('display', 'inline-block');
- host.getAppList().then(function (appList) {
- // if game grid is populated, empty it
- $("div.game-container").remove();
+ host.getAppList().then(function(appList) {
+ // if game grid is populated, empty it
+ $("div.game-container").remove();
- $('#naclSpinner').hide();
- $("#game-grid").show();
+ $('#naclSpinner').hide();
+ $("#game-grid").show();
- const sortedAppList = sortTitles(appList, 'ASC');
+ const sortedAppList = sortTitles(appList, 'ASC');
- sortedAppList.forEach(function (app) {
- if ($('#game-' + app.id).length === 0) {
- // double clicking the button will cause multiple box arts to appear.
- // to mitigate this we ensure we don't add a duplicate.
- // This isn't perfect: there's lots of RTTs before the logic prevents anything
- var outerDiv = $("
", {class: 'game-container mdl-card mdl-shadow--4dp', id: 'game-'+app.id, role: 'link', tabindex: 0, title: app.title, 'aria-label': app.title });
-
- $(outerDiv).append($("
", {class: "game-title", html: $("
", {html: app.title} )}));
- $("#game-grid").append(outerDiv);
-
- $('#game-'+app.id).on('click', function () {
- startGame(host, app.id);
- });
- $('#game-'+app.id).keypress(function(e){
- if(e.keyCode == 13) {
- startGame(host, app.id);
- }
- });
-
- // apply CSS stylization to indicate whether the app is active
- stylizeBoxArt(host, app.id);
- }
- host.getBoxArt(app.id).then(function (resolvedPromise) {
- // put the box art into the image holder
- $(outerDiv).append($("
", {src: resolvedPromise, name: app.title }));
-
- }, function (failedPromise) {
- console.log('%c[index.js, showApps]', 'color: green;', 'Error! Failed to retrieve box art for app ID: ' + app.id + '. Returned value was: ' + failedPromise, '\n Host object:', host, host.toString());
- $(outerDiv).append($("
", {src: "static/res/no_app_image.png", name: app.title }));
- });
+ sortedAppList.forEach(function(app) {
+ if ($('#game-' + app.id).length === 0) {
+ // double clicking the button will cause multiple box arts to appear.
+ // to mitigate this we ensure we don't add a duplicate.
+ // This isn't perfect: there's lots of RTTs before the logic prevents anything
+ var outerDiv = $("", {
+ class: 'game-container mdl-card mdl-shadow--4dp',
+ id: 'game-' + app.id,
+ role: 'link',
+ tabindex: 0,
+ title: app.title,
+ 'aria-label': app.title
});
- }, function (failedAppList) {
- $('#naclSpinner').hide();
- console.log('%c[index.js, showApps]', 'color: green;', 'Failed to get applist from host: ' + host.hostname, '\n Host object:', host, host.toString());
+ $(outerDiv).append($("
", {
+ class: "game-title",
+ html: $("
", {
+ html: app.title
+ })
+ }));
+ $("#game-grid").append(outerDiv);
+
+ $('#game-' + app.id).on('click', function() {
+ startGame(host, app.id);
+ });
+ $('#game-' + app.id).keypress(function(e) {
+ if (e.keyCode == 13) {
+ startGame(host, app.id);
+ }
+ });
+
+ // apply CSS stylization to indicate whether the app is active
+ stylizeBoxArt(host, app.id);
+ }
+ host.getBoxArt(app.id).then(function(resolvedPromise) {
+ // put the box art into the image holder
+ $(outerDiv).append($("
", {
+ src: resolvedPromise,
+ name: app.title
+ }));
+
+ }, function(failedPromise) {
+ console.log('%c[index.js, showApps]', 'color: green;', 'Error! Failed to retrieve box art for app ID: ' + app.id + '. Returned value was: ' + failedPromise, '\n Host object:', host, host.toString());
+ $(outerDiv).append($("
", {
+ src: "static/res/no_app_image.png",
+ name: app.title
+ }));
+ });
});
+ }, function(failedAppList) {
+ $('#naclSpinner').hide();
- showAppsMode();
+ console.log('%c[index.js, showApps]', 'color: green;', 'Failed to get applist from host: ' + host.hostname, '\n Host object:', host, host.toString());
+ });
+
+ showAppsMode();
}
// set the layout to the initial mode you see when you open moonlight
function showHostsAndSettingsMode() {
- console.log('%c[index.js]', 'color: green;', 'Entering "Show apps and hosts" mode');
- $("#main-navigation").show();
- $(".nav-menu-parent").show();
- $("#externalAudioBtn").show();
- $("#main-content").children().not("#listener, #loadingSpinner, #naclSpinner").show();
- $('#game-grid').hide();
- $('#backIcon').hide();
- $('#quitCurrentApp').hide();
- $("#main-content").removeClass("fullscreen");
- $("#listener").removeClass("fullscreen");
+ console.log('%c[index.js]', 'color: green;', 'Entering "Show apps and hosts" mode');
+ $("#main-navigation").show();
+ $(".nav-menu-parent").show();
+ $("#externalAudioBtn").show();
+ $("#main-content").children().not("#listener, #loadingSpinner, #naclSpinner").show();
+ $('#game-grid').hide();
+ $('#backIcon').hide();
+ $('#quitCurrentApp').hide();
+ $("#main-content").removeClass("fullscreen");
+ $("#listener").removeClass("fullscreen");
- startPollingHosts();
+ startPollingHosts();
}
function showAppsMode() {
- console.log('%c[index.js]', 'color: green;', 'Entrering "Show apps" mode');
- $('#backIcon').show();
- $("#main-navigation").show();
- $("#main-content").children().not("#listener, #loadingSpinner, #naclSpinner").show();
- $("#streamSettings").hide();
- $(".nav-menu-parent").hide();
- $("#externalAudioBtn").hide();
- $("#host-grid").hide();
- $("#settings").hide();
- $("#main-content").removeClass("fullscreen");
- $("#listener").removeClass("fullscreen");
+ console.log('%c[index.js]', 'color: green;', 'Entrering "Show apps" mode');
+ $('#backIcon').show();
+ $("#main-navigation").show();
+ $("#main-content").children().not("#listener, #loadingSpinner, #naclSpinner").show();
+ $("#streamSettings").hide();
+ $(".nav-menu-parent").hide();
+ $("#externalAudioBtn").hide();
+ $("#host-grid").hide();
+ $("#settings").hide();
+ $("#main-content").removeClass("fullscreen");
+ $("#listener").removeClass("fullscreen");
- // FIXME: We want to eventually poll on the app screen but we can't now
- // because it slows down box art loading and we don't update the UI live
- // anyway.
- stopPollingHosts();
+ // FIXME: We want to eventually poll on the app screen but we can't now
+ // because it slows down box art loading and we don't update the UI live
+ // anyway.
+ stopPollingHosts();
}
// start the given appID. if another app is running, offer to quit it.
// if the given app is already running, just resume it.
function startGame(host, appID) {
- if(!host || !host.paired) {
- console.error('%c[index.js, startGame]', 'color: green;', 'Attempted to start a game, but `host` did not initialize properly. Host object: ', host);
- return;
- }
+ if (!host || !host.paired) {
+ console.error('%c[index.js, startGame]', 'color: green;', 'Attempted to start a game, but `host` did not initialize properly. Host object: ', host);
+ return;
+ }
- // refresh the server info, because the user might have quit the game.
- host.refreshServerInfo().then(function (ret) {
- host.getAppById(appID).then(function (appToStart) {
+ // refresh the server info, because the user might have quit the game.
+ host.refreshServerInfo().then(function(ret) {
+ host.getAppById(appID).then(function(appToStart) {
- if(host.currentGame != 0 && host.currentGame != appID) {
- host.getAppById(host.currentGame).then(function (currentApp) {
- var quitAppDialog = document.querySelector('#quitAppDialog');
- document.getElementById('quitAppDialogText').innerHTML =
- currentApp.title + ' is already running. Would you like to quit ' +
- currentApp.title + '?';
- quitAppDialog.showModal();
- $('#cancelQuitApp').off('click');
- $('#cancelQuitApp').on('click', function () {
- quitAppDialog.close();
- console.log('[index.js, startGame]','color: green;', 'Closing app dialog, and returning');
- });
- $('#continueQuitApp').off('click');
- $('#continueQuitApp').on('click', function () {
- console.log('[index.js, startGame]','color: green;', 'Stopping game, and closing app dialog, and returning');
- stopGame(host, function () {
- // please oh please don't infinite loop with recursion
- startGame(host, appID);
- });
- quitAppDialog.close();
- });
-
- return;
- }, function (failedCurrentApp) {
- console.error('[index.js, startGame]','color: green;', 'Failed to get the current running app from host! Returned error was:' + failedCurrentApp, '\n Host object:', host, host.toString());
- return;
- });
- return;
- }
-
- var frameRate = $('#selectFramerate').data('value').toString();
- var optimize = $("#optimizeGamesSwitch").parent().hasClass('is-checked') ? 1 : 0;
- var streamWidth = $('#selectResolution').data('value').split(':')[0];
- var streamHeight = $('#selectResolution').data('value').split(':')[1];
- // we told the user it was in Mbps. We're dirty liars and use Kbps behind their back.
- var bitrate = parseInt($("#bitrateSlider").val()) * 1000;
- console.log('%c[index.js, startGame]','color:green;', 'startRequest:' + host.address + ":" + streamWidth + ":" + streamHeight + ":" + frameRate + ":" + bitrate + ":" + optimize);
-
- var rikey = generateRemoteInputKey();
- var rikeyid = generateRemoteInputKeyId();
- var gamepadMask = getConnectedGamepadMask();
-
- $('#loadingMessage').text('Starting ' + appToStart.title + '...');
- playGameMode();
-
- if(host.currentGame == appID) { // if user wants to launch the already-running app, then we resume it.
- return host.resumeApp(
- rikey, rikeyid, 0x030002 // Surround channel mask << 16 | Surround channel count
- ).then(function (ret) {
- sendMessage('startRequest', [host.address, streamWidth, streamHeight, frameRate,
- bitrate.toString(), rikey, rikeyid.toString(), host.appVersion]);
- }, function (failedResumeApp) {
- console.eror('%c[index.js, startGame]', 'color:green;', 'Failed to resume the app! Returned error was' + failedResumeApp);
- return;
- });
- }
-
- var remote_audio_enabled = $("#remoteAudioEnabledSwitch").parent().hasClass('is-checked') ? 1 : 0;
-
- host.launchApp(appID,
- streamWidth + "x" + streamHeight + "x" + frameRate,
- optimize, // DON'T Allow GFE (0) to optimize game settings, or ALLOW (1) to optimize game settings
- rikey, rikeyid,
- remote_audio_enabled, // Play audio locally too?
- 0x030002, // Surround channel mask << 16 | Surround channel count
- gamepadMask
- ).then(function (ret) {
- sendMessage('startRequest', [host.address, streamWidth, streamHeight, frameRate,
- bitrate.toString(), rikey, rikeyid.toString(), host.appVersion]);
- }, function (failedLaunchApp) {
- console.error('%c[index.js, launchApp]','color: green;','Failed to launch app width id: ' + appID + '\nReturned error was: ' + failedLaunchApp);
- return;
+ if (host.currentGame != 0 && host.currentGame != appID) {
+ host.getAppById(host.currentGame).then(function(currentApp) {
+ var quitAppDialog = document.querySelector('#quitAppDialog');
+ document.getElementById('quitAppDialogText').innerHTML =
+ currentApp.title + ' is already running. Would you like to quit ' +
+ currentApp.title + '?';
+ quitAppDialog.showModal();
+ $('#cancelQuitApp').off('click');
+ $('#cancelQuitApp').on('click', function() {
+ quitAppDialog.close();
+ console.log('[index.js, startGame]', 'color: green;', 'Closing app dialog, and returning');
+ });
+ $('#continueQuitApp').off('click');
+ $('#continueQuitApp').on('click', function() {
+ console.log('[index.js, startGame]', 'color: green;', 'Stopping game, and closing app dialog, and returning');
+ stopGame(host, function() {
+ // please oh please don't infinite loop with recursion
+ startGame(host, appID);
});
+ quitAppDialog.close();
+ });
+ return;
+ }, function(failedCurrentApp) {
+ console.error('[index.js, startGame]', 'color: green;', 'Failed to get the current running app from host! Returned error was:' + failedCurrentApp, '\n Host object:', host, host.toString());
+ return;
});
+ return;
+ }
+
+ var frameRate = $('#selectFramerate').data('value').toString();
+ var optimize = $("#optimizeGamesSwitch").parent().hasClass('is-checked') ? 1 : 0;
+ var streamWidth = $('#selectResolution').data('value').split(':')[0];
+ var streamHeight = $('#selectResolution').data('value').split(':')[1];
+ // we told the user it was in Mbps. We're dirty liars and use Kbps behind their back.
+ var bitrate = parseInt($("#bitrateSlider").val()) * 1000;
+ console.log('%c[index.js, startGame]', 'color:green;', 'startRequest:' + host.address + ":" + streamWidth + ":" + streamHeight + ":" + frameRate + ":" + bitrate + ":" + optimize);
+
+ var rikey = generateRemoteInputKey();
+ var rikeyid = generateRemoteInputKeyId();
+ var gamepadMask = getConnectedGamepadMask();
+
+ $('#loadingMessage').text('Starting ' + appToStart.title + '...');
+ playGameMode();
+
+ if (host.currentGame == appID) { // if user wants to launch the already-running app, then we resume it.
+ return host.resumeApp(
+ rikey, rikeyid, 0x030002 // Surround channel mask << 16 | Surround channel count
+ ).then(function(ret) {
+ sendMessage('startRequest', [host.address, streamWidth, streamHeight, frameRate,
+ bitrate.toString(), rikey, rikeyid.toString(), host.appVersion
+ ]);
+ }, function(failedResumeApp) {
+ console.eror('%c[index.js, startGame]', 'color:green;', 'Failed to resume the app! Returned error was' + failedResumeApp);
+ return;
+ });
+ }
+
+ var remote_audio_enabled = $("#remoteAudioEnabledSwitch").parent().hasClass('is-checked') ? 1 : 0;
+
+ host.launchApp(appID,
+ streamWidth + "x" + streamHeight + "x" + frameRate,
+ optimize, // DON'T Allow GFE (0) to optimize game settings, or ALLOW (1) to optimize game settings
+ rikey, rikeyid,
+ remote_audio_enabled, // Play audio locally too?
+ 0x030002, // Surround channel mask << 16 | Surround channel count
+ gamepadMask
+ ).then(function(ret) {
+ sendMessage('startRequest', [host.address, streamWidth, streamHeight, frameRate,
+ bitrate.toString(), rikey, rikeyid.toString(), host.appVersion
+ ]);
+ }, function(failedLaunchApp) {
+ console.error('%c[index.js, launchApp]', 'color: green;', 'Failed to launch app width id: ' + appID + '\nReturned error was: ' + failedLaunchApp);
+ return;
+ });
+
});
+ });
}
function playGameMode() {
- console.log('%c[index.js, playGameMode]', 'color:green;', 'Entering play game mode');
- isInGame = true;
+ console.log('%c[index.js, playGameMode]', 'color:green;', 'Entering play game mode');
+ isInGame = true;
- $("#main-navigation").hide();
- $("#main-content").children().not("#listener, #loadingSpinner").hide();
- $("#main-content").addClass("fullscreen");
+ $("#main-navigation").hide();
+ $("#main-content").children().not("#listener, #loadingSpinner").hide();
+ $("#main-content").addClass("fullscreen");
- chrome.app.window.current().fullscreen();
- fullscreenNaclModule();
- $('#loadingSpinner').css('display', 'inline-block');
+ chrome.app.window.current().fullscreen();
+ fullscreenNaclModule();
+ $('#loadingSpinner').css('display', 'inline-block');
}
// Maximize the size of the nacl module by scaling and resizing appropriately
function fullscreenNaclModule() {
- var streamWidth = $('#selectResolution').data('value').split(':')[0];
- var streamHeight = $('#selectResolution').data('value').split(':')[1];
- var screenWidth = window.innerWidth;
- var screenHeight = window.innerHeight;
+ var streamWidth = $('#selectResolution').data('value').split(':')[0];
+ var streamHeight = $('#selectResolution').data('value').split(':')[1];
+ var screenWidth = window.innerWidth;
+ var screenHeight = window.innerHeight;
- var xRatio = screenWidth / streamWidth;
- var yRatio = screenHeight / streamHeight;
+ var xRatio = screenWidth / streamWidth;
+ var yRatio = screenHeight / streamHeight;
- var zoom = Math.min(xRatio, yRatio);
+ var zoom = Math.min(xRatio, yRatio);
- var module = $("#nacl_module")[0];
- module.width = zoom * streamWidth;
- module.height = zoom * streamHeight;
- module.style.paddingTop = ((screenHeight - module.height) / 2) + "px";
+ var module = $("#nacl_module")[0];
+ module.width = zoom * streamWidth;
+ module.height = zoom * streamHeight;
+ module.style.paddingTop = ((screenHeight - module.height) / 2) + "px";
}
function stopGameWithConfirmation() {
- if (api.currentGame === 0) {
- snackbarLog('Nothing was running');
- } else {
- api.getAppById(api.currentGame).then(function (currentGame) {
- var quitAppDialog = document.querySelector('#quitAppDialog');
- document.getElementById('quitAppDialogText').innerHTML =
- ' Are you sure you would like to quit ' +
- currentGame.title + '? Unsaved progress will be lost.';
- quitAppDialog.showModal();
- $('#cancelQuitApp').off('click');
- $('#cancelQuitApp').on('click', function () {
- console.log('%c[index.js, stopGameWithConfirmation]', 'color:green;', 'Closing app dialog, and returning');
- quitAppDialog.close();
- });
- $('#continueQuitApp').off('click');
- $('#continueQuitApp').on('click', function () {
- console.log('%c[index.js, stopGameWithConfirmation]', 'color:green;', 'Stopping game, and closing app dialog, and returning');
- stopGame(api);
- quitAppDialog.close();
- });
+ if (api.currentGame === 0) {
+ snackbarLog('Nothing was running');
+ } else {
+ api.getAppById(api.currentGame).then(function(currentGame) {
+ var quitAppDialog = document.querySelector('#quitAppDialog');
+ document.getElementById('quitAppDialogText').innerHTML =
+ ' Are you sure you would like to quit ' +
+ currentGame.title + '? Unsaved progress will be lost.';
+ quitAppDialog.showModal();
+ $('#cancelQuitApp').off('click');
+ $('#cancelQuitApp').on('click', function() {
+ console.log('%c[index.js, stopGameWithConfirmation]', 'color:green;', 'Closing app dialog, and returning');
+ quitAppDialog.close();
+ });
+ $('#continueQuitApp').off('click');
+ $('#continueQuitApp').on('click', function() {
+ console.log('%c[index.js, stopGameWithConfirmation]', 'color:green;', 'Stopping game, and closing app dialog, and returning');
+ stopGame(api);
+ quitAppDialog.close();
+ });
- });
- }
+ });
+ }
}
function stopGame(host, callbackFunction) {
- isInGame = false;
+ isInGame = false;
- if (!host.paired) {
+ if (!host.paired) {
+ return;
+ }
+
+ host.refreshServerInfo().then(function(ret) {
+ host.getAppById(host.currentGame).then(function(runningApp) {
+ if (!runningApp) {
+ snackbarLog('Nothing was running');
return;
- }
-
- host.refreshServerInfo().then(function (ret) {
- host.getAppById(host.currentGame).then(function (runningApp) {
- if (!runningApp) {
- snackbarLog('Nothing was running');
- return;
- }
- var appName = runningApp.title;
- snackbarLog('Stopping ' + appName);
- host.quitApp().then(function (ret2) {
- host.refreshServerInfo().then(function (ret3) { // refresh to show no app is currently running.
- showAppsMode();
- stylizeBoxArt(host, runningApp.id);
- if (typeof(callbackFunction) === "function") callbackFunction();
- }, function (failedRefreshInfo2) {
- console.error('%c[index.js, stopGame]', 'color:green;', 'Failed to refresh server info! Returned error was:' + failedRefreshInfo + ' and failed server was:', host, host.toString());
- });
- }, function (failedQuitApp) {
- console.error('%c[index.js, stopGame]', 'color:green;', 'Failed to quit app! Returned error was:' + failedQuitApp);
- });
- }, function (failedGetApp) {
- console.error('%c[index.js, stopGame]', 'color:green;', 'Failed to get app ID! Returned error was:' + failedRefreshInfo);
+ }
+ var appName = runningApp.title;
+ snackbarLog('Stopping ' + appName);
+ host.quitApp().then(function(ret2) {
+ host.refreshServerInfo().then(function(ret3) { // refresh to show no app is currently running.
+ showAppsMode();
+ stylizeBoxArt(host, runningApp.id);
+ if (typeof(callbackFunction) === "function") callbackFunction();
+ }, function(failedRefreshInfo2) {
+ console.error('%c[index.js, stopGame]', 'color:green;', 'Failed to refresh server info! Returned error was:' + failedRefreshInfo + ' and failed server was:', host, host.toString());
});
- }, function (failedRefreshInfo) {
- console.error('%c[index.js, stopGame]', 'color:green;', 'Failed to refresh server info! Returned error was:' + failedRefreshInfo);
+ }, function(failedQuitApp) {
+ console.error('%c[index.js, stopGame]', 'color:green;', 'Failed to quit app! Returned error was:' + failedQuitApp);
+ });
+ }, function(failedGetApp) {
+ console.error('%c[index.js, stopGame]', 'color:green;', 'Failed to get app ID! Returned error was:' + failedRefreshInfo);
});
+ }, function(failedRefreshInfo) {
+ console.error('%c[index.js, stopGame]', 'color:green;', 'Failed to refresh server info! Returned error was:' + failedRefreshInfo);
+ });
}
function storeData(key, data, callbackFunction) {
- var obj = {};
- obj[key] = data;
- if(chrome.storage)
- chrome.storage.sync.set(obj, callbackFunction);
+ var obj = {};
+ obj[key] = data;
+ if (chrome.storage)
+ chrome.storage.sync.set(obj, callbackFunction);
}
function saveResolution() {
- var chosenResolution = $(this).data('value');
- $('#selectResolution').text($(this).text()).data('value', chosenResolution);
- storeData('resolution', chosenResolution, null);
- updateDefaultBitrate();
+ var chosenResolution = $(this).data('value');
+ $('#selectResolution').text($(this).text()).data('value', chosenResolution);
+ storeData('resolution', chosenResolution, null);
+ updateDefaultBitrate();
}
function saveOptimize() {
- // MaterialDesignLight uses the mouseup trigger, so we give it some time to change the class name before
- // checking the new state
- setTimeout(function() {
- var chosenOptimize = $("#optimizeGamesSwitch").parent().hasClass('is-checked');
- console.log('%c[index.js, saveOptimize]', 'color: green;', 'Saving optimize state : ' + chosenOptimize);
- storeData('optimize', chosenOptimize, null);
- }, 100);
+ // MaterialDesignLight uses the mouseup trigger, so we give it some time to change the class name before
+ // checking the new state
+ setTimeout(function() {
+ var chosenOptimize = $("#optimizeGamesSwitch").parent().hasClass('is-checked');
+ console.log('%c[index.js, saveOptimize]', 'color: green;', 'Saving optimize state : ' + chosenOptimize);
+ storeData('optimize', chosenOptimize, null);
+ }, 100);
}
function saveFramerate() {
- var chosenFramerate = $(this).data('value');
- $('#selectFramerate').text($(this).text()).data('value', chosenFramerate);
- storeData('frameRate', chosenFramerate, null);
- updateDefaultBitrate();
+ var chosenFramerate = $(this).data('value');
+ $('#selectFramerate').text($(this).text()).data('value', chosenFramerate);
+ storeData('frameRate', chosenFramerate, null);
+ updateDefaultBitrate();
}
@@ -769,111 +816,111 @@ function saveFramerate() {
// unfortunately, objects with function instances (classes) are stripped of their function instances when converted to a raw object
// so we cannot forget to revive the object after we load it.
function saveHosts() {
- storeData('hosts', hosts, null);
+ storeData('hosts', hosts, null);
}
function saveBitrate() {
- storeData('bitrate', $('#bitrateSlider').val(), null);
+ storeData('bitrate', $('#bitrateSlider').val(), null);
}
function saveRemoteAudio() {
- // MaterialDesignLight uses the mouseup trigger, so we give it some time to change the class name before
- // checking the new state
- setTimeout(function() {
- var remoteAudioState = $("#remoteAudioEnabledSwitch").parent().hasClass('is-checked');
- console.log('%c[index.js, saveRemoteAudio]', 'color: green;', 'Saving remote audio state : ' + remoteAudioState);
- storeData('remoteAudio', remoteAudioState, null);
- }, 100);
+ // MaterialDesignLight uses the mouseup trigger, so we give it some time to change the class name before
+ // checking the new state
+ setTimeout(function() {
+ var remoteAudioState = $("#remoteAudioEnabledSwitch").parent().hasClass('is-checked');
+ console.log('%c[index.js, saveRemoteAudio]', 'color: green;', 'Saving remote audio state : ' + remoteAudioState);
+ storeData('remoteAudio', remoteAudioState, null);
+ }, 100);
}
function updateDefaultBitrate() {
- var res = $('#selectResolution').data('value');
- var frameRate = $('#selectFramerate').data('value').toString();
+ var res = $('#selectResolution').data('value');
+ var frameRate = $('#selectFramerate').data('value').toString();
- if (res ==="1920:1080") {
- if (frameRate === "30") { // 1080p, 30fps
- $('#bitrateSlider')[0].MaterialSlider.change('10');
- } else { // 1080p, 60fps
- $('#bitrateSlider')[0].MaterialSlider.change('20');
- }
- } else if (res === "1280:720") {
- if (frameRate === "30") { // 720, 30fps
- $('#bitrateSlider')[0].MaterialSlider.change('5');
- } else { // 720, 60fps
- $('#bitrateSlider')[0].MaterialSlider.change('10');
- }
- } else if (res === "3840:2160") {
- if (frameRate === "30") { // 2160p, 30fps
- $('#bitrateSlider')[0].MaterialSlider.change('40');
- } else { // 2160p, 60fps
- $('#bitrateSlider')[0].MaterialSlider.change('80');
- }
- } else { // unrecognized option. In case someone screws with the JS to add custom resolutions
- $('#bitrateSlider')[0].MaterialSlider.change('10');
+ if (res === "1920:1080") {
+ if (frameRate === "30") { // 1080p, 30fps
+ $('#bitrateSlider')[0].MaterialSlider.change('10');
+ } else { // 1080p, 60fps
+ $('#bitrateSlider')[0].MaterialSlider.change('20');
}
+ } else if (res === "1280:720") {
+ if (frameRate === "30") { // 720, 30fps
+ $('#bitrateSlider')[0].MaterialSlider.change('5');
+ } else { // 720, 60fps
+ $('#bitrateSlider')[0].MaterialSlider.change('10');
+ }
+ } else if (res === "3840:2160") {
+ if (frameRate === "30") { // 2160p, 30fps
+ $('#bitrateSlider')[0].MaterialSlider.change('40');
+ } else { // 2160p, 60fps
+ $('#bitrateSlider')[0].MaterialSlider.change('80');
+ }
+ } else { // unrecognized option. In case someone screws with the JS to add custom resolutions
+ $('#bitrateSlider')[0].MaterialSlider.change('10');
+ }
- updateBitrateField();
- saveBitrate();
+ updateBitrateField();
+ saveBitrate();
}
-function onWindowLoad(){
- console.log('%c[index.js]', 'color: green;', 'Moonlight\'s main window loaded');
- // don't show the game selection div
- $('#gameSelection').css('display', 'none');
+function onWindowLoad() {
+ console.log('%c[index.js]', 'color: green;', 'Moonlight\'s main window loaded');
+ // don't show the game selection div
+ $('#gameSelection').css('display', 'none');
- loadWindowState();
+ loadWindowState();
- if(chrome.storage) {
- // load stored resolution prefs
- chrome.storage.sync.get('resolution', function(previousValue) {
- if(previousValue.resolution != null) {
- $('.resolutionMenu li').each(function () {
- if ($(this).data('value') === previousValue.resolution) {
- $('#selectResolution').text($(this).text()).data('value', previousValue.resolution);
- }
- });
- }
+ if (chrome.storage) {
+ // load stored resolution prefs
+ chrome.storage.sync.get('resolution', function(previousValue) {
+ if (previousValue.resolution != null) {
+ $('.resolutionMenu li').each(function() {
+ if ($(this).data('value') === previousValue.resolution) {
+ $('#selectResolution').text($(this).text()).data('value', previousValue.resolution);
+ }
});
+ }
+ });
- // Load stored remote audio prefs
- chrome.storage.sync.get('remoteAudio', function(previousValue) {
- if(previousValue.remoteAudio == null) {
- document.querySelector('#externalAudioBtn').MaterialIconToggle.uncheck();
- } else if (previousValue.remoteAudio == false) {
- document.querySelector('#externalAudioBtn').MaterialIconToggle.uncheck();
- } else {
- document.querySelector('#externalAudioBtn').MaterialIconToggle.check();
- }
- });
+ // Load stored remote audio prefs
+ chrome.storage.sync.get('remoteAudio', function(previousValue) {
+ if (previousValue.remoteAudio == null) {
+ document.querySelector('#externalAudioBtn').MaterialIconToggle.uncheck();
+ } else if (previousValue.remoteAudio == false) {
+ document.querySelector('#externalAudioBtn').MaterialIconToggle.uncheck();
+ } else {
+ document.querySelector('#externalAudioBtn').MaterialIconToggle.check();
+ }
+ });
- // load stored framerate prefs
- chrome.storage.sync.get('frameRate', function(previousValue) {
- if(previousValue.frameRate != null) {
- $('.framerateMenu li').each(function () {
- if ($(this).data('value') === previousValue.frameRate) {
- $('#selectFramerate').text($(this).text()).data('value', previousValue.frameRate);
- }
- });
- }
+ // load stored framerate prefs
+ chrome.storage.sync.get('frameRate', function(previousValue) {
+ if (previousValue.frameRate != null) {
+ $('.framerateMenu li').each(function() {
+ if ($(this).data('value') === previousValue.frameRate) {
+ $('#selectFramerate').text($(this).text()).data('value', previousValue.frameRate);
+ }
});
+ }
+ });
- // load stored optimization prefs
- chrome.storage.sync.get('optimize', function(previousValue) {
- if (previousValue.optimize == null) {
- document.querySelector('#optimizeGamesBtn').MaterialIconToggle.check();
- } else if (previousValue.optimize == false) {
- document.querySelector('#optimizeGamesBtn').MaterialIconToggle.uncheck();
- } else {
- document.querySelector('#optimizeGamesBtn').MaterialIconToggle.check();
- }
- });
+ // load stored optimization prefs
+ chrome.storage.sync.get('optimize', function(previousValue) {
+ if (previousValue.optimize == null) {
+ document.querySelector('#optimizeGamesBtn').MaterialIconToggle.check();
+ } else if (previousValue.optimize == false) {
+ document.querySelector('#optimizeGamesBtn').MaterialIconToggle.uncheck();
+ } else {
+ document.querySelector('#optimizeGamesBtn').MaterialIconToggle.check();
+ }
+ });
- // load stored bitrate prefs
- chrome.storage.sync.get('bitrate', function(previousValue) {
- $('#bitrateSlider')[0].MaterialSlider.change(previousValue.bitrate != null ? previousValue.bitrate : '10');
- updateBitrateField();
- });
- }
+ // load stored bitrate prefs
+ chrome.storage.sync.get('bitrate', function(previousValue) {
+ $('#bitrateSlider')[0].MaterialSlider.change(previousValue.bitrate != null ? previousValue.bitrate : '10');
+ updateBitrateField();
+ });
+ }
}
diff --git a/static/js/messages.js b/static/js/messages.js
index 4533466..9493a90 100644
--- a/static/js/messages.js
+++ b/static/js/messages.js
@@ -9,66 +9,69 @@ var callbacks_ids = 1;
* @return {void} The NaCl module calls back trought the handleMessage method
*/
var sendMessage = function(method, params) {
- return new Promise(function(resolve, reject) {
- var id = callbacks_ids++;
- callbacks[id] = {'resolve': resolve, 'reject': reject};
+ return new Promise(function(resolve, reject) {
+ var id = callbacks_ids++;
+ callbacks[id] = {
+ 'resolve': resolve,
+ 'reject': reject
+ };
- common.naclModule.postMessage({
- 'callbackId': id,
- 'method': method,
- 'params': params
- });
+ common.naclModule.postMessage({
+ 'callbackId': id,
+ 'method': method,
+ 'params': params
});
+ });
}
/**
* handleMessage - Handles messages from the NaCl module
*
- * @param {Object} msg An object given by the NaCl module
+ * @param {Object} msg An object given by the NaCl module
* @return {void}
*/
function handleMessage(msg) {
- if (msg.data.callbackId && callbacks[msg.data.callbackId]) { // if it's a callback, treat it as such
- callbacks[msg.data.callbackId][msg.data.type](msg.data.ret);
- delete callbacks[msg.data.callbackId]
- } else { // else, it's just info, or an event
- console.log('%c[messages.js, handleMessage]', 'color:gray;', 'Message data: ', msg.data)
- if(msg.data === 'streamTerminated') { // if it's a recognized event, notify the appropriate function
- $('#loadingSpinner').css('display', 'none'); // This is a fallback for RTSP handshake failing, which immediately terminates the stream.
- $('body').css('backgroundColor', '#282C38');
+ if (msg.data.callbackId && callbacks[msg.data.callbackId]) { // if it's a callback, treat it as such
+ callbacks[msg.data.callbackId][msg.data.type](msg.data.ret);
+ delete callbacks[msg.data.callbackId]
+ } else { // else, it's just info, or an event
+ console.log('%c[messages.js, handleMessage]', 'color:gray;', 'Message data: ', msg.data)
+ if (msg.data === 'streamTerminated') { // if it's a recognized event, notify the appropriate function
+ $('#loadingSpinner').css('display', 'none'); // This is a fallback for RTSP handshake failing, which immediately terminates the stream.
+ $('body').css('backgroundColor', '#282C38');
- // Release our keep awake request
- chrome.power.releaseKeepAwake();
+ // Release our keep awake request
+ chrome.power.releaseKeepAwake();
- api.refreshServerInfo().then(function (ret) { // refresh the serverinfo to acknowledge the currently running app
- api.getAppList().then(function (appList) {
- appList.forEach(function (app) {
- stylizeBoxArt(api, app.id); // and reapply stylization to indicate what's currently running
- });
- });
- showApps(api);
+ api.refreshServerInfo().then(function(ret) { // refresh the serverinfo to acknowledge the currently running app
+ api.getAppList().then(function(appList) {
+ appList.forEach(function(app) {
+ stylizeBoxArt(api, app.id); // and reapply stylization to indicate what's currently running
+ });
+ });
+ showApps(api);
- isInGame = false;
+ isInGame = false;
- // restore main window from 'fullscreen' to 'normal' mode (if required)
- (windowState == 'normal') && chrome.app.window.current().restore();
- });
+ // restore main window from 'fullscreen' to 'normal' mode (if required)
+ (windowState == 'normal') && chrome.app.window.current().restore();
+ });
- } else if(msg.data === 'Connection Established') {
- $('#loadingSpinner').css('display', 'none');
- $('body').css('backgroundColor', 'black');
+ } else if (msg.data === 'Connection Established') {
+ $('#loadingSpinner').css('display', 'none');
+ $('body').css('backgroundColor', 'black');
- // Keep the display awake while streaming
- chrome.power.requestKeepAwake("display");
- } else if(msg.data.indexOf('ProgressMsg: ') === 0) {
- $('#loadingMessage').text(msg.data.replace('ProgressMsg: ', ''));
- } else if(msg.data.indexOf('TransientMsg: ') === 0) {
- snackbarLog(msg.data.replace('TransientMsg: ', ''));
- } else if(msg.data.indexOf('DialogMsg: ') === 0) {
- // FIXME: Really use a dialog
- snackbarLogLong(msg.data.replace('DialogMsg: ', ''));
- } else if(msg.data === 'displayVideo') {
- $("#listener").addClass("fullscreen");
- }
+ // Keep the display awake while streaming
+ chrome.power.requestKeepAwake("display");
+ } else if (msg.data.indexOf('ProgressMsg: ') === 0) {
+ $('#loadingMessage').text(msg.data.replace('ProgressMsg: ', ''));
+ } else if (msg.data.indexOf('TransientMsg: ') === 0) {
+ snackbarLog(msg.data.replace('TransientMsg: ', ''));
+ } else if (msg.data.indexOf('DialogMsg: ') === 0) {
+ // FIXME: Really use a dialog
+ snackbarLogLong(msg.data.replace('DialogMsg: ', ''));
+ } else if (msg.data === 'displayVideo') {
+ $("#listener").addClass("fullscreen");
}
+ }
}
diff --git a/static/js/utils.js b/static/js/utils.js
index e29022a..e0d3f83 100644
--- a/static/js/utils.js
+++ b/static/js/utils.js
@@ -1,468 +1,471 @@
function guuid() {
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
- var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
- return v.toString(16);
- });
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ var r = Math.random() * 16 | 0,
+ v = c == 'x' ? r : (r & 0x3 | 0x8);
+ return v.toString(16);
+ });
}
function uniqueid() {
- return 'xxxxxxxxxxxxxxxx'.replace(/[x]/g, function(c) {
- var r = Math.random()*16|0;
- return r.toString(16);
- });
+ return 'xxxxxxxxxxxxxxxx'.replace(/[x]/g, function(c) {
+ var r = Math.random() * 16 | 0;
+ return r.toString(16);
+ });
}
function generateRemoteInputKey() {
- return 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'.replace(/[x]/g, function(c) {
- var r = Math.random()*16|0;
- return r.toString(16);
- });
+ return 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'.replace(/[x]/g, function(c) {
+ var r = Math.random() * 16 | 0;
+ return r.toString(16);
+ });
}
function generateRemoteInputKeyId() {
- return ((Math.random()-0.5) * 0x7FFFFFFF)|0;
+ return ((Math.random() - 0.5) * 0x7FFFFFFF) | 0;
}
function getConnectedGamepadMask() {
- var count = 0;
- var mask = 0;
- var gamepads = navigator.getGamepads ? navigator.getGamepads() : [];
+ var count = 0;
+ var mask = 0;
+ var gamepads = navigator.getGamepads ? navigator.getGamepads() : [];
- for (var i = 0; i < gamepads.length; i++) {
- var gamepad = gamepads[i];
- if (gamepad) {
- // See logic in gamepad.cpp
- // These must stay in sync!
+ for (var i = 0; i < gamepads.length; i++) {
+ var gamepad = gamepads[i];
+ if (gamepad) {
+ // See logic in gamepad.cpp
+ // These must stay in sync!
- if (!gamepad.connected) {
- // Not connected
- continue;
- }
+ if (!gamepad.connected) {
+ // Not connected
+ continue;
+ }
- if (gamepad.timestamp == 0) {
- // On some platforms, Chrome returns "connected" pads that
- // really aren't, so timestamp stays at zero. To work around this,
- // we'll only count gamepads that have a non-zero timestamp in our
- // controller index.
- continue;
- }
+ if (gamepad.timestamp == 0) {
+ // On some platforms, Chrome returns "connected" pads that
+ // really aren't, so timestamp stays at zero. To work around this,
+ // we'll only count gamepads that have a non-zero timestamp in our
+ // controller index.
+ continue;
+ }
- mask |= 1 << count++;
- }
+ mask |= 1 << count++;
}
+ }
- console.log('%c[utils.js, getConnectedGamepadMask]', 'color:gray;', 'Detected '+count+' gamepads');
- return mask;
+ console.log('%c[utils.js, getConnectedGamepadMask]', 'color:gray;', 'Detected ' + count + ' gamepads');
+ return mask;
}
String.prototype.toHex = function() {
- var hex = '';
- for(var i = 0; i < this.length; i++) {
- hex += '' + this.charCodeAt(i).toString(16);
- }
- return hex;
+ var hex = '';
+ for (var i = 0; i < this.length; i++) {
+ hex += '' + this.charCodeAt(i).toString(16);
+ }
+ return hex;
}
function NvHTTP(address, clientUid, userEnteredAddress = '') {
- console.log('%c[utils.js, NvHTTP Object]', 'color: gray;', this);
- this.address = address;
- this.paired = false;
- this.currentGame = 0;
- this.serverMajorVersion = 0;
- this.appVersion = '';
- this.clientUid = clientUid;
- this._pollCount = 0;
- this._consecutivePollFailures = 0;
- this.online = false;
+ console.log('%c[utils.js, NvHTTP Object]', 'color: gray;', this);
+ this.address = address;
+ this.paired = false;
+ this.currentGame = 0;
+ this.serverMajorVersion = 0;
+ this.appVersion = '';
+ this.clientUid = clientUid;
+ this._pollCount = 0;
+ this._consecutivePollFailures = 0;
+ this.online = false;
- this.userEnteredAddress = userEnteredAddress; // if the user entered an address, we keep it on hand to try when polling
- this.serverUid = '';
- this.GfeVersion = '';
- this.supportedDisplayModes = {}; // key: y-resolution:x-resolution, value: array of supported framerates (only ever seen 30 or 60, here)
- this.gputype = '';
- this.numofapps = 0;
- this.hostname = address;
- this.externalIP = '';
- this._pollCompletionCallbacks = [];
+ this.userEnteredAddress = userEnteredAddress; // if the user entered an address, we keep it on hand to try when polling
+ this.serverUid = '';
+ this.GfeVersion = '';
+ this.supportedDisplayModes = {}; // key: y-resolution:x-resolution, value: array of supported framerates (only ever seen 30 or 60, here)
+ this.gputype = '';
+ this.numofapps = 0;
+ this.hostname = address;
+ this.externalIP = '';
+ this._pollCompletionCallbacks = [];
- _self = this;
+ _self = this;
};
-function _arrayBufferToBase64( buffer ) {
- var binary = '';
- var bytes = new Uint8Array( buffer );
- var len = bytes.byteLength;
- for (var i = 0; i < len; i++) {
- binary += String.fromCharCode( bytes[ i ] );
- }
- return window.btoa( binary );
+function _arrayBufferToBase64(buffer) {
+ var binary = '';
+ var bytes = new Uint8Array(buffer);
+ var len = bytes.byteLength;
+ for (var i = 0; i < len; i++) {
+ binary += String.fromCharCode(bytes[i]);
+ }
+ return window.btoa(binary);
}
function _base64ToArrayBuffer(base64) {
- var binary_string = window.atob(base64);
- var len = binary_string.length;
- var bytes = new Uint8Array( len );
- for (var i = 0; i < len; i++) {
- bytes[i] = binary_string.charCodeAt(i);
- }
- return bytes.buffer;
+ var binary_string = window.atob(base64);
+ var len = binary_string.length;
+ var bytes = new Uint8Array(len);
+ for (var i = 0; i < len; i++) {
+ bytes[i] = binary_string.charCodeAt(i);
+ }
+ return bytes.buffer;
}
NvHTTP.prototype = {
- refreshServerInfo: function () {
- // try HTTPS first
- return sendMessage('openUrl', [ this._baseUrlHttps + '/serverinfo?' + this._buildUidStr(), false]).then(function(ret) {
- if (!this._parseServerInfo(ret)) { // if that fails
- // try HTTP as a failover. Useful to clients who aren't paired yet
- return sendMessage('openUrl', [ this._baseUrlHttp + '/serverinfo?' + this._buildUidStr(), false]).then(function(retHttp) {
- this._parseServerInfo(retHttp);
- }.bind(this));
- }
+ refreshServerInfo: function() {
+ // try HTTPS first
+ return sendMessage('openUrl', [this._baseUrlHttps + '/serverinfo?' + this._buildUidStr(), false]).then(function(ret) {
+ if (!this._parseServerInfo(ret)) { // if that fails
+ // try HTTP as a failover. Useful to clients who aren't paired yet
+ return sendMessage('openUrl', [this._baseUrlHttp + '/serverinfo?' + this._buildUidStr(), false]).then(function(retHttp) {
+ this._parseServerInfo(retHttp);
}.bind(this));
- },
+ }
+ }.bind(this));
+ },
- // refreshes the server info using a given address. This is useful for testing whether we can successfully ping a host at a given address
- refreshServerInfoAtAddress: function(givenAddress) {
- // try HTTPS first
- return sendMessage('openUrl', [ 'https://' + givenAddress + ':47984' + '/serverinfo?' + this._buildUidStr(), false]).then(function(ret) {
- if (!this._parseServerInfo(ret)) { // if that fails
- console.log('%c[utils.js, utils.js, refreshServerInfoAtAddress]', 'color: gray;', 'Failed to parse serverinfo from HTTPS, falling back to HTTP');
- // try HTTP as a failover. Useful to clients who aren't paired yet
- return sendMessage('openUrl', [ 'http://' + givenAddress + ':47989' + '/serverinfo?' + this._buildUidStr(), false]).then(function(retHttp) {
- return this._parseServerInfo(retHttp);
- }.bind(this));
- }
+ // refreshes the server info using a given address. This is useful for testing whether we can successfully ping a host at a given address
+ refreshServerInfoAtAddress: function(givenAddress) {
+ // try HTTPS first
+ return sendMessage('openUrl', ['https://' + givenAddress + ':47984' + '/serverinfo?' + this._buildUidStr(), false]).then(function(ret) {
+ if (!this._parseServerInfo(ret)) { // if that fails
+ console.log('%c[utils.js, utils.js, refreshServerInfoAtAddress]', 'color: gray;', 'Failed to parse serverinfo from HTTPS, falling back to HTTP');
+ // try HTTP as a failover. Useful to clients who aren't paired yet
+ return sendMessage('openUrl', ['http://' + givenAddress + ':47989' + '/serverinfo?' + this._buildUidStr(), false]).then(function(retHttp) {
+ return this._parseServerInfo(retHttp);
}.bind(this));
- },
+ }
+ }.bind(this));
+ },
- // called every few seconds to poll the server for updated info
- pollServer: function(onComplete) {
- // Pend this callback on completion
- this._pollCompletionCallbacks.push(onComplete);
+ // called every few seconds to poll the server for updated info
+ pollServer: function(onComplete) {
+ // Pend this callback on completion
+ this._pollCompletionCallbacks.push(onComplete);
- // Check if a poll was already in progress
- if (this._pollCompletionCallbacks.length > 1) {
- // Don't start another. The one in progress will
- // alert our caller too.
+ // Check if a poll was already in progress
+ if (this._pollCompletionCallbacks.length > 1) {
+ // Don't start another. The one in progress will
+ // alert our caller too.
+ return;
+ }
+
+ this.selectServerAddress(function(successfulAddress) {
+ // Successfully determined server address. Update base URL.
+ this.address = successfulAddress;
+ this._baseUrlHttps = 'https://' + successfulAddress + ':47984';
+ this._baseUrlHttp = 'http://' + successfulAddress + ':47989';
+
+ // Poll for the app list every 10 successful serverinfo polls.
+ // Not including the first one to avoid PCs taking a while to show
+ // as online initially
+ if (this._pollCount++ % 10 == 1) {
+ this.getAppListWithCacheFlush();
+ }
+
+ this._consecutivePollFailures = 0;
+ this.online = true;
+
+ // Call all pending completion callbacks
+ var completion;
+ while ((completion = this._pollCompletionCallbacks.pop())) {
+ completion(this);
+ }
+ }.bind(this), function() {
+ if (++this._consecutivePollFailures >= 2) {
+ this.online = false;
+ }
+
+ // Call all pending completion callbacks
+ var completion;
+ while ((completion = this._pollCompletionCallbacks.pop())) {
+ completion(this);
+ }
+ }.bind(this));
+ },
+
+ // initially pings the server to try and figure out if it's routable by any means.
+ selectServerAddress: function(onSuccess, onFailure) {
+ // TODO: Deduplicate the addresses
+ this.refreshServerInfoAtAddress(this.address).then(function(successPrevAddr) {
+ onSuccess(this.address);
+ }.bind(this), function(successPrevAddr) {
+ this.refreshServerInfoAtAddress(this.hostname + '.local').then(function(successLocal) {
+ onSuccess(this.hostname + '.local');
+ }.bind(this), function(failureLocal) {
+ this.refreshServerInfoAtAddress(this.externalIP).then(function(successExternal) {
+ onSuccess(this.externalIP);
+ }.bind(this), function(failureExternal) {
+ this.refreshServerInfoAtAddress(this.userEnteredAddress).then(function(successUserEntered) {
+ onSuccess(this.userEnteredAddress);
+ }.bind(this), function(failureUserEntered) {
+ console.warn('%c[utils.js, utils.js, selectServerAddress]', 'color: gray;', 'Failed to contact host ' + this.hostname, this);
+ onFailure();
+ }.bind(this));
+ }.bind(this));
+ }.bind(this));
+ }.bind(this));
+ },
+
+ toString: function() {
+ var string = '';
+ string += 'server address: ' + this.address + '\r\n';
+ string += 'server UID: ' + this.serverUid + '\r\n';
+ string += 'is paired: ' + this.paired + '\r\n';
+ string += 'current game: ' + this.currentGame + '\r\n';
+ string += 'server major version: ' + this.serverMajorVersion + '\r\n';
+ string += 'appversion: ' + this.appVersion + '\r\n';
+ string += 'GFE version: ' + this.GfeVersion + '\r\n';
+ string += 'gpu type: ' + this.gputype + '\r\n';
+ string += 'number of apps: ' + this.numofapps + '\r\n';
+ string += 'supported display modes: ' + '\r\n';
+ for (var displayMode in this.supportedDisplayModes) {
+ string += '\t' + displayMode + ': ' + this.supportedDisplayModes[displayMode] + '\r\n';
+ }
+ return string;
+ },
+
+ _parseServerInfo: function(xmlStr) {
+ $xml = this._parseXML(xmlStr);
+ $root = $xml.find('root');
+
+ if ($root.attr("status_code") != 200) {
+ return false;
+ }
+
+ if (this.serverUid != $root.find('uniqueid').text().trim() && this.serverUid != "") {
+ // if we received a UID that isn't the one we expected, fail.
+ return false;
+ }
+
+ console.log('%c[utils.js, _parseServerInfo]', 'color:gray;', 'Parsing server info:', $root);
+
+ this.paired = $root.find("PairStatus").text().trim() == 1;
+ this.currentGame = parseInt($root.find("currentgame").text().trim(), 10);
+ this.appVersion = $root.find("appversion").text().trim();
+ this.serverMajorVersion = parseInt(this.appVersion.substring(0, 1), 10);
+ this.serverUid = $root.find('uniqueid').text().trim();
+ this.hostname = $root.find('hostname').text().trim();
+ this.externalIP = $root.find('ExternalIP').text().trim();
+ try { // these aren't critical for functionality, and don't necessarily exist in older GFE versions.
+ this.GfeVersion = $root.find('GfeVersion').text().trim();
+ this.gputype = $root.find('gputype').text().trim();
+ this.numofapps = $root.find('numofapps').text().trim();
+ // now for the hard part: parsing the supported streaming
+ $root.find('DisplayMode').each(function(index, value) { // for each resolution:FPS object
+ var yres = parseInt($(value).find('Height').text());
+ var xres = parseInt($(value).find('Width').text());
+ var fps = parseInt($(value).find('RefreshRate').text());
+ if (!this.supportedDisplayModes[yres + ':' + xres]) {
+ this.supportedDisplayModes[yres + ':' + xres] = [];
+ }
+ if (!this.supportedDisplayModes[yres + ':' + xres].includes(fps)) {
+ this.supportedDisplayModes[yres + ':' + xres].push(fps);
+ }
+ }.bind(this));
+ } catch (err) {
+ // we don't need this data, so no error handling necessary
+ }
+
+
+ // GFE 2.8 started keeping currentgame set to the last game played. As a result, it no longer
+ // has the semantics that its name would indicate. To contain the effects of this change as much
+ // as possible, we'll force the current game to zero if the server isn't in a streaming session.
+ if (!$root.find("state").text().trim().endsWith("_SERVER_BUSY")) {
+ this.currentGame = 0;
+ }
+
+ return true;
+ },
+
+ getAppById: function(appId) {
+ return this.getAppList().then(function(list) {
+ var retApp = null;
+
+ list.some(function(app) {
+ if (app.id == appId) {
+ retApp = app;
+ return true;
+ }
+
+ return false;
+ });
+
+ return retApp;
+ });
+ },
+
+ getAppByName: function(appName) {
+ return this.getAppList().then(function(list) {
+ var retApp = null;
+
+ list.some(function(app) {
+ if (app.title == appName) {
+ retApp = app;
+ return true;
+ }
+
+ return false;
+ });
+
+ return retApp;
+ });
+ },
+
+ getAppListWithCacheFlush: function() {
+ return sendMessage('openUrl', [this._baseUrlHttps + '/applist?' + this._buildUidStr(), false]).then(function(ret) {
+ $xml = this._parseXML(ret);
+ $root = $xml.find("root");
+
+ if ($root.attr("status_code") != 200) {
+ // TODO: Bubble up an error here
+ console.error('%c[utils.js, utils.js, getAppListWithCacheFlush]', 'color: gray;', 'Applist request failed', $root.attr("status_code"));
+ return [];
+ }
+
+ var rootElement = $xml.find("root")[0];
+ var appElements = rootElement.getElementsByTagName("App");
+ var appList = [];
+
+ for (var i = 0, len = appElements.length; i < len; i++) {
+ appList.push({
+ title: appElements[i].getElementsByTagName("AppTitle")[0].innerHTML.trim(),
+ id: parseInt(appElements[i].getElementsByTagName("ID")[0].innerHTML.trim(), 10)
+ });
+ }
+
+ this._memCachedApplist = appList;
+
+ return appList;
+ }.bind(this));
+ },
+
+ getAppList: function() {
+ if (this._memCachedApplist) {
+ return new Promise(function(resolve, reject) {
+ console.log('%c[utils.js, utils.js]', 'color: gray;', 'Returning memory-cached apps list');
+ resolve(this._memCachedApplist);
+ return;
+ }.bind(this));
+ }
+
+ return this.getAppListWithCacheFlush();
+ },
+
+ // returns the box art of the given appID.
+ // three layers of response time are possible: memory cached (in javascript), storage cached (in chrome.storage.local), and streamed (host sends binary over the network)
+ getBoxArt: function(appId) {
+ if (chrome.storage) {
+ // This may be bad practice to push/pull this much data through local storage?
+ return new Promise(function(resolve, reject) {
+ chrome.storage.local.get('boxart-' + appId, function(storageData) {
+ // if we already have it, load it.
+ if (storageData !== undefined && Object.keys(storageData).length !== 0 && storageData['boxart-' + appId].constructor !== Object) {
+ console.log('%c[utils.js, getBoxArt]', 'color: gray;', 'Returning storage-cached box art for app: ', appId);
+ resolve(storageData['boxart-' + appId]);
return;
- }
+ }
- this.selectServerAddress(function(successfulAddress) {
- // Successfully determined server address. Update base URL.
- this.address = successfulAddress;
- this._baseUrlHttps = 'https://' + successfulAddress + ':47984';
- this._baseUrlHttp = 'http://' + successfulAddress + ':47989';
-
- // Poll for the app list every 10 successful serverinfo polls.
- // Not including the first one to avoid PCs taking a while to show
- // as online initially
- if (this._pollCount++ % 10 == 1) {
- this.getAppListWithCacheFlush();
- }
-
- this._consecutivePollFailures = 0;
- this.online = true;
-
- // Call all pending completion callbacks
- var completion;
- while ((completion = this._pollCompletionCallbacks.pop())) {
- completion(this);
- }
- }.bind(this), function() {
- if (++this._consecutivePollFailures >= 2) {
- this.online = false;
- }
-
- // Call all pending completion callbacks
- var completion;
- while ((completion = this._pollCompletionCallbacks.pop())) {
- completion(this);
- }
- }.bind(this));
- },
-
- // initially pings the server to try and figure out if it's routable by any means.
- selectServerAddress: function(onSuccess, onFailure) {
- // TODO: Deduplicate the addresses
- this.refreshServerInfoAtAddress(this.address).then(function(successPrevAddr) {
- onSuccess(this.address);
- }.bind(this), function(successPrevAddr) {
- this.refreshServerInfoAtAddress(this.hostname + '.local').then(function(successLocal) {
- onSuccess(this.hostname + '.local');
- }.bind(this), function(failureLocal) {
- this.refreshServerInfoAtAddress(this.externalIP).then(function(successExternal) {
- onSuccess(this.externalIP);
- }.bind(this), function(failureExternal) {
- this.refreshServerInfoAtAddress(this.userEnteredAddress).then(function(successUserEntered) {
- onSuccess(this.userEnteredAddress);
- }.bind(this), function(failureUserEntered) {
- console.warn('%c[utils.js, utils.js, selectServerAddress]', 'color: gray;', 'Failed to contact host ' + this.hostname, this);
- onFailure();
- }.bind(this));
- }.bind(this));
- }.bind(this));
- }.bind(this));
- },
-
- toString: function() {
- var string = '';
- string += 'server address: ' + this.address + '\r\n';
- string += 'server UID: ' + this.serverUid + '\r\n';
- string += 'is paired: ' + this.paired + '\r\n';
- string += 'current game: ' + this.currentGame + '\r\n';
- string += 'server major version: ' + this.serverMajorVersion + '\r\n';
- string += 'appversion: ' + this.appVersion + '\r\n';
- string += 'GFE version: ' + this.GfeVersion + '\r\n';
- string += 'gpu type: ' + this.gputype + '\r\n';
- string += 'number of apps: ' + this.numofapps + '\r\n';
- string += 'supported display modes: ' + '\r\n';
- for(var displayMode in this.supportedDisplayModes) {
- string += '\t' + displayMode + ': ' + this.supportedDisplayModes[displayMode] + '\r\n';
- }
- return string;
- },
-
- _parseServerInfo: function(xmlStr) {
- $xml = this._parseXML(xmlStr);
- $root = $xml.find('root');
-
- if($root.attr("status_code") != 200) {
- return false;
- }
-
- if(this.serverUid != $root.find('uniqueid').text().trim() && this.serverUid != "") {
- // if we received a UID that isn't the one we expected, fail.
- return false;
- }
-
- console.log('%c[utils.js, _parseServerInfo]', 'color:gray;', 'Parsing server info:', $root);
-
- this.paired = $root.find("PairStatus").text().trim() == 1;
- this.currentGame = parseInt($root.find("currentgame").text().trim(), 10);
- this.appVersion = $root.find("appversion").text().trim();
- this.serverMajorVersion = parseInt(this.appVersion.substring(0, 1), 10);
- this.serverUid = $root.find('uniqueid').text().trim();
- this.hostname = $root.find('hostname').text().trim();
- this.externalIP = $root.find('ExternalIP').text().trim();
- try { // these aren't critical for functionality, and don't necessarily exist in older GFE versions.
- this.GfeVersion = $root.find('GfeVersion').text().trim();
- this.gputype = $root.find('gputype').text().trim();
- this.numofapps = $root.find('numofapps').text().trim();
- // now for the hard part: parsing the supported streaming
- $root.find('DisplayMode').each(function(index, value) { // for each resolution:FPS object
- var yres = parseInt($(value).find('Height').text());
- var xres = parseInt($(value).find('Width').text());
- var fps = parseInt($(value).find('RefreshRate').text());
- if(!this.supportedDisplayModes[yres + ':' + xres]) {
- this.supportedDisplayModes[yres + ':' + xres] = [];
- }
- if(!this.supportedDisplayModes[yres + ':' + xres].includes(fps)) {
- this.supportedDisplayModes[yres + ':' + xres].push(fps);
- }
- }.bind(this));
- } catch (err) {
- // we don't need this data, so no error handling necessary
- }
-
-
- // GFE 2.8 started keeping currentgame set to the last game played. As a result, it no longer
- // has the semantics that its name would indicate. To contain the effects of this change as much
- // as possible, we'll force the current game to zero if the server isn't in a streaming session.
- if (!$root.find("state").text().trim().endsWith("_SERVER_BUSY")) {
- this.currentGame = 0;
- }
-
- return true;
- },
-
- getAppById: function (appId) {
- return this.getAppList().then(function (list) {
- var retApp = null;
-
- list.some(function (app) {
- if (app.id == appId) {
- retApp = app;
- return true;
- }
-
- return false;
- });
-
- return retApp;
- });
- },
-
- getAppByName: function (appName) {
- return this.getAppList().then(function (list) {
- var retApp = null;
-
- list.some(function (app) {
- if (app.title == appName) {
- retApp = app;
- return true;
- }
-
- return false;
- });
-
- return retApp;
- });
- },
-
- getAppListWithCacheFlush: function () {
- return sendMessage('openUrl', [this._baseUrlHttps + '/applist?' + this._buildUidStr(), false]).then(function (ret) {
- $xml = this._parseXML(ret);
- $root = $xml.find("root");
-
- if ($root.attr("status_code") != 200) {
- // TODO: Bubble up an error here
- console.error('%c[utils.js, utils.js, getAppListWithCacheFlush]', 'color: gray;', 'Applist request failed', $root.attr("status_code"));
- return [];
- }
-
- var rootElement = $xml.find("root")[0];
- var appElements = rootElement.getElementsByTagName("App");
- var appList = [];
-
- for (var i = 0, len = appElements.length; i < len; i++) {
- appList.push({
- title: appElements[i].getElementsByTagName("AppTitle")[0].innerHTML.trim(),
- id: parseInt(appElements[i].getElementsByTagName("ID")[0].innerHTML.trim(), 10)
- });
- }
-
- this._memCachedApplist = appList;
-
- return appList;
- }.bind(this));
- },
-
- getAppList: function () {
- if (this._memCachedApplist) {
- return new Promise(function (resolve, reject) {
- console.log('%c[utils.js, utils.js]', 'color: gray;', 'Returning memory-cached apps list');
- resolve(this._memCachedApplist);
- return;
- }.bind(this));
- }
-
- return this.getAppListWithCacheFlush();
- },
-
- // returns the box art of the given appID.
- // three layers of response time are possible: memory cached (in javascript), storage cached (in chrome.storage.local), and streamed (host sends binary over the network)
- getBoxArt: function (appId) {
- if (chrome.storage) {
- // This may be bad practice to push/pull this much data through local storage?
- return new Promise(function (resolve, reject) {
- chrome.storage.local.get('boxart-'+appId, function(storageData) {
- // if we already have it, load it.
- if (storageData !== undefined && Object.keys(storageData).length !== 0 && storageData['boxart-'+appId].constructor !== Object) {
- console.log('%c[utils.js, getBoxArt]', 'color: gray;', 'Returning storage-cached box art for app: ', appId);
- resolve(storageData['boxart-'+appId]);
- return;
- }
-
- // otherwise, put it in our cache, then return it
- sendMessage('openUrl', [
- this._baseUrlHttps +
- '/appasset?'+this._buildUidStr() +
- '&appid=' + appId +
- '&AssetType=2&AssetIdx=0',
- true
- ]).then(function(boxArtBuffer) {
- var reader = new FileReader();
- reader.onloadend = function() {
- var obj = {};
- obj['boxart-'+appId] = this.result;
- chrome.storage.local.set(obj, function(onSuccess) {});
- console.log('%c[utils.js, utils.js, getBoxArt]', 'color: gray;', 'Returning network-fetched box art');
- resolve(this.result);
- }
- reader.readAsDataURL(new Blob([boxArtBuffer], {type: "image/png"}));
- }.bind(this), function(error) {
- console.error('%c[utils.js, utils.js, getBoxArt]', 'color: gray;', 'Box-art request failed!', error);
- reject(error);
- return;
- }.bind(this));
- }.bind(this));
- }.bind(this));
-
- } else { // shouldn't run because we always have chrome.storage, but I'm not going to antagonize other browsers
- console.warn('%c[utils.js, utils.js, getBoxArt]', 'color: gray;', 'chrome.storage not detected! Box art will not be saved!');
- return sendMessage('openUrl', [
- this._baseUrlHttps +
- '/appasset?'+this._buildUidStr() +
- '&appid=' + appId +
- '&AssetType=2&AssetIdx=0',
- true
- ]);
- }
- },
-
- launchApp: function (appId, mode, sops, rikey, rikeyid, localAudio, surroundAudioInfo, gamepadMask) {
- return sendMessage('openUrl', [
+ // otherwise, put it in our cache, then return it
+ sendMessage('openUrl', [
this._baseUrlHttps +
- '/launch?' + this._buildUidStr() +
+ '/appasset?' + this._buildUidStr() +
'&appid=' + appId +
- '&mode=' + mode +
- '&additionalStates=1&sops=' + sops +
- '&rikey=' + rikey +
- '&rikeyid=' + rikeyid +
- '&localAudioPlayMode=' + localAudio +
- '&surroundAudioInfo=' + surroundAudioInfo +
- '&remoteControllersBitmap=' + gamepadMask +
- '&gcmap=' + gamepadMask,
- false
- ]).then(function (ret) {
- return true;
- });
- },
-
- resumeApp: function (rikey, rikeyid, surroundAudioInfo) {
- return sendMessage('openUrl', [
- this._baseUrlHttps +
- '/resume?' + this._buildUidStr() +
- '&rikey=' + rikey +
- '&rikeyid=' + rikeyid +
- '&surroundAudioInfo=' + surroundAudioInfo,
- false
- ]).then(function (ret) {
- return true;
- });
- },
-
- quitApp: function () {
- return sendMessage('openUrl', [this._baseUrlHttps + '/cancel?' + this._buildUidStr(), false])
- // Refresh server info after quitting because it may silently fail if the
- // session belongs to a different client.
- // TODO: We should probably bubble this up to our caller.
- .then(this.refreshServerInfo());
- },
-
- pair: function(randomNumber) {
- return this.refreshServerInfo().then(function () {
- if (this.paired)
- return true;
-
- if (this.currentGame != 0)
- return false;
-
- return sendMessage('pair', [this.serverMajorVersion.toString(), this.address, randomNumber]).then(function (pairStatus) {
- return sendMessage('openUrl', [this._baseUrlHttps + '/pair?uniqueid=' + this.clientUid + '&devicename=roth&updateState=1&phrase=pairchallenge', false]).then(function (ret) {
- $xml = this._parseXML(ret);
- this.paired = $xml.find('paired').html() == "1";
- return this.paired;
- }.bind(this));
- }.bind(this));
+ '&AssetType=2&AssetIdx=0',
+ true
+ ]).then(function(boxArtBuffer) {
+ var reader = new FileReader();
+ reader.onloadend = function() {
+ var obj = {};
+ obj['boxart-' + appId] = this.result;
+ chrome.storage.local.set(obj, function(onSuccess) {});
+ console.log('%c[utils.js, utils.js, getBoxArt]', 'color: gray;', 'Returning network-fetched box art');
+ resolve(this.result);
+ }
+ reader.readAsDataURL(new Blob([boxArtBuffer], {
+ type: "image/png"
+ }));
+ }.bind(this), function(error) {
+ console.error('%c[utils.js, utils.js, getBoxArt]', 'color: gray;', 'Box-art request failed!', error);
+ reject(error);
+ return;
+ }.bind(this));
}.bind(this));
- },
+ }.bind(this));
- _buildUidStr: function () {
- return 'uniqueid=' + this.clientUid + '&uuid=' + guuid();
- },
+ } else { // shouldn't run because we always have chrome.storage, but I'm not going to antagonize other browsers
+ console.warn('%c[utils.js, utils.js, getBoxArt]', 'color: gray;', 'chrome.storage not detected! Box art will not be saved!');
+ return sendMessage('openUrl', [
+ this._baseUrlHttps +
+ '/appasset?' + this._buildUidStr() +
+ '&appid=' + appId +
+ '&AssetType=2&AssetIdx=0',
+ true
+ ]);
+ }
+ },
- _parseXML: function (xmlData) {
- return $($.parseXML(xmlData.toString()));
- },
+ launchApp: function(appId, mode, sops, rikey, rikeyid, localAudio, surroundAudioInfo, gamepadMask) {
+ return sendMessage('openUrl', [
+ this._baseUrlHttps +
+ '/launch?' + this._buildUidStr() +
+ '&appid=' + appId +
+ '&mode=' + mode +
+ '&additionalStates=1&sops=' + sops +
+ '&rikey=' + rikey +
+ '&rikeyid=' + rikeyid +
+ '&localAudioPlayMode=' + localAudio +
+ '&surroundAudioInfo=' + surroundAudioInfo +
+ '&remoteControllersBitmap=' + gamepadMask +
+ '&gcmap=' + gamepadMask,
+ false
+ ]).then(function(ret) {
+ return true;
+ });
+ },
+
+ resumeApp: function(rikey, rikeyid, surroundAudioInfo) {
+ return sendMessage('openUrl', [
+ this._baseUrlHttps +
+ '/resume?' + this._buildUidStr() +
+ '&rikey=' + rikey +
+ '&rikeyid=' + rikeyid +
+ '&surroundAudioInfo=' + surroundAudioInfo,
+ false
+ ]).then(function(ret) {
+ return true;
+ });
+ },
+
+ quitApp: function() {
+ return sendMessage('openUrl', [this._baseUrlHttps + '/cancel?' + this._buildUidStr(), false])
+ // Refresh server info after quitting because it may silently fail if the
+ // session belongs to a different client.
+ // TODO: We should probably bubble this up to our caller.
+ .then(this.refreshServerInfo());
+ },
+
+ pair: function(randomNumber) {
+ return this.refreshServerInfo().then(function() {
+ if (this.paired)
+ return true;
+
+ if (this.currentGame != 0)
+ return false;
+
+ return sendMessage('pair', [this.serverMajorVersion.toString(), this.address, randomNumber]).then(function(pairStatus) {
+ return sendMessage('openUrl', [this._baseUrlHttps + '/pair?uniqueid=' + this.clientUid + '&devicename=roth&updateState=1&phrase=pairchallenge', false]).then(function(ret) {
+ $xml = this._parseXML(ret);
+ this.paired = $xml.find('paired').html() == "1";
+ return this.paired;
+ }.bind(this));
+ }.bind(this));
+ }.bind(this));
+ },
+
+ _buildUidStr: function() {
+ return 'uniqueid=' + this.clientUid + '&uuid=' + guuid();
+ },
+
+ _parseXML: function(xmlData) {
+ return $($.parseXML(xmlData.toString()));
+ },
};