From ebfa44cdb76afb9632556115eef10969912340f5 Mon Sep 17 00:00:00 2001 From: Roxie Linden Date: Thu, 30 Nov 2023 13:14:07 -0800 Subject: Refactor/clean-up WebRTC voice to handle multiple voice streams This is useful for cross-region voice, quick voice switching, etc. --- indra/llwebrtc/llwebrtc.cpp | 79 +- indra/llwebrtc/llwebrtc.h | 8 +- indra/llwebrtc/llwebrtc_impl.h | 7 +- indra/newview/llvoicewebrtc.cpp | 3252 ++++++++++++++------------------------- indra/newview/llvoicewebrtc.h | 398 +++-- 5 files changed, 1438 insertions(+), 2306 deletions(-) diff --git a/indra/llwebrtc/llwebrtc.cpp b/indra/llwebrtc/llwebrtc.cpp index bf1e04c2f2..b2f5e0212e 100644 --- a/indra/llwebrtc/llwebrtc.cpp +++ b/indra/llwebrtc/llwebrtc.cpp @@ -216,6 +216,7 @@ void LLWebRTCImpl::setCaptureDevice(const std::string &id) mWorkerThread->PostTask( [this, id]() { + int16_t tuningRecordingDevice = 0; int16_t captureDeviceCount = mTuningDeviceModule->RecordingDevices(); for (int16_t i = 0; i < captureDeviceCount; i++) { @@ -225,18 +226,32 @@ void LLWebRTCImpl::setCaptureDevice(const std::string &id) if (id == guid || id == "Default") // first one in list is default { RTC_LOG(LS_INFO) << __FUNCTION__ << "Set recording device to " << name << " " << guid << " " << i; - mRecordingDevice = i; + tuningRecordingDevice = i; break; } } mTuningDeviceModule->StopRecording(); - mTuningDeviceModule->SetRecordingDevice(mRecordingDevice); + mTuningDeviceModule->SetRecordingDevice(tuningRecordingDevice); mTuningDeviceModule->InitMicrophone(); mTuningDeviceModule->InitRecording(); mTuningDeviceModule->StartRecording(); bool was_peer_recording = false; if (mPeerDeviceModule) { + int16_t captureDeviceCount = mTuningDeviceModule->RecordingDevices(); + for (int16_t i = 0; i < captureDeviceCount; i++) + { + char name[webrtc::kAdmMaxDeviceNameSize]; + char guid[webrtc::kAdmMaxGuidSize]; + mTuningDeviceModule->RecordingDeviceName(i, name, guid); + if (id == guid || id == "Default") // first one in list is default + { + RTC_LOG(LS_INFO) + << __FUNCTION__ << "Set recording device to " << name << " " << guid << " " << i; + mRecordingDevice = i; + break; + } + } was_peer_recording = mPeerDeviceModule->Recording(); if (was_peer_recording) { @@ -259,6 +274,7 @@ void LLWebRTCImpl::setRenderDevice(const std::string &id) [this, id]() { int16_t renderDeviceCount = mTuningDeviceModule->PlayoutDevices(); + int16_t tuningPlayoutDevice = 0; for (int16_t i = 0; i < renderDeviceCount; i++) { char name[webrtc::kAdmMaxDeviceNameSize]; @@ -267,7 +283,7 @@ void LLWebRTCImpl::setRenderDevice(const std::string &id) if (id == guid || id == "Default") { RTC_LOG(LS_INFO) << __FUNCTION__ << "Set recording device to " << name << " " << guid << " " << i; - mPlayoutDevice = i; + tuningPlayoutDevice = i; break; } } @@ -277,30 +293,40 @@ void LLWebRTCImpl::setRenderDevice(const std::string &id) { mTuningDeviceModule->StopPlayout(); } - bool was_peer_mute = false; if (mPeerDeviceModule) { - mPeerDeviceModule->SpeakerMute(&was_peer_mute); - if (!was_peer_mute) - { - mPeerDeviceModule->SetSpeakerMute(true); - } + mPeerDeviceModule->SetSpeakerMute(true); } - mTuningDeviceModule->SetPlayoutDevice(mPlayoutDevice); + mTuningDeviceModule->SetPlayoutDevice(tuningPlayoutDevice); mTuningDeviceModule->InitSpeaker(); mTuningDeviceModule->InitPlayout(); if (was_tuning_playing) { mTuningDeviceModule->StartPlayout(); } + renderDeviceCount = mPeerDeviceModule->PlayoutDevices(); + if (mPeerDeviceModule) { + for (int16_t i = 0; i < renderDeviceCount; i++) + { + char name[webrtc::kAdmMaxDeviceNameSize]; + char guid[webrtc::kAdmMaxGuidSize]; + mPeerDeviceModule->PlayoutDeviceName(i, name, guid); + if (id == guid || id == "Default") + { + RTC_LOG(LS_INFO) + << __FUNCTION__ << "Set recording device to " << name << " " << guid << " " << i; + mPlayoutDevice = i; + break; + } + } mPeerDeviceModule->SetPlayoutDevice(mPlayoutDevice); mPeerDeviceModule->InitSpeaker(); mPeerDeviceModule->InitPlayout(); mPeerDeviceModule->StartPlayout(); - mPeerDeviceModule->SetSpeakerMute(was_peer_mute); + mPeerDeviceModule->SetSpeakerMute(false); } mTuningDeviceModule->SetSpeakerMute(false); @@ -376,6 +402,8 @@ float LLWebRTCImpl::getPeerAudioLevel() { return 20 * mPeerAudioDeviceObserver-> void LLWebRTCImpl::setSpeakerVolume(float volume) { mPeerDeviceModule->SetSpeakerVolume( (uint32_t)(volume * VOLUME_SCALE_WEBRTC));} void LLWebRTCImpl::setMicrophoneVolume(float volume) { mPeerDeviceModule->SetMicrophoneVolume((uint32_t)(volume * VOLUME_SCALE_WEBRTC));} +void LLWebRTCImpl::setMute(bool mute) { mPeerDeviceModule->SetMicrophoneMute(mute); } + // // Helpers // @@ -566,9 +594,12 @@ void LLWebRTCPeerConnectionImpl::AnswerAvailable(const std::string &sdp) mWebRTCImpl->PostSignalingTask( [this, sdp]() { - RTC_LOG(LS_INFO) << __FUNCTION__ << " " << mPeerConnection->peer_connection_state(); - mPeerConnection->SetRemoteDescription(webrtc::CreateSessionDescription(webrtc::SdpType::kAnswer, sdp), - rtc::scoped_refptr(this)); + if (mPeerConnection) + { + RTC_LOG(LS_INFO) << __FUNCTION__ << " " << mPeerConnection->peer_connection_state(); + mPeerConnection->SetRemoteDescription(webrtc::CreateSessionDescription(webrtc::SdpType::kAnswer, sdp), + rtc::scoped_refptr(this)); + } }); } @@ -844,17 +875,6 @@ void LLWebRTCPeerConnectionImpl::OnSetLocalDescriptionComplete(webrtc::RTCError } -void LLWebRTCPeerConnectionImpl::setAudioObserver(LLWebRTCAudioObserver *observer) { mAudioObserverList.emplace_back(observer); } - -void LLWebRTCPeerConnectionImpl::unsetAudioObserver(LLWebRTCAudioObserver *observer) -{ - std::vector::iterator it = std::find(mAudioObserverList.begin(), mAudioObserverList.end(), observer); - if (it != mAudioObserverList.end()) - { - mAudioObserverList.erase(it); - } -} - // // DataChannelObserver implementation // @@ -897,9 +917,12 @@ void LLWebRTCPeerConnectionImpl::OnMessage(const webrtc::DataBuffer& buffer) void LLWebRTCPeerConnectionImpl::sendData(const std::string& data, bool binary) { - rtc::CopyOnWriteBuffer cowBuffer(data.data(), data.length()); - webrtc::DataBuffer buffer(cowBuffer, binary); - mDataChannel->Send(buffer); + if (mDataChannel) + { + rtc::CopyOnWriteBuffer cowBuffer(data.data(), data.length()); + webrtc::DataBuffer buffer(cowBuffer, binary); + mDataChannel->Send(buffer); + } } void LLWebRTCPeerConnectionImpl::setDataObserver(LLWebRTCDataObserver* observer) { mDataObserverList.emplace_back(observer); } diff --git a/indra/llwebrtc/llwebrtc.h b/indra/llwebrtc/llwebrtc.h index 753fe6a983..ed80fa5648 100644 --- a/indra/llwebrtc/llwebrtc.h +++ b/indra/llwebrtc/llwebrtc.h @@ -88,19 +88,13 @@ class LLWebRTCDeviceInterface virtual void setSpeakerVolume(float volume) = 0; // volume between 0.0 and 1.0 virtual void setMicrophoneVolume(float volume) = 0; // volume between 0.0 and 1.0 + virtual void setMute(bool mute) = 0; virtual float getPeerAudioLevel() = 0; }; -class LLWebRTCAudioObserver -{ - public: -}; - class LLWebRTCAudioInterface { public: - virtual void setAudioObserver(LLWebRTCAudioObserver *observer) = 0; - virtual void unsetAudioObserver(LLWebRTCAudioObserver *observer) = 0; virtual void setMute(bool mute) = 0; }; diff --git a/indra/llwebrtc/llwebrtc_impl.h b/indra/llwebrtc/llwebrtc_impl.h index eb439b5a46..76e29c63fb 100644 --- a/indra/llwebrtc/llwebrtc_impl.h +++ b/indra/llwebrtc/llwebrtc_impl.h @@ -118,7 +118,8 @@ class LLWebRTCImpl : public LLWebRTCDeviceInterface float getPeerAudioLevel() override; void setSpeakerVolume(float volume) override; // range 0.0-1.0 - void setMicrophoneVolume(float volume) override; // range 0.0-1.0 + void setMicrophoneVolume(float volume) override; // range 0.0-1.0 + void setMute(bool mute) override; // // Helpers @@ -227,8 +228,6 @@ class LLWebRTCPeerConnectionImpl : public LLWebRTCPeerConnection, // // LLWebRTCAudioInterface // - void setAudioObserver(LLWebRTCAudioObserver *observer) override; - void unsetAudioObserver(LLWebRTCAudioObserver *observer) override; void setMute(bool mute) override; // @@ -292,8 +291,6 @@ class LLWebRTCPeerConnectionImpl : public LLWebRTCPeerConnection, bool mAnswerReceived; rtc::scoped_refptr mPeerConnection; - - std::vector mAudioObserverList; std::vector mDataObserverList; rtc::scoped_refptr mDataChannel; diff --git a/indra/newview/llvoicewebrtc.cpp b/indra/newview/llvoicewebrtc.cpp index 93efd2526d..31be3daed3 100644 --- a/indra/newview/llvoicewebrtc.cpp +++ b/indra/newview/llvoicewebrtc.cpp @@ -51,6 +51,7 @@ #include "llagent.h" #include "llcachename.h" #include "llimview.h" // for LLIMMgr +#include "llworld.h" #include "llparcel.h" #include "llviewerparcelmgr.h" #include "llfirstuse.h" @@ -230,7 +231,6 @@ LLPumpIO *LLWebRTCVoiceClient::sPump = nullptr; LLWebRTCVoiceClient::LLWebRTCVoiceClient() : mSessionTerminateRequested(false), mRelogRequested(false), - mTerminateDaemon(false), mSpatialJoiningNum(0), mTuningMode(false), @@ -247,11 +247,6 @@ LLWebRTCVoiceClient::LLWebRTCVoiceClient() : mNextAudioSession(), mCurrentParcelLocalID(0), - mConnectorEstablished(false), - mAccountLoggedIn(false), - mNumberOfAliases(0), - mCommandCookie(0), - mLoginRetryCount(0), mBuddyListMapPopulated(false), mBlockRulesListReceived(false), @@ -271,7 +266,6 @@ LLWebRTCVoiceClient::LLWebRTCVoiceClient() : mMicVolumeDirty(true), mVoiceEnabled(false), - mWriteInProgress(false), mLipSyncEnabled(false), @@ -280,17 +274,13 @@ LLWebRTCVoiceClient::LLWebRTCVoiceClient() : mAvatarNameCacheConnection(), mIsInTuningMode(false), - mIsInChannel(false), mIsJoiningSession(false), mIsWaitingForFonts(false), mIsLoggingIn(false), - mIsLoggedIn(false), mIsProcessingChannels(false), mIsCoroutineActive(false), mWebRTCPump("WebRTCClientPump"), - mWebRTCDeviceInterface(nullptr), - mWebRTCPeerConnection(nullptr), - mWebRTCAudioInterface(nullptr) + mWebRTCDeviceInterface(nullptr) { sShuttingDown = false; sConnected = false; @@ -339,15 +329,12 @@ void LLWebRTCVoiceClient::init(LLPumpIO *pump) // constructor will set up LLVoiceClient::getInstance() sPump = pump; -// LLCoros::instance().launch("LLWebRTCVoiceClient::voiceControlCoro", -// boost::bind(&LLWebRTCVoiceClient::voiceControlCoro, LLWebRTCVoiceClient::getInstance())); +// LLCoros::instance().launch("LLWebRTCVoiceClient::voiceConnectionCoro", +// boost::bind(&LLWebRTCVoiceClient::voiceConnectionCoro, LLWebRTCVoiceClient::getInstance())); llwebrtc::init(); mWebRTCDeviceInterface = llwebrtc::getDeviceInterface(); mWebRTCDeviceInterface->setDevicesObserver(this); - - mWebRTCPeerConnection = llwebrtc::newPeerConnection(); - mWebRTCPeerConnection->setSignalingObserver(this); } void LLWebRTCVoiceClient::terminate() @@ -371,7 +358,7 @@ void LLWebRTCVoiceClient::cleanUp() { LL_DEBUGS("Voice") << LL_ENDL; - deleteAllSessions(); + sessionState::deleteAllSessions(); LL_DEBUGS("Voice") << "exiting" << LL_ENDL; } @@ -401,52 +388,46 @@ void LLWebRTCVoiceClient::updateSettings() ///////////////////////////// // session control messages -void LLWebRTCVoiceClient::connectorCreate() -{ +void LLWebRTCVoiceClient::predOnConnectionEstablished(const LLWebRTCVoiceClient::sessionStatePtr_t& session, std::string channelID) +{ + session->OnConnectionEstablished(channelID); } -void LLWebRTCVoiceClient::connectorShutdown() +void LLWebRTCVoiceClient::predOnConnectionFailure(const LLWebRTCVoiceClient::sessionStatePtr_t &session, std::string channelID) { - - mShutdownComplete = true; + session->OnConnectionFailure(channelID); } -void LLWebRTCVoiceClient::userAuthorized(const std::string& user_id, const LLUUID &agentID) +void LLWebRTCVoiceClient::OnConnectionFailure(const std::string& channelID) { + sessionState::for_each(boost::bind(predOnConnectionFailure, _1, channelID)); +} - mAccountDisplayName = user_id; - - LL_INFOS("Voice") << "name \"" << mAccountDisplayName << "\" , ID " << agentID << LL_ENDL; - - mAccountName = nameFromID(agentID); +void LLWebRTCVoiceClient::OnConnectionEstablished(const std::string &channelID) +{ + if (mNextAudioSession && mNextAudioSession->mChannelID == channelID) + { + mAudioSession = mNextAudioSession; + mNextAudioSession.reset(); + } + sessionState::for_each(boost::bind(predOnConnectionEstablished, _1, channelID)); } -void LLWebRTCVoiceClient::setLoginInfo( - const std::string& account_name, - const std::string& password, - const std::string& channel_sdp) +void LLWebRTCVoiceClient::sessionState::OnConnectionEstablished(const std::string& channelID) { - mRemoteChannelSDP = channel_sdp; - mWebRTCPeerConnection->AnswerAvailable(channel_sdp); + if (channelID == mPrimaryConnectionID) + { + LLWebRTCVoiceClient::getInstance()->notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGGED_IN); + } +} - if(mAccountLoggedIn) - { - // Already logged in. - LL_WARNS("Voice") << "Called while already logged in." << LL_ENDL; - - // Don't process another login. - return; - } - else if ( account_name != mAccountName ) - { - LL_WARNS("Voice") << "Mismatched account name! " << account_name - << " instead of " << mAccountName << LL_ENDL; - } - else - { - mAccountPassword = password; - } +void LLWebRTCVoiceClient::sessionState::OnConnectionFailure(const std::string &channelID) +{ + if (channelID == mPrimaryConnectionID) + { + LLWebRTCVoiceClient::getInstance()->notifyStatusObservers(LLVoiceClientStatusObserver::ERROR_UNKNOWN); + } } void LLWebRTCVoiceClient::idle(void* user_data) @@ -460,1609 +441,449 @@ void LLWebRTCVoiceClient::idle(void* user_data) // // -typedef enum e_voice_control_coro_state -{ - VOICE_STATE_ERROR = -1, - VOICE_STATE_TP_WAIT = 0, // entry point - VOICE_STATE_START_DAEMON, - VOICE_STATE_PROVISION_ACCOUNT, - VOICE_STATE_SESSION_PROVISION_WAIT, - VOICE_STATE_START_SESSION, - VOICE_STATE_WAIT_FOR_SESSION_START, - VOICE_STATE_SESSION_RETRY, - VOICE_STATE_SESSION_ESTABLISHED, - VOICE_STATE_WAIT_FOR_CHANNEL, - VOICE_STATE_DISCONNECT, - VOICE_STATE_WAIT_FOR_EXIT, -} EVoiceControlCoroState; - -void LLWebRTCVoiceClient::voiceControlCoro() -{ - int state = 0; - try - { - // state is passed as a reference instead of being - // a member due to unresolved issues with coroutine - // surviving longer than LLWebRTCVoiceClient - voiceControlStateMachine(); - } - catch (const LLCoros::Stop&) - { - LL_DEBUGS("LLWebRTCVoiceClient") << "Received a shutdown exception" << LL_ENDL; - } - catch (const LLContinueError&) - { - LOG_UNHANDLED_EXCEPTION("LLWebRTCVoiceClient"); - } - catch (...) - { - // Ideally for Windows need to log SEH exception instead or to set SEH - // handlers but bugsplat shows local variables for windows, which should - // be enough - LL_WARNS("Voice") << "voiceControlStateMachine crashed in state " << state << LL_ENDL; - throw; - } +void LLWebRTCVoiceClient::predProcessSessionStates(const LLWebRTCVoiceClient::sessionStatePtr_t& session) +{ + session->processSessionStates(); } -void LLWebRTCVoiceClient::voiceControlStateMachine() +void LLWebRTCVoiceClient::sessionState::processSessionStates() { - if (sShuttingDown) + std::map::iterator iter; + for (iter = mWebRTCConnections.begin(); iter != mWebRTCConnections.end();) { - return; + if (!iter->second->connectionStateMachine()) + { + iter = mWebRTCConnections.erase(iter); + } + else + { + ++iter; + } } +} +void LLWebRTCVoiceClient::voiceConnectionCoro() +{ LL_DEBUGS("Voice") << "starting" << LL_ENDL; mIsCoroutineActive = true; LLCoros::set_consuming(true); - - U32 retry = 0; - U32 provisionWaitTimeout = 0; - - setVoiceControlStateUnless(VOICE_STATE_TP_WAIT); - - do + try { - if (sShuttingDown) - { - // WebRTC singleton performed the exit, logged out, - // cleaned sockets, gateway and no longer cares - // about state of coroutine, so just stop - return; - } - - processIceUpdates(); - - switch (getVoiceControlState()) + while (!sShuttingDown) { - case VOICE_STATE_TP_WAIT: - // starting point for voice - if (gAgent.getTeleportState() != LLAgent::TELEPORT_NONE) - { - LL_DEBUGS("Voice") << "Suspending voiceControlCoro() momentarily for teleport. Tuning: " << mTuningMode - << ". Relog: " << mRelogRequested << LL_ENDL; - llcoro::suspendUntilTimeout(1.0); - } - else - { - LLMutexLock lock(&mVoiceStateMutex); - - mTrickling = false; - mIceCompleted = false; - setVoiceControlStateUnless(VOICE_STATE_START_SESSION, VOICE_STATE_SESSION_RETRY); - } - break; - - case VOICE_STATE_START_SESSION: - if (establishVoiceConnection() && getVoiceControlState() != VOICE_STATE_SESSION_RETRY) - { - setVoiceControlStateUnless(VOICE_STATE_WAIT_FOR_SESSION_START, VOICE_STATE_SESSION_RETRY); - } - else - { - setVoiceControlStateUnless(VOICE_STATE_SESSION_RETRY); - } - break; - - case VOICE_STATE_WAIT_FOR_SESSION_START: + // add session for region or parcel voice. + LLViewerRegion *regionp = gAgent.getRegion(); + if (!regionp) { - llcoro::suspendUntilTimeout(1.0); - std::string channel_sdp; - { - LLMutexLock lock(&mVoiceStateMutex); - if (mVoiceControlState == VOICE_STATE_SESSION_RETRY) - { - break; - } - if (!mChannelSDP.empty()) - { - mVoiceControlState = VOICE_STATE_PROVISION_ACCOUNT; - } - } - break; + llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); + continue; } - case VOICE_STATE_PROVISION_ACCOUNT: - if (!provisionVoiceAccount()) - { - setVoiceControlStateUnless(VOICE_STATE_SESSION_RETRY); - } - else - { - provisionWaitTimeout = 0; - setVoiceControlStateUnless(VOICE_STATE_SESSION_PROVISION_WAIT, VOICE_STATE_SESSION_RETRY); - } - break; - case VOICE_STATE_SESSION_PROVISION_WAIT: - llcoro::suspendUntilTimeout(1.0); - if (provisionWaitTimeout++ > PROVISION_WAIT_TIMEOUT_SEC) - { - setVoiceControlStateUnless(VOICE_STATE_SESSION_RETRY); - } - break; - - case VOICE_STATE_SESSION_RETRY: - giveUp(); // cleans sockets and session - if (mRelogRequested) - { - // We failed to connect, give it a bit time before retrying. - retry++; - F32 full_delay = llmin(2.f * (F32) retry, 10.f); - F32 current_delay = 0.f; - LL_INFOS("Voice") << "Voice failed to establish session after " << retry << " tries. Will attempt to reconnect in " - << full_delay << " seconds" << LL_ENDL; - while (current_delay < full_delay && !sShuttingDown) - { - // Assuming that a second has passed is not accurate, - // but we don't need accurancy here, just to make sure - // that some time passed and not to outlive voice itself - current_delay++; - llcoro::suspendUntilTimeout(1.f); - } - } - setVoiceControlStateUnless(VOICE_STATE_DISCONNECT); - break; - - case VOICE_STATE_SESSION_ESTABLISHED: + if (regionp->getRegionID().isNull()) { - retry = 0; - if (mTuningMode) - { - performMicTuning(); - } - sessionEstablished(); - setVoiceControlStateUnless(VOICE_STATE_WAIT_FOR_CHANNEL, VOICE_STATE_SESSION_RETRY); + llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); + continue; } - break; - - case VOICE_STATE_WAIT_FOR_CHANNEL: + if (!mAudioSession || mAudioSession->mIsSpatial) { - if ((!waitForChannel()) || !mVoiceEnabled) // todo: split into more states like login/fonts - { - setVoiceControlStateUnless(VOICE_STATE_DISCONNECT, VOICE_STATE_SESSION_RETRY); - } - // on true, it's a retry, so let the state stand. - } - break; - - case VOICE_STATE_DISCONNECT: - LL_DEBUGS("Voice") << "lost channel RelogRequested=" << mRelogRequested << LL_ENDL; - breakVoiceConnection(true); - retry = 0; // Connected without issues - setVoiceControlStateUnless(VOICE_STATE_WAIT_FOR_EXIT); - break; + // check to see if parcel changed. + std::string channelID = regionp->getRegionID().asString(); - case VOICE_STATE_WAIT_FOR_EXIT: - if (mVoiceEnabled) + LLParcel *parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + S32 parcel_local_id = INVALID_PARCEL_ID; + if (parcel && parcel->getLocalID() != INVALID_PARCEL_ID && !parcel->getParcelFlagUseEstateVoiceChannel()) { - LL_INFOS("Voice") << "will attempt to reconnect to voice" << LL_ENDL; - setVoiceControlStateUnless(VOICE_STATE_TP_WAIT); + parcel_local_id = parcel->getLocalID(); + channelID += "-" + std::to_string(parcel->getLocalID()); } - else + if (!mAudioSession || channelID != mAudioSession->mChannelID) { - llcoro::suspendUntilTimeout(1.0); + setSpatialChannel(channelID, "", parcel_local_id); } - break; - - default: - { - LL_WARNS("Voice") << "Unknown voice control state " << getVoiceControlState() << LL_ENDL; - break; } + updatePosition(); + sessionState::for_each(boost::bind(predProcessSessionStates, _1)); + sendPositionAndVolumeUpdate(true); + updateOwnVolume(); + llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); } - } while (true); -} - -bool LLWebRTCVoiceClient::callbackEndDaemon(const LLSD& data) -{ - if (!sShuttingDown && mVoiceEnabled) + } + catch (const LLCoros::Stop&) { - LL_WARNS("Voice") << "SLVoice terminated " << ll_stream_notation_sd(data) << LL_ENDL; - terminateAudioSession(false); - closeSocket(); - cleanUp(); - LLVoiceClient::getInstance()->setUserPTTState(false); - gAgent.setVoiceConnected(false); - mRelogRequested = true; + LL_DEBUGS("LLWebRTCVoiceClient") << "Received a shutdown exception" << LL_ENDL; } - return false; -} - -bool LLWebRTCVoiceClient::provisionVoiceAccount() -{ - LL_INFOS("Voice") << "Provisioning voice account." << LL_ENDL; - - while ((!gAgent.getRegion() || !gAgent.getRegion()->capabilitiesReceived()) && !sShuttingDown) + catch (const LLContinueError&) { - LL_DEBUGS("Voice") << "no capabilities for voice provisioning; waiting " << LL_ENDL; - // *TODO* Pump a message for wake up. - llcoro::suspend(); + LOG_UNHANDLED_EXCEPTION("LLWebRTCVoiceClient"); } - - if (sShuttingDown) + catch (...) { - return false; + // Ideally for Windows need to log SEH exception instead or to set SEH + // handlers but bugsplat shows local variables for windows, which should + // be enough + LL_WARNS("Voice") << "voiceConnectionStateMachine crashed" << LL_ENDL; + throw; } - std::string url = gAgent.getRegionCapability("ProvisionVoiceAccountRequest"); + cleanUp(); +} - LL_DEBUGS("Voice") << "region ready for voice provisioning; url=" << url << LL_ENDL; +bool LLWebRTCVoiceClient::performMicTuning() +{ + LL_INFOS("Voice") << "Entering voice tuning mode." << LL_ENDL; - LLVoiceWebRTCStats::getInstance()->provisionAttemptStart(); - LLSD body; - LLSD jsep; - jsep["type"] = "offer"; - { - LLMutexLock lock(&mVoiceStateMutex); - jsep["sdp"] = mChannelSDP; - } - body["jsep"] = jsep; + mIsInTuningMode = false; - LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpPost( - url, - LLCore::HttpRequest::DEFAULT_POLICY_ID, - body, - boost::bind(&LLWebRTCVoiceClient::OnVoiceAccountProvisioned, this, _1), - boost::bind(&LLWebRTCVoiceClient::OnVoiceAccountProvisionFailure, this, url, 3, body, _1)); + //--------------------------------------------------------------------- return true; } -void LLWebRTCVoiceClient::OnVoiceAccountProvisioned(const LLSD& result) -{ - LLVoiceWebRTCStats::getInstance()->provisionAttemptEnd(true); - std::string channelSDP; - if (result.has("jsep") && - result["jsep"].has("type") && - result["jsep"]["type"] == "answer" && - result["jsep"].has("sdp")) - { - channelSDP = result["jsep"]["sdp"].asString(); - } - std::string voiceAccountServerUri; - std::string voiceUserName = gAgent.getID().asString(); - std::string voicePassword = ""; // no password for now. - - LL_DEBUGS("Voice") << "ProvisionVoiceAccountRequest response" - << " user " << (voiceUserName.empty() ? "not set" : "set") << " password " - << (voicePassword.empty() ? "not set" : "set") << " channel sdp " << channelSDP << LL_ENDL; +//========================================================================= - setLoginInfo(voiceUserName, voicePassword, channelSDP); +void LLWebRTCVoiceClient::sessionTerminate() +{ + mSessionTerminateRequested = true; +} - // switch to the default region channel. - switchChannel(gAgent.getRegion()->getRegionID().asString()); +void LLWebRTCVoiceClient::requestRelog() +{ + mSessionTerminateRequested = true; + mRelogRequested = true; } -void LLWebRTCVoiceClient::OnVoiceAccountProvisionFailure(std::string url, int retries, LLSD body, const LLSD& result) + +void LLWebRTCVoiceClient::leaveAudioSession() { - if (sShuttingDown) + if(mAudioSession) { - return; + LL_DEBUGS("Voice") << "leaving session: " << mAudioSession->mChannelID << LL_ENDL; } - if (retries >= 0) - { - - LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpPost( - url, - LLCore::HttpRequest::DEFAULT_POLICY_ID, - body, - boost::bind(&LLWebRTCVoiceClient::OnVoiceAccountProvisioned, this, _1), - boost::bind(&LLWebRTCVoiceClient::OnVoiceAccountProvisionFailure, this, url, retries - 1, body, _1)); - } - else - { - LL_WARNS("Voice") << "Unable to complete ice trickling voice account, retrying." << LL_ENDL; - } + else + { + LL_WARNS("Voice") << "called with no active session" << LL_ENDL; + } + sessionTerminate(); } -bool LLWebRTCVoiceClient::establishVoiceConnection() +void LLWebRTCVoiceClient::clearCaptureDevices() { - LL_INFOS("Voice") << "Ice Gathering voice account." << LL_ENDL; - while ((!gAgent.getRegion() || !gAgent.getRegion()->capabilitiesReceived()) && !sShuttingDown) - { - LL_DEBUGS("Voice") << "no capabilities for voice provisioning; waiting " << LL_ENDL; - // *TODO* Pump a message for wake up. - llcoro::suspend(); - return false; - } - - if (!mVoiceEnabled && mIsInitialized) - { - LL_WARNS("Voice") << "cannot establish connection; enabled "<initializeConnection(); + LL_DEBUGS("Voice") << "called" << LL_ENDL; + mCaptureDevices.clear(); } -bool LLWebRTCVoiceClient::breakVoiceConnection(bool corowait) +void LLWebRTCVoiceClient::addCaptureDevice(const LLVoiceDevice& device) { + LL_DEBUGS("Voice") << "display: '" << device.display_name << "' device: '" << device.full_name << "'" << LL_ENDL; + mCaptureDevices.push_back(device); +} - LL_INFOS("Voice") << "Breaking voice account." << LL_ENDL; +LLVoiceDeviceList& LLWebRTCVoiceClient::getCaptureDevices() +{ + return mCaptureDevices; +} - while ((!gAgent.getRegion() || !gAgent.getRegion()->capabilitiesReceived()) && !sShuttingDown) +void LLWebRTCVoiceClient::setCaptureDevice(const std::string& name) +{ + bool inTuningMode = mIsInTuningMode; + if (inTuningMode) { - LL_DEBUGS("Voice") << "no capabilities for voice breaking; waiting " << LL_ENDL; - // *TODO* Pump a message for wake up. - llcoro::suspend(); - } - - if (sShuttingDown) + tuningStop(); + } + mWebRTCDeviceInterface->setCaptureDevice(name); + if (inTuningMode) { - return false; + tuningStart(); } - - std::string url = gAgent.getRegionCapability("ProvisionVoiceAccountRequest"); - - LL_DEBUGS("Voice") << "region ready for voice break; url=" << url << LL_ENDL; - - LL_DEBUGS("Voice") << "sending ProvisionVoiceAccountRequest (breaking) (" << mCurrentRegionName << ", " << mCurrentParcelLocalID << ")" << LL_ENDL; - - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("parcelVoiceInfoRequest", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - - LLVoiceWebRTCStats::getInstance()->provisionAttemptStart(); - LLSD body; - body["logout"] = TRUE; - httpAdapter->postAndSuspend(httpRequest, url, body); - mWebRTCPeerConnection->shutdownConnection(); - return true; } - -bool LLWebRTCVoiceClient::loginToWebRTC() +void LLWebRTCVoiceClient::setDevicesListUpdated(bool state) { - mRelogRequested = false; - mIsLoggedIn = true; - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGGED_IN); - - // Set the initial state of mic mute, local speaker volume, etc. - sendLocalAudioUpdates(); - mIsLoggingIn = false; - - return true; + mDevicesListUpdated = state; } -void LLWebRTCVoiceClient::logoutOfWebRTC(bool wait) +void LLWebRTCVoiceClient::OnDevicesChanged(const llwebrtc::LLWebRTCVoiceDeviceList &render_devices, + const llwebrtc::LLWebRTCVoiceDeviceList &capture_devices) { - if (mIsLoggedIn) + clearRenderDevices(); + for (auto &device : render_devices) { - mAccountPassword.clear(); - breakVoiceConnection(wait); - // Ensure that we'll re-request provisioning before logging in again - mIsLoggedIn = false; + addRenderDevice(LLVoiceDevice(device.display_name, device.id)); + } + clearCaptureDevices(); + for (auto &device : capture_devices) + { + addCaptureDevice(LLVoiceDevice(device.display_name, device.id)); } + setDevicesListUpdated(true); } -bool LLWebRTCVoiceClient::requestParcelVoiceInfo() -{ - //_INFOS("Voice") << "Requesting voice info for Parcel" << LL_ENDL; +void LLWebRTCVoiceClient::clearRenderDevices() +{ + LL_DEBUGS("Voice") << "called" << LL_ENDL; + mRenderDevices.clear(); +} + +void LLWebRTCVoiceClient::addRenderDevice(const LLVoiceDevice& device) +{ + LL_DEBUGS("Voice") << "display: '" << device.display_name << "' device: '" << device.full_name << "'" << LL_ENDL; + mRenderDevices.push_back(device); + +} + +LLVoiceDeviceList& LLWebRTCVoiceClient::getRenderDevices() +{ + return mRenderDevices; +} + +void LLWebRTCVoiceClient::setRenderDevice(const std::string& name) +{ + mWebRTCDeviceInterface->setRenderDevice(name); +} - LLViewerRegion * region = gAgent.getRegion(); - if (region == NULL || !region->capabilitiesReceived()) +void LLWebRTCVoiceClient::tuningStart() +{ + if (!mIsInTuningMode) { - LL_DEBUGS("Voice") << "ParcelVoiceInfoRequest capability not yet available, deferring" << LL_ENDL; - return false; + mWebRTCDeviceInterface->setTuningMode(true); + mIsInTuningMode = true; } +} - // grab the cap. - std::string url = gAgent.getRegion()->getCapability("ParcelVoiceInfoRequest"); - if (url.empty()) +void LLWebRTCVoiceClient::tuningStop() +{ + if (mIsInTuningMode) { - // Region dosn't have the cap. Stop probing. - LL_DEBUGS("Voice") << "ParcelVoiceInfoRequest capability not available in this region" << LL_ENDL; - return false; + mWebRTCDeviceInterface->setTuningMode(false); + mIsInTuningMode = false; } - - // update the parcel - checkParcelChanged(true); - - LL_DEBUGS("Voice") << "sending ParcelVoiceInfoRequest (" << mCurrentRegionName << ", " << mCurrentParcelLocalID << ")" << LL_ENDL; +} - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("parcelVoiceInfoRequest", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); +bool LLWebRTCVoiceClient::inTuningMode() +{ + return mIsInTuningMode; +} - LLSD result = httpAdapter->postAndSuspend(httpRequest, url, LLSD()); +void LLWebRTCVoiceClient::tuningSetMicVolume(float volume) +{ + int scaled_volume = scale_mic_volume(volume); - if (sShuttingDown) - { - return false; - } + if(scaled_volume != mTuningMicVolume) + { + mTuningMicVolume = scaled_volume; + mTuningMicVolumeDirty = true; + } +} - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); +void LLWebRTCVoiceClient::tuningSetSpeakerVolume(float volume) +{ - if (mSessionTerminateRequested || (!mVoiceEnabled && mIsInitialized)) - { - // if a terminate request has been received, - // bail and go to the stateSessionTerminated - // state. If the cap request is still pending, - // the responder will check to see if we've moved - // to a new session and won't change any state. - LL_DEBUGS("Voice") << "terminate requested " << mSessionTerminateRequested - << " enabled " << mVoiceEnabled - << " initialized " << mIsInitialized - << LL_ENDL; - terminateAudioSession(true); - return false; - } + if (volume != mTuningSpeakerVolume) + { + mTuningSpeakerVolume = volume; + mTuningSpeakerVolumeDirty = true; + } +} + +float LLWebRTCVoiceClient::tuningGetEnergy(void) +{ + return mWebRTCDeviceInterface->getTuningAudioLevel(); +} - if ((!status) || (mSessionTerminateRequested || (!mVoiceEnabled && mIsInitialized))) - { - if (mSessionTerminateRequested || (!mVoiceEnabled && mIsInitialized)) - { - LL_WARNS("Voice") << "Session terminated." << LL_ENDL; - } +bool LLWebRTCVoiceClient::deviceSettingsAvailable() +{ + bool result = true; + + if(mRenderDevices.empty() || mCaptureDevices.empty()) + result = false; + + return result; +} +bool LLWebRTCVoiceClient::deviceSettingsUpdated() +{ + bool updated = mDevicesListUpdated; + mDevicesListUpdated = false; + return updated; +} - LL_WARNS("Voice") << "No voice on parcel" << LL_ENDL; - sessionTerminate(); - return false; - } +void LLWebRTCVoiceClient::refreshDeviceLists(bool clearCurrentList) +{ + if(clearCurrentList) + { + clearCaptureDevices(); + clearRenderDevices(); + } + mWebRTCDeviceInterface->refreshDevices(); +} - std::string uri; - std::string credentials; +void LLWebRTCVoiceClient::giveUp() +{ + // All has failed. Clean up and stop trying. + LL_WARNS("Voice") << "Terminating Voice Service" << LL_ENDL; + cleanUp(); +} - LL_WARNS("Voice") << "Got voice credentials" << result << LL_ENDL; +void LLWebRTCVoiceClient::setHidden(bool hidden) +{ + mHidden = hidden; - if (result.has("voice_credentials")) + if (mHidden && inSpatialChannel()) { - LLSD voice_credentials = result["voice_credentials"]; - if (voice_credentials.has("channel_uri")) - { - LL_DEBUGS("Voice") << "got voice channel uri" << LL_ENDL; - uri = voice_credentials["channel_uri"].asString(); - } - else - { - LL_WARNS("Voice") << "No voice channel uri" << LL_ENDL; - } - - if (voice_credentials.has("channel_credentials")) - { - LL_DEBUGS("Voice") << "got voice channel credentials" << LL_ENDL; - credentials = - voice_credentials["channel_credentials"].asString(); - } - else - { - LLVoiceChannel* channel = LLVoiceChannel::getCurrentVoiceChannel(); - if (channel != NULL) - { - if (channel->getSessionName().empty() && channel->getSessionID().isNull()) - { - if (LLViewerParcelMgr::getInstance()->allowAgentVoice()) - { - LL_WARNS("Voice") << "No channel credentials for default channel" << LL_ENDL; - } - } - else - { - LL_WARNS("Voice") << "No voice channel credentials" << LL_ENDL; - } - } - } + // get out of the channel entirely + leaveAudioSession(); } else { - if (LLViewerParcelMgr::getInstance()->allowAgentVoice()) - { - LL_WARNS("Voice") << "No voice credentials" << LL_ENDL; - } - else - { - LL_DEBUGS("Voice") << "No voice credentials" << LL_ENDL; - } + sendPositionAndVolumeUpdate(true); } - - // set the spatial channel. If no voice credentials or uri are - // available, then we simply drop out of voice spatially. - return !setSpatialChannel(uri, ""); } -bool LLWebRTCVoiceClient::addAndJoinSession(const sessionStatePtr_t &nextSession) +void LLWebRTCVoiceClient::sendPositionAndVolumeUpdate(bool force) { - mIsJoiningSession = true; + Json::FastWriter writer; + std::string spatial_data; + std::string volume_data; - sessionStatePtr_t oldSession = mAudioSession; - - LL_INFOS("Voice") << "Adding or joining voice session " << nextSession->mHandle << LL_ENDL; + F32 audio_level = 0.0; + uint32_t uint_audio_level = 0.0; - mAudioSession = nextSession; - mAudioSessionChanged = true; - if (!mAudioSession || !mAudioSession->mReconnect) + if (!mMuteMic) { - mNextAudioSession.reset(); + audio_level = (F32) mWebRTCDeviceInterface->getPeerAudioLevel(); + uint_audio_level = (uint32_t) (audio_level * 128); + } - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINING); + if (mSpatialCoordsDirty || force) + { + Json::Value spatial = Json::objectValue; + LLVector3d earPosition; + LLQuaternion earRot; + switch (mEarLocation) + { + case earLocCamera: + default: + earPosition = mCameraPosition; + earRot = mCameraRot; + break; - llcoro::suspend(); + case earLocAvatar: + earPosition = mAvatarPosition; + earRot = mAvatarRot; + break; - if (sShuttingDown) - { - return false; - } + case earLocMixed: + earPosition = mAvatarPosition; + earRot = mCameraRot; + break; + } - LLSD result; + spatial["sp"] = Json::objectValue; + spatial["sp"]["x"] = (int) (mAvatarPosition[0] * 100); + spatial["sp"]["y"] = (int) (mAvatarPosition[1] * 100); + spatial["sp"]["z"] = (int) (mAvatarPosition[2] * 100); + spatial["sh"] = Json::objectValue; + spatial["sh"]["x"] = (int) (mAvatarRot[0] * 100); + spatial["sh"]["y"] = (int) (mAvatarRot[1] * 100); + spatial["sh"]["z"] = (int) (mAvatarRot[2] * 100); + spatial["sh"]["w"] = (int) (mAvatarRot[3] * 100); + + spatial["lp"] = Json::objectValue; + spatial["lp"]["x"] = (int) (earPosition[0] * 100); + spatial["lp"]["y"] = (int) (earPosition[1] * 100); + spatial["lp"]["z"] = (int) (earPosition[2] * 100); + spatial["lh"] = Json::objectValue; + spatial["lh"]["x"] = (int) (earRot[0] * 100); + spatial["lh"]["y"] = (int) (earRot[1] * 100); + spatial["lh"]["z"] = (int) (earRot[2] * 100); + spatial["lh"]["w"] = (int) (earRot[3] * 100); - if (mSpatialJoiningNum == MAX_NORMAL_JOINING_SPATIAL_NUM) - { - // Notify observers to let them know there is problem with voice - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED); - LL_WARNS() << "There seems to be problem with connection to voice server. Disabling voice chat abilities." << LL_ENDL; + mSpatialCoordsDirty = false; + if (force || (uint_audio_level != mAudioLevel)) + { + spatial["p"] = uint_audio_level; + } + spatial_data = writer.write(spatial); } - - // Increase mSpatialJoiningNum only for spatial sessions- it's normal to reach this case for - // example for p2p many times while waiting for response, so it can't be used to detect errors - if (mAudioSession && mAudioSession->mIsSpatial) + if (force || (uint_audio_level != mAudioLevel)) { - mSpatialJoiningNum++; + Json::Value volume = Json::objectValue; + volume["p"] = uint_audio_level; + volume_data = writer.write(volume); } + mAudioLevel = uint_audio_level; - if (!mVoiceEnabled && mIsInitialized) - { - LL_DEBUGS("Voice") << "Voice no longer enabled. Exiting" - << " enabled " << mVoiceEnabled - << " initialized " << mIsInitialized - << LL_ENDL; - mIsJoiningSession = false; - // User bailed out during connect -- jump straight to teardown. - terminateAudioSession(true); - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED); - return false; - } - else if (mSessionTerminateRequested) + sessionState::for_each(boost::bind(predSendData, _1, spatial_data, volume_data)); +} + +void LLWebRTCVoiceClient::updateOwnVolume() { + F32 audio_level = 0.0; + if (!mMuteMic) { - LL_DEBUGS("Voice") << "Terminate requested" << LL_ENDL; - if (mAudioSession && !mAudioSession->mHandle.empty()) - { - // Only allow direct exits from this state in p2p calls (for cancelling an invite). - // Terminating a half-connected session on other types of calls seems to break something in the WebRTC gateway. - if (mAudioSession->mIsP2P) - { - terminateAudioSession(true); - mIsJoiningSession = false; - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); - return false; - } - } + audio_level = (F32) mWebRTCDeviceInterface->getPeerAudioLevel(); } - LLSD timeoutResult(LLSDMap("session", "timeout")); - - // We are about to start a whole new session. Anything that MIGHT still be in our - // maildrop is going to be stale and cause us much wailing and gnashing of teeth. - // Just flush it all out and start new. - mWebRTCPump.discard(); - - // add 'self' participant. - addParticipantByID(gAgent.getID()); - // tell peers that this participant has joined. + sessionState::for_each(boost::bind(predUpdateOwnVolume, _1, audio_level)); +} - if (mWebRTCDataInterface) +void LLWebRTCVoiceClient::predUpdateOwnVolume(const LLWebRTCVoiceClient::sessionStatePtr_t &session, F32 audio_level) +{ + participantStatePtr_t participant = session->findParticipant(gAgentID.asString()); + if (participant) { - Json::FastWriter writer; - Json::Value root = getPositionAndVolumeUpdateJson(true); - root["j"] = true; - std::string json_data = writer.write(root); - mWebRTCDataInterface->sendData(json_data, false); + participant->mPower = audio_level; + participant->mIsSpeaking = audio_level > SPEAKING_AUDIO_LEVEL; } - - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINED); - - return true; } -bool LLWebRTCVoiceClient::terminateAudioSession(bool wait) +void LLWebRTCVoiceClient::predSendData(const LLWebRTCVoiceClient::sessionStatePtr_t& session, const std::string& spatial_data, const std::string& volume_data) { - - if (mAudioSession) + if (session->mIsSpatial && !spatial_data.empty()) { - LL_INFOS("Voice") << "terminateAudioSession(" << wait << ") Terminating current voice session " << mAudioSession->mHandle << LL_ENDL; - - if (mIsLoggedIn) - { - if (!mAudioSession->mHandle.empty()) - { - if (wait) - { - LLSD result; - do - { - LLSD timeoutResult(LLSDMap("session", "timeout")); - - result = llcoro::suspendUntilEventOnWithTimeout(mWebRTCPump, LOGOUT_ATTEMPT_TIMEOUT, timeoutResult); - - if (sShuttingDown) - { - return false; - } - - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - if (result.has("session")) - { - if (result.has("handle")) - { - if (result["handle"] != mAudioSession->mHandle) - { - LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while waiting for \"" << mAudioSession->mHandle << "\"." << LL_ENDL; - continue; - } - } - - std::string message = result["session"].asString(); - if (message == "removed" || message == "timeout") - break; - } - } while (true); - - } - } - else - { - LL_WARNS("Voice") << "called with no session handle" << LL_ENDL; - } - } - else - { - LL_WARNS("Voice") << "Session " << mAudioSession->mHandle << " already terminated by logout." << LL_ENDL; - } - - sessionStatePtr_t oldSession = mAudioSession; - - mAudioSession.reset(); - // We just notified status observers about this change. Don't do it again. - mAudioSessionChanged = false; - - // The old session may now need to be deleted. - reapSession(oldSession); + session->sendData(spatial_data); } - else + else if (!volume_data.empty()) { - LL_WARNS("Voice") << "terminateAudioSession(" << wait << ") with NULL mAudioSession" << LL_ENDL; + session->sendData(volume_data); } - - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); - - // Always reset the terminate request flag when we get here. - // Some slower PCs have a race condition where they can switch to an incoming P2P call faster than the state machine leaves - // the region chat. - mSessionTerminateRequested = false; - - bool status=((mVoiceEnabled || !mIsInitialized) && !mRelogRequested && !sShuttingDown); - LL_DEBUGS("Voice") << "exiting" - << " VoiceEnabled " << mVoiceEnabled - << " IsInitialized " << mIsInitialized - << " RelogRequested " << mRelogRequested - << " ShuttingDown " << (sShuttingDown ? "TRUE" : "FALSE") - << " returning " << status - << LL_ENDL; - return status; } - -typedef enum e_voice_wait_for_channel_state +void LLWebRTCVoiceClient::sessionState::sendData(const std::string &data) { - VOICE_CHANNEL_STATE_LOGIN = 0, // entry point - VOICE_CHANNEL_STATE_START_CHANNEL_PROCESSING, - VOICE_CHANNEL_STATE_PROCESS_CHANNEL, - VOICE_CHANNEL_STATE_NEXT_CHANNEL_DELAY, - VOICE_CHANNEL_STATE_NEXT_CHANNEL_CHECK -} EVoiceWaitForChannelState; + for (auto& connection : mWebRTCConnections) + { + connection.second->sendData(data); + } +} -bool LLWebRTCVoiceClient::waitForChannel() +void LLWebRTCVoiceClient::sendLocalAudioUpdates() { - LL_INFOS("Voice") << "Waiting for channel" << LL_ENDL; - - EVoiceWaitForChannelState state = VOICE_CHANNEL_STATE_LOGIN; +} - do - { - if (sShuttingDown) - { - // terminate() forcefully disconects voice, no need for cleanup - return false; - } - if (getVoiceControlState() == VOICE_STATE_SESSION_RETRY) - { - mIsProcessingChannels = false; - return true; - } +void LLWebRTCVoiceClient::joinedAudioSession(const sessionStatePtr_t &session) +{ + LL_DEBUGS("Voice") << "Joined Audio Session" << LL_ENDL; + if(mAudioSession != session) + { + sessionStatePtr_t oldSession = mAudioSession; - processIceUpdates(); - switch (state) - { - case VOICE_CHANNEL_STATE_LOGIN: - if (!loginToWebRTC()) - { - return false; - } - state = VOICE_CHANNEL_STATE_START_CHANNEL_PROCESSING; - break; + mAudioSession = session; + mAudioSessionChanged = true; - case VOICE_CHANNEL_STATE_START_CHANNEL_PROCESSING: - mIsProcessingChannels = true; - llcoro::suspend(); - state = VOICE_CHANNEL_STATE_PROCESS_CHANNEL; - break; - - case VOICE_CHANNEL_STATE_PROCESS_CHANNEL: - if (mTuningMode) - { - performMicTuning(); - } - else if (checkParcelChanged() || (mNextAudioSession == NULL)) - { - // the parcel is changed, or we have no pending audio sessions, - // so try to request the parcel voice info - // if we have the cap, we move to the appropriate state - requestParcelVoiceInfo(); //suspends for http reply - } - else if (sessionNeedsRelog(mNextAudioSession)) - { - LL_INFOS("Voice") << "Session requesting reprovision and login." << LL_ENDL; - requestRelog(); - break; - } - else if (mNextAudioSession) - { - sessionStatePtr_t joinSession = mNextAudioSession; - mNextAudioSession.reset(); - if (!runSession(joinSession)) //suspends - { - LL_DEBUGS("Voice") << "runSession returned false; leaving inner loop" << LL_ENDL; - break; - } - else - { - LL_DEBUGS("Voice") - << "runSession returned true to inner loop" - << " RelogRequested=" << mRelogRequested - << " VoiceEnabled=" << mVoiceEnabled - << LL_ENDL; - } - } - - state = VOICE_CHANNEL_STATE_NEXT_CHANNEL_DELAY; - break; - - case VOICE_CHANNEL_STATE_NEXT_CHANNEL_DELAY: - if (!mNextAudioSession) - { - llcoro::suspendUntilTimeout(1.0); - } - state = VOICE_CHANNEL_STATE_NEXT_CHANNEL_CHECK; - break; - - case VOICE_CHANNEL_STATE_NEXT_CHANNEL_CHECK: - if (mVoiceEnabled && !mRelogRequested) - { - state = VOICE_CHANNEL_STATE_START_CHANNEL_PROCESSING; - break; - } - else - { - mIsProcessingChannels = false; - LL_DEBUGS("Voice") - << "leaving inner waitForChannel loop" - << " RelogRequested=" << mRelogRequested - << " VoiceEnabled=" << mVoiceEnabled - << LL_ENDL; - return !sShuttingDown; - } - } - } while (true); -} - -bool LLWebRTCVoiceClient::runSession(const sessionStatePtr_t &session) -{ - LL_INFOS("Voice") << "running new voice session " << session->mHandle << LL_ENDL; - - bool joined_session = addAndJoinSession(session); - - if (sShuttingDown) - { - return false; - } - - if (!joined_session) - { - notifyStatusObservers(LLVoiceClientStatusObserver::ERROR_UNKNOWN); - - if (mSessionTerminateRequested) - { - LL_DEBUGS("Voice") << "runSession terminate requested " << LL_ENDL; - terminateAudioSession(true); - } - // if a relog has been requested then addAndJoineSession - // failed in a spectacular way and we need to back out. - // If this is not the case then we were simply trying to - // make a call and the other party rejected it. - return !mRelogRequested; - } - - notifyParticipantObservers(); - - LLSD timeoutEvent(LLSDMap("timeout", LLSD::Boolean(true))); - - mIsInChannel = true; - mMuteMicDirty = true; - - while (!sShuttingDown - && mVoiceEnabled - && !mSessionTerminateRequested - && !mTuningMode) - { - - if (sShuttingDown) - { - return false; - } - if (getVoiceControlState() == VOICE_STATE_SESSION_RETRY) - { - break; - } - - if (mSessionTerminateRequested) - { - break; - } - - if (mAudioSession && mAudioSession->mParticipantsChanged) - { - mAudioSession->mParticipantsChanged = false; - notifyParticipantObservers(); - } - - if (!inSpatialChannel()) - { - // When in a non-spatial channel, never send positional updates. - mSpatialCoordsDirty = false; - } - else - { - updatePosition(); - - if (checkParcelChanged()) - { - // *RIDER: I think I can just return here if the parcel has changed - // and grab the new voice channel from the outside loop. - // - // if the parcel has changed, attempted to request the - // cap for the parcel voice info. If we can't request it - // then we don't have the cap URL so we do nothing and will - // recheck next time around - if (requestParcelVoiceInfo()) // suspends - { // The parcel voice URI has changed.. break out and reconnect. - break; - } - - if (sShuttingDown) - { - return false; - } - } - // Do the calculation that enforces the listener<->speaker tether (and also updates the real camera position) - enforceTether(); - } - sendPositionAndVolumeUpdate(); - - // send any requests to adjust mic and speaker settings if they have changed - sendLocalAudioUpdates(); - - mIsInitialized = true; - LLSD result = llcoro::suspendUntilEventOnWithTimeout(mWebRTCPump, UPDATE_THROTTLE_SECONDS, timeoutEvent); - - if (sShuttingDown) - { - return false; - } - - if (!result.has("timeout")) // logging the timeout event spams the log - { - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - } - if (result.has("session")) - { - if (result.has("handle")) - { - if (!mAudioSession) - { - LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while session is not initiated." << LL_ENDL; - continue; - } - if (result["handle"] != mAudioSession->mHandle) - { - LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while waiting for \"" << mAudioSession->mHandle << "\"." << LL_ENDL; - continue; - } - } - - std::string message = result["session"]; - - if (message == "removed") - { - LL_DEBUGS("Voice") << "session removed" << LL_ENDL; - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); - break; - } - } - else if (result.has("login")) - { - std::string message = result["login"]; - if (message == "account_logout") - { - LL_DEBUGS("Voice") << "logged out" << LL_ENDL; - mIsLoggedIn = false; - mRelogRequested = true; - break; - } - } - } - - if (sShuttingDown) - { - return false; - } - - mIsInChannel = false; - LL_DEBUGS("Voice") << "terminating at end of runSession" << LL_ENDL; - terminateAudioSession(true); - - return true; -} - -bool LLWebRTCVoiceClient::performMicTuning() -{ - LL_INFOS("Voice") << "Entering voice tuning mode." << LL_ENDL; - - mIsInTuningMode = false; - - //--------------------------------------------------------------------- - return true; -} - -//========================================================================= - -void LLWebRTCVoiceClient::closeSocket(void) -{ - mSocket.reset(); - sConnected = false; - mConnectorEstablished = false; - mAccountLoggedIn = false; -} - -void LLWebRTCVoiceClient::logout() -{ - // Ensure that we'll re-request provisioning before logging in again - mAccountPassword.clear(); - mAccountLoggedIn = false; -} - -void LLWebRTCVoiceClient::sessionTerminate() -{ - mSessionTerminateRequested = true; -} - -void LLWebRTCVoiceClient::requestRelog() -{ - mSessionTerminateRequested = true; - mRelogRequested = true; -} - - -void LLWebRTCVoiceClient::leaveAudioSession() -{ - if(mAudioSession) - { - LL_DEBUGS("Voice") << "leaving session: " << mAudioSession->mSIPURI << LL_ENDL; - - if(mAudioSession->mHandle.empty()) - { - LL_WARNS("Voice") << "called with no session handle" << LL_ENDL; - } - } - else - { - LL_WARNS("Voice") << "called with no active session" << LL_ENDL; - } - sessionTerminate(); -} - -void LLWebRTCVoiceClient::clearCaptureDevices() -{ - LL_DEBUGS("Voice") << "called" << LL_ENDL; - mCaptureDevices.clear(); -} - -void LLWebRTCVoiceClient::addCaptureDevice(const LLVoiceDevice& device) -{ - LL_DEBUGS("Voice") << "display: '" << device.display_name << "' device: '" << device.full_name << "'" << LL_ENDL; - mCaptureDevices.push_back(device); -} - -LLVoiceDeviceList& LLWebRTCVoiceClient::getCaptureDevices() -{ - return mCaptureDevices; -} - -void LLWebRTCVoiceClient::setCaptureDevice(const std::string& name) -{ - bool inTuningMode = mIsInTuningMode; - if (inTuningMode) - { - tuningStop(); - } - mWebRTCDeviceInterface->setCaptureDevice(name); - if (inTuningMode) - { - tuningStart(); - } -} -void LLWebRTCVoiceClient::setDevicesListUpdated(bool state) -{ - mDevicesListUpdated = state; -} - -void LLWebRTCVoiceClient::OnDevicesChanged(const llwebrtc::LLWebRTCVoiceDeviceList &render_devices, - const llwebrtc::LLWebRTCVoiceDeviceList &capture_devices) -{ - clearRenderDevices(); - for (auto &device : render_devices) - { - addRenderDevice(LLVoiceDevice(device.display_name, device.id)); - } - clearCaptureDevices(); - for (auto &device : capture_devices) - { - addCaptureDevice(LLVoiceDevice(device.display_name, device.id)); - } - setDevicesListUpdated(true); -} - -void LLWebRTCVoiceClient::clearRenderDevices() -{ - LL_DEBUGS("Voice") << "called" << LL_ENDL; - mRenderDevices.clear(); -} - -void LLWebRTCVoiceClient::addRenderDevice(const LLVoiceDevice& device) -{ - LL_DEBUGS("Voice") << "display: '" << device.display_name << "' device: '" << device.full_name << "'" << LL_ENDL; - mRenderDevices.push_back(device); - -} - -LLVoiceDeviceList& LLWebRTCVoiceClient::getRenderDevices() -{ - return mRenderDevices; -} - -void LLWebRTCVoiceClient::setRenderDevice(const std::string& name) -{ - mWebRTCDeviceInterface->setRenderDevice(name); -} - -void LLWebRTCVoiceClient::tuningStart() -{ - if (!mIsInTuningMode) - { - mWebRTCDeviceInterface->setTuningMode(true); - mIsInTuningMode = true; - } -} - -void LLWebRTCVoiceClient::tuningStop() -{ - if (mIsInTuningMode) - { - mWebRTCDeviceInterface->setTuningMode(false); - mIsInTuningMode = false; - } -} - -bool LLWebRTCVoiceClient::inTuningMode() -{ - return mIsInTuningMode; -} - -void LLWebRTCVoiceClient::tuningSetMicVolume(float volume) -{ - int scaled_volume = scale_mic_volume(volume); - - if(scaled_volume != mTuningMicVolume) - { - mTuningMicVolume = scaled_volume; - mTuningMicVolumeDirty = true; - } -} - -void LLWebRTCVoiceClient::tuningSetSpeakerVolume(float volume) -{ - - if (volume != mTuningSpeakerVolume) - { - mTuningSpeakerVolume = volume; - mTuningSpeakerVolumeDirty = true; - } -} - -float LLWebRTCVoiceClient::tuningGetEnergy(void) -{ - return mWebRTCDeviceInterface->getTuningAudioLevel(); -} - -bool LLWebRTCVoiceClient::deviceSettingsAvailable() -{ - bool result = true; - - if(mRenderDevices.empty() || mCaptureDevices.empty()) - result = false; - - return result; -} -bool LLWebRTCVoiceClient::deviceSettingsUpdated() -{ - bool updated = mDevicesListUpdated; - mDevicesListUpdated = false; - return updated; -} - -void LLWebRTCVoiceClient::refreshDeviceLists(bool clearCurrentList) -{ - if(clearCurrentList) - { - clearCaptureDevices(); - clearRenderDevices(); - } - mWebRTCDeviceInterface->refreshDevices(); -} - -void LLWebRTCVoiceClient::daemonDied() -{ - // The daemon died, so the connection is gone. Reset everything and start over. - LL_WARNS("Voice") << "Connection to WebRTC daemon lost. Resetting state."<< LL_ENDL; - - //TODO: Try to relaunch the daemon -} - -void LLWebRTCVoiceClient::giveUp() -{ - // All has failed. Clean up and stop trying. - LL_WARNS("Voice") << "Terminating Voice Service" << LL_ENDL; - closeSocket(); - cleanUp(); -} - -void LLWebRTCVoiceClient::setHidden(bool hidden) -{ - mHidden = hidden; - - if (mHidden && inSpatialChannel()) - { - // get out of the channel entirely - leaveAudioSession(); - } - else - { - sendPositionAndVolumeUpdate(); - } -} - -Json::Value LLWebRTCVoiceClient::getPositionAndVolumeUpdateJson(bool force) -{ - Json::Value root = Json::objectValue; - - if ((mSpatialCoordsDirty || force) && inSpatialChannel()) - { - LLVector3d earPosition; - LLQuaternion earRot; - switch (mEarLocation) - { - case earLocCamera: - default: - earPosition = mCameraPosition; - earRot = mCameraRot; - break; - - case earLocAvatar: - earPosition = mAvatarPosition; - earRot = mAvatarRot; - break; - - case earLocMixed: - earPosition = mAvatarPosition; - earRot = mCameraRot; - break; - } - - root["sp"] = Json::objectValue; - root["sp"]["x"] = (int) (mAvatarPosition[0] * 100); - root["sp"]["y"] = (int) (mAvatarPosition[1] * 100); - root["sp"]["z"] = (int) (mAvatarPosition[2] * 100); - root["sh"] = Json::objectValue; - root["sh"]["x"] = (int) (mAvatarRot[0] * 100); - root["sh"]["y"] = (int) (mAvatarRot[1] * 100); - root["sh"]["z"] = (int) (mAvatarRot[2] * 100); - root["sh"]["w"] = (int) (mAvatarRot[3] * 100); - - root["lp"] = Json::objectValue; - root["lp"]["x"] = (int) (earPosition[0] * 100); - root["lp"]["y"] = (int) (earPosition[1] * 100); - root["lp"]["z"] = (int) (earPosition[2] * 100); - root["lh"] = Json::objectValue; - root["lh"]["x"] = (int) (earRot[0] * 100); - root["lh"]["y"] = (int) (earRot[1] * 100); - root["lh"]["z"] = (int) (earRot[2] * 100); - root["lh"]["w"] = (int) (earRot[3] * 100); - - mSpatialCoordsDirty = false; - } - - F32 audio_level = 0.0; - - if (!mMuteMic) - { - audio_level = (F32) mWebRTCDeviceInterface->getPeerAudioLevel(); - } - uint32_t uint_audio_level = mMuteMic ? 0 : (uint32_t) (audio_level * 128); - if (force || (uint_audio_level != mAudioLevel)) - { - root["p"] = uint_audio_level; - mAudioLevel = uint_audio_level; - participantStatePtr_t participant = findParticipantByID(gAgentID); - if (participant) - { - participant->mPower = audio_level; - participant->mIsSpeaking = participant->mPower > SPEAKING_AUDIO_LEVEL; - } - } - return root; -} - -void LLWebRTCVoiceClient::sendPositionAndVolumeUpdate() -{ - - - if (mWebRTCDataInterface && mWebRTCAudioInterface) - { - Json::Value root = getPositionAndVolumeUpdateJson(false); - - if (root.size() > 0) - { - - Json::FastWriter writer; - std::string json_data = writer.write(root); - - mWebRTCDataInterface->sendData(json_data, false); - } - } - - - if(mAudioSession && (mAudioSession->mVolumeDirty || mAudioSession->mMuteDirty)) - { - participantMap::iterator iter = mAudioSession->mParticipantsByURI.begin(); - - mAudioSession->mVolumeDirty = false; - mAudioSession->mMuteDirty = false; - - for(; iter != mAudioSession->mParticipantsByURI.end(); iter++) - { - participantStatePtr_t p(iter->second); - - if(p->mVolumeDirty) - { - // Can't set volume/mute for yourself - if(!p->mIsSelf) - { - // scale from the range 0.0-1.0 to WebRTC volume in the range 0-100 - S32 volume = ll_round(p->mVolume / VOLUME_SCALE_WEBRTC); - bool mute = p->mOnMuteList; - - if(mute) - { - // SetParticipantMuteForMe doesn't work in p2p sessions. - // If we want the user to be muted, set their volume to 0 as well. - // This isn't perfect, but it will at least reduce their volume to a minimum. - volume = 0; - // Mark the current volume level as set to prevent incoming events - // changing it to 0, so that we can return to it when unmuting. - p->mVolumeSet = true; - } - - if(volume == 0) - { - mute = true; - } - - LL_DEBUGS("Voice") << "Setting volume/mute for avatar " << p->mAvatarID << " to " << volume << (mute?"/true":"/false") << LL_ENDL; - } - - p->mVolumeDirty = false; - } - } - } -} - -void LLWebRTCVoiceClient::sendLocalAudioUpdates() -{ -} - -///////////////////////////// -// WebRTC Signaling Handlers -void LLWebRTCVoiceClient::OnIceGatheringState(llwebrtc::LLWebRTCSignalingObserver::IceGatheringState state) -{ - LL_INFOS("Voice") << "Ice Gathering voice account. " << state << LL_ENDL; - - switch (state) - { - case llwebrtc::LLWebRTCSignalingObserver::IceGatheringState::ICE_GATHERING_COMPLETE: - { - LLMutexLock lock(&mVoiceStateMutex); - mIceCompleted = true; - break; - } - case llwebrtc::LLWebRTCSignalingObserver::IceGatheringState::ICE_GATHERING_NEW: - { - LLMutexLock lock(&mVoiceStateMutex); - mIceCompleted = false; - } - default: - break; - } -} - -void LLWebRTCVoiceClient::OnIceCandidate(const llwebrtc::LLWebRTCIceCandidate &candidate) -{ - LLMutexLock lock(&mVoiceStateMutex); - mIceCandidates.push_back(candidate); -} - -void LLWebRTCVoiceClient::processIceUpdates() -{ - LL_INFOS("Voice") << "Ice Gathering voice account." << LL_ENDL; - while ((!gAgent.getRegion() || !gAgent.getRegion()->capabilitiesReceived()) && !sShuttingDown) - { - LL_DEBUGS("Voice") << "no capabilities for voice provisioning; waiting " << LL_ENDL; - // *TODO* Pump a message for wake up. - llcoro::suspend(); - } - - if (sShuttingDown) - { - return; - } - - std::string url = gAgent.getRegionCapability("VoiceSignalingRequest"); - - LL_DEBUGS("Voice") << "region ready to complete voice signaling; url=" << url << LL_ENDL; - - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("voiceAccountProvision", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpOptions::ptr_t httpOpts = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); - - bool iceCompleted = false; - LLSD body; - { - LLMutexLock lock(&mVoiceStateMutex); - - if (!mTrickling) - { - if (mIceCandidates.size()) - { - LLSD candidates = LLSD::emptyArray(); - for (auto &ice_candidate : mIceCandidates) - { - LLSD body_candidate; - body_candidate["sdpMid"] = ice_candidate.sdp_mid; - body_candidate["sdpMLineIndex"] = ice_candidate.mline_index; - body_candidate["candidate"] = ice_candidate.candidate; - candidates.append(body_candidate); - } - body["candidates"] = candidates; - mIceCandidates.clear(); - } - else if (mIceCompleted) - { - LLSD body_candidate; - body_candidate["completed"] = true; - body["candidate"] = body_candidate; - iceCompleted = mIceCompleted; - mIceCompleted = false; - } - else - { - return; - } - LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpPost( - url, - LLCore::HttpRequest::DEFAULT_POLICY_ID, - body, - boost::bind(&LLWebRTCVoiceClient::onIceUpdateComplete, this, iceCompleted, _1), - boost::bind(&LLWebRTCVoiceClient::onIceUpdateError, this, 3, url, body, iceCompleted, _1)); - mTrickling = true; - } - } -} - -void LLWebRTCVoiceClient::onIceUpdateComplete(bool ice_completed, const LLSD& result) -{ mTrickling = false; } - -void LLWebRTCVoiceClient::onIceUpdateError(int retries, std::string url, LLSD body, bool ice_completed, const LLSD& result) -{ - if (sShuttingDown) - { - return; - } - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("voiceAccountProvision", httpPolicy)); - - if (retries >= 0) - { - LL_WARNS("Voice") << "Unable to complete ice trickling voice account, retrying. " << result << LL_ENDL; - LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpPost(url, - LLCore::HttpRequest::DEFAULT_POLICY_ID, - body, - boost::bind(&LLWebRTCVoiceClient::onIceUpdateComplete, this, ice_completed, _1), - boost::bind(&LLWebRTCVoiceClient::onIceUpdateError, this, retries - 1, url, body, ice_completed, _1)); - } - else - { - LL_WARNS("Voice") << "Unable to complete ice trickling voice account, restarting connection. " << result << LL_ENDL; - setVoiceControlStateUnless(VOICE_STATE_SESSION_RETRY); - mTrickling = false; - } -} - -void LLWebRTCVoiceClient::OnOfferAvailable(const std::string &sdp) -{ - LL_INFOS("Voice") << "On Offer Available." << LL_ENDL; - LLMutexLock lock(&mVoiceStateMutex); - mChannelSDP = sdp; -} - -void LLWebRTCVoiceClient::OnAudioEstablished(llwebrtc::LLWebRTCAudioInterface * audio_interface) -{ - LL_INFOS("Voice") << "On AudioEstablished." << LL_ENDL; - mWebRTCAudioInterface = audio_interface; - mWebRTCAudioInterface->setAudioObserver(this); - float speaker_volume = 0; - { - LLMutexLock lock(&mVoiceStateMutex); - speaker_volume = mSpeakerVolume; - } - mWebRTCDeviceInterface->setSpeakerVolume(mSpeakerVolume); - setVoiceControlStateUnless(VOICE_STATE_SESSION_ESTABLISHED, VOICE_STATE_SESSION_RETRY); -} - -void LLWebRTCVoiceClient::OnDataReceived(const std::string& data, bool binary) -{ - // incoming data will be a json structure (if it's not binary.) We may pack - // binary for size reasons. Most of the keys in the json objects are - // single or double characters for size reasons. - // The primary element is: - // An object where each key is an agent id. (in the future, we may allow - // integer indices into an agentid list, populated on join commands. For size. - // Each key will point to a json object with keys identifying what's updated. - // 'p' - audio source power (level/volume) (int8 as int) - // 'j' - join - object of join data (TBD) (true for now) - // 'l' - boolean, always true if exists. - - if (binary) - { - LL_WARNS("Voice") << "Binary data received from data channel." << LL_ENDL; - return; - } - - Json::Reader reader; - Json::Value voice_data; - if (reader.parse(data, voice_data, false)) // don't collect comments - { - if (!voice_data.isObject()) - { - LL_WARNS("Voice") << "Expected object from data channel:" << data << LL_ENDL; - return; - } - bool new_participant = false; - for (auto &participant_id : voice_data.getMemberNames()) - { - LLUUID agent_id(participant_id); - if (agent_id.isNull()) - { - LL_WARNS("Voice") << "Bad participant ID from data channel (" << participant_id << "):" << data << LL_ENDL; - continue; - } - participantStatePtr_t participant = findParticipantByID(agent_id); - bool joined = voice_data[participant_id].get("j", Json::Value(false)).asBool(); - new_participant |= joined; - if (!participant && joined) - { - participant = addParticipantByID(agent_id); - } - if (participant) - { - if(voice_data[participant_id].get("l", Json::Value(false)).asBool()) - { - removeParticipantByID(agent_id); - } - F32 energyRMS = (F32) (voice_data[participant_id].get("p", Json::Value(participant->mPower)).asInt()) / 128; - // convert to decibles - participant->mPower = energyRMS; - /* WebRTC appears to have deprecated VAD, but it's still in the Audio Processing Module so maybe we - can use it at some point when we actually process frames. */ - participant->mIsSpeaking = participant->mPower > SPEAKING_AUDIO_LEVEL; - } - } - } -} - -void LLWebRTCVoiceClient::OnDataChannelReady(llwebrtc::LLWebRTCDataInterface *data_interface) -{ - mWebRTCDataInterface = data_interface; - mWebRTCDataInterface->setDataObserver(this); -} - - -void LLWebRTCVoiceClient::OnRenegotiationNeeded() -{ - LL_INFOS("Voice") << "On Renegotiation Needed." << LL_ENDL; - mRelogRequested = TRUE; - mIsProcessingChannels = FALSE; - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGIN_RETRY); - setVoiceControlStateUnless(VOICE_STATE_SESSION_RETRY); -} - -void LLWebRTCVoiceClient::joinedAudioSession(const sessionStatePtr_t &session) -{ - LL_DEBUGS("Voice") << "Joined Audio Session" << LL_ENDL; - if(mAudioSession != session) - { - sessionStatePtr_t oldSession = mAudioSession; - - mAudioSession = session; - mAudioSessionChanged = true; - - // The old session may now need to be deleted. - reapSession(oldSession); - } - - // This is the session we're joining. - if(mIsJoiningSession) - { - LLSD WebRTCevent(LLSDMap("handle", LLSD::String(session->mHandle)) - ("session", "joined")); + // The old session may now need to be deleted. + reapSession(oldSession); + } + + // This is the session we're joining. + if(mIsJoiningSession) + { + LLSD WebRTCevent(LLSDMap("channel", session->mChannelID) + ("session", "joined")); mWebRTCPump.post(WebRTCevent); if(!session->mIsChannel) { // this is a p2p session. Make sure the other end is added as a participant. - participantStatePtr_t participant(session->addParticipant(LLUUID(session->mSIPURI))); + participantStatePtr_t participant(session->addParticipant(LLUUID(session->mChannelID))); if(participant) { if(participant->mAvatarIDValid) @@ -2076,8 +897,7 @@ void LLWebRTCVoiceClient::joinedAudioSession(const sessionStatePtr_t &session) } // TODO: Question: Do we need to set up mAvatarID/mAvatarIDValid here? - LL_INFOS("Voice") << "added caller as participant \"" << participant->mAccountName - << "\" (" << participant->mAvatarID << ")"<< LL_ENDL; + LL_INFOS("Voice") << "added caller as participant (" << participant->mAvatarID << ")"<< LL_ENDL; } } } @@ -2086,20 +906,19 @@ void LLWebRTCVoiceClient::joinedAudioSession(const sessionStatePtr_t &session) void LLWebRTCVoiceClient::reapSession(const sessionStatePtr_t &session) { if(session) - { - + { if(session == mAudioSession) { - LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (it's the current session)" << LL_ENDL; + LL_DEBUGS("Voice") << "NOT deleting session " << session->mChannelID << " (it's the current session)" << LL_ENDL; } else if(session == mNextAudioSession) { - LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (it's the next session)" << LL_ENDL; + LL_DEBUGS("Voice") << "NOT deleting session " << session->mChannelID << " (it's the next session)" << LL_ENDL; } else { // We don't have a reason to keep tracking this session, so just delete it. - LL_DEBUGS("Voice") << "deleting session " << session->mSIPURI << LL_ENDL; + LL_DEBUGS("Voice") << "deleting session " << session->mChannelID << LL_ENDL; deleteSession(session); } } @@ -2109,35 +928,12 @@ void LLWebRTCVoiceClient::reapSession(const sessionStatePtr_t &session) } } -// Returns true if the session seems to indicate we've moved to a region on a different voice server -bool LLWebRTCVoiceClient::sessionNeedsRelog(const sessionStatePtr_t &session) -{ - bool result = false; - - if(session) - { - // Only make this check for spatial channels (so it won't happen for group or p2p calls) - if(session->mIsSpatial) - { - std::string::size_type atsign; - - atsign = session->mSIPURI.find("@"); - - if(atsign != std::string::npos) - { - std::string urihost = session->mSIPURI.substr(atsign + 1); - } - } - } - - return result; -} void LLWebRTCVoiceClient::leftAudioSession(const sessionStatePtr_t &session) { if (mAudioSession == session) { - LLSD WebRTCevent(LLSDMap("handle", LLSD::String(session->mHandle)) + LLSD WebRTCevent(LLSDMap("channel", session->mChannelID) ("session", "removed")); mWebRTCPump.post(WebRTCevent); @@ -2293,17 +1089,20 @@ void LLWebRTCVoiceClient::sessionState::removeAllParticipants() /*static*/ void LLWebRTCVoiceClient::sessionState::VerifySessions() { - std::set::iterator it = mSession.begin(); - while (it != mSession.end()) + return; + /* + std::map::iterator it = mSessions.begin(); + while (it != mSessions.end()) { - if ((*it).expired()) + if ((*it).second.expired()) { LL_WARNS("Voice") << "Expired session found! removing" << LL_ENDL; - it = mSession.erase(it); + it = mSessions.erase(it); } else ++it; } + */ } @@ -2336,16 +1135,6 @@ LLWebRTCVoiceClient::participantStatePtr_t LLWebRTCVoiceClient::sessionState::fi participantMap::iterator iter = mParticipantsByURI.find(uri); - if(iter == mParticipantsByURI.end()) - { - if(!mAlternateSIPURI.empty() && (uri == mAlternateSIPURI)) - { - // This is a p2p session (probably with the SLIM client) with an alternate URI for the other participant. - // Look up the other URI - iter = mParticipantsByURI.find(mSIPURI); - } - } - if(iter != mParticipantsByURI.end()) { result = iter->second; @@ -2367,37 +1156,40 @@ LLWebRTCVoiceClient::participantStatePtr_t LLWebRTCVoiceClient::sessionState::fi return result; } -LLWebRTCVoiceClient::participantStatePtr_t LLWebRTCVoiceClient::findParticipantByID(const LLUUID& id) +LLWebRTCVoiceClient::participantStatePtr_t LLWebRTCVoiceClient::findParticipantByID(const std::string& channelID, const LLUUID& id) { participantStatePtr_t result; + auto& session = sessionState::matchSessionByChannelID(channelID); - if(mAudioSession) + if (session) { - result = mAudioSession->findParticipantByID(id); + result = session->findParticipantByID(id); } return result; } -LLWebRTCVoiceClient::participantStatePtr_t LLWebRTCVoiceClient::addParticipantByID(const LLUUID &id) +LLWebRTCVoiceClient::participantStatePtr_t LLWebRTCVoiceClient::addParticipantByID(const std::string& channelID, const LLUUID &id) { participantStatePtr_t result; - if (mAudioSession) + auto& session = sessionState::matchSessionByChannelID(channelID); + if (session) { - result = mAudioSession->addParticipant(id); + result = session->addParticipant(id); } return result; } -void LLWebRTCVoiceClient::removeParticipantByID(const LLUUID &id) +void LLWebRTCVoiceClient::removeParticipantByID(const std::string &channelID, const LLUUID &id) { participantStatePtr_t result; - if (mAudioSession) + auto& session = sessionState::matchSessionByChannelID(channelID); + if (session) { - participantStatePtr_t participant = mAudioSession->findParticipantByID(id); + participantStatePtr_t participant = session->findParticipantByID(id); if (participant) { - mAudioSession->removeParticipant(participant); + session->removeParticipant(participant); } } } @@ -2437,28 +1229,29 @@ bool LLWebRTCVoiceClient::checkParcelChanged(bool update) } bool LLWebRTCVoiceClient::switchChannel( - std::string uri, + const std::string channelID, bool spatial, bool no_reconnect, bool is_p2p, - std::string hash) + std::string hash, + S32 parcel_local_id) { - bool needsSwitch = !mIsInChannel; - - if (mIsInChannel) + bool needsSwitch = false; + + if (mAudioSession) { if (mSessionTerminateRequested) { // If a terminate has been requested, we need to compare against where the URI we're already headed to. if(mNextAudioSession) { - if(mNextAudioSession->mSIPURI != uri) + if (mNextAudioSession->mChannelID != channelID) needsSwitch = true; } else { // mNextAudioSession is null -- this probably means we're on our way back to spatial. - if(!uri.empty()) + if (!channelID.empty()) { // We do want to process a switch in this case. needsSwitch = true; @@ -2470,14 +1263,14 @@ bool LLWebRTCVoiceClient::switchChannel( // Otherwise, compare against the URI we're in now. if(mAudioSession) { - if(mAudioSession->mSIPURI != uri) + if (mAudioSession->mChannelID != channelID) { needsSwitch = true; } } else { - if(!uri.empty()) + if (!channelID.empty()) { // mAudioSession is null -- it's not clear what case would cause this. // For now, log it as a warning and see if it ever crops up. @@ -2487,10 +1280,24 @@ bool LLWebRTCVoiceClient::switchChannel( } } } + else + { + if (!mNextAudioSession || mNextAudioSession->mChannelID != channelID) + { + needsSwitch = true; + } + } if(needsSwitch) { - if(uri.empty()) + if (mAudioSession) + { + // If we're already in a channel, or if we're joining one, terminate + // so we can rejoin with the new session data. + sessionTerminate(); + mAudioSession->shutdownAllConnections(); + } + if (channelID.empty()) { // Leave any channel we may be in LL_DEBUGS("Voice") << "leaving channel" << LL_ENDL; @@ -2511,20 +1318,13 @@ bool LLWebRTCVoiceClient::switchChannel( } else { - LL_DEBUGS("Voice") << "switching to channel " << uri << LL_ENDL; + LL_DEBUGS("Voice") << "switching to channel " << channelID << LL_ENDL; - mNextAudioSession = addSession(uri); + mNextAudioSession = addSession(channelID, parcel_local_id); mNextAudioSession->mIsSpatial = spatial; mNextAudioSession->mReconnect = !no_reconnect; mNextAudioSession->mIsP2P = is_p2p; } - - if (mIsInChannel) - { - // If we're already in a channel, or if we're joining one, terminate - // so we can rejoin with the new session data. - sessionTerminate(); - } } return needsSwitch; @@ -2534,7 +1334,7 @@ void LLWebRTCVoiceClient::joinSession(const sessionStatePtr_t &session) { mNextAudioSession = session; - if (mIsInChannel) + if (mAudioSession) { // If we're already in a channel, or if we're joining one, terminate // so we can rejoin with the new session data. @@ -2550,14 +1350,13 @@ void LLWebRTCVoiceClient::setNonSpatialChannel( } bool LLWebRTCVoiceClient::setSpatialChannel( - const std::string &uri, const std::string &credentials) + const std::string &uri, const std::string &credentials, S32 parcel_local_id) { - mSpatialSessionURI = uri; - mAreaVoiceDisabled = mSpatialSessionURI.empty(); + mAreaVoiceDisabled = uri.empty(); LL_DEBUGS("Voice") << "got spatial channel uri: \"" << uri << "\"" << LL_ENDL; - if((mIsInChannel && mAudioSession && !(mAudioSession->mIsSpatial)) || (mNextAudioSession && !(mNextAudioSession->mIsSpatial))) + if((mAudioSession && !(mAudioSession->mIsSpatial)) || (mNextAudioSession && !(mNextAudioSession->mIsSpatial))) { // User is in a non-spatial chat or joining a non-spatial chat. Don't switch channels. LL_INFOS("Voice") << "in non-spatial chat, not switching channels" << LL_ENDL; @@ -2565,31 +1364,29 @@ bool LLWebRTCVoiceClient::setSpatialChannel( } else { - return switchChannel(mSpatialSessionURI, true, false, false); + return switchChannel(uri, true, false, false, "", parcel_local_id); } } void LLWebRTCVoiceClient::callUser(const LLUUID &uuid) -{ - switchChannel(uuid.asString(), false, true, true); +{ + switchChannel(uuid.asString(), false, true, true); } - - void LLWebRTCVoiceClient::endUserIMSession(const LLUUID &uuid) { } -bool LLWebRTCVoiceClient::isValidChannel(std::string &sessionHandle) +bool LLWebRTCVoiceClient::isValidChannel(std::string &channelID) { - return(findSession(sessionHandle) != NULL); + return(findP2PSession(LLUUID(channelID)) != NULL); } -bool LLWebRTCVoiceClient::answerInvite(std::string &sessionHandle) +bool LLWebRTCVoiceClient::answerInvite(std::string &channelID) { // this is only ever used to answer incoming p2p call invites. - sessionStatePtr_t session(findSession(sessionHandle)); + sessionStatePtr_t session(findP2PSession(LLUUID(channelID))); if(session) { session->mIsSpatial = false; @@ -2618,21 +1415,6 @@ bool LLWebRTCVoiceClient::isVoiceWorking() const BOOL LLWebRTCVoiceClient::isParticipantAvatar(const LLUUID &id) { BOOL result = TRUE; - sessionStatePtr_t session(findSession(id)); - - if(!session) - { - // Didn't find a matching session -- check the current audio session for a matching participant - if(mAudioSession) - { - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - result = participant->isAvatar(); - } - } - } - return result; } @@ -2641,7 +1423,7 @@ BOOL LLWebRTCVoiceClient::isParticipantAvatar(const LLUUID &id) BOOL LLWebRTCVoiceClient::isSessionCallBackPossible(const LLUUID &session_id) { BOOL result = TRUE; - sessionStatePtr_t session(findSession(session_id)); + sessionStatePtr_t session(findP2PSession(session_id)); if(session != NULL) { @@ -2656,7 +1438,7 @@ BOOL LLWebRTCVoiceClient::isSessionCallBackPossible(const LLUUID &session_id) BOOL LLWebRTCVoiceClient::isSessionTextIMPossible(const LLUUID &session_id) { bool result = TRUE; - sessionStatePtr_t session(findSession(session_id)); + sessionStatePtr_t session(findP2PSession(session_id)); if(session != NULL) { @@ -2689,26 +1471,12 @@ void LLWebRTCVoiceClient::leaveNonSpatialChannel() std::string LLWebRTCVoiceClient::getCurrentChannel() { - std::string result; - - if (mIsInChannel && !mSessionTerminateRequested) - { - result = getAudioSessionURI(); - } - - return result; + return getAudioSessionURI(); } bool LLWebRTCVoiceClient::inProximalChannel() { - bool result = false; - - if (mIsInChannel && !mSessionTerminateRequested) - { - result = inSpatialChannel(); - } - - return result; + return inSpatialChannel(); } std::string LLWebRTCVoiceClient::nameFromAvatar(LLVOAvatar *avatar) @@ -2817,22 +1585,11 @@ std::string LLWebRTCVoiceClient::getAudioSessionURI() std::string result; if(mAudioSession) - result = mAudioSession->mSIPURI; - - return result; -} - -std::string LLWebRTCVoiceClient::getAudioSessionHandle() -{ - std::string result; - - if(mAudioSession) - result = mAudioSession->mHandle; + result = mAudioSession->mChannelID; return result; } - ///////////////////////////// // Sending updates of current state @@ -2877,7 +1634,7 @@ void LLWebRTCVoiceClient::updatePosition(void) LLWebRTCVoiceClient::getInstance()->setCameraPosition( pos, // position LLVector3::zero, // velocity - LLViewerCamera::getInstance()->getQuaternion()); // rotation matrix + LLViewerCamera::getInstance()->getQuaternion()); // rotation matrix // Send the current avatar position to the voice code qrot = gAgentAvatarp->getRootJoint()->getWorldRotation(); @@ -2891,6 +1648,8 @@ void LLWebRTCVoiceClient::updatePosition(void) pos, // position LLVector3::zero, // velocity qrot); // rotation matrix + + enforceTether(); } } @@ -2953,7 +1712,7 @@ bool LLWebRTCVoiceClient::channelFromRegion(LLViewerRegion *region, std::string void LLWebRTCVoiceClient::leaveChannel(void) { - if (mIsInChannel) + if (mAudioSession || mNextAudioSession) { LL_DEBUGS("Voice") << "leaving channel for teleport/logout" << LL_ENDL; mChannelName.clear(); @@ -2963,16 +1722,22 @@ void LLWebRTCVoiceClient::leaveChannel(void) void LLWebRTCVoiceClient::setMuteMic(bool muted) { - participantStatePtr_t participant = findParticipantByID(gAgentID); + + if (mWebRTCDeviceInterface) + { + mWebRTCDeviceInterface->setMute(muted); + } + mMuteMic = muted; + sessionState::for_each(boost::bind(predSetMuteMic, _1, muted)); +} + +void LLWebRTCVoiceClient::predSetMuteMic(const LLWebRTCVoiceClient::sessionStatePtr_t &session, bool muted) +{ + participantStatePtr_t participant = session->findParticipant(gAgentID.asString()); if (participant) - { + { participant->mPower = 0.0; - } - if (mWebRTCAudioInterface) - { - mWebRTCAudioInterface->setMute(muted); - } - mMuteMic = muted; + } } void LLWebRTCVoiceClient::setVoiceEnabled(bool enabled) @@ -2998,8 +1763,8 @@ void LLWebRTCVoiceClient::setVoiceEnabled(bool enabled) if (!mIsCoroutineActive) { - LLCoros::instance().launch("LLWebRTCVoiceClient::voiceControlCoro", - boost::bind(&LLWebRTCVoiceClient::voiceControlCoro, LLWebRTCVoiceClient::getInstance())); + LLCoros::instance().launch("LLWebRTCVoiceClient::voiceConnectionCoro", + boost::bind(&LLWebRTCVoiceClient::voiceConnectionCoro, LLWebRTCVoiceClient::getInstance())); } else { @@ -3064,7 +1829,6 @@ void LLWebRTCVoiceClient::setVoiceVolume(F32 volume) if (volume != mSpeakerVolume) { { - LLMutexLock lock(&mVoiceStateMutex); int min_volume = 0.0; if ((volume == min_volume) || (mSpeakerVolume == min_volume)) { @@ -3074,7 +1838,7 @@ void LLWebRTCVoiceClient::setVoiceVolume(F32 volume) mSpeakerVolume = volume; mSpeakerVolumeDirty = true; } - if (mWebRTCAudioInterface) + if (mWebRTCDeviceInterface) { mWebRTCDeviceInterface->setSpeakerVolume(volume); } @@ -3097,7 +1861,11 @@ void LLWebRTCVoiceClient::setMicGain(F32 volume) BOOL LLWebRTCVoiceClient::getVoiceEnabled(const LLUUID& id) { BOOL result = FALSE; - participantStatePtr_t participant(findParticipantByID(id)); + if (!mAudioSession) + { + return FALSE; + } + participantStatePtr_t participant(mAudioSession->findParticipant(id.asString())); if(participant) { // I'm not sure what the semantics of this should be. @@ -3111,7 +1879,11 @@ BOOL LLWebRTCVoiceClient::getVoiceEnabled(const LLUUID& id) std::string LLWebRTCVoiceClient::getDisplayName(const LLUUID& id) { std::string result; - participantStatePtr_t participant(findParticipantByID(id)); + if (!mAudioSession) + { + return result; + } + participantStatePtr_t participant(mAudioSession->findParticipant(id.asString())); if(participant) { result = participant->mDisplayName; @@ -3125,8 +1897,11 @@ std::string LLWebRTCVoiceClient::getDisplayName(const LLUUID& id) BOOL LLWebRTCVoiceClient::getIsSpeaking(const LLUUID& id) { BOOL result = FALSE; - - participantStatePtr_t participant(findParticipantByID(id)); + if (!mAudioSession) + { + return result; + } + participantStatePtr_t participant(mAudioSession->findParticipant(id.asString())); if(participant) { result = participant->mIsSpeaking; @@ -3138,8 +1913,11 @@ BOOL LLWebRTCVoiceClient::getIsSpeaking(const LLUUID& id) BOOL LLWebRTCVoiceClient::getIsModeratorMuted(const LLUUID& id) { BOOL result = FALSE; - - participantStatePtr_t participant(findParticipantByID(id)); + if (!mAudioSession) + { + return result; + } + participantStatePtr_t participant(mAudioSession->findParticipant(id.asString())); if(participant) { result = participant->mIsModeratorMuted; @@ -3151,7 +1929,11 @@ BOOL LLWebRTCVoiceClient::getIsModeratorMuted(const LLUUID& id) F32 LLWebRTCVoiceClient::getCurrentPower(const LLUUID &id) { F32 result = 0; - participantStatePtr_t participant(findParticipantByID(id)); + if (!mAudioSession) + { + return result; + } + participantStatePtr_t participant(mAudioSession->findParticipant(id.asString())); if (participant) { result = participant->mPower * 2.0; @@ -3162,8 +1944,11 @@ F32 LLWebRTCVoiceClient::getCurrentPower(const LLUUID &id) BOOL LLWebRTCVoiceClient::getUsingPTT(const LLUUID& id) { BOOL result = FALSE; - - participantStatePtr_t participant(findParticipantByID(id)); + if (!mAudioSession) + { + return result; + } + participantStatePtr_t participant(mAudioSession->findParticipant(id.asString())); if(participant) { // I'm not sure what the semantics of this should be. @@ -3178,7 +1963,7 @@ BOOL LLWebRTCVoiceClient::getOnMuteList(const LLUUID& id) { BOOL result = FALSE; - participantStatePtr_t participant(findParticipantByID(id)); + participantStatePtr_t participant(mAudioSession->findParticipant(id.asString())); if(participant) { result = participant->mOnMuteList; @@ -3193,7 +1978,7 @@ F32 LLWebRTCVoiceClient::getUserVolume(const LLUUID& id) // Minimum volume will be returned for users with voice disabled F32 result = LLVoiceClient::VOLUME_MIN; - participantStatePtr_t participant(findParticipantByID(id)); + participantStatePtr_t participant(mAudioSession->findParticipant(id.asString())); if(participant) { result = participant->mVolume; @@ -3209,7 +1994,7 @@ void LLWebRTCVoiceClient::setUserVolume(const LLUUID& id, F32 volume) { if(mAudioSession) { - participantStatePtr_t participant(findParticipantByID(id)); + participantStatePtr_t participant(mAudioSession->findParticipant(id.asString())); if (participant && !participant->mIsSelf) { if (!is_approx_equal(volume, LLVoiceClient::VOLUME_DEFAULT)) @@ -3235,7 +2020,7 @@ std::string LLWebRTCVoiceClient::getGroupID(const LLUUID& id) { std::string result; - participantStatePtr_t participant(findParticipantByID(id)); + participantStatePtr_t participant(mAudioSession->findParticipant(id.asString())); if(participant) { result = participant->mGroupID; @@ -3250,12 +2035,11 @@ BOOL LLWebRTCVoiceClient::getAreaVoiceDisabled() } //------------------------------------------------------------------------ -std::set LLWebRTCVoiceClient::sessionState::mSession; +std::map LLWebRTCVoiceClient::sessionState::mSessions; LLWebRTCVoiceClient::sessionState::sessionState() : mErrorStatusCode(0), - mMediaStreamState(streamStateUnknown), mIsChannel(false), mIsSpatial(false), mIsP2P(false), @@ -3269,23 +2053,26 @@ LLWebRTCVoiceClient::sessionState::sessionState() : } /*static*/ -LLWebRTCVoiceClient::sessionState::ptr_t LLWebRTCVoiceClient::sessionState::createSession() +LLWebRTCVoiceClient::sessionState::ptr_t LLWebRTCVoiceClient::sessionState::createSession(const std::string& channelID, S32 parcelLocalID) { - sessionState::ptr_t ptr(new sessionState()); + LLUUID region_id = gAgent.getRegion()->getRegionID(); + + sessionState::ptr_t session(new sessionState()); + session->mChannelID = channelID; + session->mWebRTCConnections[channelID] = connectionPtr_t(new LLVoiceWebRTCConnection(region_id, parcelLocalID, channelID)); + session->mPrimaryConnectionID = channelID; - std::pair::iterator, bool> result = mSession.insert(ptr); + // add agent as participant + session->addParticipant(gAgentID); - if (result.second) - ptr->mMyIterator = result.first; + mSessions[channelID] = session; - return ptr; + return session; } LLWebRTCVoiceClient::sessionState::~sessionState() { - LL_INFOS("Voice") << "Destroying session handle=" << mHandle << " SIP=" << mSIPURI << LL_ENDL; - if (mMyIterator != mSession.end()) - mSession.erase(mMyIterator); + LL_INFOS("Voice") << "Destroying session CHANNEL=" << mChannelID << LL_ENDL; removeAllParticipants(); } @@ -3304,78 +2091,49 @@ bool LLWebRTCVoiceClient::sessionState::isTextIMPossible() return false; } - -/*static*/ -LLWebRTCVoiceClient::sessionState::ptr_t LLWebRTCVoiceClient::sessionState::matchSessionByHandle(const std::string &handle) -{ - sessionStatePtr_t result; - - // *TODO: My kingdom for a lambda! - std::set::iterator it = std::find_if(mSession.begin(), mSession.end(), boost::bind(testByHandle, _1, handle)); - - if (it != mSession.end()) - result = (*it).lock(); - - return result; -} - -/*static*/ -LLWebRTCVoiceClient::sessionState::ptr_t LLWebRTCVoiceClient::sessionState::matchSessionByURI(const std::string &uri) -{ - sessionStatePtr_t result; - - // *TODO: My kingdom for a lambda! - std::set::iterator it = std::find_if(mSession.begin(), mSession.end(), boost::bind(testBySIPOrAlterateURI, _1, uri)); - - if (it != mSession.end()) - result = (*it).lock(); - - return result; -} - /*static*/ -LLWebRTCVoiceClient::sessionState::ptr_t LLWebRTCVoiceClient::sessionState::matchSessionByParticipant(const LLUUID &participant_id) +LLWebRTCVoiceClient::sessionState::ptr_t LLWebRTCVoiceClient::sessionState::matchSessionByChannelID(const std::string& channel_id) { sessionStatePtr_t result; // *TODO: My kingdom for a lambda! - std::set::iterator it = std::find_if(mSession.begin(), mSession.end(), boost::bind(testByCallerId, _1, participant_id)); - - if (it != mSession.end()) - result = (*it).lock(); - + std::map::iterator it = mSessions.find(channel_id); + if (it != mSessions.end()) + { + result = (*it).second; + } return result; } void LLWebRTCVoiceClient::sessionState::for_each(sessionFunc_t func) { - std::for_each(mSession.begin(), mSession.end(), boost::bind(for_eachPredicate, _1, func)); + std::for_each(mSessions.begin(), mSessions.end(), boost::bind(for_eachPredicate, _1, func)); } -// simple test predicates. -// *TODO: These should be made into lambdas when we can pull the trigger on newer C++ features. -bool LLWebRTCVoiceClient::sessionState::testByHandle(const LLWebRTCVoiceClient::sessionState::wptr_t &a, std::string handle) +void LLWebRTCVoiceClient::sessionState::reapEmptySessions() { - ptr_t aLock(a.lock()); - - return aLock ? aLock->mHandle == handle : false; + std::map::iterator iter; + for (iter = mSessions.begin(); iter != mSessions.end();) + { + if (!iter->second->isEmpty()) + { + iter = mSessions.erase(iter); + } + else + { + ++iter; + } + } } -bool LLWebRTCVoiceClient::sessionState::testByCreatingURI(const LLWebRTCVoiceClient::sessionState::wptr_t &a, std::string uri) -{ - ptr_t aLock(a.lock()); - - return aLock ? (aLock->mSIPURI == uri) : false; -} -bool LLWebRTCVoiceClient::sessionState::testBySIPOrAlterateURI(const LLWebRTCVoiceClient::sessionState::wptr_t &a, std::string uri) +bool LLWebRTCVoiceClient::sessionState::testByCreatingURI(const LLWebRTCVoiceClient::sessionState::wptr_t &a, std::string uri) { ptr_t aLock(a.lock()); - return aLock ? ((aLock->mSIPURI == uri) || (aLock->mAlternateSIPURI == uri)) : false; + return aLock ? (aLock->mChannelID == LLUUID(uri)) : false; } - bool LLWebRTCVoiceClient::sessionState::testByCallerId(const LLWebRTCVoiceClient::sessionState::wptr_t &a, LLUUID participantId) { ptr_t aLock(a.lock()); @@ -3384,9 +2142,9 @@ bool LLWebRTCVoiceClient::sessionState::testByCallerId(const LLWebRTCVoiceClient } /*static*/ -void LLWebRTCVoiceClient::sessionState::for_eachPredicate(const LLWebRTCVoiceClient::sessionState::wptr_t &a, sessionFunc_t func) +void LLWebRTCVoiceClient::sessionState::for_eachPredicate(const std::pair &a, sessionFunc_t func) { - ptr_t aLock(a.lock()); + ptr_t aLock(a.second.lock()); if (aLock) func(aLock); @@ -3396,425 +2154,807 @@ void LLWebRTCVoiceClient::sessionState::for_eachPredicate(const LLWebRTCVoiceCli } } -void LLWebRTCVoiceClient::sessionEstablished() -{ - mWebRTCAudioInterface->setMute(mMuteMic); - addSession(gAgent.getRegion()->getRegionID().asString()); -} - -LLWebRTCVoiceClient::sessionStatePtr_t LLWebRTCVoiceClient::findSession(const std::string &handle) +LLWebRTCVoiceClient::sessionStatePtr_t LLWebRTCVoiceClient::findP2PSession(const LLUUID &agent_id) { - sessionStatePtr_t result; - sessionMap::iterator iter = mSessionsByHandle.find(handle); - if(iter != mSessionsByHandle.end()) - { - result = iter->second; - } + sessionStatePtr_t result = sessionState::matchSessionByChannelID(agent_id.asString()); + if (result && result->mIsP2P) + { + return result; + } + result.reset(); return result; } -LLWebRTCVoiceClient::sessionStatePtr_t LLWebRTCVoiceClient::findSession(const LLUUID &participant_id) -{ - sessionStatePtr_t result = sessionState::matchSessionByParticipant(participant_id); - - return result; + + +void LLWebRTCVoiceClient::sessionState::shutdownAllConnections() +{ + for (auto &&connection : mWebRTCConnections) + { + connection.second->shutDown(); + } } -LLWebRTCVoiceClient::sessionStatePtr_t LLWebRTCVoiceClient::addSession(const std::string &uri, const std::string &handle) + +LLWebRTCVoiceClient::sessionStatePtr_t LLWebRTCVoiceClient::addSession(const std::string& channel_id, S32 parcel_local_id) { sessionStatePtr_t result; - - if(handle.empty()) - { - // No handle supplied. - // Check whether there's already a session with this URI - result = sessionState::matchSessionByURI(uri); - } - else // (!handle.empty()) - { - // Check for an existing session with this handle - sessionMap::iterator iter = mSessionsByHandle.find(handle); - - if(iter != mSessionsByHandle.end()) - { - result = iter->second; - } - } + + // Check whether there's already a session with this URI + result = sessionState::matchSessionByChannelID(channel_id); if(!result) { // No existing session found. - LL_DEBUGS("Voice") << "adding new session: handle \"" << handle << "\" URI " << uri << LL_ENDL; - result = sessionState::createSession(); - result->mSIPURI = uri; - result->mHandle = handle; + LL_DEBUGS("Voice") << "adding new session: CHANNEL " << channel_id << LL_ENDL; + result = sessionState::createSession(channel_id, parcel_local_id); if (LLVoiceClient::instance().getVoiceEffectEnabled()) { result->mVoiceFontID = LLVoiceClient::instance().getVoiceEffectDefault(); } - - if(!result->mHandle.empty()) - { - // *TODO: Rider: This concerns me. There is a path (via switchChannel) where - // we do not track the session. In theory this means that we could end up with - // a mAuidoSession that does not match the session tracked in mSessionsByHandle - mSessionsByHandle.insert(sessionMap::value_type(result->mHandle, result)); - } } else { // Found an existing session - if(uri != result->mSIPURI) + if (channel_id != result->mChannelID) { // TODO: Should this be an internal error? - LL_DEBUGS("Voice") << "changing uri from " << result->mSIPURI << " to " << uri << LL_ENDL; - setSessionURI(result, uri); + LL_DEBUGS("Voice") << "changing uri from " << result->mChannelID << " to " << channel_id << LL_ENDL; + + result->mChannelID = channel_id; + + verifySessionState(); } + + LL_DEBUGS("Voice") << "returning existing session: CHANNEL " << channel_id << LL_ENDL; + } + + verifySessionState(); + + return result; +} + +void LLWebRTCVoiceClient::deleteSession(const sessionStatePtr_t &session) +{ + // At this point, the session should be unhooked from all lists and all state should be consistent. + verifySessionState(); + session->shutdownAllConnections(); + // If this is the current audio session, clean up the pointer which will soon be dangling. + if(mAudioSession == session) + { + mAudioSession.reset(); + mAudioSessionChanged = true; + } + + // ditto for the next audio session + if(mNextAudioSession == session) + { + mNextAudioSession.reset(); + } +} + +void LLWebRTCVoiceClient::sessionState::deleteAllSessions() +{ + mSessions.clear(); +} + +void LLWebRTCVoiceClient::verifySessionState(void) +{ + sessionState::VerifySessions(); +} + +void LLWebRTCVoiceClient::addObserver(LLVoiceClientParticipantObserver* observer) +{ + mParticipantObservers.insert(observer); +} + +void LLWebRTCVoiceClient::removeObserver(LLVoiceClientParticipantObserver* observer) +{ + mParticipantObservers.erase(observer); +} + +void LLWebRTCVoiceClient::notifyParticipantObservers() +{ + for (observer_set_t::iterator it = mParticipantObservers.begin(); + it != mParticipantObservers.end(); + ) + { + LLVoiceClientParticipantObserver* observer = *it; + observer->onParticipantsChanged(); + // In case onParticipantsChanged() deleted an entry. + it = mParticipantObservers.upper_bound(observer); + } +} + +void LLWebRTCVoiceClient::addObserver(LLVoiceClientStatusObserver* observer) +{ + mStatusObservers.insert(observer); +} + +void LLWebRTCVoiceClient::removeObserver(LLVoiceClientStatusObserver* observer) +{ + mStatusObservers.erase(observer); +} + +void LLWebRTCVoiceClient::notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status) +{ + LL_DEBUGS("Voice") << "( " << LLVoiceClientStatusObserver::status2string(status) << " )" + << " mAudioSession=" << mAudioSession + << LL_ENDL; - if(handle != result->mHandle) + if(mAudioSession) + { + if(status == LLVoiceClientStatusObserver::ERROR_UNKNOWN) { - if(handle.empty()) + switch(mAudioSession->mErrorStatusCode) { - // There's at least one race condition where where addSession was clearing an existing session handle, which caused things to break. - LL_DEBUGS("Voice") << "NOT clearing handle " << result->mHandle << LL_ENDL; + case 20713: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_FULL; break; + case 20714: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_LOCKED; break; + case 20715: + //invalid channel, we may be using a set of poorly cached + //info + status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; + break; + case 1009: + //invalid username and password + status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; + break; } - else + + // Reset the error code to make sure it won't be reused later by accident. + mAudioSession->mErrorStatusCode = 0; + } + else if(status == LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL) + { + switch(mAudioSession->mErrorStatusCode) { - // TODO: Should this be an internal error? - LL_DEBUGS("Voice") << "changing handle from " << result->mHandle << " to " << handle << LL_ENDL; - setSessionHandle(result, handle); + case HTTP_NOT_FOUND: // NOT_FOUND + // *TODO: Should this be 503? + case 480: // TEMPORARILY_UNAVAILABLE + case HTTP_REQUEST_TIME_OUT: // REQUEST_TIMEOUT + // call failed because other user was not available + // treat this as an error case + status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; + + // Reset the error code to make sure it won't be reused later by accident. + mAudioSession->mErrorStatusCode = 0; + break; } } + } - LL_DEBUGS("Voice") << "returning existing session: handle " << handle << " URI " << uri << LL_ENDL; + LL_DEBUGS("Voice") + << " " << LLVoiceClientStatusObserver::status2string(status) + << ", session URI " << getAudioSessionURI() + << ", proximal is " << inSpatialChannel() + << LL_ENDL; + + for (status_observer_set_t::iterator it = mStatusObservers.begin(); + it != mStatusObservers.end(); + ) + { + LLVoiceClientStatusObserver* observer = *it; + observer->onChange(status, getAudioSessionURI(), inSpatialChannel()); + // In case onError() deleted an entry. + it = mStatusObservers.upper_bound(observer); + } + mIsProcessingChannels = status == LLVoiceClientStatusObserver::STATUS_LOGGED_IN; + { + + } + // skipped to avoid speak button blinking + if ( status != LLVoiceClientStatusObserver::STATUS_JOINING + && status != LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL + && status != LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED) + { + bool voice_status = LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking(); + + gAgent.setVoiceConnected(voice_status); + + if (voice_status) + { + LLFirstUse::speak(true); + } + } +} + +void LLWebRTCVoiceClient::addObserver(LLFriendObserver* observer) +{ + mFriendObservers.insert(observer); +} + +void LLWebRTCVoiceClient::removeObserver(LLFriendObserver* observer) +{ + mFriendObservers.erase(observer); +} + +void LLWebRTCVoiceClient::notifyFriendObservers() +{ + for (friend_observer_set_t::iterator it = mFriendObservers.begin(); + it != mFriendObservers.end(); + ) + { + LLFriendObserver* observer = *it; + it++; + // The only friend-related thing we notify on is online/offline transitions. + observer->changed(LLFriendObserver::ONLINE); + } +} + +void LLWebRTCVoiceClient::lookupName(const LLUUID &id) +{ + if (mAvatarNameCacheConnection.connected()) + { + mAvatarNameCacheConnection.disconnect(); } + mAvatarNameCacheConnection = LLAvatarNameCache::get(id, boost::bind(&LLWebRTCVoiceClient::onAvatarNameCache, this, _1, _2)); +} - verifySessionState(); - - return result; +void LLWebRTCVoiceClient::onAvatarNameCache(const LLUUID& agent_id, + const LLAvatarName& av_name) +{ + mAvatarNameCacheConnection.disconnect(); + std::string display_name = av_name.getDisplayName(); + avatarNameResolved(agent_id, display_name); } -void LLWebRTCVoiceClient::clearSessionHandle(const sessionStatePtr_t &session) +void LLWebRTCVoiceClient::predAvatarNameResolution(const LLWebRTCVoiceClient::sessionStatePtr_t &session, LLUUID id, std::string name) { - if (session) + participantStatePtr_t participant(session->findParticipantByID(id)); + if (participant) { - if (!session->mHandle.empty()) - { - sessionMap::iterator iter = mSessionsByHandle.find(session->mHandle); - if (iter != mSessionsByHandle.end()) - { - mSessionsByHandle.erase(iter); - } - } - else - { - LL_WARNS("Voice") << "Session has empty handle!" << LL_ENDL; - } + // Found -- fill in the name + // and post a "participants updated" message to listeners later. + session->mParticipantsChanged = true; } - else + + // Check whether this is a p2p session whose caller name just resolved + if (session->mCallerID == id) { - LL_WARNS("Voice") << "Attempt to clear NULL session!" << LL_ENDL; + // this session's "caller ID" just resolved. Fill in the name. + session->mName = name; } - } -void LLWebRTCVoiceClient::setSessionHandle(const sessionStatePtr_t &session, const std::string &handle) +void LLWebRTCVoiceClient::avatarNameResolved(const LLUUID &id, const std::string &name) { - // Have to remove the session from the handle-indexed map before changing the handle, or things will break badly. - - if(!session->mHandle.empty()) - { - // Remove session from the map if it should have been there. - sessionMap::iterator iter = mSessionsByHandle.find(session->mHandle); - if(iter != mSessionsByHandle.end()) - { - if(iter->second != session) - { - LL_WARNS("Voice") << "Internal error: session mismatch! Session may have been duplicated. Removing version in map." << LL_ENDL; - } + sessionState::for_each(boost::bind(predAvatarNameResolution, _1, id, name)); +} - mSessionsByHandle.erase(iter); - } - else - { - LL_WARNS("Voice") << "Attempt to remove session with handle " << session->mHandle << " not found in map!" << LL_ENDL; - } - } - - session->mHandle = handle; - if(!handle.empty()) - { - mSessionsByHandle.insert(sessionMap::value_type(session->mHandle, session)); - } +std::string LLWebRTCVoiceClient::sipURIFromID(const LLUUID& id) { return id.asString(); } - verifySessionState(); -} -void LLWebRTCVoiceClient::setSessionURI(const sessionStatePtr_t &session, const std::string &uri) +///////////////////////////// +// WebRTC Signaling Handlers + +LLVoiceWebRTCConnection::LLVoiceWebRTCConnection(const LLUUID ®ionID, S32 parcelLocalID, const std::string& channelID) : + mWebRTCPeerConnection(nullptr), + mWebRTCAudioInterface(nullptr), + mWebRTCDataInterface(nullptr), + mIceCompleted(false), + mTrickling(false), + mVoiceConnectionState(VOICE_STATE_START_SESSION), + mChannelID(channelID), + mRegionID(regionID), + mParcelLocalID(parcelLocalID), + mShutDown(false) { - // There used to be a map of session URIs to sessions, which made this complex.... - session->mSIPURI = uri; + mWebRTCPeerConnection = llwebrtc::newPeerConnection(); + mWebRTCPeerConnection->setSignalingObserver(this); +} - verifySessionState(); +LLVoiceWebRTCConnection::~LLVoiceWebRTCConnection() +{ + if (LLWebRTCVoiceClient::isShuttingDown()) + { + // peer connection and observers will be cleaned up + // by llwebrtc::terminate() on shutdown. + return; + } + mWebRTCPeerConnection->unsetSignalingObserver(this); + llwebrtc::freePeerConnection(mWebRTCPeerConnection); + mWebRTCPeerConnection = nullptr; } -void LLWebRTCVoiceClient::deleteSession(const sessionStatePtr_t &session) +void LLVoiceWebRTCConnection::OnIceGatheringState(llwebrtc::LLWebRTCSignalingObserver::IceGatheringState state) { - // Remove the session from the handle map - if(!session->mHandle.empty()) - { - sessionMap::iterator iter = mSessionsByHandle.find(session->mHandle); - if(iter != mSessionsByHandle.end()) - { - if(iter->second != session) - { - LL_WARNS("Voice") << "Internal error: session mismatch, removing session in map." << LL_ENDL; - } - mSessionsByHandle.erase(iter); - } - } + LL_INFOS("Voice") << "Ice Gathering voice account. " << state << LL_ENDL; - // At this point, the session should be unhooked from all lists and all state should be consistent. - verifySessionState(); + switch (state) + { + case llwebrtc::LLWebRTCSignalingObserver::IceGatheringState::ICE_GATHERING_COMPLETE: + { + LLMutexLock lock(&mVoiceStateMutex); + mIceCompleted = true; + break; + } + case llwebrtc::LLWebRTCSignalingObserver::IceGatheringState::ICE_GATHERING_NEW: + { + LLMutexLock lock(&mVoiceStateMutex); + mIceCompleted = false; + } + default: + break; + } +} - // If this is the current audio session, clean up the pointer which will soon be dangling. - if(mAudioSession == session) - { - mAudioSession.reset(); - mAudioSessionChanged = true; - } +void LLVoiceWebRTCConnection::OnIceCandidate(const llwebrtc::LLWebRTCIceCandidate &candidate) +{ + LLMutexLock lock(&mVoiceStateMutex); + mIceCandidates.push_back(candidate); +} - // ditto for the next audio session - if(mNextAudioSession == session) - { - mNextAudioSession.reset(); - } +void LLVoiceWebRTCConnection::onIceUpdateComplete(bool ice_completed, const LLSD &result) +{ + if (LLWebRTCVoiceClient::isShuttingDown()) + { + return; + } + mTrickling = false; +} + +void LLVoiceWebRTCConnection::onIceUpdateError(int retries, std::string url, LLSD body, bool ice_completed, const LLSD &result) +{ + if (LLWebRTCVoiceClient::isShuttingDown()) + { + return; + } + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("voiceAccountProvision", httpPolicy)); + if (retries >= 0) + { + LL_WARNS("Voice") << "Unable to complete ice trickling voice account, retrying. " << result << LL_ENDL; + LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpPost( + url, + LLCore::HttpRequest::DEFAULT_POLICY_ID, + body, + boost::bind(&LLVoiceWebRTCConnection::onIceUpdateComplete, this, ice_completed, _1), + boost::bind(&LLVoiceWebRTCConnection::onIceUpdateError, this, retries - 1, url, body, ice_completed, _1)); + } + else + { + LL_WARNS("Voice") << "Unable to complete ice trickling voice account, restarting connection. " << result << LL_ENDL; + setVoiceConnectionState(VOICE_STATE_SESSION_RETRY); + mTrickling = false; + } } -void LLWebRTCVoiceClient::deleteAllSessions() +void LLVoiceWebRTCConnection::OnOfferAvailable(const std::string &sdp) { - LL_DEBUGS("Voice") << LL_ENDL; + LL_INFOS("Voice") << "On Offer Available." << LL_ENDL; + LLMutexLock lock(&mVoiceStateMutex); + mChannelSDP = sdp; + if (mVoiceConnectionState == VOICE_STATE_WAIT_FOR_SESSION_START) + { + mVoiceConnectionState = VOICE_STATE_REQUEST_CONNECTION; + } +} - while (!mSessionsByHandle.empty()) - { - const sessionStatePtr_t session = mSessionsByHandle.begin()->second; - deleteSession(session); - } - +void LLVoiceWebRTCConnection::OnAudioEstablished(llwebrtc::LLWebRTCAudioInterface *audio_interface) +{ + LL_INFOS("Voice") << "On AudioEstablished." << LL_ENDL; + mWebRTCAudioInterface = audio_interface; + setVoiceConnectionState(VOICE_STATE_SESSION_ESTABLISHED); } -void LLWebRTCVoiceClient::verifySessionState(void) +void LLVoiceWebRTCConnection::OnDataReceived(const std::string &data, bool binary) { - LL_DEBUGS("Voice") << "Sessions in handle map=" << mSessionsByHandle.size() << LL_ENDL; - sessionState::VerifySessions(); + // incoming data will be a json structure (if it's not binary.) We may pack + // binary for size reasons. Most of the keys in the json objects are + // single or double characters for size reasons. + // The primary element is: + // An object where each key is an agent id. (in the future, we may allow + // integer indices into an agentid list, populated on join commands. For size. + // Each key will point to a json object with keys identifying what's updated. + // 'p' - audio source power (level/volume) (int8 as int) + // 'j' - join - object of join data (TBD) (true for now) + // 'l' - boolean, always true if exists. + + if (binary) + { + LL_WARNS("Voice") << "Binary data received from data channel." << LL_ENDL; + return; + } + + Json::Reader reader; + Json::Value voice_data; + if (reader.parse(data, voice_data, false)) // don't collect comments + { + if (!voice_data.isObject()) + { + LL_WARNS("Voice") << "Expected object from data channel:" << data << LL_ENDL; + return; + } + bool new_participant = false; + for (auto &participant_id : voice_data.getMemberNames()) + { + LLUUID agent_id(participant_id); + if (agent_id.isNull()) + { + LL_WARNS("Voice") << "Bad participant ID from data channel (" << participant_id << "):" << data << LL_ENDL; + continue; + } + + LLWebRTCVoiceClient::participantStatePtr_t participant = LLWebRTCVoiceClient::getInstance()->findParticipantByID(mChannelID, agent_id); + bool joined = voice_data[participant_id].get("j", Json::Value(false)).asBool(); + new_participant |= joined; + if (!participant && joined) + { + participant = LLWebRTCVoiceClient::getInstance()->addParticipantByID(mChannelID, agent_id); + } + if (participant) + { + if (voice_data[participant_id].get("l", Json::Value(false)).asBool()) + { + LLWebRTCVoiceClient::getInstance()->removeParticipantByID(mChannelID, agent_id); + } + else + { + F32 energyRMS = (F32) (voice_data[participant_id].get("p", Json::Value(participant->mPower)).asInt()) / 128; + // convert to decibles + participant->mPower = energyRMS; + /* WebRTC appears to have deprecated VAD, but it's still in the Audio Processing Module so maybe we + can use it at some point when we actually process frames. */ + participant->mIsSpeaking = participant->mPower > SPEAKING_AUDIO_LEVEL; + } + } + } + } } -void LLWebRTCVoiceClient::addObserver(LLVoiceClientParticipantObserver* observer) +void LLVoiceWebRTCConnection::OnDataChannelReady(llwebrtc::LLWebRTCDataInterface *data_interface) { - mParticipantObservers.insert(observer); + if (data_interface) + { + mWebRTCDataInterface = data_interface; + mWebRTCDataInterface->setDataObserver(this); + Json::FastWriter writer; + Json::Value root = Json::objectValue; + root["j"] = true; + std::string json_data = writer.write(root); + mWebRTCDataInterface->sendData(json_data, false); + } } -void LLWebRTCVoiceClient::removeObserver(LLVoiceClientParticipantObserver* observer) +void LLVoiceWebRTCConnection::OnRenegotiationNeeded() { - mParticipantObservers.erase(observer); + LL_INFOS("Voice") << "On Renegotiation Needed." << LL_ENDL; + setVoiceConnectionState(VOICE_STATE_SESSION_RETRY); } -void LLWebRTCVoiceClient::notifyParticipantObservers() +void LLVoiceWebRTCConnection::processIceUpdates() { - for (observer_set_t::iterator it = mParticipantObservers.begin(); - it != mParticipantObservers.end(); - ) - { - LLVoiceClientParticipantObserver* observer = *it; - observer->onParticipantsChanged(); - // In case onParticipantsChanged() deleted an entry. - it = mParticipantObservers.upper_bound(observer); - } + if (LLWebRTCVoiceClient::isShuttingDown()) + { + return; + } + LLViewerRegion *regionp = LLWorld::instance().getRegionFromID(mRegionID); + if (!regionp || !regionp->capabilitiesReceived()) + { + LL_DEBUGS("Voice") << "no capabilities for ice gathering; waiting " << LL_ENDL; + return; + } + + std::string url = regionp->getCapability("VoiceSignalingRequest"); + if (url.empty()) + { + return; + } + + LL_DEBUGS("Voice") << "region ready to complete voice signaling; url=" << url << LL_ENDL; + + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("voiceAccountProvision", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpOptions::ptr_t httpOpts = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); + + bool iceCompleted = false; + LLSD body; + { + if (!mTrickling) + { + if (mIceCandidates.size()) + { + LLSD candidates = LLSD::emptyArray(); + for (auto &ice_candidate : mIceCandidates) + { + LLSD body_candidate; + body_candidate["sdpMid"] = ice_candidate.sdp_mid; + body_candidate["sdpMLineIndex"] = ice_candidate.mline_index; + body_candidate["candidate"] = ice_candidate.candidate; + candidates.append(body_candidate); + } + body["candidates"] = candidates; + mIceCandidates.clear(); + } + else if (mIceCompleted) + { + LLSD body_candidate; + body_candidate["completed"] = true; + body["candidate"] = body_candidate; + iceCompleted = mIceCompleted; + mIceCompleted = false; + } + else + { + return; + } + LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpPost( + url, + LLCore::HttpRequest::DEFAULT_POLICY_ID, + body, + boost::bind(&LLVoiceWebRTCConnection::onIceUpdateComplete, this, iceCompleted, _1), + boost::bind(&LLVoiceWebRTCConnection::onIceUpdateError, this, 3, url, body, iceCompleted, _1)); + mTrickling = true; + } + } } -void LLWebRTCVoiceClient::addObserver(LLVoiceClientStatusObserver* observer) +bool LLVoiceWebRTCConnection::requestVoiceConnection() { - mStatusObservers.insert(observer); -} + LLViewerRegion *regionp = LLWorld::instance().getRegionFromID(mRegionID); -void LLWebRTCVoiceClient::removeObserver(LLVoiceClientStatusObserver* observer) -{ - mStatusObservers.erase(observer); -} + LL_INFOS("Voice") << "Requesting voice connection." << LL_ENDL; + if (!regionp || !regionp->capabilitiesReceived()) + { + LL_DEBUGS("Voice") << "no capabilities for voice provisioning; waiting " << LL_ENDL; + return false; + } -void LLWebRTCVoiceClient::notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status) -{ - LL_DEBUGS("Voice") << "( " << LLVoiceClientStatusObserver::status2string(status) << " )" - << " mAudioSession=" << mAudioSession - << LL_ENDL; + std::string url = regionp->getCapability("ProvisionVoiceAccountRequest"); + if (url.empty()) + { + return false; + } - if(mAudioSession) - { - if(status == LLVoiceClientStatusObserver::ERROR_UNKNOWN) - { - switch(mAudioSession->mErrorStatusCode) - { - case 20713: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_FULL; break; - case 20714: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_LOCKED; break; - case 20715: - //invalid channel, we may be using a set of poorly cached - //info - status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; - break; - case 1009: - //invalid username and password - status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; - break; - } + LL_DEBUGS("Voice") << "region ready for voice provisioning; url=" << url << LL_ENDL; - // Reset the error code to make sure it won't be reused later by accident. - mAudioSession->mErrorStatusCode = 0; - } - else if(status == LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL) - { - switch(mAudioSession->mErrorStatusCode) - { - case HTTP_NOT_FOUND: // NOT_FOUND - // *TODO: Should this be 503? - case 480: // TEMPORARILY_UNAVAILABLE - case HTTP_REQUEST_TIME_OUT: // REQUEST_TIMEOUT - // call failed because other user was not available - // treat this as an error case - status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; + LLVoiceWebRTCStats::getInstance()->provisionAttemptStart(); + LLSD body; + LLSD jsep; + jsep["type"] = "offer"; + { + LLMutexLock lock(&mVoiceStateMutex); + jsep["sdp"] = mChannelSDP; + } + body["jsep"] = jsep; + if (mParcelLocalID != INVALID_PARCEL_ID) + { + body["parcel_local_id"] = mParcelLocalID; + } - // Reset the error code to make sure it won't be reused later by accident. - mAudioSession->mErrorStatusCode = 0; - break; - } - } - } - - LL_DEBUGS("Voice") - << " " << LLVoiceClientStatusObserver::status2string(status) - << ", session URI " << getAudioSessionURI() - << ", proximal is " << inSpatialChannel() - << LL_ENDL; + LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpPost( + url, + LLCore::HttpRequest::DEFAULT_POLICY_ID, + body, + boost::bind(&LLVoiceWebRTCConnection::OnVoiceConnectionRequestSuccess, this, _1), + boost::bind(&LLVoiceWebRTCConnection::OnVoiceConnectionRequestFailure, this, url, 3, body, _1)); + return true; +} - for (status_observer_set_t::iterator it = mStatusObservers.begin(); - it != mStatusObservers.end(); - ) - { - LLVoiceClientStatusObserver* observer = *it; - observer->onChange(status, getAudioSessionURI(), inSpatialChannel()); - // In case onError() deleted an entry. - it = mStatusObservers.upper_bound(observer); - } +void LLVoiceWebRTCConnection::OnVoiceConnectionRequestSuccess(const LLSD &result) +{ + if (LLWebRTCVoiceClient::isShuttingDown()) + { + return; + } + LLVoiceWebRTCStats::getInstance()->provisionAttemptEnd(true); - // skipped to avoid speak button blinking - if ( status != LLVoiceClientStatusObserver::STATUS_JOINING - && status != LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL - && status != LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED) - { - bool voice_status = LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking(); + if (result.has("jsep") && result["jsep"].has("type") && result["jsep"]["type"] == "answer" && result["jsep"].has("sdp")) + { + mRemoteChannelSDP = result["jsep"]["sdp"].asString(); + } + std::string voiceAccountServerUri; + std::string voiceUserName = gAgent.getID().asString(); + std::string voicePassword = ""; // no password for now. - gAgent.setVoiceConnected(voice_status); + LL_DEBUGS("Voice") << "ProvisionVoiceAccountRequest response" + << " user " << (voiceUserName.empty() ? "not set" : "set") << " password " + << (voicePassword.empty() ? "not set" : "set") << " channel sdp " << mRemoteChannelSDP << LL_ENDL; - if (voice_status) - { - LLFirstUse::speak(true); - } - } + mWebRTCPeerConnection->AnswerAvailable(mRemoteChannelSDP); } -void LLWebRTCVoiceClient::addObserver(LLFriendObserver* observer) +void LLVoiceWebRTCConnection::OnVoiceConnectionRequestFailure(std::string url, int retries, LLSD body, const LLSD &result) { - mFriendObservers.insert(observer); + if (LLWebRTCVoiceClient::isShuttingDown()) + { + return; + } + if (retries >= 0) + { + LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpPost( + url, + LLCore::HttpRequest::DEFAULT_POLICY_ID, + body, + boost::bind(&LLVoiceWebRTCConnection::OnVoiceConnectionRequestSuccess, this, _1), + boost::bind(&LLVoiceWebRTCConnection::OnVoiceConnectionRequestFailure, this, url, retries - 1, body, _1)); + } + else + { + LL_WARNS("Voice") << "Unable to connect voice." << result << LL_ENDL; + setVoiceConnectionState(VOICE_STATE_SESSION_RETRY); + } } -void LLWebRTCVoiceClient::removeObserver(LLFriendObserver* observer) +bool LLVoiceWebRTCConnection::connectionStateMachine() { - mFriendObservers.erase(observer); -} + U32 retry = 0; + + processIceUpdates(); -void LLWebRTCVoiceClient::notifyFriendObservers() -{ - for (friend_observer_set_t::iterator it = mFriendObservers.begin(); - it != mFriendObservers.end(); - ) - { - LLFriendObserver* observer = *it; - it++; - // The only friend-related thing we notify on is online/offline transitions. - observer->changed(LLFriendObserver::ONLINE); - } -} + switch (getVoiceConnectionState()) + { + case VOICE_STATE_START_SESSION: + { + mTrickling = false; + mIceCompleted = false; + setVoiceConnectionState(VOICE_STATE_WAIT_FOR_SESSION_START); + if (!mWebRTCPeerConnection->initializeConnection()) + { + setVoiceConnectionState(VOICE_STATE_SESSION_RETRY); + } + break; + } + case VOICE_STATE_WAIT_FOR_SESSION_START: + { + break; + } + case VOICE_STATE_REQUEST_CONNECTION: + if (!requestVoiceConnection()) + { + setVoiceConnectionState(VOICE_STATE_SESSION_RETRY); + } + else + { + setVoiceConnectionState(VOICE_STATE_CONNECTION_WAIT); + } + break; + case VOICE_STATE_CONNECTION_WAIT: + break; -void LLWebRTCVoiceClient::lookupName(const LLUUID &id) -{ - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - mAvatarNameCacheConnection = LLAvatarNameCache::get(id, boost::bind(&LLWebRTCVoiceClient::onAvatarNameCache, this, _1, _2)); + case VOICE_STATE_SESSION_ESTABLISHED: + { + LLWebRTCVoiceClient::getInstance()->OnConnectionEstablished(mChannelID); + setVoiceConnectionState(VOICE_STATE_SESSION_UP); + } + break; + case VOICE_STATE_SESSION_UP: + { + if (mShutDown) + { + setVoiceConnectionState(VOICE_STATE_DISCONNECT); + } + break; + } + + case VOICE_STATE_SESSION_RETRY: + LLWebRTCVoiceClient::getInstance()->OnConnectionFailure(mChannelID); + setVoiceConnectionState(VOICE_STATE_DISCONNECT); + break; + break; + + case VOICE_STATE_DISCONNECT: + if (breakVoiceConnection(true)) + { + setVoiceConnectionState(VOICE_STATE_WAIT_FOR_EXIT); + } + else + { + setVoiceConnectionState(VOICE_STATE_SESSION_EXIT); + } + retry = 0; // Connected without issues + break; + + case VOICE_STATE_WAIT_FOR_EXIT: + break; + case VOICE_STATE_SESSION_EXIT: + { + { + LLMutexLock lock(&mVoiceStateMutex); + if (!mShutDown) + { + mVoiceConnectionState = VOICE_STATE_START_SESSION; + } + else + { + return false; + } + } + break; + } + + default: + { + LL_WARNS("Voice") << "Unknown voice control state " << getVoiceConnectionState() << LL_ENDL; + return false; + } + } + return true; } -void LLWebRTCVoiceClient::onAvatarNameCache(const LLUUID& agent_id, - const LLAvatarName& av_name) -{ - mAvatarNameCacheConnection.disconnect(); - std::string display_name = av_name.getDisplayName(); - avatarNameResolved(agent_id, display_name); + +void LLVoiceWebRTCConnection::sendData(const std::string& data) { + if (mWebRTCDataInterface) + { + mWebRTCDataInterface->sendData(data, false); + } } -void LLWebRTCVoiceClient::predAvatarNameResolution(const LLWebRTCVoiceClient::sessionStatePtr_t &session, LLUUID id, std::string name) +bool LLVoiceWebRTCConnection::breakVoiceConnection(bool corowait) { - participantStatePtr_t participant(session->findParticipantByID(id)); - if (participant) + LL_INFOS("Voice") << "Disconnecting voice." << LL_ENDL; + if (mWebRTCDataInterface) { - // Found -- fill in the name - participant->mAccountName = name; - // and post a "participants updated" message to listeners later. - session->mParticipantsChanged = true; + mWebRTCDataInterface->unsetDataObserver(this); + mWebRTCDataInterface = nullptr; + } + mWebRTCAudioInterface = nullptr; + if (mWebRTCPeerConnection) + { + mWebRTCPeerConnection->shutdownConnection(); + } + LLViewerRegion *regionp = LLWorld::instance().getRegionFromID(mRegionID); + if (!regionp || !regionp->capabilitiesReceived()) + { + LL_DEBUGS("Voice") << "no capabilities for voice provisioning; waiting " << LL_ENDL; + return false; } - // Check whether this is a p2p session whose caller name just resolved - if (session->mCallerID == id) + std::string url = regionp->getCapability("ProvisionVoiceAccountRequest"); + if (url.empty()) { - // this session's "caller ID" just resolved. Fill in the name. - session->mName = name; + return false; } -} -void LLWebRTCVoiceClient::avatarNameResolved(const LLUUID &id, const std::string &name) -{ - sessionState::for_each(boost::bind(predAvatarNameResolution, _1, id, name)); -} + LL_DEBUGS("Voice") << "region ready for voice break; url=" << url << LL_ENDL; + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("parcelVoiceInfoRequest", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); -std::string LLWebRTCVoiceClient::sipURIFromID(const LLUUID& id) { return id.asString(); } + LLVoiceWebRTCStats::getInstance()->provisionAttemptStart(); + LLSD body; + body["logout"] = TRUE; + LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpPost( + url, + LLCore::HttpRequest::DEFAULT_POLICY_ID, + body, + boost::bind(&LLVoiceWebRTCConnection::OnVoiceDisconnectionRequestSuccess, this, _1), + boost::bind(&LLVoiceWebRTCConnection::OnVoiceDisconnectionRequestFailure, this, url, 3, body, _1)); + return true; +} -LLWebRTCSecurity::LLWebRTCSecurity() +void LLVoiceWebRTCConnection::OnVoiceDisconnectionRequestSuccess(const LLSD &result) { - // This size is an arbitrary choice; WebRTC does not care - // Use a multiple of three so that there is no '=' padding in the base64 (purely an esthetic choice) - #define WebRTC_TOKEN_BYTES 9 - U8 random_value[WebRTC_TOKEN_BYTES]; - - for (int b = 0; b < WebRTC_TOKEN_BYTES; b++) - { - random_value[b] = ll_rand() & 0xff; - } - mConnectorHandle = LLBase64::encode(random_value, WebRTC_TOKEN_BYTES); - - for (int b = 0; b < WebRTC_TOKEN_BYTES; b++) + if (LLWebRTCVoiceClient::isShuttingDown()) { - random_value[b] = ll_rand() & 0xff; + return; } - mAccountHandle = LLBase64::encode(random_value, WebRTC_TOKEN_BYTES); + setVoiceConnectionState(VOICE_STATE_SESSION_EXIT); } -LLWebRTCSecurity::~LLWebRTCSecurity() +void LLVoiceWebRTCConnection::OnVoiceDisconnectionRequestFailure(std::string url, int retries, LLSD body, const LLSD &result) { -} + if (LLWebRTCVoiceClient::isShuttingDown()) + { + return; + } + if (retries >= 0) + { + LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpPost( + url, + LLCore::HttpRequest::DEFAULT_POLICY_ID, + body, + boost::bind(&LLVoiceWebRTCConnection::OnVoiceConnectionRequestSuccess, this, _1), + boost::bind(&LLVoiceWebRTCConnection::OnVoiceConnectionRequestFailure, this, url, retries - 1, body, _1)); + } + setVoiceConnectionState(VOICE_STATE_SESSION_EXIT); +} \ No newline at end of file diff --git a/indra/newview/llvoicewebrtc.h b/indra/newview/llvoicewebrtc.h index ee7edb3030..456681ed25 100644 --- a/indra/newview/llvoicewebrtc.h +++ b/indra/newview/llvoicewebrtc.h @@ -39,6 +39,7 @@ class LLWebRTCProtocolParser; #include "llcallingcard.h" // for LLFriendObserver #include "lleventcoro.h" #include "llcoros.h" +#include "llparcel.h" #include #include "json/reader.h" @@ -53,14 +54,13 @@ class LLWebRTCProtocolParser; #include class LLAvatarName; +class LLVoiceWebRTCConnection; +typedef boost::shared_ptr connectionPtr_t; class LLWebRTCVoiceClient : public LLSingleton, virtual public LLVoiceModuleInterface, virtual public LLVoiceEffectInterface, - public llwebrtc::LLWebRTCDevicesObserver, - public llwebrtc::LLWebRTCSignalingObserver, - public llwebrtc::LLWebRTCAudioObserver, - public llwebrtc::LLWebRTCDataObserver + public llwebrtc::LLWebRTCDevicesObserver { LLSINGLETON_C11(LLWebRTCVoiceClient); LOG_CLASS(LLWebRTCVoiceClient); @@ -72,6 +72,8 @@ public: //@{ void init(LLPumpIO *pump) override; // Call this once at application startup (creates connector) void terminate() override; // Call this to clean up during shutdown + + static bool isShuttingDown() { return sShuttingDown; } const LLVoiceVersionInfo& getVersion() override; @@ -145,8 +147,12 @@ public: void setNonSpatialChannel(const std::string &uri, const std::string &credentials) override; - bool setSpatialChannel(const std::string &uri, - const std::string &credentials) override; + bool setSpatialChannel(const std::string &uri, const std::string &credentials) override + { + return setSpatialChannel(uri, credentials, INVALID_PARCEL_ID); + } + + bool setSpatialChannel(const std::string &uri, const std::string &credentials, S32 localParcelID); void leaveNonSpatialChannel() override; @@ -163,9 +169,9 @@ public: //@{ // start a voice channel with the specified user void callUser(const LLUUID &uuid) override; - bool isValidChannel(std::string &channelHandle) override; - bool answerInvite(std::string &channelHandle) override; - void declineInvite(std::string &channelHandle) override; + bool isValidChannel(std::string &channelID) override; + bool answerInvite(std::string &channelID) override; + void declineInvite(std::string &channelID) override; //@} ///////////////////////// @@ -232,10 +238,14 @@ public: bool isPreviewPlaying() override { return false; } //@} - // authorize the user - void userAuthorized(const std::string& user_id, - const LLUUID &agentID) override; + void userAuthorized(const std::string &user_id, const LLUUID &agentID) override {}; + + + void OnConnectionEstablished(const std::string& channelID); + void OnConnectionFailure(const std::string &channelID); + void sendPositionAndVolumeUpdate(bool force); + void updateOwnVolume(); ////////////////////////////// /// @name Status notification @@ -247,10 +257,6 @@ public: void addObserver(LLVoiceClientParticipantObserver* observer) override; void removeObserver(LLVoiceClientParticipantObserver* observer) override; //@} - - //@} - - ////////////////////////////// /// @name Devices change notification @@ -260,64 +266,17 @@ public: const llwebrtc::LLWebRTCVoiceDeviceList &capture_devices) override; //@} - ////////////////////////////// - /// @name Signaling notification - // LLWebRTCSignalingObserver - //@{ - void OnIceGatheringState(IceGatheringState state) override; - void OnIceCandidate(const llwebrtc::LLWebRTCIceCandidate &candidate) override; - void OnOfferAvailable(const std::string &sdp) override; - void OnRenegotiationNeeded() override; - void OnAudioEstablished(llwebrtc::LLWebRTCAudioInterface *audio_interface) override; - //@} - - ////////////////////////////// - /// @name Signaling notification - // LLWebRTCAudioObserver - //@{ - //@} - - ///////////////////////// - /// @name Data Notification - /// LLWebRTCDataObserver - //@{ - void OnDataReceived(const std::string& data, bool binary) override; - void OnDataChannelReady(llwebrtc::LLWebRTCDataInterface *data_interface) override; - //@} - - void processIceUpdates(); - void onIceUpdateComplete(bool ice_completed, const LLSD& result); - void onIceUpdateError(int retries, std::string url, LLSD body, bool ice_completed, const LLSD& result); - - - //@} - -protected: - ////////////////////// - // WebRTC Specific definitions - - - enum streamState - { - streamStateUnknown = 0, - streamStateIdle = 1, - streamStateConnected = 2, - streamStateRinging = 3, - streamStateConnecting = 6, // same as WebRTC session_media_connecting enum - streamStateDisconnecting = 7, //Same as WebRTC session_media_disconnecting enum - }; struct participantState { public: participantState(const LLUUID& agent_id); - bool updateMuteState(); // true if mute state has changed + bool updateMuteState(); // true if mute state has changed bool isAvatar(); std::string mURI; LLUUID mAvatarID; - std::string mAccountName; std::string mDisplayName; LLFrameTimer mSpeakingTimeout; F32 mLastSpokeTimestamp; @@ -337,6 +296,12 @@ protected: typedef boost::shared_ptr participantStatePtr_t; typedef boost::weak_ptr participantStateWptr_t; + participantStatePtr_t findParticipantByID(const std::string &channelID, const LLUUID &id); + participantStatePtr_t addParticipantByID(const std::string& channelID, const LLUUID &id); + void removeParticipantByID(const std::string& channelID, const LLUUID &id); + + protected: + typedef std::map participantMap; typedef std::map participantUUIDMap; @@ -348,7 +313,7 @@ protected: typedef boost::function sessionFunc_t; - static ptr_t createSession(); + static ptr_t createSession(const std::string& channelID, S32 parcel_local_id); ~sessionState(); participantStatePtr_t addParticipant(const LLUUID& agent_id); @@ -358,28 +323,37 @@ protected: participantStatePtr_t findParticipant(const std::string &uri); participantStatePtr_t findParticipantByID(const LLUUID& id); - static ptr_t matchSessionByHandle(const std::string &handle); - static ptr_t matchSessionByURI(const std::string &uri); - static ptr_t matchSessionByParticipant(const LLUUID &participant_id); + static ptr_t matchSessionByChannelID(const std::string& channel_id); + + void shutdownAllConnections(); bool isCallBackPossible(); bool isTextIMPossible(); + + void processSessionStates(); + + void OnConnectionEstablished(const std::string &channelID); + void OnConnectionFailure(const std::string &channelID); + + void sendData(const std::string &data); static void for_each(sessionFunc_t func); + static void reapEmptySessions(); + + bool isEmpty() { return mWebRTCConnections.empty(); } + std::string mHandle; std::string mGroupHandle; - std::string mSIPURI; + std::string mChannelID; std::string mAlias; std::string mName; - std::string mAlternateSIPURI; std::string mErrorStatusString; std::queue mTextMsgQueue; LLUUID mIMSessionID; LLUUID mCallerID; int mErrorStatusCode; - int mMediaStreamState; bool mIsChannel; // True for both group and spatial channels (false for p2p, PSTN) bool mIsSpatial; // True for spatial channels bool mIsP2P; @@ -399,18 +373,20 @@ protected: LLUUID mVoiceFontID; static void VerifySessions(); + static void deleteAllSessions(); private: + + std::map mWebRTCConnections; + std::string mPrimaryConnectionID; + sessionState(); - static std::set mSession; // canonical list of outstanding sessions. - std::set::iterator mMyIterator; // used for delete + static std::map mSessions; // canonical list of outstanding sessions. - static void for_eachPredicate(const wptr_t &a, sessionFunc_t func); + static void for_eachPredicate(const std::pair &a, sessionFunc_t func); - static bool testByHandle(const LLWebRTCVoiceClient::sessionState::wptr_t &a, std::string handle); static bool testByCreatingURI(const LLWebRTCVoiceClient::sessionState::wptr_t &a, std::string uri); - static bool testBySIPOrAlterateURI(const LLWebRTCVoiceClient::sessionState::wptr_t &a, std::string uri); static bool testByCallerId(const LLWebRTCVoiceClient::sessionState::wptr_t &a, LLUUID participantId); }; @@ -422,28 +398,20 @@ protected: // Private Member Functions ////////////////////////////////////////////////////// - - + static void predProcessSessionStates(const LLWebRTCVoiceClient::sessionStatePtr_t &session); + static void predOnConnectionEstablished(const LLWebRTCVoiceClient::sessionStatePtr_t &session, std::string channelID); + static void predOnConnectionFailure(const LLWebRTCVoiceClient::sessionStatePtr_t &session, std::string channelID); + static void predSendData(const LLWebRTCVoiceClient::sessionStatePtr_t &session, const std::string& spatial_data, const std::string& volume_data); + static void predUpdateOwnVolume(const LLWebRTCVoiceClient::sessionStatePtr_t &session, F32 audio_level); + static void predSetMuteMic(const LLWebRTCVoiceClient::sessionStatePtr_t &session, bool mute); ////////////////////////////// /// @name TVC/Server management and communication //@{ - // Call this if the connection to the daemon terminates unexpectedly. It will attempt to reset everything and relaunch. - void daemonDied(); // Call this if we're just giving up on voice (can't provision an account, etc.). It will clean up and go away. void giveUp(); - - void connectorCreate(); - void connectorShutdown(); - void closeSocket(void); - // void requestVoiceAccountProvision(S32 retries = 3); - void setLoginInfo( - const std::string& account_name, - const std::string& password, - const std::string& channel_sdp); - void logout(); //@} @@ -486,30 +454,12 @@ protected: ///////////////////////////// BOOL getAreaVoiceDisabled(); // returns true if the area the avatar is in is speech-disabled. // Use this to determine whether to show a "no speech" icon in the menu bar. - - participantStatePtr_t findParticipantByID(const LLUUID& id); - participantStatePtr_t addParticipantByID(const LLUUID &id); - void removeParticipantByID(const LLUUID &id); -#if 0 - //////////////////////////////////////// - // voice sessions. - typedef std::set sessionSet; - - typedef sessionSet::iterator sessionIterator; - sessionIterator sessionsBegin(void); - sessionIterator sessionsEnd(void); -#endif - void sessionEstablished(); - sessionStatePtr_t findSession(const std::string &handle); - sessionStatePtr_t findSession(const LLUUID &participant_id); - - sessionStatePtr_t addSession(const std::string &uri, const std::string &handle = std::string()); - void clearSessionHandle(const sessionStatePtr_t &session); - void setSessionHandle(const sessionStatePtr_t &session, const std::string &handle); - void setSessionURI(const sessionStatePtr_t &session, const std::string &uri); + void sessionEstablished(const LLUUID& region_id); + sessionStatePtr_t findP2PSession(const LLUUID &agent_id); + + sessionStatePtr_t addSession(const std::string& channel_id, S32 parcel_local_id); void deleteSession(const sessionStatePtr_t &session); - void deleteAllSessions(void); void verifySessionState(void); @@ -518,11 +468,7 @@ protected: // This is called in several places where the session _may_ need to be deleted. // It contains logic for whether to delete the session or keep it around. - void reapSession(const sessionStatePtr_t &session); - - // Returns true if the session seems to indicate we've moved to a region on a different voice server - bool sessionNeedsRelog(const sessionStatePtr_t &session); - + void reapSession(const sessionStatePtr_t &session); ////////////////////////////////////// // buddy list stuff, needed for SLIM later @@ -583,41 +529,9 @@ private: // Coroutine support methods //--- - void voiceControlCoro(); - void voiceControlStateMachine(); - - int mVoiceControlState; - LLMutex mVoiceStateMutex; - void setVoiceControlStateUnless(int new_voice_control_state, int unless=-1) - { - LLMutexLock lock(&mVoiceStateMutex); - if (mVoiceControlState != unless) - { - mVoiceControlState = new_voice_control_state; - } - } - int getVoiceControlState() - { - LLMutexLock lock(&mVoiceStateMutex); - return mVoiceControlState; - } - - bool callbackEndDaemon(const LLSD& data); - bool provisionVoiceAccount(); - void OnVoiceAccountProvisioned(const LLSD& body); - void OnVoiceAccountProvisionFailure(std::string url, int retries, LLSD body, const LLSD& result); - bool establishVoiceConnection(); - bool breakVoiceConnection(bool wait); - bool loginToWebRTC(); - void logoutOfWebRTC(bool wait); - - bool requestParcelVoiceInfo(); + void voiceConnectionCoro(); - bool addAndJoinSession(const sessionStatePtr_t &nextSession); - bool terminateAudioSession(bool wait); - - bool waitForChannel(); - bool runSession(const sessionStatePtr_t &session); + void voiceConnectionStateMachine(); bool performMicTuning(); //--- @@ -632,72 +546,38 @@ private: int mSpatialJoiningNum; static void idle(void *user_data); - - LLHost mDaemonHost; - LLSocket::ptr_t mSocket; - - // We should kill the voice daemon in case of connection alert - bool mTerminateDaemon; - - std::string mAccountName; - std::string mAccountPassword; - std::string mChannelSDP; - std::string mRemoteChannelSDP; - std::string mAccountDisplayName; - bool mTuningMode; float mTuningEnergy; - std::string mTuningAudioFile; int mTuningMicVolume; bool mTuningMicVolumeDirty; int mTuningSpeakerVolume; bool mTuningSpeakerVolumeDirty; bool mDevicesListUpdated; // set to true when the device list has been updated // and false when the panelvoicedevicesettings has queried for an update status. - - std::string mSpatialSessionURI; std::string mSpatialSessionCredentials; std::string mMainSessionGroupHandle; // handle of the "main" session group. std::string mChannelName; // Name of the channel to be looked up bool mAreaVoiceDisabled; - sessionStatePtr_t mAudioSession; // Session state for the current audio session + sessionStatePtr_t mAudioSession; // Session state for the current audio session bool mAudioSessionChanged; // set to true when the above pointer gets changed, so observers can be notified. sessionStatePtr_t mNextAudioSession; // Session state for the audio session we're trying to join S32 mCurrentParcelLocalID; // Used to detect parcel boundary crossings std::string mCurrentRegionName; // Used to detect parcel boundary crossings - - bool mConnectorEstablished; // set by "Create Connector" response - bool mAccountLoggedIn; // set by login message - int mNumberOfAliases; - U32 mCommandCookie; - - int mLoginRetryCount; - - sessionMap mSessionsByHandle; // Active sessions, indexed by session handle. Sessions which are being initiated may not be in this map. -#if 0 - sessionSet mSessions; // All sessions, not indexed. This is the canonical session list. -#endif - + bool mBuddyListMapPopulated; bool mBlockRulesListReceived; bool mAutoAcceptRulesListReceived; buddyListMap mBuddyListMap; llwebrtc::LLWebRTCDeviceInterface *mWebRTCDeviceInterface; - llwebrtc::LLWebRTCPeerConnection *mWebRTCPeerConnection; - llwebrtc::LLWebRTCAudioInterface *mWebRTCAudioInterface; - llwebrtc::LLWebRTCDataInterface *mWebRTCDataInterface; LLVoiceDeviceList mCaptureDevices; LLVoiceDeviceList mRenderDevices; - std::vector mIceCandidates; - bool mIceCompleted; - bool mTrickling; uint32_t mAudioLevel; @@ -705,7 +585,12 @@ private: bool mShutdownComplete; bool checkParcelChanged(bool update = false); - bool switchChannel(std::string uri = std::string(), bool spatial = true, bool no_reconnect = false, bool is_p2p = false, std::string hash = ""); + bool switchChannel(const std::string channelID, + bool spatial = true, + bool no_reconnect = false, + bool is_p2p = false, + std::string hash = "", + S32 parcel_local_id = INVALID_PARCEL_ID); void joinSession(const sessionStatePtr_t &session); std::string nameFromAvatar(LLVOAvatar *avatar); @@ -716,11 +601,8 @@ private: bool inSpatialChannel(void); std::string getAudioSessionURI(); - std::string getAudioSessionHandle(); void setHidden(bool hidden) override; //virtual - Json::Value getPositionAndVolumeUpdateJson(bool force); - void sendPositionAndVolumeUpdate(); void enforceTether(void); @@ -759,9 +641,6 @@ private: bool mMicVolumeDirty; bool mVoiceEnabled; - bool mWriteInProgress; - std::string mWriteString; - size_t mWriteOffset; BOOL mLipSyncEnabled; @@ -781,15 +660,13 @@ private: S32 mPlayRequestCount; bool mIsInTuningMode; - bool mIsInChannel; bool mIsJoiningSession; bool mIsWaitingForFonts; bool mIsLoggingIn; - bool mIsLoggedIn; bool mIsProcessingChannels; bool mIsCoroutineActive; - // This variables can last longer than WebRTC in coroutines so we need them as static + // These variables can last longer than WebRTC in coroutines so we need them as static static bool sShuttingDown; static bool sConnected; static LLPumpIO* sPump; @@ -797,19 +674,6 @@ private: LLEventMailDrop mWebRTCPump; }; -class LLWebRTCSecurity : public LLSingleton -{ - LLSINGLETON(LLWebRTCSecurity); - virtual ~LLWebRTCSecurity(); - - public: - std::string connectorHandle() { return mConnectorHandle; }; - std::string accountHandle() { return mAccountHandle; }; - - private: - std::string mConnectorHandle; - std::string mAccountHandle; -}; class LLVoiceWebRTCStats : public LLSingleton { @@ -843,5 +707,119 @@ class LLVoiceWebRTCStats : public LLSingleton LLSD read(); }; +class LLVoiceWebRTCConnection : + public llwebrtc::LLWebRTCSignalingObserver, + public llwebrtc::LLWebRTCDataObserver +{ + public: + LLVoiceWebRTCConnection(const LLUUID& regionID, S32 parcelLocalID, const std::string& channelID); + + virtual ~LLVoiceWebRTCConnection(); + + ////////////////////////////// + /// @name Signaling notification + // LLWebRTCSignalingObserver + //@{ + void OnIceGatheringState(IceGatheringState state) override; + void OnIceCandidate(const llwebrtc::LLWebRTCIceCandidate &candidate) override; + void OnOfferAvailable(const std::string &sdp) override; + void OnRenegotiationNeeded() override; + void OnAudioEstablished(llwebrtc::LLWebRTCAudioInterface *audio_interface) override; + //@} + + ///////////////////////// + /// @name Data Notification + /// LLWebRTCDataObserver + //@{ + void OnDataReceived(const std::string &data, bool binary) override; + void OnDataChannelReady(llwebrtc::LLWebRTCDataInterface *data_interface) override; + //@} + + void processIceUpdates(); + void onIceUpdateComplete(bool ice_completed, const LLSD &result); + void onIceUpdateError(int retries, std::string url, LLSD body, bool ice_completed, const LLSD &result); + + bool requestVoiceConnection(); + void OnVoiceConnectionRequestSuccess(const LLSD &body); + void OnVoiceConnectionRequestFailure(std::string url, int retries, LLSD body, const LLSD &result); + + bool connectionStateMachine(); + + void sendData(const std::string &data); + + void shutDown() + { + LLMutexLock lock(&mVoiceStateMutex); + mShutDown = true; + } + +protected: + typedef enum e_voice_connection_state + { + VOICE_STATE_ERROR = 0x0, + VOICE_STATE_START_SESSION = 0x1, + VOICE_STATE_WAIT_FOR_SESSION_START = 0x2, + VOICE_STATE_REQUEST_CONNECTION = 0x4, + VOICE_STATE_CONNECTION_WAIT = 0x8, + VOICE_STATE_SESSION_ESTABLISHED = 0x10, + VOICE_STATE_SESSION_UP = 0x20, + VOICE_STATE_SESSION_RETRY = 0x40, + VOICE_STATE_DISCONNECT = 0x80, + VOICE_STATE_WAIT_FOR_EXIT = 0x100, + VOICE_STATE_SESSION_EXIT = 0x200, + VOICE_STATE_SESSION_STOPPING = 0x3c0, + VOICE_STATE_SESSION_WAITING = 0x10a + } EVoiceConnectionState; + + EVoiceConnectionState mVoiceConnectionState; + LLMutex mVoiceStateMutex; + void setVoiceConnectionState(EVoiceConnectionState new_voice_connection_state) + { + LLMutexLock lock(&mVoiceStateMutex); + + if (new_voice_connection_state & VOICE_STATE_SESSION_STOPPING) + { + // the new state is shutdown or restart. + mVoiceConnectionState = new_voice_connection_state; + return; + } + if (mVoiceConnectionState & VOICE_STATE_SESSION_STOPPING) + { + // we're currently shutting down or restarting, so ignore any + // state changes. + return; + } + + mVoiceConnectionState = new_voice_connection_state; + } + EVoiceConnectionState getVoiceConnectionState() + { + LLMutexLock lock(&mVoiceStateMutex); + return mVoiceConnectionState; + } + + bool breakVoiceConnection(bool wait); + void OnVoiceDisconnectionRequestSuccess(const LLSD &body); + void OnVoiceDisconnectionRequestFailure(std::string url, int retries, LLSD body, const LLSD &result); + + std::string mChannelSDP; + std::string mRemoteChannelSDP; + + std::string mChannelID; + LLUUID mRegionID; + S32 mParcelLocalID; + + bool mShutDown; + + std::vector mIceCandidates; + bool mIceCompleted; + bool mTrickling; + + llwebrtc::LLWebRTCPeerConnection *mWebRTCPeerConnection; + llwebrtc::LLWebRTCAudioInterface *mWebRTCAudioInterface; + llwebrtc::LLWebRTCDataInterface *mWebRTCDataInterface; +}; + + #endif //LL_WebRTC_VOICE_CLIENT_H -- cgit v1.2.3