diff --git a/src/js/services/snapcast/actions.js b/src/js/services/snapcast/actions.js new file mode 100755 index 00000000..037f454d --- /dev/null +++ b/src/js/services/snapcast/actions.js @@ -0,0 +1,131 @@ + +export function connect(){ + return { + type: 'SNAPCAST_CONNECT' + } +} + +export function disconnect(){ + return { + type: 'SNAPCAST_DISCONNECT' + } +} + +export function reload(){ + return { + type: 'SNAPCAST_RELOAD' + } +} + +export function getConnections(){ + return { + type: 'SNAPCAST_GET_CONNECTIONS' + } +} + +export function connectionAdded(connection){ + return { + type: 'SNAPCAST_CONNECTION_ADDED', + connection: connection + } +} + +export function connectionChanged(connection){ + return { + type: 'SNAPCAST_CONNECTION_CHANGED', + connection: connection + } +} + +export function connectionRemoved(connection){ + return { + type: 'SNAPCAST_CONNECTION_REMOVED', + connection: connection + } +} + +export function getServer(){ + return { + type: 'SNAPCAST_GET_SERVER' + } +} + +export function serverLoaded(server){ + return { + type: 'SNAPCAST_SERVER_LOADED', + server: server + } +} + +export function setClientName(id, name){ + return { + type: 'SNAPCAST_SET_CLIENT_NAME', + id: id, + name: name + } +} + +export function setClientMute(id, mute){ + return { + type: 'SNAPCAST_SET_CLIENT_MUTE', + id: id, + mute: mute + } +} + +export function setClientVolume(id, percent){ + return { + type: 'SNAPCAST_SET_CLIENT_VOLUME', + id: id, + percent: percent + } +} + +export function setClientLatency(id, latency){ + return { + type: 'SNAPCAST_SET_CLIENT_LATENCY', + id: id, + latency: latency + } +} + +export function setClientGroup(id, group_id){ + return { + type: 'SNAPCAST_SET_CLIENT_GROUP', + id: id, + group_id: group_id + } +} + +export function deleteClient(id){ + return { + type: 'SNAPCAST_DELETE_CLIENT', + id: id + } +} + +export function setGroupStream(id, stream_id){ + return { + type: 'SNAPCAST_SET_GROUP_STREAM', + id: id, + stream_id: stream_id + } +} + +export function setGroupMute(id, mute){ + return { + type: 'SNAPCAST_SET_GROUP_MUTE', + id: id, + mute: mute + } +} + +export function setGroupVolume(id, percent, old_percent = 0){ + return { + type: 'SNAPCAST_SET_GROUP_VOLUME', + id: id, + percent: percent, + old_percent: old_percent + } +} + diff --git a/src/js/services/snapcast/middleware.js b/src/js/services/snapcast/middleware.js new file mode 100755 index 00000000..23ec28ac --- /dev/null +++ b/src/js/services/snapcast/middleware.js @@ -0,0 +1,381 @@ + +import ReactGA from 'react-ga' + +var helpers = require('../../helpers') +var coreActions = require('../core/actions') +var uiActions = require('../ui/actions') +var pusherActions = require('../pusher/actions') + +const SnapcastMiddleware = (function(){ + + const request = (store, params = null) => { + store.dispatch(pusherActions.instruct('snapcast_instruct', params)); + return; + } + + return store => next => action => { + switch(action.type){ + + case 'SNAPCAST_GET_SERVER': + console.log( request(store, {method: 'Server.GetStatus'}) )/* + .then( + response => { + store.dispatch(snapcastActions.serverLoaded(response.server)); + }, + error => { + store.dispatch(coreActions.handleException( + 'Could not get Snapcast server', + error, + error.message + )); + } + );*/ + break + + case 'SNAPCAST_SERVER_LOADED': + var groups = {}; + var clients = {}; + var streams = {}; + + // Loop all the groups + for (var i = 0; i < action.server.groups.length; i++){ + var group = action.server.groups[i]; + var clients_ids = []; + + // And now this groups' clients + for (var j = 0; j < group.clients.length; j++){ + var client = group.clients[j]; + clients[client.id] = client; + clients_ids.push(client.id); + } + + groups[group.id] = { + id: group.id, + muted: group.muted, + name: group.name, + stream_id: group.stream_id, + clients_ids: clients_ids + } + } + + // Loop all the streams + for (var i = 0; i < action.server.streams.length; i++){ + var stream = action.server.streams[i]; + streams[stream.id] = stream; + } + + action.clients = clients; + action.groups = groups; + action.streams = streams; + next(action); + break + + case 'SNAPCAST_SET_CLIENT_NAME': + var client = store.getState().snapcast.clients[action.id]; + var data = { + method: 'Client.SetName', + params: { + id: action.id, + name: action.name + } + } + + request(store, data) + .then( + response => { + store.dispatch({ + type: 'SNAPCAST_CLIENT_UPDATED', + key: action.id, + client: { + config: { + name: response.name + } + } + }) + }, + error => { + store.dispatch(coreActions.handleException( + 'Error', + error, + error.message + )); + } + ); + break + + case 'SNAPCAST_SET_CLIENT_MUTE': + var client = store.getState().snapcast.clients[action.id]; + var data = { + method: 'Client.SetVolume', + params: { + id: action.id, + volume: { + muted: action.mute, + percent: client.config.volume.percent, + } + } + } + + request(store, data) + .then( + response => { + store.dispatch({ + type: 'SNAPCAST_CLIENT_UPDATED', + key: action.id, + client: { + config: { + volume: response.volume + } + } + }) + }, + error => { + store.dispatch(coreActions.handleException( + 'Error', + error, + error.message + )); + } + ); + break + + case 'SNAPCAST_SET_CLIENT_VOLUME': + var client = store.getState().snapcast.clients[action.id]; + var data = { + method: 'Client.SetVolume', + params: { + id: action.id, + volume: { + muted: client.config.volume.muted, + percent: action.percent + } + } + } + + request(store, data) + .then( + response => { + store.dispatch({ + type: 'SNAPCAST_CLIENT_UPDATED', + key: action.id, + client: { + config: { + volume: response.volume + } + } + }) + }, + error => { + store.dispatch(coreActions.handleException( + 'Error', + error, + error.message + )); + } + ); + break + + case 'SNAPCAST_SET_CLIENT_LATENCY': + var client = store.getState().snapcast.clients[action.id]; + var data = { + method: 'Client.SetLatency', + params: { + id: action.id, + latency: action.latency + } + } + + request(store, data) + .then( + response => { + store.dispatch({ + type: 'SNAPCAST_CLIENT_UPDATED', + key: action.id, + client: { + config: { + latency: response.latency + } + } + }) + }, + error => { + store.dispatch(coreActions.handleException( + 'Error', + error, + error.message + )); + } + ); + break + + case 'SNAPCAST_SET_CLIENT_GROUP': + + var group = store.getState().snapcast.groups[action.group_id]; + var clients_ids = group.clients_ids; + var clients_ids_index = clients_ids.indexOf(action.id); + + // Not in group (yet), so add it + if (clients_ids_index <= -1){ + clients_ids.push(action.id); + + // Already there, so remove it + } else { + clients_ids.splice(clients_ids_index, 1); + } + + var data = { + method: 'Group.SetClients', + params: { + id: action.group_id, + clients: clients_ids + } + } + + request(store, data) + .then( + response => { + store.dispatch(snapcastActions.serverLoaded(response.server)); + }, + error => { + store.dispatch(coreActions.handleException( + 'Error', + error, + error.message + )); + } + ); + break + + case 'SNAPCAST_DELETE_CLIENT': + var data = { + method: 'Server.DeleteClient', + params: { + id: action.id + } + } + + request(store, data) + .then( + response => { + store.dispatch({ + type: 'SNAPCAST_CLIENT_REMOVED', + key: action.data.params.id + }) + }, + error => { + store.dispatch(coreActions.handleException( + 'Error', + error, + error.message + )); + } + ); + break + + case 'SNAPCAST_SET_GROUP_STREAM': + var group = store.getState().snapcast.groups[action.id]; + var data = { + method: 'Group.SetStream', + params: { + id: action.id, + stream_id: action.stream_id + } + } + + request(store, data) + .then( + response => { + store.dispatch({ + type: 'SNAPCAST_GROUP_UPDATED', + key: action.id, + group: { + stream_id: action.stream_id + } + }) + }, + error => { + store.dispatch(coreActions.handleException( + 'Could not change stream', + error, + error.message + )); + } + ); + break + + case 'SNAPCAST_SET_GROUP_MUTE': + var group = store.getState().snapcast.groups[action.id]; + var data = { + method: 'Group.SetMute', + params: { + id: action.id, + mute: action.mute + } + } + + request(store, data) + .then( + response => { + store.dispatch({ + type: 'SNAPCAST_GROUP_UPDATED', + key: action.id, + group: { + muted: response.mute + } + }) + }, + error => { + store.dispatch(coreActions.handleException( + 'Could not toggle mute', + error, + error.message + )); + } + ); + break + + case 'SNAPCAST_SET_GROUP_VOLUME': + var clients_to_update = []; + var group = store.getState().snapcast.groups[action.id]; + var change = action.percent - action.old_percent; + + for (var i = 0; i < group.clients_ids.length; i++){ + + // Apply the change proportionately to each client + var client = store.getState().snapcast.clients[group.clients_ids[i]]; + var current_percent = client.config.volume.percent; + var new_percent = current_percent + change; + + // Only change if the client is within min/max limits + if ((change > 0 && current_percent < 100) || (change < 0 && current_percent > 0)){ + clients_to_update.push({ + id: client.id, + percent: new_percent + }); + } + } + + // Loop our required changes, and post each to Snapcast + for (var i = 0; i < clients_to_update.length; i++){ + var update = clients_to_update[i]; + var percent = update.percent + ((group.clients_ids.length - clients_to_update.length) * change); + + // Make sure we're not creating an impossible percent + if (percent < 0){ + percent = 0; + } else if (percent > 100){ + percent = 100; + } + + store.dispatch(snapcastActions.setClientVolume(update.id, percent)); + } + + // This action is irrelevant to us, pass it on to the next middleware + default: + return next(action); + } + } + +})(); + +export default SnapcastMiddleware diff --git a/src/js/services/snapcast/reducer.js b/src/js/services/snapcast/reducer.js new file mode 100755 index 00000000..67727f33 --- /dev/null +++ b/src/js/services/snapcast/reducer.js @@ -0,0 +1,36 @@ + +export default function reducer(snapcast = {}, action){ + switch (action.type){ + + case 'SNAPCAST_SERVER_LOADED': + return Object.assign({}, snapcast, { + streams: action.streams, + groups: action.groups, + clients: action.clients + }); + + case 'SNAPCAST_GROUP_UPDATED': + var groups = Object.assign({}, snapcast.groups); + var group = groups[action.key]; + groups[action.key] = Object.assign({}, group, action.group); + return Object.assign({}, snapcast, { groups: groups }); + + case 'SNAPCAST_CLIENT_UPDATED': + var clients = Object.assign({}, snapcast.clients); + var client = clients[action.key]; + client.config = Object.assign({}, client.config, action.client.config); + clients[action.key] = client; + return Object.assign({}, snapcast, { clients: clients }); + + case 'SNAPCAST_CLIENT_REMOVED': + var clients = Object.assign({}, snapcast.clients); + delete clients[action.key]; + return Object.assign({}, snapcast, { clients: clients }); + + default: + return snapcast + } +} + + +