diff --git a/android/Snapcast/src/main/java/de/badaix/snapcast/ClientItem.java b/android/Snapcast/src/main/java/de/badaix/snapcast/ClientItem.java index 13fe7ee5..bf30afb9 100644 --- a/android/Snapcast/src/main/java/de/badaix/snapcast/ClientItem.java +++ b/android/Snapcast/src/main/java/de/badaix/snapcast/ClientItem.java @@ -20,7 +20,6 @@ package de.badaix.snapcast; import android.content.Context; import android.support.v7.widget.PopupMenu; -import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; @@ -64,7 +63,7 @@ public class ClientItem extends LinearLayout implements SeekBar.OnSeekBarChangeL } private void update() { - Log.d(TAG, "update: " + client.getVisibleName() + ", connected: " + client.isConnected()); + //Log.d(TAG, "update: " + client.getVisibleName() + ", connected: " + client.isConnected()); title.setText(client.getVisibleName()); title.setEnabled(client.isConnected()); volumeSeekBar.setProgress(client.getConfig().getVolume().getPercent()); 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 bb76ca73..23a4dd4f 100644 --- a/android/Snapcast/src/main/java/de/badaix/snapcast/MainActivity.java +++ b/android/Snapcast/src/main/java/de/badaix/snapcast/MainActivity.java @@ -55,6 +55,7 @@ import java.util.ArrayList; import de.badaix.snapcast.control.RemoteControl; 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.utils.NsdHelper; @@ -431,8 +432,9 @@ public class MainActivity extends AppCompatActivity implements GroupItem.GroupIt remoteControl.setStream(groupId, streamId); changed = true; } - if (changed) - remoteControl.getServerStatus(); +//TODO +// if (changed) +// remoteControl.getServerStatus(); } } @@ -467,7 +469,7 @@ public class MainActivity extends AppCompatActivity implements GroupItem.GroupIt @Override public void onClientEvent(RemoteControl remoteControl, Client client, RemoteControl.ClientEvent event) { Log.d(TAG, "onClientEvent: " + event.toString()); - remoteControl.getServerStatus(); +// remoteControl.getServerStatus(); /* TODO: group if (event == RemoteControl.ClientEvent.deleted) serverStatus.removeClient(client); @@ -475,6 +477,9 @@ public class MainActivity extends AppCompatActivity implements GroupItem.GroupIt serverStatus.updateClient(client); sectionsPagerAdapter.updateServer(serverStatus); */ + if (event != RemoteControl.ClientEvent.deleted) + serverStatus.updateClient(client); + groupListFragment.updateServer(serverStatus); } @Override @@ -489,6 +494,12 @@ public class MainActivity extends AppCompatActivity implements GroupItem.GroupIt // TODO: group sectionsPagerAdapter.updateServer(serverStatus); } + @Override + public void onGroupUpdate(RemoteControl remoteControl, Group group) { + // TODO + Log.d(TAG, "onGroupUpdate: " + group.toString()); + } + private void setActionbarSubtitle(final String subtitle) { MainActivity.this.runOnUiThread(new Runnable() { 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 4bca1f41..57f55a27 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 @@ -44,14 +44,12 @@ public class RemoteControl implements TcpClient.TcpClientListener { private TcpClient tcpClient; private long msgId; private RemoteControlListener listener; - private ServerStatus serverStatus; private String host; private int port; private HashMap pendingRequests; public RemoteControl(RemoteControlListener listener) { this.listener = listener; - serverStatus = new ServerStatus(); msgId = 0; pendingRequests = new HashMap<>(); } @@ -96,7 +94,9 @@ public class RemoteControl implements TcpClient.TcpClientListener { // Log.d(TAG, "Msg received: " + message); try { JSONObject json = new JSONObject(message); + if (json.has("id")) { + /// Response // Log.d(TAG, "ID: " + json.getString("id")); long id = json.getLong("id"); String request = ""; @@ -107,38 +107,53 @@ public class RemoteControl implements TcpClient.TcpClientListener { pendingRequests.remove(id); } } + + if (listener == null) + return; + if (json.has("error")) { JSONObject error = json.getJSONObject("error"); Log.e(TAG, "error " + error.getInt("code") + ": " + error.getString("message")); - } else if (!TextUtils.isEmpty(request)) { - if (request.equals("Server.GetStatus")) { - serverStatus.fromJson(json.getJSONObject("result")); - if (listener != null) - listener.onServerStatus(this, serverStatus); + } + + if (TextUtils.isEmpty(request)) { + Log.e(TAG, "request for id " + id + " not found"); + return; + } + + /// Response to a "Object.GetStatus" message + if (request.equals("Client.GetStatus")) { + listener.onClientEvent(this, new Client(json.getJSONObject("result")), ClientEvent.updated); + } else if (request.equals("Group.GetStatus")) { + listener.onGroupUpdate(this, new Group(json.getJSONObject("result"))); + } else if (request.equals("Server.GetStatus")) { + listener.onServerStatus(this, new ServerStatus(json.getJSONObject("result"))); + } 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, new Client(result.getJSONObject("params")), ClientEvent.updated); + } else if ("Group.OnUpdate".equals(method)) { + listener.onGroupUpdate(this, new Group(result.getJSONObject("params"))); + } else if ("Server.OnUpdate".equals(method)) { + listener.onServerStatus(this, new ServerStatus(result.getJSONObject("params"))); } } } else { + /// Notification + if (listener == null) + return; String method = json.getString("method"); -// Log.d(TAG, "Notification: " + method); if (method.contains("Client.On")) { - final Client client = new Client(json.getJSONObject("params").getJSONObject("data")); -// serverStatus.addClient(client); - if (listener != null) { - ClientEvent event; - if (method.equals("Client.OnUpdate")) - listener.onClientEvent(this, client, ClientEvent.updated); - else if (method.equals("Client.OnConnect")) - listener.onClientEvent(this, client, ClientEvent.connected); - else if (method.equals("Client.OnDisconnect")) - listener.onClientEvent(this, client, ClientEvent.disconnected); - else if (method.equals("Client.OnDelete")) { - listener.onClientEvent(this, client, ClientEvent.deleted); - } - } + final Client client = new Client(json.getJSONObject("params")); + listener.onClientEvent(this, client, ClientEvent.fromString(method)); } else if (method.equals("Stream.OnUpdate")) { - Stream stream = new Stream(json.getJSONObject("params").getJSONObject("data")); - listener.onStreamUpdate(this, stream); - Log.d(TAG, stream.toString()); + listener.onStreamUpdate(this, new Stream(json.getJSONObject("params"))); + } else if (method.equals("Group.OnUpdate")) { + listener.onGroupUpdate(this, new Group(json.getJSONObject("params"))); + } else if (method.equals("Server.OnUpdate")) { + listener.onServerStatus(this, new ServerStatus(json.getJSONObject("params"))); } } @@ -157,7 +172,6 @@ public class RemoteControl implements TcpClient.TcpClientListener { @Override public void onConnected(TcpClient tcpClient) { Log.d(TAG, "onConnected"); - serverStatus = new ServerStatus(); if (listener != null) listener.onConnected(this); } @@ -165,7 +179,6 @@ public class RemoteControl implements TcpClient.TcpClientListener { @Override public void onDisconnected(TcpClient tcpClient, Exception e) { Log.d(TAG, "onDisconnected"); - serverStatus = null; if (listener != null) listener.onDisconnected(this, e); } @@ -262,10 +275,30 @@ public class RemoteControl implements TcpClient.TcpClientListener { } public enum ClientEvent { - connected, - disconnected, - updated, - deleted + connected("Client.OnConnect"), + disconnected("Client.OnDisconnect"), + updated("Client.OnUpdate"), + deleted("Client.OnDelete"); + private String text; + + ClientEvent(String text) { + this.text = text; + } + + public static ClientEvent fromString(String text) { + if (text != null) { + for (ClientEvent b : ClientEvent.values()) { + if (text.equalsIgnoreCase(b.text)) { + return b; + } + } + } + throw new IllegalArgumentException("No ClientEvent with text " + text + " found"); + } + + public String getText() { + return this.text; + } } public interface RemoteControlListener { @@ -280,5 +313,7 @@ public class RemoteControl implements TcpClient.TcpClientListener { void onServerStatus(RemoteControl remoteControl, ServerStatus serverStatus); void onStreamUpdate(RemoteControl remoteControl, Stream stream); + + void onGroupUpdate(RemoteControl remoteControl, Group group); } } diff --git a/server/json/jsonrpc.cpp b/server/json/jsonrpc.cpp index c46894f2..8f88d331 100644 --- a/server/json/jsonrpc.cpp +++ b/server/json/jsonrpc.cpp @@ -150,9 +150,7 @@ Json JsonNotification::getJson(const std::string& method, Json data) Json notification = { {"jsonrpc", "2.0"}, {"method", method}, - {"params", { - {"data", data} - }} + {"params", data} }; return notification; diff --git a/server/streamServer.cpp b/server/streamServer.cpp index 5aaee1a9..4ce5ea37 100644 --- a/server/streamServer.cpp +++ b/server/streamServer.cpp @@ -112,153 +112,191 @@ void StreamServer::onMessageReceived(ControlSession* controlSession, const std:: try { request.parse(message); - logD << "method: " << request.method << ", " << "id: " << request.id << "\n"; + logO << "method: " << request.method << ", " << "id: " << request.id << "\n"; - json response; - ClientInfoPtr clientInfo = nullptr; - GroupPtr group = nullptr; - msg::ServerSettings serverSettings; - serverSettings.setBufferMs(settings_.bufferMs); + json result; - if (request.method.find("Group.Set") == 0) + if (request.method.find("Client.") == 0) { - group = Config::instance().getGroup(request.getParam("group").get()); + ClientInfoPtr clientInfo = Config::instance().getClientInfo(request.getParam("client").get()); + if (clientInfo == nullptr) + throw JsonInternalErrorException("Client not found", request.id); + + if (request.method == "Client.GetStatus") + { + result = clientInfo->toJson(); + } + else if (request.method == "Client.SetVolume") + { + clientInfo->config.volume.fromJson(request.getParam("volume")); + } + else if (request.method == "Client.SetLatency") + { + clientInfo->config.latency = request.getParam("latency", -10000, settings_.bufferMs); + } + else if (request.method == "Client.SetName") + { + clientInfo->config.name = request.getParam("name").get(); + } + else + throw JsonMethodNotFoundException(request.id); + + + if (request.method.find("Client.Set") == 0) + { + /// Response: updated client + result = {{"method", "Client.OnUpdate"}, {"params", clientInfo->toJson()}}; + + /// Update client + session_ptr session = getStreamSession(request.getParam("client").get()); + if (session != nullptr) + { + msg::ServerSettings serverSettings; + serverSettings.setBufferMs(settings_.bufferMs); + serverSettings.setVolume(clientInfo->config.volume.percent); + serverSettings.setMuted(clientInfo->config.volume.muted); + serverSettings.setLatency(clientInfo->config.latency); + session->send(&serverSettings); + } + + /// Notify others + json notification = JsonNotification::getJson("Client.OnUpdate", clientInfo->toJson()); + logO << "Notification: " << notification.dump() << "\n"; + controlServer_->send(notification.dump(), controlSession); + } + } + else if (request.method.find("Group.") == 0) + { + GroupPtr group = Config::instance().getGroup(request.getParam("group").get()); if (group == nullptr) throw JsonInternalErrorException("Group not found", request.id); - } - if (request.method.find("Client.Set") == 0) - { - clientInfo = Config::instance().getClientInfo(request.getParam("client").get()); - if (clientInfo == nullptr) - throw JsonInternalErrorException("Client not found", request.id); - } - - if (request.method == "Server.GetStatus") - { - /// TODO: rpc - string clientId = request.hasParam("client") ? request.getParam("client").get() : ""; - response = Config::instance().getServerStatus(/*clientId,*/ streamManager_->toJson()); -// logO << response.dump(4); - } - else if (request.method == "Server.DeleteClient") - { - clientInfo = Config::instance().getClientInfo(request.getParam("client").get()); - if (clientInfo == nullptr) - throw JsonInternalErrorException("Client not found", request.id); - response = clientInfo->host.mac; - Config::instance().remove(clientInfo); - Config::instance().save(); - json notification = JsonNotification::getJson("Client.OnDelete", clientInfo->toJson()); - controlServer_->send(notification.dump(), controlSession); - clientInfo = nullptr; - } - else if (request.method == "Client.SetVolume") - { - clientInfo->config.volume.fromJson(request.getParam("volume")); - response = clientInfo->config.volume.toJson(); - } - else if (request.method == "Group.SetStream") - { - string streamId = request.getParam("id").get(); - PcmStreamPtr stream = streamManager_->getStream(streamId); - if (stream == nullptr) - throw JsonInternalErrorException("Stream not found", request.id); - - group->streamId = streamId; - response = group->streamId; - - for (auto client: group->clients) + if (request.method == "Group.GetStatus") { - session_ptr session = getStreamSession(client->id); - if (session && (session->pcmStream() != stream)) - { - session->sendAsync(stream->getHeader()); - session->setPcmStream(stream); - } + result = group->toJson(); } - } - else if (request.method == "Group.SetClients") - { - vector clients = request.getParam("clients").get>(); - string groupId = request.getParam("group").get(); - - GroupPtr group = Config::instance().getGroup(groupId); - /// Remove clients from group - for (auto iter = group->clients.begin(); iter != group->clients.end();) + else if (request.method == "Group.SetStream") { - auto client = *iter; - if (find(clients.begin(), clients.end(), client->id) != clients.end()) - { - ++iter; - continue; - } - iter = group->clients.erase(iter); - GroupPtr newGroup = Config::instance().addClientInfo(client); - newGroup->streamId = group->streamId; - } + string streamId = request.getParam("id").get(); + PcmStreamPtr stream = streamManager_->getStream(streamId); + if (stream == nullptr) + throw JsonInternalErrorException("Stream not found", request.id); - /// Add clients to group - PcmStreamPtr stream = streamManager_->getStream(group->streamId); - for (const auto& clientId: clients) + group->streamId = streamId; + + /// Response: updated group + result = {{"method", "Group.OnUpdate"}, {"params", group->toJson()}}; + + /// Update clients + for (auto client: group->clients) + { + session_ptr session = getStreamSession(client->id); + if (session && (session->pcmStream() != stream)) + { + session->sendAsync(stream->getHeader()); + session->setPcmStream(stream); + } + } + + /// Notify others + json notification = JsonNotification::getJson("Group.OnUpdate", group->toJson()); + logO << "Notification: " << notification.dump() << "\n"; + controlServer_->send(notification.dump(), controlSession); + } + else if (request.method == "Group.SetClients") { - ClientInfoPtr client = Config::instance().getClientInfo(clientId); - if (!client) - continue; - GroupPtr oldGroup = Config::instance().getGroupFromClient(client); - if (oldGroup && (oldGroup->id == groupId)) - continue; - - if (oldGroup) - { - oldGroup->removeClient(client); - Config::instance().remove(oldGroup); - } - - group->addClient(client); + vector clients = request.getParam("clients").get>(); + string groupId = request.getParam("group").get(); - /// assign new stream - session_ptr session = getStreamSession(client->id); - if (session && stream && (session->pcmStream() != stream)) + GroupPtr group = Config::instance().getGroup(groupId); + /// Remove clients from group + for (auto iter = group->clients.begin(); iter != group->clients.end();) { - session->sendAsync(stream->getHeader()); - session->setPcmStream(stream); + auto client = *iter; + if (find(clients.begin(), clients.end(), client->id) != clients.end()) + { + ++iter; + continue; + } + iter = group->clients.erase(iter); + GroupPtr newGroup = Config::instance().addClientInfo(client); + newGroup->streamId = group->streamId; } - } - if (group->empty()) - Config::instance().remove(group); -// response = Config::instance().getServerStatus(/*clientId,*/ streamManager_->toJson()); + /// Add clients to group + PcmStreamPtr stream = streamManager_->getStream(group->streamId); + for (const auto& clientId: clients) + { + ClientInfoPtr client = Config::instance().getClientInfo(clientId); + if (!client) + continue; + GroupPtr oldGroup = Config::instance().getGroupFromClient(client); + if (oldGroup && (oldGroup->id == groupId)) + continue; + + if (oldGroup) + { + oldGroup->removeClient(client); + Config::instance().remove(oldGroup); + } + + group->addClient(client); + + /// assign new stream + session_ptr session = getStreamSession(client->id); + if (session && stream && (session->pcmStream() != stream)) + { + session->sendAsync(stream->getHeader()); + session->setPcmStream(stream); + } + } + + if (group->empty()) + Config::instance().remove(group); + + json serverJson = Config::instance().getServerStatus(streamManager_->toJson()); + result = {{"method", "Server.OnUpdate"}, {"params", serverJson}}; + + /// Notify others: since at least two groups are affected, send a complete server update + json notification = JsonNotification::getJson("Server.OnUpdate", serverJson); + logO << "Notification: " << notification.dump() << "\n"; + controlServer_->send(notification.dump(), controlSession); + } + else + throw JsonMethodNotFoundException(request.id); } - else if (request.method == "Client.SetLatency") + else if (request.method.find("Server.") == 0) { - clientInfo->config.latency = request.getParam("latency", -10000, settings_.bufferMs); - response = clientInfo->config.latency; - } - else if (request.method == "Client.SetName") - { - clientInfo->config.name = request.getParam("name").get(); - response = clientInfo->config.name; + if (request.method == "Server.GetStatus") + { + result = Config::instance().getServerStatus(streamManager_->toJson()); + } + else if (request.method == "Server.DeleteClient") + { + ClientInfoPtr clientInfo = Config::instance().getClientInfo(request.getParam("client").get()); + if (clientInfo == nullptr) + throw JsonInternalErrorException("Client not found", request.id); + + Config::instance().remove(clientInfo); + + json serverJson = Config::instance().getServerStatus(streamManager_->toJson()); + result = {{"method", "Server.OnUpdate"}, {"params", serverJson}}; + + /// Notify others + json notification = JsonNotification::getJson("Server.OnUpdate", serverJson); + logO << "Notification: " << notification.dump() << "\n"; + controlServer_->send(notification.dump(), controlSession); + } + else + throw JsonMethodNotFoundException(request.id); } else throw JsonMethodNotFoundException(request.id); - if (clientInfo != nullptr) - { - serverSettings.setVolume(clientInfo->config.volume.percent); - serverSettings.setMuted(clientInfo->config.volume.muted); - serverSettings.setLatency(clientInfo->config.latency); - - session_ptr session = getStreamSession(request.getParam("client").get()); - if (session != nullptr) - session->send(&serverSettings); - - Config::instance().save(); - json notification = JsonNotification::getJson("Client.OnUpdate", clientInfo->toJson()); - controlServer_->send(notification.dump(), controlSession); - } - - controlSession->send(request.getResponse(response).dump()); + Config::instance().save(); + string responseJson = request.getResponse(result).dump(); + logO << "Response: " << responseJson << "\n"; + controlSession->send(responseJson); } catch (const JsonRequestException& e) { @@ -394,7 +432,7 @@ void StreamServer::handleAccept(socket_ptr socket) setsockopt(socket->native_handle(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); setsockopt(socket->native_handle(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); - /// experimental: turn on tcp::no_delay + /// experimental: turn on tcp::no_delay socket->set_option(tcp::no_delay(true)); logS(kLogNotice) << "StreamServer::NewConnection: " << socket->remote_endpoint().address().to_string() << endl; @@ -465,7 +503,7 @@ void StreamServer::stop() controlServer_->stop(); controlServer_ = nullptr; } - + if (acceptor_) { acceptor_->cancel();