diff --git a/src/blackcore/afv/clients/afvclient.cpp b/src/blackcore/afv/clients/afvclient.cpp index 86c99ec77..aedbd71f2 100644 --- a/src/blackcore/afv/clients/afvclient.cpp +++ b/src/blackcore/afv/clients/afvclient.cpp @@ -186,13 +186,13 @@ namespace BlackCore } } - void CAfvClient::disconnectFrom() + void CAfvClient::disconnectFrom(bool stop) { if (QThread::currentThread() != thread()) { // Method needs to be executed in the object thread since it will create new QObject children QPointer myself(this); - QMetaObject::invokeMethod(this, [ = ]() { if (myself) disconnectFrom(); }); + QMetaObject::invokeMethod(this, [ = ]() { if (myself) disconnectFrom(stop); }); return; } @@ -203,6 +203,8 @@ namespace BlackCore m_connection->disconnectFrom(); } emit connectionStatusChanged(Disconnected); + + if (stop) { this->stopAudio(); } } QStringList CAfvClient::availableInputDevices() const diff --git a/src/blackcore/afv/clients/afvclient.h b/src/blackcore/afv/clients/afvclient.h index 65f7a7919..2ec954443 100644 --- a/src/blackcore/afv/clients/afvclient.h +++ b/src/blackcore/afv/clients/afvclient.h @@ -94,7 +94,11 @@ namespace BlackCore //! Disconnect from network //! \threadsafe //! \remark runs in thread of CAfvClient object and is ASYNC when called from another thread - Q_INVOKABLE void disconnectFrom(); + //! @{ + void disconnectFrom(bool stop); + Q_INVOKABLE void disconnectFrom() { this->disconnectFrom(false); } + void disconnectFromAndStop() { this->disconnectFrom(true); } + //! @} //! Audio devices @{ Q_INVOKABLE QStringList availableInputDevices() const; diff --git a/src/blackcore/afv/connection/clientconnection.cpp b/src/blackcore/afv/connection/clientconnection.cpp index cfc9fb9d3..d63a1f363 100644 --- a/src/blackcore/afv/connection/clientconnection.cpp +++ b/src/blackcore/afv/connection/clientconnection.cpp @@ -30,9 +30,9 @@ namespace BlackCore { CLogMessage(this).debug(u"ClientConnection instantiated"); - // connect(&m_apiServerConnection, &ApiServerConnection::authenticationFinished, this, &ClientConnection::apiConnectionFinished); - // connect(&m_apiServerConnection, &ApiServerConnection::addCallsignFinished, this, &ClientConnection::addCallsignFinished); - // connect(&m_apiServerConnection, &ApiServerConnection::removeCallsignFinished, this, &ClientConnection::removeCallsignFinished); + // connect(&m_apiServerConnection, &ApiServerConnection::authenticationFinished, this, &ClientConnection::apiConnectionFinished); + // connect(&m_apiServerConnection, &ApiServerConnection::addCallsignFinished, this, &ClientConnection::addCallsignFinished); + // connect(&m_apiServerConnection, &ApiServerConnection::removeCallsignFinished, this, &ClientConnection::removeCallsignFinished); connect(m_voiceServerTimer, &QTimer::timeout, this, &CClientConnection::voiceServerHeartbeat); connect(m_udpSocket, &QUdpSocket::readyRead, this, &CClientConnection::readPendingDatagrams); @@ -69,7 +69,7 @@ namespace BlackCore this->connectToVoiceServer(); // taskServerConnectionCheck.Start(); - CLogMessage(this).debug(u"Connected: '%1'") << callsign; + CLogMessage(this).info(u"Connected: '%1' to voice server, socket open: ") << callsign << boolToYesNo(m_udpSocket->isOpen()); } // callback of the calling parent @@ -121,11 +121,11 @@ namespace BlackCore void CClientConnection::connectToVoiceServer() { - QHostAddress localAddress(QHostAddress::AnyIPv4); + const QHostAddress localAddress(QHostAddress::AnyIPv4); m_udpSocket->bind(localAddress); m_voiceServerTimer->start(3000); - CLogMessage(this).info(u"Connected to voice server '%1'") << m_connection.getTokens().VoiceServer.addressIpV4; + CLogMessage(this).info(u"Connected to voice server '%2'") << m_connection.getTokens().VoiceServer.addressIpV4; } void CClientConnection::disconnectFromVoiceServer() diff --git a/src/blackcore/context/contextaudio.cpp b/src/blackcore/context/contextaudio.cpp index c5754a257..783547b2f 100644 --- a/src/blackcore/context/contextaudio.cpp +++ b/src/blackcore/context/contextaudio.cpp @@ -111,6 +111,32 @@ namespace BlackCore CIdentifiable(this), m_voiceClient(new CAfvClient(CVoiceSetup().getAfvVoiceServerUrl(), this)) { + this->initVoiceClient(); + const CSettings as = m_audioSettings.getThreadLocal(); + this->setVoiceOutputVolume(as.getOutVolume()); + m_selcalPlayer = new CSelcalPlayer(CAudioDeviceInfo::getDefaultOutputDevice(), this); + + this->changeDeviceSettings(); + QPointer myself(this); + QTimer::singleShot(5000, this, [ = ] + { + if (!myself || !sApp || sApp->isShuttingDown()) { return; } + myself->onChangedAudioSettings(); + }); + } + + CContextAudioBase::~CContextAudioBase() + { + this->gracefulShutdown(); + } + + void CContextAudioBase::initVoiceClient() + { + if (!m_voiceClient) + { + m_voiceClient = new CAfvClient(CVoiceSetup().getAfvVoiceServerUrl(), this); + } + const CVoiceSetup vs = m_voiceSettings.getThreadLocal(); m_voiceClient->updateVoiceServerUrl(vs.getAfvVoiceServerUrl()); @@ -144,33 +170,16 @@ namespace BlackCore connect(m_voiceClient, &CAfvClient::startedAudio, this, &CContextAudioBase::startedAudio, Qt::QueuedConnection); connect(m_voiceClient, &CAfvClient::stoppedAudio, this, &CContextAudioBase::stoppedAudio, Qt::QueuedConnection); connect(m_voiceClient, &CAfvClient::ptt, this, &CContextAudioBase::ptt, Qt::QueuedConnection); - - const CSettings as = m_audioSettings.getThreadLocal(); - this->setVoiceOutputVolume(as.getOutVolume()); - m_selcalPlayer = new CSelcalPlayer(CAudioDeviceInfo::getDefaultOutputDevice(), this); - - this->changeDeviceSettings(); - QPointer myself(this); - QTimer::singleShot(5000, this, [ = ] - { - if (!myself || !sApp || sApp->isShuttingDown()) { return; } - myself->onChangedAudioSettings(); - }); } - CContextAudioBase::~CContextAudioBase() - { - gracefulShutdown(); - } - - void CContextAudioBase::gracefulShutdown() + void CContextAudioBase::terminateVoiceClient() { if (m_voiceClient) { m_voiceClient->gracefulShutdown(); Q_ASSERT_X(CThreadUtils::isCurrentThreadObjectThread(m_voiceClient), Q_FUNC_INFO, "Needs to be back in current thread"); + m_voiceClient->deleteLater(); m_voiceClient = nullptr; - #ifdef Q_OS_WIN if (m_winCoInitialized) { @@ -179,9 +188,23 @@ namespace BlackCore } #endif } + } + + void CContextAudioBase::gracefulShutdown() + { + this->terminateVoiceClient(); QObject::disconnect(this); } + void CContextAudioBase::enableVoiceClientAndStart() + { + this->initVoiceClient(); + if (m_voiceClient) { + m_voiceClient->startAudio(); + this->connectAudioWithNetworkCredentials(); + } + } + const CIdentifier &CContextAudioBase::audioRunsWhere() const { static const CIdentifier i("CContextAudioBaseImpl"); @@ -200,6 +223,28 @@ namespace BlackCore return m_voiceClient->isTransmittingdComUnit(comUnit); } + bool CContextAudioBase::connectAudioWithNetworkCredentials() + { + if (!m_voiceClient) { return false; } + if (!sApp || sApp->isShuttingDown() || !sApp->getIContextNetwork()) { return false; } + + const CEcosystem ecoSystem = this->getIContextNetwork()->getConnectedServer().getEcosystem(); + if (ecoSystem != CEcosystem::vatsim()) + { + CLogMessage(this).info(u"Will not use AFV as ecosystem is '%1'") << ecoSystem.toQString(true); + return false; + } + + const CVoiceSetup vs = m_voiceSettings.getThreadLocal(); + m_voiceClient->updateVoiceServerUrl(vs.getAfvVoiceServerUrl()); + + const CUser connectedUser = this->getIContextNetwork()->getConnectedServer().getUser(); + const QString client = "swift " % BlackConfig::CBuildConfig::getShortVersionString(); + m_voiceClient->connectTo(connectedUser.getId(), connectedUser.getPassword(), connectedUser.getCallsign().asString(), client); + + return true; + } + bool CContextAudioBase::isAudioConnected() const { return m_voiceClient && m_voiceClient->isConnected(); @@ -212,7 +257,7 @@ namespace BlackCore QString CContextAudioBase::audioRunsWhereInfo() const { - static const QString s = QStringLiteral("Audio on '%1', '%2'.").arg(audioRunsWhere().getMachineName(), audioRunsWhere().getProcessName()); + const QString s = QStringLiteral("[%1] Audio on '%2', '%3'.").arg(boolToEnabledDisabled(this->isAudioStarted()), audioRunsWhere().getMachineName(), audioRunsWhere().getProcessName()); return s; } @@ -439,19 +484,7 @@ namespace BlackCore // we only change network connection of AFC client here if (to.isConnected() && this->getIContextNetwork()) { - const CEcosystem ecoSystem = this->getIContextNetwork()->getConnectedServer().getEcosystem(); - if (ecoSystem != CEcosystem::vatsim()) - { - CLogMessage(this).info(u"Will not use AFV as ecosystem is '%1'") << ecoSystem.toQString(true); - return; - } - - const CVoiceSetup vs = m_voiceSettings.getThreadLocal(); - m_voiceClient->updateVoiceServerUrl(vs.getAfvVoiceServerUrl()); - - const CUser connectedUser = this->getIContextNetwork()->getConnectedServer().getUser(); - const QString client = "swift " % BlackConfig::CBuildConfig::getShortVersionString(); - m_voiceClient->connectTo(connectedUser.getId(), connectedUser.getPassword(), connectedUser.getCallsign().asString(), client); + this->connectAudioWithNetworkCredentials(); } else if (to.isDisconnected()) { diff --git a/src/blackcore/context/contextaudio.h b/src/blackcore/context/contextaudio.h index db07bbf30..7be61fdd4 100644 --- a/src/blackcore/context/contextaudio.h +++ b/src/blackcore/context/contextaudio.h @@ -41,7 +41,6 @@ // clazy:excludeall=const-signal-or-slot - //! \addtogroup dbus //! @{ @@ -123,6 +122,12 @@ namespace BlackCore //! Graceful shutdown void gracefulShutdown(); + //! Enable/disable @{ + void enableVoiceClient() { this->initVoiceClient(); } + void enableVoiceClientAndStart(); + void disableVoiceClient() { this->terminateVoiceClient(); } + //! @} + // -------- parts which can run in core and GUI, referring to local voice client ------------ //! Audio devices @@ -177,12 +182,19 @@ namespace BlackCore //! Is COM unit transmitting? bool isTransmittingComUnit(BlackMisc::Aviation::CComSystem::ComUnit comUnit) const; + //! Connect to audio with network credentials + //! \remark if there is no network connection/credential this just returns + bool connectAudioWithNetworkCredentials(); + //! Is audio connected? bool isAudioConnected() const; //! Is audio started? bool isAudioStarted() const; + //! Is audio enabled? + bool isAudioEnabled() const { return m_voiceClient; } + //! \todo WORKAROUND to hide the "local signals" Afv::Clients::CAfvClient *afvClient() const { return m_voiceClient; } @@ -237,11 +249,17 @@ namespace BlackCore //! Client updated from own aicraft data void updatedFromOwnAircraftCockpit(); - **/ + Workaround **/ // ------------ local signals ------- private: + //! Init the voice client + void initVoiceClient(); + + //! Terminate the voice client + void terminateVoiceClient(); + //! Enable/disable voice transmission, nornally used with hotkey //! @{ void setVoiceTransmission(bool enable, BlackMisc::Audio::PTTCOM com); diff --git a/src/blackgui/components/audioadvanceddistributedcomponent.cpp b/src/blackgui/components/audioadvanceddistributedcomponent.cpp index 6c06a580f..81c8582f8 100644 --- a/src/blackgui/components/audioadvanceddistributedcomponent.cpp +++ b/src/blackgui/components/audioadvanceddistributedcomponent.cpp @@ -11,6 +11,7 @@ #include "blackgui/guiapplication.h" #include "blackcore/context/contextaudio.h" +#include "blackcore/context/contextnetwork.h" #include "blackcore/afv/clients/afvclient.h" using namespace BlackMisc::Audio; @@ -28,9 +29,10 @@ namespace BlackGui connect(sGui->getCContextAudioBase(), &CContextAudioBase::startedAudio, this, &CAudioAdvancedDistributedComponent::onAudioStarted, Qt::QueuedConnection); connect(sGui->getCContextAudioBase(), &CContextAudioBase::stoppedAudio, this, &CAudioAdvancedDistributedComponent::onAudioStoppend, Qt::QueuedConnection); - connect(ui->pb_StartStop, &QPushButton::released, this, &CAudioAdvancedDistributedComponent::toggleAudioStartStop, Qt::QueuedConnection); + connect(ui->pb_EnableDisable, &QPushButton::pressed, this, &CAudioAdvancedDistributedComponent::toggleAudioEnableDisable, Qt::QueuedConnection); + connect(ui->pb_StartStop, &QPushButton::pressed, this, &CAudioAdvancedDistributedComponent::toggleAudioStartStop, Qt::QueuedConnection); - this->setStartButton(); + this->setButtons(); } CAudioAdvancedDistributedComponent::~CAudioAdvancedDistributedComponent() @@ -42,37 +44,73 @@ namespace BlackGui const bool started = sGui->getCContextAudioBase()->isAudioStarted(); if (started) { - sGui->getCContextAudioBase()->afvClient()->stopAudio(); + sGui->getCContextAudioBase()->afvClient()->disconnectFromAndStop(); } else { sGui->getCContextAudioBase()->afvClient()->startAudio(); + if (sGui->getIContextNetwork()->isConnected()) + { + sGui->getCContextAudioBase()->connectAudioWithNetworkCredentials(); + } } + + this->setButtons(2000); } - void CAudioAdvancedDistributedComponent::setStartButton() + void CAudioAdvancedDistributedComponent::toggleAudioEnableDisable() + { + if (!hasContexts()) { return; } + const bool enabled = sGui->getCContextAudioBase()->isAudioEnabled(); + if (enabled) + { + sGui->getCContextAudioBase()->disableVoiceClient(); + } + else + { + sGui->getCContextAudioBase()->enableVoiceClientAndStart(); + } + + this->setButtons(2000); + } + + void CAudioAdvancedDistributedComponent::setButtons() { if (!hasContexts()) { return; } const bool started = sGui->getCContextAudioBase()->isAudioStarted(); + const bool enabled = sGui->getCContextAudioBase()->isAudioEnabled(); ui->pb_StartStop->setText(started ? "stop" : "start"); + ui->pb_StartStop->setEnabled(enabled); + ui->pb_EnableDisable->setText(enabled ? "disable" : "enable"); + } + + void CAudioAdvancedDistributedComponent::setButtons(int delayMs) + { + if (!hasContexts()) { return; } + QPointer myself(this); + QTimer::singleShot(delayMs, this, [ = ] + { + if (!sGui || !myself || sGui->isShuttingDown()) { return; } + this->setButtons(); + }); } void CAudioAdvancedDistributedComponent::onAudioStarted(const CAudioDeviceInfo &inputDevice, const CAudioDeviceInfo &outputDevice) { Q_UNUSED(inputDevice) Q_UNUSED(outputDevice) - this->setStartButton(); + this->setButtons(); } void CAudioAdvancedDistributedComponent::onAudioStoppend() { - this->setStartButton(); + this->setButtons(); } bool CAudioAdvancedDistributedComponent::hasContexts() { if (!sGui || sGui->isShuttingDown() || !sGui->getCContextAudioBase()) { return false; } - if (!sGui->getCContextAudioBase()->afvClient()) { return false; } + if (!sGui->getIContextNetwork()) { return false; } return true; } } diff --git a/src/blackgui/components/audioadvanceddistributedcomponent.h b/src/blackgui/components/audioadvanceddistributedcomponent.h index 7bcc835a3..0c0d42658 100644 --- a/src/blackgui/components/audioadvanceddistributedcomponent.h +++ b/src/blackgui/components/audioadvanceddistributedcomponent.h @@ -39,8 +39,13 @@ namespace BlackGui //! Audio start/stop void toggleAudioStartStop(); - //! Start/stop button - void setStartButton(); + //! Audio enable/disable + void toggleAudioEnableDisable(); + + //! Start/stop button @{ + void setButtons(); + void setButtons(int delayMs); + //! @} void onAudioStarted(const BlackMisc::Audio::CAudioDeviceInfo &inputDevice, const BlackMisc::Audio::CAudioDeviceInfo &outputDevice); void onAudioStoppend(); diff --git a/src/blackgui/components/audioadvanceddistributedcomponent.ui b/src/blackgui/components/audioadvanceddistributedcomponent.ui index e49df06d0..196eda6b1 100644 --- a/src/blackgui/components/audioadvanceddistributedcomponent.ui +++ b/src/blackgui/components/audioadvanceddistributedcomponent.ui @@ -7,29 +7,46 @@ 0 0 166 - 32 + 55 Frame - - + + - Start/stop audio: + enable - + stop + + + + Enable/disable: + + + + + + + Start/stop audio: + + + + + pb_StartStop + diff --git a/src/blackgui/components/audiodevicevolumesetupcomponent.cpp b/src/blackgui/components/audiodevicevolumesetupcomponent.cpp index deee0b70e..39e388852 100644 --- a/src/blackgui/components/audiodevicevolumesetupcomponent.cpp +++ b/src/blackgui/components/audiodevicevolumesetupcomponent.cpp @@ -101,10 +101,7 @@ namespace BlackGui if (audio) { - const QString ai = sGui->getCContextAudioBase()->audioRunsWhereInfo(); - ui->le_Info->setText(ai); - ui->le_Info->setPlaceholderText(ai); - + this->setAudioRunsWhere(); this->initAudioDeviceLists(); // default @@ -121,18 +118,16 @@ namespace BlackGui Q_ASSERT(c); c = connect(sGui->getCContextAudioBase(), &CContextAudioBase::startedAudio, this, &CAudioDeviceVolumeSetupComponent::onAudioStarted, Qt::QueuedConnection); Q_ASSERT(c); + c = connect(sGui->getCContextAudioBase(), &CContextAudioBase::stoppedAudio, this, &CAudioDeviceVolumeSetupComponent::onAudioStopped, Qt::QueuedConnection); + Q_ASSERT(c); //! \todo Workaround to avoid context signals - c = connect(sGui->getCContextAudioBase()->afvClient(), &CAfvClient::outputVolumePeakVU, this, &CAudioDeviceVolumeSetupComponent::onOutputVU, Qt::QueuedConnection); Q_ASSERT(c); - c = connect(sGui->getCContextAudioBase()->afvClient(), &CAfvClient::inputVolumePeakVU, this, &CAudioDeviceVolumeSetupComponent::onInputVU, Qt::QueuedConnection); Q_ASSERT(c); - c = connect(sGui->getCContextAudioBase()->afvClient(), &CAfvClient::receivedCallsignsChanged, this, &CAudioDeviceVolumeSetupComponent::onReceivingCallsignsChanged, Qt::QueuedConnection); Q_ASSERT(c); - c = connect(sGui->getCContextAudioBase()->afvClient(), &CAfvClient::updatedFromOwnAircraftCockpit, this, &CAudioDeviceVolumeSetupComponent::onUpdatedClientWithCockpitData, Qt::QueuedConnection); Q_ASSERT(c); @@ -303,6 +298,12 @@ namespace BlackGui ui->hs_VolumeOut->setValue((ui->hs_VolumeOut->maximum() - ui->hs_VolumeOut->minimum()) / 2); } + void CAudioDeviceVolumeSetupComponent::setAudioRunsWhere() + { + const QString ai = sGui->getCContextAudioBase()->audioRunsWhereInfo(); + ui->le_Info->setPlaceholderText(ai); + } + void CAudioDeviceVolumeSetupComponent::onReceivingCallsignsChanged(const CCallsignSet &com1Callsigns, const CCallsignSet &com2Callsigns) { const QString info = (com1Callsigns.isEmpty() ? QString() : QStringLiteral("COM1: ") % com1Callsigns.getCallsignsAsString()) % @@ -347,6 +348,12 @@ namespace BlackGui { ui->cb_SetupAudioInputDevice->setCurrentText(input.toQString(true)); ui->cb_SetupAudioOutputDevice->setCurrentText(output.toQString(true)); + this->setAudioRunsWhere(); + } + + void CAudioDeviceVolumeSetupComponent::onAudioStopped() + { + this->setAudioRunsWhere(); } bool CAudioDeviceVolumeSetupComponent::onAudioDevicesChanged(const CAudioDeviceInfoList &devices) @@ -354,6 +361,7 @@ namespace BlackGui if (m_cbDevices.hasSameDevices(devices)) { return false; } // avoid numerous follow up actions m_cbDevices = devices; + this->setAudioRunsWhere(); ui->cb_SetupAudioOutputDevice->clear(); ui->cb_SetupAudioInputDevice->clear(); @@ -374,6 +382,7 @@ namespace BlackGui if (!i.isEmpty()) { ui->cb_SetupAudioInputDevice->setCurrentText(i); } if (!o.isEmpty()) { ui->cb_SetupAudioOutputDevice->setCurrentText(o); } + return true; } diff --git a/src/blackgui/components/audiodevicevolumesetupcomponent.h b/src/blackgui/components/audiodevicevolumesetupcomponent.h index 704c2b278..a54162187 100644 --- a/src/blackgui/components/audiodevicevolumesetupcomponent.h +++ b/src/blackgui/components/audiodevicevolumesetupcomponent.h @@ -73,6 +73,9 @@ namespace BlackGui //! Current audio devices changed void onAudioStarted(const BlackMisc::Audio::CAudioDeviceInfo &input, const BlackMisc::Audio::CAudioDeviceInfo &output); + //! Audio has been stopped + void onAudioStopped(); + //! Audio devices changed bool onAudioDevicesChanged(const BlackMisc::Audio::CAudioDeviceInfoList &devices); @@ -101,6 +104,8 @@ namespace BlackGui void onResetVolumeIn(); void onResetVolumeOut(); + void setAudioRunsWhere(); + // TODO: Move TransceiverReceivingCallsignsChangedArgs to Blackmisc void onReceivingCallsignsChanged(const BlackMisc::Aviation::CCallsignSet &com1Callsigns, const BlackMisc::Aviation::CCallsignSet &com2Callsigns); void onUpdatedClientWithCockpitData();