diff --git a/docs/JSON-RPC.md b/docs/JSON-RPC.md index 36cc18e087..b1fd6d08a0 100644 --- a/docs/JSON-RPC.md +++ b/docs/JSON-RPC.md @@ -129,6 +129,57 @@ Results: | result.version | string | The Jamulus version. | +### jamulus/pollServerList + +Request list of servers in a directory + +Parameters: + +| Name | Type | Description | +| --- | --- | --- | +| params.directory | string | socket address of directory to query, e.g. anygenre1.jamulus.io:22124. | + +Results: + +| Name | Type | Description | +| --- | --- | --- | +| result | string | "ok" or "error" if bad arguments. | + + +### jamulusclient/connect + +Disconnect client from server + +Parameters: + +| Name | Type | Description | +| --- | --- | --- | +| params.address | string | Server socket address (ip_addr:port). | + +Results: + +| Name | Type | Description | +| --- | --- | --- | +| result | string | Always "ok". | + + +### jamulusclient/disconnect + +Disconnect client from server + +Parameters: + +| Name | Type | Description | +| --- | --- | --- | +| params | object | No parameters (empty object). | + +Results: + +| Name | Type | Description | +| --- | --- | --- | +| result | string | Always "ok". | + + ### jamulusclient/getChannelInfo Returns the client's profile information. @@ -147,8 +198,10 @@ Results: | result.name | string | The musician’s name. | | result.skillLevel | string | The musician’s skill level (beginner, intermediate, expert, or null). | | result.countryId | number | The musician’s country ID (see QLocale::Country). | +| result.country | string | The musician’s country. | | result.city | string | The musician’s city. | | result.instrumentId | number | The musician’s instrument ID (see CInstPictures::GetTable). | +| result.instrument | string | The musician’s instrument. | | result.skillLevel | string | Your skill level (beginner, intermediate, expert, or null). | @@ -445,8 +498,10 @@ Parameters: | params.clients[*].name | string | The musician’s name. | | params.clients[*].skillLevel | string | The musician’s skill level (beginner, intermediate, expert, or null). | | params.clients[*].countryId | number | The musician’s country ID (see QLocale::Country). | +| params.clients[*].country | string | The musician’s country. | | params.clients[*].city | string | The musician’s city. | | params.clients[*].instrumentId | number | The musician’s instrument ID (see CInstPictures::GetTable). | +| params.clients[*].instrument | string | The musician’s instrument. | ### jamulusclient/connected @@ -471,3 +526,43 @@ Parameters: | params | object | No parameters (empty object). | +### jamulusclient/recorderState + +Emitted when a server's recorder state changes. + +Parameters: + +| Name | Type | Description | +| --- | --- | --- | +| params.state | number | The recorder state | + + +### jamulusclient/serverInfoReceived + +Emitted when a server info is received. + +Parameters: + +| Name | Type | Description | +| --- | --- | --- | +| params.address | string | The server socket address | +| params.pingtime | number | The round-trip ping time in ms | +| params.numClients | number | The quantity of clients connected to the server | + + +### jamulusclient/serverListReceived + +Emitted when the server list is received. + +Parameters: + +| Name | Type | Description | +| --- | --- | --- | +| params.servers | array | The server list. | +| params.servers[*].address | string | Socket address (ip_address:port) | +| params.servers[*].name | string | Server name | +| params.servers[*].countryId | number | Server country ID (see QLocale::Country). | +| params.servers[*].country | string | Server country | +| params.servers[*].city | string | Server city | + + diff --git a/src/client.h b/src/client.h index 1fc33d8430..dfb0e6fa39 100644 --- a/src/client.h +++ b/src/client.h @@ -271,6 +271,8 @@ class CClient : public QObject Channel.GetBufErrorRates ( vecErrRates, dLimit, dMaxUpLimit ); } + CProtocol* getConnLessProtocol() { return &ConnLessProtocol; } + // settings CChannelCoreInfo ChannelInfo; QString strClientName; diff --git a/src/clientrpc.cpp b/src/clientrpc.cpp index ee992cbfd9..b3f3f3cbff 100644 --- a/src/clientrpc.cpp +++ b/src/clientrpc.cpp @@ -54,8 +54,10 @@ CClientRpc::CClientRpc ( CClient* pClient, CRpcServer* pRpcServer, QObject* pare /// @param {string} params.clients[*].name - The musician’s name. /// @param {string} params.clients[*].skillLevel - The musician’s skill level (beginner, intermediate, expert, or null). /// @param {number} params.clients[*].countryId - The musician’s country ID (see QLocale::Country). + /// @param {string} params.clients[*].country - The musician’s country. /// @param {string} params.clients[*].city - The musician’s city. /// @param {number} params.clients[*].instrumentId - The musician’s instrument ID (see CInstPictures::GetTable). + /// @param {string} params.clients[*].instrument - The musician’s instrument. connect ( pClient, &CClient::ConClientListMesReceived, [=] ( CVector vecChanInfo ) { QJsonArray arrChanInfo; for ( const auto& chanInfo : vecChanInfo ) @@ -65,8 +67,10 @@ CClientRpc::CClientRpc ( CClient* pClient, CRpcServer* pRpcServer, QObject* pare { "name", chanInfo.strName }, { "skillLevel", SerializeSkillLevel ( chanInfo.eSkillLevel ) }, { "countryId", chanInfo.eCountry }, + { "country", QLocale::countryToString ( chanInfo.eCountry ) }, { "city", chanInfo.strCity }, { "instrumentId", chanInfo.iInstrument }, + { "instrument", CInstPictures::GetName ( chanInfo.iInstrument ) }, }; arrChanInfo.append ( objChanInfo ); } @@ -94,11 +98,127 @@ CClientRpc::CClientRpc ( CClient* pClient, CRpcServer* pRpcServer, QObject* pare } ); } ); + /// @rpc_notification jamulusclient/serverListReceived + /// @brief Emitted when the server list is received. + /// @param {array} params.servers - The server list. + /// @param {string} params.servers[*].address - Socket address (ip_address:port) + /// @param {string} params.servers[*].name - Server name + /// @param {number} params.servers[*].countryId - Server country ID (see QLocale::Country). + /// @param {string} params.servers[*].country - Server country + /// @param {string} params.servers[*].city - Server city + connect ( pClient->getConnLessProtocol(), + &CProtocol::CLServerListReceived, + [=] ( CHostAddress /* unused */, CVector vecServerInfo ) { + QJsonArray arrServerInfo; + for ( const auto& serverInfo : vecServerInfo ) + { + QJsonObject objServerInfo{ + { "address", serverInfo.HostAddr.toString() }, + { "name", serverInfo.strName }, + { "countryId", serverInfo.eCountry }, + { "country", QLocale::countryToString ( serverInfo.eCountry ) }, + { "city", serverInfo.strCity }, + }; + arrServerInfo.append ( objServerInfo ); + pClient->CreateCLServerListPingMes ( serverInfo.HostAddr ); + } + pRpcServer->BroadcastNotification ( "jamulusclient/serverListReceived", + QJsonObject{ + { "servers", arrServerInfo }, + } ); + } ); + + /// @rpc_notification jamulusclient/serverInfoReceived + /// @brief Emitted when a server info is received. + /// @param {string} params.address - The server socket address + /// @param {number} params.pingtime - The round-trip ping time in ms + /// @param {number} params.numClients - The quantity of clients connected to the server + connect ( pClient, &CClient::CLPingTimeWithNumClientsReceived, [=] ( CHostAddress InetAddr, int iPingTime, int iNumClients ) { + pRpcServer->BroadcastNotification ( + "jamulusclient/serverInfoReceived", + QJsonObject{ { "address", InetAddr.toString() }, { "pingTime", iPingTime }, { "numClients", iNumClients } } ); + } ); + /// @rpc_notification jamulusclient/disconnected /// @brief Emitted when the client is disconnected from the server. /// @param {object} params - No parameters (empty object). connect ( pClient, &CClient::Disconnected, [=]() { pRpcServer->BroadcastNotification ( "jamulusclient/disconnected", QJsonObject{} ); } ); + /// @rpc_notification jamulusclient/recorderState + /// @brief Emitted when the client is connected to a server who's recorder state changes. + /// @param {number} params.state - The recorder state + connect ( pClient, &CClient::RecorderStateReceived, [=] ( const ERecorderState newRecorderState ) { + pRpcServer->BroadcastNotification ( "jamulusclient/recorderState", QJsonObject{ { "state", newRecorderState } } ); + } ); + + /// @rpc_method jamulus/pollServerList + /// @brief Request list of servers in a directory + /// @param {string} params.directory - socket address of directory to query, e.g. anygenre1.jamulus.io:22124. + /// @result {string} result - "ok" or "error" if bad arguments. + pRpcServer->HandleMethod ( "jamulusclient/pollServerList", [=] ( const QJsonObject& params, QJsonObject& response ) { + auto jsonDirectoryIp = params["directory"]; + if ( !jsonDirectoryIp.isString() ) + { + response["error"] = CRpcServer::CreateJsonRpcError ( CRpcServer::iErrInvalidParams, "Invalid params: directory is not a string" ); + return; + } + + CHostAddress haDirectoryAddress; + if ( NetworkUtil().ParseNetworkAddress ( jsonDirectoryIp.toString(), haDirectoryAddress, false ) ) + { + // send the request for the server list + pClient->CreateCLReqServerListMes ( haDirectoryAddress ); + response["result"] = "ok"; + } + else + { + response["error"] = + CRpcServer::CreateJsonRpcError ( CRpcServer::iErrInvalidParams, "Invalid params: directory is not a valid socket address" ); + } + + response["result"] = "ok"; + } ); + + /// @rpc_method jamulusclient/connect + /// @brief Connect client to server + /// @param {string} params.address - Server socket address (ip_addr:port). + /// @result {string} result - Always "ok". + pRpcServer->HandleMethod ( "jamulusclient/connect", [=] ( const QJsonObject& params, QJsonObject& response ) { + auto jsonAddr = params["address"]; + if ( !jsonAddr.isString() ) + { + response["error"] = CRpcServer::CreateJsonRpcError ( CRpcServer::iErrInvalidParams, "Invalid params: address is not a string" ); + return; + } + + if ( pClient->SetServerAddr ( jsonAddr.toString() ) ) + { + if ( !pClient->IsRunning() ) + { + pClient->Start(); + } + response["result"] = "ok"; + } + else + { + response["error"] = CRpcServer::CreateJsonRpcError ( 1, "Bad server address" ); + } + } ); + + /// @rpc_method jamulusclient/disconnect + /// @brief Disconnect client from server + /// @param {object} params - No parameters (empty object). + /// @result {string} result - Always "ok". + pRpcServer->HandleMethod ( "jamulusclient/disconnect", [=] ( const QJsonObject& params, QJsonObject& response ) { + if ( pClient->IsRunning() ) + { + pClient->Stop(); + } + + response["result"] = "ok"; + Q_UNUSED ( params ); + } ); + /// @rpc_method jamulus/getMode /// @brief Returns the current mode, i.e. whether Jamulus is running as a server or client. /// @param {object} params - No parameters (empty object). @@ -126,16 +246,20 @@ CClientRpc::CClientRpc ( CClient* pClient, CRpcServer* pRpcServer, QObject* pare /// @result {string} result.name - The musician’s name. /// @result {string} result.skillLevel - The musician’s skill level (beginner, intermediate, expert, or null). /// @result {number} result.countryId - The musician’s country ID (see QLocale::Country). + /// @result {string} result.country - The musician’s country. /// @result {string} result.city - The musician’s city. /// @result {number} result.instrumentId - The musician’s instrument ID (see CInstPictures::GetTable). + /// @result {string} result.instrument - The musician’s instrument. /// @result {string} result.skillLevel - Your skill level (beginner, intermediate, expert, or null). pRpcServer->HandleMethod ( "jamulusclient/getChannelInfo", [=] ( const QJsonObject& params, QJsonObject& response ) { QJsonObject result{ - // TODO: We cannot include "id" here is pClient->ChannelInfo is a CChannelCoreInfo which lacks that field. + // TODO: We cannot include "id" here as pClient->ChannelInfo is a CChannelCoreInfo which lacks that field. { "name", pClient->ChannelInfo.strName }, { "countryId", pClient->ChannelInfo.eCountry }, + { "country", QLocale::countryToString ( pClient->ChannelInfo.eCountry ) }, { "city", pClient->ChannelInfo.strCity }, { "instrumentId", pClient->ChannelInfo.iInstrument }, + { "instrument", CInstPictures::GetName ( pClient->ChannelInfo.iInstrument ) }, { "skillLevel", SerializeSkillLevel ( pClient->ChannelInfo.eSkillLevel ) }, }; response["result"] = result;