Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 54 additions & 1 deletion docs/JSON-RPC.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,42 @@ Results:
| result.version | string | The Jamulus version. |


### jamulusclient/connect

Connects to a server previously discovered via jamulusclient/pollServerList on the given directory. Blocks for a short time (up to a few seconds) while the outcome of the connection attempt is determined.

Parameters:

| Name | Type | Description |
| --- | --- | --- |
| params.directory | string | Socket address of the directory the server was discovered from. Must have already been queried via jamulusclient/pollServerList. |
| params.server | string | Socket address of the server to connect to, as returned in params.servers[*].address of jamulusclient/serverListReceived. |

Results:

| Name | Type | Description |
| --- | --- | --- |
| result | string | "ok"; "Not Found" if the directory hasn't been polled, or the server address is invalid; "Unauthorized" if the server requires accepting a licence, which isn't supported over this API; "Gone (server no longer listed)" if the server is not, or is no longer, present in the directory's server list; "Upgrade Required (obsolete protocol, upgrade Jamulus)" if the server requires a newer protocol version than this client supports; or "Insufficient Storage (Full)" if the server is full. If the local audio interface fails to start, an error is returned instead of a result. |


### jamulusclient/disconnect

Disconnects from the server that was connected via jamulusclient/connect.

Parameters:

| Name | Type | Description |
| --- | --- | --- |
| params.directory | string | Socket address of the directory that was passed to jamulusclient/connect. |
| params.server | string | Socket address of the server that was passed to jamulusclient/connect. |

Results:

| Name | Type | Description |
| --- | --- | --- |
| result | string | "ok"; "Not Found" if params.directory/params.server do not match the connection established by the most recent jamulusclient/connect call; or, reflecting the outcome of that call, "Unauthorized", "Gone (server no longer listed)", or "Upgrade Required (obsolete protocol, upgrade Jamulus)". |


### jamulusclient/getChannelInfo

Returns the client's profile information.
Expand All @@ -143,7 +179,7 @@ Results:

| Name | Type | Description |
| --- | --- | --- |
| result.id | number | The channel ID. |
| result.id | number | The channel ID assigned by the server, or -1 if not currently connected. |
| 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). |
Expand Down Expand Up @@ -188,6 +224,23 @@ Results:
| result.clients | array | The client list. See jamulusclient/clientListReceived for the format. |


### jamulusclient/getDirectories

Returns the list of directories in the same order as presented in Jamulus.

Parameters:

| Name | Type | Description |
| --- | --- | --- |
| params | object | No parameters (empty object). |

Results:

| Name | Type | Description |
| --- | --- | --- |
| result | array | Array of directory socket address strings, usable as params.directory in jamulusclient/pollServerList. |


### jamulusclient/getMidiDevices

Returns a list of available MIDI input devices.
Expand Down
122 changes: 121 additions & 1 deletion src/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
#include "client.h"
#include "settings.h"
#include "util.h"
#include <QEventLoop>
#include <QVersionNumber>

/* Implementation *************************************************************/
CClient::CClient ( const quint16 iPortNumber,
Expand Down Expand Up @@ -92,7 +94,8 @@ CClient::CClient ( const quint16 iPortNumber,
bJitterBufferOK ( true ),
bMuteMeInPersonalMix ( bNMuteMeInPersonalMix ),
iServerSockBufNumFrames ( DEF_NET_BUF_SIZE_NUM_BL ),
bRawAudioIsSupported ( false )
bRawAudioIsSupported ( false ),
iMyChannelID ( INVALID_INDEX )
{
int iOpusError;

Expand Down Expand Up @@ -1003,6 +1006,8 @@ void CClient::OnClientIDReceived ( int iServerChanID )
SetRemoteChanGain ( iChanID, 0, false );
}

iMyChannelID = iChanID;

emit ClientIDReceived ( iChanID );
}

Expand Down Expand Up @@ -1059,6 +1064,9 @@ void CClient::Stop()
bRawAudioIsSupported = false;
Init();

// no longer connected, so no valid channel ID (see GetMyChannelID())
iMyChannelID = INVALID_INDEX;

// wait for approx. 100 ms to make sure no audio packet is still in the
// network queue causing the channel to be reconnected right after having
// received the disconnect message (seems not to gain much, disconnect is
Expand Down Expand Up @@ -1088,6 +1096,118 @@ void CClient::Stop()
// Allow hibernation or display dimming if the app is running again (Windows)
SetThreadExecutionState ( ES_CONTINUOUS );
#endif

// Let every caller of Stop() (the UI's Disconnect button, the JSON-RPC API, or our own
// ConnectToServer() dropping a stale connection before reconnecting) rely on this single
// signal for post-disconnect cleanup, instead of each having to duplicate it. This is in
// addition to the same signal being emitted when the channel notices a timeout on its own.
emit Disconnected();
}

CClient::EConnectResult CClient::ConnectToServer ( const QString& strServerAddress, QString* pstrErrorMessage )
{
if ( IsRunning() )
{
Stop();
}

if ( !SetServerAddr ( strServerAddress ) )
{
return CR_INVALID_ADDRESS;
}

// Wait for the connection attempt to resolve into a known outcome, collecting the result from
// whichever signal the connection process triggers first. On success, we wait for both the client
// ID and the version info so a server that turns out to require a newer client is still caught
// rather than being missed because we already quit the loop.
bool bGotClientID = false;
bool bGotVersion = false;
EConnectResult eResult = CR_OK;
QEventLoop loop;

auto conClientID = connect ( this, &CClient::ClientIDReceived, [&] ( int /* unused */ ) {
bGotClientID = true;
if ( bGotVersion )
{
loop.quit();
}
} );

auto conVersion = connect ( this, &CClient::VersionAndOSReceived, [&] ( COSUtil::EOpSystemType /* unused */, QString strServerVersion ) {
int iServerSuffixIndex;
QVersionNumber serverVersion = QVersionNumber::fromString ( strServerVersion, &iServerSuffixIndex );

int iMySuffixIndex;
QVersionNumber myVersion = QVersionNumber::fromString ( VERSION, &iMySuffixIndex );

// only compare release versions (a dev/beta suffix means we cannot reliably compare)
if ( ( strServerVersion.size() == iServerSuffixIndex ) && ( QVersionNumber::compare ( serverVersion, myVersion ) > 0 ) )
{
eResult = CR_UPGRADE_REQUIRED;
loop.quit();
return;
}

bGotVersion = true;
if ( bGotClientID )
{
loop.quit();
}
} );

auto conFull = connect ( getConnLessProtocol(), &CProtocol::ServerFullMesReceived, [&]() {
eResult = CR_FULL;
loop.quit();
} );

auto conLicence = connect ( this, &CClient::LicenceRequired, [&] ( ELicenceType /* unused */ ) {
eResult = CR_UNAUTHORIZED;
loop.quit();
} );

auto conGone = connect ( getConnLessProtocol(), &CProtocol::CLDisconnection, [&] ( CHostAddress InetAddr ) {
if ( InetAddr.toString() == strServerAddress )
{
eResult = CR_GONE;
loop.quit();
}
} );

QTimer::singleShot ( 3000, &loop, &QEventLoop::quit );

try
{
Start();
loop.exec();
}
catch ( const CGenErr& generr )
{
disconnect ( conClientID );
disconnect ( conVersion );
disconnect ( conFull );
disconnect ( conLicence );
disconnect ( conGone );

if ( pstrErrorMessage )
{
*pstrErrorMessage = generr.GetErrorText();
}
return CR_SOUND_DEVICE_ERROR;
}

disconnect ( conClientID );
disconnect ( conVersion );
disconnect ( conFull );
disconnect ( conLicence );
disconnect ( conGone );

if ( ( eResult == CR_OK ) && !bGotClientID )
{
// no response of any kind before the timeout
eResult = CR_GONE;
}

return eResult;
}

void CClient::Init()
Expand Down
32 changes: 32 additions & 0 deletions src/client.h
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,35 @@ class CClient : public QObject
bool IsCallbackEntered() const { return Sound.IsCallbackEntered(); }
bool SetServerAddr ( QString strNAddr );

// The address most recently passed to SetServerAddr()/ConnectToServer(), i.e. the server we are
// currently connected (or attempting to connect) to.
const CHostAddress& GetServerAddress() const { return Channel.GetAddress(); }

// The client channel ID assigned to us by the server (see the ClientIDReceived signal), or
// INVALID_INDEX if not currently connected.
int GetMyChannelID() const { return iMyChannelID; }

// Outcome of ConnectToServer(). Used by both the client UI and the JSON-RPC API so they agree on
// what a connection attempt resulted in.
enum EConnectResult
{
CR_OK,
CR_INVALID_ADDRESS, // strServerAddress could not be parsed
CR_SOUND_DEVICE_ERROR, // the audio interface could not be started (see pstrErrorMessage)
CR_UNAUTHORIZED, // the server requires accepting a licence before use
CR_GONE, // the server did not respond, or told us it is disconnecting us
CR_UPGRADE_REQUIRED, // the server requires a newer protocol version than this client supports
CR_FULL // the server is full
};

// Sets the server address and starts the client, then blocks for a short time (up to a few seconds)
// while the outcome of the connection attempt is determined. On CR_SOUND_DEVICE_ERROR, *pstrErrorMessage
// (if given) is set to a human-readable description of the audio failure. Does not stop the client on a
// non-CR_OK outcome; callers decide whether/when to give up on the attempt (e.g. the UI keeps the
// connection alive on CR_UNAUTHORIZED so the licence dialog, wired independently to LicenceRequired,
// can still let the user accept and continue).
EConnectResult ConnectToServer ( const QString& strServerAddress, QString* pstrErrorMessage = nullptr );

// IPv6 Available
bool IsIPv6Available() { return bIPv6Available; }

Expand Down Expand Up @@ -439,6 +468,9 @@ class CClient : public QObject
int iServerSockBufNumFrames;
bool bRawAudioIsSupported;

// see GetMyChannelID()
int iMyChannelID;

// for ping measurement
QElapsedTimer PreciseTime;

Expand Down
Loading
Loading