Connected the wires between Snapcast backend and frontend

This commit is contained in:
Fabio Manganiello 2019-01-08 01:47:16 +01:00
parent a346442246
commit fd8c3bb846
4 changed files with 398 additions and 162 deletions

View file

@ -1,5 +1,6 @@
.snapcast-host-container {
min-width: 40em;
max-width: 80em;
margin: 1em auto;
background: rgba(245,245,245,0.6);
border: 1px solid rgba(220,220,220,1.0);
@ -9,26 +10,113 @@
.snapcast-host-header {
border-bottom: 1px solid rgba(220,220,220,1.0);
font-size: 1em;
text-transform: uppercase;
padding: 0 .5em;
}
.snapcast-host-header h1 {
font-size: 1.9em;
}
.snapcast-group-header {
padding: 0.5em;
padding: .5em;
margin: 0 1.4em 1.8em .2em;
border-bottom: 1px solid #e8e8e8;
}
.snapcast-group-settings {
text-align: center;
.snapcast-settings-btn {
cursor: pointer;
padding: .2em;
}
.snapcast-settings-btn:hover {
border: .05em solid #e0e0e0;
padding: .15em;
}
.snapcast-group-header h2 {
font-size: 1.5em;
margin-top: .6em;
}
.snapcast-client-container {
padding: 0 1.4em;
.snapcast-client-disconnected {
color: rgba(0, 0, 0, 0.35);
}
.snapcast-client-header h3 {
.snapcast-client-row {
padding: 0 1.4em;
margin: 1.5em .5em;
}
.snapcast-client-row h3 {
font-size: 1.2em;
}
.snapcast-client-mute-toggle {
margin-top: -1.2em;
}
.snapcast-form {
margin-bottom: 1rem;
}
.snapcast-form * > input[type=text] {
width: 100%;
}
.snapcast-form > .row {
padding: 0.5rem;
}
.snapcast-form-bottom {
text-align: right;
margin-top: 2rem;
border-top: 1px solid #ddd;
}
.snapcast-form-bottom input {
margin-top: 2rem;
}
.snapcast-form * > label {
transform: translateY(25%);
}
.snapcast-form > .snapcast-client-info {
max-width: 70%;
margin: 1em auto .5em auto;
padding: 2em;
background: rgba(240,240,240,0.6);
border-radius: 10px;
border: .05em solid rgba(225,225,225,1.0)
}
.snapcast-form > .snapcast-client-info > .row {
padding: .2em;
}
.snapcast-form > .snapcast-client-info > .row:hover {
background-color: #daf8e2 !important;
}
.snapcast-form > .snapcast-client-info * > .info-name {
font-weight: bold;
}
.snapcast-form > .snapcast-client-info * > .info-value {
text-align: right;
}
.snapcast-form > .snapcast-client-delete {
width: 30%;
margin: 1em auto 0 auto;
text-align: center;
}
.snapcast-form > .snapcast-client-delete > label {
display: inline;
margin-left: .5em;
color: rgba(200, 44, 23, 1.0);
text-align: right;
}

View file

@ -1,5 +1,6 @@
$(document).ready(function() {
var statuses = [],
var serverInfo = {},
clientInfo = {},
$container = $('#snapcast-container');
var createPowerToggleElement = function(data) {
@ -25,19 +26,64 @@ $(document).ready(function() {
return $powerToggle;
};
var onEvent = function(event) {
switch (event.args.type) {
case 'platypush.message.event.music.snapcast.ClientConnectedEvent':
case 'platypush.message.event.music.snapcast.ClientDisconnectedEvent':
case 'platypush.message.event.music.snapcast.ClientNameChangeEvent':
case 'platypush.message.event.music.snapcast.GroupStreamChangeEvent':
case 'platypush.message.event.music.snapcast.ServerUpdateEvent':
redraw();
break;
case 'platypush.message.event.music.snapcast.ClientVolumeChangeEvent':
var $host = $($container.find('.snapcast-host-container').filter(
(i, hostDiv) => $(hostDiv).data('host') === event.args.host
));
var $client = $($host.find('.snapcast-client-container').filter(
(i, clientDiv) => $(clientDiv).data('id') === event.args.client
));
$client.find('.snapcast-volume-slider').val(event.args.volume);
$client.find('.snapcast-mute-toggle').find('input.toggle--checkbox')
.prop('checked', !event.args.muted);
break;
case 'platypush.message.event.music.snapcast.GroupMuteChangeEvent':
var $host = $($container.find('.snapcast-host-container').filter(
(i, hostDiv) => $(hostDiv).data('host') === event.args.host
));
var $group = $($host.find('.snapcast-group-container').filter(
(i, groupDiv) => $(groupDiv).data('id') === event.args.group
));
$group.find('.snapcast-group-mute-toggle').find('input.toggle--checkbox')
.prop('checked', !event.args.muted);
break;
}
};
var update = function(statuses) {
$container.html('');
serverInfo = {};
clientInfo = {};
var networkNames = Object.keys(window.config.snapcast_hosts);
for (var i=0; i < networkNames.length; i++) {
var hosts = Object.keys(window.config.snapcast_hosts);
for (var i=0; i < hosts.length; i++) {
var status = statuses[i];
var networkName = networkNames[i];
var host = hosts[i];
var name = status.server.host.name || status.server.host.ip;
serverInfo[host] = status.server;
var $host = $('<div></div>')
.addClass('snapcast-host-container')
.data('name', name)
.data('network-name', networkName);
.data('host', host);
var $header = $('<div></div>').addClass('row')
.addClass('snapcast-host-header');
@ -58,21 +104,25 @@ $(document).ready(function() {
.addClass('snapcast-group-header');
var $groupTitle = $('<h2></h2>')
.addClass('ten columns');
.addClass('snapcast-group-settings')
.addClass('snapcast-settings-btn')
.attr('data-modal', '#snapcast-group-modal')
.addClass('eleven columns');
var $groupSettings = $('<i></i>')
.addClass('snapcast-group-settings')
.addClass('fa fa-cog')
.data('name', groupName)
.data('id', group.id);
.attr('data-modal', '#snapcast-group-modal')
.addClass('fa fa-ellipsis-v');
var $groupName = $('<span></span>')
.attr('data-modal', '#snapcast-group-modal')
.html('&nbsp; ' + groupName);
var $groupMuteToggle = createPowerToggleElement({
id: group.id,
on: !group.muted,
}).addClass('two columns').addClass('snapcast-group-mute-toggle');
}).addClass('one column')
.addClass('snapcast-mute-toggle')
.addClass('snapcast-group-mute-toggle');
$groupSettings.appendTo($groupTitle);
$groupName.appendTo($groupTitle);
@ -82,22 +132,41 @@ $(document).ready(function() {
for (var client of group.clients) {
var clientName = client.config.name || client.host.name || client.host.ip;
clientInfo[client.id] = client.host;
clientInfo[client.id].clientName = client.snapclient.name;
clientInfo[client.id].clientVersion = client.snapclient.version;
clientInfo[client.id].protocolVersion = client.snapclient.protocolVersion;
var $client = $('<div></div>')
.addClass('snapcast-client-container')
.data('name', clientName)
.data('id', client.id);
.data('id', client.id)
.data('connected', client.connected);
if (!client.connected) {
$client.addClass('snapcast-client-disconnected');
}
var $clientRow = $('<div></div>').addClass('row')
.addClass('snapcast-client-header');
.addClass('snapcast-client-row');
var $clientTitle = $('<h3></h3>')
.addClass('three columns')
.data('connected', client.connected)
.text(clientName);
.addClass('snapcast-settings-btn')
.addClass('snapcast-client-settings')
.attr('data-modal', '#snapcast-client-modal')
.addClass('two columns');
var $clientSettings = $('<i></i>')
.attr('data-modal', '#snapcast-client-modal')
.addClass('fa fa-ellipsis-v');
var $clientName = $('<span></span>')
.attr('data-modal', '#snapcast-client-modal')
.html('&nbsp; ' + clientName);
var $volumeSlider = $('<input></input>')
.addClass('slider snapcast-volume-slider')
.addClass('eight columns')
.addClass('nine columns')
.data('id', client.id)
.attr('type', 'range')
.attr('min', 0).attr('max', 100)
@ -108,8 +177,11 @@ $(document).ready(function() {
on: !client.config.volume.muted,
})
.addClass('one column')
.addClass('snapcast-mute-toggle')
.addClass('snapcast-client-mute-toggle');
$clientSettings.appendTo($clientTitle);
$clientName.appendTo($clientTitle);
$clientTitle.appendTo($clientRow);
$volumeSlider.appendTo($clientRow);
$clientMuteToggle.appendTo($clientRow);
@ -142,7 +214,7 @@ $(document).ready(function() {
$.when.apply($, promises)
.done(function() {
statuses = [];
var statuses = [];
for (var status of arguments) {
statuses.push(status[0].response.output);
}
@ -154,168 +226,146 @@ $(document).ready(function() {
};
var initBindings = function() {
// $roomsList.on('click touch', '.room-item', function() {
// $('.room-item').removeClass('selected');
// $('.room-lights-item').hide();
// $('.room-scenes-item').hide();
$container.on('click touch', '.toggle--checkbox', function(evt) {
evt.stopPropagation();
var id = $(this).attr('id');
var host = $(this).parents('.snapcast-host-container').data('host');
var args = {
host: host,
port: window.config.snapcast_hosts[host],
mute: !$(this).prop('checked'),
};
// var roomId = $(this).data('id');
// var $roomLights = $('.room-lights-item').filter(function(i, item) {
// return $(item).data('id') === roomId
// });
if ($(this).parents('.snapcast-mute-toggle').hasClass('snapcast-client-mute-toggle')) {
args.client = id;
} else if ($(this).parents('.snapcast-mute-toggle').hasClass('snapcast-group-mute-toggle')) {
args.group = id;
} else {
return;
}
// var $roomScenes = $('.room-scenes-item').filter(function(i, item) {
// return $(item).data('room-id') === roomId
// });
execute({
type: 'request',
action: 'music.snapcast.mute',
args: args,
});
});
// $(this).addClass('selected');
// $roomLights.show();
// $roomScenes.show();
// });
$container.on('mouseup touchend', '.snapcast-volume-slider', function(evt) {
evt.stopPropagation();
var id = $(this).data('id');
var host = $(this).parents('.snapcast-host-container').data('host');
var args = {
host: host,
port: window.config.snapcast_hosts[host],
client: id,
volume: $(this).val(),
};
// $scenesList.on('click touch', '.scene-item', function() {
// $('.scene-item').removeClass('selected');
// $(this).addClass('selected');
execute({
type: 'request',
action: 'music.snapcast.volume',
args: args,
});
});
// execute({
// type: 'request',
// action: 'light.hue.scene',
// args: {
// name: $(this).data('name')
// }
// }, refreshStatus);
// });
$container.on('click touch', '.snapcast-group-settings', function(evt) {
var host = $(this).parents('.snapcast-host-container').data('host');
var groupId = $(this).parents('.snapcast-group-container').data('id');
var groupName = $(this).parents('.snapcast-group-container').data('name');
var $modal = $($(this).data('modal'));
// $lightsList.on('click touch', '.light-item-name', function() {
// var $lightItem = $(this).parents('.light-item');
// var $colorSelector = $lightItem.find('.light-color-selector');
$modal.find('.modal-header').text(groupName);
});
// $('.light-color-selector').hide();
// $colorSelector.toggle();
$container.on('click touch', '.snapcast-client-settings', function(evt) {
var host = $(this).parents('.snapcast-host-container').data('host');
var clientId = $(this).parents('.snapcast-client-container').data('id');
var clientName = $(this).parents('.snapcast-client-container').data('name');
var info = clientInfo[clientId];
var $modal = $($(this).data('modal'));
var $form = $modal.find('#snapcast-client-form');
var $info = $form.find('.snapcast-client-info');
// $('.light-item').removeClass('selected');
// $lightItem.addClass('selected');
// });
$form.data('host', host);
$form.data('client', clientId);
$modal.find('.modal-header').text(clientName);
$form.find('input[name=name]').val(clientName);
// $lightsList.on('click touch', '.light-ctrl-switch', function(e) {
// e.stopPropagation();
for (var attr in info) {
$info.find('[data-bind=' + attr + ']').text(info[attr]);
}
});
// var $lightItem = $($(this).parents('.light-item'));
// var type = $lightItem.data('type');
// var name = $lightItem.data('name');
// var isOn = $lightItem.data('on');
// var action = 'light.hue.' + (isOn ? 'off' : 'on');
// var key = (type == 'light' ? 'lights' : 'groups');
// var args = {
// type: 'request',
// action: action,
// args: {}
// };
$('.snapcast-form').on('click touch', '[data-dismiss-modal]', function(evt) {
var $modal = $(this).parents($(this).data('dismiss-modal'));
// args['args'][key] = [name];
// execute(args, function() {
// $lightItem.data('on', !isOn);
// refreshStatus();
// });
// });
var clearModal = function() {
$modal.find('form').find('input').prop('disabled', false);
$modal.find('form').find('[name=delete]').prop('checked', false);
};
// $lightsList.on('click touch', '.animation-switch', function(e) {
// e.stopPropagation();
console.log($modal);
clearModal();
});
// var turnedOn = $(this).prop('checked');
// var args = {};
// args['groups'] = $(this).parents('.animation-item').data('name');
// args['animation'] = $(this).parents('.animation-item')
// .find('input.animation-type:checked').data('type');
$('#snapcast-client-form').on('submit', function(evt) {
var $form = $(this);
var host = $form.data('host');
var clientId = $form.data('client');
// var $animationCtrl = $(this).parents('.animation-item')
// .find('.animation-container').filter(
// (index, node) => $(node).data('animation-type') === args['animation']
// );
var clearModal = function() {
$form.parents('.modal').fadeOut();
$form.find('input').prop('disabled', false);
$form.find('[name=delete]').prop('checked', false);
};
// var params = $animationCtrl.find('*').filter(
// (index, node) => $(node).data('animation-property'))
// .toArray().reduce(
// (map, input) => {
// if ($(input).val().length) {
// var val = $(input).val();
// val = Array.isArray(val) ? val.map((i) => parseFloat(i)) : parseFloat(val);
// map[$(input).data('animation-property')] = val;
// }
var request = {
type: 'request',
args: {
host: host,
port: window.config.snapcast_hosts[host],
client: clientId,
},
};
// return map
// }, {}
// );
if ($form.find('[name=delete]').prop('checked')) {
if (!confirm('Are you sure you want to remove this client?')) {
return false;
}
// for (var p of Object.keys(params)) {
// args[p] = params[p];
// }
request.action = 'music.snapcast.delete_client';
} else {
request.action = 'music.snapcast.set_client_name';
request.args.name = $form.find('input[name=name]').val().trim();
}
// if (turnedOn) {
// execute(
// {
// type: 'request',
// action: 'light.hue.animate',
// args: args,
// },
$form.find('input').prop('disabled', true);
// onSuccess = function() {
// $(this).prop('checked', true);
// }
// );
// } else {
// execute(
// {
// type: 'request',
// action: 'light.hue.stop_animation',
// },
execute(
(response) => {},
(xhr, status, error) => {
createNotification({
'icon': 'exclamation',
'text': status + ': ' + error,
});
},
() => {
clearModal();
}
);
// onSuccess = function() {
// $(this).prop('checked', false);
// }
// );
// }
// });
return false;
});
};
// $lightsList.on('mouseup touchend', '.light-slider', function() {
// var property = $(this).data('property');
// var type = $(this).data('type');
// var name = $(this).data('name');
// var args = {
// type: 'request',
// action: 'light.hue.' + property,
// args: { value: $(this).val() }
// };
// if (type === 'light') {
// args.args.lights = [name];
// } else {
// args.args.groups = [name];
// }
// execute(args, refreshStatus);
// });
// $lightsList.on('click touch', 'input.animation-type', function(e) {
// var type = $(this).data('type');
// var $animationContainers = $(this).parents('.animation-item').find('.animation-container')
// var $animationContainer = $(this).parents('.animation-item').find('.animation-container')
// .filter(function() { return $(this).data('animationType') === type })
// $animationContainers.hide();
// $animationContainer.show();
// });
// if (window.config.light.hue.default_group) {
// var $defaultRoomItem = $roomsList.find('.room-item').filter(
// (i, r) => $(r).data('name') == window.config.light.hue.default_group);
// $defaultRoomItem.click();
// }
var initEvents = function() {
window.registerEventListener(onEvent);
};
var init = function() {
redraw();
initEvents();
};
init();

View file

@ -13,6 +13,91 @@
}
</script>
<div id="snapcast-group-modal" class="modal snapcast-modal">
<div class="modal-container">
<div class="modal-header"></div>
<div class="modal-body">
<form id="snapcast-group-form" class="snapcast-form" action="#">
<div class="row snapcast-form-bottom">
<div class="six columns offset-by-six forms-btns">
<input type="button" class="btn-default" data-dismiss-modal="#snapcast-group-modal"
value="Close">
<input type="submit" class="btn-primary" value="Save">
</div>
</div>
</form>
</div>
</div>
</div>
<div id="snapcast-client-modal" class="modal snapcast-modal">
<div class="modal-container">
<div class="modal-header"></div>
<div class="modal-body">
<form id="snapcast-client-form" class="snapcast-form" action="#">
<div class="row form-row">
<div class="two columns">
<label for="name">Name</label>
</div>
<div class="ten columns">
<input type="text" name="name">
</div>
</div>
<div class="row snapcast-client-info">
<div class="row">
<div class="three columns info-name">IP Address</div>
<div class="nine columns info-value" data-bind="ip"></div>
</div>
<div class="row">
<div class="three columns info-name">MAC Address</div>
<div class="nine columns info-value" data-bind="mac"></div>
</div>
<div class="row">
<div class="three columns info-name">OS</div>
<div class="nine columns info-value" data-bind="os"></div>
</div>
<div class="row">
<div class="three columns info-name">Architecture</div>
<div class="nine columns info-value" data-bind="arch"></div>
</div>
<div class="row">
<div class="three columns info-name">Client Name</div>
<div class="nine columns info-value" data-bind="clientName"></div>
</div>
<div class="row">
<div class="three columns info-name">Client Version</div>
<div class="nine columns info-value" data-bind="clientVersion"></div>
</div>
<div class="row">
<div class="three columns info-name">Protocol Version</div>
<div class="nine columns info-value" data-bind="protocolVersion"></div>
</div>
</div>
<div class="row snapcast-client-delete">
<input type="checkbox" name="delete">
<label for="delete">Delete client</label>
</div>
<div class="row snapcast-form-bottom">
<div class="six columns offset-by-six forms-btns">
<input type="button" class="btn-default" data-dismiss-modal="#snapcast-client-modal"
value="Close">
<input type="submit" class="btn-primary" value="Save">
</div>
</div>
</form>
</div>
</div>
</div>
<div id="snapcast-container" class="row">
</div>

View file

@ -129,7 +129,20 @@ class MusicSnapcastBackend(Backend):
def _client(self, host, port):
def _thread():
status = self._status(host, port)
status = None
try:
status = self._status(host, port)
except Exception as e:
self.logger.warning(('Exception while getting the status ' +
'of the Snapcast server {}:{}: {}').
format(host, port, str(e)))
try:
self._disconnect(host, port)
time.sleep(5)
except:
pass
while not self.should_stop():
try: