From 0472f78b6001c6a16e868effe412af3a88ff834a Mon Sep 17 00:00:00 2001 From: badaix Date: Fri, 24 Feb 2017 18:25:10 +0100 Subject: [PATCH] RPC update --- .../java/de/badaix/snapcast/GroupItem.java | 1 - .../java/de/badaix/snapcast/MainActivity.java | 226 ++++++++++++----- .../de/badaix/snapcast/control/JsonRPC.java | 175 +++++++++++++ .../snapcast/control/RemoteControl.java | 233 +++++++++--------- .../badaix/snapcast/control/json/Client.java | 4 + .../snapcast/control/json/ServerStatus.java | 9 + server/streamServer.cpp | 36 +-- 7 files changed, 499 insertions(+), 185 deletions(-) create mode 100644 android/Snapcast/src/main/java/de/badaix/snapcast/control/JsonRPC.java diff --git a/android/Snapcast/src/main/java/de/badaix/snapcast/GroupItem.java b/android/Snapcast/src/main/java/de/badaix/snapcast/GroupItem.java index 2c97ffdc..80297fa3 100644 --- a/android/Snapcast/src/main/java/de/badaix/snapcast/GroupItem.java +++ b/android/Snapcast/src/main/java/de/badaix/snapcast/GroupItem.java @@ -25,7 +25,6 @@ import android.view.MotionEvent; import android.view.View; import android.widget.ImageButton; import android.widget.LinearLayout; -import android.widget.RelativeLayout; import android.widget.SeekBar; import android.widget.TextView; diff --git a/android/Snapcast/src/main/java/de/badaix/snapcast/MainActivity.java b/android/Snapcast/src/main/java/de/badaix/snapcast/MainActivity.java index f934271c..40b8f58a 100644 --- a/android/Snapcast/src/main/java/de/badaix/snapcast/MainActivity.java +++ b/android/Snapcast/src/main/java/de/badaix/snapcast/MainActivity.java @@ -57,6 +57,7 @@ import de.badaix.snapcast.control.json.Client; import de.badaix.snapcast.control.json.Group; import de.badaix.snapcast.control.json.ServerStatus; import de.badaix.snapcast.control.json.Stream; +import de.badaix.snapcast.control.json.Volume; import de.badaix.snapcast.utils.NsdHelper; import de.badaix.snapcast.utils.Settings; import de.badaix.snapcast.utils.Setup; @@ -82,6 +83,7 @@ public class MainActivity extends AppCompatActivity implements GroupItem.GroupIt private int nativeSampleRate = 0; private CoordinatorLayout coordinatorLayout; private Button btnConnect = null; + private boolean batchActive = false; /** @@ -431,63 +433,6 @@ public class MainActivity extends AppCompatActivity implements GroupItem.GroupIt } } - @Override - public void onConnected(RemoteControl remoteControl) { - setActionbarSubtitle(remoteControl.getHost()); - remoteControl.getServerStatus(); - updateMenuItems(true); - } - - @Override - public void onConnecting(RemoteControl remoteControl) { - setActionbarSubtitle("connecting: " + remoteControl.getHost()); - } - - @Override - public void onDisconnected(RemoteControl remoteControl, Exception e) { - Log.d(TAG, "onDisconnected"); - serverStatus = new ServerStatus(); - groupListFragment.updateServer(serverStatus); - if (e != null) { - if (e instanceof UnknownHostException) - setActionbarSubtitle("error: unknown host"); - else - setActionbarSubtitle("error: " + e.getMessage()); - } else { - setActionbarSubtitle("not connected"); - } - updateMenuItems(false); - } - - @Override - public void onClientEvent(RemoteControl remoteControl, RemoteControl.RpcEvent rpcEvent, Client client, RemoteControl.ClientEvent event) { - Log.d(TAG, "onClientEvent: " + event.toString()); - /// update only in case of notifications - if (rpcEvent == RemoteControl.RpcEvent.response) - return; - - serverStatus.updateClient(client); - groupListFragment.updateServer(serverStatus); - } - - @Override - public void onServerUpdate(RemoteControl remoteControl, RemoteControl.RpcEvent rpcEvent, ServerStatus serverStatus) { - this.serverStatus = serverStatus; - groupListFragment.updateServer(serverStatus); - } - - @Override - public void onStreamUpdate(RemoteControl remoteControl, RemoteControl.RpcEvent rpcEvent, Stream stream) { - serverStatus.updateStream(stream); - groupListFragment.updateServer(serverStatus); - } - - @Override - public void onGroupUpdate(RemoteControl remoteControl, RemoteControl.RpcEvent rpcEvent, Group group) { - serverStatus.updateGroup(group); - groupListFragment.updateServer(serverStatus); - } - private void setActionbarSubtitle(final String subtitle) { MainActivity.this.runOnUiThread(new Runnable() { @@ -609,5 +554,172 @@ public class MainActivity extends AppCompatActivity implements GroupItem.GroupIt startActivityForResult(intent, GROUP_PROPERTIES_REQUEST); } + @Override + public void onConnected(RemoteControl remoteControl) { + setActionbarSubtitle(remoteControl.getHost()); + remoteControl.getServerStatus(); + updateMenuItems(true); + } + + @Override + public void onConnecting(RemoteControl remoteControl) { + setActionbarSubtitle("connecting: " + remoteControl.getHost()); + } + + @Override + public void onDisconnected(RemoteControl remoteControl, Exception e) { + Log.d(TAG, "onDisconnected"); + serverStatus = new ServerStatus(); + groupListFragment.updateServer(serverStatus); + if (e != null) { + if (e instanceof UnknownHostException) + setActionbarSubtitle("error: unknown host"); + else + setActionbarSubtitle("error: " + e.getMessage()); + } else { + setActionbarSubtitle("not connected"); + } + updateMenuItems(false); + } + + + @Override + public void onBatchStart() { + batchActive = true; + } + + + @Override + public void onBatchEnd() { + batchActive = false; + groupListFragment.updateServer(serverStatus); + } + + + +/* + @Override + public void onClientEvent(RemoteControl remoteControl, RemoteControl.RpcEvent rpcEvent, Client client, RemoteControl.ClientEvent event) { + Log.d(TAG, "onClientEvent: " + event.toString()); + /// update only in case of notifications + if (rpcEvent == RemoteControl.RpcEvent.response) + return; + + serverStatus.updateClient(client); + groupListFragment.updateServer(serverStatus); + } + + @Override + public void onServerUpdate(RemoteControl remoteControl, RemoteControl.RpcEvent rpcEvent, ServerStatus serverStatus) { + this.serverStatus = serverStatus; + groupListFragment.updateServer(serverStatus); + } + + @Override + public void onStreamUpdate(RemoteControl remoteControl, RemoteControl.RpcEvent rpcEvent, Stream stream) { + serverStatus.updateStream(stream); + groupListFragment.updateServer(serverStatus); + } + + @Override + public void onGroupUpdate(RemoteControl remoteControl, RemoteControl.RpcEvent rpcEvent, Group group) { + serverStatus.updateGroup(group); + groupListFragment.updateServer(serverStatus); + } +*/ + + @Override + public void onConnect(Client client) { + serverStatus.getClient(client.getId()); + if (client == null) + return; + client.setConnected(true); + serverStatus.updateClient(client); + groupListFragment.updateServer(serverStatus); + } + + @Override + public void onDisconnect(String clientId) { + Client client = serverStatus.getClient(clientId); + if (client == null) + return; + client.setConnected(false); + serverStatus.updateClient(client); + groupListFragment.updateServer(serverStatus); + } + + @Override + public void onUpdate(Client client) { + serverStatus.updateClient(client); + groupListFragment.updateServer(serverStatus); + } + + @Override + public void onVolumeChanged(RemoteControl.RPCEvent event, String clientId, Volume volume) { + if (event == RemoteControl.RPCEvent.response) + return; + Client client = serverStatus.getClient(clientId); + if (client == null) + return; + client.setVolume(volume); + if (!batchActive) + groupListFragment.updateServer(serverStatus); + } + + @Override + public void onLatencyChanged(RemoteControl.RPCEvent event, String clientId, long latency) { + Client client = serverStatus.getClient(clientId); + if (client == null) + return; + client.getConfig().setLatency((int)latency); + groupListFragment.updateServer(serverStatus); + } + + @Override + public void onNameChanged(RemoteControl.RPCEvent event, String clientId, String name) { + Client client = serverStatus.getClient(clientId); + if (client == null) + return; + client.getConfig().setName(name); + groupListFragment.updateServer(serverStatus); + } + + @Override + public void onUpdate(Group group) { + serverStatus.updateGroup(group); + groupListFragment.updateServer(serverStatus); + } + + @Override + public void onMute(RemoteControl.RPCEvent event, String groupId, boolean mute) { + Group g = serverStatus.getGroup(groupId); + if (g == null) + return; + g.setMuted(mute); + serverStatus.updateGroup(g); + groupListFragment.updateServer(serverStatus); + } + + @Override + public void onStreamChanged(RemoteControl.RPCEvent event, String groupId, String streamId) { + Group g = serverStatus.getGroup(groupId); + if (g == null) + return; + g.setStreamId(streamId); + serverStatus.updateGroup(g); + groupListFragment.updateServer(serverStatus); + } + + @Override + public void onUpdate(ServerStatus server) { + this.serverStatus = server; + groupListFragment.updateServer(serverStatus); + } + + @Override + public void onUpdate(String streamId, Stream stream) { + serverStatus.updateStream(stream); + groupListFragment.updateServer(serverStatus); + } } diff --git a/android/Snapcast/src/main/java/de/badaix/snapcast/control/JsonRPC.java b/android/Snapcast/src/main/java/de/badaix/snapcast/control/JsonRPC.java new file mode 100644 index 00000000..c3e389e3 --- /dev/null +++ b/android/Snapcast/src/main/java/de/badaix/snapcast/control/JsonRPC.java @@ -0,0 +1,175 @@ +/* + * This file is part of snapcast + * Copyright (C) 2014-2017 Johannes Pohl + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.badaix.snapcast.control; + +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Created by johannes on 19.02.17. + */ + +class RPCRequest { + RPCRequest(JSONObject json) throws JSONException { + fromJson(json); + } + + RPCRequest(String json) throws JSONException { + this(new JSONObject(json)); + } + + RPCRequest(String method, long id, JSONObject params) { + this.method = method; + this.id = id; + this.params = params; + } + + @Override + public String toString() { + return toJson().toString(); + } + + JSONObject toJson() { + JSONObject request = new JSONObject(); + try { + request.put("jsonrpc", "2.0"); + request.put("method", method); + request.put("id", id); + if (params != null) + request.put("params", params); + return request; + } catch (JSONException e) { + e.printStackTrace(); + return null; + } + } + + void fromJson(JSONObject json) throws JSONException { + id = json.getLong("id"); + method = json.getString("method"); + if (json.has("params")) + params = json.getJSONObject("params"); + else + params = null; + } + + String method; + long id; + JSONObject params; +} + + +class RPCNotification { + RPCNotification(JSONObject json) throws JSONException { + fromJson(json); + } + + RPCNotification(String json) throws JSONException { + this(new JSONObject(json)); + } + + RPCNotification(String method, long id, JSONObject params) { + this.method = method; + this.params = params; + } + + @Override + public String toString() { + return toJson().toString(); + } + + JSONObject toJson() { + JSONObject request = new JSONObject(); + try { + request.put("jsonrpc", "2.0"); + request.put("method", method); + if (params != null) + request.put("params", params); + return request; + } catch (JSONException e) { + e.printStackTrace(); + return null; + } + } + + void fromJson(JSONObject json) throws JSONException { + method = json.getString("method"); + if (json.has("params")) + params = json.getJSONObject("params"); + else + params = null; + } + + String method; + JSONObject params; +} + + +class RPCResponse { + RPCResponse(JSONObject json) throws JSONException { + fromJson(json); + } + + RPCResponse(String json) throws JSONException { + this(new JSONObject(json)); + } + + @Override + public String toString() { + return toJson().toString(); + } + + JSONObject toJson() { + JSONObject response = new JSONObject(); + try { + response.put("jsonrpc", "2.0"); + if (error != null) + response.put("error", error); + else if (result != null) + response.put("result", result); + else + throw new JSONException("error and result are null"); + + response.put("id", id); + return response; + } catch (JSONException e) { + e.printStackTrace(); + return null; + } + } + + void fromJson(JSONObject json) throws JSONException { + id = json.getLong("id"); + if (json.has("error")) { + error = json.getJSONObject("error"); + result = null; + } + else if (json.has("result")) { + result = json.getJSONObject("result"); + error = null; + } + else + throw new JSONException("error and result are null"); + } + + long id; + JSONObject result; + JSONObject error; +} + diff --git a/android/Snapcast/src/main/java/de/badaix/snapcast/control/RemoteControl.java b/android/Snapcast/src/main/java/de/badaix/snapcast/control/RemoteControl.java index fdbc37a3..7f046b8a 100644 --- a/android/Snapcast/src/main/java/de/badaix/snapcast/control/RemoteControl.java +++ b/android/Snapcast/src/main/java/de/badaix/snapcast/control/RemoteControl.java @@ -18,7 +18,6 @@ package de.badaix.snapcast.control; -import android.text.TextUtils; import android.util.Log; import org.json.JSONArray; @@ -39,6 +38,11 @@ import de.badaix.snapcast.control.json.Volume; */ public class RemoteControl implements TcpClient.TcpClientListener { + public enum RPCEvent { + response, + notification + } + private static final String TAG = "RC"; private TcpClient tcpClient; @@ -113,14 +117,14 @@ public class RemoteControl implements TcpClient.TcpClientListener { if (json.has("id")) { /// Response + RPCResponse response = new RPCResponse(json); // Log.d(TAG, "ID: " + json.getString("id")); - long id = json.getLong("id"); - String requestMethod = ""; + RPCRequest request = null; synchronized (pendingRequests) { - if (pendingRequests.containsKey(id)) { - requestMethod = pendingRequests.get(id); -// Log.d(TAG, "Response to: " + request); - pendingRequests.remove(id); + if (pendingRequests.containsKey(response.id)) { + request = new RPCRequest(new JSONObject(pendingRequests.get(response.id))); + Log.d(TAG, "Response to: " + request.method); + pendingRequests.remove(response.id); } } @@ -132,68 +136,57 @@ public class RemoteControl implements TcpClient.TcpClientListener { Log.e(TAG, "error " + error.getInt("code") + ": " + error.getString("message") + "; data: " + error.getString("data")); } - if (TextUtils.isEmpty(requestMethod)) { - Log.e(TAG, "request for id " + id + " not found"); + if (request == null) { + Log.e(TAG, "request for id " + response.id + " not found"); return; } - RpcEvent rpcEvent = RpcEvent.response; + RPCEvent rpcEvent = RPCEvent.response; /// Response to a "Object.GetStatus" message - if (requestMethod.equals("Client.GetStatus")) { - listener.onClientEvent(this, rpcEvent, new Client(json.getJSONObject("result")), ClientEvent.updated); - } else if (requestMethod.equals("Client.SetVolume")) { - } else if (requestMethod.equals("Client.SetLatency")) { - } else if (requestMethod.equals("Client.SetName")) { - - } else if (requestMethod.equals("Group.GetStatus")) { - listener.onGroupUpdate(this, rpcEvent, new Group(json.getJSONObject("result"))); - } else if (requestMethod.equals("Group.SetMute")) { - } else if (requestMethod.equals("Group.SetStream")) { - } else if (requestMethod.equals("Group.SetClients")) { - listener.onServerUpdate(this, rpcEvent, new ServerStatus(json.getJSONObject("result").getJSONObject("server"))); - } else if (requestMethod.equals("Server.GetStatus")) { - listener.onServerUpdate(this, rpcEvent, new ServerStatus(json.getJSONObject("result"))); - } else if (requestMethod.equals("Server.DeleteClient")) { -// } else if (json.getJSONObject("result").has("method") && json.getJSONObject("result").has("params")) { - /// Response to a "Object.Set" message - JSONObject result = json.getJSONObject("result"); - String method = result.getString("method"); - if ("Client.OnUpdate".equals(method)) { - listener.onClientEvent(this, rpcEvent, new Client(result.getJSONObject("params")), ClientEvent.updated); - } else if ("Group.OnUpdate".equals(method)) { - listener.onGroupUpdate(this, rpcEvent, new Group(result.getJSONObject("params"))); - } else if ("Server.OnUpdate".equals(method)) { - listener.onServerUpdate(this, rpcEvent, new ServerStatus(result.getJSONObject("params"))); - } + if (request.method.equals("Client.GetStatus")) { + listener.onUpdate(new Client(json.getJSONObject("result"))); + } else if (request.method.equals("Client.SetVolume")) { + } else if (request.method.equals("Client.SetLatency")) { + } else if (request.method.equals("Client.SetName")) { + } else if (request.method.equals("Group.GetStatus")) { + listener.onUpdate(new Group(json.getJSONObject("result"))); + } else if (request.method.equals("Group.SetMute")) { +// listener.onMute(rpcEvent, request.params.getString("id"), response.result.getBoolean("mute")); + } else if (request.method.equals("Group.SetStream")) { + } else if (request.method.equals("Group.SetClients")) { + listener.onUpdate(new ServerStatus(json.getJSONObject("result").getJSONObject("server"))); + } else if (request.method.equals("Server.GetStatus")) { + listener.onUpdate(new ServerStatus(json.getJSONObject("result"))); + } else if (request.method.equals("Server.DeleteClient")) { + listener.onUpdate(new ServerStatus(json.getJSONObject("result").getJSONObject("server"))); } } else { /// Notification if (listener == null) return; - RpcEvent rpcEvent = RpcEvent.notification; - String method = json.getString("method"); + RPCEvent rpcEvent = RPCEvent.notification; + RPCNotification notification = new RPCNotification(json); - if (method.equals("Client.OnConnect")) { - } else if (method.equals("Client.OnDisconnect")) { - } else if (method.equals("Client.OnVolumeChanged")) { - } else if (method.equals("Client.OnLatencyChanged")) { - } else if (method.equals("Client.OnNameChanged")) { - - } else if (method.equals("Group.OnMute")) { - } else if (method.equals("Group.OnStreamChanged")) { - - } else if (method.equals("Stream.OnUpdate")) { - - } else if (method.equals("Server.OnUpdate")) { - - } else if (method.contains("Client.On")) { - listener.onClientEvent(this, rpcEvent, new Client(json.getJSONObject("params").getJSONObject("client")), ClientEvent.fromString(method)); - } else if (method.equals("Stream.OnUpdate")) { - listener.onStreamUpdate(this, rpcEvent, new Stream(json.getJSONObject("params").getJSONObject("stream"))); - } else if (method.equals("Group.OnUpdate")) { - listener.onGroupUpdate(this, rpcEvent, new Group(json.getJSONObject("params").getJSONObject("group"))); - } else if (method.equals("Server.OnUpdate")) { - listener.onServerUpdate(this, rpcEvent, new ServerStatus(json.getJSONObject("params").getJSONObject("server"))); + if (notification.method.equals("Client.OnConnect")) { + listener.onConnect(new Client(notification.params.getJSONObject("client"))); + } else if (notification.method.equals("Client.OnDisconnect")) { + listener.onDisconnect(notification.params.getString("id")); + } else if (notification.method.equals("Client.OnVolumeChanged")) { + listener.onVolumeChanged(rpcEvent, notification.params.getString("id"), new Volume(notification.params.getJSONObject("volume"))); + } else if (notification.method.equals("Client.OnLatencyChanged")) { + listener.onLatencyChanged(rpcEvent, notification.params.getString("id"), notification.params.getInt("latency")); + } else if (notification.method.equals("Client.OnNameChanged")) { + listener.onNameChanged(rpcEvent, notification.params.getString("id"), notification.params.getString("name")); + } else if (notification.method.equals("Group.OnMute")) { + listener.onMute(rpcEvent, notification.params.getString("id"), notification.params.getBoolean("mute")); + } else if (notification.method.equals("Group.OnStreamChanged")) { + listener.onStreamChanged(rpcEvent, notification.params.getString("id"), notification.params.getString("stream_id")); + } else if (notification.method.equals("Stream.OnUpdate")) { + listener.onUpdate(notification.params.getString("id"), new Stream(notification.params.getJSONObject("stream"))); + } else if (notification.method.equals("Group.OnUpdate")) { + listener.onUpdate(new Group(notification.params.getJSONObject("group"))); + } else if (notification.method.equals("Server.OnUpdate")) { + listener.onUpdate(new ServerStatus(notification.params.getJSONObject("server"))); } } @@ -223,35 +216,26 @@ public class RemoteControl implements TcpClient.TcpClientListener { listener.onDisconnected(this, e); } - private JSONObject jsonRequest(String method, JSONObject params) { - JSONObject request = new JSONObject(); - try { - request.put("jsonrpc", "2.0"); - request.put("method", method); - request.put("id", msgId); - if (params != null) - request.put("params", params); - synchronized (pendingRequests) { - pendingRequests.put(msgId, method); - } - msgId++; - } catch (JSONException e) { - e.printStackTrace(); + private RPCRequest jsonRequest(String method, JSONObject params) { + RPCRequest request = new RPCRequest(method, msgId, params); + synchronized (pendingRequests) { + pendingRequests.put(msgId, request.toString()); } + msgId++; return request; } public void getServerStatus() { - JSONObject request = jsonRequest("Server.GetStatus", null); + RPCRequest request = jsonRequest("Server.GetStatus", null); tcpClient.sendMessage(request.toString()); } public void setName(Client client, String name) { try { - JSONObject body = new JSONObject(); - body.put("id", client.getId()); - body.put("name", name); - JSONObject request = jsonRequest("Client.SetName", body); + JSONObject params = new JSONObject(); + params.put("id", client.getId()); + params.put("name", name); + RPCRequest request = jsonRequest("Client.SetName", params); tcpClient.sendMessage(request.toString()); } catch (JSONException e) { e.printStackTrace(); @@ -260,10 +244,10 @@ public class RemoteControl implements TcpClient.TcpClientListener { public void setLatency(Client client, int latency) { try { - JSONObject body = new JSONObject(); - body.put("id", client.getId()); - body.put("latency", latency); - JSONObject request = jsonRequest("Client.SetLatency", body); + JSONObject params = new JSONObject(); + params.put("id", client.getId()); + params.put("latency", latency); + RPCRequest request = jsonRequest("Client.SetLatency", params); tcpClient.sendMessage(request.toString()); } catch (JSONException e) { e.printStackTrace(); @@ -279,10 +263,10 @@ public class RemoteControl implements TcpClient.TcpClientListener { JSONArray clients = new JSONArray(); for (String clientId : clientIds) clients.put(clientId); - JSONObject body = new JSONObject(); - body.put("id", groupId); - body.put("clients", clients); - JSONObject request = jsonRequest("Group.SetClients", body); + JSONObject params = new JSONObject(); + params.put("id", groupId); + params.put("clients", clients); + RPCRequest request = jsonRequest("Group.SetClients", params); tcpClient.sendMessage(request.toString()); } catch (JSONException e) { e.printStackTrace(); @@ -291,10 +275,10 @@ public class RemoteControl implements TcpClient.TcpClientListener { public void setStream(String groupId, String streamId) { try { - JSONObject body = new JSONObject(); - body.put("id", groupId); - body.put("stream_id", streamId); - JSONObject request = jsonRequest("Group.SetStream", body); + JSONObject params = new JSONObject(); + params.put("id", groupId); + params.put("stream_id", streamId); + RPCRequest request = jsonRequest("Group.SetStream", params); tcpClient.sendMessage(request.toString()); } catch (JSONException e) { e.printStackTrace(); @@ -303,10 +287,10 @@ public class RemoteControl implements TcpClient.TcpClientListener { public void setGroupMuted(Group group, boolean muted) { try { - JSONObject body = new JSONObject(); - body.put("id", group.getId()); - body.put("mute", muted); - JSONObject request = jsonRequest("Group.SetMute", body); + JSONObject params = new JSONObject(); + params.put("id", group.getId()); + params.put("mute", muted); + RPCRequest request = jsonRequest("Group.SetMute", params); tcpClient.sendMessage(request.toString()); } catch (JSONException e) { e.printStackTrace(); @@ -318,8 +302,8 @@ public class RemoteControl implements TcpClient.TcpClientListener { JSONArray batch = new JSONArray(); for (Client client : group.getClients()) { Volume volume = client.getConfig().getVolume(); - JSONObject volumeRequest = getVolumeRequest(client, volume.getPercent(), volume.isMuted()); - batch.put(volumeRequest); + RPCRequest volumeRequest = getVolumeRequest(client, volume.getPercent(), volume.isMuted()); + batch.put(volumeRequest.toJson()); } tcpClient.sendMessage(batch.toString()); } catch (JSONException e) { @@ -327,12 +311,12 @@ public class RemoteControl implements TcpClient.TcpClientListener { } } - private JSONObject getVolumeRequest(Client client, int percent, boolean mute) throws JSONException { + private RPCRequest getVolumeRequest(Client client, int percent, boolean mute) throws JSONException { Volume volume = new Volume(percent, mute); - JSONObject body = new JSONObject(); - body.put("id", client.getId()); - body.put("volume", volume.toJson()); - return jsonRequest("Client.SetVolume", body); + JSONObject params = new JSONObject(); + params.put("id", client.getId()); + params.put("volume", volume.toJson()); + return jsonRequest("Client.SetVolume", params); } public void setVolume(Client client, int percent, boolean mute) { @@ -345,9 +329,9 @@ public class RemoteControl implements TcpClient.TcpClientListener { public void delete(Client client) { try { - JSONObject body = new JSONObject(); - body.put("id", client.getId()); - JSONObject request = jsonRequest("Server.DeleteClient", body); + JSONObject params = new JSONObject(); + params.put("id", client.getId()); + RPCRequest request = jsonRequest("Server.DeleteClient", params); tcpClient.sendMessage(request.toString()); } catch (JSONException e) { e.printStackTrace(); @@ -381,24 +365,49 @@ public class RemoteControl implements TcpClient.TcpClientListener { } } - public enum RpcEvent { - response, - notification + + public interface ClientListener { + void onConnect(Client client); + void onDisconnect(String clientId); + void onUpdate(Client client); + void onVolumeChanged(RPCEvent event, String clientId, Volume volume); + void onLatencyChanged(RPCEvent event, String clientId, long latency); + void onNameChanged(RPCEvent event, String clientId, String name); } - public interface RemoteControlListener { + public interface GroupListener { + void onUpdate(Group group); + void onMute(RPCEvent event, String groupId, boolean mute); + void onStreamChanged(RPCEvent event, String groupId, String streamId); + } + + public interface StreamListener { + void onUpdate(String streamId, Stream stream); + } + + public interface ServerListener { + void onUpdate(ServerStatus server); + } + + + + public interface RemoteControlListener extends ServerListener, StreamListener, GroupListener, ClientListener { void onConnected(RemoteControl remoteControl); void onConnecting(RemoteControl remoteControl); void onDisconnected(RemoteControl remoteControl, Exception e); - void onClientEvent(RemoteControl remoteControl, RpcEvent rpcEvent, Client client, ClientEvent event); + void onBatchStart(); + void onBatchEnd(); +/* + void onClientEvent(RemoteControl remoteControl, RPCEvent rpcEvent, Client client, ClientEvent event); - void onServerUpdate(RemoteControl remoteControl, RpcEvent rpcEvent, ServerStatus serverStatus); + void onServerUpdate(RemoteControl remoteControl, RPCEvent rpcEvent, ServerStatus serverStatus); - void onStreamUpdate(RemoteControl remoteControl, RpcEvent rpcEvent, Stream stream); + void onStreamUpdate(RemoteControl remoteControl, RPCEvent rpcEvent, Stream stream); - void onGroupUpdate(RemoteControl remoteControl, RpcEvent rpcEvent, Group group); + void onGroupUpdate(RemoteControl remoteControl, RPCEvent rpcEvent, Group group); +*/ } } diff --git a/android/Snapcast/src/main/java/de/badaix/snapcast/control/json/Client.java b/android/Snapcast/src/main/java/de/badaix/snapcast/control/json/Client.java index ba7b8add..c2238a61 100644 --- a/android/Snapcast/src/main/java/de/badaix/snapcast/control/json/Client.java +++ b/android/Snapcast/src/main/java/de/badaix/snapcast/control/json/Client.java @@ -105,6 +105,10 @@ public class Client implements JsonSerialisable, Comparable { return connected; } + public void setConnected(boolean connected) { + this.connected = connected; + } + public String getId() { return clientId; } diff --git a/android/Snapcast/src/main/java/de/badaix/snapcast/control/json/ServerStatus.java b/android/Snapcast/src/main/java/de/badaix/snapcast/control/json/ServerStatus.java index c0b1d2a7..23c96239 100644 --- a/android/Snapcast/src/main/java/de/badaix/snapcast/control/json/ServerStatus.java +++ b/android/Snapcast/src/main/java/de/badaix/snapcast/control/json/ServerStatus.java @@ -152,6 +152,15 @@ public class ServerStatus implements JsonSerialisable { return true; } + public Client getClient(String id) { + for (Group group: groups) { + Client client = group.getClient(id); + if (client != null) + return client; + } + return null; + } + public ArrayList getGroups() { return groups; } diff --git a/server/streamServer.cpp b/server/streamServer.cpp index 567f47f5..997add2e 100644 --- a/server/streamServer.cpp +++ b/server/streamServer.cpp @@ -130,16 +130,18 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp } else if (request->method == "Client.SetVolume") { - /// -> {"jsonrpc":"2.0","method":"Client.SetVolume","id":5,"params":{"id":"00:21:6a:7d:74:fc#2","volume":{"percent":56,"muted":false}}} - /// <- {"id":5,"jsonrpc":"2.0","result":{"volume":{"muted":false,"percent":56}}} + /// Request: {"id":2,"jsonrpc":"2.0","method":"Client.SetVolume","params":{"id":"00:21:6a:7d:74:fc","volume":{"muted":false,"percent":36}}} + /// Response: {"id":2,"jsonrpc":"2.0","result":{"volume":{"muted":false,"percent":36}}} + /// Notification: {"jsonrpc":"2.0","method":"Client.OnVolumeChanged","params":{"id":"00:21:6a:7d:74:fc","volume":{"muted":false,"percent":36}}} clientInfo->config.volume.fromJson(request->params.get("volume")); result["volume"] = clientInfo->config.volume.toJson(); notification.reset(new jsonrpcpp::Notification("Client.OnVolumeChanged", jsonrpcpp::Parameter("id", clientInfo->id, "volume", clientInfo->config.volume.toJson()))); } else if (request->method == "Client.SetLatency") { - /// -> {"jsonrpc":"2.0","method":"Client.SetLatency","id":4,"params":{"id":"00:21:6a:7d:74:fc#2","latency":50}} - /// <- {"id":4,"jsonrpc":"2.0","result":{"latency":50}} + /// Request: {"id":4,"jsonrpc":"2.0","method":"Client.SetLatency","params":{"id":"00:21:6a:7d:74:fc","latency":20}} + /// Response: {"id":4,"jsonrpc":"2.0","result":{"latency":20}} + /// Notification: {"jsonrpc":"2.0","method":"Client.OnLatencyChanged","params":{"id":"00:21:6a:7d:74:fc","latency":20}} int latency = request->params.get("latency"); if (latency < -10000) latency = -10000; @@ -151,8 +153,9 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp } else if (request->method == "Client.SetName") { - /// -> {"jsonrpc":"2.0","method":"Client.SetName","id":3,"params":{"id":"00:21:6a:7d:74:fc#2","name":"test"}} - /// <- {"id":3,"jsonrpc":"2.0","result":{"name":"test"}} + /// Request: {"id":3,"jsonrpc":"2.0","method":"Client.SetName","params":{"id":"00:21:6a:7d:74:fc","name":"teat"}} + /// Response: {"id":3,"jsonrpc":"2.0","result":{"name":"teat"}} + /// Notification: {"jsonrpc":"2.0","method":"Client.OnNameChanged","params":{"id":"00:21:6a:7d:74:fc","name":"teat"}} clientInfo->config.name = request->params.get("name"); result["name"] = clientInfo->config.name; notification.reset(new jsonrpcpp::Notification("Client.OnNameChanged", jsonrpcpp::Parameter("id", clientInfo->id, "name", clientInfo->config.name))); @@ -189,8 +192,9 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp } else if (request->method == "Group.SetMute") { - /// -> {"jsonrpc":"2.0","method":"Group.SetMute","id":3,"params":{"id":"296ddff1-56fd-f2e4-232e-62c44faba7aa","mute":true}} - /// <- {"id":3,"jsonrpc":"2.0","result":{"mute":true}} + /// Request: {"id":6,"jsonrpc":"2.0","method":"Group.SetMute","params":{"id":"8e374310-b1a7-d410-d80e-ff29cf504657","mute":true}} + /// Response: {"id":6,"jsonrpc":"2.0","result":{"mute":true}} + /// Notification: {"jsonrpc":"2.0","method":"Group.OnMute","params":{"id":"8e374310-b1a7-d410-d80e-ff29cf504657","mute":true}} bool muted = request->params.get("mute"); group->muted = muted; @@ -215,8 +219,9 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp } else if (request->method == "Group.SetStream") { - /// -> {"jsonrpc":"2.0","method":"Group.SetStream","id":1,"params":{"id":"296ddff1-56fd-f2e4-232e-62c44faba7aa","stream_id":"stream 2"}} - /// <- {"id":1,"jsonrpc":"2.0","result":{"stream_id":"stream 2"}} + /// Request: {"id":8,"jsonrpc":"2.0","method":"Group.SetStream","params":{"id":"8e374310-b1a7-d410-d80e-ff29cf504657","stream_id":"stream 2"}} + /// Response: {"id":8,"jsonrpc":"2.0","result":{"stream_id":"stream 2"}} + /// Notification: {"jsonrpc":"2.0","method":"Group.OnStreamChanged","params":{"id":"8e374310-b1a7-d410-d80e-ff29cf504657","stream_id":"stream 2"}} string streamId = request->params.get("stream_id"); PcmStreamPtr stream = streamManager_->getStream(streamId); if (stream == nullptr) @@ -241,8 +246,9 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp } else if (request->method == "Group.SetClients") { - /// -> {"jsonrpc":"2.0","method":"Group.SetClients","id":6,"params":{"id":"25d719fd-3a4b-2090-2ac4-b17f62b7b18b","clients":["00:21:6a:7d:74:fc","00:21:6a:7d:74:fc#2"]}} - /// <- {"id":6,"jsonrpc":"2.0","result":{"server":{"groups":[{"clients":[{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":100}},"connected":true,"host":{"arch":"x86_64","ip":"192.168.0.54","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1487521394,"usec":253219},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},{"config":{"instance":2,"latency":50,"name":"test","volume":{"muted":false,"percent":56}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1487521393,"usec":332977},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"25d719fd-3a4b-2090-2ac4-b17f62b7b18b","muted":false,"name":"","stream_id":"default"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3 Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"default","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"default","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=default","scheme":"pipe"}}]}}} + /// Request: {"id":5,"jsonrpc":"2.0","method":"Group.SetClients","params":{"clients":["00:21:6a:7d:74:fc#2","00:21:6a:7d:74:fc"],"id":"8e374310-b1a7-d410-d80e-ff29cf504657"}} + /// Response: {"id":5,"jsonrpc":"2.0","result":{"server":{"groups":[{"clients":[{"config":{"instance":1,"latency":20,"name":"teat","volume":{"muted":false,"percent":36}},"connected":true,"host":{"arch":"x86_64","ip":"192.168.0.54","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1487524341,"usec":500664},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},{"config":{"instance":2,"latency":0,"name":"","volume":{"muted":false,"percent":100}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1487524340,"usec":557095},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"8e374310-b1a7-d410-d80e-ff29cf504657","muted":false,"name":"","stream_id":"stream 1"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3 Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}} + /// Notification: {"jsonrpc":"2.0","method":"Server.OnUpdate","params":{"server":{"groups":[{"clients":[{"config":{"instance":1,"latency":20,"name":"teat","volume":{"muted":false,"percent":36}},"connected":true,"host":{"arch":"x86_64","ip":"192.168.0.54","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1487524341,"usec":500664},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},{"config":{"instance":2,"latency":0,"name":"","volume":{"muted":false,"percent":100}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1487524340,"usec":557095},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"8e374310-b1a7-d410-d80e-ff29cf504657","muted":false,"name":"","stream_id":"stream 1"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3 Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}} vector clients = request->params.get("clients"); /// Remove clients from group for (auto iter = group->clients.begin(); iter != group->clients.end();) @@ -366,16 +372,16 @@ void StreamServer::onMessageReceived(ControlSession* controlSession, const std:: if (entity->is_request()) { jsonrpcpp::request_ptr request = dynamic_pointer_cast(entity); - cout << "Request:\n" << request->to_json().dump(4) << "\n"; ProcessRequest(request, response, notification); + cout << "Request: " << request->to_json().dump() << "\n"; if (response) { - cout << "Response:\n" << response->to_json().dump(4) << "\n"; + cout << "Response: " << response->to_json().dump() << "\n"; controlSession->send(response->to_json().dump()); } if (notification) { - cout << "Notification:\n" << notification->to_json().dump(4) << "\n"; + cout << "Notification: " << notification->to_json().dump() << "\n"; controlServer_->send(notification->to_json().dump(), controlSession); } }