New upstream version 1.7.0+ds
authorIOhannes m zmölnig (Debian/GNU) <umlaeute@debian.org>
Fri, 27 Jan 2023 08:13:32 +0000 (09:13 +0100)
committerIOhannes m zmölnig (Debian/GNU) <umlaeute@debian.org>
Fri, 27 Jan 2023 08:13:32 +0000 (09:13 +0100)
34 files changed:
CMakeLists.txt
build
docs/changelog.yml
jacktrip.pro
meson.build
releases/edge/mac-manifests.json
releases/edge/win-manifests.json
releases/stable/linux-manifests.json
releases/stable/mac-manifests.json
releases/stable/win-manifests.json
src/JackTrip.cpp
src/JackTrip.h
src/RtAudioInterface.cpp
src/Settings.cpp
src/freeverbmonodsp.h
src/gui/AudioInterfaceMode.h [new file with mode: 0644]
src/gui/Browse.qml
src/gui/Connected.qml
src/gui/Settings.qml
src/gui/Setup.qml
src/gui/Studio.qml
src/gui/join.svg
src/gui/qjacktrip.qrc
src/gui/start.svg [new file with mode: 0644]
src/gui/video.svg [new file with mode: 0644]
src/gui/virtualstudio.cpp
src/gui/virtualstudio.h
src/gui/vsAudioInterface.cpp
src/gui/vsAudioInterface.h
src/gui/vsDevice.cpp
src/gui/vsDevice.h
src/gui/vsServerInfo.cpp
src/gui/vsServerInfo.h
src/jacktrip_globals.h

index 456762b2ddf1850322c5045f7f768c1ede3a2b49..0ea77b841f59724058c14f8e820fc0f4ce539eb2 100644 (file)
@@ -1,5 +1,6 @@
 cmake_minimum_required(VERSION 3.12)
 set(CMAKE_OSX_DEPLOYMENT_TARGET 10.14)
+set(CMAKE_CXX_STANDARD 17)
 project(QJackTrip)
 
 set(nogui FALSE)
@@ -186,6 +187,7 @@ if (NOT nogui)
       src/gui/vsAudioInterface.cpp
       src/gui/vsUrlHandler.cpp
       src/gui/vsWebSocket.cpp
+      src/gui/vsPermissions.cpp
       src/gui/qjacktrip.qrc
       src/Volume.cpp
       src/Tone.cpp
@@ -193,6 +195,9 @@ if (NOT nogui)
       src/JTApplication.h
       src/gui/vsQmlClipboard.h
     )
+    if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
+      set (qjacktrip_SRC ${qjacktrip_SRC} src/gui/vsMacPermissions.mm)
+    endif ()
   else ()
     set (qjacktrip_SRC ${qjacktrip_SRC} src/gui/qjacktrip_novs.qrc)
   endif ()
@@ -219,6 +224,9 @@ if (NOT nogui)
     set (qjacktrip_SRC ${qjacktrip_SRC} src/gui/NoNap.mm)
     set (CMAKE_C_FLAGS "-x objective-c")
     set (CMAKE_EXE_LINKER_FLAGS "-framework Foundation")
+    if (NOT novs)
+      set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework AVFoundation")
+    endif ()
   endif ()
 endif ()
 
diff --git a/build b/build
index cd05509e74dc3aa88c8666eaa18cda24d58caabd..e1261fb4ae203804c40ed774af9eb227715c2958 100755 (executable)
--- a/build
+++ b/build
@@ -4,7 +4,7 @@
 # Parse command line options
 clean=1
 install=0
-CONFIG=
+CONFIG="DEFINES+=JACKTRIP_BUILD_INFO=\\\"$(git describe --tags)-$(git rev-parse --short HEAD)\\\""
 UNKNOWN_OPTIONS=
 BUILD_RTAUDIO=0
 RTAUDIO=0
index fb8bfd903b08b5d44b6dfe02edc3ebb530cd0bd9..dd9bc72db07a7d7af0a4f7e5f72d5a181a5ca07e 100644 (file)
@@ -1,3 +1,24 @@
+- Version: "1.7.0"
+  Date: 2023-01-20
+  Description:
+  - (added) VS Mode - Start and join inactive studios
+  - (added) JackTrip now prints build info on running from console
+  - (added) VS Mode - supports changing output volume from the server
+  - (added) VS Mode - Link to video on VS web when connected
+  - (updated) signing now happens in the main build workflow
+  - (updated) VS mode sorts active studios above inactive studios
+  - (updated) cmake build
+  - (updated) meson builds will fail if no backend is enabled
+  - (updated) replaced many ifdefs with if constexpr
+  - (updated) After signing out of VS mode, you will be asked to sign back in on the web
+  - (fixed) network stats failing after studio start
+  - (fixed) occasional immediate disconnects
+  - (fixed) segfault issue due to ifdefs
+  - (fixed) Hanging UI on Windows
+  - (fixed) turned a comment warning into an appropriate error
+  - (fixed) VS Mode - Join issue withs studios started in app
+  - (fixed) hanging app after refreshing studios
+  - (fixed) VS Mode - TCP 19 error after starting a studio
 - Version: "1.6.8"
   Date: 2022-12-05
   Description:
index fca54203d1b64fc7ab59fa968630453f9efeb949..d7c56f0f06e306da089dfc16d1ea9e11ae72928a 100644 (file)
@@ -2,7 +2,7 @@
 # Created by Juan-Pablo Caceres
 #******************************
 
-CONFIG += c++11 console
+CONFIG += c++17 console
 CONFIG -= app_bundle
 
 CONFIG += qt thread debug_and_release build_all
@@ -151,7 +151,7 @@ linux-g++-64 {
 win32 {
   message(Building on win32)
 #cc  CONFIG += x86 console
-  CONFIG += c++11 console
+  CONFIG += c++17 console
   exists("C:\Program Files\JACK2") {
     message("using Jack in C:\Program Files\JACK2")
     INCLUDEPATH += "C:\Program Files\JACK2\include"
index 864f9d605291947d65be39ba7b4ce9b4ec8efa45..dc19dc26c01a38275b0086b915aee78257fa5176 100644 (file)
@@ -182,6 +182,13 @@ if rtaudio_dep.found() == true
        deps += rtaudio_dep
 endif
 
+if rtaudio_dep.found() == false and jack_dep.found() == false
+       error('''
+       JackTrip requires at least one available audio backend. Install the
+       appropriate library or enable the appropriate backends using meson
+       configure.''')
+endif
+
 if host_machine.system() == 'darwin'
        src += ['src/gui/NoNap.mm']
        # Adding CoreAudio here is a workaround and should be removed
index 931144e87de9e65e2769de2fbda30ef8ff7ee04e..47fdfc4df4cf9906435aaacf4a2c37b294c7e17f 100644 (file)
@@ -1,6 +1,56 @@
 {
     "app_name": "JackTrip",
     "releases": [
+        {
+            "version": "1.7.0-rc1",
+            "changelog": "Start/join inactive studios, fix crashes and hanging UI on Windows: https://github.com/jacktrip/jacktrip/releases/tag/v1.7.0-rc1",
+            "download": {
+                "date": "2022-01-20T00:00:00Z",
+                "url": "https://github.com/jacktrip/jacktrip/releases/download/v1.7.0-rc1/JackTrip-v1.7.0-rc1-macOS-x64-installer.pkg",
+                "downloadSize": 11530680,
+                "sha256": "406134ee2017bcb762f968f893bc28463149d7567dd33b92f963ca9e09608636"
+            }
+        },
+        {
+            "version": "1.6.9-beta3",
+            "changelog": "Start/join inactive studios, fix crashes and hanging UI on Windows: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.9-beta3",
+            "download": {
+                "date": "2022-01-18T00:00:00Z",
+                "url": "https://github.com/jacktrip/jacktrip/releases/download/v1.6.9-beta3/JackTrip-v1.6.9-beta3-macOS-x64-installer.pkg",
+                "downloadSize": 11528836,
+                "sha256": "30689d83641377c1594e1db44d8e6cf75a45780969381d02c11ede81a175561f"
+            }
+        },
+        {
+            "version": "1.6.9-beta2",
+            "changelog": "Start/join inactive studios, fix crashes and hanging UI on Windows: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.9-beta2",
+            "download": {
+                "date": "2022-01-10T00:00:00Z",
+                "url": "https://github.com/jacktrip/jacktrip/releases/download/v1.6.9-beta2/JackTrip-v1.6.9-beta2-macOS-x64-installer.pkg",
+                "downloadSize": 11528427,
+                "sha256": "884e5c0cf3ea5bc82b348a739df3ba11238074a9552dcb1d1f250f484be89b77"
+            }
+        },
+        {
+            "version": "1.6.9-beta1",
+            "changelog": "Start/join inactive studios, fix crashes and hanging UI on Windows: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.9-beta1",
+            "download": {
+                "date": "2022-12-16T00:00:00Z",
+                "url": "https://files.jacktrip.org/app-builds/JackTrip-v1.6.9-beta1-macOS-x64-installer.pkg",
+                "downloadSize": 11527211,
+                "sha256": "636055dee6fb84286cc73a95c887dd39c4a6d14780c451504db87860738b2278"
+            }
+        },
+        {
+            "version": "1.6.8",
+            "changelog": "Critical bug fix: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.8",
+            "download": {
+                "date": "2022-12-05T00:00:00Z",
+                "url": "https://files.jacktrip.org/app-builds/JackTrip-v1.6.8-macOS-x64-installer.pkg",
+                "downloadSize": 11517093,
+                "sha256": "5c040b2caa1e7dea97d7f48fd888f532a1c30da7385535b2d4a2805551bc5f71"
+            }
+        },
         {
             "version": "1.6.7",
             "changelog": "Updated Windows networking, enabling all audio devices on Windows, new default device behavior, and fixes: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.7",
             }
         }
     ]
-}
\ No newline at end of file
+}
index 24ff18e14129615f05c07536ef00f8bb3f420082..46ff9f2900f5e6f7f1544cafe490c32492b025b0 100644 (file)
@@ -1,6 +1,56 @@
 {
     "app_name": "JackTrip",
     "releases": [
+        {
+            "version": "1.7.0-rc1",
+            "changelog": "Start/join inactive studios, fix crashes and hanging UI on Windows: https://github.com/jacktrip/jacktrip/releases/tag/v1.7.0-rc1",
+            "download": {
+                "date": "2022-01-20T00:00:00Z",
+                "url": "https://github.com/jacktrip/jacktrip/releases/download/v1.7.0-rc1/JackTrip-v1.7.0-rc1-Windows-x64-installer.msi",
+                "downloadSize": 44556288,
+                "sha256": "edba383791a598954d129d39024e87f3c062985d10f47dbea43f3d226ee37c6c"
+            }
+        },
+        {
+            "version": "1.6.9-beta3",
+            "changelog": "Start/join inactive studios, fix crashes and hanging UI on Windows: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.9-beta3",
+            "download": {
+                "date": "2022-01-10T00:00:00Z",
+                "url": "https://github.com/jacktrip/jacktrip/releases/download/v1.6.9-beta3/JackTrip-v1.6.9-beta3-Windows-x64-installer.msi",
+                "downloadSize": 44552192,
+                "sha256": "f0d8157d99da5ecfa3fb21e6bb039ea48052fc0792b114ca4f40ae0a554a4852"
+            }
+        },
+        {
+            "version": "1.6.9-beta2",
+            "changelog": "Start/join inactive studios, fix crashes and hanging UI on Windows: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.9-beta2",
+            "download": {
+                "date": "2022-01-10T00:00:00Z",
+                "url": "https://github.com/jacktrip/jacktrip/releases/download/v1.6.9-beta2/JackTrip-v1.6.9-beta2-Windows-x64-installer.msi",
+                "downloadSize": 44548096,
+                "sha256": "a8e7c9b353d953df894a827e2856baa71f89dc8fa835a5f3eb8422a94ffff5b5"
+            }
+        },
+        {
+            "version": "1.6.9-beta1",
+            "changelog": "Start/join inactive studios, fix crashes and hanging UI on Windows: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.9-beta1",
+            "download": {
+                "date": "2022-12-16T00:00:00Z",
+                "url": "https://files.jacktrip.org/app-builds/JackTrip-v1.6.9-beta1-Windows-x64-installer.msi",
+                "downloadSize": 44548096,
+                "sha256": "c5cfbfc1c7650d685489974b69f005671ceb44a1301006d33ceeda45fc00f7c7"
+            }
+        },
+        {
+            "version": "1.6.8",
+            "changelog": "Critical bug fix: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.8",
+            "download": {
+                "date": "2022-12-05T00:00:00Z",
+                "url": "https://files.jacktrip.org/app-builds/JackTrip-v1.6.8-Windows-x64-installer.msi",
+                "downloadSize": 44539904,
+                "sha256": "e4ac16e7a66b4d656eea4e88f703fe156d656aedc16ad7f63ec570d82c27ed54"
+            }
+        },
         {
             "version": "1.6.7",
             "changelog": "Updated Windows networking, enabling all audio devices on Windows, new default device behavior, and fixes: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.7",
index d4e82961b56823ec8dc60af10f26df92510abfc6..96b9b59e469810bac5e02e35a444c3db2735794d 100644 (file)
@@ -1,6 +1,16 @@
 {
     "app_name": "JackTrip",
     "releases": [
+        {
+            "version": "1.6.8",
+            "changelog": "Critical bug fix: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.8",
+            "download": {
+                "date": "2022-12-05T00:00:00Z",
+                "url": "https://files.jacktrip.org/app-builds/JackTrip-v1.6.8-Linux-x64-binary.zip",
+                "downloadSize": 30496801,
+                "sha256": "c2fcbf86f8ad4db862431f9ccf6b52b275b3cf5fc3bc2f7eea7ac803ee7ca590"
+            }
+        },
         {
             "version": "1.6.7",
             "changelog": "Updated Windows networking, enabling all audio devices on Windows, new default device behavior, and fixes: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.7",
index 6fec53931ec6b33b012dd7d105e773440da66fb1..65af705d70a55f3b0657f8d1c2232a1fac6e11cd 100644 (file)
@@ -1,6 +1,16 @@
 {
     "app_name": "JackTrip",
     "releases": [
+        {
+            "version": "1.6.8",
+            "changelog": "Critical bug fix: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.8",
+            "download": {
+                "date": "2022-12-05T00:00:00Z",
+                "url": "https://files.jacktrip.org/app-builds/JackTrip-v1.6.8-macOS-x64-installer.pkg",
+                "downloadSize": 11517093,
+                "sha256": "5c040b2caa1e7dea97d7f48fd888f532a1c30da7385535b2d4a2805551bc5f71"
+            }
+        },
         {
             "version": "1.6.7",
             "changelog": "Updated Windows networking, enabling all audio devices on Windows, new default device behavior, and fixes: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.7",
index 15d612253309bc5c3f7cd4488ccb328cfbfda25f..be118aa3cdfbb8890f5b5c0a0a8f2be8ab0715e2 100644 (file)
@@ -1,6 +1,16 @@
 {
     "app_name": "JackTrip",
     "releases": [
+        {
+            "version": "1.6.8",
+            "changelog": "Critical bug fix: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.8",
+            "download": {
+                "date": "2022-12-05T00:00:00Z",
+                "url": "https://files.jacktrip.org/app-builds/JackTrip-v1.6.8-Windows-x64-installer.msi",
+                "downloadSize": 44539904,
+                "sha256": "e4ac16e7a66b4d656eea4e88f703fe156d656aedc16ad7f63ec570d82c27ed54"
+            }
+        },
         {
             "version": "1.6.7",
             "changelog": "Updated Windows networking, enabling all audio devices on Windows, new default device behavior, and fixes: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.7",
index 5fa1f5aba016f04ba60cc05d2ffde0560fc21a02..c829b7e8a9ddacc996f19b5391cd2af8a06b32af 100644 (file)
@@ -53,6 +53,7 @@
 #include <QDateTime>
 #include <QHostAddress>
 #include <QHostInfo>
+#include <QRandomGenerator>
 #include <QThread>
 #include <QTimer>
 #include <QtEndian>
@@ -126,6 +127,8 @@ JackTrip::JackTrip(jacktripModeT JacktripMode, dataProtocolT DataProtocolType,
     , mUseAuth(false)
     , mRedundancy(redundancy)
     , mTimeoutTimer(this)
+    , mRetryTimer(this)
+    , mRetries(0)
     , mSleepTime(100)
     , mElapsedTime(0)
     , mEndTime(0)
@@ -734,7 +737,7 @@ void JackTrip::receivedConnectionTCP()
             return;
         }
         mAwaitingTcp = false;
-        mTimeoutTimer.stop();
+        mRetryTimer.stop();
     }
     if (gVerboseFlag)
         cout << "TCP Socket Connected to Server!" << endl;
@@ -898,6 +901,15 @@ void JackTrip::receivedDataTCP()
     completeConnection();
 }
 
+void JackTrip::receivedErrorTCP(QAbstractSocket::SocketError socketError)
+{
+    if (socketError != QAbstractSocket::ConnectionRefusedError) {
+        mTcpClient.close();
+        mRetryTimer.stop();
+        stop(QStringLiteral("TCP Socket Error: ") + QString::number(socketError));
+    }
+}
+
 void JackTrip::connectionSecured()
 {
     // Now that the connection is encrypted, send out port, and credentials.
@@ -1047,18 +1059,48 @@ void JackTrip::tcpTimerTick()
         // Stop everything.
         mAwaitingTcp = false;
         mTcpClient.close();
-        mTimeoutTimer.stop();
+        mRetryTimer.stop();
         stop();
+        return;
     }
 
-    mElapsedTime += mSleepTime;
+    mElapsedTime += mRetryTimer.interval();
     if (mEndTime > 0 && mElapsedTime >= mEndTime) {
         mAwaitingTcp = false;
         mTcpClient.close();
-        mTimeoutTimer.stop();
+        mRetryTimer.stop();
         cout << "JackTrip Server Timed Out!" << endl;
         stop(QStringLiteral("Initial TCP Connection Timed Out"));
+        return;
     }
+
+    // Use randomized exponential backoff to reconnect the TCP client
+    QRandomGenerator randomizer;
+    mRetries++;
+    // exponential backoff sleep with 6s maximum + jitter
+    int newInterval = 2000 * pow(2, mRetries);
+    newInterval     = std::min(newInterval, 6000);
+    newInterval += randomizer.bounded(0, 500);
+    QString now = QDateTime::currentDateTime().toString(Qt::ISODate);
+    qDebug() << "Sleep time " << newInterval << " ms at " << now;
+    mRetryTimer.setInterval(newInterval);
+
+    qDebug() << "Connection timed out. Retrying again using exponential backoff";
+
+    mTcpClient.abort();
+
+    // Create Socket Objects
+    QHostAddress serverHostAddress;
+    if (!serverHostAddress.setAddress(mPeerAddress)) {
+        QHostInfo info = QHostInfo::fromName(mPeerAddress);
+        if (!info.addresses().isEmpty()) {
+            // use the first IP address
+            serverHostAddress = info.addresses().constFirst();
+        }
+    }
+    mTcpClient.connectToHost(serverHostAddress, mTcpServerPort);
+
+    mRetryTimer.start();
 }
 
 //*******************************************************************************
@@ -1247,16 +1289,27 @@ int JackTrip::clientPingToServerStart()
     // ----------------------------------------------
     connect(&mTcpClient, &QTcpSocket::readyRead, this, &JackTrip::receivedDataTCP);
     connect(&mTcpClient, &QTcpSocket::connected, this, &JackTrip::receivedConnectionTCP);
+    // Enable CI builds on Ubuntu 20.04 with Qt 5.12.8
+#ifdef __linux__
+    connect(&mTcpClient,
+            QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error), this,
+            &JackTrip::receivedErrorTCP);
+#else
+    connect(&mTcpClient, &QTcpSocket::errorOccurred, this, &JackTrip::receivedErrorTCP);
+#endif
     {
         QMutexLocker lock(&mTimerMutex);
+        QRandomGenerator randomizer;
         mAwaitingTcp = true;
         mElapsedTime = 0;
-        mEndTime     = 5000;  // Timeout after 5 seconds.
-        mTimeoutTimer.setInterval(mSleepTime);
-        mTimeoutTimer.disconnect();
-        connect(&mTimeoutTimer, &QTimer::timeout, this, &JackTrip::tcpTimerTick);
-        mTimeoutTimer.start();
+        mEndTime     = 30000;  // Timeout after 30 seconds.
+        mRetryTimer.setInterval(randomizer.bounded(0, 2000 * pow(2, mRetries)));
+        mRetryTimer.setSingleShot(true);
+        mRetryTimer.disconnect();
+        connect(&mRetryTimer, &QTimer::timeout, this, &JackTrip::tcpTimerTick);
+        mRetryTimer.start();
     }
+
     mTcpClient.connectToHost(serverHostAddress, mTcpServerPort);
 
     if (gVerboseFlag)
index 9393ffc405299411583ad8e1c8fb0bf20d746c8c..c86151aa7c224c7713465bff0877d1e31527922f 100644 (file)
@@ -567,6 +567,7 @@ class JackTrip : public QObject
    private slots:
     void receivedConnectionTCP();
     void receivedDataTCP();
+    void receivedErrorTCP(QAbstractSocket::SocketError socketError);
     void connectionSecured();
     void receivedDataUDP();
     void udpTimerTick();
@@ -682,6 +683,8 @@ class JackTrip : public QObject
         mProcessPluginsToNetwork;  ///< Vector of ProcessPlugin<EM>s</EM>
 
     QTimer mTimeoutTimer;
+    QTimer mRetryTimer;
+    int mRetries;
     int mSleepTime;
     int mElapsedTime;
     int mEndTime;
index 8bd1217e3008643e9e4e0e0c865983b51338608c..b061fddfb7ea20948c9d22ef1dbe9d2156816cea 100644 (file)
@@ -100,8 +100,8 @@ void RtAudioInterface::setup(bool verbose)
     unsigned int n_devices_output = all_output_devices.size();
     unsigned int n_devices_total  = n_devices_input + n_devices_output;
 
-    RtAudio* rtAudioIn;
-    RtAudio* rtAudioOut;
+    RtAudio* rtAudioIn  = NULL;
+    RtAudio* rtAudioOut = NULL;
 
     // unsigned int n_devices = mRtAudio->getDeviceCount();
     if (n_devices_total < 1) {
@@ -257,6 +257,11 @@ void RtAudioInterface::printDevices()
     RtAudio::getCompiledApi(apis);
 
     for (uint32_t i = 0; i < apis.size(); i++) {
+#ifdef _WIN32
+        if (apis.at(i) == RtAudio::UNIX_JACK) {
+            continue;
+        }
+#endif
         RtAudio rtaudio(apis.at(i));
         unsigned int devices = rtaudio.getDeviceCount();
         for (unsigned int j = 0; j < devices; j++) {
@@ -483,6 +488,11 @@ void RtAudioInterface::getDeviceList(QStringList* list, QStringList* categories,
     RtAudio::getCompiledApi(apis);
 
     for (uint32_t i = 0; i < apis.size(); i++) {
+#ifdef _WIN32
+        if (apis.at(i) == RtAudio::UNIX_JACK) {
+            continue;
+        }
+#endif
         RtAudio::Api api = apis.at(i);
         RtAudio rtaudio(api);
         unsigned int devices = rtaudio.getDeviceCount();
@@ -500,6 +510,14 @@ void RtAudioInterface::getDeviceList(QStringList* list, QStringList* categories,
                     continue;
                 }
 
+                if (QString::fromStdString(info.name) == "JackRouter") {
+                    continue;
+                }
+
+                if (info.probed == false) {
+                    continue;
+                }
+
                 if (isInput && info.inputChannels > 0) {
                     list->append(QString::fromStdString(info.name));
                 } else if (!isInput && info.outputChannels > 0) {
@@ -541,6 +559,11 @@ void RtAudioInterface::getDeviceInfoFromName(std::string deviceName, int* index,
     RtAudio::getCompiledApi(apis);
 
     for (uint32_t i = 0; i < apis.size(); i++) {
+#ifdef _WIN32
+        if (apis.at(i) == RtAudio::UNIX_JACK) {
+            continue;
+        }
+#endif
         RtAudio rtaudio(apis.at(i));
         unsigned int devices = rtaudio.getDeviceCount();
         for (unsigned int j = 0; j < devices; j++) {
index bd7fd5932e6c37ef659acd05b4c6fbce75eaf291..60ac5206ebfc68d8c59d1fbd3c08cf8d1cff63da 100644 (file)
 #include "RtAudioInterface.h"
 #endif
 
+#ifdef JACKTRIP_BUILD_INFO
+#define STR(s)           #s
+#define TO_STRING(s)     STR(s)
+#define PRINT_BUILD_INFO cout << "Build Info: " << TO_STRING(JACKTRIP_BUILD_INFO) << endl;
+#endif
+
 //#include "ThreadPoolTest.h"
 
 using std::cout;
@@ -89,7 +95,9 @@ enum JTLongOptIDS {
     OPT_NUMSEND,
     OPT_APPENDTHREADID,
     OPT_LISTDEVICES,
-    OPT_AUDIODEVICE
+    OPT_AUDIODEVICE,
+    OPT_AUDIOINPUTDEVICE,
+    OPT_AUDIOOUTPUTDEVICE
 };
 
 //*******************************************************************************
@@ -153,6 +161,10 @@ void Settings::parseInput(int argc, char** argv)
          'd'},  // Set RTAudio device id to use (DEPRECATED)
         {"audiodevice", required_argument, NULL,
          OPT_AUDIODEVICE},  // Set RTAudio devices by name
+        {"audioinputdevice", required_argument, NULL,
+         OPT_AUDIOINPUTDEVICE},  // Set RTAudio input device by name
+        {"audiooutputdevice", required_argument, NULL,
+         OPT_AUDIOOUTPUTDEVICE},  // Set RTAudio output device by name
         {"listdevices", no_argument, NULL,
          OPT_LISTDEVICES},                          // Set RTAudio device id to use
         {"bufsize", required_argument, NULL, 'F'},  // Set buffer Size
@@ -403,6 +415,12 @@ void Settings::parseInput(int argc, char** argv)
             //-------------------------------------------------------
             setDevicesByString(optarg);
             break;
+        case OPT_AUDIOINPUTDEVICE:
+            mInputDeviceName = optarg;
+            break;
+        case OPT_AUDIOOUTPUTDEVICE:
+            mOutputDeviceName = optarg;
+            break;
         case OPT_LISTDEVICES:  // List audio devices
             //-------------------------------------------------------
             RtAudioInterface::printDevices();
@@ -416,7 +434,10 @@ void Settings::parseInput(int argc, char** argv)
         case 'v':
             //-------------------------------------------------------
             cout << "JackTrip VERSION: " << gVersion << endl;
-            cout << "Copyright (c) 2008-2021 Juan-Pablo Caceres, Chris Chafe." << endl;
+#ifdef JACKTRIP_BUILD_INFO
+            PRINT_BUILD_INFO
+#endif
+            cout << "Copyright (c) 2008-2022 Juan-Pablo Caceres, Chris Chafe." << endl;
             cout << "SoundWIRE group at CCRMA, Stanford University" << endl;
 #ifdef QT_OPENSOURCE
             cout << "This build of JackTrip is subject to LGPL license." << endl;
@@ -688,7 +709,7 @@ void Settings::printUsage()
     cout << "" << endl;
     cout << "JackTrip: A System for High-Quality Audio Network Performance" << endl;
     cout << "over the Internet" << endl;
-    cout << "Copyright (c) 2008-2021 Juan-Pablo Caceres, Chris Chafe." << endl;
+    cout << "Copyright (c) 2008-2022 Juan-Pablo Caceres, Chris Chafe." << endl;
     cout << "SoundWIRE group at CCRMA, Stanford University" << endl;
 #ifdef QT_OPENSOURCE
     cout << "This build of JackTrip is subject to LGPL license." << endl;
@@ -816,6 +837,8 @@ void Settings::printUsage()
          << endl;
     cout << " --audiodevice \"input-output device name\"" << endl;
     cout << " --audiodevice \"input device name\",\"output device name\"" << endl;
+    cout << " --audioinputdevice \"input device name\"" << endl;
+    cout << " --audiooutputdevice \"output device name\"" << endl;
     cout << "                                          Set audio device to use; if not set, "
             "the default device will be used"
          << endl;
index 33187843c48f7915e583ccd75061518479809a0d..8c291a4d02a2d25dc12f9b65bc6d8a312b5b7509 100644 (file)
@@ -116,7 +116,7 @@ struct FAUST_API dsp_memory_manager {
      * Inform the Memory Manager with the number of expected memory zones.
      * @param count - the number of expected memory zones
      */
-    virtual void begin(size_t /*count*?) {}
+    virtual void begin(size_t /*count*/) {}
 
     /**
      * Give the Memory Manager information on a given memory zone.
diff --git a/src/gui/AudioInterfaceMode.h b/src/gui/AudioInterfaceMode.h
new file mode 100644 (file)
index 0000000..6a4558f
--- /dev/null
@@ -0,0 +1,83 @@
+//*****************************************************************
+/*
+  JackTrip: A System for High-Quality Audio Network Performance
+  over the Internet
+
+  Copyright (c) 2008-2022 Juan-Pablo Caceres, Chris Chafe.
+  SoundWIRE group at CCRMA, Stanford University.
+
+  Permission is hereby granted, free of charge, to any person
+  obtaining a copy of this software and associated documentation
+  files (the "Software"), to deal in the Software without
+  restriction, including without limitation the rights to use,
+  copy, modify, merge, publish, distribute, sublicense, and/or sell
+  copies of the Software, and to permit persons to whom the
+  Software is furnished to do so, subject to the following
+  conditions:
+
+  The above copyright notice and this permission notice shall be
+  included in all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+  OTHER DEALINGS IN THE SOFTWARE.
+*/
+//*****************************************************************
+
+/**
+ * \file AudioInterfaceMode.h
+ * \author Matt Horton
+ * \date December 2022
+ */
+
+enum class AudioInterfaceMode {
+    JACK,     ///< Jack Mode
+    RTAUDIO,  ///< RtAudio Mode
+    ALL,
+    NONE
+};
+
+#ifdef RT_AUDIO
+#ifndef NO_JACK
+constexpr AudioInterfaceMode mode = AudioInterfaceMode::ALL;
+#else
+constexpr AudioInterfaceMode mode = AudioInterfaceMode::RTAUDIO;
+#endif
+#else
+#ifndef NO_JACK
+constexpr AudioInterfaceMode mode = AudioInterfaceMode::JACK;
+#else
+constexpr AudioInterfaceMode mode = AudioInterfaceMode::NONE;
+#endif
+#endif
+
+template<AudioInterfaceMode backend>
+constexpr auto isBackendAvailable()
+{
+    if constexpr (backend == AudioInterfaceMode::RTAUDIO) {
+        if (mode == AudioInterfaceMode::RTAUDIO || mode == AudioInterfaceMode::ALL) {
+            return true;
+        } else {
+            return false;
+        }
+    } else if constexpr (backend == AudioInterfaceMode::JACK) {
+        if (mode == AudioInterfaceMode::JACK || mode == AudioInterfaceMode::ALL) {
+            return true;
+        } else {
+            return false;
+        }
+    } else if constexpr (backend == AudioInterfaceMode::ALL) {
+        if (mode == AudioInterfaceMode::ALL) {
+            return true;
+        } else {
+            return false;
+        }
+    } else {
+        return false;
+    }
+}
\ No newline at end of file
index 4dcf4fe203921980e7175f538d07fcb8705e7813..01026db274cf6bcf5ff45a1eaacaf6c0b708707d 100644 (file)
@@ -294,7 +294,12 @@ Item {
                 border.width: 1
                 border.color: settingsButton.down ? buttonPressedStroke : (settingsButton.hovered ? buttonHoverStroke : buttonStroke)
             }
-            onClicked: virtualstudio.windowState = "settings"
+            Timer {
+                id: restartAudioTimer
+                interval: 805; running: false; repeat: false
+                onTriggered: virtualstudio.audioActivated = true;
+            }
+            onClicked: { virtualstudio.windowState = "settings"; restartAudioTimer.restart(); }
             display: AbstractButton.TextBesideIcon
             font {
                 family: "Poppins"; 
index 80e13178548141d2724e56ae2e50ebb05ff843a3..bd728c96af471f975a0171c66bd1873a11ae4f72 100644 (file)
@@ -5,9 +5,9 @@ import QtGraphicalEffects 1.12
 Item {
     width: parent.width; height: parent.height
     clip: true
-    
+
     property bool connecting: false
-    
+
     property int leftHeaderMargin: 16
     property int fontBig: 28
     property int fontMedium: 12
@@ -17,8 +17,22 @@ Item {
     property int bodyMargin: 60
     property int bottomToolTipMargin: 8
     property int rightToolTipMargin: 4
-    
+
+    property string studioStatus: (virtualstudio.currentStudio >= 0 ? serverModel[virtualstudio.currentStudio].status : "")
+    property bool showReadyScreen: studioStatus === "Ready"
+    property bool showStartingScreen: studioStatus === "Starting"
+    property bool showStoppingScreen: (virtualstudio.currentStudio >= 0 ? serverModel[virtualstudio.currentStudio].isManageable && !serverModel[virtualstudio.currentStudio].enabled && serverModel[virtualstudio.currentStudio].cloudId !== "" : false)
+    property bool showWaitingScreen: !showStoppingScreen && !showStartingScreen && !showReadyScreen
+
     property string buttonColour: virtualstudio.darkMode ? "#494646" : "#EAECEC"
+
+    property string browserButtonColour: virtualstudio.darkMode ? "#494646" : "#EAECEC"
+    property string browserButtonHoverColour: virtualstudio.darkMode ? "#5B5858" : "#D3D4D4"
+    property string browserButtonPressedColour: virtualstudio.darkMode ? "#524F4F" : "#DEE0E0"
+    property string browserButtonStroke: virtualstudio.darkMode ? "#80827D7D" : "#40979797"
+    property string browserButtonHoverStroke: virtualstudio.darkMode ? "#7B7777" : "#BABCBC"
+    property string browserButtonPressedStroke: virtualstudio.darkMode ? "#827D7D" : "#BABCBC"
+
     property string muteButtonMutedColor: "#FCB6B6"
     property string textColour: virtualstudio.darkMode ? "#FAFBFB" : "#0F0D0D"
     property string meterColor: virtualstudio.darkMode ? "gray" : "#E0E0E0"
@@ -78,15 +92,15 @@ Item {
         fillMode: Image.PreserveAspectFit
         smooth: true
     }
-    
+
     Text {
         id: heading
-        text: virtualstudio.connectionState
+        text: studioStatus === "Starting" ? "Starting..." : virtualstudio.connectionState
         x: leftHeaderMargin * virtualstudio.uiScale; y: 34 * virtualstudio.uiScale
         font { family: "Poppins"; weight: Font.Bold; pixelSize: fontBig * virtualstudio.fontScale * virtualstudio.uiScale }
         color: textColour
     }
-    
+
     Studio {
         x: leftHeaderMargin * virtualstudio.uiScale; y: 96 * virtualstudio.uiScale
         width: parent.width - (2 * x)
@@ -103,6 +117,7 @@ Item {
 
     Item {
         id: inputDevice
+        visible: showReadyScreen
         x: bodyMargin * virtualstudio.uiScale; y: 230 * virtualstudio.uiScale
         width: Math.min(parent.width / 2, 320 * virtualstudio.uiScale) - x
         height: 100 * virtualstudio.uiScale
@@ -152,6 +167,7 @@ Item {
 
     Item {
         id: outputDevice
+        visible: showReadyScreen
         x: bodyMargin * virtualstudio.uiScale; y: 320 * virtualstudio.uiScale
         width: Math.min(parent.width / 2, 320 * virtualstudio.uiScale) - x
         height: 100 * virtualstudio.uiScale
@@ -201,6 +217,7 @@ Item {
 
     Item {
         id: inputControls
+        visible: showReadyScreen
         x: inputDevice.x + inputDevice.width; y: 230 * virtualstudio.uiScale
         width: parent.width - inputDevice.width - 2 * bodyMargin * virtualstudio.uiScale
 
@@ -303,6 +320,7 @@ Item {
 
     Item {
         id: outputControls
+        visible: showReadyScreen
         x: outputDevice.x + outputDevice.width; y: 320 * virtualstudio.uiScale
         width: parent.width - inputDevice.width - 2 * bodyMargin * virtualstudio.uiScale
 
@@ -339,6 +357,7 @@ Item {
 
     Item {
         id: networkStatsHeader
+        visible: showReadyScreen
         x: bodyMargin * virtualstudio.uiScale; y: 410 * virtualstudio.uiScale
         width: Math.min(parent.width / 2, 320 * virtualstudio.uiScale) - x
         height: 128 * virtualstudio.uiScale
@@ -373,6 +392,7 @@ Item {
 
     Item {
         id: networkStatsText
+        visible: showReadyScreen
         x: networkStatsHeader.x + networkStatsHeader.width; y: 410 * virtualstudio.uiScale
         width: parent.width - networkStatsHeader.width - 2 * bodyMargin * virtualstudio.uiScale
         height: 128 * virtualstudio.uiScale
@@ -395,4 +415,126 @@ Item {
             color: textColour
         }
     }
+
+    Item {
+        id: waitingScreen
+        visible: showWaitingScreen
+        x: bodyMargin * virtualstudio.uiScale; y: 230 * virtualstudio.uiScale
+        width: parent.width - (2 * x)
+
+        property bool isManageable: (virtualstudio.currentStudio >= 0 ? serverModel[virtualstudio.currentStudio].isManageable : false)
+
+        Text {
+            id: waitingText0
+            x: 0
+            width: parent.width
+            color: textColour
+            font {family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
+            text: parent.isManageable
+                    ? "Waiting for this studio to start. Please start the studio using one of the options below."
+                    : "This studio is currently inactive. Please contact an owner or admin for this studio to start it."
+            wrapMode: Text.WordWrap
+        }
+
+        Item {
+            id: startButtonsBox
+            anchors.top: waitingText0.bottom
+            anchors.topMargin: 16 * virtualstudio.uiScale
+            anchors.bottomMargin: 16 * virtualstudio.uiScale
+            visible: parent.isManageable
+
+            height: 64 * virtualstudio.uiScale
+
+            Button {
+                id: startStudioNowButton
+                anchors.verticalCenter: startButtonsBox.verticalCenter
+                x: 0
+                onClicked: {
+                    virtualstudio.manageStudio(-1, true)
+                }
+
+                width: 210 * virtualstudio.uiScale; height: 45 * virtualstudio.uiScale
+                background: Rectangle {
+                    radius: 6 * virtualstudio.uiScale
+                    color: startStudioNowButton.down ? browserButtonPressedColour : (startStudioNowButton.hovered ? browserButtonHoverColour : browserButtonColour)
+                    border.width: 1
+                    border.color: startStudioNowButton.down ? browserButtonPressedStroke : (startStudioNowButton.hovered ? browserButtonHoverStroke : browserButtonStroke)
+                }
+
+                Text {
+                    text: "Start Studio"
+                    font.family: "Poppins"
+                    font.pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale
+                    anchors.horizontalCenter: parent.horizontalCenter
+                    anchors.verticalCenter: parent.verticalCenter
+                    color: textColour
+                }
+            }
+
+            Text {
+                id: startStudioInBrowserText
+                anchors.verticalCenter: startStudioNowButton.verticalCenter
+                anchors.left: startStudioNowButton.right
+                anchors.leftMargin: 24 * virtualstudio.uiScale
+                width: 240 * virtualstudio.uiScale
+                textFormat: Text.RichText
+                text:`<a style="color: ${textColour};" href="https://${virtualstudio.apiHost}/studios/${virtualstudio.currentStudio >= 0 ? serverModel[virtualstudio.currentStudio].id : ""}/live?start=true">Change Settings and Start</a>`
+
+                onLinkActivated: link => {
+                    virtualstudio.openLink(link)
+                }
+                horizontalAlignment: Text.AlignHLeft
+                wrapMode: Text.WordWrap
+                font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
+            }
+        }
+
+        Text {
+            id: waitingText1
+            x: 0
+            width: parent.width
+            color: textColour
+            anchors.top: parent.isManageable ? startButtonsBox.bottom : waitingText0.bottom
+            anchors.topMargin: 16 * virtualstudio.uiScale
+            anchors.bottomMargin: 16 * virtualstudio.uiScale
+            visible: parent.isManageable
+            font {family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
+            text: "You will be automatically connected to the studio when it is ready."
+            wrapMode: Text.WordWrap
+        }
+    }
+
+    Item {
+        id: studioStartingScreen
+        visible: showStartingScreen
+        x: bodyMargin * virtualstudio.uiScale; y: 230 * virtualstudio.uiScale
+        width: parent.width - (2 * x)
+
+        Text {
+            id: studioStartingText0
+            x: 0
+            width: parent.width
+            color: textColour
+            font {family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
+            text: "This studio is currently starting up. You will be connected automatically when it is ready."
+            wrapMode: Text.WordWrap
+        }
+    }
+
+    Item {
+        id: studioStoppingScreen
+        visible: showStoppingScreen
+        x: bodyMargin * virtualstudio.uiScale; y: 230 * virtualstudio.uiScale
+        width: parent.width - (2 * x)
+
+        Text {
+            id: studioStoppingText0
+            x: 0
+            width: parent.width
+            color: textColour
+            font {family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
+            text: "This studio is shutting down, please wait to start it again."
+            wrapMode: Text.WordWrap
+        }
+    }
 }
index 76c4d0b83c987cbdbc4cf5f9405a0fbfc96b3cad..e033a19aaa5cdd799b80d4a2e0ae23d5c1d02cf9 100644 (file)
@@ -245,7 +245,18 @@ Item {
             text: "Using JACK for audio input and output. Use QjackCtl to adjust your sample rate, buffer, and device settings."
             font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
             wrapMode: Text.WordWrap
-            visible: virtualstudio.audioBackend == "JACK" && !virtualstudio.selectableBackend
+            visible: virtualstudio.audioBackend == "JACK" && !virtualstudio.selectableBackend && virtualstudio.backendAvailable
+            color: textColour
+        }
+
+        Text {
+            id: noBackendLabel
+            x: leftMargin * virtualstudio.uiScale; y: 150 * virtualstudio.uiScale
+            width: parent.width - x - (16 * virtualstudio.uiScale)
+            text: "JackTrip has been compiled without an audio backend. Please rebuild with the rtaudio flag or without the nojack flag."
+            font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
+            wrapMode: Text.WordWrap
+            visible: !virtualstudio.backendAvailable
             color: textColour
         }
 
@@ -308,7 +319,7 @@ Item {
                 horizontalAlignment: Text.AlignHLeft
                 verticalAlignment: Text.AlignVCenter
                 elide: Text.ElideRight
-                text: outputCombo.model[outputCombo.currentIndex].text
+                text: outputCombo.model[outputCombo.currentIndex].text ? outputCombo.model[outputCombo.currentIndex].text : ""
             }
         }
 
@@ -323,21 +334,25 @@ Item {
             onClicked: { virtualstudio.playOutputAudio() }
             width: 216 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale
             x: parent.width - (232 * virtualstudio.uiScale)
-            y: virtualstudio.audioBackend != "JACK" ? outputCombo.y + (48 * virtualstudio.uiScale) : outputCombo.y + (48 * virtualstudio.uiScale)
+            y: virtualstudio.audioBackend != "JACK" ? outputCombo.y + (48 * virtualstudio.uiScale) : jackLabel.y + (72 * virtualstudio.uiScale)
             Text {
                 text: "Test Output Audio"
                 font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
                 anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter }
                 color: textColour
             }
+            visible: virtualstudio.audioReady
         }
 
         Text {
+            id: inputLabel
             anchors.verticalCenter: inputCombo.verticalCenter
-            x: leftMargin * virtualstudio.uiScale; y: testOutputAudioButton.y + (48 * virtualstudio.uiScale)
+            x: leftMargin * virtualstudio.uiScale
+            anchors.top: virtualstudio.audioBackend != "JACK" ? inputCombo.top : inputDeviceMeters.top
+            anchors.topMargin: virtualstudio.audioBackend != "JACK" ? (inputCombo.height - inputLabel.height)/2 : 0
             text: "Input Device"
             font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
-            visible: virtualstudio.audioBackend != "JACK"
+            visible: virtualstudio.backendAvailable
             color: textColour
         }
 
@@ -361,7 +376,7 @@ Item {
             x: backendCombo.x; y: testOutputAudioButton.y + (48 * virtualstudio.uiScale)
             width: parent.width - x - (16 * virtualstudio.uiScale); height: 36 * virtualstudio.uiScale
             visible: virtualstudio.audioBackend != "JACK"
-                        delegate: ItemDelegate {
+            delegate: ItemDelegate {
                 required property var modelData
                 required property int index
 
@@ -391,7 +406,7 @@ Item {
                 horizontalAlignment: Text.AlignHLeft
                 verticalAlignment: Text.AlignVCenter
                 elide: Text.ElideRight
-                text: inputCombo.model[inputCombo.currentIndex].text
+                text: inputCombo.model[inputCombo.currentIndex].text ? inputCombo.model[inputCombo.currentIndex].text : ""
             }
         }
 
@@ -400,11 +415,24 @@ Item {
             anchors.left: backendCombo.left
             anchors.right: parent.right
             anchors.rightMargin: rightMargin * virtualstudio.uiScale
-            y: virtualstudio.audioBackend != "JACK" ?  inputCombo.y + 48 * virtualstudio.uiScale : virtualstudio.uiScale * (virtualstudio.selectableBackend ? 112 : 64)
+            y: virtualstudio.audioBackend != "JACK" ?  inputCombo.y + 48 * virtualstudio.uiScale : testOutputAudioButton.y + 72 * virtualstudio.uiScale
             height: 100 * virtualstudio.uiScale
             model: inputMeterModel
             clipped: inputClipped
             enabled: !Boolean(virtualstudio.devicesError)
+            visible: virtualstudio.audioReady
+        }
+
+        Text {
+            anchors.left: backendCombo.left
+            anchors.right: parent.right
+            anchors.rightMargin: rightMargin * virtualstudio.uiScale
+            y: virtualstudio.audioBackend != "JACK" ?  inputCombo.y + 48 * virtualstudio.uiScale : virtualstudio.uiScale * (virtualstudio.selectableBackend ? 112 : 64)
+            height: 100 * virtualstudio.uiScale
+            text: "Preparing audio..."
+            font { family: "Poppins"; pixelSize: 13 * virtualstudio.fontScale * virtualstudio.uiScale }
+            visible: virtualstudio.audioBackend != "JACK" && !virtualstudio.audioReady
+            color: textColour
         }
 
         Button {
@@ -720,7 +748,12 @@ Item {
                 border.width: 1
                 border.color: cancelButton.down ? buttonPressedStroke : (cancelButton.hovered ? buttonHoverStroke : buttonStroke)
             }
-            onClicked: { virtualstudio.windowState = "browse"; virtualstudio.revertSettings() }
+            onClicked: {
+                virtualstudio.windowState = "browse";
+                inputCombo.currentIndex = virtualstudio.previousInput;
+                outputCombo.currentIndex = virtualstudio.previousOutput;
+                virtualstudio.revertSettings()
+            }
             anchors.verticalCenter: parent.verticalCenter
             x: parent.width - (230 * virtualstudio.uiScale)
             width: buttonWidth * virtualstudio.uiScale; height: buttonHeight * virtualstudio.uiScale
index 65e5f716b936b4cdb53e8a961df6d401dfd453e2..3fa839d38613e89400efb4d4d9a56f0756c99a8a 100644 (file)
@@ -575,7 +575,18 @@ Item {
             text: "Using JACK for audio input and output. Use QjackCtl to adjust your sample rate, buffer, and device settings."
             font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
             wrapMode: Text.WordWrap
-            visible: virtualstudio.audioBackend == "JACK" && !virtualstudio.selectableBackend
+            visible: virtualstudio.audioBackend == "JACK" && !virtualstudio.selectableBackend && virtualstudio.backendAvailable
+            color: textColour
+        }
+
+        Text {
+            id: noBackendLabel
+            x: leftMargin * virtualstudio.uiScale; y: 150 * virtualstudio.uiScale
+            width: parent.width - x - (16 * virtualstudio.uiScale)
+            text: "JackTrip has been compiled without an audio backend. Please rebuild with the rtaudio flag or without the nojack flag."
+            font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
+            wrapMode: Text.WordWrap
+            visible: !virtualstudio.backendAvailable
             color: textColour
         }
 
@@ -629,7 +640,7 @@ Item {
                 horizontalAlignment: Text.AlignHLeft
                 verticalAlignment: Text.AlignVCenter
                 elide: Text.ElideRight
-                text: outputCombo.model[outputCombo.currentIndex].text
+                text: outputCombo.model[outputCombo.currentIndex].text ? outputCombo.model[outputCombo.currentIndex].text : ""
             }
         }
 
@@ -640,6 +651,7 @@ Item {
             text: virtualstudio.audioBackend != "JACK" ? "Output Device" : "Output Volume"
             font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
             color: textColour
+            visible: virtualstudio.backendAvailable
         }
 
         Slider {
@@ -649,7 +661,7 @@ Item {
             onMoved: { audioInterface.outputVolume = value }
             to: 1.0
             padding: 0
-            y: virtualstudio.audioBackend != "JACK" ? outputCombo.y + 48 * virtualstudio.uiScale : backendCombo.y + virtualstudio.uiScale * (virtualstudio.selectableBackend ? 60 : 0)
+            y: virtualstudio.audioBackend != "JACK" ? outputCombo.y + 48 * virtualstudio.uiScale : jackLabel.y + 72 * virtualstudio.uiScale
             anchors.left: outputCombo.left
             anchors.right: parent.right
             anchors.rightMargin: rightMargin * virtualstudio.uiScale
@@ -662,6 +674,7 @@ Item {
                 color: outputSlider.pressed ? sliderPressedColour : sliderColour
                 border.color: buttonStroke
             }
+            visible: virtualstudio.backendAvailable
         }
 
         Button {
@@ -677,7 +690,7 @@ Item {
             anchors.rightMargin: rightMargin * virtualstudio.uiScale
             y: outputSlider.y + (36 * virtualstudio.uiScale)
             width: 216 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale
-            visible: virtualstudio.audioBackend != "JACK"
+            visible: virtualstudio.audioReady
             Text {
                 text: "Test Output Audio"
                 font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
@@ -738,7 +751,7 @@ Item {
                 horizontalAlignment: Text.AlignHLeft
                 verticalAlignment: Text.AlignVCenter
                 elide: Text.ElideRight
-                text: inputCombo.model[inputCombo.currentIndex].text
+                text: inputCombo.model[inputCombo.currentIndex].text ? inputCombo.model[inputCombo.currentIndex].text : ""
             }
         }
 
@@ -750,6 +763,7 @@ Item {
             text: virtualstudio.audioBackend != "JACK" ? "Input Device" : "Input Volume"
             font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
             color: textColour
+            visible: virtualstudio.backendAvailable
         }
 
         Meter {
@@ -757,11 +771,12 @@ Item {
             anchors.left: backendCombo.left
             anchors.right: parent.right
             anchors.rightMargin: rightMargin * virtualstudio.uiScale
-            y: virtualstudio.audioBackend != "JACK" ? inputCombo.y + 72 * virtualstudio.uiScale : outputSlider.y + (72 * virtualstudio.uiScale)
+            y: virtualstudio.audioBackend != "JACK" ? inputCombo.y + 72 * virtualstudio.uiScale : testOutputAudioButton.y + (72 * virtualstudio.uiScale)
             height: 100 * virtualstudio.uiScale
             model: inputMeterModel
             clipped: inputClipped
             enabled: !Boolean(virtualstudio.devicesError)
+            visible: virtualstudio.backendAvailable
         }
 
         Slider {
@@ -784,6 +799,7 @@ Item {
                 color: inputSlider.pressed ? sliderPressedColour : sliderColour
                 border.color: buttonStroke
             }
+            visible: virtualstudio.backendAvailable
         }
 
         Button {
@@ -800,7 +816,7 @@ Item {
             anchors.topMargin: 18 * virtualstudio.uiScale
             anchors.top: inputSlider.bottom
             width: 216 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale
-            visible: virtualstudio.audioBackend != "JACK"
+            visible: virtualstudio.audioBackend != "JACK" && virtualstudio.backendAvailable
             Text {
                 text: "Refresh Devices"
                 font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
@@ -847,7 +863,7 @@ Item {
                     color: saveButtonShadow
                 }
             }
-            enabled: !Boolean(virtualstudio.devicesError)
+            enabled: !Boolean(virtualstudio.devicesError) && virtualstudio.backendAvailable
             onClicked: { virtualstudio.windowState = "browse"; virtualstudio.applySettings() }
             anchors.right: parent.right
             anchors.rightMargin: rightMargin * virtualstudio.uiScale
@@ -859,7 +875,7 @@ Item {
                 font.family: "Poppins"
                 font.pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale
                 font.weight: Font.Bold
-                color: !Boolean(virtualstudio.devicesError) ? saveButtonText : disabledButtonText
+                color: !Boolean(virtualstudio.devicesError) && virtualstudio.backendAvailable ? saveButtonText : disabledButtonText
                 anchors.horizontalCenter: parent.horizontalCenter
                 anchors.verticalCenter: parent.verticalCenter
             }
@@ -868,6 +884,7 @@ Item {
         CheckBox {
             id: showAgainCheckbox
             checked: virtualstudio.showDeviceSetup
+            visible: virtualstudio.backendAvailable
             text: qsTr("Ask again next time")
             anchors.right: saveButton.left
             anchors.rightMargin: 16 * virtualstudio.uiScale
index f7f06b9e40b0203e15b0d47e080f1bcbf624ac35..f6808a3547e2743436d188feb8cfa68c2344bf4c 100644 (file)
@@ -45,14 +45,36 @@ Rectangle {
     property string shadowColour: virtualstudio.darkMode ? "#40000000" : "#80A1A1A1"
     property string toolTipBackgroundColour: inviteCopied ? "#57B147" : (virtualstudio.darkMode ? "#323232" : "#F3F3F3")
     property string toolTipTextColour: inviteCopied ? "#FAFBFB" : textColour
-    property string joinColour: virtualstudio.darkMode ? (connected ? "#FCB6B6" : "#E2EBE0") : (connected ? "#FCB6B6" : "#C4F4BE")
-    property string joinHoverColour: virtualstudio.darkMode ? (connected ? "#D49696" : "#BAC7B8") : (connected ? "#E3A4A4" : "#B0DCAB")
-    property string joinPressedColour: virtualstudio.darkMode ? (connected ? "#F2AEAE" : "#D8E2D6") : (connected ? "#EFADAD" : "#BAE8B5")
-    property string joinStroke: virtualstudio.darkMode ? (connected ? "#A65959" : "#748F70") : (connected ? "#C95E5E" : "#5DB752")
-    property string manageColour: virtualstudio.darkMode ? "#F0F1F1" : "#EAEBEB"
-    property string manageHoverColour: virtualstudio.darkMode ? "#CCCDCD" : "#D3D3D3"
-    property string managePressedColour: virtualstudio.darkMode ? "#E4E5E5" : "#EAEBEB"
-    property string manageStroke: virtualstudio.darkMode ? "#8B8D8D" : "#949494"
+
+    property string baseButtonColour: virtualstudio.darkMode ? "#F0F1F1" : "#EAEBEB"
+    property string baseButtonHoverColour: virtualstudio.darkMode ? "#CCCDCD" : "#D3D3D3"
+    property string baseButtonPressedColour: virtualstudio.darkMode ? "#E4E5E5" : "#EAEBEB"
+    property string baseButtonStroke: virtualstudio.darkMode ? "#8B8D8D" : "#949494"
+
+    property string joinAvailableColour: virtualstudio.darkMode ? "#E2EBE0" : "#C4F4BE"
+    property string joinAvailableHoverColour: virtualstudio.darkMode ? "#BAC7B8" : "#B0DCAB"
+    property string joinAvailablePressedColour: virtualstudio.darkMode ? "#D8E2D6" : "#BAE8B5"
+    property string joinAvailableStroke: virtualstudio.darkMode ? "#748F70" : "#5DB752"
+    
+    property string joinUnavailableColour: baseButtonColour
+    property string joinUnavailableHoverColour: baseButtonHoverColour
+    property string joinUnavailablePressedColour: baseButtonPressedColour
+    property string joinUnavailableStroke: baseButtonStroke
+
+    property string startColour: virtualstudio.darkMode ? "#E2EBE0" : "#C4F4BE"
+    property string startHoverColour: virtualstudio.darkMode ? "#BAC7B8" : "#B0DCAB"
+    property string startPressedColour: virtualstudio.darkMode ? "#D8E2D6" : "#BAE8B5"
+    property string startStroke: virtualstudio.darkMode ? "#748F70" : "#5DB752"
+
+    property string manageColour: baseButtonColour
+    property string manageHoverColour: baseButtonHoverColour
+    property string managePressedColour: baseButtonPressedColour
+    property string manageStroke: baseButtonStroke
+
+    property string leaveColour: virtualstudio.darkMode ? "#FCB6B6" : "#FCB6B6"
+    property string leaveHoverColour: virtualstudio.darkMode ? "#D49696" : "#E3A4A4"
+    property string leavePressedColour: virtualstudio.darkMode ? "#F2AEAE" : "#EFADAD"
+    property string leaveStroke: virtualstudio.darkMode ? "#A65959" : "#C95E5E"
 
     Clipboard {
         id: clipboard
@@ -172,25 +194,47 @@ Rectangle {
         y: topMargin * virtualstudio.uiScale; width: 40 * virtualstudio.uiScale; height: width
         background: Rectangle {
             radius: width / 2
-            color: joinButton.down ? joinPressedColour : (joinButton.hovered ? joinHoverColour : joinColour)
+            color: available ? (joinButton.down ? joinAvailablePressedColour : (joinButton.hovered ? joinAvailableHoverColour : joinAvailableColour))
+                : (joinButton.down ? joinUnavailablePressedColour : (joinButton.hovered ? joinUnavailableHoverColour : joinUnavailableColour))
             border.width: joinButton.down ? 1 : 0
-            border.color: joinStroke
+            border.color: available ? joinAvailableStroke : joinUnavailableStroke
         }
-        visible: connected || canConnect || canStart
+        visible: !connected
         onClicked: {
-            if (!connected) {
-                virtualstudio.windowState = "connected";
-                virtualstudio.connectToStudio(index);
-            } else {
-                virtualstudio.disconnect();
-            }
+            virtualstudio.windowState = "connected";
+            virtualstudio.connectToStudio(index);
         }
         Image {
-            id: joinLeave
+            id: join
             width: 22 * virtualstudio.uiScale; height: 20 * virtualstudio.uiScale
             anchors { verticalCenter: parent.verticalCenter; horizontalCenter: parent.horizontalCenter }
-            source: connected ? "leave.svg" : "join.svg"
-            sourceSize: Qt.size(joinLeave.width,joinLeave.height)
+            source: "join.svg"
+            sourceSize: Qt.size(join.width,join.height)
+            fillMode: Image.PreserveAspectFit
+            smooth: true
+        }
+    }
+
+    Button {
+        id: leaveButton
+        x: manageable ? parent.width - (219 * virtualstudio.uiScale) : parent.width - (142 * virtualstudio.uiScale)
+        y: topMargin * virtualstudio.uiScale; width: 40 * virtualstudio.uiScale; height: width
+        background: Rectangle {
+            radius: width / 2
+            color: leaveButton.down ? leavePressedColour : (leaveButton.hovered ? leaveHoverColour : leaveColour)
+            border.width: leaveButton.down ? 1 : 0
+            border.color: leaveStroke
+        }
+        visible: connected
+        onClicked: {
+            virtualstudio.disconnect();
+        }
+        Image {
+            id: leave
+            width: 22 * virtualstudio.uiScale; height: 20 * virtualstudio.uiScale
+            anchors { verticalCenter: parent.verticalCenter; horizontalCenter: parent.horizontalCenter }
+            source: "leave.svg"
+            sourceSize: Qt.size(leave.width,leave.height)
             fillMode: Image.PreserveAspectFit
             smooth: true
         }
@@ -199,9 +243,9 @@ Rectangle {
     Text {
         anchors.horizontalCenter: joinButton.horizontalCenter
         y: 56 * virtualstudio.uiScale
-        text: connected ? "Leave" : available ? "Join" : "Start"
+        text: connected ? "Leave" : "Join"
         font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale}
-        visible: connected || canConnect || canStart
+        visible: true
         color: textColour
     }
 
@@ -296,10 +340,12 @@ Rectangle {
             border.color: manageStroke
         }
         onClicked: { 
-            if (!connected) {
-                virtualstudio.manageStudio(index)
+            if (manageable && connected) {
+                virtualstudio.launchVideo(-1)
+            } else if (connected) {
+                virtualstudio.manageStudio(-1);
             } else {
-                virtualstudio.manageStudio(-1)
+                virtualstudio.manageStudio(index);
             }
         }
         visible: manageable
@@ -307,7 +353,7 @@ Rectangle {
             id: manageImg
             width: 20 * virtualstudio.uiScale; height: width
             anchors { verticalCenter: parent.verticalCenter; horizontalCenter: parent.horizontalCenter }
-            source: "manage.svg"
+            source: manageable && connected ? "video.svg" : "manage.svg"
             sourceSize: Qt.size(manageImg.width,manageImg.height)
             fillMode: Image.PreserveAspectFit
             smooth: true
@@ -317,7 +363,7 @@ Rectangle {
     Text {
         anchors.horizontalCenter: manageButton.horizontalCenter
         y: 56 * virtualstudio.uiScale
-        text: "Manage"
+        text: manageable && connected ? "Video" : "Manage"
         font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
         visible: manageable
         color: textColour
index a52a5347f9f2f53ea24a8cc4cb954a61f0a43a8d..bc50d97563a1201501bb36d436156868d92030d9 100644 (file)
@@ -1,3 +1,3 @@
 <svg width="23" height="19" viewBox="0 0 23 19" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M8 3C9.06087 3 10.0783 3.42143 10.8284 4.17157C11.5786 4.92172 12 5.93913 12 7C12 8.06087 11.5786 9.07828 10.8284 9.82843C10.0783 10.5786 9.06087 11 8 11C6.93913 11 5.92172 10.5786 5.17157 9.82843C4.42143 9.07828 4 8.06087 4 7C4 5.93913 4.42143 4.92172 5.17157 4.17157C5.92172 3.42143 6.93913 3 8 3V3ZM8 13C10.67 13 16 14.34 16 17V19H0V17C0 14.34 5.33 13 8 13ZM15.76 3.36C17.78 5.56 17.78 8.61 15.76 10.63L14.08 8.94C14.92 7.76 14.92 6.23 14.08 5.05L15.76 3.36ZM19.07 0C23 4.05 22.97 10.11 19.07 14L17.44 12.37C20.21 9.19 20.21 4.65 17.44 1.63L19.07 0Z" fill="#204C1A"/>
+<path d="M8 3C9.06087 3 10.0783 3.42143 10.8284 4.17157C11.5786 4.92172 12 5.93913 12 7C12 8.06087 11.5786 9.07828 10.8284 9.82843C10.0783 10.5786 9.06087 11 8 11C6.93913 11 5.92172 10.5786 5.17157 9.82843C4.42143 9.07828 4 8.06087 4 7C4 5.93913 4.42143 4.92172 5.17157 4.17157C5.92172 3.42143 6.93913 3 8 3V3ZM8 13C10.67 13 16 14.34 16 17V19H0V17C0 14.34 5.33 13 8 13ZM15.76 3.36C17.78 5.56 17.78 8.61 15.76 10.63L14.08 8.94C14.92 7.76 14.92 6.23 14.08 5.05L15.76 3.36ZM19.07 0C23 4.05 22.97 10.11 19.07 14L17.44 12.37C20.21 9.19 20.21 4.65 17.44 1.63L19.07 0Z" fill="#000000"/>
 </svg>
index 9aca57fa00301a3a485eff56b7c579a1397daa4e..98c85bbad1421cf2e233414f99d5b65c5f5024d8 100644 (file)
@@ -25,6 +25,7 @@
     <file>leave.svg</file>
     <file>manage.svg</file>
     <file>share.svg</file>
+    <file>start.svg</file>
     <file>cog.svg</file>
     <file>mic.svg</file>
     <file>micoff.svg</file>
@@ -33,6 +34,7 @@
     <file>headphones.svg</file>
     <file>Prompt.svg</file>
     <file>network.svg</file>
+    <file>video.svg</file>
     <file>jacktrip.png</file>
     <file>jacktrip white.png</file>
     <file>JTOriginal.png</file>
diff --git a/src/gui/start.svg b/src/gui/start.svg
new file mode 100644 (file)
index 0000000..f75a587
--- /dev/null
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24" fill="#000000"><path d="M8 6.82v10.36c0 .79.87 1.27 1.54.84l8.14-5.18c.62-.39.62-1.29 0-1.69L9.54 5.98C8.87 5.55 8 6.03 8 6.82z" /></svg>
\ No newline at end of file
diff --git a/src/gui/video.svg b/src/gui/video.svg
new file mode 100644 (file)
index 0000000..a1b644f
--- /dev/null
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 0 24 24" width="48px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M21 3H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 13H3V5h18v11z"/></svg>
\ No newline at end of file
index 806c54de1603895536bfd509caee828994402579..29bf7566ccaa87d00818f23dec6125ce4671ee44 100644 (file)
@@ -121,6 +121,10 @@ VirtualStudio::VirtualStudio(bool firstRun, QObject* parent)
     refreshDevices();
     m_previousInput  = m_inputDevice;
     m_previousOutput = m_outputDevice;
+
+    if constexpr (!isBackendAvailable<AudioInterfaceMode::ALL>()) {
+        m_selectableBackend = false;
+    }
 #else
     m_selectableBackend = false;
     m_vsAudioInterface.reset(new VsAudioInterface());
@@ -203,7 +207,6 @@ VirtualStudio::VirtualStudio(bool firstRun, QObject* parent)
     m_view.resize(696 * m_uiScale, 577 * m_uiScale);
 
     // Connect our timers
-    connect(&m_startTimer, &QTimer::timeout, this, &VirtualStudio::checkForHostname);
     connect(&m_retryPeriodTimer, &QTimer::timeout, this, &VirtualStudio::endRetryPeriod);
     connect(&m_refreshTimer, &QTimer::timeout, this, [&]() {
         m_refreshMutex.lock();
@@ -223,14 +226,14 @@ VirtualStudio::VirtualStudio(bool firstRun, QObject* parent)
     // thread
     connect(this, &VirtualStudio::refreshFinished, this, &VirtualStudio::joinStudio,
             Qt::QueuedConnection);
+
+    connect(this, &VirtualStudio::audioActivatedChanged, this,
+            &VirtualStudio::toggleAudio, Qt::QueuedConnection);
     connect(
         this, &VirtualStudio::studioToJoinChanged, this,
         [&]() {
             if (!m_studioToJoin.isEmpty()) {
-                // join studio when studio to join changes
-                if (readyToJoin()) {
-                    joinStudio();
-                }
+                joinStudio();
             }
         },
         Qt::QueuedConnection);
@@ -348,6 +351,7 @@ void VirtualStudio::setInputDevice([[maybe_unused]] int device)
     }
 
     m_inputDevice = filteredInputDeviceList.at(device);
+    emit inputDeviceChanged(m_inputDevice, false);
     emit inputDeviceSelected(m_inputDevice);
 #endif
 }
@@ -384,10 +388,83 @@ void VirtualStudio::setOutputDevice([[maybe_unused]] int device)
     }
 
     m_outputDevice = filteredOutputDeviceList.at(device);
+    emit outputDeviceChanged(m_outputDevice, false);
     emit outputDeviceSelected(m_outputDevice);
 #endif
 }
 
+int VirtualStudio::previousInput()
+{
+#ifdef RT_AUDIO
+    if (m_useRtAudio) {
+        QStringList filteredInputDeviceList;
+        for (int i = 0; i < m_inputDeviceList.size(); i++) {
+            if (m_inputDeviceList.at(i) != "(default)") {
+                filteredInputDeviceList += m_inputDeviceList.at(i);
+            }
+        }
+
+        int index = filteredInputDeviceList.indexOf(m_previousInput);
+        return index >= 0 ? index : 0;
+    }
+#endif
+    return 0;
+}
+
+void VirtualStudio::setPreviousInput([[maybe_unused]] int device)
+{
+    if (!m_useRtAudio) {
+        return;
+    }
+#ifdef RT_AUDIO
+    QStringList filteredInputDeviceList;
+    for (int i = 0; i < m_inputDeviceList.size(); i++) {
+        if (m_inputDeviceList.at(i) != "(default)") {
+            filteredInputDeviceList += m_inputDeviceList.at(i);
+        }
+    }
+
+    m_previousInput = filteredInputDeviceList.at(device);
+    emit previousInputChanged();
+#endif
+}
+
+int VirtualStudio::previousOutput()
+{
+#ifdef RT_AUDIO
+    if (m_useRtAudio) {
+        QStringList filteredOutputDeviceList;
+        for (int i = 0; i < m_outputDeviceList.size(); i++) {
+            if (m_outputDeviceList.at(i) != "(default)") {
+                filteredOutputDeviceList += m_outputDeviceList.at(i);
+            }
+        }
+
+        int index = filteredOutputDeviceList.indexOf(m_previousOutput);
+        return index >= 0 ? index : 0;
+    }
+#endif
+    return 0;
+}
+
+void VirtualStudio::setPreviousOutput([[maybe_unused]] int device)
+{
+    if (!m_useRtAudio) {
+        return;
+    }
+#ifdef RT_AUDIO
+    QStringList filteredOutputDeviceList;
+    for (int i = 0; i < m_outputDeviceList.size(); i++) {
+        if (m_outputDeviceList.at(i) != "(default)") {
+            filteredOutputDeviceList += m_outputDeviceList.at(i);
+        }
+    }
+
+    m_previousOutput = filteredOutputDeviceList.at(device);
+    emit previousOutputChanged();
+#endif
+}
+
 QString VirtualStudio::devicesWarning()
 {
     return m_devicesWarningMsg;
@@ -428,6 +505,26 @@ bool VirtualStudio::outputMuted()
     return m_outMuted;
 }
 
+bool VirtualStudio::audioActivated()
+{
+    return m_audioActivated;
+}
+
+bool VirtualStudio::audioReady()
+{
+    return m_audioReady;
+}
+
+bool VirtualStudio::backendAvailable()
+{
+    if constexpr ((isBackendAvailable<AudioInterfaceMode::JACK>()
+                   || isBackendAvailable<AudioInterfaceMode::RTAUDIO>())) {
+        return true;
+    } else {
+        return false;
+    }
+}
+
 void VirtualStudio::setInputVolume(float multiplier)
 {
     m_inMultiplier = multiplier;
@@ -507,6 +604,18 @@ void VirtualStudio::setBufferStrategy(int index)
     settings.endGroup();
 }
 
+void VirtualStudio::setAudioActivated(bool activated)
+{
+    m_audioActivated = activated;
+    emit audioActivatedChanged();
+}
+
+void VirtualStudio::setAudioReady(bool ready)
+{
+    m_audioReady = ready;
+    emit audioReadyChanged();
+}
+
 int VirtualStudio::currentStudio()
 {
     return m_currentStudio;
@@ -605,6 +714,17 @@ void VirtualStudio::setWindowState(QString state)
     emit windowStateUpdated();
 }
 
+QString VirtualStudio::apiHost()
+{
+    return m_apiHost;
+}
+
+void VirtualStudio::setApiHost(QString host)
+{
+    m_apiHost = host;
+    emit apiHostChanged();
+}
+
 bool VirtualStudio::showWarnings()
 {
     return m_showWarnings;
@@ -620,12 +740,7 @@ void VirtualStudio::setShowWarnings(bool show)
     emit showWarningsChanged();
     // attempt to join studio if requested
     if (!m_studioToJoin.isEmpty()) {
-        // device setup view proceeds warning view
-        // if device setup is shown, do not immediately join
-        if (readyToJoin()) {
-            // We're done waiting to be on the browse page
-            joinStudio();
-        }
+        joinStudio();
     }
 }
 
@@ -727,6 +842,12 @@ void VirtualStudio::joinStudio()
         return;
     }
 
+    // FTUX shows warnings and device setup views
+    // if any of these enabled, do not immediately join
+    if (!readyToJoin()) {
+        return;
+    }
+
     QString scheme = m_studioToJoin.scheme();
     QString path   = m_studioToJoin.path();
     QString url    = m_studioToJoin.toString();
@@ -785,6 +906,7 @@ void VirtualStudio::toVirtualStudio()
                 (*parameters)[QStringLiteral("code")] = QUrl::fromPercentEncoding(code);
             } else if (stage == QAbstractOAuth2::Stage::RequestingAuthorization) {
                 parameters->insert(QStringLiteral("audience"), AUTH_AUDIENCE);
+                parameters->insert(QStringLiteral("prompt"), QStringLiteral("login"));
             }
             if (!parameters->contains("client_id")) {
                 parameters->insert("client_id", AUTH_CLIENT_ID);
@@ -860,8 +982,8 @@ void VirtualStudio::refreshDevices()
         m_outputDevice = QStringLiteral("(default)");
     }
 
-    emit inputDeviceChanged(m_inputDevice);
-    emit outputDeviceChanged(m_outputDevice);
+    emit inputDeviceChanged(m_inputDevice, false);
+    emit outputDeviceChanged(m_outputDevice, false);
 #endif
 }
 
@@ -874,16 +996,21 @@ void VirtualStudio::revertSettings()
 {
     m_uiScale = m_previousUiScale;
     emit uiScaleChanged();
+
+    setAudioActivated(false);
 #ifdef RT_AUDIO
     // Restore our previous settings
     m_inputDevice  = m_previousInput;
     m_outputDevice = m_previousOutput;
     m_bufferSize   = m_previousBuffer;
     m_useRtAudio   = m_previousUseRtAudio;
-    emit inputDeviceChanged(m_inputDevice);
-    emit outputDeviceChanged(m_outputDevice);
+
+    emit inputDeviceChanged(m_inputDevice, false);
+    emit outputDeviceChanged(m_outputDevice, false);
     emit bufferSizeChanged();
-    emit audioBackendChanged(m_useRtAudio);
+    if (m_useRtAudio != m_previousUseRtAudio) {
+        emit audioBackendChanged(m_useRtAudio, false);
+    }
 #endif
 }
 
@@ -891,6 +1018,9 @@ void VirtualStudio::applySettings()
 {
     m_previousUiScale = m_uiScale;
     emit newScale();
+
+    setAudioActivated(false);
+
     QSettings settings;
     settings.beginGroup(QStringLiteral("VirtualStudio"));
     settings.setValue(QStringLiteral("UiScale"), m_uiScale);
@@ -909,8 +1039,10 @@ void VirtualStudio::applySettings()
     m_previousInput      = m_inputDevice;
     m_previousOutput     = m_outputDevice;
 
-    emit inputDeviceChanged(m_inputDevice);
-    emit outputDeviceChanged(m_outputDevice);
+    emit previousInputChanged();
+    emit previousOutputChanged();
+    emit inputDeviceChanged(m_inputDevice, false);
+    emit outputDeviceChanged(m_outputDevice, false);
 #endif
 
     // attempt to join studio if requested
@@ -938,48 +1070,21 @@ void VirtualStudio::connectToStudio(int studioIndex)
     emit currentStudioChanged();
     m_onConnectedScreen = true;
 
-    // Check if we have an address for our server
-    if (studioInfo->host().isEmpty()) {
-        // EXPERIMENTAL CODE. (It shouldn't be possible to arrive here.)
-        if (studioInfo->isManageable()) {
-            m_connectionState = QStringLiteral("Starting Studio...");
-            emit connectionStateChanged();
-
-            // Send a put request to start our studio
-            m_startedStudio = true;
-            QString expiration =
-                QDateTime::currentDateTimeUtc().addSecs(60 * 60).toString(Qt::ISODate);
-            QJsonObject json      = {{QLatin1String("enabled"), true},
-                                {QLatin1String("expiresAt"), expiration}};
-            QJsonDocument request = QJsonDocument(json);
-
-            QNetworkReply* reply =
-                m_authenticator->put(QStringLiteral("https://%1/api/servers/%2")
-                                         .arg(m_apiHost, studioInfo->id()),
-                                     request.toJson());
-            connect(reply, &QNetworkReply::finished, this, [&, reply]() {
-                if (reply->error() != QNetworkReply::NoError) {
-                    m_connectionState = QStringLiteral("Unable to Start Studio");
-                    emit connectionStateChanged();
-                } else {
-                    QByteArray response       = reply->readAll();
-                    QJsonDocument serverState = QJsonDocument::fromJson(response);
-                    if (serverState.object()[QStringLiteral("status")].toString()
-                        == QLatin1String("Starting")) {
-                        // Start our timer to check for our hostname
-                        m_startTimer.setInterval(5000);
-                        m_startTimer.start();
-                    }
-                }
-                reply->deleteLater();
+    m_studioSocket = new VsWebSocket(
+        QUrl(QStringLiteral("wss://%1/api/servers/%2?auth_code=%3")
+                 .arg(m_apiHost, studioInfo->id(), m_authenticator->token())),
+        m_authenticator->token(), QString(), QString());
+    connect(m_studioSocket, &VsWebSocket::textMessageReceived, this,
+            [&](QString message) {
+                handleWebsocketMessage(message);
             });
-        } else {
-            m_connectionState = QStringLiteral("Unable to Start Studio");
-            emit connectionStateChanged();
-            m_startedStudio = false;
-        }
+    m_studioSocket->openSocket();
+
+    // Check if we have an address for our server
+    if (studioInfo->status() != "Ready" && studioInfo->isManageable() == true) {
+        m_connectionState = QStringLiteral("Waiting...");
+        emit connectionStateChanged();
     } else {
-        m_startedStudio = false;
         completeConnection();
     }
 }
@@ -990,10 +1095,14 @@ void VirtualStudio::completeConnection()
         return;
     }
 
+    VsServerInfo* studioInfo = static_cast<VsServerInfo*>(m_servers.at(m_currentStudio));
+    if (studioInfo->status() == QStringLiteral("Disabled")) {
+        return;
+    }
+
     m_jackTripRunning = true;
     m_connectionState = QStringLiteral("Preparing audio...");
     emit connectionStateChanged();
-    VsServerInfo* studioInfo = static_cast<VsServerInfo*>(m_servers.at(m_currentStudio));
     try {
         std::string input  = "";
         std::string output = "";
@@ -1022,10 +1131,7 @@ void VirtualStudio::completeConnection()
                          &VirtualStudio::receivedConnectionFromPeer,
                          Qt::QueuedConnection);
 
-        // Stop VsAudioInterface
-        if (!m_vsAudioInterface.isNull()) {
-            m_vsAudioInterface->closeAudio();
-        }
+        setAudioActivated(false);
 
         // Setup output volume
         m_outputVolumePlugin = new Volume(jackTrip->getNumOutputChannels());
@@ -1110,35 +1216,13 @@ void VirtualStudio::disconnect()
     m_retryPeriod = false;
 
     if (m_jackTripRunning) {
-        if (m_startedStudio) {
-            VsServerInfo* studioInfo =
-                static_cast<VsServerInfo*>(m_servers.at(m_currentStudio));
-            QMessageBox msgBox;
-            msgBox.setText(QStringLiteral("Do you want to stop the current studio?"));
-            msgBox.setWindowTitle(QStringLiteral("Stop Studio"));
-            msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
-            msgBox.setDefaultButton(QMessageBox::Yes);
-            int ret = msgBox.exec();
-            if (ret == QMessageBox::Yes) {
-                studioInfo->setHost(QLatin1String(""));
-                stopStudio();
-            }
-        }
-
         m_device->stopPinger();
         m_device->stopJackTrip();
-    } else if (m_startedStudio) {
-        m_startTimer.stop();
-        stopStudio();
-        if (!m_isExiting) {
-            emit disconnected();
-            m_onConnectedScreen = false;
-        }
     } else {
         // How did we get here? This shouldn't be possible, but include for safety.
         if (m_isExiting) {
             emit signalExit();
-        } else {
+        } else if (m_onConnectedScreen) {
             emit disconnected();
             m_onConnectedScreen = false;
         }
@@ -1151,31 +1235,58 @@ void VirtualStudio::disconnect()
         m_refreshTimer.start();
     }
 
-    // Start VsAudioInterface again
-    if (!m_vsAudioInterface.isNull()) {
-        m_vsAudioInterface->setupAudio();
-        m_vsAudioInterface->setupPlugins();
-
-        m_view.engine()->rootContext()->setContextProperty(
-            QStringLiteral("inputMeterModel"),
-            QVariant::fromValue(
-                QVector<float>(m_vsAudioInterface->getNumInputChannels())));
+    m_connectionState = QStringLiteral("Disconnected");
+    emit connectionStateChanged();
+}
 
-        m_vsAudioInterface->startProcess();
+void VirtualStudio::manageStudio(int studioIndex, bool start)
+{
+    if (studioIndex == -1) {
+        // We're here from a connected screen. Use our current studio.
+        studioIndex = m_currentStudio;
     }
 
-    m_connectionState = QStringLiteral("Disconnected");
-    emit connectionStateChanged();
+    QUrl url;
+    if (!start) {
+        url = QUrl(QStringLiteral("https://%1/studios/%2")
+                       .arg(m_apiHost,
+                            static_cast<VsServerInfo*>(m_servers.at(studioIndex))->id()));
+    } else {
+        QString expiration =
+            QDateTime::currentDateTimeUtc().addSecs(60 * 30).toString(Qt::ISODate);
+        QJsonObject json      = {{QLatin1String("enabled"), true},
+                            {QLatin1String("expiresAt"), expiration}};
+        QJsonDocument request = QJsonDocument(json);
+
+        QNetworkReply* reply = m_authenticator->put(
+            QStringLiteral("https://%1/api/servers/%2")
+                .arg(m_apiHost,
+                     static_cast<VsServerInfo*>(m_servers.at(studioIndex))->id()),
+            request.toJson());
+        connect(reply, &QNetworkReply::finished, this, [&, reply]() {
+            if (reply->error() != QNetworkReply::NoError) {
+                m_connectionState = QStringLiteral("Unable to Start Studio");
+                emit connectionStateChanged();
+            } else {
+                QByteArray response       = reply->readAll();
+                QJsonDocument serverState = QJsonDocument::fromJson(response);
+                if (serverState.object()[QStringLiteral("status")].toString()
+                    == QLatin1String("Starting")) {}
+            }
+            reply->deleteLater();
+        });
+    }
+    QDesktopServices::openUrl(url);
 }
 
-void VirtualStudio::manageStudio(int studioIndex)
+void VirtualStudio::launchVideo(int studioIndex)
 {
     if (studioIndex == -1) {
         // We're here from a connected screen. Use our current studio.
         studioIndex = m_currentStudio;
     }
     QUrl url = QUrl(
-        QStringLiteral("https://%1/studios/%2")
+        QStringLiteral("https://%1/studios/%2/live")
             .arg(m_apiHost, static_cast<VsServerInfo*>(m_servers.at(studioIndex))->id()));
     QDesktopServices::openUrl(url);
 }
@@ -1242,13 +1353,12 @@ void VirtualStudio::slotAuthSucceded()
     m_device = new VsDevice(m_authenticator.data(), m_testMode);
     m_device->registerApp();
 
-#ifdef __APPLE__
-    if (m_permissions->micPermission() == "granted") {
-        startAudio();
+    if (m_showDeviceSetup) {
+        if constexpr (isBackendAvailable<AudioInterfaceMode::JACK>()
+                      || isBackendAvailable<AudioInterfaceMode::RTAUDIO>()) {
+            setAudioActivated(true);
+        }
     }
-#else
-    startAudio();
-#endif
 
     if (m_userId.isEmpty()) {
         getUserId();
@@ -1266,20 +1376,25 @@ void VirtualStudio::slotAuthSucceded()
 
     // attempt to join studio if requested
     if (!m_studioToJoin.isEmpty()) {
-        // FTUX shows warnings and device setup views
-        // if any of these enabled, do not immediately join
-        if (readyToJoin()) {
-            // We should join in this case
-            joinStudio();
-        }
+        joinStudio();
     }
     connect(m_device, &VsDevice::updateNetworkStats, this, &VirtualStudio::updatedStats);
-    connect(m_device, &VsDevice::updatedVolumeFromServer, this,
+    connect(m_device, &VsDevice::updatedCaptureVolumeFromServer, this,
             &VirtualStudio::setInputVolume);
-    connect(m_device, &VsDevice::updatedMuteFromServer, this,
+    connect(m_device, &VsDevice::updatedCaptureMuteFromServer, this,
             &VirtualStudio::setInputMuted);
-    connect(this, &VirtualStudio::updatedInputVolume, m_device, &VsDevice::updateVolume);
-    connect(this, &VirtualStudio::updatedInputMuted, m_device, &VsDevice::updateMute);
+    connect(m_device, &VsDevice::updatedPlaybackVolumeFromServer, this,
+            &VirtualStudio::setOutputVolume);
+    connect(m_device, &VsDevice::updatedPlaybackMuteFromServer, this,
+            &VirtualStudio::setOutputMuted);
+    connect(this, &VirtualStudio::updatedInputVolume, m_device,
+            &VsDevice::updateCaptureVolume);
+    connect(this, &VirtualStudio::updatedInputMuted, m_device,
+            &VsDevice::updateCaptureMute);
+    connect(this, &VirtualStudio::updatedOutputVolume, m_device,
+            &VsDevice::updatePlaybackVolume);
+    connect(this, &VirtualStudio::updatedOutputMuted, m_device,
+            &VsDevice::updatePlaybackMute);
 }
 
 void VirtualStudio::slotAuthFailed()
@@ -1290,6 +1405,9 @@ void VirtualStudio::slotAuthFailed()
 
 void VirtualStudio::processFinished()
 {
+    // use disconnect function to handle reset of all internal flags and timers
+    disconnect();
+
     // reset network statistics
     m_networkStats = QJsonObject();
 
@@ -1298,12 +1416,6 @@ void VirtualStudio::processFinished()
         return;
     }
 
-    if (m_retryPeriod && m_startedStudio) {
-        // Retry if necessary.
-        completeConnection();
-        return;
-    }
-
     if (!m_jackTripRunning) {
         return;
     }
@@ -1311,8 +1423,14 @@ void VirtualStudio::processFinished()
     m_jackTripRunning = false;
     m_connectionState = QStringLiteral("Disconnected");
     emit connectionStateChanged();
-    emit disconnected();
-    m_onConnectedScreen = false;
+
+    // if this occurs on the setup or settings screen (for example, due to an issue with
+    // devices) then don't emit disconnected, as that would move you back to the "Browse"
+    // screen
+    if (m_onConnectedScreen) {
+        m_onConnectedScreen = false;
+        emit disconnected();
+    }
 #ifdef __APPLE__
     m_noNap.enableNap();
 #endif
@@ -1358,42 +1476,34 @@ void VirtualStudio::receivedConnectionFromPeer()
     emit connected();
 }
 
-void VirtualStudio::checkForHostname()
+void VirtualStudio::handleWebsocketMessage(const QString& msg)
 {
-    if (m_currentStudio < 0) {
-        return;
-    }
-
+    QJsonObject serverState  = QJsonDocument::fromJson(msg.toUtf8()).object();
+    QString serverStatus     = serverState[QStringLiteral("status")].toString();
+    bool serverEnabled       = serverState[QStringLiteral("enabled")].toBool();
+    QString serverCloudId    = serverState[QStringLiteral("cloudId")].toString();
     VsServerInfo* studioInfo = static_cast<VsServerInfo*>(m_servers.at(m_currentStudio));
-    QNetworkReply* reply     = m_authenticator->get(
-            QStringLiteral("https://%1/api/servers/%2").arg(m_apiHost, studioInfo->id()));
-    connect(reply, &QNetworkReply::finished, this, [&, reply, studioInfo]() {
-        if (reply->error() != QNetworkReply::NoError) {
-            m_connectionState = QStringLiteral("Unable to Start Studio");
-            emit connectionStateChanged();
-
-            // Stop our timer
-            m_startTimer.stop();
-        } else {
-            QByteArray response       = reply->readAll();
-            QJsonDocument serverState = QJsonDocument::fromJson(response);
-            if (serverState.object()[QStringLiteral("status")].toString()
-                == QLatin1String("Ready")) {
-                // Ready to connect
-                m_startTimer.stop();
-                studioInfo->setHost(
-                    serverState.object()[QStringLiteral("serverHost")].toString());
-                studioInfo->setPort(
-                    serverState.object()[QStringLiteral("serverPort")].toInt());
-                m_retryPeriod = true;
-                m_retryPeriodTimer.setInterval(15000);
-                m_retryPeriodTimer.start();
+    studioInfo->setStatus(serverStatus);
+    studioInfo->setEnabled(serverEnabled);
+    studioInfo->setCloudId(serverCloudId);
+    if (!m_jackTripRunning) {
+        if (serverStatus == QLatin1String("Ready") && m_onConnectedScreen) {
+            studioInfo->setHost(serverState[QStringLiteral("serverHost")].toString());
+            studioInfo->setPort(serverState[QStringLiteral("serverPort")].toInt());
+            studioInfo->setSessionId(serverState[QStringLiteral("sessionId")].toString());
+
+            // Call completeConnection after a short timeout
+            m_startTimer.setInterval(1000);
+            m_startTimer.setSingleShot(true);
+            connect(&m_startTimer, &QTimer::timeout, this, [&]() {
                 completeConnection();
-            }
+            });
+
+            m_startTimer.start();
         }
-        reply->deleteLater();
-        ;
-    });
+    }
+
+    emit currentStudioChanged();
 }
 
 void VirtualStudio::endRetryPeriod()
@@ -1543,6 +1653,7 @@ void VirtualStudio::setupAuthenticator()
             } else if (stage == QAbstractOAuth2::Stage::RequestingAuthorization) {
                 parameters->insert(QStringLiteral("audience"),
                                    QStringLiteral("https://api.jacktrip.org"));
+                parameters->insert(QStringLiteral("prompt"), QStringLiteral("login"));
             }
         });
 
@@ -1579,6 +1690,9 @@ void VirtualStudio::getServerList(bool firstLoad, bool signalRefresh, int index)
     {
         QMutexLocker locker(&m_refreshMutex);
         if (!m_allowRefresh || m_refreshInProgress) {
+            if (signalRefresh) {
+                emit refreshFinished(index);
+            }
             return;
         } else {
             m_refreshInProgress = true;
@@ -1597,6 +1711,9 @@ void VirtualStudio::getServerList(bool firstLoad, bool signalRefresh, int index)
         reply, &QNetworkReply::finished, this,
         [&, reply, topServerId, firstLoad, signalRefresh]() {
             if (reply->error() != QNetworkReply::NoError) {
+                if (signalRefresh) {
+                    emit refreshFinished(index);
+                }
                 std::cout << "Error: " << reply->errorString().toStdString() << std::endl;
                 emit authFailed();
                 reply->deleteLater();
@@ -1606,6 +1723,9 @@ void VirtualStudio::getServerList(bool firstLoad, bool signalRefresh, int index)
             QByteArray response      = reply->readAll();
             QJsonDocument serverList = QJsonDocument::fromJson(response);
             if (!serverList.isArray()) {
+                if (signalRefresh) {
+                    emit refreshFinished(index);
+                }
                 std::cout << "Error: Not an array" << std::endl;
                 QMutexLocker locker(&m_refreshMutex);
                 m_refreshInProgress = false;
@@ -1642,7 +1762,7 @@ void VirtualStudio::getServerList(bool firstLoad, bool signalRefresh, int index)
                         }
                         continue;
                     }
-                    if (activeStudio || (serverInfo->isManageable() && m_showInactive)) {
+                    if (activeStudio || m_showInactive) {
                         serverInfo->setName(
                             servers.at(i)[QStringLiteral("name")].toString());
                         serverInfo->setHost(
@@ -1668,6 +1788,14 @@ void VirtualStudio::getServerList(bool firstLoad, bool signalRefresh, int index)
                             servers.at(i)[QStringLiteral("sessionId")].toString());
                         serverInfo->setInviteKey(
                             servers.at(i)[QStringLiteral("inviteKey")].toString());
+                        serverInfo->setCloudId(
+                            servers.at(i)[QStringLiteral("cloudId")].toString());
+                        serverInfo->setEnabled(
+                            servers.at(i)[QStringLiteral("enabled")].toBool());
+                        serverInfo->setIsOwner(
+                            servers.at(i)[QStringLiteral("owner")].toBool());
+                        serverInfo->setIsAdmin(
+                            servers.at(i)[QStringLiteral("admin")].toBool());
                         if (servers.at(i)[QStringLiteral("owner")].toBool()) {
                             yourServers.append(serverInfo);
                             serverInfo->setSection(VsServerInfo::YOUR_STUDIOS);
@@ -1683,18 +1811,18 @@ void VirtualStudio::getServerList(bool firstLoad, bool signalRefresh, int index)
 
             std::sort(yourServers.begin(), yourServers.end(),
                       [](QObject* first, QObject* second) {
-                          return static_cast<VsServerInfo*>(first)->name()
-                                 < static_cast<VsServerInfo*>(second)->name();
+                          return *static_cast<VsServerInfo*>(first)
+                                 < *static_cast<VsServerInfo*>(second);
                       });
             std::sort(subServers.begin(), subServers.end(),
                       [](QObject* first, QObject* second) {
-                          return static_cast<VsServerInfo*>(first)->name()
-                                 < static_cast<VsServerInfo*>(second)->name();
+                          return *static_cast<VsServerInfo*>(first)
+                                 < *static_cast<VsServerInfo*>(second);
                       });
             std::sort(pubServers.begin(), pubServers.end(),
                       [](QObject* first, QObject* second) {
-                          return static_cast<VsServerInfo*>(first)->name()
-                                 < static_cast<VsServerInfo*>(second)->name();
+                          return *static_cast<VsServerInfo*>(first)
+                                 < *static_cast<VsServerInfo*>(second);
                       });
 
             // If we don't have any owned servers, move the JackTrip logo to an
@@ -1725,6 +1853,9 @@ void VirtualStudio::getServerList(bool firstLoad, bool signalRefresh, int index)
             // request going out and the response.
             if (!m_allowRefresh) {
                 m_refreshInProgress = false;
+                if (signalRefresh) {
+                    emit refreshFinished(index);
+                }
                 return;
             }
             m_servers.clear();
@@ -1870,8 +2001,8 @@ void VirtualStudio::startAudio()
             QStringLiteral("audioInterface"), m_vsAudioInterface.data());
     }
 #ifdef RT_AUDIO
-    m_vsAudioInterface->setInputDevice(m_inputDevice);
-    m_vsAudioInterface->setOutputDevice(m_outputDevice);
+    m_vsAudioInterface->setInputDevice(m_inputDevice, false);
+    m_vsAudioInterface->setOutputDevice(m_outputDevice, false);
     m_vsAudioInterface->setAudioInterfaceMode(m_useRtAudio);
 #endif
     connect(m_vsAudioInterface.data(), &VsAudioInterface::devicesErrorMsgChanged, this,
@@ -1903,6 +2034,9 @@ void VirtualStudio::startAudio()
 
     m_vsAudioInterface->setupPlugins();
 
+    m_audioReady = true;
+    emit audioReadyChanged();
+
     m_view.engine()->rootContext()->setContextProperty(
         QStringLiteral("inputMeterModel"),
         QVariant::fromValue(QVector<float>(m_vsAudioInterface->getNumInputChannels())));
@@ -1910,6 +2044,61 @@ void VirtualStudio::startAudio()
     m_vsAudioInterface->startProcess();
 }
 
+void VirtualStudio::restartAudio()
+{
+#ifdef __APPLE__
+    if (m_permissions->micPermission() != "granted") {
+        return;
+    }
+#endif
+    // Start VsAudioInterface again
+    if (!m_vsAudioInterface.isNull()) {
+        m_vsAudioInterface->setupAudio();
+        m_vsAudioInterface->setupPlugins();
+
+        m_audioReady = true;
+        emit audioReadyChanged();
+
+        m_view.engine()->rootContext()->setContextProperty(
+            QStringLiteral("inputMeterModel"),
+            QVariant::fromValue(
+                QVector<float>(m_vsAudioInterface->getNumInputChannels())));
+
+        m_vsAudioInterface->startProcess();
+    } else {
+        startAudio();
+    }
+}
+
+void VirtualStudio::stopAudio()
+{
+    // Stop VsAudioInterface
+    if (!m_vsAudioInterface.isNull()) {
+        m_vsAudioInterface->closeAudio();
+        setAudioReady(false);
+    }
+}
+
+void VirtualStudio::toggleAudio()
+{
+#ifdef __APPLE__
+    if (m_permissions->micPermission() != "granted") {
+        return;
+    }
+#endif
+
+    if constexpr (!(isBackendAvailable<AudioInterfaceMode::JACK>()
+                    || isBackendAvailable<AudioInterfaceMode::RTAUDIO>())) {
+        return;
+    }
+
+    if (!m_audioActivated) {
+        stopAudio();
+    } else {
+        restartAudio();
+    }
+}
+
 void VirtualStudio::stopStudio()
 {
     if (m_currentStudio < 0) {
@@ -1933,8 +2122,10 @@ void VirtualStudio::stopStudio()
 
 bool VirtualStudio::readyToJoin()
 {
+    // FTUX shows warnings and device setup views
+    // if any of these enabled, do not immediately join
     return m_windowState == "browse"
-           && (m_connectionState == QStringLiteral("Waiting")
+           && (m_connectionState == QStringLiteral("Waiting...")
                || m_connectionState == QStringLiteral("Disconnected"));
 }
 
@@ -1998,6 +2189,7 @@ VirtualStudio::~VirtualStudio()
     delete m_inputMeter;
     delete m_outputMeter;
     delete m_inputTestMeter;
+    delete m_studioSocket;
 
     QDesktopServices::unsetUrlHandler("jacktrip");
 }
index 5f1009b328abffdecbb47e244791ed3632260df9..f6d91f49cfe63cc717c7d1ab4e6b9f7be4ae1640 100644 (file)
@@ -82,6 +82,10 @@ class VirtualStudio : public QObject
         int inputDevice READ inputDevice WRITE setInputDevice NOTIFY inputDeviceChanged)
     Q_PROPERTY(int outputDevice READ outputDevice WRITE setOutputDevice NOTIFY
                    outputDeviceChanged)
+    Q_PROPERTY(int previousInput READ previousInput WRITE setPreviousInput NOTIFY
+                   previousInputChanged)
+    Q_PROPERTY(int previousOutput READ previousOutput WRITE setPreviousOutput NOTIFY
+                   previousOutputChanged)
 
     Q_PROPERTY(QString devicesWarning READ devicesWarning NOTIFY devicesWarningChanged)
     Q_PROPERTY(QString devicesError READ devicesError NOTIFY devicesErrorChanged)
@@ -125,8 +129,14 @@ class VirtualStudio : public QObject
                    updatedOutputVolume)
     Q_PROPERTY(
         bool inputMuted READ inputMuted WRITE setInputMuted NOTIFY updatedInputMuted)
+    Q_PROPERTY(bool audioActivated READ audioActivated WRITE setAudioActivated NOTIFY
+                   audioActivatedChanged)
+    Q_PROPERTY(
+        bool audioReady READ audioReady WRITE setAudioReady NOTIFY audioReadyChanged)
+    Q_PROPERTY(bool backendAvailable READ backendAvailable CONSTANT)
     Q_PROPERTY(QString windowState READ windowState WRITE setWindowState NOTIFY
                    windowStateUpdated)
+    Q_PROPERTY(QString apiHost READ apiHost WRITE setApiHost NOTIFY apiHostChanged)
 
    public:
     explicit VirtualStudio(bool firstRun = false, QObject* parent = nullptr);
@@ -148,6 +158,10 @@ class VirtualStudio : public QObject
     void setInputDevice(int device);
     int outputDevice();
     void setOutputDevice(int device);
+    int previousInput();
+    void setPreviousInput(int device);
+    int previousOutput();
+    void setPreviousOutput(int device);
     QString devicesWarning();
     QString devicesError();
     QString devicesWarningHelpUrl();
@@ -191,7 +205,13 @@ class VirtualStudio : public QObject
     float outputVolume();
     bool inputMuted();
     bool outputMuted();
+    Q_INVOKABLE void restartAudio();
+    bool audioActivated();
+    bool audioReady();
+    bool backendAvailable();
     QString windowState();
+    QString apiHost();
+    void setApiHost(QString host);
 
    public slots:
     void toStandard();
@@ -206,7 +226,8 @@ class VirtualStudio : public QObject
     void connectToStudio(int studioIndex);
     void completeConnection();
     void disconnect();
-    void manageStudio(int studioIndex);
+    void manageStudio(int studioIndex, bool start = false);
+    void launchVideo(int studioIndex);
     void createStudio();
     void editProfile();
     void showAbout();
@@ -217,6 +238,8 @@ class VirtualStudio : public QObject
     void setOutputVolume(float multiplier);
     void setInputMuted(bool muted);
     void setOutputMuted(bool muted);
+    void setAudioActivated(bool activated);
+    void setAudioReady(bool ready);
     void setWindowState(QString state);
     void exit();
 
@@ -230,11 +253,13 @@ class VirtualStudio : public QObject
     void showFirstRunChanged();
     void hasRefreshTokenChanged();
     void logoSectionChanged();
-    void audioBackendChanged(bool useRtAudio);
-    void inputDeviceChanged(QString device);
-    void outputDeviceChanged(QString device);
-    void inputDeviceSelected(QString device);
-    void outputDeviceSelected(QString device);
+    void audioBackendChanged(bool useRtAudio, bool shouldRestart = true);
+    void inputDeviceChanged(QString device, bool shouldRestart = true);
+    void outputDeviceChanged(QString device, bool shouldRestart = true);
+    void inputDeviceSelected(QString device, bool shouldRestart = true);
+    void outputDeviceSelected(QString device, bool shouldRestart = true);
+    void previousInputChanged();
+    void previousOutputChanged();
     void devicesWarningChanged();
     void devicesErrorChanged();
     void devicesWarningHelpUrlChanged();
@@ -265,7 +290,10 @@ class VirtualStudio : public QObject
     void updatedOutputVolume(float multiplier);
     void updatedInputMuted(bool muted);
     void updatedOutputMuted(bool muted);
+    void audioActivatedChanged();
+    void audioReadyChanged();
     void windowStateUpdated();
+    void apiHostChanged();
 
    private slots:
     void slotAuthSucceded();
@@ -273,7 +301,7 @@ class VirtualStudio : public QObject
     void processFinished();
     void processError(const QString& errorMessage);
     void receivedConnectionFromPeer();
-    void checkForHostname();
+    void handleWebsocketMessage(const QString& msg);
     void endRetryPeriod();
     void launchBrowser(const QUrl& url);
     void joinStudio();
@@ -295,6 +323,8 @@ class VirtualStudio : public QObject
     void getRegions();
     void getUserMetadata();
     void stopStudio();
+    void toggleAudio();
+    void stopAudio();
     bool readyToJoin();
 #ifdef RT_AUDIO
     QVariant formatDeviceList(const QStringList& devices, const QStringList& categories);
@@ -317,11 +347,11 @@ class VirtualStudio : public QObject
     bool m_selectableBackend  = true;
     bool m_useRtAudio         = false;
     int m_currentStudio       = -1;
-    QString m_connectionState = QStringLiteral("Waiting");
+    QString m_connectionState = QStringLiteral("Waiting...");
     QScopedPointer<JackTrip> m_jackTrip;
+    VsWebSocket* m_studioSocket = NULL;
     QTimer m_startTimer;
     QTimer m_retryPeriodTimer;
-    bool m_startedStudio = false;
     bool m_retryPeriod;
     bool m_jackTripRunning = false;
 
@@ -352,7 +382,9 @@ class VirtualStudio : public QObject
     bool m_testMode         = false;
     QString m_failedMessage = "";
     QUrl m_studioToJoin;
-    bool m_authenticated = false;
+    bool m_authenticated  = false;
+    bool m_audioActivated = false;
+    bool m_audioReady     = false;
 
     Meter* m_inputMeter;
     Meter* m_outputMeter;
index df0c7a7600ec732a7daa4c2b4941ec110677b318..45a65da321938a2fdd0cfbc32bb7ac41fd7271d8 100644 (file)
@@ -57,17 +57,28 @@ VsAudioInterface::VsAudioInterface(int NumChansIn, int NumChansOut,
     , m_inputDeviceName("")
     , m_outputDeviceName("")
     , m_audioBufferSize(gDefaultBufferSizeInSamples)
+#ifdef RT_AUDIO
     , m_audioInterfaceMode(VsAudioInterface::RTAUDIO)
+#else
+    , m_audioInterfaceMode(VsAudioInterface::JACK)
+#endif
 {
     QSettings settings;
     settings.beginGroup(QStringLiteral("Audio"));
-    m_inMultiplier       = settings.value(QStringLiteral("InMultiplier"), 1).toFloat();
-    m_outMultiplier      = settings.value(QStringLiteral("OutMultiplier"), 1).toFloat();
-    m_inMuted            = settings.value(QStringLiteral("InMuted"), false).toBool();
-    m_outMuted           = settings.value(QStringLiteral("OutMuted"), false).toBool();
-    m_audioInterfaceMode = (settings.value(QStringLiteral("Backend"), 0).toInt() == 1)
-                               ? VsAudioInterface::RTAUDIO
-                               : VsAudioInterface::JACK;
+    m_inMultiplier  = settings.value(QStringLiteral("InMultiplier"), 1).toFloat();
+    m_outMultiplier = settings.value(QStringLiteral("OutMultiplier"), 1).toFloat();
+    m_inMuted       = settings.value(QStringLiteral("InMuted"), false).toBool();
+    m_outMuted      = settings.value(QStringLiteral("OutMuted"), false).toBool();
+    if constexpr (isBackendAvailable<AudioInterfaceMode::ALL>()) {
+        m_audioInterfaceMode = (settings.value(QStringLiteral("Backend"), 0).toInt() == 1)
+                                   ? VsAudioInterface::RTAUDIO
+                                   : VsAudioInterface::JACK;
+    } else if constexpr (isBackendAvailable<AudioInterfaceMode::RTAUDIO>()) {
+        m_audioInterfaceMode = VsAudioInterface::RTAUDIO;
+    } else {
+        m_audioInterfaceMode = VsAudioInterface::JACK;
+    }
+
     m_inputDeviceName =
         settings.value(QStringLiteral("InputDevice"), "").toString().toStdString();
     m_outputDeviceName =
@@ -99,131 +110,30 @@ void VsAudioInterface::setupAudio()
 
         // Create AudioInterface Client Object
         if (m_audioInterfaceMode == VsAudioInterface::JACK) {
-#ifndef NO_JACK
-            if (gVerboseFlag)
-                std::cout << "  JackTrip:setupAudio before new JackAudioInterface"
-                          << std::endl;
-            m_audioInterface.reset(new JackAudioInterface(
-                m_numAudioChansIn, m_numAudioChansOut, m_audioBitResolution));
-
-            m_audioInterface->setClientName(QStringLiteral("JackTrip"));
-
-            if (gVerboseFlag)
-                std::cout << "  JackTrip:setupAudio before m_audioInterface->setup"
-                          << std::endl;
-            m_audioInterface->setup(true);
-
-            std::string devicesWarningMsg = m_audioInterface->getDevicesWarningMsg();
-            std::string devicesErrorMsg   = m_audioInterface->getDevicesErrorMsg();
-
-            if (devicesWarningMsg != "") {
-                qDebug() << "Devices Warning: "
-                         << QString::fromStdString(devicesWarningMsg);
-            }
-
-            if (devicesErrorMsg != "") {
-                qDebug() << "Devices Error: " << QString::fromStdString(devicesErrorMsg);
-            }
-
-            updateDevicesWarningMsg(QString::fromStdString(devicesWarningMsg));
-            updateDevicesErrorMsg(QString::fromStdString(devicesErrorMsg));
-
-            if (gVerboseFlag)
-                std::cout
-                    << "  JackTrip:setupAudio before m_audioInterface->getSampleRate"
-                    << std::endl;
-            m_sampleRate = m_audioInterface->getSampleRate();
-            if (gVerboseFlag)
-                std::cout << "  JackTrip:setupAudio before m_audioInterface->getDeviceID"
-                          << std::endl;
-            m_deviceID = m_audioInterface->getDeviceID();
-            if (gVerboseFlag)
-                std::cout << "  JackTrip:setupAudio before "
-                             "m_audioInterface->getBufferSizeInSamples"
-                          << std::endl;
-            m_audioBufferSize = m_audioInterface->getBufferSizeInSamples();
-#endif          //__NON_JACK__
-#ifdef NO_JACK  /// \todo FIX THIS REPETITION OF CODE
-#ifdef RT_AUDIO
-            std::cout << "Warning: using non jack version, RtAudio will be used instead"
-                      << std::endl;
-            m_audioInterface.reset(new RtAudioInterface(
-                m_numAudioChansIn, m_numAudioChansOut, m_audioBitResolution));
-            m_audioInterface->setSampleRate(m_sampleRate);
-            m_audioInterface->setDeviceID(m_deviceID);
-            m_audioInterface->setInputDevice(m_inputDeviceName);
-            m_audioInterface->setOutputDevice(m_outputDeviceName);
-            m_audioInterface->setBufferSizeInSamples(m_audioBufferSize);
-
-            m_audioInterface->setup(true);
-            // Setup might have reduced number of channels
-            m_numAudioChansIn  = m_audioInterface->getNumInputChannels();
-            m_numAudioChansOut = m_audioInterface->getNumOutputChannels();
-            // Setup might have changed buffer size
-            m_audioBufferSize = m_audioInterface->getBufferSizeInSamples();
-
-            std::string devicesWarningMsg = m_audioInterface->getDevicesWarningMsg();
-            std::string devicesErrorMsg   = m_audioInterface->getDevicesErrorMsg();
-
-            if (devicesWarningMsg != "") {
-                qDebug() << "Devices Warning: "
-                         << QString::fromStdString(devicesWarningMsg);
-            }
-
-            if (devicesErrorMsg != "") {
-                qDebug() << "Devices Error: " << QString::fromStdString(devicesErrorMsg);
-            }
-
-            updateDevicesWarningMsg(QString::fromStdString(devicesWarningMsg));
-            updateDevicesErrorMsg(QString::fromStdString(devicesErrorMsg));
-
-#endif
-#endif
-        } else if (m_audioInterfaceMode == VsAudioInterface::RTAUDIO) {
-#ifdef RT_AUDIO
-            m_audioInterface.reset(new RtAudioInterface(
-                m_numAudioChansIn, m_numAudioChansOut, m_audioBitResolution));
-            m_audioInterface->setSampleRate(m_sampleRate);
-            m_audioInterface->setDeviceID(m_deviceID);
-            m_audioInterface->setInputDevice(m_inputDeviceName);
-            m_audioInterface->setOutputDevice(m_outputDeviceName);
-            m_audioInterface->setBufferSizeInSamples(m_audioBufferSize);
-
-            m_audioInterface->setup(true);
-            // Setup might have reduced number of channels
-            m_numAudioChansIn  = m_audioInterface->getNumInputChannels();
-            m_numAudioChansOut = m_audioInterface->getNumOutputChannels();
-            // Setup might have changed buffer size
-            m_audioBufferSize = m_audioInterface->getBufferSizeInSamples();
-
-            std::string devicesWarningMsg = m_audioInterface->getDevicesWarningMsg();
-            std::string devicesErrorMsg   = m_audioInterface->getDevicesErrorMsg();
-            std::string devicesWarningHelpUrl =
-                m_audioInterface->getDevicesWarningHelpUrl();
-            std::string devicesErrorHelpUrl = m_audioInterface->getDevicesErrorHelpUrl();
-
-            if (devicesWarningMsg != "") {
-                qDebug() << "Devices Warning: "
-                         << QString::fromStdString(devicesWarningMsg);
-                if (devicesWarningHelpUrl != "") {
-                    qDebug() << "Learn More: "
-                             << QString::fromStdString(devicesWarningHelpUrl);
+            if constexpr (isBackendAvailable<AudioInterfaceMode::ALL>()
+                          || isBackendAvailable<AudioInterfaceMode::JACK>()) {
+                setupJackAudio();
+            } else {
+                if constexpr (isBackendAvailable<AudioInterfaceMode::RTAUDIO>()) {
+                    setupRtAudio();
+                } else {
+                    throw std::runtime_error(
+                        "JackTrip was compiled without RtAudio and can't find JACK. In "
+                        "order to use JackTrip, you'll need to install JACK or rebuild "
+                        "with RtAudio support.");
+                    std::exit(1);
                 }
             }
-
-            if (devicesErrorMsg != "") {
-                qDebug() << "Devices Error: " << QString::fromStdString(devicesErrorMsg);
-                if (devicesErrorHelpUrl != "") {
-                    qDebug() << "Learn More: "
-                             << QString::fromStdString(devicesErrorHelpUrl);
-                }
+        } else if (m_audioInterfaceMode == VsAudioInterface::RTAUDIO) {
+            if constexpr (isBackendAvailable<AudioInterfaceMode::RTAUDIO>()) {
+                setupRtAudio();
+            } else {
+                throw std::runtime_error(
+                    "JackTrip was compiled without RtAudio and can't find JACK. In order "
+                    "to use JackTrip, you'll need to install JACK or rebuild with "
+                    "RtAudio support.");
+                std::exit(1);
             }
-
-            updateDevicesWarningMsg(QString::fromStdString(devicesWarningMsg));
-            updateDevicesErrorMsg(QString::fromStdString(devicesErrorMsg));
-            updateDevicesWarningHelpUrl(QString::fromStdString(devicesWarningHelpUrl));
-            updateDevicesErrorHelpUrl(QString::fromStdString(devicesErrorHelpUrl));
-#endif
         }
 
         std::cout << "The Sampling Rate is: " << m_sampleRate << std::endl;
@@ -243,6 +153,111 @@ void VsAudioInterface::setupAudio()
     }
 }
 
+void VsAudioInterface::setupJackAudio()
+{
+#ifndef NO_JACK
+    if constexpr (isBackendAvailable<AudioInterfaceMode::ALL>()
+                  || isBackendAvailable<AudioInterfaceMode::JACK>()) {
+        if (gVerboseFlag)
+            std::cout << "  JackTrip:setupAudio before new JackAudioInterface"
+                      << std::endl;
+        m_audioInterface.reset(new JackAudioInterface(
+            m_numAudioChansIn, m_numAudioChansOut, m_audioBitResolution));
+
+        m_audioInterface->setClientName(QStringLiteral("JackTrip"));
+
+        if (gVerboseFlag)
+            std::cout << "  JackTrip:setupAudio before m_audioInterface->setup"
+                      << std::endl;
+        m_audioInterface->setup(true);
+
+        std::string devicesWarningMsg = m_audioInterface->getDevicesWarningMsg();
+        std::string devicesErrorMsg   = m_audioInterface->getDevicesErrorMsg();
+
+        if (devicesWarningMsg != "") {
+            qDebug() << "Devices Warning: " << QString::fromStdString(devicesWarningMsg);
+        }
+
+        if (devicesErrorMsg != "") {
+            qDebug() << "Devices Error: " << QString::fromStdString(devicesErrorMsg);
+        }
+
+        updateDevicesWarningMsg(QString::fromStdString(devicesWarningMsg));
+        updateDevicesErrorMsg(QString::fromStdString(devicesErrorMsg));
+
+        if (gVerboseFlag)
+            std::cout << "  JackTrip:setupAudio before m_audioInterface->getSampleRate"
+                      << std::endl;
+        m_sampleRate = m_audioInterface->getSampleRate();
+        if (gVerboseFlag)
+            std::cout << "  JackTrip:setupAudio before m_audioInterface->getDeviceID"
+                      << std::endl;
+        m_deviceID = m_audioInterface->getDeviceID();
+        if (gVerboseFlag)
+            std::cout << "  JackTrip:setupAudio before "
+                         "m_audioInterface->getBufferSizeInSamples"
+                      << std::endl;
+        m_audioBufferSize = m_audioInterface->getBufferSizeInSamples();
+    } else {
+        return;
+    }
+#else
+    return;
+#endif
+}
+
+void VsAudioInterface::setupRtAudio()
+{
+#ifdef RT_AUDIO
+    if constexpr (isBackendAvailable<AudioInterfaceMode::ALL>()
+                  || isBackendAvailable<AudioInterfaceMode::RTAUDIO>()) {
+        m_audioInterface.reset(new RtAudioInterface(m_numAudioChansIn, m_numAudioChansOut,
+                                                    m_audioBitResolution));
+        m_audioInterface->setSampleRate(m_sampleRate);
+        m_audioInterface->setDeviceID(m_deviceID);
+        m_audioInterface->setInputDevice(m_inputDeviceName);
+        m_audioInterface->setOutputDevice(m_outputDeviceName);
+        m_audioInterface->setBufferSizeInSamples(m_audioBufferSize);
+
+        m_audioInterface->setup(true);
+        // Setup might have reduced number of channels
+        m_numAudioChansIn  = m_audioInterface->getNumInputChannels();
+        m_numAudioChansOut = m_audioInterface->getNumOutputChannels();
+        // Setup might have changed buffer size
+        m_audioBufferSize = m_audioInterface->getBufferSizeInSamples();
+
+        std::string devicesWarningMsg     = m_audioInterface->getDevicesWarningMsg();
+        std::string devicesErrorMsg       = m_audioInterface->getDevicesErrorMsg();
+        std::string devicesWarningHelpUrl = m_audioInterface->getDevicesWarningHelpUrl();
+        std::string devicesErrorHelpUrl   = m_audioInterface->getDevicesErrorHelpUrl();
+
+        if (devicesWarningMsg != "") {
+            qDebug() << "Devices Warning: " << QString::fromStdString(devicesWarningMsg);
+            if (devicesWarningHelpUrl != "") {
+                qDebug() << "Learn More: "
+                         << QString::fromStdString(devicesWarningHelpUrl);
+            }
+        }
+
+        if (devicesErrorMsg != "") {
+            qDebug() << "Devices Error: " << QString::fromStdString(devicesErrorMsg);
+            if (devicesErrorHelpUrl != "") {
+                qDebug() << "Learn More: " << QString::fromStdString(devicesErrorHelpUrl);
+            }
+        }
+
+        updateDevicesWarningMsg(QString::fromStdString(devicesWarningMsg));
+        updateDevicesErrorMsg(QString::fromStdString(devicesErrorMsg));
+        updateDevicesWarningHelpUrl(QString::fromStdString(devicesWarningHelpUrl));
+        updateDevicesErrorHelpUrl(QString::fromStdString(devicesErrorHelpUrl));
+    } else {
+        return;
+    }
+#else
+    return;
+#endif
+}
+
 void VsAudioInterface::closeAudio()
 {
     if (!m_audioInterface.isNull()) {
@@ -286,7 +301,7 @@ void VsAudioInterface::addOutputPlugin(ProcessPlugin* plugin)
     m_audioInterface->appendProcessPluginFromNetwork(plugin);
 }
 
-void VsAudioInterface::setInputDevice(QString deviceName)
+void VsAudioInterface::setInputDevice(QString deviceName, bool shouldRestart)
 {
     m_inputDeviceName = deviceName.toStdString();
     if (m_inputDeviceName == "(default)") {
@@ -295,13 +310,13 @@ void VsAudioInterface::setInputDevice(QString deviceName)
 
     if (!m_audioInterface.isNull()) {
         m_audioInterface->setInputDevice(m_inputDeviceName);
-        if (m_audioActive) {
+        if (m_audioActive && shouldRestart) {
             emit settingsUpdated();
         }
     }
 }
 
-void VsAudioInterface::setOutputDevice(QString deviceName)
+void VsAudioInterface::setOutputDevice(QString deviceName, bool shouldRestart)
 {
     m_outputDeviceName = deviceName.toStdString();
     if (m_outputDeviceName == "(default)") {
@@ -310,20 +325,24 @@ void VsAudioInterface::setOutputDevice(QString deviceName)
 
     if (!m_audioInterface.isNull()) {
         m_audioInterface->setOutputDevice(m_outputDeviceName);
-        if (m_audioActive) {
+        if (m_audioActive && shouldRestart) {
             emit settingsUpdated();
         }
     }
 }
 
-void VsAudioInterface::setAudioInterfaceMode(bool useRtAudio)
+void VsAudioInterface::setAudioInterfaceMode(bool useRtAudio, bool shouldRestart)
 {
     if (useRtAudio) {
+#ifdef RT_AUDIO
         m_audioInterfaceMode = VsAudioInterface::RTAUDIO;
+#endif
     } else {
+#ifndef NO_JACK
         m_audioInterfaceMode = VsAudioInterface::JACK;
+#endif
     }
-    if (!m_audioInterface.isNull() || m_hasBeenActive) {
+    if ((!m_audioInterface.isNull() || m_hasBeenActive) && shouldRestart) {
         emit modeUpdated();
     }
 }
@@ -373,9 +392,11 @@ void VsAudioInterface::startProcess()
         try {
             m_audioInterface->initPlugins(false);
             m_audioInterface->startProcess();
+#ifndef NO_JACK
             if (m_audioInterfaceMode == VsAudioInterface::JACK) {
                 m_audioInterface->connectDefaultPorts();
             }
+#endif
         } catch (const std::exception& e) {
             emit errorToProcess(QString::fromUtf8(e.what()));
         }
index f4660d14b6ca7787e14b14404451befb6d1d425c..9c28c9c9aceb3859a659a5248b4a876b5db10ff3 100644 (file)
@@ -54,6 +54,7 @@
 #include "../Tone.h"
 #include "../Volume.h"
 #include "../jacktrip_globals.h"
+#include "AudioInterfaceMode.h"
 
 class VsAudioInterface : public QObject
 {
@@ -91,9 +92,9 @@ class VsAudioInterface : public QObject
     };
 
    public slots:
-    void setInputDevice(QString deviceName);
-    void setOutputDevice(QString deviceName);
-    void setAudioInterfaceMode(bool useRtAudio);
+    void setInputDevice(QString deviceName, bool shouldRestart = true);
+    void setOutputDevice(QString deviceName, bool shouldRestart = true);
+    void setAudioInterfaceMode(bool useRtAudio, bool shouldRestart = true);
     void setInputVolume(float multiplier);
     void setOutputVolume(float multiplier);
     void setInputMuted(bool muted);
@@ -120,6 +121,9 @@ class VsAudioInterface : public QObject
     void processMeterMeasurements(QVector<float> values);
 
    private:
+    void setupJackAudio();
+    void setupRtAudio();
+
     float m_inMultiplier  = 1.0;
     float m_outMultiplier = 1.0;
     bool m_inMuted        = false;
index 3bc63b5844e33493fb152aad6d56eeff98d82c36..4f06599bdc540a3ab39d5b4ec598e8f01b5f989e 100644 (file)
@@ -55,6 +55,9 @@ VsDevice::VsDevice(QOAuth2AuthorizationCodeFlow* authenticator, bool testMode,
     m_captureVolume =
         (float)settings.value(QStringLiteral("InMultiplier"), 1.0).toDouble();
     m_captureMute = settings.value(QStringLiteral("InMuted"), false).toBool();
+    m_playbackVolume =
+        (float)settings.value(QStringLiteral("OutMultiplier"), 1.0).toDouble();
+    m_playbackMute = settings.value(QStringLiteral("OutMuted"), false).toBool();
     settings.endGroup();
 
     m_sendVolumeTimer = new QTimer(this);
@@ -71,6 +74,8 @@ VsDevice::VsDevice(QOAuth2AuthorizationCodeFlow* authenticator, bool testMode,
     QJsonObject json = {
         {QLatin1String("captureVolume"), (int)(m_captureVolume * 100.0)},
         {QLatin1String("captureMute"), m_captureMute},
+        {QLatin1String("playbackVolume"), (int)(m_playbackVolume * 100.0)},
+        {QLatin1String("playbackMute"), m_playbackMute},
     };
     QJsonDocument request = QJsonDocument(json);
 
@@ -84,31 +89,37 @@ VsDevice::VsDevice(QOAuth2AuthorizationCodeFlow* authenticator, bool testMode,
                 reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
             if (!statusCode.isValid()) {
                 std::cout << "Error: " << reply->errorString().toStdString() << std::endl;
-                // TODO: Fix me
-                // emit authFailed();
                 reply->deleteLater();
                 return;
             }
         } else {
             QByteArray response       = reply->readAll();
             QJsonDocument deviceState = QJsonDocument::fromJson(response);
-            float deviceCaptureVol =
+
+            // capture (input) volume
+            m_captureVolume =
                 (float)(deviceState.object()[QStringLiteral("captureVolume")].toDouble()
                         / 100.0);
-            float deviceCaptureMute =
-                deviceState.object()[QStringLiteral("captureMute")].toBool();
-
-            m_captureVolume = deviceCaptureVol;
-            emit updatedVolumeFromServer(m_captureVolume);
+            m_captureMute = deviceState.object()[QStringLiteral("captureMute")].toBool();
+            emit updatedCaptureVolumeFromServer(m_captureVolume);
+            emit updatedCaptureMuteFromServer(m_captureMute);
 
-            m_captureMute = deviceCaptureMute;
-            emit updatedMuteFromServer(m_captureMute);
+            // playback (output) volume
+            m_playbackVolume =
+                (float)(deviceState.object()[QStringLiteral("playbackVolume")].toDouble()
+                        / 100.0);
+            m_playbackMute =
+                deviceState.object()[QStringLiteral("playbackMute")].toBool();
+            emit updatedPlaybackVolumeFromServer(m_playbackVolume);
+            emit updatedPlaybackMuteFromServer(m_playbackMute);
         }
 
         QSettings settings;
         settings.beginGroup(QStringLiteral("Audio"));
         settings.setValue(QStringLiteral("InMultiplier"), m_captureVolume);
         settings.setValue(QStringLiteral("InMuted"), m_captureMute);
+        settings.setValue(QStringLiteral("OutMultiplier"), m_playbackVolume);
+        settings.setValue(QStringLiteral("OutMuted"), m_playbackMute);
         settings.endGroup();
 
         reply->deleteLater();
@@ -132,8 +143,6 @@ void VsDevice::registerApp()
                 reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
             if (!statusCode.isValid()) {
                 std::cout << "Error: " << reply->errorString().toStdString() << std::endl;
-                // TODO: Fix me
-                // emit authFailed();
                 reply->deleteLater();
                 return;
             }
@@ -152,8 +161,6 @@ void VsDevice::registerApp()
             } else {
                 // Other error status. Won't create device.
                 std::cout << "Error: " << reply->errorString().toStdString() << std::endl;
-                // TODO: Fix me
-                // emit authFailed();
                 reply->deleteLater();
                 return;
             }
@@ -184,8 +191,6 @@ void VsDevice::removeApp()
     connect(reply, &QNetworkReply::finished, this, [=]() {
         if (reply->error() != QNetworkReply::NoError) {
             std::cout << "Error: " << reply->errorString().toStdString() << std::endl;
-            // TODO: Fix me
-            // emit authFailed();
             reply->deleteLater();
             return;
         } else {
@@ -280,8 +285,6 @@ void VsDevice::sendHeartbeat()
         connect(reply, &QNetworkReply::finished, this, [=]() {
             if (reply->error() != QNetworkReply::NoError) {
                 std::cout << "Error: " << reply->errorString().toStdString() << std::endl;
-                // TODO: Fix me
-                // emit authFailed();
                 reply->deleteLater();
                 return;
             } else {
@@ -307,8 +310,6 @@ void VsDevice::setServerId(QString serverId)
     connect(reply, &QNetworkReply::finished, this, [=]() {
         if (reply->error() != QNetworkReply::NoError) {
             std::cout << "Error: " << reply->errorString().toStdString() << std::endl;
-            // TODO: Fix me
-            // emit authFailed();
             reply->deleteLater();
             return;
         }
@@ -322,6 +323,8 @@ void VsDevice::sendLevels()
     QJsonObject json = {
         {QLatin1String("captureVolume"), (int)(m_captureVolume * 100.0)},
         {QLatin1String("captureMute"), m_captureMute},
+        {QLatin1String("playbackVolume"), (int)(m_playbackVolume * 100.0)},
+        {QLatin1String("playbackMute"), m_playbackMute},
     };
     QJsonDocument request = QJsonDocument(json);
     QNetworkReply* reply  = m_authenticator->put(
@@ -330,8 +333,6 @@ void VsDevice::sendLevels()
     connect(reply, &QNetworkReply::finished, this, [=]() {
         if (reply->error() != QNetworkReply::NoError) {
             std::cout << "Error: " << reply->errorString().toStdString() << std::endl;
-            // TODO: Fix me
-            // emit authFailed();
             reply->deleteLater();
             return;
         }
@@ -415,11 +416,13 @@ void VsDevice::reconcileAgentConfig(QJsonDocument newState)
         return;
     }
     for (auto it = newObject.constBegin(); it != newObject.constEnd(); it++) {
+        // if currently enabled but new config is not enabled, disconnect immediately
+        if (enabled() && it.key() == "enabled" && !it.value().toBool()
+            && !m_jackTrip.isNull()) {
+            stopJackTrip();
+        }
         m_deviceAgentConfig.insert(it.key(), it.value());
     }
-    if (!enabled() && !m_jackTrip.isNull()) {
-        stopJackTrip();
-    }
 }
 
 // initPinger intializes the pinger used to generate network latency statistics for
@@ -445,8 +448,8 @@ void VsDevice::stopPinger()
     }
 }
 
-// updateVolume sets VsDevice's capture volume to the provided float
-void VsDevice::updateVolume(float multiplier)
+// updateCaptureVolume sets VsDevice's capture (input) volume to the provided float
+void VsDevice::updateCaptureVolume(float multiplier)
 {
     if (multiplier == m_captureVolume) {
         return;
@@ -458,8 +461,8 @@ void VsDevice::updateVolume(float multiplier)
     }
 }
 
-// updateMute sets VsDevice's capture mute to the provided boolean
-void VsDevice::updateMute(bool muted)
+// updateCaptureMute sets VsDevice's capture (input) mute to the provided boolean
+void VsDevice::updateCaptureMute(bool muted)
 {
     if (muted == m_captureMute) {
         return;
@@ -471,6 +474,32 @@ void VsDevice::updateMute(bool muted)
     }
 }
 
+// updatePlaybackVolume sets VsDevice's playback (output) volume to the provided float
+void VsDevice::updatePlaybackVolume(float multiplier)
+{
+    if (multiplier == m_playbackVolume) {
+        return;
+    }
+    m_playbackVolume = multiplier;
+
+    if (m_sendVolumeTimer) {
+        m_sendVolumeTimer->start(200);
+    }
+}
+
+// updatePlaybackMute sets VsDevice's playback (output) mute to the provided boolean
+void VsDevice::updatePlaybackMute(bool muted)
+{
+    if (muted == m_playbackMute) {
+        return;
+    }
+    m_playbackMute = muted;
+
+    if (m_sendVolumeTimer) {
+        m_sendVolumeTimer->start(200);
+    }
+}
+
 // terminateJackTrip is a slot intended to be triggered on jacktrip process signals
 void VsDevice::terminateJackTrip()
 {
@@ -493,17 +522,32 @@ void VsDevice::onTextMessageReceived(const QString& message)
         m_pinger->start();
     }
 
-    bool newMute           = newState["captureMute"].toBool();
-    float newCaptureVolume = (float)(newState["captureVolume"].toDouble() / 100.0);
+    // capture (input) volume
+    bool newMute    = newState["captureMute"].toBool();
+    float newVolume = (float)(newState["captureVolume"].toDouble() / 100.0);
 
-    if (newCaptureVolume != m_captureVolume) {
-        m_captureVolume = newCaptureVolume;
-        emit updatedVolumeFromServer(m_captureVolume);
+    if (newVolume != m_captureVolume) {
+        m_captureVolume = newVolume;
+        emit updatedCaptureVolumeFromServer(m_captureVolume);
     }
 
     if (newMute != m_captureMute) {
         m_captureMute = newMute;
-        emit updatedMuteFromServer(m_captureMute);
+        emit updatedCaptureMuteFromServer(m_captureMute);
+    }
+
+    // playback (output) volume
+    newMute   = newState["playbackMute"].toBool();
+    newVolume = (float)(newState["playbackVolume"].toDouble() / 100.0);
+
+    if (newVolume != m_playbackVolume) {
+        m_playbackVolume = newVolume;
+        emit updatedPlaybackVolumeFromServer(m_playbackVolume);
+    }
+
+    if (newMute != m_playbackMute) {
+        m_playbackMute = newMute;
+        emit updatedPlaybackMuteFromServer(m_playbackMute);
     }
 
     reconcileAgentConfig(newState);
@@ -575,8 +619,6 @@ void VsDevice::registerJTAsDevice()
     connect(reply, &QNetworkReply::finished, this, [=]() {
         if (reply->error() != QNetworkReply::NoError) {
             std::cout << "Error: " << reply->errorString().toStdString() << std::endl;
-            // TODO: Fix me
-            // emit authFailed();
             reply->deleteLater();
             return;
         } else {
index 760756bee27b05557020b26116097dd5c4e287ff..373c7535eae95e84ff7e7e6a4d52111f36d9c41d 100644 (file)
@@ -77,12 +77,16 @@ class VsDevice : public QObject
 
    signals:
     void updateNetworkStats(QJsonObject stats);
-    void updatedVolumeFromServer(float multiplier);
-    void updatedMuteFromServer(bool muted);
+    void updatedCaptureVolumeFromServer(float multiplier);
+    void updatedCaptureMuteFromServer(bool muted);
+    void updatedPlaybackVolumeFromServer(float multiplier);
+    void updatedPlaybackMuteFromServer(bool muted);
 
    public slots:
-    void updateVolume(float multiplier);
-    void updateMute(bool muted);
+    void updateCaptureVolume(float multiplier);
+    void updateCaptureMute(bool muted);
+    void updatePlaybackVolume(float multiplier);
+    void updatePlaybackMute(bool muted);
 
    private slots:
     void terminateJackTrip();
@@ -107,8 +111,10 @@ class VsDevice : public QObject
     QScopedPointer<JackTrip> m_jackTrip;
     QOAuth2AuthorizationCodeFlow* m_authenticator;
     QRandomGenerator m_randomizer;
-    float m_captureVolume = 1.0;
-    bool m_captureMute    = false;
+    float m_captureVolume  = 1.0;
+    bool m_captureMute     = false;
+    float m_playbackVolume = 1.0;
+    bool m_playbackMute    = false;
     QTimer* m_sendVolumeTimer;
 };
 
index 7a2352a381a4150d16f0242af52a1debc95d775b..5657a44ea32956682d4db7e316b6b6d6da6a3656 100644 (file)
@@ -44,7 +44,7 @@ VsServerInfo::serverSectionT VsServerInfo::section()
     return m_section;
 }
 
-QString VsServerInfo::type()
+QString VsServerInfo::type() const
 {
     if (m_section == YOUR_STUDIOS) {
         return QStringLiteral("Your Studios");
@@ -60,7 +60,7 @@ void VsServerInfo::setSection(serverSectionT section)
     m_section = section;
 }
 
-QString VsServerInfo::name()
+QString VsServerInfo::name() const
 {
     return m_name;
 }
@@ -70,28 +70,24 @@ void VsServerInfo::setName(const QString& name)
     m_name = name;
 }
 
-QString VsServerInfo::host()
+QString VsServerInfo::host() const
 {
     return m_host;
 }
 
-QString VsServerInfo::status()
+QString VsServerInfo::status() const
 {
     return m_status;
 }
 
-bool VsServerInfo::canConnect()
+bool VsServerInfo::canConnect() const
 {
     return !m_host.isEmpty() && m_status == "Ready";
 }
 
-bool VsServerInfo::canStart()
+bool VsServerInfo::canStart() const
 {
-#ifdef PSI
-    return true;
-#else
-    return false;
-#endif
+    return m_owner || m_admin;
 }
 
 void VsServerInfo::setHost(const QString& host)
@@ -106,7 +102,7 @@ void VsServerInfo::setStatus(const QString& status)
     emit canConnectChanged();
 }
 
-quint16 VsServerInfo::port()
+quint16 VsServerInfo::port() const
 {
     return m_port;
 }
@@ -116,7 +112,37 @@ void VsServerInfo::setPort(quint16 port)
     m_port = port;
 }
 
-bool VsServerInfo::isPublic()
+bool VsServerInfo::enabled() const
+{
+    return m_enabled;
+}
+
+void VsServerInfo::setEnabled(bool enabled)
+{
+    m_enabled = enabled;
+}
+
+bool VsServerInfo::isOwner() const
+{
+    return m_owner;
+}
+
+void VsServerInfo::setIsOwner(bool owner)
+{
+    m_owner = owner;
+}
+
+bool VsServerInfo::isAdmin() const
+{
+    return m_admin;
+}
+
+void VsServerInfo::setIsAdmin(bool admin)
+{
+    m_admin = admin;
+}
+
+bool VsServerInfo::isPublic() const
 {
     return m_isPublic;
 }
@@ -126,12 +152,12 @@ void VsServerInfo::setIsPublic(bool isPublic)
     m_isPublic = isPublic;
 }
 
-QString VsServerInfo::region()
+QString VsServerInfo::region() const
 {
     return m_region;
 }
 
-QString VsServerInfo::flag()
+QString VsServerInfo::flag() const
 {
     QStringList parts = m_region.split(QStringLiteral("-"));
     if (parts.count() > 1) {
@@ -145,7 +171,7 @@ QString VsServerInfo::flag()
     return QStringLiteral("flags/US.svg");
 }
 
-QString VsServerInfo::location()
+QString VsServerInfo::location() const
 {
     return m_region;
 }
@@ -155,7 +181,7 @@ void VsServerInfo::setRegion(const QString& region)
     m_region = region;
 }
 
-bool VsServerInfo::isManageable()
+bool VsServerInfo::isManageable() const
 {
     return m_isManageable;
 }
@@ -165,7 +191,7 @@ void VsServerInfo::setIsManageable(bool isManageable)
     m_isManageable = isManageable;
 }
 
-quint16 VsServerInfo::period()
+quint16 VsServerInfo::period() const
 {
     return m_period;
 }
@@ -175,7 +201,7 @@ void VsServerInfo::setPeriod(quint16 period)
     m_period = period;
 }
 
-quint32 VsServerInfo::sampleRate()
+quint32 VsServerInfo::sampleRate() const
 {
     return m_sampleRate;
 }
@@ -185,7 +211,7 @@ void VsServerInfo::setSampleRate(quint32 sampleRate)
     m_sampleRate = sampleRate;
 }
 
-quint16 VsServerInfo::queueBuffer()
+quint16 VsServerInfo::queueBuffer() const
 {
     return m_queueBuffer;
 }
@@ -195,7 +221,7 @@ void VsServerInfo::setQueueBuffer(quint16 queueBuffer)
     m_queueBuffer = queueBuffer;
 }
 
-QString VsServerInfo::bannerURL()
+QString VsServerInfo::bannerURL() const
 {
     return m_bannerURL;
 }
@@ -205,7 +231,7 @@ void VsServerInfo::setBannerURL(const QString& bannerURL)
     m_bannerURL = bannerURL;
 }
 
-QString VsServerInfo::id()
+QString VsServerInfo::id() const
 {
     return m_id;
 }
@@ -215,7 +241,7 @@ void VsServerInfo::setId(const QString& id)
     m_id = id;
 }
 
-QString VsServerInfo::sessionId()
+QString VsServerInfo::sessionId() const
 {
     return m_sessionId;
 }
@@ -225,7 +251,7 @@ void VsServerInfo::setSessionId(const QString& sessionId)
     m_sessionId = sessionId;
 }
 
-QString VsServerInfo::inviteKey()
+QString VsServerInfo::inviteKey() const
 {
     return m_inviteKey;
 }
@@ -235,4 +261,26 @@ void VsServerInfo::setInviteKey(const QString& inviteKey)
     m_inviteKey = inviteKey;
 }
 
+QString VsServerInfo::cloudId() const
+{
+    return m_cloudId;
+}
+
+void VsServerInfo::setCloudId(const QString& cloudId)
+{
+    m_cloudId = cloudId;
+}
+
+bool VsServerInfo::operator<(const VsServerInfo& other) const
+{
+    if (status() == QStringLiteral("Ready")) {
+        if (other.status() != QStringLiteral("Ready")) {
+            return true;
+        }
+    } else if (other.status() == QStringLiteral("Ready")) {
+        return false;
+    }
+    return name() < other.name();
+}
+
 VsServerInfo::~VsServerInfo() = default;
index 9039ca731d0b3bd9032e0e2950226d289faafeac..956d6117a3877681819948d7af1414bb5b9c4eec 100644 (file)
@@ -59,6 +59,8 @@ class VsServerInfo : public QObject
     Q_PROPERTY(quint32 sampleRate READ sampleRate CONSTANT)
     Q_PROPERTY(quint16 queueBuffer READ queueBuffer CONSTANT)
     Q_PROPERTY(QString status READ status CONSTANT)
+    Q_PROPERTY(bool enabled READ enabled CONSTANT)
+    Q_PROPERTY(QString cloudId READ cloudId CONSTANT)
     Q_PROPERTY(QString id READ id CONSTANT)
     Q_PROPERTY(QString inviteKey READ inviteKey CONSTANT)
 
@@ -69,40 +71,49 @@ class VsServerInfo : public QObject
     ~VsServerInfo() override;
 
     serverSectionT section();
-    QString type();
+    QString type() const;
     void setSection(serverSectionT section);
-    QString name();
+    QString name() const;
     void setName(const QString& name);
-    QString host();
-    bool canConnect();
-    bool canStart();
+    QString host() const;
+    bool canConnect() const;
+    bool canStart() const;
     void setHost(const QString& host);
-    quint16 port();
+    quint16 port() const;
     void setPort(quint16 port);
-    bool isPublic();
+    bool enabled() const;
+    void setEnabled(bool enabled);
+    bool isOwner() const;
+    void setIsOwner(bool owner);
+    bool isAdmin() const;
+    void setIsAdmin(bool admin);
+    bool isPublic() const;
     void setIsPublic(bool isPublic);
-    QString region();
-    QString flag();
-    QString location();
+    QString region() const;
+    QString flag() const;
+    QString location() const;
     void setRegion(const QString& region);
-    bool isManageable();
+    bool isManageable() const;
     void setIsManageable(bool isManageable);
-    quint16 period();
+    quint16 period() const;
     void setPeriod(quint16 period);
-    quint32 sampleRate();
+    quint32 sampleRate() const;
     void setSampleRate(quint32 sampleRate);
-    quint16 queueBuffer();
+    quint16 queueBuffer() const;
     void setQueueBuffer(quint16 queueBuffer);
-    QString bannerURL();
+    QString bannerURL() const;
     void setBannerURL(const QString& bannerURL);
-    QString id();
+    QString id() const;
     void setId(const QString& id);
-    QString sessionId();
+    QString sessionId() const;
     void setSessionId(const QString& sessionId);
-    QString status();
+    QString status() const;
     void setStatus(const QString& status);
-    QString inviteKey();
+    QString inviteKey() const;
     void setInviteKey(const QString& inviteKey);
+    QString cloudId() const;
+    void setCloudId(const QString& cloudId);
+    bool operator<(const VsServerInfo& other) const;
 
    signals:
     void canConnectChanged();
@@ -112,6 +123,9 @@ class VsServerInfo : public QObject
     QString m_name;
     QString m_host;
     quint16 m_port;
+    bool m_enabled;
+    bool m_owner;
+    bool m_admin;
     bool m_isPublic;
     QString m_region;
     bool m_isManageable;
@@ -122,6 +136,7 @@ class VsServerInfo : public QObject
     QString m_id;
     QString m_sessionId;
     QString m_status;
+    QString m_cloudId;
     QString m_inviteKey;
 
     /* Remaining JSON fields
@@ -132,17 +147,11 @@ class VsServerInfo : public QObject
     "size": "c5.large",
     "mixBranch": "main",
     "mixCode": "SimpleMix(~maxClients).masterVolume_(1).connect.start;",
-    "enabled": true,
-    "admin": true,
-    "cloudId": "string",
-    "owner": true,
     "ownerId": "string",
-    "status": "Ready",
     "subStatus": "Active",
     "createdAt": "2021-09-07T17:15:38Z",
     "expiresAt": "2021-09-07T17:15:38Z",
     "updatedAt": "2021-09-07T17:15:38Z"
-    "inviteKey": "invitestring",
     */
 };
 
index 034f34e330147db0f4b4b6ee7b503447874ddbb8..740b5ea1cdfb044641e80204ed280eb5a0526d12 100644 (file)
@@ -40,7 +40,7 @@
 
 #include "AudioInterface.h"
 
-constexpr const char* const gVersion = "1.6.8";  ///< JackTrip version
+constexpr const char* const gVersion = "1.7.0";  ///< JackTrip version
 
 //*******************************************************************************
 /// \name Default Values