Merge pull request #77 from abdallahsoliman/master

more polishes to grid-UI
This commit is contained in:
R. Aidan Campbell 2016-07-24 07:57:29 -04:00 committed by GitHub
commit 6ccf313761
6 changed files with 141 additions and 76 deletions

View File

@ -34,15 +34,20 @@
<option value="30">30 FPS</option>
<option value="60">60 FPS</option>
</select>
<output id='bitrateField'>10 Mbps</output>
</div>
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect" for="remoteAudioEnabledSwitch" id="remoteAudioEnabledSwitchContainer">
<input type="checkbox" id="remoteAudioEnabledSwitch" class="mdl-switch__input" checked>
<span class="mdl-switch__label">Remote audio off/on</span>
</label>
<output id='bitrateField'>10 Mbps</output>
<input id="bitrateSlider" class="mdl-slider mdl-js-slider" type="range" min="0" max="100" step="0.5" value="10">
</div>
<div id="hostSettings">
<div class="mdl-grid" id='host-grid'>
<div class='mdl-cell mdl-cell--3-col' id='addHostCell'>
<div class='mdl-cell mdl-cell--3-col host-cell mdl-button mdl-js-button mdl-js-ripple-effect' id='addHostCell'>
<img src="static/res/ic_add_circle_white_24px.svg" id='addHostIcon'></img>
Add Host
</div>
</div>
</div>

View File

@ -1,36 +1,3 @@
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: local('Material Icons'),
local('MaterialIcons-Regular'),
url('../fonts/MaterialIcons-Regular.woff') format('woff'),
}
.material-icons {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: 24px; /* Preferred icon size */
display: inline-block;
line-height: 1;
text-transform: none;
letter-spacing: normal;
word-wrap: normal;
white-space: nowrap;
direction: ltr;
/* Support for all WebKit browsers. */
-webkit-font-smoothing: antialiased;
/* Support for Safari and Chrome. */
text-rendering: optimizeLegibility;
/* Support for Firefox. */
-moz-osx-font-smoothing: grayscale;
/* Support for IE. */
font-feature-settings: 'liga';
}
.mdl-layout__header-row {
color: #fff;
}
@ -46,21 +13,14 @@
color:#fff;
padding:25px;
}
#addHostIcon {
width:64px;
height:64px;
margin:auto;
display:block;
}
#backIcon {
height: 32px;
width: 32px;
margin-left: -20px;
padding-right: 20px;
}
#addHostCell {
padding-top:3px;
padding-bottom:3px;
#backIcon:hover {
cursor: pointer;
}
.mdl-dialog {
border: none;
@ -173,6 +133,12 @@ main {
margin: 0;
width: 100%;
height: 100%;
-webkit-box-shadow: 10px 10px 5px 0px rgba(0,0,0,0.34);
-moz-box-shadow: 10px 10px 5px 0px rgba(0,0,0,0.34);
box-shadow: 10px 10px 5px 0px rgba(0,0,0,0.34);
}
.box-art:hover {
cursor: pointer;
}
.current-game {
border: 3px solid green;
@ -184,3 +150,27 @@ main {
color: #000;
font-weight: bold;
}
.host-cell {
display: inline-block;
width: 150px !important;
height: 150px !important;
background-color: #000;
-webkit-box-shadow: 10px 10px 5px 0px rgba(0,0,0,0.34);
-moz-box-shadow: 10px 10px 5px 0px rgba(0,0,0,0.34);
box-shadow: 10px 10px 5px 0px rgba(0,0,0,0.34);
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
}
.host-cell:hover {
cursor: pointer;
color: #000 !important;
}
.host-cell:hover img {
-webkit-filter: invert(100%);
filter: invert(100%);
}
.host-cell img {
width: 80px;;
margin-bottom: 10px;
}

View File

@ -1,4 +1,7 @@
var host = "";
// CURRENT ISSUE: host is not being saved. or it may have not been saved, and my state is screwed up.
// if (given host not in hosts) hosts.append(given host);
var _host = "";
var hosts = [];
var pairingCert;
var myUniqueid;
@ -13,6 +16,7 @@ function attachListeners() {
$('#selectFramerate').on('change', saveFramerate);
$('#bitrateSlider').on('input', updateBitrateField); // input occurs every notch you slide
$('#bitrateSlider').on('change', saveBitrate); // change occurs once the mouse lets go.
$("#remoteAudioEnabledSwitch").on('click', saveRemoteAudio);
$('#hostChosen').on('click', hostChosen);
$('#addHostCell').on('click', addHost);
$('#cancelAddHost').on('click', cancelAddHost);
@ -149,14 +153,19 @@ function pairTo(host, onSuccess, onFailure) {
function hostChosen(sourceEvent) {
if(sourceEvent && sourceEvent.srcElement) {
if (sourceEvent.srcElement.innerText == "") {
console.log('user clicked image. we gotta hack to parse out the host.');
host = sourceEvent.currentTarget.childNodes[1].textContent;
} else {
console.log('parsing host from grid element.');
host = sourceEvent.srcElement.innerText;
}
}
api = new NvHTTP(host, myUniqueid);
api.refreshServerInfo().then(function (ret) {
if(!api.paired) {
pairTo(host, function(){ showApps(); }, function(){});
pairTo(host, function(){ showApps(); saveHosts(); }, function(){});
} else {
showApps();
}
@ -178,15 +187,15 @@ function cancelAddHost() {
}
function addHostToGrid(host) {
if(hosts.indexOf(host) < 0) { // we don't have this host in our list. add it, and save it.
var cell = document.createElement('div');
cell.className += 'mdl-cell mdl-cell--3-col';
cell.className += 'mdl-cell mdl-cell--3-col host-cell mdl-button mdl-js-button mdl-js-ripple-effect';
cell.id = 'hostgrid-' + host;
cell.innerHTML = host;
$(cell).prepend($("<img>", {src: "static/res/ic_desktop_windows_white_24px.svg"}));
$('#host-grid').append(cell);
cell.onclick = hostChosen;
if(hosts.indexOf(host) < 0) {
hosts.push(host);
saveHosts();
}
}
@ -196,6 +205,7 @@ function continueAddHost() {
pairTo(inputHost,
function() {
addHostToGrid(inputHost);
saveHosts();
document.querySelector('#addHostDialog').close();
},
function() {
@ -218,6 +228,25 @@ function pairingPopupCanceled() {
document.querySelector('#pairingDialog').close();
}
// puts the CSS style for current app on the app that's currently running
// and puts the CSS style for non-current app apps that aren't running
// this requires a hot-off-the-host `api`, and the appId we're going to stylize
// 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');
}
}
// show the app list
function showApps() {
if(!api || !api.paired) { // safety checking. shouldn't happen.
@ -231,21 +260,13 @@ function showApps() {
api.getAppList().then(function (appList) {
appList.forEach(function (app) {
api.getBoxArt(app.id).then(function (resolvedPromise) {
// put the box art into the image holder
var imageBlob = new Blob([resolvedPromise], {type: "image/png"});
$("#game-grid").append($("<div>", {html:$("<img \>", {src: URL.createObjectURL(imageBlob), id: 'game-'+app.id, name: app.title }), class: 'box-art mdl-cell mdl-cell--3-col'}).append($("<span>", {html: app.title, class:"game-title"})));
$('#game-'+app.id).on('click', startGame);
if (api.currentGame === app.id){ // stylize the currently running game
// destylize it, if it has the not-current-game style
if ($('#game-'+ app.id).hasClass("not-current-game")) $('#game-'+ app.id).removeClass("not-current-game");
// add the current-game style
$('#game-'+ app.id).addClass("current-game");
} else {
// destylize it, if it has the current-game style
if ($('#game-'+ app.id).hasClass("current-game")) $('#game-'+ app.id).removeClass("current-game");
// add the not-current-game style
$('#game-'+ app.id).addClass('not-current-game');
}
// apply CSS stylization to indicate whether the app is active
stylizeBoxArt(api, app.id);
}, function (failedPromise) {
console.log('Error! Failed to retrieve box art for app ID: ' + app.id + '. Returned value was: ' + failedPromise)
@ -349,6 +370,8 @@ function startGame(sourceEvent) {
});
}
remote_audio_enabled = $("#remoteAudioEnabledSwitch").parent().hasClass('is-checked') ? 1 : 0;
api.launchApp(appID,
streamWidth + "x" + streamHeight + "x" + frameRate,
1, // Allow GFE to optimize game settings
@ -477,6 +500,14 @@ function saveBitrate() {
storeData('bitrate', $('#bitrateSlider').val(), null);
}
function saveRemoteAudio() {
console.log('saving remote audio state');
// problem: when off, and the app is just starting, a tick to the switch doesn't always toggle it
// second problem: this callback is called immediately after clicking, so the HTML class `is-checked` isn't toggled yet
// to solve the second problem, we invert the boolean. This has worked in all cases I've tried, except for the first case
storeData('remoteAudio', !$("#remoteAudioEnabledSwitch").parent().hasClass('is-checked'), null);
}
function updateDefaultBitrate() {
var res = $('#selectResolution').val();
var frameRate = $('#selectFramerate').val();
@ -514,6 +545,16 @@ function onWindowLoad(){
chrome.storage.sync.get('resolution', function(previousValue) {
$('#selectResolution').val(previousValue.resolution != null ? previousValue.resolution : '1280:720');
});
chrome.storage.sync.get('remoteAudio', function(previousValue) {
if(previousValue.remoteAudio == null) {
document.querySelector('#remoteAudioEnabledSwitchContainer').MaterialSwitch.off();
return;
} else if(previousValue.remoteAudio == false) {
document.querySelector('#remoteAudioEnabledSwitchContainer').MaterialSwitch.off();
} else {
document.querySelector('#remoteAudioEnabledSwitchContainer').MaterialSwitch.on();
}
});
// load stored framerate prefs
chrome.storage.sync.get('frameRate', function(previousValue) {
$('#selectFramerate').val(previousValue.frameRate != null ? previousValue.frameRate : '60');
@ -522,12 +563,7 @@ function onWindowLoad(){
chrome.storage.sync.get('hosts', function(previousValue) {
hosts = previousValue.hosts != null ? previousValue.hosts : [];
for(var i = 0; i < hosts.length; i++) { // programmatically add each new host.
var cell = document.createElement('div');
cell.className += 'mdl-cell mdl-cell--3-col';
cell.id = 'hostgrid-' + hosts[i];
cell.innerHTML = hosts[i];
$('#host-grid').append(cell);
cell.onclick = hostChosen;
addHostToGrid(hosts[i]);
}
});

View File

@ -22,10 +22,17 @@ function handleMessage(msg) {
console.log(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.
api.refreshServerInfo().then(function (ret) {
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();
chrome.app.window.current().restore();
});
} else if(msg.data === 'Connection Established') {
$('#loadingSpinner').css('display', 'none');
} else if(msg.data.indexOf('ProgressMsg: ') === 0) {

View File

@ -41,6 +41,12 @@ function NvHTTP(address, clientUid) {
this._baseUrlHttps = 'https://' + address + ':47984';
this._baseUrlHttp = 'http://' + address + ':47989';
this._memCachedBoxArtArray = {};
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;
_self = this;
};
@ -86,6 +92,23 @@ NvHTTP.prototype = {
_self.paired = $root.find("PairStatus").text().trim() == 1;
_self.currentGame = parseInt($root.find("currentgame").text().trim(), 10);
_self.serverMajorVersion = parseInt($root.find("appversion").text().trim().substring(0, 1), 10);
_self.serverUid = $root.find('uniqueid').text().trim();
_self.GfeVersion = $root.find('GfeVersion').text().trim();
_self.gputype = $root.find('gputype').text().trim();
_self.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(!_self.supportedDisplayModes[yres + ':' + xres]) {
_self.supportedDisplayModes[yres + ':' + xres] = [];
}
if(!_self.supportedDisplayModes[yres + ':' + xres].includes(fps)) {
_self.supportedDisplayModes[yres + ':' + xres].push(fps);
}
});
// 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

View File

@ -0,0 +1,4 @@
<svg fill="#FFFFFF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M21 2H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h7v2H8v2h8v-2h-2v-2h7c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H3V4h18v12z"/>
</svg>

After

Width:  |  Height:  |  Size: 274 B