cmake_minimum_required(VERSION 3.12)
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.14)
+set(CMAKE_CXX_STANDARD 17)
project(QJackTrip)
set(nogui FALSE)
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
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 ()
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 ()
# 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
+- 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:
# Created by Juan-Pablo Caceres
#******************************
-CONFIG += c++11 console
+CONFIG += c++17 console
CONFIG -= app_bundle
CONFIG += qt thread debug_and_release build_all
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"
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
{
"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
+}
{
"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",
{
"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",
{
"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",
{
"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",
#include <QDateTime>
#include <QHostAddress>
#include <QHostInfo>
+#include <QRandomGenerator>
#include <QThread>
#include <QTimer>
#include <QtEndian>
, mUseAuth(false)
, mRedundancy(redundancy)
, mTimeoutTimer(this)
+ , mRetryTimer(this)
+ , mRetries(0)
, mSleepTime(100)
, mElapsedTime(0)
, mEndTime(0)
return;
}
mAwaitingTcp = false;
- mTimeoutTimer.stop();
+ mRetryTimer.stop();
}
if (gVerboseFlag)
cout << "TCP Socket Connected to Server!" << endl;
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.
// 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();
}
//*******************************************************************************
// ----------------------------------------------
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)
private slots:
void receivedConnectionTCP();
void receivedDataTCP();
+ void receivedErrorTCP(QAbstractSocket::SocketError socketError);
void connectionSecured();
void receivedDataUDP();
void udpTimerTick();
mProcessPluginsToNetwork; ///< Vector of ProcessPlugin<EM>s</EM>
QTimer mTimeoutTimer;
+ QTimer mRetryTimer;
+ int mRetries;
int mSleepTime;
int mElapsedTime;
int mEndTime;
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) {
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++) {
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();
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) {
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++) {
#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;
OPT_NUMSEND,
OPT_APPENDTHREADID,
OPT_LISTDEVICES,
- OPT_AUDIODEVICE
+ OPT_AUDIODEVICE,
+ OPT_AUDIOINPUTDEVICE,
+ OPT_AUDIOOUTPUTDEVICE
};
//*******************************************************************************
'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
//-------------------------------------------------------
setDevicesByString(optarg);
break;
+ case OPT_AUDIOINPUTDEVICE:
+ mInputDeviceName = optarg;
+ break;
+ case OPT_AUDIOOUTPUTDEVICE:
+ mOutputDeviceName = optarg;
+ break;
case OPT_LISTDEVICES: // List audio devices
//-------------------------------------------------------
RtAudioInterface::printDevices();
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;
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;
<< 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;
* 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.
--- /dev/null
+//*****************************************************************
+/*
+ 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
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";
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
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"
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)
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
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
Item {
id: inputControls
+ visible: showReadyScreen
x: inputDevice.x + inputDevice.width; y: 230 * virtualstudio.uiScale
width: parent.width - inputDevice.width - 2 * bodyMargin * virtualstudio.uiScale
Item {
id: outputControls
+ visible: showReadyScreen
x: outputDevice.x + outputDevice.width; y: 320 * virtualstudio.uiScale
width: parent.width - inputDevice.width - 2 * bodyMargin * virtualstudio.uiScale
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
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
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
+ }
+ }
}
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
}
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 : ""
}
}
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
}
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
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 : ""
}
}
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 {
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
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
}
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 : ""
}
}
text: virtualstudio.audioBackend != "JACK" ? "Output Device" : "Output Volume"
font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
color: textColour
+ visible: virtualstudio.backendAvailable
}
Slider {
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
color: outputSlider.pressed ? sliderPressedColour : sliderColour
border.color: buttonStroke
}
+ visible: virtualstudio.backendAvailable
}
Button {
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 }
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 : ""
}
}
text: virtualstudio.audioBackend != "JACK" ? "Input Device" : "Input Volume"
font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
color: textColour
+ visible: virtualstudio.backendAvailable
}
Meter {
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 {
color: inputSlider.pressed ? sliderPressedColour : sliderColour
border.color: buttonStroke
}
+ visible: virtualstudio.backendAvailable
}
Button {
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 }
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
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
}
CheckBox {
id: showAgainCheckbox
checked: virtualstudio.showDeviceSetup
+ visible: virtualstudio.backendAvailable
text: qsTr("Ask again next time")
anchors.right: saveButton.left
anchors.rightMargin: 16 * virtualstudio.uiScale
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
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
}
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
}
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
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
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
<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>
<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>
<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>
--- /dev/null
+<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
--- /dev/null
+<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
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());
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();
// 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);
}
m_inputDevice = filteredInputDeviceList.at(device);
+ emit inputDeviceChanged(m_inputDevice, false);
emit inputDeviceSelected(m_inputDevice);
#endif
}
}
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;
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;
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;
emit windowStateUpdated();
}
+QString VirtualStudio::apiHost()
+{
+ return m_apiHost;
+}
+
+void VirtualStudio::setApiHost(QString host)
+{
+ m_apiHost = host;
+ emit apiHostChanged();
+}
+
bool VirtualStudio::showWarnings()
{
return m_showWarnings;
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();
}
}
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();
(*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);
m_outputDevice = QStringLiteral("(default)");
}
- emit inputDeviceChanged(m_inputDevice);
- emit outputDeviceChanged(m_outputDevice);
+ emit inputDeviceChanged(m_inputDevice, false);
+ emit outputDeviceChanged(m_outputDevice, false);
#endif
}
{
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
}
{
m_previousUiScale = m_uiScale;
emit newScale();
+
+ setAudioActivated(false);
+
QSettings settings;
settings.beginGroup(QStringLiteral("VirtualStudio"));
settings.setValue(QStringLiteral("UiScale"), m_uiScale);
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
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();
}
}
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 = "";
&VirtualStudio::receivedConnectionFromPeer,
Qt::QueuedConnection);
- // Stop VsAudioInterface
- if (!m_vsAudioInterface.isNull()) {
- m_vsAudioInterface->closeAudio();
- }
+ setAudioActivated(false);
// Setup output volume
m_outputVolumePlugin = new Volume(jackTrip->getNumOutputChannels());
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;
}
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);
}
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();
// 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()
void VirtualStudio::processFinished()
{
+ // use disconnect function to handle reset of all internal flags and timers
+ disconnect();
+
// reset network statistics
m_networkStats = QJsonObject();
return;
}
- if (m_retryPeriod && m_startedStudio) {
- // Retry if necessary.
- completeConnection();
- return;
- }
-
if (!m_jackTripRunning) {
return;
}
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
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()
} else if (stage == QAbstractOAuth2::Stage::RequestingAuthorization) {
parameters->insert(QStringLiteral("audience"),
QStringLiteral("https://api.jacktrip.org"));
+ parameters->insert(QStringLiteral("prompt"), QStringLiteral("login"));
}
});
{
QMutexLocker locker(&m_refreshMutex);
if (!m_allowRefresh || m_refreshInProgress) {
+ if (signalRefresh) {
+ emit refreshFinished(index);
+ }
return;
} else {
m_refreshInProgress = true;
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();
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;
}
continue;
}
- if (activeStudio || (serverInfo->isManageable() && m_showInactive)) {
+ if (activeStudio || m_showInactive) {
serverInfo->setName(
servers.at(i)[QStringLiteral("name")].toString());
serverInfo->setHost(
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);
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
// request going out and the response.
if (!m_allowRefresh) {
m_refreshInProgress = false;
+ if (signalRefresh) {
+ emit refreshFinished(index);
+ }
return;
}
m_servers.clear();
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,
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();
}
+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) {
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"));
}
delete m_inputMeter;
delete m_outputMeter;
delete m_inputTestMeter;
+ delete m_studioSocket;
QDesktopServices::unsetUrlHandler("jacktrip");
}
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)
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);
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();
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();
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();
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();
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();
void updatedOutputVolume(float multiplier);
void updatedInputMuted(bool muted);
void updatedOutputMuted(bool muted);
+ void audioActivatedChanged();
+ void audioReadyChanged();
void windowStateUpdated();
+ void apiHostChanged();
private slots:
void slotAuthSucceded();
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();
void getRegions();
void getUserMetadata();
void stopStudio();
+ void toggleAudio();
+ void stopAudio();
bool readyToJoin();
#ifdef RT_AUDIO
QVariant formatDeviceList(const QStringList& devices, const QStringList& categories);
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;
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;
, 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 =
// 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;
}
}
+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()) {
m_audioInterface->appendProcessPluginFromNetwork(plugin);
}
-void VsAudioInterface::setInputDevice(QString deviceName)
+void VsAudioInterface::setInputDevice(QString deviceName, bool shouldRestart)
{
m_inputDeviceName = deviceName.toStdString();
if (m_inputDeviceName == "(default)") {
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)") {
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();
}
}
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()));
}
#include "../Tone.h"
#include "../Volume.h"
#include "../jacktrip_globals.h"
+#include "AudioInterfaceMode.h"
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);
void processMeterMeasurements(QVector<float> values);
private:
+ void setupJackAudio();
+ void setupRtAudio();
+
float m_inMultiplier = 1.0;
float m_outMultiplier = 1.0;
bool m_inMuted = false;
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);
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);
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();
reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
if (!statusCode.isValid()) {
std::cout << "Error: " << reply->errorString().toStdString() << std::endl;
- // TODO: Fix me
- // emit authFailed();
reply->deleteLater();
return;
}
} else {
// Other error status. Won't create device.
std::cout << "Error: " << reply->errorString().toStdString() << std::endl;
- // TODO: Fix me
- // emit authFailed();
reply->deleteLater();
return;
}
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 {
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 {
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;
}
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(
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;
}
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
}
}
-// 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;
}
}
-// 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;
}
}
+// 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()
{
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);
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 {
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();
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;
};
return m_section;
}
-QString VsServerInfo::type()
+QString VsServerInfo::type() const
{
if (m_section == YOUR_STUDIOS) {
return QStringLiteral("Your Studios");
m_section = section;
}
-QString VsServerInfo::name()
+QString VsServerInfo::name() const
{
return m_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)
emit canConnectChanged();
}
-quint16 VsServerInfo::port()
+quint16 VsServerInfo::port() const
{
return m_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;
}
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) {
return QStringLiteral("flags/US.svg");
}
-QString VsServerInfo::location()
+QString VsServerInfo::location() const
{
return m_region;
}
m_region = region;
}
-bool VsServerInfo::isManageable()
+bool VsServerInfo::isManageable() const
{
return m_isManageable;
}
m_isManageable = isManageable;
}
-quint16 VsServerInfo::period()
+quint16 VsServerInfo::period() const
{
return m_period;
}
m_period = period;
}
-quint32 VsServerInfo::sampleRate()
+quint32 VsServerInfo::sampleRate() const
{
return m_sampleRate;
}
m_sampleRate = sampleRate;
}
-quint16 VsServerInfo::queueBuffer()
+quint16 VsServerInfo::queueBuffer() const
{
return m_queueBuffer;
}
m_queueBuffer = queueBuffer;
}
-QString VsServerInfo::bannerURL()
+QString VsServerInfo::bannerURL() const
{
return m_bannerURL;
}
m_bannerURL = bannerURL;
}
-QString VsServerInfo::id()
+QString VsServerInfo::id() const
{
return m_id;
}
m_id = id;
}
-QString VsServerInfo::sessionId()
+QString VsServerInfo::sessionId() const
{
return m_sessionId;
}
m_sessionId = sessionId;
}
-QString VsServerInfo::inviteKey()
+QString VsServerInfo::inviteKey() const
{
return m_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;
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)
~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();
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;
QString m_id;
QString m_sessionId;
QString m_status;
+ QString m_cloudId;
QString m_inviteKey;
/* Remaining JSON fields
"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",
*/
};
#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