diff options
Diffstat (limited to 'indra')
44 files changed, 7269 insertions, 1945 deletions
diff --git a/indra/CMakeLists.txt b/indra/CMakeLists.txt index 500ffa3e8b..422927704a 100644 --- a/indra/CMakeLists.txt +++ b/indra/CMakeLists.txt @@ -54,6 +54,7 @@ add_subdirectory(${LIBS_OPEN_PREFIX}llmessage) add_subdirectory(${LIBS_OPEN_PREFIX}llprimitive) add_subdirectory(${LIBS_OPEN_PREFIX}llrender) add_subdirectory(${LIBS_OPEN_PREFIX}llfilesystem) +add_subdirectory(${LIBS_OPEN_PREFIX}llwebrtc) add_subdirectory(${LIBS_OPEN_PREFIX}llwindow) add_subdirectory(${LIBS_OPEN_PREFIX}llxml) diff --git a/indra/cmake/00-Common.cmake b/indra/cmake/00-Common.cmake index 24534c98d9..26a4162e42 100644 --- a/indra/cmake/00-Common.cmake +++ b/indra/cmake/00-Common.cmake @@ -182,6 +182,10 @@ if (LINUX OR DARWIN) list(APPEND GCC_WARNINGS -Wno-reorder -Wno-non-virtual-dtor ) + if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 13) + list(APPEND GCC_WARNINGS -Wno-unused-but-set-variable -Wno-unused-variable ) + endif() + add_compile_options(${GCC_WARNINGS}) add_compile_options(-m${ADDRESS_SIZE}) endif (LINUX OR DARWIN) diff --git a/indra/cmake/CMakeLists.txt b/indra/cmake/CMakeLists.txt index 05c51c018d..cce1c042bd 100644 --- a/indra/cmake/CMakeLists.txt +++ b/indra/cmake/CMakeLists.txt @@ -64,6 +64,7 @@ set(cmake_SOURCE_FILES ViewerMiscLibs.cmake VisualLeakDetector.cmake LibVLCPlugin.cmake + WebRTC.cmake XmlRpcEpi.cmake xxHash.cmake ZLIBNG.cmake diff --git a/indra/cmake/WebRTC.cmake b/indra/cmake/WebRTC.cmake new file mode 100644 index 0000000000..909a1345ed --- /dev/null +++ b/indra/cmake/WebRTC.cmake @@ -0,0 +1,32 @@ +# -*- cmake -*- +include(Linking) +include(Prebuilt) + +include_guard() + +add_library( ll::webrtc INTERFACE IMPORTED ) +target_include_directories( ll::webrtc SYSTEM INTERFACE "${LIBS_PREBUILT_DIR}/include/webrtc" "${LIBS_PREBUILT_DIR}/include/webrtc/third_party/abseil-cpp") +use_prebuilt_binary(webrtc-shim) + +if (WINDOWS) + target_link_libraries( ll::webrtc INTERFACE webrtc.lib ) +elseif (DARWIN) + FIND_LIBRARY(COREAUDIO_LIBRARY CoreAudio) + FIND_LIBRARY(COREGRAPHICS_LIBRARY CoreGraphics) + FIND_LIBRARY(AUDIOTOOLBOX_LIBRARY AudioToolbox) + FIND_LIBRARY(COREFOUNDATION_LIBRARY CoreFoundation) + FIND_LIBRARY(COCOA_LIBRARY Cocoa) + + target_link_libraries( ll::webrtc INTERFACE + libwebrtc.a + ${COREAUDIO_LIBRARY} + ${AUDIOTOOLBOX_LIBRARY} + ${COREGRAPHICS_LIBRARY} + ${COREFOUNDATION_LIBRARY} + ${COCOA_LIBRARY} + ) +elseif (LINUX) + target_link_libraries( ll::webrtc INTERFACE libwebrtc ) +endif (WINDOWS) + + diff --git a/indra/llmath/llquaternion.h b/indra/llmath/llquaternion.h index 51ce163b4e..609d75a095 100644 --- a/indra/llmath/llquaternion.h +++ b/indra/llmath/llquaternion.h @@ -132,6 +132,7 @@ public: friend LLQuaternion operator~(const LLQuaternion &a); // Returns a* (Conjugate of a) bool operator==(const LLQuaternion &b) const; // Returns a == b bool operator!=(const LLQuaternion &b) const; // Returns a != b + F64 operator[](int idx) const { return mQ[idx]; } friend const LLQuaternion& operator*=(LLQuaternion &a, const LLQuaternion &b); // Returns a * b diff --git a/indra/llwebrtc/CMakeLists.txt b/indra/llwebrtc/CMakeLists.txt new file mode 100644 index 0000000000..29dc1df8d6 --- /dev/null +++ b/indra/llwebrtc/CMakeLists.txt @@ -0,0 +1,60 @@ +# -*- cmake -*- + +# some webrtc headers require C++ 20 +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +include(00-Common) +include(Linking) +include(WebRTC) + +project(llwebrtc) + +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++ -lc++abi") + + +set(llwebrtc_SOURCE_FILES + llwebrtc.cpp + ) + +set(llwebrtc_HEADER_FILES + CMakeLists.txt + llwebrtc.h + llwebrtc_impl.h + ) + +list(APPEND llwebrtc_SOURCE_FILES ${llwebrtc_HEADER_FILES}) + +add_library (llwebrtc SHARED ${llwebrtc_SOURCE_FILES}) + +set_target_properties(llwebrtc PROPERTIES PUBLIC_HEADER llwebrtc.h) + +if (WINDOWS) + target_link_libraries(llwebrtc PRIVATE ll::webrtc + secur32 + winmm + dmoguids + wmcodecdspuuid + msdmo + strmiids + iphlpapi) +elseif (DARWIN) + target_link_libraries(llwebrtc PRIVATE ll::webrtc) +elseif (LINUX) + target_link_libraries(llwebrtc PRIVATE ll::webrtc) +endif (WINDOWS) + +target_include_directories( llwebrtc INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) + +if (WINDOWS) + set_property(TARGET llwebrtc PROPERTY + MSVC_RUNTIME_LIBRARY "MultiThreadedDebug") +endif (WINDOWS) + +ADD_CUSTOM_COMMAND(TARGET llwebrtc POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + $<TARGET_FILE:llwebrtc> + ${SHARED_LIB_STAGING_DIR}) +# Add tests +if (LL_TESTS) +endif (LL_TESTS) diff --git a/indra/llwebrtc/llwebrtc.cpp b/indra/llwebrtc/llwebrtc.cpp new file mode 100644 index 0000000000..875f233e65 --- /dev/null +++ b/indra/llwebrtc/llwebrtc.cpp @@ -0,0 +1,1213 @@ +/** + * @file llwebrtc.cpp + * @brief WebRTC interface implementation + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llwebrtc_impl.h" +#include <algorithm> +#include <format> +#include <string.h> + +#include "api/audio_codecs/audio_decoder_factory.h" +#include "api/audio_codecs/audio_encoder_factory.h" +#include "api/audio_codecs/builtin_audio_decoder_factory.h" +#include "api/audio_codecs/builtin_audio_encoder_factory.h" +#include "api/media_stream_interface.h" +#include "api/media_stream_track.h" +#include "modules/audio_processing/audio_buffer.h" + +namespace llwebrtc +{ + +LLAudioDeviceObserver::LLAudioDeviceObserver() : mSumVector {0}, mMicrophoneEnergy(0.0) {} + +float LLAudioDeviceObserver::getMicrophoneEnergy() { return mMicrophoneEnergy; } + +// TODO: Pull smoothing/filtering code into a common helper function +// for LLAudioDeviceObserver and LLCustomProcessor + +void LLAudioDeviceObserver::OnCaptureData(const void *audio_samples, + const size_t num_samples, + const size_t bytes_per_sample, + const size_t num_channels, + const uint32_t samples_per_sec) +{ + // calculate the energy + float energy = 0; + const short *samples = (const short *) audio_samples; + for (size_t index = 0; index < num_samples * num_channels; index++) + { + float sample = (static_cast<float>(samples[index]) / (float) 32767); + energy += sample * sample; + } + + // smooth it. + size_t buffer_size = sizeof(mSumVector) / sizeof(mSumVector[0]); + float totalSum = 0; + int i; + for (i = 0; i < (buffer_size - 1); i++) + { + mSumVector[i] = mSumVector[i + 1]; + totalSum += mSumVector[i]; + } + mSumVector[i] = energy; + totalSum += energy; + mMicrophoneEnergy = std::sqrt(totalSum / (num_samples * buffer_size)); +} + +void LLAudioDeviceObserver::OnRenderData(const void *audio_samples, + const size_t num_samples, + const size_t bytes_per_sample, + const size_t num_channels, + const uint32_t samples_per_sec) +{ +} + +LLCustomProcessor::LLCustomProcessor() : + mSampleRateHz(0), + mNumChannels(0), + mMicrophoneEnergy(0.0) +{ + memset(mSumVector, 0, sizeof(mSumVector)); +} + +void LLCustomProcessor::Initialize(int sample_rate_hz, int num_channels) +{ + mSampleRateHz = sample_rate_hz; + mNumChannels = num_channels; + memset(mSumVector, 0, sizeof(mSumVector)); +} + +void LLCustomProcessor::Process(webrtc::AudioBuffer *audio_in) +{ + webrtc::StreamConfig stream_config; + stream_config.set_sample_rate_hz(mSampleRateHz); + stream_config.set_num_channels(mNumChannels); + std::vector<float *> frame; + std::vector<float> frame_samples; + + if (audio_in->num_channels() < 1 || audio_in->num_frames() < 480) + { + return; + } + + // grab the input audio + frame_samples.resize(stream_config.num_samples()); + frame.resize(stream_config.num_channels()); + for (size_t ch = 0; ch < stream_config.num_channels(); ++ch) + { + frame[ch] = &(frame_samples)[ch * stream_config.num_frames()]; + } + + audio_in->CopyTo(stream_config, &frame[0]); + + // calculate the energy + float energy = 0; + for (size_t index = 0; index < stream_config.num_samples(); index++) + { + float sample = frame_samples[index]; + energy += sample * sample; + } + + // smooth it. + size_t buffer_size = sizeof(mSumVector) / sizeof(mSumVector[0]); + float totalSum = 0; + int i; + for (i = 0; i < (buffer_size - 1); i++) + { + mSumVector[i] = mSumVector[i + 1]; + totalSum += mSumVector[i]; + } + mSumVector[i] = energy; + totalSum += energy; + mMicrophoneEnergy = std::sqrt(totalSum / (stream_config.num_samples() * buffer_size)); +} + +// +// LLWebRTCImpl implementation +// + +LLWebRTCImpl::LLWebRTCImpl() : + mPeerCustomProcessor(nullptr), + mMute(true), + mPlayoutDevice(0), + mRecordingDevice(0), + mTuningAudioDeviceObserver(nullptr) +{ +} + +void LLWebRTCImpl::init() +{ + RTC_DCHECK(mPeerConnectionFactory); + mPlayoutDevice = 0; + mRecordingDevice = 0; + rtc::InitializeSSL(); + + // Normal logging is rather spammy, so turn it off. + rtc::LogMessage::LogToDebug(rtc::LS_NONE); + rtc::LogMessage::SetLogToStderr(true); + + mTaskQueueFactory = webrtc::CreateDefaultTaskQueueFactory(); + + // Create the native threads. + mNetworkThread = rtc::Thread::CreateWithSocketServer(); + mNetworkThread->SetName("WebRTCNetworkThread", nullptr); + mNetworkThread->Start(); + mWorkerThread = rtc::Thread::Create(); + mWorkerThread->SetName("WebRTCWorkerThread", nullptr); + mWorkerThread->Start(); + mSignalingThread = rtc::Thread::Create(); + mSignalingThread->SetName("WebRTCSignalingThread", nullptr); + mSignalingThread->Start(); + + mTuningAudioDeviceObserver = new LLAudioDeviceObserver; + mWorkerThread->PostTask( + [this]() + { + // Initialize the audio devices on the Worker Thread + mTuningDeviceModule = webrtc::CreateAudioDeviceWithDataObserver( + webrtc::AudioDeviceModule::AudioLayer::kPlatformDefaultAudio, + mTaskQueueFactory.get(), + std::unique_ptr<webrtc::AudioDeviceDataObserver>(mTuningAudioDeviceObserver)); + + mTuningDeviceModule->Init(); + mTuningDeviceModule->SetStereoRecording(true); + mTuningDeviceModule->SetStereoPlayout(true); + mTuningDeviceModule->EnableBuiltInAEC(false); + mTuningDeviceModule->SetAudioDeviceSink(this); + updateDevices(); + }); + + mWorkerThread->BlockingCall( + [this]() + { + // the peer device module doesn't need an observer + // as we pull peer data after audio processing. + mPeerDeviceModule = + webrtc::CreateAudioDeviceWithDataObserver( + webrtc::AudioDeviceModule::AudioLayer::kPlatformDefaultAudio, + mTaskQueueFactory.get(), + nullptr); + mPeerDeviceModule->Init(); + mPeerDeviceModule->SetPlayoutDevice(mPlayoutDevice); + mPeerDeviceModule->SetRecordingDevice(mRecordingDevice); + mPeerDeviceModule->SetStereoRecording(true); + mPeerDeviceModule->SetStereoPlayout(true); + mPeerDeviceModule->EnableBuiltInAEC(false); + mPeerDeviceModule->InitMicrophone(); + mPeerDeviceModule->InitSpeaker(); + mPeerDeviceModule->InitRecording(); + mPeerDeviceModule->InitPlayout(); + }); + + // The custom processor allows us to retrieve audio data (and levels) + // from after other audio processing such as AEC, AGC, etc. + mPeerCustomProcessor = new LLCustomProcessor; + webrtc::AudioProcessingBuilder apb; + apb.SetCapturePostProcessing(std::unique_ptr<webrtc::CustomProcessing>(mPeerCustomProcessor)); + rtc::scoped_refptr<webrtc::AudioProcessing> apm = apb.Create(); + + // TODO: wire some of these to the primary interface and ultimately + // to the UI to allow user config. + webrtc::AudioProcessing::Config apm_config; + apm_config.echo_canceller.enabled = true; + apm_config.echo_canceller.mobile_mode = false; + apm_config.gain_controller1.enabled = true; + apm_config.gain_controller1.mode = webrtc::AudioProcessing::Config::GainController1::kAdaptiveAnalog; + apm_config.gain_controller2.enabled = true; + apm_config.high_pass_filter.enabled = true; + apm_config.noise_suppression.enabled = true; + apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kVeryHigh; + apm_config.transient_suppression.enabled = true; + apm_config.pipeline.multi_channel_render = true; + apm_config.pipeline.multi_channel_capture = true; + apm_config.pipeline.multi_channel_capture = true; + + webrtc::ProcessingConfig processing_config; + processing_config.input_stream().set_num_channels(2); + processing_config.input_stream().set_sample_rate_hz(8000); + processing_config.output_stream().set_num_channels(2); + processing_config.output_stream().set_sample_rate_hz(8000); + processing_config.reverse_input_stream().set_num_channels(2); + processing_config.reverse_input_stream().set_sample_rate_hz(48000); + processing_config.reverse_output_stream().set_num_channels(2); + processing_config.reverse_output_stream().set_sample_rate_hz(48000); + + apm->Initialize(processing_config); + apm->ApplyConfig(apm_config); + + mPeerConnectionFactory = webrtc::CreatePeerConnectionFactory(mNetworkThread.get(), + mWorkerThread.get(), + mSignalingThread.get(), + mPeerDeviceModule, + webrtc::CreateBuiltinAudioEncoderFactory(), + webrtc::CreateBuiltinAudioDecoderFactory(), + nullptr /* video_encoder_factory */, + nullptr /* video_decoder_factory */, + nullptr /* audio_mixer */, + apm); + + + mWorkerThread->BlockingCall( + [this]() + { + mPeerDeviceModule->StartPlayout(); + }); +} + +void LLWebRTCImpl::terminate() +{ + for (auto &connection : mPeerConnections) + { + connection->terminate(); + } + mPeerConnections.clear(); + + mSignalingThread->BlockingCall([this]() { mPeerConnectionFactory = nullptr; }); + mWorkerThread->BlockingCall( + [this]() + { + if (mTuningDeviceModule) + { + mTuningDeviceModule->StopRecording(); + mTuningDeviceModule->Terminate(); + } + if (mPeerDeviceModule) + { + mPeerDeviceModule->StopRecording(); + mPeerDeviceModule->Terminate(); + } + mTuningDeviceModule = nullptr; + mPeerDeviceModule = nullptr; + mTaskQueueFactory = nullptr; + }); +} + +// +// Devices functions +// +// Most device-related functionality needs to happen +// on the worker thread (the audio thread,) so those calls will be +// proxied over to that thread. +// +void LLWebRTCImpl::setRecording(bool recording) +{ + mWorkerThread->PostTask( + [this, recording]() + { + if (recording) + { + mPeerDeviceModule->StartRecording(); + } + else + { + mPeerDeviceModule->StopRecording(); + } + }); +} + +void LLWebRTCImpl::refreshDevices() +{ + mWorkerThread->PostTask([this]() { updateDevices(); }); +} + +void LLWebRTCImpl::setDevicesObserver(LLWebRTCDevicesObserver *observer) { mVoiceDevicesObserverList.emplace_back(observer); } + +void LLWebRTCImpl::unsetDevicesObserver(LLWebRTCDevicesObserver *observer) +{ + std::vector<LLWebRTCDevicesObserver *>::iterator it = + std::find(mVoiceDevicesObserverList.begin(), mVoiceDevicesObserverList.end(), observer); + if (it != mVoiceDevicesObserverList.end()) + { + mVoiceDevicesObserverList.erase(it); + } +} + +// TODO: There's potential for shared code here as the patterns +// are similar. +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++) + { + 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; + tuningRecordingDevice = i; + break; + } + } + mTuningDeviceModule->StopRecording(); + mTuningDeviceModule->SetRecordingDevice(tuningRecordingDevice); + mTuningDeviceModule->InitMicrophone(); + mTuningDeviceModule->InitRecording(); + mTuningDeviceModule->StartRecording(); + if (mPeerDeviceModule) + { + int16_t captureDeviceCount = mPeerDeviceModule->RecordingDevices(); + for (int16_t i = 0; i < captureDeviceCount; i++) + { + char name[webrtc::kAdmMaxDeviceNameSize]; + char guid[webrtc::kAdmMaxGuidSize]; + mPeerDeviceModule->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; + } + } + bool was_peer_recording = mPeerDeviceModule->Recording(); + if (was_peer_recording) + { + mPeerDeviceModule->StopRecording(); + } + mPeerDeviceModule->SetRecordingDevice(mRecordingDevice); + mPeerDeviceModule->InitMicrophone(); + mPeerDeviceModule->InitRecording(); + if (was_peer_recording) + { + mPeerDeviceModule->StartRecording(); + } + } + }); +} + +void LLWebRTCImpl::setRenderDevice(const std::string &id) +{ + mWorkerThread->PostTask( + [this, id]() + { + int16_t renderDeviceCount = mTuningDeviceModule->PlayoutDevices(); + int16_t tuningPlayoutDevice = 0; + for (int16_t i = 0; i < renderDeviceCount; i++) + { + char name[webrtc::kAdmMaxDeviceNameSize]; + char guid[webrtc::kAdmMaxGuidSize]; + mTuningDeviceModule->PlayoutDeviceName(i, name, guid); + if (id == guid || id == "Default") + { + RTC_LOG(LS_INFO) << __FUNCTION__ << "Set playout device to " << name << " " << guid << " " << i; + tuningPlayoutDevice = i; + break; + } + } + bool was_tuning_playing = mTuningDeviceModule->Playing(); + if (was_tuning_playing) + { + mTuningDeviceModule->StopPlayout(); + } + + mTuningDeviceModule->SetPlayoutDevice(tuningPlayoutDevice); + mTuningDeviceModule->InitSpeaker(); + mTuningDeviceModule->InitPlayout(); + if (was_tuning_playing) + { + mTuningDeviceModule->StartPlayout(); + } + + if (mPeerDeviceModule) + { + renderDeviceCount = mPeerDeviceModule->PlayoutDevices(); + 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 playout device to " << name << " " << guid << " " << i; + mPlayoutDevice = i; + break; + } + } + mPeerDeviceModule->StopPlayout(); + mPeerDeviceModule->SetPlayoutDevice(mPlayoutDevice); + mPeerDeviceModule->InitSpeaker(); + mPeerDeviceModule->InitPlayout(); + mPeerDeviceModule->StartPlayout(); + + } + }); +} + +// updateDevices needs to happen on the worker thread. +void LLWebRTCImpl::updateDevices() +{ + int16_t renderDeviceCount = mTuningDeviceModule->PlayoutDevices(); + int16_t currentRenderDeviceIndex = mTuningDeviceModule->GetPlayoutDevice(); + + LLWebRTCVoiceDeviceList renderDeviceList; + for (int16_t index = 0; index < renderDeviceCount; index++) + { + char name[webrtc::kAdmMaxDeviceNameSize]; + char guid[webrtc::kAdmMaxGuidSize]; + mTuningDeviceModule->PlayoutDeviceName(index, name, guid); + renderDeviceList.emplace_back(name, guid, index == currentRenderDeviceIndex); + } + + int16_t captureDeviceCount = mTuningDeviceModule->RecordingDevices(); + int16_t currentCaptureDeviceIndex = mTuningDeviceModule->GetRecordingDevice(); + + LLWebRTCVoiceDeviceList captureDeviceList; + for (int16_t index = 0; index < captureDeviceCount; index++) + { + char name[webrtc::kAdmMaxDeviceNameSize]; + char guid[webrtc::kAdmMaxGuidSize]; + mTuningDeviceModule->RecordingDeviceName(index, name, guid); + captureDeviceList.emplace_back(name, guid, index == currentCaptureDeviceIndex); + } + for (auto &observer : mVoiceDevicesObserverList) + { + observer->OnDevicesChanged(renderDeviceList, + captureDeviceList); + } +} + +void LLWebRTCImpl::OnDevicesUpdated() +{ + updateDevices(); +} + + +void LLWebRTCImpl::setTuningMode(bool enable) +{ + mSignalingThread->PostTask( + [this, enable] + { + for (auto &connection : mPeerConnections) + { + if (enable) + { + connection->enableSenderTracks(false); + } + else + { + connection->resetMute(); + } + connection->enableReceiverTracks(!enable); + } + }); +} + +float LLWebRTCImpl::getTuningAudioLevel() { return -20 * log10f(mTuningAudioDeviceObserver->getMicrophoneEnergy()); } + +float LLWebRTCImpl::getPeerConnectionAudioLevel() { return -20 * log10f(mPeerCustomProcessor->getMicrophoneEnergy()); } + + +// +// Peer Connection Helpers +// + +LLWebRTCPeerConnectionInterface *LLWebRTCImpl::newPeerConnection() +{ + rtc::scoped_refptr<LLWebRTCPeerConnectionImpl> peerConnection = rtc::scoped_refptr<LLWebRTCPeerConnectionImpl>(new rtc::RefCountedObject<LLWebRTCPeerConnectionImpl>()); + peerConnection->init(this); + + mPeerConnections.emplace_back(peerConnection); + peerConnection->enableSenderTracks(!mMute); + return peerConnection.get(); +} + +void LLWebRTCImpl::freePeerConnection(LLWebRTCPeerConnectionInterface* peer_connection) +{ + std::vector<rtc::scoped_refptr<LLWebRTCPeerConnectionImpl>>::iterator it = + std::find(mPeerConnections.begin(), mPeerConnections.end(), peer_connection); + if (it != mPeerConnections.end()) + { + (*it)->terminate(); + mPeerConnections.erase(it); + } + if (mPeerConnections.empty()) + { + setRecording(false); + } +} + + +// +// LLWebRTCPeerConnectionImpl implementation. +// +// Most peer connection (signaling) happens on +// the signaling thread. + +LLWebRTCPeerConnectionImpl::LLWebRTCPeerConnectionImpl() : + mWebRTCImpl(nullptr), + mMute(false), + mAnswerReceived(false) +{ +} + +// +// LLWebRTCPeerConnection interface +// + +void LLWebRTCPeerConnectionImpl::init(LLWebRTCImpl * webrtc_impl) +{ + mWebRTCImpl = webrtc_impl; + mPeerConnectionFactory = mWebRTCImpl->getPeerConnectionFactory(); +} +void LLWebRTCPeerConnectionImpl::terminate() +{ + mWebRTCImpl->SignalingBlockingCall( + [this]() + { + if (mPeerConnection) + { + mPeerConnection->Close(); + mPeerConnection = nullptr; + } + }); +} + +void LLWebRTCPeerConnectionImpl::setSignalingObserver(LLWebRTCSignalingObserver *observer) { mSignalingObserverList.emplace_back(observer); } + +void LLWebRTCPeerConnectionImpl::unsetSignalingObserver(LLWebRTCSignalingObserver *observer) +{ + std::vector<LLWebRTCSignalingObserver *>::iterator it = + std::find(mSignalingObserverList.begin(), mSignalingObserverList.end(), observer); + if (it != mSignalingObserverList.end()) + { + mSignalingObserverList.erase(it); + } +} + +// TODO: Add initialization structure through which +// stun and turn servers may be passed in from +// the sim or login. +bool LLWebRTCPeerConnectionImpl::initializeConnection() +{ + RTC_DCHECK(!mPeerConnection); + mAnswerReceived = false; + + mWebRTCImpl->PostSignalingTask( + [this]() + { + webrtc::PeerConnectionInterface::RTCConfiguration config; + config.sdp_semantics = webrtc::SdpSemantics::kUnifiedPlan; + webrtc::PeerConnectionInterface::IceServer server; + server.uri = "stun:roxie-turn.staging.secondlife.io:3478"; + config.servers.push_back(server); + server.uri = "stun:stun.l.google.com:19302"; + config.servers.push_back(server); + server.uri = "stun:stun1.l.google.com:19302"; + config.servers.push_back(server); + server.uri = "stun:stun2.l.google.com:19302"; + config.servers.push_back(server); + server.uri = "stun:stun3.l.google.com:19302"; + config.servers.push_back(server); + server.uri = "stun:stun4.l.google.com:19302"; + config.servers.push_back(server); + + config.set_min_port(60000); + config.set_max_port(60100); + + webrtc::PeerConnectionDependencies pc_dependencies(this); + auto error_or_peer_connection = mPeerConnectionFactory->CreatePeerConnectionOrError(config, std::move(pc_dependencies)); + if (error_or_peer_connection.ok()) + { + mPeerConnection = std::move(error_or_peer_connection.value()); + } + else + { + RTC_LOG(LS_ERROR) << __FUNCTION__ << "Error creating peer connection: " << error_or_peer_connection.error().message(); + return; + } + + webrtc::DataChannelInit init; + init.ordered = true; + + auto data_channel_or_error = mPeerConnection->CreateDataChannelOrError("SLData", &init); + if (data_channel_or_error.ok()) + { + mDataChannel = std::move(data_channel_or_error.value()); + + mDataChannel->RegisterObserver(this); + } + + cricket::AudioOptions audioOptions; + audioOptions.auto_gain_control = true; + audioOptions.echo_cancellation = true; // incompatible with opus stereo + audioOptions.noise_suppression = true; + + mLocalStream = mPeerConnectionFactory->CreateLocalMediaStream("SLStream"); + + rtc::scoped_refptr<webrtc::AudioTrackInterface> audio_track( + mPeerConnectionFactory->CreateAudioTrack("SLAudio", mPeerConnectionFactory->CreateAudioSource(audioOptions).get())); + audio_track->set_enabled(true); + mLocalStream->AddTrack(audio_track); + + mPeerConnection->AddTrack(audio_track, {"SLStream"}); + + auto senders = mPeerConnection->GetSenders(); + + for (auto &sender : senders) + { + webrtc::RtpParameters params; + webrtc::RtpCodecParameters codecparam; + codecparam.name = "opus"; + codecparam.kind = cricket::MEDIA_TYPE_AUDIO; + codecparam.clock_rate = 48000; + codecparam.num_channels = 2; + codecparam.parameters["stereo"] = "1"; + codecparam.parameters["sprop-stereo"] = "1"; + params.codecs.push_back(codecparam); + sender->SetParameters(params); + } + + auto receivers = mPeerConnection->GetReceivers(); + for (auto &receiver : receivers) + { + webrtc::RtpParameters params; + webrtc::RtpCodecParameters codecparam; + codecparam.name = "opus"; + codecparam.kind = cricket::MEDIA_TYPE_AUDIO; + codecparam.clock_rate = 48000; + codecparam.num_channels = 2; + codecparam.parameters["stereo"] = "1"; + codecparam.parameters["sprop-stereo"] = "1"; + params.codecs.push_back(codecparam); + receiver->SetParameters(params); + } + + webrtc::PeerConnectionInterface::RTCOfferAnswerOptions offerOptions; + mPeerConnection->CreateOffer(this, offerOptions); + }); + + return true; +} + +bool LLWebRTCPeerConnectionImpl::shutdownConnection() +{ + if (mPeerConnection) + { + mWebRTCImpl->PostSignalingTask( + [this]() + { + if (mPeerConnection) + { + mPeerConnection->Close(); + mPeerConnection = nullptr; + } + for (auto &observer : mSignalingObserverList) + { + observer->OnPeerConnectionShutdown(); + } + }); + return true; + } + return false; +} + +void LLWebRTCPeerConnectionImpl::enableSenderTracks(bool enable) +{ + // set_enabled shouldn't be done on the worker thread. + if (mPeerConnection) + { + auto senders = mPeerConnection->GetSenders(); + for (auto &sender : senders) + { + sender->track()->set_enabled(enable); + } + } +} + +void LLWebRTCPeerConnectionImpl::enableReceiverTracks(bool enable) +{ + // set_enabled shouldn't be done on the worker thread + if (mPeerConnection) + { + auto receivers = mPeerConnection->GetReceivers(); + for (auto &receiver : receivers) + { + receiver->track()->set_enabled(enable); + } + } +} + +// Tell the peer connection that we've received a SDP answer from the sim. +void LLWebRTCPeerConnectionImpl::AnswerAvailable(const std::string &sdp) +{ + RTC_LOG(LS_INFO) << __FUNCTION__ << " Remote SDP: " << sdp; + + mWebRTCImpl->PostSignalingTask( + [this, sdp]() + { + if (mPeerConnection) + { + RTC_LOG(LS_INFO) << __FUNCTION__ << " " << mPeerConnection->peer_connection_state(); + mPeerConnection->SetRemoteDescription(webrtc::CreateSessionDescription(webrtc::SdpType::kAnswer, sdp), + rtc::scoped_refptr<webrtc::SetRemoteDescriptionObserverInterface>(this)); + } + }); +} + + +// +// LLWebRTCAudioInterface implementation +// + +void LLWebRTCPeerConnectionImpl::setMute(bool mute) +{ + mMute = mute; + mWebRTCImpl->PostSignalingTask( + [this]() + { + if (mPeerConnection) + { + auto senders = mPeerConnection->GetSenders(); + + RTC_LOG(LS_INFO) << __FUNCTION__ << (mMute ? "disabling" : "enabling") << " streams count " << senders.size(); + for (auto &sender : senders) + { + auto track = sender->track(); + if (track) + { + track->set_enabled(!mMute); + } + } + } + }); +} + +void LLWebRTCPeerConnectionImpl::resetMute() +{ + setMute(mMute); +} + +void LLWebRTCPeerConnectionImpl::setReceiveVolume(float volume) +{ + mWebRTCImpl->PostSignalingTask( + [this, volume]() + { + if (mPeerConnection) + { + auto receivers = mPeerConnection->GetReceivers(); + + for (auto &receiver : receivers) + { + for (auto &stream : receiver->streams()) + { + for (auto &track : stream->GetAudioTracks()) + { + track->GetSource()->SetVolume(volume); + } + } + } + } + }); +} + +void LLWebRTCPeerConnectionImpl::setSendVolume(float volume) +{ + mWebRTCImpl->PostSignalingTask( + [this, volume]() + { + if (mLocalStream) + { + for (auto &track : mLocalStream->GetAudioTracks()) + { + track->GetSource()->SetVolume(volume); + } + } + }); +} + +// +// PeerConnectionObserver implementation. +// + +void LLWebRTCPeerConnectionImpl::OnAddTrack(rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver, + const std::vector<rtc::scoped_refptr<webrtc::MediaStreamInterface>> &streams) +{ + RTC_LOG(LS_INFO) << __FUNCTION__ << " " << receiver->id(); + webrtc::RtpParameters params; + webrtc::RtpCodecParameters codecparam; + codecparam.name = "opus"; + codecparam.kind = cricket::MEDIA_TYPE_AUDIO; + codecparam.clock_rate = 48000; + codecparam.num_channels = 2; + codecparam.parameters["stereo"] = "1"; + codecparam.parameters["sprop-stereo"] = "1"; + params.codecs.push_back(codecparam); + receiver->SetParameters(params); +} + +void LLWebRTCPeerConnectionImpl::OnRemoveTrack(rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver) +{ + RTC_LOG(LS_INFO) << __FUNCTION__ << " " << receiver->id(); +} + +void LLWebRTCPeerConnectionImpl::OnDataChannel(rtc::scoped_refptr<webrtc::DataChannelInterface> channel) +{ + mDataChannel = channel; + channel->RegisterObserver(this); +} + +void LLWebRTCPeerConnectionImpl::OnIceGatheringChange(webrtc::PeerConnectionInterface::IceGatheringState new_state) +{ + LLWebRTCSignalingObserver::EIceGatheringState webrtc_new_state = LLWebRTCSignalingObserver::EIceGatheringState::ICE_GATHERING_NEW; + switch (new_state) + { + case webrtc::PeerConnectionInterface::IceGatheringState::kIceGatheringNew: + webrtc_new_state = LLWebRTCSignalingObserver::EIceGatheringState::ICE_GATHERING_NEW; + break; + case webrtc::PeerConnectionInterface::IceGatheringState::kIceGatheringGathering: + webrtc_new_state = LLWebRTCSignalingObserver::EIceGatheringState::ICE_GATHERING_GATHERING; + break; + case webrtc::PeerConnectionInterface::IceGatheringState::kIceGatheringComplete: + webrtc_new_state = LLWebRTCSignalingObserver::EIceGatheringState::ICE_GATHERING_COMPLETE; + break; + default: + RTC_LOG(LS_ERROR) << __FUNCTION__ << " Bad Ice Gathering State" << new_state; + webrtc_new_state = LLWebRTCSignalingObserver::EIceGatheringState::ICE_GATHERING_NEW; + return; + } + + if (mAnswerReceived) + { + for (auto &observer : mSignalingObserverList) + { + observer->OnIceGatheringState(webrtc_new_state); + } + } +} + +// Called any time the PeerConnectionState changes. +void LLWebRTCPeerConnectionImpl::OnConnectionChange(webrtc::PeerConnectionInterface::PeerConnectionState new_state) +{ + RTC_LOG(LS_ERROR) << __FUNCTION__ << " Peer Connection State Change " << new_state; + + switch (new_state) + { + case webrtc::PeerConnectionInterface::PeerConnectionState::kConnected: + { + mWebRTCImpl->setRecording(true); + + mWebRTCImpl->PostWorkerTask([this]() { + for (auto &observer : mSignalingObserverList) + { + observer->OnAudioEstablished(this); + } + }); + break; + } + case webrtc::PeerConnectionInterface::PeerConnectionState::kFailed: + case webrtc::PeerConnectionInterface::PeerConnectionState::kDisconnected: + { + for (auto &observer : mSignalingObserverList) + { + observer->OnRenegotiationNeeded(); + } + + break; + } + default: + { + break; + } + } +} + +// Convert an ICE candidate into a string appropriate for trickling +// to the Secondlife WebRTC server via the sim. +static std::string iceCandidateToTrickleString(const webrtc::IceCandidateInterface *candidate) +{ + std::ostringstream candidate_stream; + + candidate_stream << + candidate->candidate().foundation() << " " << + std::to_string(candidate->candidate().component()) << " " << + candidate->candidate().protocol() << " " << + std::to_string(candidate->candidate().priority()) << " " << + candidate->candidate().address().ipaddr().ToString() << " " << + candidate->candidate().address().PortAsString() << " typ "; + + if (candidate->candidate().type() == cricket::LOCAL_PORT_TYPE) + { + candidate_stream << "host"; + } + else if (candidate->candidate().type() == cricket::STUN_PORT_TYPE) + { + candidate_stream << "srflx " << + "raddr " << candidate->candidate().related_address().ipaddr().ToString() << " " << + "rport " << candidate->candidate().related_address().PortAsString(); + } + else if (candidate->candidate().type() == cricket::RELAY_PORT_TYPE) + { + candidate_stream << "relay " << + "raddr " << candidate->candidate().related_address().ipaddr().ToString() << " " << + "rport " << candidate->candidate().related_address().PortAsString(); + } + else if (candidate->candidate().type() == cricket::PRFLX_PORT_TYPE) + { + candidate_stream << "prflx " << + "raddr " << candidate->candidate().related_address().ipaddr().ToString() << " " << + "rport " << candidate->candidate().related_address().PortAsString(); + } + else { + RTC_LOG(LS_ERROR) << __FUNCTION__ << " Unknown candidate type " << candidate->candidate().type(); + } + if (candidate->candidate().protocol() == "tcp") + { + candidate_stream << " tcptype " << candidate->candidate().tcptype(); + } + + return candidate_stream.str(); +} + +// The webrtc library has a new ice candidate. +void LLWebRTCPeerConnectionImpl::OnIceCandidate(const webrtc::IceCandidateInterface *candidate) +{ + RTC_LOG(LS_INFO) << __FUNCTION__ << " " << candidate->sdp_mline_index(); + + if (!candidate) + { + RTC_LOG(LS_ERROR) << __FUNCTION__ << " No Ice Candidate Given"; + return; + } + if (mAnswerReceived) + { + // We've already received an answer SDP from the Secondlife WebRTC server + // so simply tell observers about our new ice candidate. + for (auto &observer : mSignalingObserverList) + { + LLWebRTCIceCandidate ice_candidate; + ice_candidate.mCandidate = iceCandidateToTrickleString(candidate); + ice_candidate.mMLineIndex = candidate->sdp_mline_index(); + ice_candidate.mSdpMid = candidate->sdp_mid(); + observer->OnIceCandidate(ice_candidate); + } + } + else + { + // As we've not yet received our answer, cache the candidate. + mCachedIceCandidates.push_back( + webrtc::CreateIceCandidate(candidate->sdp_mid(), + candidate->sdp_mline_index(), + candidate->candidate())); + } +} + +// +// CreateSessionDescriptionObserver implementation. +// +void LLWebRTCPeerConnectionImpl::OnSuccess(webrtc::SessionDescriptionInterface *desc) +{ + std::string sdp; + desc->ToString(&sdp); + RTC_LOG(LS_INFO) << sdp; + ; + // mangle the sdp as this is the only way currently to bump up + // the send audio rate to 48k + std::istringstream sdp_stream(sdp); + std::ostringstream sdp_mangled_stream; + std::string sdp_line; + std::string opus_payload; + while (std::getline(sdp_stream, sdp_line)) + { + int bandwidth = 0; + int payload_id = 0; + // force mono down, stereo up + if (std::sscanf(sdp_line.c_str(), "a=rtpmap:%i opus/%i/2", &payload_id, &bandwidth) == 2) + { + opus_payload = std::to_string(payload_id); + sdp_mangled_stream << "a=rtpmap:" << opus_payload << " opus/48000/2" << "\n"; + } + else if (sdp_line.find("a=fmtp:" + opus_payload) == 0) + { + sdp_mangled_stream << sdp_line << "a=fmtp:" << opus_payload + << " minptime=10;useinbandfec=1;stereo=1;sprop-stereo=1;maxplaybackrate=48000;sprop-maxplaybackrate=48000;sprop-maxcapturerate=48000\n"; + } + else + { + sdp_mangled_stream << sdp_line << "\n"; + } + } + + RTC_LOG(LS_INFO) << __FUNCTION__ << " Local SDP: " << sdp_mangled_stream.str(); + + for (auto &observer : mSignalingObserverList) + { + observer->OnOfferAvailable(sdp_mangled_stream.str()); + } + + mPeerConnection->SetLocalDescription(std::unique_ptr<webrtc::SessionDescriptionInterface>(webrtc::CreateSessionDescription(webrtc::SdpType::kOffer, sdp_mangled_stream.str())), + rtc::scoped_refptr<webrtc::SetLocalDescriptionObserverInterface>(this)); +} + +void LLWebRTCPeerConnectionImpl::OnFailure(webrtc::RTCError error) +{ + RTC_LOG(LS_ERROR) << ToString(error.type()) << ": " << error.message(); +} + +// +// SetRemoteDescriptionObserverInterface implementation. +// +void LLWebRTCPeerConnectionImpl::OnSetRemoteDescriptionComplete(webrtc::RTCError error) +{ + // we've received an answer SDP from the sim. + + RTC_LOG(LS_INFO) << __FUNCTION__ << " " << mPeerConnection->signaling_state(); + if (!error.ok()) + { + RTC_LOG(LS_ERROR) << ToString(error.type()) << ": " << error.message(); + return; + } + mAnswerReceived = true; + + // tell the observers about any cached ICE candidates. + for (auto &observer : mSignalingObserverList) + { + for (auto &candidate : mCachedIceCandidates) + { + LLWebRTCIceCandidate ice_candidate; + ice_candidate.mCandidate = iceCandidateToTrickleString(candidate.get()); + ice_candidate.mMLineIndex = candidate->sdp_mline_index(); + ice_candidate.mSdpMid = candidate->sdp_mid(); + observer->OnIceCandidate(ice_candidate); + } + } + mCachedIceCandidates.clear(); + OnIceGatheringChange(mPeerConnection->ice_gathering_state()); + +} + +// +// SetLocalDescriptionObserverInterface implementation. +// +void LLWebRTCPeerConnectionImpl::OnSetLocalDescriptionComplete(webrtc::RTCError error) +{ +} + +// +// DataChannelObserver implementation +// + +void LLWebRTCPeerConnectionImpl::OnStateChange() +{ + RTC_LOG(LS_INFO) << __FUNCTION__ << " Data Channel State: " << webrtc::DataChannelInterface::DataStateString(mDataChannel->state()); + switch (mDataChannel->state()) + { + case webrtc::DataChannelInterface::kOpen: + RTC_LOG(LS_INFO) << __FUNCTION__ << " Data Channel State Open"; + for (auto &observer : mSignalingObserverList) + { + observer->OnDataChannelReady(this); + } + break; + case webrtc::DataChannelInterface::kConnecting: + RTC_LOG(LS_INFO) << __FUNCTION__ << " Data Channel State Connecting"; + break; + case webrtc::DataChannelInterface::kClosing: + RTC_LOG(LS_INFO) << __FUNCTION__ << " Data Channel State closing"; + break; + case webrtc::DataChannelInterface::kClosed: + RTC_LOG(LS_INFO) << __FUNCTION__ << " Data Channel State closed"; + break; + default: + break; + } +} + +void LLWebRTCPeerConnectionImpl::OnMessage(const webrtc::DataBuffer& buffer) +{ + std::string data((const char*)buffer.data.cdata(), buffer.size()); + for (auto &observer : mDataObserverList) + { + observer->OnDataReceived(data, buffer.binary); + } +} + +// +// LLWebRTCDataInterface +// + +void LLWebRTCPeerConnectionImpl::sendData(const std::string& data, bool binary) +{ + 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); +} + +void LLWebRTCPeerConnectionImpl::unsetDataObserver(LLWebRTCDataObserver* observer) +{ + std::vector<LLWebRTCDataObserver *>::iterator it = + std::find(mDataObserverList.begin(), mDataObserverList.end(), observer); + if (it != mDataObserverList.end()) + { + mDataObserverList.erase(it); + } +} + +LLWebRTCImpl * gWebRTCImpl = nullptr; +LLWebRTCDeviceInterface * getDeviceInterface() +{ + return gWebRTCImpl; +} + +LLWebRTCPeerConnectionInterface* newPeerConnection() +{ + return gWebRTCImpl->newPeerConnection(); +} + +void freePeerConnection(LLWebRTCPeerConnectionInterface* peer_connection) +{ + gWebRTCImpl->freePeerConnection(peer_connection); +} + + +void init() +{ + gWebRTCImpl = new LLWebRTCImpl(); + gWebRTCImpl->init(); +} + +void terminate() +{ + if (gWebRTCImpl) + { + gWebRTCImpl->terminate(); + gWebRTCImpl = nullptr; + } +} + +} // namespace llwebrtc diff --git a/indra/llwebrtc/llwebrtc.h b/indra/llwebrtc/llwebrtc.h new file mode 100644 index 0000000000..43b48e79ab --- /dev/null +++ b/indra/llwebrtc/llwebrtc.h @@ -0,0 +1,235 @@ +/** + * @file llwebrtc.h + * @brief WebRTC interface + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free tSoftware + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +/* + * llwebrtc wraps the native webrtc c++ library in a dynamic library with a simlified interface + * so that the viewer can use it. This is done because native webrtc has a different + * overall threading model than the viewer. + * The native webrtc library is also compiled with clang, and has memory management + * functions that conflict namespace-wise with those in the viewer. + * + * Due to these differences, code from the viewer cannot be pulled in to this + * dynamic library, so it remains very simple. + */ + +#ifndef LLWEBRTC_H +#define LLWEBRTC_H + +#include <string> +#include <vector> + +#ifdef LL_MAKEDLL +#ifdef WEBRTC_WIN +#define LLSYMEXPORT __declspec(dllexport) +#elif WEBRTC_LINUX +#define LLSYMEXPORT __attribute__((visibility("default"))) +#else +#define LLSYMEXPORT /**/ +#endif +#else +#define LLSYMEXPORT /**/ +#endif // LL_MAKEDLL + +namespace llwebrtc +{ + +// LLWebRTCVoiceDevice is a simple representation of the +// components of a device, used to communicate this +// information to the viewer. + + +// A note on threading. +// Native WebRTC has it's own threading model. Some discussion +// can be found here (https://webrtc.github.io/webrtc-org/native-code/native-apis/) +// +// Note that all callbacks to observers will occurr on one of the WebRTC native threads +// (signaling, worker, etc.) Care should be taken to assure there are not +// bad interactions with the viewer threads. + +class LLWebRTCVoiceDevice +{ + public: + std::string mDisplayName; // friendly name for user interface purposes + std::string mID; // internal value for selection + bool mCurrent; // current device + + LLWebRTCVoiceDevice(const std::string &display_name, const std::string &id, bool current) : + mDisplayName(display_name), + mID(id), + mCurrent(current) + {}; +}; + +typedef std::vector<LLWebRTCVoiceDevice> LLWebRTCVoiceDeviceList; + + +// The LLWebRTCDeviceObserver should be implemented by the viewer +// webrtc module, which will receive notifications when devices +// change (are unplugged, etc.) +class LLWebRTCDevicesObserver +{ + public: + virtual void OnDevicesChanged(const LLWebRTCVoiceDeviceList &render_devices, + const LLWebRTCVoiceDeviceList &capture_devices) = 0; +}; + + +// The LLWebRTCDeviceInterface provides a way for the viewer +// to enumerate, set, and get notifications of changes +// for both capture (microphone) and render (speaker) +// devices. +class LLWebRTCDeviceInterface +{ + public: + + // instructs webrtc to refresh the device list. + virtual void refreshDevices() = 0; + + // set the capture and render devices using the unique identifier for the device + virtual void setCaptureDevice(const std::string& id) = 0; + virtual void setRenderDevice(const std::string& id) = 0; + + // Device observers for device change callbacks. + virtual void setDevicesObserver(LLWebRTCDevicesObserver *observer) = 0; + virtual void unsetDevicesObserver(LLWebRTCDevicesObserver *observer) = 0; + + // tuning and audio levels + virtual void setTuningMode(bool enable) = 0; + virtual float getTuningAudioLevel() = 0; // for use during tuning + virtual float getPeerConnectionAudioLevel() = 0; // for use when not tuning +}; + +// LLWebRTCAudioInterface provides the viewer with a way +// to set audio characteristics (mute, send and receive volume) +class LLWebRTCAudioInterface +{ + public: + virtual void setMute(bool mute) = 0; + virtual void setReceiveVolume(float volume) = 0; // volume between 0.0 and 1.0 + virtual void setSendVolume(float volume) = 0; // volume between 0.0 and 1.0 +}; + +// LLWebRTCDataObserver allows the viewer voice module to be notified when +// data is received over the data channel. +class LLWebRTCDataObserver +{ +public: + virtual void OnDataReceived(const std::string& data, bool binary) = 0; +}; + +// LLWebRTCDataInterface allows the viewer to send data over the data channel. +class LLWebRTCDataInterface +{ +public: + + virtual void sendData(const std::string& data, bool binary=false) = 0; + + virtual void setDataObserver(LLWebRTCDataObserver *observer) = 0; + virtual void unsetDataObserver(LLWebRTCDataObserver *observer) = 0; +}; + +// LLWebRTCIceCandidate is a basic structure containing +// information needed for ICE trickling. +struct LLWebRTCIceCandidate +{ + std::string mCandidate; + std::string mSdpMid; + int mMLineIndex; +}; + +// LLWebRTCSignalingObserver provides a way for the native +// webrtc library to notify the viewer voice module of +// various state changes. +class LLWebRTCSignalingObserver +{ + public: + + typedef enum e_ice_gathering_state { + ICE_GATHERING_NEW, + ICE_GATHERING_GATHERING, + ICE_GATHERING_COMPLETE + } EIceGatheringState; + + // Called when ICE gathering states have changed. + // This may be called at any time, as ICE gathering + // can be redone while a connection is up. + virtual void OnIceGatheringState(EIceGatheringState state) = 0; + + // Called when a new ice candidate is available. + virtual void OnIceCandidate(const LLWebRTCIceCandidate& candidate) = 0; + + // Called when an offer is available after a connection is requested. + virtual void OnOfferAvailable(const std::string& sdp) = 0; + + // Called when a connection enters a failure state and renegotiation is needed. + virtual void OnRenegotiationNeeded() = 0; + + // Called when the audio channel has been established and audio + // can begin. + virtual void OnAudioEstablished(LLWebRTCAudioInterface *audio_interface) = 0; + + // Called when the data channel has been established and data + // transfer can begin. + virtual void OnDataChannelReady(LLWebRTCDataInterface *data_interface) = 0; + + // Called when a peer connection has finished shutting down. + virtual void OnPeerConnectionShutdown() = 0; +}; + + +// LLWebRTCPeerConnectionInterface representsd a connection to a peer, +// in most cases a Secondlife WebRTC server. This interface +// allows for management of this peer connection. +class LLWebRTCPeerConnectionInterface +{ + public: + + virtual void setSignalingObserver(LLWebRTCSignalingObserver* observer) = 0; + virtual void unsetSignalingObserver(LLWebRTCSignalingObserver* observer) = 0; + + virtual bool initializeConnection() = 0; + virtual bool shutdownConnection() = 0; + virtual void AnswerAvailable(const std::string &sdp) = 0; +}; + +// The following define the dynamic linked library +// exports. + +// This library must be initialized before use. +LLSYMEXPORT void init(); + +// And should be terminated as part of shutdown. +LLSYMEXPORT void terminate(); + +// Return an interface for device management. +LLSYMEXPORT LLWebRTCDeviceInterface* getDeviceInterface(); + +// Allocate and free peer connections. +LLSYMEXPORT LLWebRTCPeerConnectionInterface* newPeerConnection(); +LLSYMEXPORT void freePeerConnection(LLWebRTCPeerConnectionInterface *connection); +} + +#endif // LLWEBRTC_H diff --git a/indra/llwebrtc/llwebrtc_impl.h b/indra/llwebrtc/llwebrtc_impl.h new file mode 100644 index 0000000000..16afec061e --- /dev/null +++ b/indra/llwebrtc/llwebrtc_impl.h @@ -0,0 +1,364 @@ +/** + * @file llwebrtc_impl.h + * @brief WebRTC dynamic library implementation header + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLWEBRTC_IMPL_H +#define LLWEBRTC_IMPL_H + +#define LL_MAKEDLL +#if defined(_WIN32) || defined(_WIN64) +#define WEBRTC_WIN 1 +#elif defined(__APPLE__) +#define WEBRTC_MAC 1 +#define WEBRTC_POSIX 1 +#elif __linux__ +#define WEBRTC_LINUX 1 +#endif + +#include "llwebrtc.h" +// WebRTC Includes +#ifdef WEBRTC_WIN +#pragma warning(disable : 4996) +#pragma warning(disable : 4068) +#endif // WEBRTC_WIN + +#include "api/scoped_refptr.h" +#include "rtc_base/ref_count.h" +#include "rtc_base/ref_counted_object.h" +#include "rtc_base/ssl_adapter.h" +#include "rtc_base/thread.h" +#include "api/peer_connection_interface.h" +#include "api/media_stream_interface.h" +#include "api/create_peerconnection_factory.h" +#include "modules/audio_device/include/audio_device.h" +#include "modules/audio_device/include/audio_device_data_observer.h" +#include "rtc_base/task_queue.h" +#include "api/task_queue/task_queue_factory.h" +#include "api/task_queue/default_task_queue_factory.h" +#include "modules/audio_device/include/audio_device_defines.h" + + +namespace llwebrtc +{ + +class LLWebRTCPeerConnectionImpl; + + +// Implements a class allowing capture of audio data +// to determine audio level of the microphone. +class LLAudioDeviceObserver : public webrtc::AudioDeviceDataObserver +{ + public: + LLAudioDeviceObserver(); + + // Retrieve the RMS audio loudness + float getMicrophoneEnergy(); + + // Data retrieved from the caputure device is + // passed in here for processing. + void OnCaptureData(const void *audio_samples, + const size_t num_samples, + const size_t bytes_per_sample, + const size_t num_channels, + const uint32_t samples_per_sec) override; + + // This is for data destined for the render device. + // not currently used. + void OnRenderData(const void *audio_samples, + const size_t num_samples, + const size_t bytes_per_sample, + const size_t num_channels, + const uint32_t samples_per_sec) override; + + protected: + static const int NUM_PACKETS_TO_FILTER = 30; // 300 ms of smoothing (30 frames) + float mSumVector[NUM_PACKETS_TO_FILTER]; + float mMicrophoneEnergy; +}; + +// Used to process/retrieve audio levels after +// all of the processing (AGC, AEC, etc.) for display in-world to the user. +class LLCustomProcessor : public webrtc::CustomProcessing +{ + public: + LLCustomProcessor(); + ~LLCustomProcessor() override {} + + // (Re-) Initializes the submodule. + void Initialize(int sample_rate_hz, int num_channels) override; + + // Analyzes the given capture or render signal. + void Process(webrtc::AudioBuffer *audio) override; + + // Returns a string representation of the module state. + std::string ToString() const override { return ""; } + + float getMicrophoneEnergy() { return mMicrophoneEnergy; } + + protected: + static const int NUM_PACKETS_TO_FILTER = 30; // 300 ms of smoothing + int mSampleRateHz; + int mNumChannels; + + float mSumVector[NUM_PACKETS_TO_FILTER]; + float mMicrophoneEnergy; +}; + + +// Primary singleton implementation for interfacing +// with the native webrtc library. +class LLWebRTCImpl : public LLWebRTCDeviceInterface, public webrtc::AudioDeviceSink +{ + public: + LLWebRTCImpl(); + ~LLWebRTCImpl() {} + + void init(); + void terminate(); + + // + // LLWebRTCDeviceInterface + // + + void refreshDevices() override; + + void setDevicesObserver(LLWebRTCDevicesObserver *observer) override; + void unsetDevicesObserver(LLWebRTCDevicesObserver *observer) override; + + void setCaptureDevice(const std::string& id) override; + void setRenderDevice(const std::string& id) override; + + void setTuningMode(bool enable) override; + float getTuningAudioLevel() override; + float getPeerConnectionAudioLevel() override; + + // + // AudioDeviceSink + // + void OnDevicesUpdated() override; + + // + // Helpers + // + + // The following thread helpers allow the + // LLWebRTCPeerConnectionImpl class to post + // tasks to the native webrtc threads. + void PostWorkerTask(absl::AnyInvocable<void() &&> task, + const webrtc::Location& location = webrtc::Location::Current()) + { + mWorkerThread->PostTask(std::move(task), location); + } + + void PostSignalingTask(absl::AnyInvocable<void() &&> task, + const webrtc::Location& location = webrtc::Location::Current()) + { + mSignalingThread->PostTask(std::move(task), location); + } + + void PostNetworkTask(absl::AnyInvocable<void() &&> task, + const webrtc::Location& location = webrtc::Location::Current()) + { + mNetworkThread->PostTask(std::move(task), location); + } + + void WorkerBlockingCall(rtc::FunctionView<void()> functor, + const webrtc::Location& location = webrtc::Location::Current()) + { + mWorkerThread->BlockingCall(std::move(functor), location); + } + + void SignalingBlockingCall(rtc::FunctionView<void()> functor, + const webrtc::Location& location = webrtc::Location::Current()) + { + mSignalingThread->BlockingCall(std::move(functor), location); + } + + void NetworkBlockingCall(rtc::FunctionView<void()> functor, + const webrtc::Location& location = webrtc::Location::Current()) + { + mNetworkThread->BlockingCall(std::move(functor), location); + } + + // Allows the LLWebRTCPeerConnectionImpl class to retrieve the + // native webrtc PeerConnectionFactory. + rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface> getPeerConnectionFactory() + { + return mPeerConnectionFactory; + } + + // create or destroy a peer connection. + LLWebRTCPeerConnectionInterface* newPeerConnection(); + void freePeerConnection(LLWebRTCPeerConnectionInterface* peer_connection); + + // enables/disables capture via the capture device + void setRecording(bool recording); + + protected: + // The native webrtc threads + std::unique_ptr<rtc::Thread> mNetworkThread; + std::unique_ptr<rtc::Thread> mWorkerThread; + std::unique_ptr<rtc::Thread> mSignalingThread; + + // The factory that allows creation of native webrtc PeerConnections. + rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface> mPeerConnectionFactory; + + // more native webrtc stuff + std::unique_ptr<webrtc::TaskQueueFactory> mTaskQueueFactory; + + + // Devices + void updateDevices(); + rtc::scoped_refptr<webrtc::AudioDeviceModule> mTuningDeviceModule; + rtc::scoped_refptr<webrtc::AudioDeviceModule> mPeerDeviceModule; + std::vector<LLWebRTCDevicesObserver *> mVoiceDevicesObserverList; + + // accessors in native webrtc for devices aren't apparently implemented yet. + int32_t mPlayoutDevice; + int32_t mRecordingDevice; + bool mMute; + + LLAudioDeviceObserver * mTuningAudioDeviceObserver; + LLCustomProcessor * mPeerCustomProcessor; + + // peer connections + std::vector<rtc::scoped_refptr<LLWebRTCPeerConnectionImpl>> mPeerConnections; +}; + + +// The implementation of a peer connection, which contains +// the various interfaces used by the viewer to interact with +// the webrtc connection. +class LLWebRTCPeerConnectionImpl : public LLWebRTCPeerConnectionInterface, + public LLWebRTCAudioInterface, + public LLWebRTCDataInterface, + public webrtc::PeerConnectionObserver, + public webrtc::CreateSessionDescriptionObserver, + public webrtc::SetRemoteDescriptionObserverInterface, + public webrtc::SetLocalDescriptionObserverInterface, + public webrtc::DataChannelObserver + +{ + public: + LLWebRTCPeerConnectionImpl(); + ~LLWebRTCPeerConnectionImpl() {} + + void init(LLWebRTCImpl * webrtc_impl); + void terminate(); + + virtual void AddRef() const override = 0; + virtual rtc::RefCountReleaseStatus Release() const override = 0; + + // + // LLWebRTCPeerConnection + // + + void setSignalingObserver(LLWebRTCSignalingObserver *observer) override; + void unsetSignalingObserver(LLWebRTCSignalingObserver *observer) override; + bool initializeConnection() override; + bool shutdownConnection() override; + void AnswerAvailable(const std::string &sdp) override; + + // + // LLWebRTCAudioInterface + // + void setMute(bool mute) override; + void setReceiveVolume(float volume) override; // volume between 0.0 and 1.0 + void setSendVolume(float volume) override; // volume between 0.0 and 1.0 + + // + // LLWebRTCDataInterface + // + void sendData(const std::string& data, bool binary=false) override; + void setDataObserver(LLWebRTCDataObserver *observer) override; + void unsetDataObserver(LLWebRTCDataObserver *observer) override; + + // + // PeerConnectionObserver implementation. + // + + void OnSignalingChange(webrtc::PeerConnectionInterface::SignalingState new_state) override {} + void OnAddTrack(rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver, + const std::vector<rtc::scoped_refptr<webrtc::MediaStreamInterface>> &streams) override; + void OnRemoveTrack(rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver) override; + void OnDataChannel(rtc::scoped_refptr<webrtc::DataChannelInterface> channel) override; + void OnRenegotiationNeeded() override {} + void OnIceConnectionChange(webrtc::PeerConnectionInterface::IceConnectionState new_state) override {}; + void OnIceGatheringChange(webrtc::PeerConnectionInterface::IceGatheringState new_state) override; + void OnIceCandidate(const webrtc::IceCandidateInterface *candidate) override; + void OnIceConnectionReceivingChange(bool receiving) override {} + void OnConnectionChange(webrtc::PeerConnectionInterface::PeerConnectionState new_state) override; + + // + // CreateSessionDescriptionObserver implementation. + // + void OnSuccess(webrtc::SessionDescriptionInterface *desc) override; + void OnFailure(webrtc::RTCError error) override; + + // + // SetRemoteDescriptionObserverInterface implementation. + // + void OnSetRemoteDescriptionComplete(webrtc::RTCError error) override; + + // + // SetLocalDescriptionObserverInterface implementation. + // + void OnSetLocalDescriptionComplete(webrtc::RTCError error) override; + + // + // DataChannelObserver implementation. + // + void OnStateChange() override; + void OnMessage(const webrtc::DataBuffer& buffer) override; + + // Helpers + void resetMute(); + void enableSenderTracks(bool enable); + void enableReceiverTracks(bool enable); + + protected: + + LLWebRTCImpl * mWebRTCImpl; + + rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface> mPeerConnectionFactory; + + bool mMute; + + // signaling + std::vector<LLWebRTCSignalingObserver *> mSignalingObserverList; + std::vector<std::unique_ptr<webrtc::IceCandidateInterface>> mCachedIceCandidates; + bool mAnswerReceived; + + rtc::scoped_refptr<webrtc::PeerConnectionInterface> mPeerConnection; + rtc::scoped_refptr<webrtc::MediaStreamInterface> mLocalStream; + + // data + std::vector<LLWebRTCDataObserver *> mDataObserverList; + rtc::scoped_refptr<webrtc::DataChannelInterface> mDataChannel; +}; + +} + +#endif // LLWEBRTC_IMPL_H diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 355f35c558..dc0c92bd19 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -692,6 +692,7 @@ set(viewer_SOURCE_FILES llvoiceclient.cpp llvoicevisualizer.cpp llvoicevivox.cpp + llvoicewebrtc.cpp llvoinventorylistener.cpp llvopartgroup.cpp llvosky.cpp @@ -1338,6 +1339,7 @@ set(viewer_HEADER_FILES llvoiceclient.h llvoicevisualizer.h llvoicevivox.h + llvoicewebrtc.h llvoinventorylistener.h llvopartgroup.h llvosky.h @@ -1438,6 +1440,7 @@ if (LINUX) endif (LINUX) if (WINDOWS) + list(APPEND viewer_SOURCE_FILES llappviewerwin32.cpp llwindebug.cpp @@ -1729,6 +1732,7 @@ if (WINDOWS) ${SHARED_LIB_STAGING_DIR}/openjp2.dll ${SHARED_LIB_STAGING_DIR}/libhunspell.dll ${SHARED_LIB_STAGING_DIR}/uriparser.dll + ${SHARED_LIB_STAGING_DIR}/llwebrtc.dll #${SHARED_LIB_STAGING_DIR}/${LL_INTDIR}/SLVoice.exe #${SHARED_LIB_STAGING_DIR}/${LL_INTDIR}/libsndfile-1.dll #${SHARED_LIB_STAGING_DIR}/${LL_INTDIR}/vivoxoal.dll @@ -1795,13 +1799,14 @@ if (WINDOWS) DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/viewer_manifest.py stage_third_party_libs + llwebrtc ${COPY_INPUT_DEPENDENCIES} COMMENT "Performing viewer_manifest copy" ) add_custom_target(copy_w_viewer_manifest ALL DEPENDS ${CMAKE_CFG_INTDIR}/copy_touched.bat) - add_dependencies(${VIEWER_BINARY_NAME} stage_third_party_libs llcommon copy_w_viewer_manifest) + add_dependencies(${VIEWER_BINARY_NAME} stage_third_party_libs llcommon llwebrtc copy_w_viewer_manifest) if (EXISTS ${CMAKE_SOURCE_DIR}/copy_win_scripts) add_dependencies(${VIEWER_BINARY_NAME} copy_win_scripts) @@ -1925,6 +1930,7 @@ target_link_libraries(${VIEWER_BINARY_NAME} llcorehttp llcommon llmeshoptimizer + llwebrtc ll::ndof lllogin llprimitive diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index bd38527462..5f7d1d8a21 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -15128,7 +15128,7 @@ <key>Type</key> <string>String</string> <key>Value</key> - <string>vivox</string> + <string>webrtc</string> </map> <key>WLSkyDetail</key> <map> diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index 3853aaa8fd..ca27dbd818 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -311,7 +311,7 @@ bool LLAgent::isActionAllowed(const LLSD& sdname) } else { - allow_agent_voice = channel->isActive() && channel->callStarted(); + allow_agent_voice = channel->isActive(); } } @@ -4078,10 +4078,6 @@ bool LLAgent::teleportCore(bool is_local) } make_ui_sound("UISndTeleportOut"); - // MBW -- Let the voice client know a teleport has begun so it can leave the existing channel. - // This was breaking the case of teleporting within a single sim. Backing it out for now. -// LLVoiceClient::getInstance()->leaveChannel(); - return true; } diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 4a43133ff6..c079b3d1a1 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -3344,15 +3344,15 @@ LLSD LLAppViewer::getViewerInfo() const { LLVoiceVersionInfo version = LLVoiceClient::getInstance()->getVersion(); const std::string build_version = version.mBuildVersion; - std::ostringstream version_string; - if (std::equal(build_version.begin(), build_version.begin() + version.serverVersion.size(), + std::ostringstream version_string; + if (std::equal(version.mBuildVersion.begin(), version.mBuildVersion.begin() + version.serverVersion.size(), version.serverVersion.begin())) { // Normal case: Show type and build version. - version_string << version.serverType << " " << build_version << std::endl; + version_string << version.voiceServerType << " " << version.mBuildVersion << std::endl; } else { // Mismatch: Show both versions. - version_string << version.serverVersion << "/" << build_version << std::endl; + version_string << version.voiceServerType << " " << version.serverVersion << "/" << version.mBuildVersion << std::endl; } info["VOICE_VERSION"] = version_string.str(); } @@ -5121,7 +5121,7 @@ void LLAppViewer::sendLogoutRequest() if(LLVoiceClient::instanceExists()) { - LLVoiceClient::getInstance()->leaveChannel(); + LLVoiceClient::getInstance()->setVoiceEnabled(false); } } } diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index 6d1496d517..bfa1dea324 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -43,6 +43,7 @@ #ifndef LL_LLAPPVIEWER_H #define LL_LLAPPVIEWER_H +#include "llapp.h" #include "llallocator.h" #include "llapr.h" #include "llcontrol.h" diff --git a/indra/newview/llavataractions.cpp b/indra/newview/llavataractions.cpp index 313339f131..156b5b4047 100644 --- a/indra/newview/llavataractions.cpp +++ b/indra/newview/llavataractions.cpp @@ -241,7 +241,7 @@ static void on_avatar_name_cache_start_call(const LLUUID& agent_id, const LLAvatarName& av_name) { std::string name = av_name.getDisplayName(); - LLUUID session_id = gIMMgr->addSession(name, IM_NOTHING_SPECIAL, agent_id, true); + LLUUID session_id = gIMMgr->addSession(name, IM_NOTHING_SPECIAL, agent_id, LLSD()); if (session_id != LLUUID::null) { gIMMgr->startCall(session_id); @@ -277,8 +277,7 @@ void LLAvatarActions::startAdhocCall(const uuid_vec_t& ids, const LLUUID& floate // create the new ad hoc voice session const std::string title = LLTrans::getString("conference-title"); - LLUUID session_id = gIMMgr->addSession(title, IM_SESSION_CONFERENCE_START, - ids[0], id_array, true, floater_id); + LLUUID session_id = gIMMgr->addSession(title, IM_SESSION_CONFERENCE_START, ids[0], id_array, LLSD(), floater_id); if (session_id == LLUUID::null) { return; @@ -322,7 +321,7 @@ void LLAvatarActions::startConference(const uuid_vec_t& ids, const LLUUID& float id_array.push_back(*it); } const std::string title = LLTrans::getString("conference-title"); - LLUUID session_id = gIMMgr->addSession(title, IM_SESSION_CONFERENCE_START, ids[0], id_array, false, floater_id); + LLUUID session_id = gIMMgr->addSession(title, IM_SESSION_CONFERENCE_START, ids[0], id_array, LLSD(), floater_id); if (session_id == LLUUID::null) { diff --git a/indra/newview/llconversationview.cpp b/indra/newview/llconversationview.cpp index 48c7df40df..42194c9c16 100644 --- a/indra/newview/llconversationview.cpp +++ b/indra/newview/llconversationview.cpp @@ -57,7 +57,7 @@ public: : conversation(conv) {} - virtual void onChange(EStatusType status, const std::string &channelURI, bool proximal) + virtual void onChange(EStatusType status, const LLSD& channelInfo, bool proximal) { conversation->showVoiceIndicator(conversation && status != STATUS_JOINING diff --git a/indra/newview/llfloaterimsession.cpp b/indra/newview/llfloaterimsession.cpp index ed2a2807b5..c6868ffeda 100644 --- a/indra/newview/llfloaterimsession.cpp +++ b/indra/newview/llfloaterimsession.cpp @@ -552,7 +552,7 @@ void LLFloaterIMSession::onCallButtonClicked() } } -void LLFloaterIMSession::onChange(EStatusType status, const std::string &channelURI, bool proximal) +void LLFloaterIMSession::onChange(EStatusType status, const LLSD& channelInfo, bool proximal) { if(status != STATUS_JOINING && status != STATUS_LEFT_CHANNEL) { diff --git a/indra/newview/llfloaterimsession.h b/indra/newview/llfloaterimsession.h index 28464fc14b..fc431f3ced 100644 --- a/indra/newview/llfloaterimsession.h +++ b/indra/newview/llfloaterimsession.h @@ -114,8 +114,7 @@ public: // Implements LLVoiceClientStatusObserver::onChange() to enable the call // button when voice is available - void onChange(EStatusType status, const std::string &channelURI, - bool proximal); + void onChange(EStatusType status, const LLSD& channelInfo, bool proximal); virtual LLTransientFloaterMgr::ETransientGroup getGroup() { return LLTransientFloaterMgr::IM; } virtual void onVoiceChannelStateChanged( diff --git a/indra/newview/llgroupactions.cpp b/indra/newview/llgroupactions.cpp index 380e49c320..9d39da148c 100644 --- a/indra/newview/llgroupactions.cpp +++ b/indra/newview/llgroupactions.cpp @@ -254,7 +254,7 @@ void LLGroupActions::startCall(const LLUUID& group_id) return; } - LLUUID session_id = gIMMgr->addSession(gdata.mName, IM_SESSION_GROUP_START, group_id, true); + LLUUID session_id = gIMMgr->addSession(gdata.mName, IM_SESSION_GROUP_START, group_id, LLSD()); if (session_id == LLUUID::null) { LL_WARNS() << "Error adding session" << LL_ENDL; diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index 61a01d7418..b2769e9bab 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -87,7 +87,20 @@ const S32 XL8_PADDING = 3; // XL8_START_TAG.size() + XL8_END_TAG.size() /** Timeout of outgoing session initialization (in seconds) */ const static U32 SESSION_INITIALIZATION_TIMEOUT = 30; -void startConfrenceCoro(std::string url, LLUUID tempSessionId, LLUUID creatorId, LLUUID otherParticipantId, LLSD agents); + +enum EMultiAgentChatSessionType +{ + GROUP_CHAT_SESSION = 0, + CONFERENCE_SESSION = 1, + P2P_CHAT_SESSION = 2, + SESSION_TYPE_COUNT +}; + + +void startConferenceCoro(std::string url, LLUUID tempSessionId, LLUUID creatorId, LLUUID otherParticipantId, LLSD agents); + +void startP2PCoro(std::string url, LLUUID tempSessionId, LLUUID creatorId, LLUUID otherParticipantId); + void chatterBoxInvitationCoro(std::string url, LLUUID sessionId, LLIMMgr::EInvitationType invitationType); void chatterBoxHistoryCoro(std::string url, LLUUID sessionId, std::string from, std::string message, U32 timestamp); void start_deprecated_conference_chat(const LLUUID& temp_session_id, const LLUUID& creator_id, const LLUUID& other_participant_id, const LLSD& agents_to_invite); @@ -108,7 +121,7 @@ BOOL LLSessionTimeoutTimer::tick() { gIMMgr->showSessionStartError("session_initialization_timed_out_error", mSessionId); } - return TRUE; + return TRUE; } @@ -137,7 +150,7 @@ void process_dnd_im(const LLSD& notification) name, IM_NOTHING_SPECIAL, fromID, - false, + LLSD(), false); //will need slight refactor to retrieve whether offline message or not (assume online for now) } @@ -396,7 +409,7 @@ void on_new_message(const LLSD& msg) notify_of_message(msg, false); } -void startConfrenceCoro(std::string url, +void startConferenceCoro(std::string url, LLUUID tempSessionId, LLUUID creatorId, LLUUID otherParticipantId, LLSD agents) { LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); @@ -408,6 +421,9 @@ void startConfrenceCoro(std::string url, postData["method"] = "start conference"; postData["session-id"] = tempSessionId; postData["params"] = agents; + LLSD altParams; + altParams["voice_server_type"] = gSavedSettings.getString("VoiceServerType"); + postData["alt_params"] = altParams; LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData); @@ -437,6 +453,38 @@ void startConfrenceCoro(std::string url, } } +void startP2PCoro(std::string url, LLUUID sessionID, LLUUID creatorId, LLUUID otherParticipantId) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("ConferenceChatStart", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + + LLSD postData; + postData["method"] = "start p2p"; + postData["session-id"] = sessionID; + postData["params"] = otherParticipantId; + LLSD altParams; + altParams["voice_server_type"] = gSavedSettings.getString("VoiceServerType"); + postData["alt_params"] = altParams; + + LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + LL_WARNS("LLIMModel") << "Failed to start p2p session:" << postData << "->" << result << LL_ENDL; + // try an "old school" way. + // *TODO: What about other error status codes? 4xx 5xx? + if (status == LLCore::HttpStatus(HTTP_BAD_REQUEST)) + { + static const std::string error_string("session_does_not_exist_error"); + gIMMgr->showSessionStartError(error_string, sessionID); + } + } +} + void chatterBoxInvitationCoro(std::string url, LLUUID sessionId, LLIMMgr::EInvitationType invitationType) { LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); @@ -643,7 +691,13 @@ LLIMModel::LLIMModel() LLCallDialogManager::instance(); } -LLIMModel::LLIMSession::LLIMSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, const LLUUID& other_participant_id, const uuid_vec_t& ids, bool voice, bool has_offline_msg) +LLIMModel::LLIMSession::LLIMSession(const LLUUID& session_id, + const std::string& name, + const EInstantMessage& type, + const LLUUID& other_participant_id, + const uuid_vec_t& ids, + const LLSD& voiceChannelInfo, + bool has_offline_msg) : mSessionID(session_id), mName(name), mType(type), @@ -658,36 +712,49 @@ LLIMModel::LLIMSession::LLIMSession(const LLUUID& session_id, const std::string& mCallBackEnabled(true), mTextIMPossible(true), mStartCallOnInitialize(false), - mStartedAsIMCall(voice), + mStartedAsIMCall(!voiceChannelInfo.isUndefined()), mIsDNDsend(false), mAvatarNameCacheConnection() { // set P2P type by default - mSessionType = P2P_SESSION; + mSessionType = P2P_SESSION; + bool p2pAsAdhocCall = false; if (IM_NOTHING_SPECIAL == mType || IM_SESSION_P2P_INVITE == mType) - { - mVoiceChannel = new LLVoiceChannelP2P(session_id, name, other_participant_id); + { + LLVoiceP2POutgoingCallInterface *outgoingInterface = + LLVoiceClient::getInstance()->getOutgoingCallInterface(voiceChannelInfo); + + if (outgoingInterface) + { + // only use LLVoiceChannelP2P if the provider can handle the special P2P interface, + // which uses the voice server to relay calls and invites. Otherwise, + // we use the group voice provider. + mVoiceChannel = new LLVoiceChannelP2P(session_id, name, other_participant_id, outgoingInterface); + } + else + { + p2pAsAdhocCall = true; + mVoiceChannel = new LLVoiceChannelGroup(session_id, name, true); + } } else { - mVoiceChannel = new LLVoiceChannelGroup(session_id, name); - // determine whether it is group or conference session if (gAgent.isInGroup(mSessionID)) { mSessionType = GROUP_SESSION; + mVoiceChannel = new LLVoiceChannelGroup(session_id, name, false); } - else + else { mSessionType = ADHOC_SESSION; + mVoiceChannel = new LLVoiceChannelGroup(session_id, name, false); } } - if(mVoiceChannel) - { - mVoiceChannelStateChangeConnection = mVoiceChannel->setStateChangedCallback(boost::bind(&LLIMSession::onVoiceChannelStateChanged, this, _1, _2, _3)); - } + mVoiceChannelStateChangeConnection = mVoiceChannel->setStateChangedCallback(boost::bind(&LLIMSession::onVoiceChannelStateChanged, this, _1, _2, _3)); + mVoiceChannel->setChannelInfo(voiceChannelInfo); mSpeakers = new LLIMSpeakerMgr(mVoiceChannel); @@ -699,8 +766,7 @@ LLIMModel::LLIMSession::LLIMSession(const LLUUID& session_id, const std::string& //we need to wait for session initialization for outgoing ad-hoc and group chat session //correct session id for initiated ad-hoc chat will be received from the server - if (!LLIMModel::getInstance()->sendStartSession(mSessionID, mOtherParticipantID, - mInitialTargetIDs, mType)) + if (!LLIMModel::getInstance()->sendStartSession(mSessionID, mOtherParticipantID, mInitialTargetIDs, mType, p2pAsAdhocCall)) { //we don't need to wait for any responses //so we're already initialized @@ -849,22 +915,6 @@ LLIMModel::LLIMSession::~LLIMSession() delete mSpeakers; mSpeakers = NULL; - // End the text IM session if necessary - if(LLVoiceClient::getInstance() && mOtherParticipantID.notNull()) - { - switch(mType) - { - case IM_NOTHING_SPECIAL: - case IM_SESSION_P2P_INVITE: - LLVoiceClient::getInstance()->endUserIMSession(mOtherParticipantID); - break; - - default: - // Appease the linux compiler - break; - } - } - mVoiceChannelStateChangeConnection.disconnect(); // HAVE to do this here -- if it happens in the LLVoiceChannel destructor it will call the wrong version (since the object's partially deconstructed at that point). @@ -1439,7 +1489,7 @@ void LLIMModel::testMessages() //session name should not be empty bool LLIMModel::newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, - const LLUUID& other_participant_id, const uuid_vec_t& ids, bool voice, bool has_offline_msg) + const LLUUID& other_participant_id, const uuid_vec_t& ids, const LLSD& voiceChannelInfo, bool has_offline_msg) { if (name.empty()) { @@ -1453,7 +1503,7 @@ bool LLIMModel::newSession(const LLUUID& session_id, const std::string& name, co return false; } - LLIMSession* session = new LLIMSession(session_id, name, type, other_participant_id, ids, voice, has_offline_msg); + LLIMSession* session = new LLIMSession(session_id, name, type, other_participant_id, ids, voiceChannelInfo, has_offline_msg); mId2SessionMap[session_id] = session; // When notifying observer, name of session is used instead of "name", because they may not be the @@ -1465,11 +1515,11 @@ bool LLIMModel::newSession(const LLUUID& session_id, const std::string& name, co } -bool LLIMModel::newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, const LLUUID& other_participant_id, bool voice, bool has_offline_msg) +bool LLIMModel::newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, const LLUUID& other_participant_id, const LLSD& voiceChannelInfo, bool has_offline_msg) { uuid_vec_t ids; ids.push_back(other_participant_id); - return newSession(session_id, name, type, other_participant_id, ids, voice, has_offline_msg); + return newSession(session_id, name, type, other_participant_id, ids, voiceChannelInfo, has_offline_msg); } bool LLIMModel::clearSession(const LLUUID& session_id) @@ -2018,7 +2068,8 @@ bool LLIMModel::sendStartSession( const LLUUID& temp_session_id, const LLUUID& other_participant_id, const uuid_vec_t& ids, - EInstantMessage dialog) + EInstantMessage dialog, + bool p2p_as_adhoc_call) { if ( dialog == IM_SESSION_GROUP_START ) { @@ -2034,7 +2085,7 @@ bool LLIMModel::sendStartSession( return true; } - else if ( dialog == IM_SESSION_CONFERENCE_START ) + else if (dialog == IM_SESSION_CONFERENCE_START ) { LLSD agents; for (int i = 0; i < (S32) ids.size(); i++) @@ -2049,8 +2100,8 @@ bool LLIMModel::sendStartSession( std::string url = region->getCapability( "ChatSessionRequest"); - LLCoros::instance().launch("startConfrenceCoro", - boost::bind(&startConfrenceCoro, url, + LLCoros::instance().launch("startConferenceCoro", + boost::bind(&startConferenceCoro, url, temp_session_id, gAgent.getID(), other_participant_id, agents)); } else @@ -2065,7 +2116,16 @@ bool LLIMModel::sendStartSession( //we also need to wait for reply from the server in case of ad-hoc chat (we'll get new session id) return true; } - + else if ((dialog == IM_SESSION_P2P_INVITE) || (dialog == IM_NOTHING_SPECIAL)) + { + LLViewerRegion *region = gAgent.getRegion(); + if (region) + { + std::string url = region->getCapability("ChatSessionRequest"); + LLCoros::instance().launch("startP2P", boost::bind(&startP2PCoro, url, temp_session_id, gAgent.getID(), other_participant_id)); + } + return true; + } return false; } @@ -2308,6 +2368,11 @@ void LLCallDialogManager::onVoiceChannelStateChangedInt(const LLVoiceChannel::ES return; } break; + case LLVoiceChannel::STATE_NO_CHANNEL_INFO : + // This will happen in p2p calls using the adhoc + // infrastructure, which marks the channel as no channel info + // after the call is closed, which forces a dialogue. + return; case LLVoiceChannel::STATE_HUNG_UP: // this state is coming before session is changed @@ -2658,21 +2723,21 @@ mAvatarNameCacheConnection() void LLIncomingCallDialog::onLifetimeExpired() { - std::string session_handle = mPayload["session_handle"].asString(); - if (LLVoiceClient::getInstance()->isValidChannel(session_handle)) - { - // restart notification's timer if call is still valid - mLifetimeTimer.start(); - } - else - { - // close invitation if call is already not valid - mLifetimeTimer.stop(); - LLUUID session_id = mPayload["session_id"].asUUID(); - gIMMgr->clearPendingAgentListUpdates(session_id); - gIMMgr->clearPendingInvitation(session_id); - closeFloater(); - } + LLVoiceP2PIncomingCallInterfacePtr call = LLVoiceClient::getInstance()->getIncomingCallInterface(mPayload["voice_session_info"]); + if (call) + { + // restart notification's timer if call is still valid + mLifetimeTimer.start(); + } + else + { + // close invitation if call is already not valid + mLifetimeTimer.stop(); + LLUUID session_id = mPayload["session_id"].asUUID(); + gIMMgr->clearPendingAgentListUpdates(session_id); + gIMMgr->clearPendingInvitation(session_id); + closeFloater(); + } } BOOL LLIncomingCallDialog::postBuild() @@ -2687,7 +2752,7 @@ BOOL LLIncomingCallDialog::postBuild() LLUUID session_id = mPayload["session_id"].asUUID(); LLSD caller_id = mPayload["caller_id"]; - std::string caller_name = mPayload["caller_name"].asString(); + std::string caller_name = mPayload["caller_name"].asString(); if (session_id.isNull() && caller_id.asUUID().isNull()) { @@ -2836,6 +2901,10 @@ void LLIncomingCallDialog::processCallResponse(S32 response, const LLSD &payload LLUUID session_id = payload["session_id"].asUUID(); LLUUID caller_id = payload["caller_id"].asUUID(); std::string session_name = payload["session_name"].asString(); + if (session_name.empty()) + { + session_name = payload["caller_name"].asString(); + } EInstantMessage type = (EInstantMessage)payload["type"].asInteger(); LLIMMgr::EInvitationType inv_type = (LLIMMgr::EInvitationType)payload["inv_type"].asInteger(); bool voice = true; @@ -2852,10 +2921,7 @@ void LLIncomingCallDialog::processCallResponse(S32 response, const LLSD &payload { // create a normal IM session session_id = gIMMgr->addP2PSession( - session_name, - caller_id, - payload["session_handle"].asString(), - payload["session_uri"].asString()); + session_name, caller_id, payload["voice_channel_info"]); if (voice) { @@ -2905,7 +2971,7 @@ void LLIncomingCallDialog::processCallResponse(S32 response, const LLSD &payload } } - gIMMgr->addSession(correct_session_name, type, session_id, true); + gIMMgr->addSession(correct_session_name, type, session_id, payload["voice_channel_info"]); std::string url = gAgent.getRegion()->getCapability( "ChatSessionRequest"); @@ -2935,11 +3001,11 @@ void LLIncomingCallDialog::processCallResponse(S32 response, const LLSD &payload { if (type == IM_SESSION_P2P_INVITE) { - if(LLVoiceClient::getInstance()) - { - std::string s = payload["session_handle"].asString(); - LLVoiceClient::getInstance()->declineInvite(s); - } + LLVoiceP2PIncomingCallInterfacePtr call = LLVoiceClient::getInstance()->getIncomingCallInterface(payload["voice_session_info"]); + if (call) + { + call->declineInvite(); + } } else { @@ -2961,90 +3027,6 @@ void LLIncomingCallDialog::processCallResponse(S32 response, const LLSD &payload } } -bool inviteUserResponse(const LLSD& notification, const LLSD& response) -{ - if (!gIMMgr) - return false; - - const LLSD& payload = notification["payload"]; - LLUUID session_id = payload["session_id"].asUUID(); - EInstantMessage type = (EInstantMessage)payload["type"].asInteger(); - LLIMMgr::EInvitationType inv_type = (LLIMMgr::EInvitationType)payload["inv_type"].asInteger(); - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - switch(option) - { - case 0: // accept - { - if (type == IM_SESSION_P2P_INVITE) - { - // create a normal IM session - session_id = gIMMgr->addP2PSession( - payload["session_name"].asString(), - payload["caller_id"].asUUID(), - payload["session_handle"].asString(), - payload["session_uri"].asString()); - - gIMMgr->startCall(session_id); - - gIMMgr->clearPendingAgentListUpdates(session_id); - gIMMgr->clearPendingInvitation(session_id); - } - else - { - gIMMgr->addSession( - payload["session_name"].asString(), - type, - session_id, true); - - std::string url = gAgent.getRegion()->getCapability( - "ChatSessionRequest"); - - LLCoros::instance().launch("chatterBoxInvitationCoro", - boost::bind(&chatterBoxInvitationCoro, url, - session_id, inv_type)); - } - } - break; - case 2: // mute (also implies ignore, so this falls through to the "ignore" case below) - { - // mute the sender of this invite - if (!LLMuteList::getInstance()->isMuted(payload["caller_id"].asUUID())) - { - LLMute mute(payload["caller_id"].asUUID(), payload["caller_name"].asString(), LLMute::AGENT); - LLMuteList::getInstance()->add(mute); - } - } - /* FALLTHROUGH */ - - case 1: // decline - { - if (type == IM_SESSION_P2P_INVITE) - { - std::string s = payload["session_handle"].asString(); - LLVoiceClient::getInstance()->declineInvite(s); - } - else - { - std::string url = gAgent.getRegion()->getCapability( - "ChatSessionRequest"); - - LLSD data; - data["method"] = "decline invitation"; - data["session-id"] = session_id; - LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, data, - "Invitation declined.", - "Invitation decline failed."); - } - } - - gIMMgr->clearPendingAgentListUpdates(session_id); - gIMMgr->clearPendingInvitation(session_id); - break; - } - - return false; -} - // // Member Functions // @@ -3116,7 +3098,7 @@ void LLIMMgr::addMessage( { fixed_session_name = av_name.getDisplayName(); } - LLIMModel::getInstance()->newSession(new_session_id, fixed_session_name, dialog, other_participant_id, false, is_offline_msg); + LLIMModel::getInstance()->newSession(new_session_id, fixed_session_name, dialog, other_participant_id, LLSD(), is_offline_msg); LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(new_session_id); if (session) @@ -3276,22 +3258,11 @@ void LLIMMgr::autoStartCallOnStartup(const LLUUID& session_id) } LLUUID LLIMMgr::addP2PSession(const std::string& name, - const LLUUID& other_participant_id, - const std::string& voice_session_handle, - const std::string& caller_uri) + const LLUUID& other_participant_id, + const LLSD& voice_channel_info) { - LLUUID session_id = addSession(name, IM_NOTHING_SPECIAL, other_participant_id, true); - - LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(session_id); - if (speaker_mgr) - { - LLVoiceChannelP2P* voice_channel = dynamic_cast<LLVoiceChannelP2P*>(speaker_mgr->getVoiceChannel()); - if (voice_channel) - { - voice_channel->setSessionHandle(voice_session_handle, caller_uri); - } - } - return session_id; + LL_WARNS("Voice") << "ADD P2P VOICE CHANNEL INFO: " << voice_channel_info << LL_ENDL; + return addSession(name, IM_NOTHING_SPECIAL, other_participant_id, voice_channel_info); } // This adds a session to the talk view. The name is the local name of @@ -3301,11 +3272,12 @@ LLUUID LLIMMgr::addP2PSession(const std::string& name, LLUUID LLIMMgr::addSession( const std::string& name, EInstantMessage dialog, - const LLUUID& other_participant_id, bool voice) + const LLUUID& other_participant_id, + const LLSD& voiceChannelInfo) { std::vector<LLUUID> ids; ids.push_back(other_participant_id); - LLUUID session_id = addSession(name, dialog, other_participant_id, ids, voice); + LLUUID session_id = addSession(name, dialog, other_participant_id, ids, voiceChannelInfo); return session_id; } @@ -3315,7 +3287,8 @@ LLUUID LLIMMgr::addSession( const std::string& name, EInstantMessage dialog, const LLUUID& other_participant_id, - const std::vector<LLUUID>& ids, bool voice, + const std::vector<LLUUID>& ids, + const LLSD& voiceChannelInfo, const LLUUID& floater_id) { if (ids.empty()) @@ -3348,7 +3321,9 @@ LLUUID LLIMMgr::addSession( bool new_session = (LLIMModel::getInstance()->findIMSession(session_id) == NULL); //works only for outgoing ad-hoc sessions - if (new_session && IM_SESSION_CONFERENCE_START == dialog && ids.size()) + if (new_session && + ((IM_NOTHING_SPECIAL == dialog) || (IM_SESSION_P2P_INVITE == dialog) || (IM_SESSION_CONFERENCE_START == dialog)) && + ids.size()) { LLIMModel::LLIMSession* ad_hoc_found = LLIMModel::getInstance()->findAdHocIMSession(ids); if (ad_hoc_found) @@ -3361,7 +3336,7 @@ LLUUID LLIMMgr::addSession( //Notify observers that a session was added if (new_session) { - LLIMModel::getInstance()->newSession(session_id, name, dialog, other_participant_id, ids, voice); + LLIMModel::getInstance()->newSession(session_id, name, dialog, other_participant_id, ids, voiceChannelInfo); } //Notifies observers that the session was already added else @@ -3422,9 +3397,15 @@ void LLIMMgr::inviteToSession( const std::string& caller_name, EInstantMessage type, EInvitationType inv_type, - const std::string& session_handle, - const std::string& session_uri) + const LLSD& voice_channel_info) { + + if (caller_id == gAgentID) + { + // ignore invites from ourself. + return; + } + std::string notify_box_type; // voice invite question is different from default only for group call (EXT-7118) std::string question_type = "VoiceInviteQuestionDefault"; @@ -3465,11 +3446,12 @@ void LLIMMgr::inviteToSession( payload["caller_name"] = caller_name; payload["type"] = type; payload["inv_type"] = inv_type; - payload["session_handle"] = session_handle; - payload["session_uri"] = session_uri; + payload["voice_channel_info"] = voice_channel_info; payload["notify_box_type"] = notify_box_type; payload["question_type"] = question_type; + LL_WARNS("Voice") << "INVITE PAYLOAD: " << payload << LL_ENDL; + //ignore invites from muted residents if (!is_linden) { @@ -3518,7 +3500,7 @@ void LLIMMgr::inviteToSession( fixed_session_name = av_name.getDisplayName(); } } - LLIMModel::getInstance()->newSession(session_id, fixed_session_name, IM_NOTHING_SPECIAL, caller_id, false, false); + LLIMModel::getInstance()->newSession(session_id, fixed_session_name, IM_NOTHING_SPECIAL, caller_id, LLSD(), false); } LLSD args; @@ -3747,7 +3729,6 @@ bool LLIMMgr::startCall(const LLUUID& session_id, LLVoiceChannel::EDirection dir { LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(session_id); if (!voice_channel) return false; - voice_channel->setCallDirection(direction); voice_channel->activate(); return true; @@ -4144,13 +4125,16 @@ public: return; } + BOOL session_type_p2p = input["body"]["voice"].get("invitation_type").asInteger() == EMultiAgentChatSessionType::P2P_CHAT_SESSION; + LL_WARNS("Voice") << "VOICE DATA: " << input["body"]<< LL_ENDL; gIMMgr->inviteToSession( input["body"]["session_id"].asUUID(), input["body"]["session_name"].asString(), input["body"]["from_id"].asUUID(), input["body"]["from_name"].asString(), - IM_SESSION_INVITE, - LLIMMgr::INVITATION_TYPE_VOICE); + session_type_p2p ? IM_SESSION_P2P_INVITE : IM_SESSION_INVITE, + LLIMMgr::INVITATION_TYPE_VOICE, + input["body"]["voice"]); } else if ( input["body"].has("immediate") ) { diff --git a/indra/newview/llimview.h b/indra/newview/llimview.h index bace97d37a..2f084d1392 100644 --- a/indra/newview/llimview.h +++ b/indra/newview/llimview.h @@ -80,7 +80,7 @@ public: } SType; LLIMSession(const LLUUID& session_id, const std::string& name, - const EInstantMessage& type, const LLUUID& other_participant_id, const uuid_vec_t& ids, bool voice, bool has_offline_msg); + const EInstantMessage& type, const LLUUID& other_participant_id, const uuid_vec_t& ids, const LLSD& voiceChannelInfo, bool has_offline_msg); virtual ~LLIMSession(); void sessionInitReplyReceived(const LLUUID& new_session_id); @@ -199,10 +199,10 @@ public: * @param name session name should not be empty, will return false if empty */ bool newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, const LLUUID& other_participant_id, - const uuid_vec_t& ids, bool voice = false, bool has_offline_msg = false); + const uuid_vec_t& ids, const LLSD& voiceChannelInfo = LLSD(), bool has_offline_msg = false); - bool newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, - const LLUUID& other_participant_id, bool voice = false, bool has_offline_msg = false); + bool newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, const LLUUID &other_participant_id, + const LLSD &voiceChannelInfo = LLSD(), bool has_offline_msg = false); /** * Remove all session data associated with a session specified by session_id @@ -296,7 +296,7 @@ public: static void sendLeaveSession(const LLUUID& session_id, const LLUUID& other_participant_id); static bool sendStartSession(const LLUUID& temp_session_id, const LLUUID& other_participant_id, - const uuid_vec_t& ids, EInstantMessage dialog); + const uuid_vec_t& ids, EInstantMessage dialog, bool p2p_as_adhoc_call); static void sendTypingState(LLUUID session_id, LLUUID other_participant_id, BOOL typing); static void sendMessage(const std::string& utf8_text, const LLUUID& im_session_id, const LLUUID& other_participant_id, EInstantMessage dialog); @@ -379,7 +379,8 @@ public: // session. LLUUID addSession(const std::string& name, EInstantMessage dialog, - const LLUUID& other_participant_id, bool voice = false); + const LLUUID& other_participant_id, + const LLSD& voiceChannelInfo = LLSD()); // Adds a session using a specific group of starting agents // the dialog type is assumed correct. Returns the uuid of the session. @@ -387,7 +388,8 @@ public: LLUUID addSession(const std::string& name, EInstantMessage dialog, const LLUUID& other_participant_id, - const std::vector<LLUUID>& ids, bool voice = false, + const std::vector<LLUUID> &ids, + const LLSD& voiceChannelInfo = LLSD(), const LLUUID& floater_id = LLUUID::null); /** @@ -397,10 +399,7 @@ public: * @param caller_uri - sip URI of caller. It should be always be passed into the method to avoid * incorrect working of LLVoiceChannel instances. See EXT-2985. */ - LLUUID addP2PSession(const std::string& name, - const LLUUID& other_participant_id, - const std::string& voice_session_handle, - const std::string& caller_uri); + LLUUID addP2PSession(const std::string &name, const LLUUID &other_participant_id, const LLSD &voice_call_info); /** * Leave the session with session id. Send leave session notification @@ -415,9 +414,9 @@ public: const LLUUID& caller, const std::string& caller_name, EInstantMessage type, - EInvitationType inv_type, - const std::string& session_handle = LLStringUtil::null, - const std::string& session_uri = LLStringUtil::null); + EInvitationType inv_type, + const LLSD &voice_channel_info = LLSD() + ); void processIMTypingStart(const LLUUID& from_id, const EInstantMessage im_type); void processIMTypingStop(const LLUUID& from_id, const EInstantMessage im_type); diff --git a/indra/newview/llpanelgroup.cpp b/indra/newview/llpanelgroup.cpp index ab255d5215..a06b50390f 100644 --- a/indra/newview/llpanelgroup.cpp +++ b/indra/newview/llpanelgroup.cpp @@ -276,7 +276,7 @@ void LLPanelGroup::changed(LLGroupChange gc) } // virtual -void LLPanelGroup::onChange(EStatusType status, const std::string &channelURI, bool proximal) +void LLPanelGroup::onChange(EStatusType status, const LLSD& channelInfo, bool proximal) { if(status == STATUS_JOINING || status == STATUS_LEFT_CHANNEL) { diff --git a/indra/newview/llpanelgroup.h b/indra/newview/llpanelgroup.h index be40b08a6d..3ca6426887 100644 --- a/indra/newview/llpanelgroup.h +++ b/indra/newview/llpanelgroup.h @@ -62,7 +62,7 @@ public: // Implements LLVoiceClientStatusObserver::onChange() to enable the call // button when voice is available - /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal); + /*virtual*/ void onChange(EStatusType status, const LLSD& channelInfo, bool proximal); void showNotice(const std::string& subject, const std::string& message, diff --git a/indra/newview/llpanelpeople.cpp b/indra/newview/llpanelpeople.cpp index 13b52e97c5..27019badd2 100644 --- a/indra/newview/llpanelpeople.cpp +++ b/indra/newview/llpanelpeople.cpp @@ -733,7 +733,7 @@ BOOL LLPanelPeople::postBuild() } // virtual -void LLPanelPeople::onChange(EStatusType status, const std::string &channelURI, bool proximal) +void LLPanelPeople::onChange(EStatusType status, const LLSD& channelInfo, bool proximal) { if(status == STATUS_JOINING || status == STATUS_LEFT_CHANNEL) { diff --git a/indra/newview/llpanelpeople.h b/indra/newview/llpanelpeople.h index 14205cebe2..e4484b66c6 100644 --- a/indra/newview/llpanelpeople.h +++ b/indra/newview/llpanelpeople.h @@ -55,7 +55,7 @@ public: /*virtual*/ bool notifyChildren(const LLSD& info); // Implements LLVoiceClientStatusObserver::onChange() to enable call buttons // when voice is available - /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal); + /*virtual*/ void onChange(EStatusType status, const LLSD& channelInfo, bool proximal); // internals class Updater; diff --git a/indra/newview/llpanelprofile.cpp b/indra/newview/llpanelprofile.cpp index ffbed778c1..132b710620 100644 --- a/indra/newview/llpanelprofile.cpp +++ b/indra/newview/llpanelprofile.cpp @@ -1446,7 +1446,7 @@ void LLPanelProfileSecondLife::changed(U32 mask) } // virtual, called by LLVoiceClient -void LLPanelProfileSecondLife::onChange(EStatusType status, const std::string &channelURI, bool proximal) +void LLPanelProfileSecondLife::onChange(EStatusType status, const LLSD& channelInfo, bool proximal) { if(status == STATUS_JOINING || status == STATUS_LEFT_CHANNEL) { diff --git a/indra/newview/llpanelprofile.h b/indra/newview/llpanelprofile.h index 11632a10ae..ea2bb25ac2 100644 --- a/indra/newview/llpanelprofile.h +++ b/indra/newview/llpanelprofile.h @@ -85,7 +85,7 @@ public: // Implements LLVoiceClientStatusObserver::onChange() to enable the call // button when voice is available - void onChange(EStatusType status, const std::string &channelURI, bool proximal) override; + void onChange(EStatusType status, const LLSD& channelInfo, bool proximal) override; void setAvatarId(const LLUUID& avatar_id) override; diff --git a/indra/newview/llpanelvoicedevicesettings.cpp b/indra/newview/llpanelvoicedevicesettings.cpp index 28631e2b7b..48c90d6856 100644 --- a/indra/newview/llpanelvoicedevicesettings.cpp +++ b/indra/newview/llpanelvoicedevicesettings.cpp @@ -236,44 +236,47 @@ void LLPanelVoiceDeviceSettings::refresh() if(mCtrlInputDevices) { - mCtrlInputDevices->removeall(); - mCtrlInputDevices->add(getLocalizedDeviceName(DEFAULT_DEVICE), DEFAULT_DEVICE, ADD_BOTTOM); - - for(device=LLVoiceClient::getInstance()->getCaptureDevices().begin(); - device != LLVoiceClient::getInstance()->getCaptureDevices().end(); - device++) - { - mCtrlInputDevices->add(getLocalizedDeviceName(device->display_name), device->full_name, ADD_BOTTOM); - } - - // Fix invalid input audio device preference. - if (!mCtrlInputDevices->setSelectedByValue(mInputDevice, TRUE)) - { - mCtrlInputDevices->setValue(DEFAULT_DEVICE); - gSavedSettings.setString("VoiceInputAudioDevice", DEFAULT_DEVICE); - mInputDevice = DEFAULT_DEVICE; - } + LLVoiceDeviceList devices = LLVoiceClient::getInstance()->getCaptureDevices(); + if (devices.size() > 0) // if zero, we've not received our devices yet + { + mCtrlInputDevices->removeall(); + mCtrlInputDevices->add(getLocalizedDeviceName(DEFAULT_DEVICE), DEFAULT_DEVICE, ADD_BOTTOM); + for (auto& device : devices) + { + mCtrlInputDevices->add(getLocalizedDeviceName(device.display_name), device.full_name, ADD_BOTTOM); + } + + // Fix invalid input audio device preference. + if (!mCtrlInputDevices->setSelectedByValue(mInputDevice, TRUE)) + { + mCtrlInputDevices->setValue(DEFAULT_DEVICE); + gSavedSettings.setString("VoiceInputAudioDevice", DEFAULT_DEVICE); + mInputDevice = DEFAULT_DEVICE; + } + } } if(mCtrlOutputDevices) { - mCtrlOutputDevices->removeall(); - mCtrlOutputDevices->add(getLocalizedDeviceName(DEFAULT_DEVICE), DEFAULT_DEVICE, ADD_BOTTOM); - - for(device = LLVoiceClient::getInstance()->getRenderDevices().begin(); - device != LLVoiceClient::getInstance()->getRenderDevices().end(); - device++) - { - mCtrlOutputDevices->add(getLocalizedDeviceName(device->display_name), device->full_name, ADD_BOTTOM); - } - - // Fix invalid output audio device preference. - if (!mCtrlOutputDevices->setSelectedByValue(mOutputDevice, TRUE)) - { - mCtrlOutputDevices->setValue(DEFAULT_DEVICE); - gSavedSettings.setString("VoiceOutputAudioDevice", DEFAULT_DEVICE); - mOutputDevice = DEFAULT_DEVICE; - } + LLVoiceDeviceList devices = LLVoiceClient::getInstance()->getRenderDevices(); + if (devices.size() > 0) // if zero, we've not received our devices yet + { + mCtrlOutputDevices->removeall(); + mCtrlOutputDevices->add(getLocalizedDeviceName(DEFAULT_DEVICE), DEFAULT_DEVICE, ADD_BOTTOM); + + for (auto& device : devices) + { + mCtrlOutputDevices->add(getLocalizedDeviceName(device.display_name), device.full_name, ADD_BOTTOM); + } + + // Fix invalid output audio device preference. + if (!mCtrlOutputDevices->setSelectedByValue(mOutputDevice, TRUE)) + { + mCtrlOutputDevices->setValue(DEFAULT_DEVICE); + gSavedSettings.setString("VoiceOutputAudioDevice", DEFAULT_DEVICE); + mOutputDevice = DEFAULT_DEVICE; + } + } } } } @@ -316,8 +319,11 @@ void LLPanelVoiceDeviceSettings::onCommitInputDevice() if(LLVoiceClient::getInstance()) { mInputDevice = mCtrlInputDevices->getValue().asString(); - LLVoiceClient::getInstance()->setRenderDevice(mInputDevice); + LLVoiceClient::getInstance()->setCaptureDevice(mInputDevice); } + // the preferences floater stuff is a mess, hence apply will never + // be called when 'ok' is pressed, so just force it for now. + apply(); } void LLPanelVoiceDeviceSettings::onCommitOutputDevice() @@ -328,6 +334,9 @@ void LLPanelVoiceDeviceSettings::onCommitOutputDevice() mOutputDevice = mCtrlOutputDevices->getValue().asString(); LLVoiceClient::getInstance()->setRenderDevice(mOutputDevice); } + // the preferences floater stuff is a mess, hence apply will never + // be called when 'ok' is pressed, so just force it for now. + apply(); } void LLPanelVoiceDeviceSettings::onOutputDevicesClicked() diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index d0b76848f7..e86ea5f2e3 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -3337,7 +3337,7 @@ LLSD transform_cert_args(LLPointer<LLCertificate> cert) // are actually arrays, and we want to format them as comma separated // strings, so special case those. LLSDSerialize::toXML(cert_info[iter->first], std::cout); - if((iter->first == std::string(CERT_KEY_USAGE)) || + if((iter->first== std::string(CERT_KEY_USAGE)) || (iter->first == std::string(CERT_EXTENDED_KEY_USAGE))) { value = ""; diff --git a/indra/newview/llvieweraudio.cpp b/indra/newview/llvieweraudio.cpp index 6a0edbecb1..184b6d8e93 100644 --- a/indra/newview/llvieweraudio.cpp +++ b/indra/newview/llvieweraudio.cpp @@ -480,11 +480,11 @@ void audio_update_volume(bool force_update) if (!gViewerWindow->getActive() && (gSavedSettings.getBOOL("MuteWhenMinimized"))) { - voice_inst->setMuteMic(true); + //voice_inst->setMuteMic(true); } else { - voice_inst->setMuteMic(false); + //voice_inst->setMuteMic(false); } } } diff --git a/indra/newview/llviewerhelp.cpp b/indra/newview/llviewerhelp.cpp index 3181ae6283..6374d68988 100644 --- a/indra/newview/llviewerhelp.cpp +++ b/indra/newview/llviewerhelp.cpp @@ -45,7 +45,7 @@ public: // requests will be throttled from a non-trusted browser LLHelpHandler() : LLCommandHandler("help", UNTRUSTED_CLICK_ONLY) {} - bool handle(const LLSD& params, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web) + bool handle(const LLSD& params, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web) override { LLViewerHelp* vhelp = LLViewerHelp::getInstance(); if (! vhelp) diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index 452dcdd8fd..84b007eaa4 100755 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -3136,6 +3136,7 @@ void LLViewerRegionImpl::buildCapabilityNames(LLSD& capabilityNames) capabilityNames.append("ParcelVoiceInfoRequest"); capabilityNames.append("ProductInfoRequest"); capabilityNames.append("ProvisionVoiceAccountRequest"); + capabilityNames.append("VoiceSignalingRequest"); capabilityNames.append("ReadOfflineMsgs"); // Requires to respond reliably: AcceptFriendship, AcceptGroupInvite, DeclineFriendship, DeclineGroupInvite capabilityNames.append("RegionObjects"); capabilityNames.append("RemoteParcelRequest"); diff --git a/indra/newview/llviewerregion.h b/indra/newview/llviewerregion.h index a409d837a4..5817bd7d8d 100644 --- a/indra/newview/llviewerregion.h +++ b/indra/newview/llviewerregion.h @@ -43,6 +43,7 @@ #include "m4math.h" // LLMatrix4 #include "llframetimer.h" #include "llreflectionmap.h" +#include "llpointer.h" // Surface id's #define LAND 1 @@ -545,7 +546,7 @@ public: U8 mCentralBakeVersion; LLVOCacheEntry* mLastVisitedEntry; - U32 mInvisibilityCheckHistory; + U32 mInvisibilityCheckHistory; // Information for Homestead / CR-53 S32 mClassID; diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index fee00eb6f4..5d7adb9613 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -614,7 +614,8 @@ BOOL LLVOAvatar::sShowAnimationDebug = FALSE; BOOL LLVOAvatar::sVisibleInFirstPerson = FALSE; F32 LLVOAvatar::sLODFactor = 1.f; F32 LLVOAvatar::sPhysicsLODFactor = 1.f; -BOOL LLVOAvatar::sJointDebug = FALSE; +BOOL LLVOAvatar::sJointDebug = FALSE; +BOOL LLVOAvatar::sLipSyncEnabled = FALSE; F32 LLVOAvatar::sUnbakedTime = 0.f; F32 LLVOAvatar::sUnbakedUpdateTime = 0.f; F32 LLVOAvatar::sGreyTime = 0.f; @@ -1155,6 +1156,7 @@ void LLVOAvatar::initClass() LLControlAvatar::sRegionChangedSlot = gAgent.addRegionChangedCallback(&LLControlAvatar::onRegionChanged); sCloudTexture = LLViewerTextureManager::getFetchedTextureFromFile("cloud-particle.j2c"); + gSavedSettings.getControl("LipSyncEnabled")->getSignal()->connect(boost::bind(&LLVOAvatar::handleVOAvatarPrefsChanged, _2)); } @@ -1162,6 +1164,12 @@ void LLVOAvatar::cleanupClass() { } +bool LLVOAvatar::handleVOAvatarPrefsChanged(const LLSD &newvalue) +{ + sLipSyncEnabled = gSavedSettings.getBOOL("LipSyncEnabled"); + return true; +} + // virtual void LLVOAvatar::initInstance() { @@ -3072,7 +3080,7 @@ void LLVOAvatar::idleUpdateLipSync(bool voice_enabled) // Use the Lipsync_Ooh and Lipsync_Aah morphs for lip sync if ( voice_enabled && mLastRezzedStatus > 0 // no point updating lip-sync for clouds - && (LLVoiceClient::getInstance()->lipSyncEnabled()) + && sLipSyncEnabled && LLVoiceClient::getInstance()->getIsSpeaking( mID ) ) { F32 ooh_morph_amount = 0.0f; diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h index 48bfd5293a..a19476153c 100644 --- a/indra/newview/llvoavatar.h +++ b/indra/newview/llvoavatar.h @@ -106,9 +106,10 @@ public: virtual void markDead(); static void initClass(); // Initialize data that's only init'd once per class. static void cleanupClass(); // Cleanup data that's only init'd once per class. - virtual void initInstance(); // Called after construction to initialize the class. + virtual void initInstance(); // Called after construction to initialize the class. protected: virtual ~LLVOAvatar(); + static bool handleVOAvatarPrefsChanged(const LLSD &newvalue); /** Initialization ** ** @@ -365,6 +366,7 @@ public: static F32 sLODFactor; // user-settable LOD factor static F32 sPhysicsLODFactor; // user-settable physics LOD factor static BOOL sJointDebug; // output total number of joints being touched for each avatar + static BOOL sLipSyncEnabled; static LLPointer<LLViewerTexture> sCloudTexture; diff --git a/indra/newview/llvoicechannel.cpp b/indra/newview/llvoicechannel.cpp index b0eb8d962c..4d85f160c2 100644 --- a/indra/newview/llvoicechannel.cpp +++ b/indra/newview/llvoicechannel.cpp @@ -39,7 +39,6 @@ #include "llcorehttputil.h" LLVoiceChannel::voice_channel_map_t LLVoiceChannel::sVoiceChannelMap; -LLVoiceChannel::voice_channel_map_uri_t LLVoiceChannel::sVoiceChannelURIMap; LLVoiceChannel* LLVoiceChannel::sCurrentVoiceChannel = NULL; LLVoiceChannel* LLVoiceChannel::sSuspendedVoiceChannel = NULL; LLVoiceChannel::channel_changed_signal_t LLVoiceChannel::sCurrentVoiceChannelChangedSignal; @@ -89,29 +88,18 @@ LLVoiceChannel::~LLVoiceChannel() } sVoiceChannelMap.erase(mSessionID); - sVoiceChannelURIMap.erase(mURI); } -void LLVoiceChannel::setChannelInfo( - const std::string& uri, - const std::string& credentials) +void LLVoiceChannel::setChannelInfo(const LLSD &channelInfo) { - setURI(uri); - - mCredentials = credentials; + mChannelInfo = channelInfo; if (mState == STATE_NO_CHANNEL_INFO) { - if (mURI.empty()) - { - LLNotificationsUtil::add("VoiceChannelJoinFailed", mNotifyArgs); - LL_WARNS("Voice") << "Received empty URI for channel " << mSessionName << LL_ENDL; - deactivate(); - } - else if (mCredentials.empty()) + if (mChannelInfo.isUndefined()) { LLNotificationsUtil::add("VoiceChannelJoinFailed", mNotifyArgs); - LL_WARNS("Voice") << "Received empty credentials for channel " << mSessionName << LL_ENDL; + LL_WARNS("Voice") << "Received empty channel info for channel " << mSessionName << LL_ENDL; deactivate(); } else @@ -130,11 +118,17 @@ void LLVoiceChannel::setChannelInfo( } } -void LLVoiceChannel::onChange(EStatusType type, const std::string &channelURI, bool proximal) +void LLVoiceChannel::onChange(EStatusType type, const LLSD& channelInfo, bool proximal) { - if (channelURI != mURI) + LL_WARNS("Voice") << channelInfo << LL_ENDL; + LL_WARNS("Voice") << mChannelInfo << LL_ENDL; + if (mChannelInfo.isUndefined()) { - return; + mChannelInfo = channelInfo; + } + if (!LLVoiceClient::getInstance()->compareChannels(mChannelInfo, channelInfo)) + { + return; } if (type < BEGIN_ERROR_STATUS) @@ -193,7 +187,7 @@ void LLVoiceChannel::handleError(EStatusType type) BOOL LLVoiceChannel::isActive() { // only considered active when currently bound channel matches what our channel - return callStarted() && LLVoiceClient::getInstance()->getCurrentChannel() == mURI; + return callStarted() && LLVoiceClient::getInstance()->isCurrentChannel(mChannelInfo); } BOOL LLVoiceChannel::callStarted() @@ -246,10 +240,8 @@ void LLVoiceChannel::activate() // activating the proximal channel between IM calls LLVoiceChannel* old_channel = sCurrentVoiceChannel; sCurrentVoiceChannel = this; - mCallDialogPayload["old_channel_name"] = ""; if (old_channel) { - mCallDialogPayload["old_channel_name"] = old_channel->getSessionName(); old_channel->deactivate(); } } @@ -257,7 +249,7 @@ void LLVoiceChannel::activate() if (mState == STATE_NO_CHANNEL_INFO) { // responsible for setting status to active - getChannelInfo(); + requestChannelInfo(); } else { @@ -270,7 +262,7 @@ void LLVoiceChannel::activate() sCurrentVoiceChannelChangedSignal(this->mSessionID); } -void LLVoiceChannel::getChannelInfo() +void LLVoiceChannel::requestChannelInfo() { // pretend we have everything we need if (sCurrentVoiceChannel == this) @@ -293,20 +285,6 @@ LLVoiceChannel* LLVoiceChannel::getChannelByID(const LLUUID& session_id) } } -//static -LLVoiceChannel* LLVoiceChannel::getChannelByURI(std::string uri) -{ - voice_channel_map_uri_t::iterator found_it = sVoiceChannelURIMap.find(uri); - if (found_it == sVoiceChannelURIMap.end()) - { - return NULL; - } - else - { - return found_it->second; - } -} - LLVoiceChannel* LLVoiceChannel::getCurrentVoiceChannel() { return sCurrentVoiceChannel; @@ -319,13 +297,6 @@ void LLVoiceChannel::updateSessionID(const LLUUID& new_session_id) sVoiceChannelMap.insert(std::make_pair(mSessionID, this)); } -void LLVoiceChannel::setURI(std::string uri) -{ - sVoiceChannelURIMap.erase(mURI); - mURI = uri; - sVoiceChannelURIMap.insert(std::make_pair(mURI, this)); -} - void LLVoiceChannel::setState(EState state) { switch(state) @@ -410,8 +381,11 @@ boost::signals2::connection LLVoiceChannel::setCurrentVoiceChannelChangedCallbac // LLVoiceChannelGroup // -LLVoiceChannelGroup::LLVoiceChannelGroup(const LLUUID& session_id, const std::string& session_name) : - LLVoiceChannel(session_id, session_name) +LLVoiceChannelGroup::LLVoiceChannelGroup(const LLUUID &session_id, + const std::string &session_name, + bool is_p2p) : + LLVoiceChannel(session_id, session_name), + mIsP2P(is_p2p) { mRetries = DEFAULT_RETRIES_COUNT; mIsRetrying = FALSE; @@ -424,7 +398,15 @@ void LLVoiceChannelGroup::deactivate() LLVoiceClient::getInstance()->leaveNonSpatialChannel(); } LLVoiceChannel::deactivate(); -} + + if (mIsP2P) + { + // void the channel info for p2p adhoc channels + // so we request it again, hence throwing up the + // connect dialogue on the other side. + setState(STATE_NO_CHANNEL_INFO); + } + } void LLVoiceChannelGroup::activate() { @@ -435,43 +417,46 @@ void LLVoiceChannelGroup::activate() if (callStarted()) { // we have the channel info, just need to use it now - LLVoiceClient::getInstance()->setNonSpatialChannel( - mURI, - mCredentials); + LLVoiceClient::getInstance()->setNonSpatialChannel(mChannelInfo, + mCallDirection == OUTGOING_CALL, + mIsP2P); - if (!gAgent.isInGroup(mSessionID)) // ad-hoc channel - { - LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(mSessionID); - // Adding ad-hoc call participants to Recent People List. - // If it's an outgoing ad-hoc, we can use mInitialTargetIDs that holds IDs of people we - // called(both online and offline) as source to get people for recent (STORM-210). - if (session->isOutgoingAdHoc()) - { - for (uuid_vec_t::iterator it = session->mInitialTargetIDs.begin(); - it!=session->mInitialTargetIDs.end();++it) - { - const LLUUID id = *it; - LLRecentPeople::instance().add(id); - } - } - // If this ad-hoc is incoming then trying to get ids of people from mInitialTargetIDs - // would lead to EXT-8246. So in this case we get them from speakers list. - else - { - LLIMModel::addSpeakersToRecent(mSessionID); - } - } + if (mIsP2P) + { + LLIMModel::addSpeakersToRecent(mSessionID); + } + else + { + if (!gAgent.isInGroup(mSessionID)) // ad-hoc channel + { + LLIMModel::LLIMSession *session = LLIMModel::getInstance()->findIMSession(mSessionID); + // Adding ad-hoc call participants to Recent People List. + // If it's an outgoing ad-hoc, we can use mInitialTargetIDs that holds IDs of people we + // called(both online and offline) as source to get people for recent (STORM-210). + if (session->isOutgoingAdHoc()) + { + for (uuid_vec_t::iterator it = session->mInitialTargetIDs.begin(); it != session->mInitialTargetIDs.end(); ++it) + { + const LLUUID id = *it; + LLRecentPeople::instance().add(id); + } + } + // If this ad-hoc is incoming then trying to get ids of people from mInitialTargetIDs + // would lead to EXT-8246. So in this case we get them from speakers list. + else + { + LLIMModel::addSpeakersToRecent(mSessionID); + } + } + } - //Mic default state is OFF on initiating/joining Ad-Hoc/Group calls - if (LLVoiceClient::getInstance()->getUserPTTState() && LLVoiceClient::getInstance()->getPTTIsToggle()) - { - LLVoiceClient::getInstance()->inputUserControlState(true); - } - + // Mic default state is OFF on initiating/joining Ad-Hoc/Group calls. It's on for P2P using the AdHoc infra. + + LLVoiceClient::getInstance()->setUserPTTState(mIsP2P); } } -void LLVoiceChannelGroup::getChannelInfo() +void LLVoiceChannelGroup::requestChannelInfo() { LLViewerRegion* region = gAgent.getRegion(); if (region) @@ -483,17 +468,13 @@ void LLVoiceChannelGroup::getChannelInfo() } } -void LLVoiceChannelGroup::setChannelInfo( - const std::string& uri, - const std::string& credentials) +void LLVoiceChannelGroup::setChannelInfo(const LLSD& channelInfo) { - setURI(uri); - - mCredentials = credentials; + mChannelInfo = channelInfo; if (mState == STATE_NO_CHANNEL_INFO) { - if(!mURI.empty() && !mCredentials.empty()) + if(!mChannelInfo.isUndefined()) { setState(STATE_READY); @@ -516,9 +497,9 @@ void LLVoiceChannelGroup::setChannelInfo( else if ( mIsRetrying ) { // we have the channel info, just need to use it now - LLVoiceClient::getInstance()->setNonSpatialChannel( - mURI, - mCredentials); + LLVoiceClient::getInstance()->setNonSpatialChannel(channelInfo, + mCallDirection == OUTGOING_CALL, + mIsP2P); } } @@ -556,7 +537,7 @@ void LLVoiceChannelGroup::handleError(EStatusType status) mIsRetrying = TRUE; mIgnoreNextSessionLeave = TRUE; - getChannelInfo(); + requestChannelInfo(); return; } else @@ -612,6 +593,9 @@ void LLVoiceChannelGroup::voiceCallCapCoro(std::string url) LLSD postData; postData["method"] = "call"; postData["session-id"] = mSessionID; + LLSD altParams; + altParams["preferred_voice_server_type"] = gSavedSettings.getString("VoiceServerType"); + postData["alt_params"] = altParams; LL_INFOS("Voice", "voiceCallCapCoro") << "Generic POST for " << url << LL_ENDL; @@ -651,14 +635,12 @@ void LLVoiceChannelGroup::voiceCallCapCoro(std::string url) LLSD::map_const_iterator iter; for (iter = result.beginMap(); iter != result.endMap(); ++iter) { - LL_DEBUGS("Voice") << "LLVoiceCallCapResponder::result got " + LL_DEBUGS("Voice") << "LLVoiceChannelGroup::voiceCallCapCoro got " << iter->first << LL_ENDL; } + LL_INFOS("Voice") << "LLVoiceChannelGroup::voiceCallCapCoro got " << result << LL_ENDL; - channelp->setChannelInfo( - result["voice_credentials"]["channel_uri"].asString(), - result["voice_credentials"]["channel_credentials"].asString()); - + channelp->setChannelInfo(result["voice_credentials"]); } @@ -682,13 +664,14 @@ void LLVoiceChannelProximal::activate() if((LLVoiceChannel::sCurrentVoiceChannel != this) && (LLVoiceChannel::getState() == STATE_CONNECTED)) { // we're connected to a non-spatial channel, so disconnect. - LLVoiceClient::getInstance()->leaveNonSpatialChannel(); + LLVoiceClient::getInstance()->leaveNonSpatialChannel(); } + LLVoiceClient::getInstance()->activateSpatialChannel(true); LLVoiceChannel::activate(); } -void LLVoiceChannelProximal::onChange(EStatusType type, const std::string &channelURI, bool proximal) +void LLVoiceChannelProximal::onChange(EStatusType type, const LLSD& channelInfo, bool proximal) { if (!proximal) { @@ -758,19 +741,22 @@ void LLVoiceChannelProximal::deactivate() { setState(STATE_HUNG_UP); } + LLVoiceClient::getInstance()->activateSpatialChannel(false); } // // LLVoiceChannelP2P // -LLVoiceChannelP2P::LLVoiceChannelP2P(const LLUUID& session_id, const std::string& session_name, const LLUUID& other_user_id) : - LLVoiceChannelGroup(session_id, session_name), +LLVoiceChannelP2P::LLVoiceChannelP2P(const LLUUID &session_id, + const std::string &session_name, + const LLUUID &other_user_id, + LLVoiceP2POutgoingCallInterface* outgoing_call_interface) : + LLVoiceChannelGroup(session_id, session_name, true), mOtherUserID(other_user_id), - mReceivedCall(FALSE) + mReceivedCall(FALSE), + mOutgoingCallInterface(outgoing_call_interface) { - // make sure URI reflects encoded version of other user's agent id - setURI(LLVoiceClient::getInstance()->sipURIFromID(other_user_id)); } void LLVoiceChannelP2P::handleStatusChange(EStatusType type) @@ -837,23 +823,23 @@ void LLVoiceChannelP2P::activate() if (callStarted()) { // no session handle yet, we're starting the call - if (mSessionHandle.empty()) + if (mIncomingCallInterface == nullptr) { mReceivedCall = FALSE; - LLVoiceClient::getInstance()->callUser(mOtherUserID); + mOutgoingCallInterface->callUser(mOtherUserID); } // otherwise answering the call else { - if (!LLVoiceClient::getInstance()->answerInvite(mSessionHandle)) + if (!mIncomingCallInterface->answerInvite()) { mCallEndedByAgent = false; - mSessionHandle.clear(); + mIncomingCallInterface.reset(); handleError(ERROR_UNKNOWN); return; } - // using the session handle invalidates it. Clear it out here so we can't reuse it by accident. - mSessionHandle.clear(); + // using the incoming call interface invalidates it. Clear it out here so we can't reuse it by accident. + mIncomingCallInterface.reset(); } // Add the party to the list of people with which we've recently interacted. @@ -867,7 +853,17 @@ void LLVoiceChannelP2P::activate() } } -void LLVoiceChannelP2P::getChannelInfo() +void LLVoiceChannelP2P::deactivate() +{ + if (callStarted()) + { + mOutgoingCallInterface->hangup(); + } + LLVoiceChannel::deactivate(); +} + + +void LLVoiceChannelP2P::requestChannelInfo() { // pretend we have everything we need, since P2P doesn't use channel info if (sCurrentVoiceChannel == this) @@ -877,8 +873,9 @@ void LLVoiceChannelP2P::getChannelInfo() } // receiving session from other user who initiated call -void LLVoiceChannelP2P::setSessionHandle(const std::string& handle, const std::string &inURI) +void LLVoiceChannelP2P::setChannelInfo(const LLSD& channel_info) { + mChannelInfo = channel_info; BOOL needs_activate = FALSE; if (callStarted()) { @@ -893,28 +890,16 @@ void LLVoiceChannelP2P::setSessionHandle(const std::string& handle, const std::s { // we are active and have priority, invite the other user again // under the assumption they will join this new session - mSessionHandle.clear(); - LLVoiceClient::getInstance()->callUser(mOtherUserID); + mOutgoingCallInterface->callUser(mOtherUserID); return; } } - - mSessionHandle = handle; - - // The URI of a p2p session should always be the other end's SIP URI. - if(!inURI.empty()) - { - setURI(inURI); - } - else - { - LL_WARNS("Voice") << "incoming SIP URL is not provided. Channel may not work properly." << LL_ENDL; - // See LLVoiceClient::sessionAddedEvent() - setURI(LLVoiceClient::getInstance()->sipURIFromID(mOtherUserID)); - } mReceivedCall = TRUE; - + if (!channel_info.isUndefined()) + { + mIncomingCallInterface = LLVoiceClient::getInstance()->getIncomingCallInterface(channel_info); + } if (needs_activate) { activate(); diff --git a/indra/newview/llvoicechannel.h b/indra/newview/llvoicechannel.h index e68bfbe1ff..5725e7ec5c 100644 --- a/indra/newview/llvoicechannel.h +++ b/indra/newview/llvoicechannel.h @@ -66,16 +66,14 @@ public: LLVoiceChannel(const LLUUID& session_id, const std::string& session_name); virtual ~LLVoiceChannel(); - /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal); + virtual void onChange(EStatusType status, const LLSD& channelInfo, bool proximal); virtual void handleStatusChange(EStatusType status); virtual void handleError(EStatusType status); virtual void deactivate(); virtual void activate(); - virtual void setChannelInfo( - const std::string& uri, - const std::string& credentials); - virtual void getChannelInfo(); + virtual void setChannelInfo(const LLSD &channelInfo); + virtual void requestChannelInfo(); virtual BOOL isActive(); virtual BOOL callStarted(); @@ -96,7 +94,6 @@ public: EDirection getCallDirection() {return mCallDirection;} static LLVoiceChannel* getChannelByID(const LLUUID& session_id); - static LLVoiceChannel* getChannelByURI(std::string uri); static LLVoiceChannel* getCurrentVoiceChannel(); static void initClass(); @@ -110,29 +107,24 @@ protected: * Use this method if you want mStateChangedCallback to be executed while state is changed */ void doSetState(const EState& state); - void setURI(std::string uri); // there can be two directions INCOMING and OUTGOING EDirection mCallDirection; - std::string mURI; - std::string mCredentials; LLUUID mSessionID; EState mState; std::string mSessionName; - LLSD mNotifyArgs; - LLSD mCallDialogPayload; + LLSD mNotifyArgs; + LLSD mChannelInfo; // true if call was ended by agent bool mCallEndedByAgent; - BOOL mIgnoreNextSessionLeave; + bool mIgnoreNextSessionLeave; LLHandle<LLPanel> mLoginNotificationHandle; typedef std::map<LLUUID, LLVoiceChannel*> voice_channel_map_t; static voice_channel_map_t sVoiceChannelMap; - typedef std::map<std::string, LLVoiceChannel*> voice_channel_map_uri_t; - static voice_channel_map_uri_t sVoiceChannelURIMap; - + static LLVoiceChannel* sProximalVoiceChannel; static LLVoiceChannel* sCurrentVoiceChannel; static LLVoiceChannel* sSuspendedVoiceChannel; static BOOL sSuspended; @@ -144,55 +136,58 @@ private: class LLVoiceChannelGroup : public LLVoiceChannel { public: - LLVoiceChannelGroup(const LLUUID& session_id, const std::string& session_name); + LLVoiceChannelGroup(const LLUUID& session_id, + const std::string& session_name, + bool is_p2p); - /*virtual*/ void handleStatusChange(EStatusType status); - /*virtual*/ void handleError(EStatusType status); - /*virtual*/ void activate(); - /*virtual*/ void deactivate(); - /*vritual*/ void setChannelInfo( - const std::string& uri, - const std::string& credentials); - /*virtual*/ void getChannelInfo(); + void handleStatusChange(EStatusType status) override; + void handleError(EStatusType status) override; + void activate() override; + void deactivate() override; + void setChannelInfo(const LLSD &channelInfo) override; + void requestChannelInfo() override; protected: - virtual void setState(EState state); + void setState(EState state) override; private: void voiceCallCapCoro(std::string url); U32 mRetries; BOOL mIsRetrying; + bool mIsP2P; }; class LLVoiceChannelProximal : public LLVoiceChannel, public LLSingleton<LLVoiceChannelProximal> { - LLSINGLETON(LLVoiceChannelProximal); -public: - - /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal) override; - /*virtual*/ void handleStatusChange(EStatusType status) override; - /*virtual*/ void handleError(EStatusType status) override; - /*virtual*/ BOOL isActive() override; - /*virtual*/ void activate() override; - /*virtual*/ void deactivate() override; - + LLSINGLETON_C11(LLVoiceChannelProximal); + public: + + void onChange(EStatusType status, const LLSD &channelInfo, bool proximal) override; + void handleStatusChange(EStatusType status) override; + void handleError(EStatusType status) override; + BOOL isActive() override; + void activate() override; + void deactivate() override; }; class LLVoiceChannelP2P : public LLVoiceChannelGroup { -public: - LLVoiceChannelP2P(const LLUUID& session_id, const std::string& session_name, const LLUUID& other_user_id); - - /*virtual*/ void handleStatusChange(EStatusType status) override; - /*virtual*/ void handleError(EStatusType status) override; - /*virtual*/ void activate() override; - /*virtual*/ void getChannelInfo() override; - - void setSessionHandle(const std::string& handle, const std::string &inURI); - -protected: - virtual void setState(EState state) override; + public: + LLVoiceChannelP2P(const LLUUID &session_id, + const std::string &session_name, + const LLUUID &other_user_id, + LLVoiceP2POutgoingCallInterface * outgoing_call_interface); + + void handleStatusChange(EStatusType status) override; + void handleError(EStatusType status) override; + void activate() override; + void requestChannelInfo() override; + void deactivate() override; + void setChannelInfo(const LLSD& channel_info) override; + + protected: + void setState(EState state) override; private: @@ -201,10 +196,10 @@ private: * **/ void addToTheRecentPeopleList(); - - std::string mSessionHandle; LLUUID mOtherUserID; BOOL mReceivedCall; + LLVoiceP2POutgoingCallInterface *mOutgoingCallInterface; + LLVoiceP2PIncomingCallInterfacePtr mIncomingCallInterface; }; #endif // LL_VOICECHANNEL_H diff --git a/indra/newview/llvoiceclient.cpp b/indra/newview/llvoiceclient.cpp index 68d9f4ffab..33e76c14ae 100644 --- a/indra/newview/llvoiceclient.cpp +++ b/indra/newview/llvoiceclient.cpp @@ -1,36 +1,36 @@ - /** + /** * @file llvoiceclient.cpp * @brief Voice client delegation class implementation. * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ -#include "llviewerprecompiledheaders.h" #include "llvoiceclient.h" -#include "llviewercontrol.h" -#include "llviewerwindow.h" #include "llvoicevivox.h" +#include "llvoicewebrtc.h" #include "llviewernetwork.h" +#include "llviewercontrol.h" #include "llcommandhandler.h" +#include "lldir.h" #include "llhttpnode.h" #include "llnotificationsutil.h" #include "llsdserialize.h" @@ -81,10 +81,10 @@ LLVoiceHandler gVoiceHandler; std::string LLVoiceClientStatusObserver::status2string(LLVoiceClientStatusObserver::EStatusType inStatus) { std::string result = "UNTRANSLATED"; - + // Prevent copy-paste errors when updating this list... #define CASE(x) case x: result = #x; break - + switch(inStatus) { CASE(STATUS_LOGIN_RETRY); @@ -107,19 +107,34 @@ std::string LLVoiceClientStatusObserver::status2string(LLVoiceClientStatusObserv } break; } - + #undef CASE - + return result; } - +LLVoiceModuleInterface *getVoiceModule(const std::string &voice_server_type) +{ + if (voice_server_type == VIVOX_VOICE_SERVER_TYPE || voice_server_type.empty()) + { + return (LLVoiceModuleInterface *) LLVivoxVoiceClient::getInstance(); + } + else if (voice_server_type == WEBRTC_VOICE_SERVER_TYPE) + { + return (LLVoiceModuleInterface *) LLWebRTCVoiceClient::getInstance(); + } + else + { + LLNotificationsUtil::add("VoiceVersionMismatch"); + return nullptr; + } +} /////////////////////////////////////////////////////////////////////////////////////////////// LLVoiceClient::LLVoiceClient(LLPumpIO *pump) : - mVoiceModule(NULL), + mSpatialVoiceModule(NULL), m_servicePump(NULL), mVoiceEffectEnabled(LLCachedControl<bool>(gSavedSettings, "VoiceMorphingEnabled", true)), mVoiceEffectDefault(LLCachedControl<std::string>(gSavedPerAccountSettings, "VoiceEffectDefault", "00000000-0000-0000-0000-000000000000")), @@ -133,7 +148,6 @@ LLVoiceClient::LLVoiceClient(LLPumpIO *pump) mMuteMic(false), mDisableMic(false) { - updateSettings(); init(pump); } @@ -142,46 +156,103 @@ LLVoiceClient::LLVoiceClient(LLPumpIO *pump) LLVoiceClient::~LLVoiceClient() { - llassert(!mVoiceModule); + llassert(!mSpatialVoiceModule); } void LLVoiceClient::init(LLPumpIO *pump) { // Initialize all of the voice modules m_servicePump = pump; + LLWebRTCVoiceClient::getInstance()->init(pump); + LLVivoxVoiceClient::getInstance()->init(pump); } void LLVoiceClient::userAuthorized(const std::string& user_id, const LLUUID &agentID) { - // In the future, we should change this to allow voice module registration - // with a table lookup of sorts. - std::string voice_server = gSavedSettings.getString("VoiceServerType"); - LL_DEBUGS("Voice") << "voice server type " << voice_server << LL_ENDL; - if(voice_server == "vivox") - { - mVoiceModule = (LLVoiceModuleInterface *)LLVivoxVoiceClient::getInstance(); - } - else + gAgent.addRegionChangedCallback(boost::bind(&LLVoiceClient::onRegionChanged, this)); + LLWebRTCVoiceClient::getInstance()->userAuthorized(user_id, agentID); + LLVivoxVoiceClient::getInstance()->userAuthorized(user_id, agentID); +} + +void LLVoiceClient::onRegionChanged() +{ + LLViewerRegion *region = gAgent.getRegion(); + if (region && region->simulatorFeaturesReceived()) + { + LLSD simulatorFeatures; + region->getSimulatorFeatures(simulatorFeatures); + setSpatialVoiceModule(simulatorFeatures["VoiceServerType"].asString()); + } + else if (region) + { + if (mSimulatorFeaturesReceivedSlot.connected()) + { + mSimulatorFeaturesReceivedSlot.disconnect(); + } + mSimulatorFeaturesReceivedSlot = + region->setSimulatorFeaturesReceivedCallback( + boost::bind(&LLVoiceClient::onSimulatorFeaturesReceived, this, _1)); + } +} + +void LLVoiceClient::onSimulatorFeaturesReceived(const LLUUID& region_id) +{ + LLViewerRegion *region = gAgent.getRegion(); + if (region && (region->getRegionID() == region_id)) + { + LLSD simulatorFeatures; + region->getSimulatorFeatures(simulatorFeatures); + setSpatialVoiceModule(simulatorFeatures["VoiceServerType"].asString()); + } +} + +void LLVoiceClient::setSpatialVoiceModule(const std::string &voice_server_type) +{ + LLVoiceModuleInterface *module = getVoiceModule(voice_server_type); + if (!module) { - mVoiceModule = NULL; - return; + return; } - mVoiceModule->init(m_servicePump); - mVoiceModule->userAuthorized(user_id, agentID); + if (module != mSpatialVoiceModule) + { + if (inProximalChannel()) + { + mSpatialVoiceModule->processChannels(false); + } + module->processChannels(true); + mSpatialVoiceModule = module; + mSpatialVoiceModule->updateSettings(); + } +} + +void LLVoiceClient::setNonSpatialVoiceModule(const std::string &voice_server_type) +{ + mNonSpatialVoiceModule = getVoiceModule(voice_server_type); + if (!mNonSpatialVoiceModule) + { + // we don't have a non-spatial voice module, + // so revert to spatial. + if (mSpatialVoiceModule) + { + mSpatialVoiceModule->processChannels(true); + } + return; + } + mNonSpatialVoiceModule->updateSettings(); } void LLVoiceClient::setHidden(bool hidden) { - if (mVoiceModule) + if (mSpatialVoiceModule) { - mVoiceModule->setHidden(hidden); + mSpatialVoiceModule->setHidden(hidden); } } void LLVoiceClient::terminate() { - if (mVoiceModule) mVoiceModule->terminate(); - mVoiceModule = NULL; + if (mSpatialVoiceModule) mSpatialVoiceModule->terminate(); + mSpatialVoiceModule = NULL; m_servicePump = NULL; // Shutdown speaker volume storage before LLSingletonBase::deleteAll() does it @@ -193,15 +264,15 @@ void LLVoiceClient::terminate() const LLVoiceVersionInfo LLVoiceClient::getVersion() { - if (mVoiceModule) + if (mSpatialVoiceModule) { - return mVoiceModule->getVersion(); + return mSpatialVoiceModule->getVersion(); } else { LLVoiceVersionInfo result; result.serverVersion = std::string(); - result.serverType = std::string(); + result.voiceServerType = std::string(); result.mBuildVersion = std::string(); return result; } @@ -215,10 +286,8 @@ void LLVoiceClient::updateSettings() updateMicMuteLogic(); - if (mVoiceModule) - { - mVoiceModule->updateSettings(); - } + LLWebRTCVoiceClient::getInstance()->updateSettings(); + LLVivoxVoiceClient::getInstance()->updateSettings(); } //-------------------------------------------------- @@ -226,117 +295,76 @@ void LLVoiceClient::updateSettings() void LLVoiceClient::tuningStart() { - if (mVoiceModule) mVoiceModule->tuningStart(); + LLWebRTCVoiceClient::getInstance()->tuningStart(); + LLVivoxVoiceClient::getInstance()->tuningStart(); } void LLVoiceClient::tuningStop() { - if (mVoiceModule) mVoiceModule->tuningStop(); + LLWebRTCVoiceClient::getInstance()->tuningStop(); + LLVivoxVoiceClient::getInstance()->tuningStop(); + } bool LLVoiceClient::inTuningMode() { - if (mVoiceModule) - { - return mVoiceModule->inTuningMode(); - } - else - { - return false; - } + return LLWebRTCVoiceClient::getInstance()->inTuningMode(); } void LLVoiceClient::tuningSetMicVolume(float volume) { - if (mVoiceModule) mVoiceModule->tuningSetMicVolume(volume); + LLWebRTCVoiceClient::getInstance()->tuningSetMicVolume(volume); } void LLVoiceClient::tuningSetSpeakerVolume(float volume) { - if (mVoiceModule) mVoiceModule->tuningSetSpeakerVolume(volume); + LLWebRTCVoiceClient::getInstance()->tuningSetSpeakerVolume(volume); } float LLVoiceClient::tuningGetEnergy(void) { - if (mVoiceModule) - { - return mVoiceModule->tuningGetEnergy(); - } - else - { - return 0.0; - } + return LLWebRTCVoiceClient::getInstance()->tuningGetEnergy(); } - //------------------------------------------------ // devices bool LLVoiceClient::deviceSettingsAvailable() { - if (mVoiceModule) - { - return mVoiceModule->deviceSettingsAvailable(); - } - else - { - return false; - } + return LLWebRTCVoiceClient::getInstance()->deviceSettingsAvailable(); } bool LLVoiceClient::deviceSettingsUpdated() { - if (mVoiceModule) - { - return mVoiceModule->deviceSettingsUpdated(); - } - else - { - return false; - } + return LLWebRTCVoiceClient::getInstance()->deviceSettingsUpdated(); } void LLVoiceClient::refreshDeviceLists(bool clearCurrentList) { - if (mVoiceModule) mVoiceModule->refreshDeviceLists(clearCurrentList); + LLWebRTCVoiceClient::getInstance()->refreshDeviceLists(clearCurrentList); } void LLVoiceClient::setCaptureDevice(const std::string& name) { - if (mVoiceModule) mVoiceModule->setCaptureDevice(name); - + LLWebRTCVoiceClient::getInstance()->setCaptureDevice(name); + LLVivoxVoiceClient::getInstance()->setCaptureDevice(name); } void LLVoiceClient::setRenderDevice(const std::string& name) { - if (mVoiceModule) mVoiceModule->setRenderDevice(name); + LLWebRTCVoiceClient::getInstance()->setRenderDevice(name); + LLVivoxVoiceClient::getInstance()->setRenderDevice(name); } const LLVoiceDeviceList& LLVoiceClient::getCaptureDevices() { - static LLVoiceDeviceList nullCaptureDevices; - if (mVoiceModule) - { - return mVoiceModule->getCaptureDevices(); - } - else - { - return nullCaptureDevices; - } + return LLWebRTCVoiceClient::getInstance()->getCaptureDevices(); } const LLVoiceDeviceList& LLVoiceClient::getRenderDevices() { - static LLVoiceDeviceList nullRenderDevices; - if (mVoiceModule) - { - return mVoiceModule->getRenderDevices(); - } - else - { - return nullRenderDevices; - } + return LLWebRTCVoiceClient::getInstance()->getRenderDevices(); } @@ -345,74 +373,30 @@ const LLVoiceDeviceList& LLVoiceClient::getRenderDevices() void LLVoiceClient::getParticipantList(std::set<LLUUID> &participants) { - if (mVoiceModule) - { - mVoiceModule->getParticipantList(participants); - } - else - { - participants = std::set<LLUUID>(); - } + LLWebRTCVoiceClient::getInstance()->getParticipantList(participants); + LLVivoxVoiceClient::getInstance()->getParticipantList(participants); } bool LLVoiceClient::isParticipant(const LLUUID &speaker_id) { - if(mVoiceModule) - { - return mVoiceModule->isParticipant(speaker_id); - } - return false; + return LLWebRTCVoiceClient::getInstance()->isParticipant(speaker_id) || + LLVivoxVoiceClient::getInstance()->isParticipant(speaker_id); } //-------------------------------------------------- // text chat - BOOL LLVoiceClient::isSessionTextIMPossible(const LLUUID& id) { - if (mVoiceModule) - { - return mVoiceModule->isSessionTextIMPossible(id); - } - else - { - return FALSE; - } + // all sessions can do TextIM, as we no longer support PSTN + return TRUE; } BOOL LLVoiceClient::isSessionCallBackPossible(const LLUUID& id) { - if (mVoiceModule) - { - return mVoiceModule->isSessionCallBackPossible(id); - } - else - { - return FALSE; - } -} - -/* obsolete -BOOL LLVoiceClient::sendTextMessage(const LLUUID& participant_id, const std::string& message) -{ - if (mVoiceModule) - { - return mVoiceModule->sendTextMessage(participant_id, message); - } - else - { - return FALSE; - } -} -*/ - -void LLVoiceClient::endUserIMSession(const LLUUID& participant_id) -{ - if (mVoiceModule) - { - // mVoiceModule->endUserIMSession(participant_id); // A SLim leftover - } + // we don't support PSTN calls anymore. (did we ever?) + return TRUE; } //---------------------------------------------- @@ -420,9 +404,9 @@ void LLVoiceClient::endUserIMSession(const LLUUID& participant_id) bool LLVoiceClient::inProximalChannel() { - if (mVoiceModule) + if (mSpatialVoiceModule) { - return mVoiceModule->inProximalChannel(); + return mSpatialVoiceModule->inProximalChannel(); } else { @@ -431,104 +415,117 @@ bool LLVoiceClient::inProximalChannel() } void LLVoiceClient::setNonSpatialChannel( - const std::string &uri, - const std::string &credentials) + const LLSD& channelInfo, + bool notify_on_first_join, + bool hangup_on_last_leave) { - if (mVoiceModule) + setNonSpatialVoiceModule(channelInfo["voice_server_type"].asString()); + if (mSpatialVoiceModule) { - mVoiceModule->setNonSpatialChannel(uri, credentials); + mSpatialVoiceModule->processChannels(false); } + if (mNonSpatialVoiceModule) + { + mNonSpatialVoiceModule->setNonSpatialChannel(channelInfo, notify_on_first_join, hangup_on_last_leave); + mNonSpatialVoiceModule->processChannels(true); + } } -void LLVoiceClient::setSpatialChannel( - const std::string &uri, - const std::string &credentials) +void LLVoiceClient::setSpatialChannel(const LLSD &channelInfo) { - if (mVoiceModule) + LLViewerRegion *region = gAgent.getRegion(); + if (region && region->simulatorFeaturesReceived()) + { + LLSD simulatorFeatures; + region->getSimulatorFeatures(simulatorFeatures); + setSpatialVoiceModule(simulatorFeatures["VoiceServerType"].asString()); + } + if (mNonSpatialVoiceModule) { - mVoiceModule->setSpatialChannel(uri, credentials); + mNonSpatialVoiceModule->leaveNonSpatialChannel(); + mNonSpatialVoiceModule->processChannels(false); + mNonSpatialVoiceModule = nullptr; + } + if (mSpatialVoiceModule) + { + mSpatialVoiceModule->setSpatialChannel(channelInfo); + mSpatialVoiceModule->processChannels(true); } } void LLVoiceClient::leaveNonSpatialChannel() { - if (mVoiceModule) + if (mNonSpatialVoiceModule) { - mVoiceModule->leaveNonSpatialChannel(); + mNonSpatialVoiceModule->leaveNonSpatialChannel(); + mNonSpatialVoiceModule->processChannels(false); + mNonSpatialVoiceModule = nullptr; } + if (mSpatialVoiceModule) + { + mSpatialVoiceModule->processChannels(true); + ; + } } -void LLVoiceClient::leaveChannel(void) +void LLVoiceClient::activateSpatialChannel(bool activate) { - if (mVoiceModule) + if (mSpatialVoiceModule) { - mVoiceModule->leaveChannel(); + mSpatialVoiceModule->processChannels(activate); } } -std::string LLVoiceClient::getCurrentChannel() +bool LLVoiceClient::isCurrentChannel(const LLSD& channelInfo) { - if (mVoiceModule) - { - return mVoiceModule->getCurrentChannel(); - } - else - { - return std::string(); - } + return LLWebRTCVoiceClient::getInstance()->isCurrentChannel(channelInfo) || + LLVivoxVoiceClient::getInstance()->isCurrentChannel(channelInfo); } - -//--------------------------------------- -// invitations - -void LLVoiceClient::callUser(const LLUUID &uuid) +bool LLVoiceClient::compareChannels(const LLSD &channelInfo1, const LLSD &channelInfo2) { - if (mVoiceModule) mVoiceModule->callUser(uuid); + return LLWebRTCVoiceClient::getInstance()->compareChannels(channelInfo1, channelInfo2) || + LLVivoxVoiceClient::getInstance()->compareChannels(channelInfo1, channelInfo2); } -bool LLVoiceClient::isValidChannel(std::string &session_handle) +LLVoiceP2PIncomingCallInterfacePtr LLVoiceClient::getIncomingCallInterface(const LLSD& voice_call_info) { - if (mVoiceModule) - { - return mVoiceModule->isValidChannel(session_handle); - } - else + LLVoiceModuleInterface *module = getVoiceModule(voice_call_info["voice_server_type"]); + if (module) { - return false; + return module->getIncomingCallInterface(voice_call_info); } -} + return nullptr; -bool LLVoiceClient::answerInvite(std::string &channelHandle) -{ - if (mVoiceModule) - { - return mVoiceModule->answerInvite(channelHandle); - } - else - { - return false; - } } -void LLVoiceClient::declineInvite(std::string &channelHandle) +//--------------------------------------- +// outgoing calls +LLVoiceP2POutgoingCallInterface *LLVoiceClient::getOutgoingCallInterface(const LLSD& voiceChannelInfo) { - if (mVoiceModule) mVoiceModule->declineInvite(channelHandle); + std::string voiceServerType = gSavedSettings.getString("VoiceServerType"); + if (voiceChannelInfo.has("voice_server_type")) + { + voiceServerType = voiceChannelInfo["voice_server_type"].asString(); + } + LLVoiceModuleInterface *module = getVoiceModule(voiceServerType); + return dynamic_cast<LLVoiceP2POutgoingCallInterface *>(module); } - //------------------------------------------ // Volume/gain void LLVoiceClient::setVoiceVolume(F32 volume) { - if (mVoiceModule) mVoiceModule->setVoiceVolume(volume); + LLWebRTCVoiceClient::getInstance()->setVoiceVolume(volume); + LLVivoxVoiceClient::getInstance()->setVoiceVolume(volume); } -void LLVoiceClient::setMicGain(F32 volume) +void LLVoiceClient::setMicGain(F32 gain) { - if (mVoiceModule) mVoiceModule->setMicGain(volume); + LLWebRTCVoiceClient::getInstance()->setMicGain(gain); + LLVivoxVoiceClient::getInstance()->setMicGain(gain); } @@ -537,29 +534,20 @@ void LLVoiceClient::setMicGain(F32 volume) bool LLVoiceClient::voiceEnabled() { - if (mVoiceModule) - { - return mVoiceModule->voiceEnabled(); - } - else - { - return false; - } + return gSavedSettings.getBOOL("EnableVoiceChat") && !gSavedSettings.getBOOL("CmdLineDisableVoice") && !gNonInteractive; } void LLVoiceClient::setVoiceEnabled(bool enabled) { - if (mVoiceModule) - { - mVoiceModule->setVoiceEnabled(enabled); - } + LLWebRTCVoiceClient::getInstance()->setVoiceEnabled(enabled); + LLVivoxVoiceClient::getInstance()->setVoiceEnabled(enabled); } void LLVoiceClient::updateMicMuteLogic() { // If not configured to use PTT, the mic should be open (otherwise the user will be unable to speak). bool new_mic_mute = false; - + if(mUsePTT) { // If configured to use PTT, track the user state. @@ -571,25 +559,8 @@ void LLVoiceClient::updateMicMuteLogic() // Either of these always overrides any other PTT setting. new_mic_mute = true; } - - if (mVoiceModule) mVoiceModule->setMuteMic(new_mic_mute); -} - -void LLVoiceClient::setLipSyncEnabled(BOOL enabled) -{ - if (mVoiceModule) mVoiceModule->setLipSyncEnabled(enabled); -} - -BOOL LLVoiceClient::lipSyncEnabled() -{ - if (mVoiceModule) - { - return mVoiceModule->lipSyncEnabled(); - } - else - { - return false; - } + LLWebRTCVoiceClient::getInstance()->setMuteMic(new_mic_mute); + LLVivoxVoiceClient::getInstance()->setMuteMic(new_mic_mute); } void LLVoiceClient::setMuteMic(bool muted) @@ -627,7 +598,7 @@ void LLVoiceClient::setUsePTT(bool usePTT) mUserPTTState = false; } mUsePTT = usePTT; - + updateMicMuteLogic(); } @@ -638,7 +609,7 @@ void LLVoiceClient::setPTTIsToggle(bool PTTIsToggle) // When the user turns off toggle, reset the current state. mUserPTTState = false; } - + mPTTIsToggle = PTTIsToggle; updateMicMuteLogic(); @@ -653,12 +624,12 @@ void LLVoiceClient::inputUserControlState(bool down) { if(mPTTIsToggle) { - if(down) // toggle open-mic state on 'down' + if(down) // toggle open-mic state on 'down' { toggleUserPTTState(); } } - else // set open-mic state as an absolute + else // set open-mic state as an absolute { setUserPTTState(down); } @@ -675,117 +646,71 @@ void LLVoiceClient::toggleUserPTTState(void) BOOL LLVoiceClient::getVoiceEnabled(const LLUUID& id) { - if (mVoiceModule) - { - return mVoiceModule->getVoiceEnabled(id); - } - else - { - return FALSE; - } + return isParticipant(id) ? TRUE : FALSE; } std::string LLVoiceClient::getDisplayName(const LLUUID& id) { - if (mVoiceModule) - { - return mVoiceModule->getDisplayName(id); - } - else + std::string result = LLWebRTCVoiceClient::getInstance()->getDisplayName(id); + if (result.empty()) { - return std::string(); + result = LLVivoxVoiceClient::getInstance()->getDisplayName(id); } + return result; } bool LLVoiceClient::isVoiceWorking() const { - if (mVoiceModule) - { - return mVoiceModule->isVoiceWorking(); - } - return false; + return LLVivoxVoiceClient::getInstance()->isVoiceWorking() || + LLWebRTCVoiceClient::getInstance()->isVoiceWorking(); } BOOL LLVoiceClient::isParticipantAvatar(const LLUUID& id) { - if (mVoiceModule) - { - return mVoiceModule->isParticipantAvatar(id); - } - else - { - return FALSE; - } + return TRUE; } BOOL LLVoiceClient::isOnlineSIP(const LLUUID& id) { - return FALSE; + return FALSE; } BOOL LLVoiceClient::getIsSpeaking(const LLUUID& id) { - if (mVoiceModule) - { - return mVoiceModule->getIsSpeaking(id); - } - else - { - return FALSE; - } + return LLWebRTCVoiceClient::getInstance()->getIsSpeaking(id) || + LLVivoxVoiceClient::getInstance()->getIsSpeaking(id); } BOOL LLVoiceClient::getIsModeratorMuted(const LLUUID& id) { - if (mVoiceModule) - { - return mVoiceModule->getIsModeratorMuted(id); - } - else - { - return FALSE; - } + // don't bother worrying about p2p calls, as + // p2p calls don't have mute. + return LLWebRTCVoiceClient::getInstance()->getIsModeratorMuted(id) || + LLVivoxVoiceClient::getInstance()->getIsModeratorMuted(id); } F32 LLVoiceClient::getCurrentPower(const LLUUID& id) -{ - if (mVoiceModule) - { - return mVoiceModule->getCurrentPower(id); - } - else - { - return 0.0; - } +{ + return std::fmax(LLVivoxVoiceClient::getInstance()->getCurrentPower(id), + LLWebRTCVoiceClient::getInstance()->getCurrentPower(id)); } BOOL LLVoiceClient::getOnMuteList(const LLUUID& id) { - if (mVoiceModule) - { - return mVoiceModule->getOnMuteList(id); - } - else - { - return FALSE; - } + // don't bother worrying about p2p calls, as + // p2p calls don't have mute. + return LLMuteList::getInstance()->isMuted(id, LLMute::flagVoiceChat); } F32 LLVoiceClient::getUserVolume(const LLUUID& id) { - if (mVoiceModule) - { - return mVoiceModule->getUserVolume(id); - } - else - { - return 0.0; - } + return std::fmax(LLVivoxVoiceClient::getInstance()->getUserVolume(id), LLWebRTCVoiceClient::getInstance()->getUserVolume(id)); } void LLVoiceClient::setUserVolume(const LLUUID& id, F32 volume) { - if (mVoiceModule) mVoiceModule->setUserVolume(id, volume); + LLWebRTCVoiceClient::getInstance()->setUserVolume(id, volume); + LLVivoxVoiceClient::getInstance()->setUserVolume(id, volume); } //-------------------------------------------------- @@ -793,48 +718,49 @@ void LLVoiceClient::setUserVolume(const LLUUID& id, F32 volume) void LLVoiceClient::addObserver(LLVoiceClientStatusObserver* observer) { - if (mVoiceModule) mVoiceModule->addObserver(observer); + LLVivoxVoiceClient::getInstance()->addObserver(observer); + LLWebRTCVoiceClient::getInstance()->addObserver(observer); } void LLVoiceClient::removeObserver(LLVoiceClientStatusObserver* observer) { - if (mVoiceModule) - { - mVoiceModule->removeObserver(observer); - } + LLVivoxVoiceClient::getInstance()->removeObserver(observer); + LLWebRTCVoiceClient::getInstance()->removeObserver(observer); } void LLVoiceClient::addObserver(LLFriendObserver* observer) { - if (mVoiceModule) mVoiceModule->addObserver(observer); + LLVivoxVoiceClient::getInstance()->addObserver(observer); + LLWebRTCVoiceClient::getInstance()->addObserver(observer); } void LLVoiceClient::removeObserver(LLFriendObserver* observer) { - if (mVoiceModule) - { - mVoiceModule->removeObserver(observer); - } + LLVivoxVoiceClient::getInstance()->removeObserver(observer); + LLWebRTCVoiceClient::getInstance()->removeObserver(observer); } void LLVoiceClient::addObserver(LLVoiceClientParticipantObserver* observer) { - if (mVoiceModule) mVoiceModule->addObserver(observer); + LLVivoxVoiceClient::getInstance()->addObserver(observer); + LLWebRTCVoiceClient::getInstance()->addObserver(observer); } void LLVoiceClient::removeObserver(LLVoiceClientParticipantObserver* observer) { - if (mVoiceModule) - { - mVoiceModule->removeObserver(observer); - } + LLVivoxVoiceClient::getInstance()->removeObserver(observer); + LLWebRTCVoiceClient::getInstance()->removeObserver(observer); } std::string LLVoiceClient::sipURIFromID(const LLUUID &id) { - if (mVoiceModule) + if (mNonSpatialVoiceModule) + { + return mNonSpatialVoiceModule->sipURIFromID(id); + } + else if (mSpatialVoiceModule) { - return mVoiceModule->sipURIFromID(id); + return mSpatialVoiceModule->sipURIFromID(id); } else { @@ -844,7 +770,7 @@ std::string LLVoiceClient::sipURIFromID(const LLUUID &id) LLVoiceEffectInterface* LLVoiceClient::getVoiceEffectInterface() const { - return getVoiceEffectEnabled() ? dynamic_cast<LLVoiceEffectInterface*>(mVoiceModule) : NULL; + return getVoiceEffectEnabled() ? dynamic_cast<LLVoiceEffectInterface*>(mSpatialVoiceModule) : NULL; } /////////////////// @@ -852,31 +778,54 @@ LLVoiceEffectInterface* LLVoiceClient::getVoiceEffectInterface() const class LLViewerRequiredVoiceVersion : public LLHTTPNode { - static BOOL sAlertedUser; + static bool sAlertedUser; virtual void post( LLHTTPNode::ResponsePtr response, const LLSD& context, const LLSD& input) const { - //You received this messsage (most likely on region cross or - //teleport) - if ( input.has("body") && input["body"].has("major_version") ) + + + std::string voice_server_type = "vivox"; + if (input.has("body") && input["body"].has("voice_server_type")) { - int major_voice_version = - input["body"]["major_version"].asInteger(); - // int minor_voice_version = - // input["body"]["minor_version"].asInteger(); - LLVoiceVersionInfo versionInfo = LLVoiceClient::getInstance()->getVersion(); - - if (major_voice_version > 1) - { - if (!sAlertedUser) - { - //sAlertedUser = TRUE; - LLNotificationsUtil::add("VoiceVersionMismatch"); - gSavedSettings.setBOOL("EnableVoiceChat", FALSE); // toggles listener - } - } + voice_server_type = input["body"]["voice_server_type"].asString(); + } + + LLVoiceModuleInterface *voiceModule = NULL; + + if (voice_server_type == "vivox" || voice_server_type.empty()) + { + voiceModule = (LLVoiceModuleInterface *) LLVivoxVoiceClient::getInstance(); + } + else if (voice_server_type == "webrtc") + { + voiceModule = (LLVoiceModuleInterface *) LLWebRTCVoiceClient::getInstance(); + } + else + { + LL_WARNS("Voice") << "Unknown voice server type " << voice_server_type << LL_ENDL; + if (!sAlertedUser) + { + // sAlertedUser = true; + LLNotificationsUtil::add("VoiceVersionMismatch"); + } + return; + } + + LLVoiceVersionInfo versionInfo = voiceModule->getVersion(); + if (input.has("body") && input["body"].has("major_version") && + input["body"]["major_version"].asInteger() > versionInfo.majorVersion) + { + if (!sAlertedUser) + { + // sAlertedUser = true; + LLNotificationsUtil::add("VoiceVersionMismatch"); + LL_WARNS("Voice") << "Voice server version mismatch " << input["body"]["major_version"].asInteger() << "/" + << versionInfo.majorVersion + << LL_ENDL; + } + return; } } }; @@ -890,41 +839,27 @@ class LLViewerParcelVoiceInfo : public LLHTTPNode { //the parcel you are in has changed something about its //voice information - + //this is a misnomer, as it can also be when you are not in //a parcel at all. Should really be something like //LLViewerVoiceInfoChanged..... if ( input.has("body") ) { LLSD body = input["body"]; - + //body has "region_name" (str), "parcel_local_id"(int), //"voice_credentials" (map). - + //body["voice_credentials"] has "channel_uri" (str), //body["voice_credentials"] has "channel_credentials" (str) - + //if we really wanted to be extra careful, //we'd check the supplied //local parcel id to make sure it's for the same parcel //we believe we're in if ( body.has("voice_credentials") ) { - LLSD voice_credentials = body["voice_credentials"]; - std::string uri; - std::string credentials; - - if ( voice_credentials.has("channel_uri") ) - { - uri = voice_credentials["channel_uri"].asString(); - } - if ( voice_credentials.has("channel_credentials") ) - { - credentials = - voice_credentials["channel_credentials"].asString(); - } - - LLVoiceClient::getInstance()->setSpatialChannel(uri, credentials); + LLVoiceClient::getInstance()->setSpatialChannel(body["voice_credentials"]); } } } @@ -966,7 +901,7 @@ void LLSpeakerVolumeStorage::storeSpeakerVolume(const LLUUID& speaker_id, F32 vo bool LLSpeakerVolumeStorage::getSpeakerVolume(const LLUUID& speaker_id, F32& volume) { speaker_data_map_t::const_iterator it = mSpeakersData.find(speaker_id); - + if (it != mSpeakersData.end()) { volume = it->second; @@ -1045,9 +980,9 @@ void LLSpeakerVolumeStorage::load() if (LLSDParser::PARSE_FAILURE == LLSDSerialize::fromXML(settings_llsd, file)) { LL_WARNS("Voice") << "failed to parse " << filename << LL_ENDL; - + } - + } for (LLSD::map_const_iterator iter = settings_llsd.beginMap(); @@ -1087,7 +1022,7 @@ void LLSpeakerVolumeStorage::save() } } -BOOL LLViewerRequiredVoiceVersion::sAlertedUser = FALSE; +bool LLViewerRequiredVoiceVersion::sAlertedUser = false; LLHTTPRegistration<LLViewerParcelVoiceInfo> gHTTPRegistrationMessageParcelVoiceInfo( diff --git a/indra/newview/llvoiceclient.h b/indra/newview/llvoiceclient.h index aa67502908..46dd325e21 100644 --- a/indra/newview/llvoiceclient.h +++ b/indra/newview/llvoiceclient.h @@ -1,25 +1,25 @@ -/** +/** * @file llvoiceclient.h * @brief Declaration of LLVoiceClient class which is the interface to the voice client process. * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ @@ -34,9 +34,11 @@ class LLVOAvatar; #include "lliosocket.h" #include "v3math.h" #include "llframetimer.h" +#include "llsingleton.h" #include "llcallingcard.h" // for LLFriendObserver #include "llsecapi.h" #include "llcontrol.h" +#include <boost/shared_ptr.hpp> // devices @@ -86,19 +88,54 @@ public: } EStatusType; virtual ~LLVoiceClientStatusObserver() { } - virtual void onChange(EStatusType status, const std::string &channelURI, bool proximal) = 0; + virtual void onChange(EStatusType status, const LLSD& channelInfo, bool proximal) = 0; static std::string status2string(EStatusType inStatus); }; struct LLVoiceVersionInfo { - std::string serverType; - std::string serverVersion; + std::string voiceServerType; + std::string internalVoiceServerType; + int majorVersion; + int minorVersion; + std::string serverVersion; std::string mBuildVersion; }; ////////////////////////////////// +/// @class LLVoiceP2POutgoingCallInterface +/// @brief Outgoing call interface +/// +/// For providers that support P2P signaling (vivox) +///////////////////////////////// + +class LLVoiceP2POutgoingCallInterface +{ + public: + // initiate an outgoing call to a user + virtual void callUser(const LLUUID &agentID) = 0; + virtual void hangup() = 0; +}; + +////////////////////////////////// +/// @class LLVoiceP2PIncomingCallInterface +/// @brief Incoming call interface +/// +/// For providers that support P2P signaling (vivox) +///////////////////////////////// +class LLVoiceP2PIncomingCallInterface +{ + public: + virtual ~LLVoiceP2PIncomingCallInterface() {} + + virtual bool answerInvite() = 0; + virtual void declineInvite() = 0; +}; + +typedef boost::shared_ptr<LLVoiceP2PIncomingCallInterface> LLVoiceP2PIncomingCallInterfacePtr; + +////////////////////////////////// /// @class LLVoiceModuleInterface /// @brief Voice module interface /// @@ -110,30 +147,32 @@ class LLVoiceModuleInterface public: LLVoiceModuleInterface() {} virtual ~LLVoiceModuleInterface() {} - + virtual void init(LLPumpIO *pump)=0; // Call this once at application startup (creates connector) virtual void terminate()=0; // Call this to clean up during shutdown - + virtual void updateSettings()=0; // call after loading settings and whenever they change - + virtual bool isVoiceWorking() const = 0; // connected to a voice server and voice channel - + virtual void setHidden(bool hidden)=0; // Hides the user from voice. virtual const LLVoiceVersionInfo& getVersion()=0; - + + + ///////////////////// /// @name Tuning //@{ virtual void tuningStart()=0; virtual void tuningStop()=0; virtual bool inTuningMode()=0; - + virtual void tuningSetMicVolume(float volume)=0; virtual void tuningSetSpeakerVolume(float volume)=0; virtual float tuningGetEnergy(void)=0; //@} - + ///////////////////// /// @name Devices //@{ @@ -141,114 +180,110 @@ public: // i.e. when the daemon is running and connected, and the device lists are populated. virtual bool deviceSettingsAvailable()=0; virtual bool deviceSettingsUpdated() = 0; - + // Requery the vivox daemon for the current list of input/output devices. // If you pass true for clearCurrentList, deviceSettingsAvailable() will be false until the query has completed // (use this if you want to know when it's done). // If you pass false, you'll have no way to know when the query finishes, but the device lists will not appear empty in the interim. virtual void refreshDeviceLists(bool clearCurrentList = true)=0; - + virtual void setCaptureDevice(const std::string& name)=0; virtual void setRenderDevice(const std::string& name)=0; - + virtual LLVoiceDeviceList& getCaptureDevices()=0; virtual LLVoiceDeviceList& getRenderDevices()=0; - + virtual void getParticipantList(std::set<LLUUID> &participants)=0; virtual bool isParticipant(const LLUUID& speaker_id)=0; //@} - + //////////////////////////// /// @ name Channel stuff //@{ // returns true iff the user is currently in a proximal (local spatial) channel. // Note that gestures should only fire if this returns true. virtual bool inProximalChannel()=0; - - virtual void setNonSpatialChannel(const std::string &uri, - const std::string &credentials)=0; - - virtual bool setSpatialChannel(const std::string &uri, - const std::string &credentials)=0; - - virtual void leaveNonSpatialChannel()=0; - - virtual void leaveChannel(void)=0; - - // Returns the URI of the current channel, or an empty string if not currently in a channel. - // NOTE that it will return an empty string if it's in the process of joining a channel. - virtual std::string getCurrentChannel()=0; + + virtual void setNonSpatialChannel(const LLSD& channelInfo, + bool notify_on_first_join, + bool hangup_on_last_leave)=0; + + virtual bool setSpatialChannel(const LLSD& channelInfo)=0; + + virtual void leaveNonSpatialChannel() = 0; + virtual void processChannels(bool process) = 0; + + virtual bool isCurrentChannel(const LLSD &channelInfo) = 0; + virtual bool compareChannels(const LLSD &channelInfo1, const LLSD &channelInfo2) = 0; + //@} - - + + ////////////////////////// - /// @name invitations + /// @name p2p //@{ - // start a voice channel with the specified user - virtual void callUser(const LLUUID &uuid)=0; - virtual bool isValidChannel(std::string& channelHandle)=0; - virtual bool answerInvite(std::string &channelHandle)=0; - virtual void declineInvite(std::string &channelHandle)=0; + + // initiate a call with a peer using the P2P interface, which only applies to some + // voice server types. Otherwise, a group call should be used for P2P + virtual LLVoiceP2POutgoingCallInterface* getOutgoingCallInterface() = 0; + + // an incoming call was received, and the incoming call dialogue is asking for an interface to + // answer or decline. + virtual LLVoiceP2PIncomingCallInterfacePtr getIncomingCallInterface(const LLSD &voice_call_info) = 0; //@} - + ///////////////////////// /// @name Volume/gain //@{ virtual void setVoiceVolume(F32 volume)=0; virtual void setMicGain(F32 volume)=0; //@} - + ///////////////////////// /// @name enable disable voice and features //@{ - virtual bool voiceEnabled()=0; virtual void setVoiceEnabled(bool enabled)=0; - virtual void setLipSyncEnabled(BOOL enabled)=0; - virtual BOOL lipSyncEnabled()=0; virtual void setMuteMic(bool muted)=0; // Set the mute state of the local mic. //@} - + ////////////////////////// /// @name nearby speaker accessors //@{ - virtual BOOL getVoiceEnabled(const LLUUID& id)=0; // true if we've received data for this avatar virtual std::string getDisplayName(const LLUUID& id)=0; virtual BOOL isParticipantAvatar(const LLUUID &id)=0; virtual BOOL getIsSpeaking(const LLUUID& id)=0; virtual BOOL getIsModeratorMuted(const LLUUID& id)=0; virtual F32 getCurrentPower(const LLUUID& id)=0; // "power" is related to "amplitude" in a defined way. I'm just not sure what the formula is... - virtual BOOL getOnMuteList(const LLUUID& id)=0; virtual F32 getUserVolume(const LLUUID& id)=0; - virtual void setUserVolume(const LLUUID& id, F32 volume)=0; // set's volume for specified agent, from 0-1 (where .5 is nominal) + virtual void setUserVolume(const LLUUID& id, F32 volume)=0; // set's volume for specified agent, from 0-1 (where .5 is nominal) //@} - + ////////////////////////// /// @name text chat //@{ virtual BOOL isSessionTextIMPossible(const LLUUID& id)=0; virtual BOOL isSessionCallBackPossible(const LLUUID& id)=0; //virtual BOOL sendTextMessage(const LLUUID& participant_id, const std::string& message)=0; - virtual void endUserIMSession(const LLUUID &uuid)=0; //@} - + // authorize the user virtual void userAuthorized(const std::string& user_id, const LLUUID &agentID)=0; - + ////////////////////////////// /// @name Status notification //@{ virtual void addObserver(LLVoiceClientStatusObserver* observer)=0; virtual void removeObserver(LLVoiceClientStatusObserver* observer)=0; virtual void addObserver(LLFriendObserver* observer)=0; - virtual void removeObserver(LLFriendObserver* observer)=0; + virtual void removeObserver(LLFriendObserver* observer)=0; virtual void addObserver(LLVoiceClientParticipantObserver* observer)=0; - virtual void removeObserver(LLVoiceClientParticipantObserver* observer)=0; + virtual void removeObserver(LLVoiceClientParticipantObserver* observer)=0; //@} - + virtual std::string sipURIFromID(const LLUUID &id)=0; //@} - + }; @@ -320,9 +355,9 @@ public: micro_changed_signal_t mMicroChangedSignal; void terminate(); // Call this to clean up during shutdown - + const LLVoiceVersionInfo getVersion(); - + static const F32 OVERDRIVEN_POWER_LEVEL; static const F32 VOLUME_MIN; @@ -337,18 +372,18 @@ public: void tuningStart(); void tuningStop(); bool inTuningMode(); - + void tuningSetMicVolume(float volume); void tuningSetSpeakerVolume(float volume); float tuningGetEnergy(void); - + // devices - + // This returns true when it's safe to bring up the "device settings" dialog in the prefs. // i.e. when the daemon is running and connected, and the device lists are populated. bool deviceSettingsAvailable(); bool deviceSettingsUpdated(); // returns true when the device list has been updated recently. - + // Requery the vivox daemon for the current list of input/output devices. // If you pass true for clearCurrentList, deviceSettingsAvailable() will be false until the query has completed // (use this if you want to know when it's done). @@ -365,60 +400,59 @@ public: //////////////////////////// // Channel stuff // - + // returns true iff the user is currently in a proximal (local spatial) channel. // Note that gestures should only fire if this returns true. bool inProximalChannel(); - void setNonSpatialChannel( - const std::string &uri, - const std::string &credentials); - void setSpatialChannel( - const std::string &uri, - const std::string &credentials); + + void setNonSpatialChannel(const LLSD& channelInfo, + bool notify_on_first_join, + bool hangup_on_last_leave); + + void setSpatialChannel(const LLSD &channelInfo); + + void activateSpatialChannel(bool activate); + void leaveNonSpatialChannel(); - - // Returns the URI of the current channel, or an empty string if not currently in a channel. - // NOTE that it will return an empty string if it's in the process of joining a channel. - std::string getCurrentChannel(); - // start a voice channel with the specified user - void callUser(const LLUUID &uuid); - bool isValidChannel(std::string& channelHandle); - bool answerInvite(std::string &channelHandle); - void declineInvite(std::string &channelHandle); - void leaveChannel(void); // call this on logout or teleport begin - - + + bool isCurrentChannel(const LLSD& channelInfo); + + bool compareChannels(const LLSD& channelInfo1, const LLSD& channelInfo2); + + // initiate a call with a peer using the P2P interface, which only applies to some + // voice server types. Otherwise, a group call should be used for P2P + LLVoiceP2POutgoingCallInterface* getOutgoingCallInterface(const LLSD& voiceChannelInfo); + + LLVoiceP2PIncomingCallInterfacePtr getIncomingCallInterface(const LLSD &voiceCallInfo); + ///////////////////////////// // Sending updates of current state - + void setVoiceVolume(F32 volume); void setMicGain(F32 volume); - void setUserVolume(const LLUUID& id, F32 volume); // set's volume for specified agent, from 0-1 (where .5 is nominal) + void setUserVolume(const LLUUID& id, F32 volume); // set's volume for specified agent, from 0-1 (where .5 is nominal) bool voiceEnabled(); - void setLipSyncEnabled(BOOL enabled); void setMuteMic(bool muted); // Use this to mute the local mic (for when the client is minimized, etc), ignoring user PTT state. void setUserPTTState(bool ptt); bool getUserPTTState(); void toggleUserPTTState(void); - void inputUserControlState(bool down); // interpret any sort of up-down mic-open control input according to ptt-toggle prefs + void inputUserControlState(bool down); // interpret any sort of up-down mic-open control input according to ptt-toggle prefs void setVoiceEnabled(bool enabled); void setUsePTT(bool usePTT); void setPTTIsToggle(bool PTTIsToggle); - bool getPTTIsToggle(); + bool getPTTIsToggle(); void updateMicMuteLogic(); - BOOL lipSyncEnabled(); - boost::signals2::connection MicroChangedCallback(const micro_changed_signal_t::slot_type& cb ) { return mMicroChangedSignal.connect(cb); } - + ///////////////////////////// // Accessors for data related to nearby speakers BOOL getVoiceEnabled(const LLUUID& id); // true if we've received data for this avatar - std::string getDisplayName(const LLUUID& id); + std::string getDisplayName(const LLUUID& id); BOOL isOnlineSIP(const LLUUID &id); BOOL isParticipantAvatar(const LLUUID &id); BOOL getIsSpeaking(const LLUUID& id); @@ -428,32 +462,34 @@ public: F32 getUserVolume(const LLUUID& id); ///////////////////////////// - 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. void getParticipantList(std::set<LLUUID> &participants); bool isParticipant(const LLUUID& speaker_id); - + ////////////////////////// /// @name text chat //@{ BOOL isSessionTextIMPossible(const LLUUID& id); BOOL isSessionCallBackPossible(const LLUUID& id); //BOOL sendTextMessage(const LLUUID& participant_id, const std::string& message) const {return true;} ; - void endUserIMSession(const LLUUID &uuid); //@} - + + void setSpatialVoiceModule(const std::string& voice_server_type); + void setNonSpatialVoiceModule(const std::string &voice_server_type); void userAuthorized(const std::string& user_id, - const LLUUID &agentID); - + const LLUUID &agentID); + + void onRegionChanged(); + void onSimulatorFeaturesReceived(const LLUUID ®ion_id); + void addObserver(LLVoiceClientStatusObserver* observer); void removeObserver(LLVoiceClientStatusObserver* observer); void addObserver(LLFriendObserver* observer); void removeObserver(LLFriendObserver* observer); void addObserver(LLVoiceClientParticipantObserver* observer); void removeObserver(LLVoiceClientParticipantObserver* observer); - - std::string sipURIFromID(const LLUUID &id); + + std::string sipURIFromID(const LLUUID &id); ////////////////////////// /// @name Voice effects @@ -468,16 +504,18 @@ private: void init(LLPumpIO *pump); protected: - LLVoiceModuleInterface* mVoiceModule; + LLVoiceModuleInterface* mSpatialVoiceModule; + LLVoiceModuleInterface* mNonSpatialVoiceModule; LLPumpIO *m_servicePump; + boost::signals2::connection mSimulatorFeaturesReceivedSlot; LLCachedControl<bool> mVoiceEffectEnabled; LLCachedControl<std::string> mVoiceEffectDefault; bool mPTTDirty; bool mPTT; - + bool mUsePTT; S32 mPTTMouseButton; KEY mPTTKey; diff --git a/indra/newview/llvoicevivox.cpp b/indra/newview/llvoicevivox.cpp index 3725510b6a..9a1fb925eb 100644 --- a/indra/newview/llvoicevivox.cpp +++ b/indra/newview/llvoicevivox.cpp @@ -1,25 +1,25 @@ - /** + /** * @file LLVivoxVoiceClient.cpp * @brief Implementation of LLVivoxVoiceClient class which is the interface to the voice client process. * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ @@ -80,12 +80,14 @@ extern LLMenuBarGL* gMenuBarView; extern void handle_voice_morphing_subscribe(); +const std::string VIVOX_VOICE_SERVER_TYPE = "vivox"; + namespace { const F32 VOLUME_SCALE_VIVOX = 0.01f; const F32 SPEAKING_TIMEOUT = 1.f; - static const std::string VOICE_SERVER_TYPE = "Vivox"; + static const std::string VISIBLE_VOICE_SERVER_TYPE = "Vivox"; // Don't retry connecting to the daemon more frequently than this: const F32 DAEMON_CONNECT_THROTTLE_SECONDS = 1.0f; @@ -94,7 +96,7 @@ namespace { // Don't send positional updates more frequently than this: const F32 UPDATE_THROTTLE_SECONDS = 0.5f; - // Timeout for connection to Vivox + // Timeout for connection to Vivox const F32 CONNECT_ATTEMPT_TIMEOUT = 300.0f; const F32 CONNECT_DNS_TIMEOUT = 5.0f; const int CONNECT_RETRY_MAX = 3; @@ -113,8 +115,8 @@ namespace { const F32 SESSION_JOIN_TIMEOUT = 30.0f; // Defines the maximum number of times(in a row) "stateJoiningSession" case for spatial channel is reached in stateMachine() - // which is treated as normal. The is the number of frames to wait for a channel join before giving up. This was changed - // from the original count of 50 for two reason. Modern PCs have higher frame rates and sometimes the SLVoice process + // which is treated as normal. The is the number of frames to wait for a channel join before giving up. This was changed + // from the original count of 50 for two reason. Modern PCs have higher frame rates and sometimes the SLVoice process // backs up processing join requests. There is a log statement that records when channel joins take longer than 100 frames. const int MAX_NORMAL_JOINING_SPATIAL_NUM = 1500; @@ -133,17 +135,17 @@ namespace { static int scale_mic_volume(float volume) { - // incoming volume has the range [0.0 ... 2.0], with 1.0 as the default. - // Map it to Vivox levels as follows: 0.0 -> 30, 1.0 -> 50, 2.0 -> 70 + // incoming volume has the range [0.0 ... 2.0], with 1.0 as the default. + // Map it to Vivox levels as follows: 0.0 -> 30, 1.0 -> 50, 2.0 -> 70 return 30 + (int)(volume * 20.0f); } static int scale_speaker_volume(float volume) { - // incoming volume has the range [0.0 ... 1.0], with 0.5 as the default. - // Map it to Vivox levels as follows: 0.0 -> 30, 0.5 -> 50, 1.0 -> 70 + // incoming volume has the range [0.0 ... 1.0], with 0.5 as the default. + // Map it to Vivox levels as follows: 0.0 -> 30, 0.5 -> 50, 1.0 -> 70 return 30 + (int)(volume * 40.0f); - + } @@ -293,7 +295,6 @@ LLVivoxVoiceClient::LLVivoxVoiceClient() : mTuningSpeakerVolumeDirty(true), mDevicesListUpdated(false), - mAreaVoiceDisabled(false), mAudioSession(), // TBD - should be NULL mAudioSessionChanged(false), mNextAudioSession(), @@ -325,10 +326,9 @@ LLVivoxVoiceClient::LLVivoxVoiceClient() : mMicVolumeDirty(true), mVoiceEnabled(false), + mProcessChannels(false), mWriteInProgress(false), - mLipSyncEnabled(false), - mVoiceFontsReceived(false), mVoiceFontsNew(false), mVoiceFontListDirty(false), @@ -358,19 +358,24 @@ LLVivoxVoiceClient::LLVivoxVoiceClient() : mSpeakerVolume = scale_speaker_volume(0); mVoiceVersion.serverVersion = ""; - mVoiceVersion.serverType = VOICE_SERVER_TYPE; - + mVoiceVersion.voiceServerType = VISIBLE_VOICE_SERVER_TYPE; + mVoiceVersion.internalVoiceServerType = VIVOX_VOICE_SERVER_TYPE; + mVoiceVersion.majorVersion = 1; + mVoiceVersion.minorVersion = 0; + mVoiceVersion.mBuildVersion = ""; + mVoiceVersion.serverVersion = ""; + // gMuteListp isn't set up at this point, so we defer this until later. // gMuteListp->addObserver(&mutelist_listener); - + #if LL_DARWIN || LL_LINUX // HACK: THIS DOES NOT BELONG HERE // When the vivox daemon dies, the next write attempt on our socket generates a SIGPIPE, which kills us. // This should cause us to ignore SIGPIPE and handle the error through proper channels. // This should really be set up elsewhere. Where should it go? signal(SIGPIPE, SIG_IGN); - + // Since we're now launching the gateway with fork/exec instead of system(), we need to deal with zombie processes. // Ignoring SIGCHLD should prevent zombies from being created. Alternately, we could use wait(), but I'd rather not do that. signal(SIGCHLD, SIG_IGN); @@ -410,13 +415,13 @@ void LLVivoxVoiceClient::terminate() return; } - // needs to be done manually here since we will not get another pass in + // needs to be done manually here since we will not get another pass in // coroutines... that mechanism is long since gone. if (mIsLoggedIn) { logoutOfVivox(false); } - + if(sConnected) { breakVoiceConnection(false); @@ -437,7 +442,7 @@ void LLVivoxVoiceClient::terminate() void LLVivoxVoiceClient::cleanUp() { LL_DEBUGS("Voice") << LL_ENDL; - + deleteAllSessions(); deleteAllVoiceFonts(); deleteVoiceFontTemplates(); @@ -455,7 +460,7 @@ const LLVoiceVersionInfo& LLVivoxVoiceClient::getVersion() void LLVivoxVoiceClient::updateSettings() { - setVoiceEnabled(voiceEnabled()); + setVoiceEnabled(LLVoiceClient::getInstance()->voiceEnabled()); setEarLocation(gSavedSettings.getS32("VoiceEarLocation")); std::string inputDevice = gSavedSettings.getString("VoiceInputAudioDevice"); @@ -464,7 +469,6 @@ void LLVivoxVoiceClient::updateSettings() setRenderDevice(outputDevice); F32 mic_level = gSavedSettings.getF32("AudioLevelMic"); setMicGain(mic_level); - setLipSyncEnabled(gSavedSettings.getBOOL("LipSyncEnabled")); } ///////////////////////////// @@ -480,7 +484,7 @@ bool LLVivoxVoiceClient::writeString(const std::string &str) apr_status_t err; apr_size_t size = (apr_size_t)str.size(); apr_size_t written = size; - + //MARK: Turn this on to log outgoing XML // LL_DEBUGS("Voice") << "sending: " << str << LL_ENDL; @@ -489,7 +493,7 @@ bool LLVivoxVoiceClient::writeString(const std::string &str) mSocket->getSocket(), (const char*)str.data(), &written); - + if(err == 0 && written == size) { // Success. @@ -512,7 +516,7 @@ bool LLVivoxVoiceClient::writeString(const std::string &str) daemonDied(); } } - + return result; } @@ -523,7 +527,7 @@ void LLVivoxVoiceClient::connectorCreate() { std::ostringstream stream; std::string logdir = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ""); - + // Transition to stateConnectorStarted when the connector handle comes back. std::string vivoxLogLevel = gSavedSettings.getString("VivoxDebugLevel"); if ( vivoxLogLevel.empty() ) @@ -531,8 +535,8 @@ void LLVivoxVoiceClient::connectorCreate() vivoxLogLevel = "0"; } LL_DEBUGS("Voice") << "creating connector with log level " << vivoxLogLevel << LL_ENDL; - - stream + + stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.Create.1\">" << "<ClientName>V2 SDK</ClientName>" << "<AccountManagementServer>" << mVoiceAccountServerURI << "</AccountManagementServer>" @@ -548,7 +552,7 @@ void LLVivoxVoiceClient::connectorCreate() //<< "<Application></Application>" //Name can cause problems per vivox. << "<MaxCalls>12</MaxCalls>" << "</Request>\n\n\n"; - + writeString(stream.str()); } @@ -562,10 +566,10 @@ void LLVivoxVoiceClient::connectorShutdown() << "<ConnectorHandle>" << LLVivoxSecurity::getInstance()->connectorHandle() << "</ConnectorHandle>" << "</Request>" << "\n\n\n"; - + mShutdownComplete = false; mConnectorEstablished = false; - + writeString(stream.str()); } else @@ -597,7 +601,7 @@ void LLVivoxVoiceClient::setLoginInfo( { // Already logged in. LL_WARNS("Voice") << "Called while already logged in." << LL_ENDL; - + // Don't process another login. return; } @@ -612,14 +616,14 @@ void LLVivoxVoiceClient::setLoginInfo( } std::string debugSIPURIHostName = gSavedSettings.getString("VivoxDebugSIPURIHostName"); - + if( !debugSIPURIHostName.empty() ) { LL_INFOS("Voice") << "Overriding account server based on VivoxDebugSIPURIHostName: " << debugSIPURIHostName << LL_ENDL; mVoiceSIPURIHostName = debugSIPURIHostName; } - + if( mVoiceSIPURIHostName.empty() ) { // we have an empty account server name @@ -639,7 +643,7 @@ void LLVivoxVoiceClient::setLoginInfo( << mVoiceSIPURIHostName << LL_ENDL; } - + std::string debugAccountServerURI = gSavedSettings.getString("VivoxDebugVoiceAccountServerURI"); if( !debugAccountServerURI.empty() ) @@ -648,11 +652,11 @@ void LLVivoxVoiceClient::setLoginInfo( << debugAccountServerURI << LL_ENDL; mVoiceAccountServerURI = debugAccountServerURI; } - + if( mVoiceAccountServerURI.empty() ) { // If the account server URI isn't specified, construct it from the SIP URI hostname - mVoiceAccountServerURI = "https://www." + mVoiceSIPURIHostName + "/api2/"; + mVoiceAccountServerURI = "https://www." + mVoiceSIPURIHostName + "/api2/"; LL_INFOS("Voice") << "Inferring account server based on SIP URI Host name: " << mVoiceAccountServerURI << LL_ENDL; } @@ -663,11 +667,11 @@ void LLVivoxVoiceClient::idle(void* user_data) } //========================================================================= -// the following are methods to support the coroutine implementation of the -// voice connection and processing. They should only be called in the context +// the following are methods to support the coroutine implementation of the +// voice connection and processing. They should only be called in the context // of a coroutine. -// -// +// +// typedef enum e_voice_control_coro_state { @@ -816,7 +820,7 @@ void LLVivoxVoiceClient::voiceControlStateMachine(S32 &coro_state) case VOICE_STATE_SESSION_ESTABLISHED: { - // enable/disable the automatic VAD and explicitly set the initial values of + // enable/disable the automatic VAD and explicitly set the initial values of // the VAD variables ourselves when it is off - see SL-15072 for more details // note: we set the other parameters too even if the auto VAD is on which is ok unsigned int vad_auto = gSavedSettings.getU32("VivoxVadAuto"); @@ -914,7 +918,7 @@ bool LLVivoxVoiceClient::callbackEndDaemon(const LLSD& data) bool LLVivoxVoiceClient::startAndLaunchDaemon() { //--------------------------------------------------------------------- - if (!voiceEnabled()) + if (!LLVoiceClient::getInstance()->voiceEnabled()) { // Voice is locked out, we must not launch the vivox daemon. LL_WARNS("Voice") << "voice disabled; not starting daemon" << LL_ENDL; @@ -991,7 +995,7 @@ bool LLVivoxVoiceClient::startAndLaunchDaemon() { LLFile::rename(new_log, old_log); } - + std::string shutdown_timeout = gSavedSettings.getString("VivoxShutdownTimeout"); if (!shutdown_timeout.empty()) { @@ -1072,7 +1076,7 @@ bool LLVivoxVoiceClient::startAndLaunchDaemon() llcoro::suspendUntilTimeout(DAEMON_CONNECT_THROTTLE_SECONDS); } } - + //--------------------------------------------------------------------- if (sShuttingDown && !sConnected) { @@ -1090,7 +1094,7 @@ bool LLVivoxVoiceClient::startAndLaunchDaemon() { return false; } - + // MBW -- Note to self: pumps and pipes examples in // indra/test/io.cpp // indra/test/llpipeutil.{cpp|h} @@ -1128,7 +1132,7 @@ bool LLVivoxVoiceClient::provisionVoiceAccount() // *TODO* Pump a message for wake up. llcoro::suspend(); } - + if (sShuttingDown) { return false; @@ -1180,7 +1184,7 @@ bool LLVivoxVoiceClient::provisionVoiceAccount() else { provisioned = true; - } + } } while (!provisioned && ++retryCount <= PROVISION_RETRY_MAX && !sShuttingDown); if (sShuttingDown && !provisioned) @@ -1194,7 +1198,7 @@ bool LLVivoxVoiceClient::provisionVoiceAccount() LL_WARNS("Voice") << "Could not access voice provision cap after " << retryCount << " attempts." << LL_ENDL; return false; } - + LL_WARNS("Voice") << "Voice Provision Result." << result << LL_ENDL; std::string voiceSipUriHostname; std::string voiceAccountServerUri; std::string voiceUserName = result["username"].asString(); @@ -1204,7 +1208,7 @@ bool LLVivoxVoiceClient::provisionVoiceAccount() { voiceSipUriHostname = result["voice_sip_uri_hostname"].asString(); } - + // this key is actually misnamed -- it will be an entire URI, not just a hostname. if (result.has("voice_account_server_name")) { @@ -1235,7 +1239,7 @@ bool LLVivoxVoiceClient::establishVoiceConnection() { return false; } - + LLSD result; bool connected(false); bool giving_up(false); @@ -1354,7 +1358,7 @@ bool LLVivoxVoiceClient::loginToVivox() bool account_login(false); bool send_login(true); - do + do { mIsLoggingIn = true; if (send_login) @@ -1362,7 +1366,7 @@ bool LLVivoxVoiceClient::loginToVivox() loginSendMessage(); send_login = false; } - + LLSD result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, LOGIN_ATTEMPT_TIMEOUT, timeoutResult); if (sShuttingDown) @@ -1402,7 +1406,7 @@ bool LLVivoxVoiceClient::loginToVivox() // tell the user there is a problem LL_WARNS("Voice") << "login " << loginresp << " will retry login in " << timeout << " seconds." << LL_ENDL; - + if (!sShuttingDown) { // Todo: this is way to long, viewer can get stuck waiting during shutdown @@ -1507,7 +1511,7 @@ bool LLVivoxVoiceClient::retrieveVoiceFonts() mIsWaitingForFonts = true; LLSD result; - do + do { result = llcoro::suspendUntilEventOn(mVivoxPump); @@ -1542,7 +1546,7 @@ bool LLVivoxVoiceClient::requestParcelVoiceInfo() LL_DEBUGS("Voice") << "ParcelVoiceInfoRequest capability not available in this region" << LL_ENDL; return false; } - + // update the parcel checkParcelChanged(true); @@ -1590,48 +1594,7 @@ bool LLVivoxVoiceClient::requestParcelVoiceInfo() return false; } - std::string uri; - std::string credentials; - - if (result.has("voice_credentials")) - { - 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; - } - } - } - } - else + if (!result.has("voice_credentials")) { if (LLViewerParcelMgr::getInstance()->allowAgentVoice()) { @@ -1643,9 +1606,9 @@ bool LLVivoxVoiceClient::requestParcelVoiceInfo() } } - // set the spatial channel. If no voice credentials or uri are + // set the spatial channel. If no voice credentials or uri are // available, then we simply drop out of voice spatially. - return !setSpatialChannel(uri, credentials); + return setSpatialChannel(result["voice_credentials"]); } bool LLVivoxVoiceClient::addAndJoinSession(const sessionStatePtr_t &nextSession) @@ -1698,7 +1661,7 @@ bool LLVivoxVoiceClient::addAndJoinSession(const sessionStatePtr_t &nextSession) notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED); LL_WARNS() << "There seems to be problem with connection to voice server. Disabling voice chat abilities." << LL_ENDL; } - + // 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) @@ -1740,8 +1703,8 @@ bool LLVivoxVoiceClient::addAndJoinSession(const sessionStatePtr_t &nextSession) 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. + // 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. mVivoxPump.discard(); @@ -1784,25 +1747,25 @@ bool LLVivoxVoiceClient::addAndJoinSession(const sessionStatePtr_t &nextSession) } else if ((message == "failed") || (message == "removed") || (message == "timeout")) { // we will get a removed message if a voice call is declined. - - if (message == "failed") + + if (message == "failed") { int reason = result["reason"].asInteger(); LL_WARNS("Voice") << "Add and join failed for reason " << reason << LL_ENDL; - + if ( (reason == ERROR_VIVOX_NOT_LOGGED_IN) || (reason == ERROR_VIVOX_OBJECT_NOT_FOUND)) { LL_DEBUGS("Voice") << "Requesting reprovision and login." << LL_ENDL; requestRelog(); - } + } } else { LL_WARNS("Voice") << "session '" << message << "' " << LL_ENDL; } - + notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); mIsJoiningSession = false; return false; @@ -1821,8 +1784,8 @@ bool LLVivoxVoiceClient::addAndJoinSession(const sessionStatePtr_t &nextSession) // Events that need to happen when a session is joined could go here. // send an initial positional information immediately upon joining. - // - // do an initial update for position and the camera position, then send a + // + // do an initial update for position and the camera position, then send a // positional update. updatePosition(); enforceTether(); @@ -1929,7 +1892,7 @@ bool LLVivoxVoiceClient::terminateAudioSession(bool wait) 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 + // 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; @@ -1964,7 +1927,7 @@ bool LLVivoxVoiceClient::waitForChannel() EVoiceWaitForChannelState state = VOICE_CHANNEL_STATE_LOGIN; - do + do { if (sShuttingDown) { @@ -2008,7 +1971,6 @@ bool LLVivoxVoiceClient::waitForChannel() break; case VOICE_CHANNEL_STATE_START_CHANNEL_PROCESSING: - mIsProcessingChannels = true; llcoro::suspend(); state = VOICE_CHANNEL_STATE_PROCESS_CHANNEL; break; @@ -2022,7 +1984,7 @@ bool LLVivoxVoiceClient::waitForChannel() { recordingAndPlaybackMode(); } - else if (checkParcelChanged() || (mNextAudioSession == NULL)) + else if (mProcessChannels && (mNextAudioSession == NULL) && checkParcelChanged()) { // the parcel is changed, or we have no pending audio sessions, // so try to request the parcel voice info @@ -2037,15 +1999,23 @@ bool LLVivoxVoiceClient::waitForChannel() } else if (mNextAudioSession) { + if (!mNextAudioSession->mIsP2P && !mProcessChannels) + { + llcoro::suspend(); + break; + } sessionStatePtr_t joinSession = mNextAudioSession; mNextAudioSession.reset(); + mIsProcessingChannels = true; if (!runSession(joinSession)) //suspends { + mIsProcessingChannels = false; LL_DEBUGS("Voice") << "runSession returned false; leaving inner loop" << LL_ENDL; break; } else { + mIsProcessingChannels = false; LL_DEBUGS("Voice") << "runSession returned true to inner loop" << " RelogRequested=" << mRelogRequested @@ -2147,10 +2117,10 @@ bool LLVivoxVoiceClient::runSession(const sessionStatePtr_t &session) LL_DEBUGS("Voice") << "runSession terminate requested " << LL_ENDL; terminateAudioSession(true); } - // if a relog has been requested then addAndJoineSession + // 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. + // make a call and the other party rejected it. return !mRelogRequested; } @@ -2161,6 +2131,7 @@ bool LLVivoxVoiceClient::runSession(const sessionStatePtr_t &session) mIsInChannel = true; mMuteMicDirty = true; + mSessionTerminateRequested = false; while (!sShuttingDown && mVoiceEnabled @@ -2185,7 +2156,7 @@ bool LLVivoxVoiceClient::runSession(const sessionStatePtr_t &session) mAudioSession->mParticipantsChanged = false; notifyParticipantObservers(); } - + if (!inSpatialChannel()) { // When in a non-spatial channel, never send positional updates. @@ -2197,9 +2168,9 @@ bool LLVivoxVoiceClient::runSession(const sessionStatePtr_t &session) if (checkParcelChanged()) { - // *RIDER: I think I can just return here if the parcel has changed + // *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 @@ -2242,7 +2213,7 @@ bool LLVivoxVoiceClient::runSession(const sessionStatePtr_t &session) LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; } if (result.has("session")) - { + { if (result.has("handle")) { if (!mAudioSession) @@ -2347,7 +2318,7 @@ void LLVivoxVoiceClient::recordingAndPlaybackMode() int LLVivoxVoiceClient::voiceRecordBuffer() { - LLSD timeoutResult(LLSDMap("recplay", "stop")); + LLSD timeoutResult(LLSDMap("recplay", "stop")); LL_INFOS("Voice") << "Recording voice buffer" << LL_ENDL; @@ -2418,95 +2389,11 @@ int LLVivoxVoiceClient::voicePlaybackBuffer() bool LLVivoxVoiceClient::performMicTuning() { LL_INFOS("Voice") << "Entering voice tuning mode." << LL_ENDL; - mIsInTuningMode = true; - llcoro::suspend(); while (mTuningMode && !sShuttingDown) { - - if (mCaptureDeviceDirty || mRenderDeviceDirty) - { - // These can't be changed while in tuning mode. Set them before starting. - std::ostringstream stream; - - buildSetCaptureDevice(stream); - buildSetRenderDevice(stream); - - if (!stream.str().empty()) - { - writeString(stream.str()); - } - - llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); - } - - // loop mic back to render device. - //setMuteMic(0); // make sure the mic is not muted - std::ostringstream stream; - - stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.MuteLocalMic.1\">" - << "<ConnectorHandle>" << LLVivoxSecurity::getInstance()->connectorHandle() << "</ConnectorHandle>" - << "<Value>false</Value>" - << "</Request>\n\n\n"; - - // Dirty the mute mic state so that it will get reset when we finishing previewing - mMuteMicDirty = true; - mTuningSpeakerVolumeDirty = true; - - writeString(stream.str()); - tuningCaptureStartSendMessage(1); // 1-loop, zero, don't loop - - //--------------------------------------------------------------------- - if (!sShuttingDown) - { - llcoro::suspend(); - } - - while (mTuningMode && !mCaptureDeviceDirty && !mRenderDeviceDirty && !sShuttingDown) - { - // process mic/speaker volume changes - if (mTuningMicVolumeDirty || mTuningSpeakerVolumeDirty) - { - std::ostringstream stream; - - if (mTuningMicVolumeDirty) - { - LL_INFOS("Voice") << "setting tuning mic level to " << mTuningMicVolume << LL_ENDL; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetMicLevel.1\">" - << "<Level>" << mTuningMicVolume << "</Level>" - << "</Request>\n\n\n"; - } - - if (mTuningSpeakerVolumeDirty) - { - LL_INFOS("Voice") << "setting tuning speaker level to " << mTuningSpeakerVolume << LL_ENDL; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetSpeakerLevel.1\">" - << "<Level>" << mTuningSpeakerVolume << "</Level>" - << "</Request>\n\n\n"; - } - - mTuningMicVolumeDirty = false; - mTuningSpeakerVolumeDirty = false; - - if (!stream.str().empty()) - { - writeString(stream.str()); - } - } - llcoro::suspend(); - } - - //--------------------------------------------------------------------- - - // transition out of mic tuning - tuningCaptureStopSendMessage(); - if ((mCaptureDeviceDirty || mRenderDeviceDirty) && !sShuttingDown) - { - llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); - } + llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); } mIsInTuningMode = false; @@ -2554,7 +2441,7 @@ void LLVivoxVoiceClient::logout() // Ensure that we'll re-request provisioning before logging in again mAccountPassword.clear(); mVoiceAccountServerURI.clear(); - + logoutSendMessage(); } @@ -2579,7 +2466,7 @@ void LLVivoxVoiceClient::logoutSendMessage() void LLVivoxVoiceClient::sessionGroupCreateSendMessage() { if(mAccountLoggedIn) - { + { std::ostringstream stream; LL_DEBUGS("Voice") << "creating session group" << LL_ENDL; @@ -2632,6 +2519,7 @@ void LLVivoxVoiceClient::sessionCreateSendMessage(const sessionStatePtr_t &sessi << "<VoiceFontID>" << font_index << "</VoiceFontID>" << "<Name>" << mChannelName << "</Name>" << "</Request>\n\n\n"; + LL_WARNS("Voice") << "Session.Create: " << stream.str() << LL_ENDL; writeString(stream.str()); } @@ -2647,7 +2535,7 @@ void LLVivoxVoiceClient::sessionGroupAddSessionSendMessage(const sessionStatePtr { session->mMediaConnectInProgress = true; } - + std::string password; if(!session->mHash.empty()) { @@ -2672,7 +2560,7 @@ void LLVivoxVoiceClient::sessionGroupAddSessionSendMessage(const sessionStatePtr << "<PasswordHashAlgorithm>SHA1UserName</PasswordHashAlgorithm>" << "</Request>\n\n\n" ; - + writeString(stream.str()); } @@ -2684,7 +2572,7 @@ void LLVivoxVoiceClient::sessionMediaConnectSendMessage(const sessionStatePtr_t << LL_ENDL; session->mMediaConnectInProgress = true; - + std::ostringstream stream; stream @@ -2701,7 +2589,7 @@ void LLVivoxVoiceClient::sessionMediaConnectSendMessage(const sessionStatePtr_t void LLVivoxVoiceClient::sessionTextConnectSendMessage(const sessionStatePtr_t &session) { LL_DEBUGS("Voice") << "connecting text to session handle: " << session->mHandle << LL_ENDL; - + std::ostringstream stream; stream @@ -2742,7 +2630,7 @@ void LLVivoxVoiceClient::leaveAudioSession() time_t now = time(NULL); const size_t BUF_SIZE = 64; char time_str[BUF_SIZE]; /* Flawfinder: ignore */ - + strftime(time_str, BUF_SIZE, "%Y-%m-%dT%H:%M:%SZ", gmtime(&now)); savepath += time_str; } @@ -2753,7 +2641,7 @@ void LLVivoxVoiceClient::leaveAudioSession() } else { - LL_WARNS("Voice") << "called with no session handle" << LL_ENDL; + LL_WARNS("Voice") << "called with no session handle" << LL_ENDL; } } else @@ -2770,12 +2658,12 @@ void LLVivoxVoiceClient::sessionTerminateSendMessage(const sessionStatePtr_t &se sessionGroupTerminateSendMessage(session); return; /* - LL_DEBUGS("Voice") << "Sending Session.Terminate with handle " << session->mHandle << LL_ENDL; + LL_DEBUGS("Voice") << "Sending Session.Terminate with handle " << session->mHandle << LL_ENDL; stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Terminate.1\">" << "<SessionHandle>" << session->mHandle << "</SessionHandle>" << "</Request>\n\n\n"; - + writeString(stream.str()); */ } @@ -2783,13 +2671,13 @@ void LLVivoxVoiceClient::sessionTerminateSendMessage(const sessionStatePtr_t &se void LLVivoxVoiceClient::sessionGroupTerminateSendMessage(const sessionStatePtr_t &session) { std::ostringstream stream; - - LL_DEBUGS("Voice") << "Sending SessionGroup.Terminate with handle " << session->mGroupHandle << LL_ENDL; + + LL_DEBUGS("Voice") << "Sending SessionGroup.Terminate with handle " << session->mGroupHandle << LL_ENDL; stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.Terminate.1\">" << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>" << "</Request>\n\n\n"; - + writeString(stream.str()); } @@ -2799,17 +2687,17 @@ void LLVivoxVoiceClient::sessionMediaDisconnectSendMessage(const sessionStatePtr sessionGroupTerminateSendMessage(session); return; /* - LL_DEBUGS("Voice") << "Sending Session.MediaDisconnect with handle " << session->mHandle << LL_ENDL; + LL_DEBUGS("Voice") << "Sending Session.MediaDisconnect with handle " << session->mHandle << LL_ENDL; stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.MediaDisconnect.1\">" << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>" << "<SessionHandle>" << session->mHandle << "</SessionHandle>" << "<Media>Audio</Media>" << "</Request>\n\n\n"; - + writeString(stream.str()); */ - + } @@ -2819,7 +2707,7 @@ void LLVivoxVoiceClient::getCaptureDevicesSendMessage() stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.GetCaptureDevices.1\">" << "</Request>\n\n\n"; - + writeString(stream.str()); } @@ -2829,7 +2717,7 @@ void LLVivoxVoiceClient::getRenderDevicesSendMessage() stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.GetRenderDevices.1\">" << "</Request>\n\n\n"; - + writeString(stream.str()); } @@ -2857,7 +2745,7 @@ void LLVivoxVoiceClient::setCaptureDevice(const std::string& name) if(!mCaptureDevice.empty()) { mCaptureDevice.clear(); - mCaptureDeviceDirty = true; + mCaptureDeviceDirty = true; } } else @@ -2865,7 +2753,7 @@ void LLVivoxVoiceClient::setCaptureDevice(const std::string& name) if(mCaptureDevice != name) { mCaptureDevice = name; - mCaptureDeviceDirty = true; + mCaptureDeviceDirty = true; } } } @@ -2875,7 +2763,7 @@ void LLVivoxVoiceClient::setDevicesListUpdated(bool state) } void LLVivoxVoiceClient::clearRenderDevices() -{ +{ LL_DEBUGS("Voice") << "called" << LL_ENDL; mRenderDevices.clear(); } @@ -2898,7 +2786,7 @@ void LLVivoxVoiceClient::setRenderDevice(const std::string& name) if(!mRenderDevice.empty()) { mRenderDevice.clear(); - mRenderDeviceDirty = true; + mRenderDeviceDirty = true; } } else @@ -2906,10 +2794,10 @@ void LLVivoxVoiceClient::setRenderDevice(const std::string& name) if(mRenderDevice != name) { mRenderDevice = name; - mRenderDeviceDirty = true; + mRenderDeviceDirty = true; } } - + } void LLVivoxVoiceClient::tuningStart() @@ -2931,6 +2819,9 @@ void LLVivoxVoiceClient::tuningStart() void LLVivoxVoiceClient::tuningStop() { mTuningMode = false; + // force a renegotiation. + mCurrentParcelLocalID = 0; + mCurrentRegionName = ""; } bool LLVivoxVoiceClient::inTuningMode() @@ -2939,7 +2830,7 @@ bool LLVivoxVoiceClient::inTuningMode() } void LLVivoxVoiceClient::tuningRenderStartSendMessage(const std::string& name, bool loop) -{ +{ mTuningAudioFile = name; std::ostringstream stream; stream @@ -2947,7 +2838,7 @@ void LLVivoxVoiceClient::tuningRenderStartSendMessage(const std::string& name, b << "<SoundFilePath>" << mTuningAudioFile << "</SoundFilePath>" << "<Loop>" << (loop?"1":"0") << "</Loop>" << "</Request>\n\n\n"; - + writeString(stream.str()); } @@ -2958,33 +2849,33 @@ void LLVivoxVoiceClient::tuningRenderStopSendMessage() << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.RenderAudioStop.1\">" << "<SoundFilePath>" << mTuningAudioFile << "</SoundFilePath>" << "</Request>\n\n\n"; - + writeString(stream.str()); } void LLVivoxVoiceClient::tuningCaptureStartSendMessage(int loop) { LL_DEBUGS("Voice") << "sending CaptureAudioStart" << LL_ENDL; - + std::ostringstream stream; stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.CaptureAudioStart.1\">" << "<Duration>-1</Duration>" << "<LoopToRenderDevice>" << loop << "</LoopToRenderDevice>" << "</Request>\n\n\n"; - + writeString(stream.str()); } void LLVivoxVoiceClient::tuningCaptureStopSendMessage() { LL_DEBUGS("Voice") << "sending CaptureAudioStop" << LL_ENDL; - + std::ostringstream stream; stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.CaptureAudioStop.1\">" << "</Request>\n\n\n"; - + writeString(stream.str()); mTuningEnergy = 0.0f; @@ -3003,7 +2894,7 @@ void LLVivoxVoiceClient::tuningSetMicVolume(float volume) void LLVivoxVoiceClient::tuningSetSpeakerVolume(float volume) { - int scaled_volume = scale_speaker_volume(volume); + int scaled_volume = scale_speaker_volume(volume); if(scaled_volume != mTuningSpeakerVolume) { @@ -3011,7 +2902,7 @@ void LLVivoxVoiceClient::tuningSetSpeakerVolume(float volume) mTuningSpeakerVolumeDirty = true; } } - + float LLVivoxVoiceClient::tuningGetEnergy(void) { return mTuningEnergy; @@ -3020,13 +2911,13 @@ float LLVivoxVoiceClient::tuningGetEnergy(void) bool LLVivoxVoiceClient::deviceSettingsAvailable() { bool result = true; - + if(!sConnected) result = false; - + if(mRenderDevices.empty()) result = false; - + return result; } bool LLVivoxVoiceClient::deviceSettingsUpdated() @@ -3037,7 +2928,7 @@ bool LLVivoxVoiceClient::deviceSettingsUpdated() // a hot swap event or a polling of the audio devices has been parsed since the last redraw of the input and output device panel. mDevicesListUpdated = false; // toggle the setting } - return updated; + return updated; } void LLVivoxVoiceClient::refreshDeviceLists(bool clearCurrentList) @@ -3070,9 +2961,9 @@ void LLVivoxVoiceClient::giveUp() static void oldSDKTransform (LLVector3 &left, LLVector3 &up, LLVector3 &at, LLVector3d &pos, LLVector3 &vel) { F32 nat[3], nup[3], nl[3]; // the new at, up, left vectors and the new position and velocity -// F32 nvel[3]; +// F32 nvel[3]; F64 npos[3]; - + // The original XML command was sent like this: /* << "<Position>" @@ -3130,7 +3021,7 @@ static void oldSDKTransform (LLVector3 &left, LLVector3 &up, LLVector3 &at, LLVe left.mV[i] = nl[i]; pos.mdV[i] = npos[i]; } - + // This was the original transform done in the SDK nat[0] = at.mV[2]; nat[1] = 0; // y component of at vector is always 0, this was up[2] @@ -3184,7 +3075,7 @@ static void oldSDKTransform (LLVector3 &left, LLVector3 &up, LLVector3 &at, LLVe left.mV[i] = nl[i]; pos.mdV[i] = npos[i]; } - + #endif } @@ -3194,7 +3085,7 @@ void LLVivoxVoiceClient::setHidden(bool hidden) if (mHidden && inSpatialChannel()) { - // get out of the channel entirely + // get out of the channel entirely leaveAudioSession(); } else @@ -3204,20 +3095,20 @@ void LLVivoxVoiceClient::setHidden(bool hidden) } void LLVivoxVoiceClient::sendPositionAndVolumeUpdate(void) -{ +{ std::ostringstream stream; - + if (mSpatialCoordsDirty && inSpatialChannel()) { LLVector3 l, u, a, vel; LLVector3d pos; mSpatialCoordsDirty = false; - + // Always send both speaker and listener positions together. - stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Set3DPosition.1\">" + stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Set3DPosition.1\">" << "<SessionHandle>" << getAudioSessionHandle() << "</SessionHandle>"; - + stream << "<SpeakerPosition>"; LLMatrix3 avatarRot = mAvatarRot.getMatrix3(); @@ -3233,7 +3124,7 @@ void LLVivoxVoiceClient::sendPositionAndVolumeUpdate(void) // SLIM SDK: the old SDK was doing a transform on the passed coordinates that the new one doesn't do anymore. // The old transform is replicated by this function. oldSDKTransform(l, u, a, pos, vel); - + if (mHidden) { for (int i=0;i<3;++i) @@ -3241,7 +3132,7 @@ void LLVivoxVoiceClient::sendPositionAndVolumeUpdate(void) pos.mdV[i] = VX_NULL_POSITION; } } - + stream << "<Position>" << "<X>" << pos.mdV[VX] << "</X>" @@ -3269,7 +3160,7 @@ void LLVivoxVoiceClient::sendPositionAndVolumeUpdate(void) << "<Z>" << l.mV [VZ] << "</Z>" << "</LeftOrientation>" ; - + stream << "</SpeakerPosition>"; stream << "<ListenerPosition>"; @@ -3277,7 +3168,7 @@ void LLVivoxVoiceClient::sendPositionAndVolumeUpdate(void) LLVector3d earPosition; LLVector3 earVelocity; LLMatrix3 earRot; - + switch(mEarLocation) { case earLocCamera: @@ -3286,13 +3177,13 @@ void LLVivoxVoiceClient::sendPositionAndVolumeUpdate(void) earVelocity = mCameraVelocity; earRot = mCameraRot; break; - + case earLocAvatar: earPosition = mAvatarPosition; earVelocity = mAvatarVelocity; earRot = avatarRot; break; - + case earLocMixed: earPosition = mAvatarPosition; earVelocity = mAvatarVelocity; @@ -3307,9 +3198,9 @@ void LLVivoxVoiceClient::sendPositionAndVolumeUpdate(void) pos = earPosition; vel = earVelocity; - + oldSDKTransform(l, u, a, pos, vel); - + if (mHidden) { for (int i=0;i<3;++i) @@ -3317,7 +3208,7 @@ void LLVivoxVoiceClient::sendPositionAndVolumeUpdate(void) pos.mdV[i] = VX_NULL_POSITION; } } - + stream << "<Position>" << "<X>" << pos.mdV[VX] << "</X>" @@ -3350,19 +3241,19 @@ void LLVivoxVoiceClient::sendPositionAndVolumeUpdate(void) stream << "<ReqDispositionType>1</ReqDispositionType>"; //do not generate responses for update requests stream << "</Request>\n\n\n"; - } - + } + 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 @@ -3371,7 +3262,7 @@ void LLVivoxVoiceClient::sendPositionAndVolumeUpdate(void) // scale from the range 0.0-1.0 to vivox volume in the range 0-100 S32 volume = ll_round(p->mVolume / VOLUME_SCALE_VIVOX); bool mute = p->mOnMuteList; - + if(mute) { // SetParticipantMuteForMe doesn't work in p2p sessions. @@ -3382,16 +3273,16 @@ void LLVivoxVoiceClient::sendPositionAndVolumeUpdate(void) // 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; - + // SLIM SDK: Send both volume and mute commands. - + // Send a "volume for me" command for the user. stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.SetParticipantVolumeForMe.1\">" << "<SessionHandle>" << getAudioSessionHandle() << "</SessionHandle>" @@ -3411,7 +3302,7 @@ void LLVivoxVoiceClient::sendPositionAndVolumeUpdate(void) << "</Request>\n\n\n"; } } - + p->mVolumeDirty = false; } } @@ -3431,13 +3322,13 @@ void LLVivoxVoiceClient::buildSetCaptureDevice(std::ostringstream &stream) if(mCaptureDeviceDirty) { LL_DEBUGS("Voice") << "Setting input device = \"" << mCaptureDevice << "\"" << LL_ENDL; - - stream + + stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetCaptureDevice.1\">" << "<CaptureDeviceSpecifier>" << mCaptureDevice << "</CaptureDeviceSpecifier>" << "</Request>" << "\n\n\n"; - + mCaptureDeviceDirty = false; } } @@ -3529,7 +3420,7 @@ void LLVivoxVoiceClient::sendLocalAudioUpdates() * Because of the recurring voice cutout issues (SL-15072) we are going to try * to disable the automatic VAD (Voice Activity Detection) and set the associated * parameters directly. We will expose them via Debug Settings and that should - * let us iterate on a collection of values that work for us. Hopefully! + * let us iterate on a collection of values that work for us. Hopefully! * * From the VIVOX Docs: * @@ -3539,16 +3430,16 @@ void LLVivoxVoiceClient::sendLocalAudioUpdates() * for the VAD to switch back to silence from speech mode after the last speech * frame has been detected. * - * VadNoiseFloor: A dimensionless value between 0 and + * VadNoiseFloor: A dimensionless value between 0 and * 20000 (default 576) that controls the maximum level at which the noise floor * may be set at by the VAD's noise tracking. Too low of a value will make noise - * tracking ineffective (A value of 0 disables noise tracking and the VAD then - * relies purely on the sensitivity property). Too high of a value will make + * tracking ineffective (A value of 0 disables noise tracking and the VAD then + * relies purely on the sensitivity property). Too high of a value will make * long speech classifiable as noise. * - * VadSensitivity: A dimensionless value between 0 and + * VadSensitivity: A dimensionless value between 0 and * 100, indicating the 'sensitivity of the VAD'. Increasing this value corresponds - * to decreasing the sensitivity of the VAD (i.e. '0' is most sensitive, + * to decreasing the sensitivity of the VAD (i.e. '0' is most sensitive, * while 100 is 'least sensitive') */ void LLVivoxVoiceClient::setupVADParams(unsigned int vad_auto, @@ -3596,7 +3487,7 @@ void LLVivoxVoiceClient::onVADSettingsChange() // Response/Event handlers void LLVivoxVoiceClient::connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle, std::string &versionID) -{ +{ LLSD result = LLSD::emptyMap(); if(statusCode == 0) @@ -3630,7 +3521,7 @@ void LLVivoxVoiceClient::connectorCreateResponse(int statusCode, std::string &st // this is usually fatal, but a long timeout might work result["connector"] = LLSD::Boolean(false); result["retry"] = LLSD::Real(CONNECT_ATTEMPT_TIMEOUT); - + LL_WARNS("Voice") << "Voice connection failed" << LL_ENDL; } else if (statusCode == 10006) // name resolution failure - a shorter retry may work @@ -3638,7 +3529,7 @@ void LLVivoxVoiceClient::connectorCreateResponse(int statusCode, std::string &st // some networks have slower DNS, but a short timeout might let it catch up result["connector"] = LLSD::Boolean(false); result["retry"] = LLSD::Real(CONNECT_DNS_TIMEOUT); - + LL_WARNS("Voice") << "Voice connection DNS lookup failed" << LL_ENDL; } else // unknown failure - give up @@ -3652,13 +3543,13 @@ void LLVivoxVoiceClient::connectorCreateResponse(int statusCode, std::string &st } void LLVivoxVoiceClient::loginResponse(int statusCode, std::string &statusString, std::string &accountHandle, int numberOfAliases) -{ +{ LLSD result = LLSD::emptyMap(); LL_DEBUGS("Voice") << "Account.Login response (" << statusCode << "): " << statusString << LL_ENDL; - + // Status code of 20200 means "bad password". We may want to special-case that at some point. - + if ( statusCode == HTTP_UNAUTHORIZED ) { // Login failure which is probably caused by the delay after a user's password being updated. @@ -3683,20 +3574,20 @@ void LLVivoxVoiceClient::loginResponse(int statusCode, std::string &statusString } void LLVivoxVoiceClient::sessionCreateResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle) -{ +{ sessionStatePtr_t session(findSessionBeingCreatedByURI(requestId)); - + if(session) { session->mCreateInProgress = false; } - + if(statusCode != 0) { LL_WARNS("Voice") << "Session.Create response failure (" << statusCode << "): " << statusString << LL_ENDL; if(session) { - session->mErrorStatusCode = statusCode; + session->mErrorStatusCode = statusCode; session->mErrorStatusString = statusString; if(session == mAudioSession) { @@ -3727,20 +3618,20 @@ void LLVivoxVoiceClient::sessionCreateResponse(std::string &requestId, int statu } void LLVivoxVoiceClient::sessionGroupAddSessionResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle) -{ +{ sessionStatePtr_t session(findSessionBeingCreatedByURI(requestId)); - + if(session) { session->mCreateInProgress = false; } - + if(statusCode != 0) { LL_WARNS("Voice") << "SessionGroup.AddSession response failure (" << statusCode << "): " << statusString << LL_ENDL; if(session) { - session->mErrorStatusCode = statusCode; + session->mErrorStatusCode = statusCode; session->mErrorStatusString = statusString; if(session == mAudioSession) { @@ -3801,7 +3692,7 @@ void LLVivoxVoiceClient::sessionConnectResponse(std::string &requestId, int stat } void LLVivoxVoiceClient::logoutResponse(int statusCode, std::string &statusString) -{ +{ if(statusCode != 0) { LL_WARNS("Voice") << "Account.Logout response failure: " << statusString << LL_ENDL; @@ -3819,21 +3710,21 @@ void LLVivoxVoiceClient::connectorShutdownResponse(int statusCode, std::string & LL_WARNS("Voice") << "Connector.InitiateShutdown response failure: " << statusString << LL_ENDL; // Should this ever fail? do we care if it does? } - + sConnected = false; mShutdownComplete = true; - + LLSD vivoxevent(LLSDMap("connector", LLSD::Boolean(false))); mVivoxPump.post(vivoxevent); } void LLVivoxVoiceClient::sessionAddedEvent( - std::string &uriString, - std::string &alias, - std::string &sessionHandle, - std::string &sessionGroupHandle, - bool isChannel, + std::string &uriString, + std::string &alias, + std::string &sessionHandle, + std::string &sessionGroupHandle, + bool isChannel, bool incoming, std::string &nameString, std::string &applicationString) @@ -3841,7 +3732,7 @@ void LLVivoxVoiceClient::sessionAddedEvent( sessionStatePtr_t session; LL_INFOS("Voice") << "session " << uriString << ", alias " << alias << ", name " << nameString << " handle " << sessionHandle << LL_ENDL; - + session = addSession(uriString, sessionHandle); if(session) { @@ -3849,19 +3740,19 @@ void LLVivoxVoiceClient::sessionAddedEvent( session->mIsChannel = isChannel; session->mIncoming = incoming; session->mAlias = alias; - + // Generate a caller UUID -- don't need to do this for channels if(!session->mIsChannel) { if(IDFromName(session->mSIPURI, session->mCallerID)) { - // Normal URI(base64-encoded UUID) + // Normal URI(base64-encoded UUID) } else if(!session->mAlias.empty() && IDFromName(session->mAlias, session->mCallerID)) { // Wrong URI, but an alias is available. Stash the incoming URI as an alternate session->mAlternateSIPURI = session->mSIPURI; - + // and generate a proper URI from the ID. setSessionURI(session, sipURIFromID(session->mCallerID)); } @@ -3870,7 +3761,7 @@ void LLVivoxVoiceClient::sessionAddedEvent( LL_INFOS("Voice") << "Could not generate caller id from uri, using hash of uri " << session->mSIPURI << LL_ENDL; session->mCallerID.generate(session->mSIPURI); session->mSynthesizedCallerID = true; - + // Can't look up the name in this case -- we have to extract it from the URI. std::string namePortion = nameFromsipURI(session->mSIPURI); if(namePortion.empty()) @@ -3878,14 +3769,14 @@ void LLVivoxVoiceClient::sessionAddedEvent( // Didn't seem to be a SIP URI, just use the whole provided name. namePortion = nameString; } - + // Some incoming names may be separated with an underscore instead of a space. Fix this. LLStringUtil::replaceChar(namePortion, '_', ' '); - + // Act like we just finished resolving the name (this stores it in all the right places) avatarNameResolved(session->mCallerID, namePortion); } - + LL_INFOS("Voice") << "caller ID: " << session->mCallerID << LL_ENDL; if(!session->mSynthesizedCallerID) @@ -3900,7 +3791,7 @@ void LLVivoxVoiceClient::sessionAddedEvent( void LLVivoxVoiceClient::sessionGroupAddedEvent(std::string &sessionGroupHandle) { LL_DEBUGS("Voice") << "handle " << sessionGroupHandle << LL_ENDL; - + #if USE_SESSION_GROUPS if(mMainSessionGroupHandle.empty()) { @@ -3927,7 +3818,7 @@ void LLVivoxVoiceClient::joinedAudioSession(const sessionStatePtr_t &session) // The old session may now need to be deleted. reapSession(oldSession); } - + // This is the session we're joining. if(mIsJoiningSession) { @@ -3943,10 +3834,10 @@ void LLVivoxVoiceClient::joinedAudioSession(const sessionStatePtr_t &session) participant->mIsSelf = true; lookupName(participant->mAvatarID); - LL_INFOS("Voice") << "added self as participant \"" << participant->mAccountName + LL_INFOS("Voice") << "added self as participant \"" << participant->mAccountName << "\" (" << participant->mAvatarID << ")"<< LL_ENDL; } - + if(!session->mIsChannel) { // this is a p2p session. Make sure the other end is added as a participant. @@ -3962,9 +3853,9 @@ void LLVivoxVoiceClient::joinedAudioSession(const sessionStatePtr_t &session) participant->mDisplayName = session->mName; avatarNameResolved(participant->mAvatarID, session->mName); } - + // TODO: Question: Do we need to set up mAvatarID/mAvatarIDValid here? - LL_INFOS("Voice") << "added caller as participant \"" << participant->mAccountName + LL_INFOS("Voice") << "added caller as participant \"" << participant->mAccountName << "\" (" << participant->mAvatarID << ")"<< LL_ENDL; } } @@ -3972,11 +3863,11 @@ void LLVivoxVoiceClient::joinedAudioSession(const sessionStatePtr_t &session) } void LLVivoxVoiceClient::sessionRemovedEvent( - std::string &sessionHandle, + std::string &sessionHandle, std::string &sessionGroupHandle) { LL_INFOS("Voice") << "handle " << sessionHandle << LL_ENDL; - + sessionStatePtr_t session(findSession(sessionHandle)); if(session) { @@ -3984,15 +3875,15 @@ void LLVivoxVoiceClient::sessionRemovedEvent( // This message invalidates the session's handle. Set it to empty. clearSessionHandle(session); - + // This also means that the session's session group is now empty. // Terminate the session group so it doesn't leak. sessionGroupTerminateSendMessage(session); - + // Reset the media state (we now have no info) session->mMediaStreamState = streamStateUnknown; //session->mTextStreamState = streamStateUnknown; - + // Conditionally delete the session reapSession(session); } @@ -4008,7 +3899,7 @@ void LLVivoxVoiceClient::reapSession(const sessionStatePtr_t &session) { if(session) { - + if(session->mCreateInProgress) { LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (create in progress)" << LL_ENDL; @@ -4030,7 +3921,7 @@ void LLVivoxVoiceClient::reapSession(const sessionStatePtr_t &session) // We don't have a reason to keep tracking this session, so just delete it. LL_DEBUGS("Voice") << "deleting session " << session->mSIPURI << LL_ENDL; deleteSession(session); - } + } } else { @@ -4042,32 +3933,32 @@ void LLVivoxVoiceClient::reapSession(const sessionStatePtr_t &session) bool LLVivoxVoiceClient::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); if(stricmp(urihost.c_str(), mVoiceSIPURIHostName.c_str())) { // The hostname in this URI is different from what we expect. This probably means we need to relog. - + // We could make a ProvisionVoiceAccountRequest and compare the result with the current values of // mVoiceSIPURIHostName and mVoiceAccountServerURI to be really sure, but this is a pretty good indicator. - + result = true; } } } } - + return result; } @@ -4083,9 +3974,9 @@ void LLVivoxVoiceClient::leftAudioSession(const sessionStatePtr_t &session) } void LLVivoxVoiceClient::accountLoginStateChangeEvent( - std::string &accountHandle, - int statusCode, - std::string &statusString, + std::string &accountHandle, + int statusCode, + std::string &statusString, int state) { LLSD levent = LLSD::emptyMap(); @@ -4097,9 +3988,9 @@ void LLVivoxVoiceClient::accountLoginStateChangeEvent( login_state_logging_in = 2, login_state_logging_out = 3, login_state_resetting = 4, - login_state_error=100 + login_state_error=100 */ - + LL_DEBUGS("Voice") << "state change event: " << state << LL_ENDL; switch(state) { @@ -4129,7 +4020,7 @@ void LLVivoxVoiceClient::accountLoginStateChangeEvent( mVivoxPump.post(levent); break; - + default: //Used to be a commented out warning LL_WARNS("Voice") << "unknown account state event: " << state << LL_ENDL; @@ -4166,24 +4057,24 @@ void LLVivoxVoiceClient::mediaCompletionEvent(std::string &sessionGroupHandle, s } void LLVivoxVoiceClient::mediaStreamUpdatedEvent( - std::string &sessionHandle, - std::string &sessionGroupHandle, - int statusCode, - std::string &statusString, - int state, + std::string &sessionHandle, + std::string &sessionGroupHandle, + int statusCode, + std::string &statusString, + int state, bool incoming) { sessionStatePtr_t session(findSession(sessionHandle)); - + LL_DEBUGS("Voice") << "session " << sessionHandle << ", status code " << statusCode << ", string \"" << statusString << "\"" << LL_ENDL; - + if(session) { // We know about this session - + // Save the state for later use session->mMediaStreamState = state; - + switch(statusCode) { case 0: @@ -4211,9 +4102,9 @@ void LLVivoxVoiceClient::mediaStreamUpdatedEvent( session->mVoiceActive = true; session->mMediaConnectInProgress = false; joinedAudioSession(session); - case streamStateConnecting: // do nothing, but prevents a warning getting into the logs. + case streamStateConnecting: // do nothing, but prevents a warning getting into the logs. break; - + case streamStateRinging: if(incoming) { @@ -4232,13 +4123,13 @@ void LLVivoxVoiceClient::mediaStreamUpdatedEvent( } } break; - + default: LL_WARNS("Voice") << "unknown state " << state << LL_ENDL; break; - + } - + } else { @@ -4248,12 +4139,12 @@ void LLVivoxVoiceClient::mediaStreamUpdatedEvent( } void LLVivoxVoiceClient::participantAddedEvent( - std::string &sessionHandle, - std::string &sessionGroupHandle, - std::string &uriString, - std::string &alias, - std::string &nameString, - std::string &displayNameString, + std::string &sessionHandle, + std::string &sessionGroupHandle, + std::string &uriString, + std::string &alias, + std::string &nameString, + std::string &displayNameString, int participantType) { sessionStatePtr_t session(findSession(sessionHandle)); @@ -4264,7 +4155,7 @@ void LLVivoxVoiceClient::participantAddedEvent( { participant->mAccountName = nameString; - LL_DEBUGS("Voice") << "added participant \"" << participant->mAccountName + LL_DEBUGS("Voice") << "added participant \"" << participant->mAccountName << "\" (" << participant->mAvatarID << ")"<< LL_ENDL; if(participant->mAvatarIDValid) @@ -4286,7 +4177,7 @@ void LLVivoxVoiceClient::participantAddedEvent( // Problems with both of the above, fall back to the account name namePortion = nameString; } - + // Set the display name (which is a hint to the active speakers window not to do its own lookup) participant->mDisplayName = namePortion; avatarNameResolved(participant->mAvatarID, namePortion); @@ -4296,10 +4187,10 @@ void LLVivoxVoiceClient::participantAddedEvent( } void LLVivoxVoiceClient::participantRemovedEvent( - std::string &sessionHandle, - std::string &sessionGroupHandle, - std::string &uriString, - std::string &alias, + std::string &sessionHandle, + std::string &sessionGroupHandle, + std::string &uriString, + std::string &alias, std::string &nameString) { sessionStatePtr_t session(findSession(sessionHandle)); @@ -4324,20 +4215,20 @@ void LLVivoxVoiceClient::participantRemovedEvent( void LLVivoxVoiceClient::participantUpdatedEvent( - std::string &sessionHandle, - std::string &sessionGroupHandle, - std::string &uriString, - std::string &alias, - bool isModeratorMuted, - bool isSpeaking, - int volume, + std::string &sessionHandle, + std::string &sessionGroupHandle, + std::string &uriString, + std::string &alias, + bool isModeratorMuted, + bool isSpeaking, + int volume, F32 energy) { sessionStatePtr_t session(findSession(sessionHandle)); if(session) { participantStatePtr_t participant(session->findParticipant(uriString)); - + if(participant) { //LL_INFOS("Voice") << "Participant Update for " << participant->mDisplayName << LL_ENDL; @@ -4362,25 +4253,25 @@ void LLVivoxVoiceClient::participantUpdatedEvent( { participant->mVolume = (F32)volume * VOLUME_SCALE_VIVOX; } - - // *HACK: mantipov: added while working on EXT-3544 - /* - Sometimes LLVoiceClient::participantUpdatedEvent callback is called BEFORE - LLViewerChatterBoxSessionAgentListUpdates::post() sometimes AFTER. - - participantUpdatedEvent updates voice participant state in particular participantState::mIsModeratorMuted - Originally we wanted to update session Speaker Manager to fire LLSpeakerVoiceModerationEvent to fix the EXT-3544 bug. - Calling of the LLSpeakerMgr::update() method was added into LLIMMgr::processAgentListUpdates. - - But in case participantUpdatedEvent() is called after LLViewerChatterBoxSessionAgentListUpdates::post() - voice participant mIsModeratorMuted is changed after speakers are updated in Speaker Manager - and event is not fired. - - So, we have to call LLSpeakerMgr::update() here. + + // *HACK: mantipov: added while working on EXT-3544 + /* + Sometimes LLVoiceClient::participantUpdatedEvent callback is called BEFORE + LLViewerChatterBoxSessionAgentListUpdates::post() sometimes AFTER. + + participantUpdatedEvent updates voice participant state in particular participantState::mIsModeratorMuted + Originally we wanted to update session Speaker Manager to fire LLSpeakerVoiceModerationEvent to fix the EXT-3544 bug. + Calling of the LLSpeakerMgr::update() method was added into LLIMMgr::processAgentListUpdates. + + But in case participantUpdatedEvent() is called after LLViewerChatterBoxSessionAgentListUpdates::post() + voice participant mIsModeratorMuted is changed after speakers are updated in Speaker Manager + and event is not fired. + + So, we have to call LLSpeakerMgr::update() here. */ LLVoiceChannel* voice_cnl = LLVoiceChannel::getCurrentVoiceChannel(); - - // ignore session ID of local chat + + // ignore session ID of local chat if (voice_cnl && voice_cnl->getSessionID().notNull()) { LLSpeakerMgr* speaker_manager = LLIMModel::getInstance()->getSpeakerManager(voice_cnl->getSessionID()); @@ -4409,10 +4300,10 @@ void LLVivoxVoiceClient::participantUpdatedEvent( } void LLVivoxVoiceClient::messageEvent( - std::string &sessionHandle, - std::string &uriString, - std::string &alias, - std::string &messageHeader, + std::string &sessionHandle, + std::string &uriString, + std::string &alias, + std::string &messageHeader, std::string &messageBody, std::string &applicationString) { @@ -4433,7 +4324,7 @@ void LLVivoxVoiceClient::messageEvent( const std::string endSpan = "</span>"; std::string::size_type start; std::string::size_type end; - + // Default to displaying the raw string, so the message gets through. message = messageBody; @@ -4445,38 +4336,38 @@ void LLVivoxVoiceClient::messageEvent( if(start != std::string::npos) { start += startMarker2.size(); - + if(end != std::string::npos) end -= start; - + message.assign(messageBody, start, end); } - else + else { // Didn't find a <body>, try looking for a <span> instead. start = messageBody.find(startSpan); start = messageBody.find(startMarker2, start); end = messageBody.find(endSpan); - + if(start != std::string::npos) { start += startMarker2.size(); - + if(end != std::string::npos) end -= start; - + message.assign(messageBody, start, end); - } + } } - } - + } + // LL_DEBUGS("Voice") << " raw message = \n" << message << LL_ENDL; // strip formatting tags { std::string::size_type start; std::string::size_type end; - + while((start = message.find('<')) != std::string::npos) { if((end = message.find('>', start + 1)) != std::string::npos) @@ -4491,7 +4382,7 @@ void LLVivoxVoiceClient::messageEvent( } } } - + // Decode ampersand-escaped chars { std::string::size_type mark = 0; @@ -4503,14 +4394,14 @@ void LLVivoxVoiceClient::messageEvent( message.replace(mark, 4, "<"); mark += 1; } - + mark = 0; while((mark = message.find(">", mark)) != std::string::npos) { message.replace(mark, 4, ">"); mark += 1; } - + mark = 0; while((mark = message.find("&", mark)) != std::string::npos) { @@ -4518,12 +4409,12 @@ void LLVivoxVoiceClient::messageEvent( mark += 1; } } - + // strip leading/trailing whitespace (since we always seem to get a couple newlines) LLStringUtil::trim(message); - + // LL_DEBUGS("Voice") << " stripped message = \n" << message << LL_ENDL; - + sessionStatePtr_t session(findSession(sessionHandle)); if(session) { @@ -4533,7 +4424,7 @@ void LLVivoxVoiceClient::messageEvent( LLChat chat; chat.mMuted = is_muted && !is_linden; - + if(!chat.mMuted) { chat.mFromID = session->mCallerID; @@ -4544,7 +4435,7 @@ void LLVivoxVoiceClient::messageEvent( { // TODO: Question: Return do not disturb mode response here? Or maybe when session is started instead? } - + LL_DEBUGS("Voice") << "adding message, name " << session->mName << " session " << session->mIMSessionID << ", target " << session->mCallerID << LL_ENDL; LLIMMgr::getInstance()->addMessage(session->mIMSessionID, session->mCallerID, @@ -4557,14 +4448,14 @@ void LLVivoxVoiceClient::messageEvent( LLUUID::null, // default arg LLVector3::zero); // default arg } - } + } } } void LLVivoxVoiceClient::sessionNotificationEvent(std::string &sessionHandle, std::string &uriString, std::string ¬ificationType) { sessionStatePtr_t session(findSession(sessionHandle)); - + if(session) { participantStatePtr_t participant(session->findParticipant(uriString)); @@ -4627,11 +4518,11 @@ void LLVivoxVoiceClient::muteListChanged() if(mAudioSession) { participantMap::iterator iter = mAudioSession->mParticipantsByURI.begin(); - + for(; iter != mAudioSession->mParticipantsByURI.end(); iter++) { participantStatePtr_t p(iter->second); - + // Check to see if this participant is on the mute list already if(p->updateMuteState()) mAudioSession->mVolumeDirty = true; @@ -4641,18 +4532,18 @@ void LLVivoxVoiceClient::muteListChanged() ///////////////////////////// // Managing list of participants -LLVivoxVoiceClient::participantState::participantState(const std::string &uri) : - mURI(uri), - mPTT(false), - mIsSpeaking(false), - mIsModeratorMuted(false), - mLastSpokeTimestamp(0.f), - mPower(0.f), - mVolume(LLVoiceClient::VOLUME_DEFAULT), +LLVivoxVoiceClient::participantState::participantState(const std::string &uri) : + mURI(uri), + mPTT(false), + mIsSpeaking(false), + mIsModeratorMuted(false), + mLastSpokeTimestamp(0.f), + mPower(0.f), + mVolume(LLVoiceClient::VOLUME_DEFAULT), mUserVolume(0), - mOnMuteList(false), + mOnMuteList(false), mVolumeSet(false), - mVolumeDirty(false), + mVolumeDirty(false), mAvatarIDValid(false), mIsSelf(false) { @@ -4662,7 +4553,7 @@ LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::sessionState::addP { participantStatePtr_t result; bool useAlternateURI = false; - + // Note: this is mostly the body of LLVivoxVoiceClient::sessionState::findParticipant(), but since we need to know if it // matched the alternate SIP URI (so we can add it properly), we need to reproduce it here. { @@ -4684,14 +4575,14 @@ LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::sessionState::addP result = iter->second; } } - + if(!result) { // participant isn't already in one list or the other. result.reset(new participantState(useAlternateURI?mSIPURI:uri)); mParticipantsByURI.insert(participantMap::value_type(result->mURI, result)); mParticipantsChanged = true; - + // Try to do a reverse transform on the URI to get the GUID back. { LLUUID id; @@ -4707,12 +4598,12 @@ LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::sessionState::addP result->mAvatarID.generate(uri); } } - + if(result->updateMuteState()) { mMuteDirty = true; } - + mParticipantsByUUID.insert(participantUUIDMap::value_type(result->mAvatarID, result)); if (LLSpeakerVolumeStorage::getInstance()->getSpeakerVolume(result->mAvatarID, result->mVolume)) @@ -4720,10 +4611,10 @@ LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::sessionState::addP result->mVolumeDirty = true; mVolumeDirty = true; } - + LL_DEBUGS("Voice") << "participant \"" << result->mURI << "\" added." << LL_ENDL; } - + return result; } @@ -4752,9 +4643,9 @@ void LLVivoxVoiceClient::sessionState::removeParticipant(const LLVivoxVoiceClien { participantMap::iterator iter = mParticipantsByURI.find(participant->mURI); participantUUIDMap::iterator iter2 = mParticipantsByUUID.find(participant->mAvatarID); - + LL_DEBUGS("Voice") << "participant \"" << participant->mURI << "\" (" << participant->mAvatarID << ") removed." << LL_ENDL; - + if(iter == mParticipantsByURI.end()) { LL_WARNS("Voice") << "Internal error: participant " << participant->mURI << " not in URI map" << LL_ENDL; @@ -4785,7 +4676,7 @@ void LLVivoxVoiceClient::sessionState::removeAllParticipants() { removeParticipant(mParticipantsByURI.begin()->second); } - + if(!mParticipantsByUUID.empty()) { LL_WARNS("Voice") << "Internal error: empty URI map, non-empty UUID map" << LL_ENDL; @@ -4811,10 +4702,10 @@ void LLVivoxVoiceClient::sessionState::VerifySessions() void LLVivoxVoiceClient::getParticipantList(std::set<LLUUID> &participants) { - if(mAudioSession) + if(mProcessChannels && mAudioSession) { for(participantUUIDMap::iterator iter = mAudioSession->mParticipantsByUUID.begin(); - iter != mAudioSession->mParticipantsByUUID.end(); + iter != mAudioSession->mParticipantsByUUID.end(); iter++) { participants.insert(iter->first); @@ -4824,18 +4715,18 @@ void LLVivoxVoiceClient::getParticipantList(std::set<LLUUID> &participants) bool LLVivoxVoiceClient::isParticipant(const LLUUID &speaker_id) { - if(mAudioSession) + if(mProcessChannels && mAudioSession) { - return (mAudioSession->mParticipantsByUUID.find(speaker_id) != mAudioSession->mParticipantsByUUID.end()); + return (mAudioSession->mParticipantsByUUID.find(speaker_id) != mAudioSession->mParticipantsByUUID.end()); } - return false; + return false; } LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::sessionState::findParticipant(const std::string &uri) { participantStatePtr_t result; - + participantMap::iterator iter = mParticipantsByURI.find(uri); if(iter == mParticipantsByURI.end()) @@ -4852,7 +4743,7 @@ LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::sessionState::find { result = iter->second; } - + return result; } @@ -4872,12 +4763,12 @@ LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::sessionState::find LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::findParticipantByID(const LLUUID& id) { participantStatePtr_t result; - + if(mAudioSession) { result = mAudioSession->findParticipantByID(id); } - + return result; } @@ -4888,15 +4779,15 @@ bool LLVivoxVoiceClient::checkParcelChanged(bool update) { LLViewerRegion *region = gAgent.getRegion(); LLParcel *parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); - + if(region && parcel) { S32 parcelLocalID = parcel->getLocalID(); std::string regionName = region->getName(); - + // LL_DEBUGS("Voice") << "Region name = \"" << regionName << "\", parcel local ID = " << parcelLocalID << ", cap URI = \"" << capURI << "\"" << LL_ENDL; - - // The region name starts out empty and gets filled in later. + + // The region name starts out empty and gets filled in later. // Also, the cap gets filled in a short time after the region cross, but a little too late for our purposes. // If either is empty, wait for the next time around. if(!regionName.empty()) @@ -4924,7 +4815,7 @@ bool LLVivoxVoiceClient::switchChannel( std::string hash) { bool needsSwitch = !mIsInChannel; - + if (mIsInChannel) { if (mSessionTerminateRequested) @@ -4932,8 +4823,10 @@ bool LLVivoxVoiceClient::switchChannel( // 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->mSIPURI != uri) + { needsSwitch = true; + } } else { @@ -4999,7 +4892,7 @@ bool LLVivoxVoiceClient::switchChannel( mNextAudioSession->mReconnect = !no_reconnect; mNextAudioSession->mIsP2P = is_p2p; } - + if (mIsInChannel) { // If we're already in a channel, or if we're joining one, terminate @@ -5023,23 +4916,19 @@ void LLVivoxVoiceClient::joinSession(const sessionStatePtr_t &session) } } -void LLVivoxVoiceClient::setNonSpatialChannel( - const std::string &uri, - const std::string &credentials) +void LLVivoxVoiceClient::setNonSpatialChannel(const LLSD& channelInfo, bool notify_on_first_join, bool hangup_on_last_leave) { - switchChannel(uri, false, false, false, credentials); + switchChannel(channelInfo["channel_uri"].asString(), false, false, false, channelInfo["channel_credentials"].asString()); } -bool LLVivoxVoiceClient::setSpatialChannel( - const std::string &uri, - const std::string &credentials) +bool LLVivoxVoiceClient::setSpatialChannel(const LLSD& channelInfo) { - mSpatialSessionURI = uri; - mSpatialSessionCredentials = credentials; - mAreaVoiceDisabled = mSpatialSessionURI.empty(); + mProcessChannels = true; + mSpatialSessionURI = channelInfo["channel_uri"].asString(); + mSpatialSessionCredentials = channelInfo["channel_credentials"].asString(); + + LL_DEBUGS("Voice") << "got spatial channel uri: \"" << mSpatialSessionURI << "\"" << LL_ENDL; - LL_DEBUGS("Voice") << "got spatial channel uri: \"" << uri << "\"" << LL_ENDL; - if((mIsInChannel && mAudioSession && !(mAudioSession->mIsSpatial)) || (mNextAudioSession && !(mNextAudioSession->mIsSpatial))) { // User is in a non-spatial chat or joining a non-spatial chat. Don't switch channels. @@ -5059,85 +4948,39 @@ void LLVivoxVoiceClient::callUser(const LLUUID &uuid) switchChannel(userURI, false, true, true); } -#if 0 -// Vivox text IMs are not in use. -LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::startUserIMSession(const LLUUID &uuid) -{ - // Figure out if a session with the user already exists - sessionStatePtr_t session(findSession(uuid)); - if(!session) - { - // No session with user, need to start one. - std::string uri = sipURIFromID(uuid); - session = addSession(uri); +void LLVivoxVoiceClient::hangup() { leaveChannel(); } - llassert(session); - if (!session) - return session; - session->mIsSpatial = false; - session->mReconnect = false; - session->mIsP2P = true; - session->mCallerID = uuid; - } - - if(session->mHandle.empty()) - { - // Session isn't active -- start it up. - sessionCreateSendMessage(session, false, false); - } - else - { - // Session is already active -- start up text. - sessionTextConnectSendMessage(session); - } - - return session; -} -#endif - -void LLVivoxVoiceClient::endUserIMSession(const LLUUID &uuid) +LLVoiceP2PIncomingCallInterfacePtr LLVivoxVoiceClient::getIncomingCallInterface(const LLSD &voice_call_info) { -#if 0 - // Vivox text IMs are not in use. - - // Figure out if a session with the user exists - sessionStatePtr_t session(findSession(uuid)); - if(session) - { - // found the session - if(!session->mHandle.empty()) - { - // sessionTextDisconnectSendMessage(session); // a SLim leftover, not used any more. - } - } - else - { - LL_DEBUGS("Voice") << "Session not found for participant ID " << uuid << LL_ENDL; - } -#endif + return boost::make_shared<LLVivoxVoiceP2PIncomingCall>(voice_call_info); } -bool LLVivoxVoiceClient::isValidChannel(std::string &sessionHandle) + +bool LLVivoxVoiceClient::answerInvite(const std::string &sessionHandle) { - return(findSession(sessionHandle) != NULL); - + // this is only ever used to answer incoming p2p call invites. + + sessionStatePtr_t session(findSession(sessionHandle)); + if (session) + { + session->mIsSpatial = false; + session->mReconnect = false; + session->mIsP2P = true; + + joinSession(session); + return true; + } + + return false; } -bool LLVivoxVoiceClient::answerInvite(std::string &sessionHandle) + +void LLVivoxVoiceClient::declineInvite(const std::string &sessionHandle) { - // this is only ever used to answer incoming p2p call invites. - sessionStatePtr_t session(findSession(sessionHandle)); - if(session) - { - session->mIsSpatial = false; - session->mReconnect = false; - session->mIsP2P = true; - - joinSession(session); - return true; - } - - return false; + if (session) + { + sessionMediaDisconnectSendMessage(session); + } } bool LLVivoxVoiceClient::isVoiceWorking() const @@ -5154,9 +4997,9 @@ bool LLVivoxVoiceClient::isVoiceWorking() const // Currently this will be false only for PSTN callers into group chats, and PSTN p2p calls. BOOL LLVivoxVoiceClient::isParticipantAvatar(const LLUUID &id) { - BOOL result = TRUE; + BOOL result = TRUE; sessionStatePtr_t session(findSession(id)); - + if(session) { // this is a p2p session with the indicated caller, or the session with the specified UUID. @@ -5175,22 +5018,22 @@ BOOL LLVivoxVoiceClient::isParticipantAvatar(const LLUUID &id) } } } - + return result; } // Returns true if calling back the session URI after the session has closed is possible. -// Currently this will be false only for PSTN P2P calls. +// Currently this will be false only for PSTN P2P calls. BOOL LLVivoxVoiceClient::isSessionCallBackPossible(const LLUUID &session_id) { - BOOL result = TRUE; + BOOL result = TRUE; sessionStatePtr_t session(findSession(session_id)); - + if(session != NULL) { result = session->isCallBackPossible(); } - + return result; } @@ -5200,62 +5043,69 @@ BOOL LLVivoxVoiceClient::isSessionTextIMPossible(const LLUUID &session_id) { bool result = TRUE; sessionStatePtr_t session(findSession(session_id)); - + if(session != NULL) { result = session->isTextIMPossible(); } - - return result; -} - -void LLVivoxVoiceClient::declineInvite(std::string &sessionHandle) -{ - sessionStatePtr_t session(findSession(sessionHandle)); - if(session) - { - sessionMediaDisconnectSendMessage(session); - } + return result; } void LLVivoxVoiceClient::leaveNonSpatialChannel() { LL_DEBUGS("Voice") << "Request to leave spacial channel." << LL_ENDL; - - // Make sure we don't rejoin the current session. + + // Make sure we don't rejoin the current session. sessionStatePtr_t oldNextSession(mNextAudioSession); mNextAudioSession.reset(); - + // Most likely this will still be the current session at this point, but check it anyway. reapSession(oldNextSession); - + verifySessionState(); - + sessionTerminate(); } -std::string LLVivoxVoiceClient::getCurrentChannel() +void LLVivoxVoiceClient::processChannels(bool process) { - std::string result; - - if (mIsInChannel && !mSessionTerminateRequested) - { - result = getAudioSessionURI(); - } - - return result; + mProcessChannels = process; +} + +bool LLVivoxVoiceClient::isCurrentChannel(const LLSD &channelInfo) +{ + if (!mProcessChannels || (channelInfo["voice_server_type"].asString() != VIVOX_VOICE_SERVER_TYPE)) + { + return false; + } + if (mAudioSession) + { + if (!channelInfo["sessionHandle"].asString().empty()) + { + return mAudioSession->mHandle == channelInfo["session_handle"].asString(); + } + return channelInfo["channel_uri"].asString() == mAudioSession->mSIPURI; + } + return false; +} + +bool LLVivoxVoiceClient::compareChannels(const LLSD& channelInfo1, const LLSD& channelInfo2) +{ + return (channelInfo1["voice_server_type"] == VIVOX_VOICE_SERVER_TYPE) && + (channelInfo1["voice_server_type"] == channelInfo2["voice_server_type"]) && + (channelInfo1["channel_uri"] == channelInfo2["channel_uri"]); } bool LLVivoxVoiceClient::inProximalChannel() { bool result = false; - + if (mIsInChannel && !mSessionTerminateRequested) { result = inSpatialChannel(); } - + return result; } @@ -5266,7 +5116,7 @@ std::string LLVivoxVoiceClient::sipURIFromID(const LLUUID &id) result += nameFromID(id); result += "@"; result += mVoiceSIPURIHostName; - + return result; } @@ -5280,24 +5130,14 @@ std::string LLVivoxVoiceClient::sipURIFromAvatar(LLVOAvatar *avatar) result += "@"; result += mVoiceSIPURIHostName; } - - return result; -} -std::string LLVivoxVoiceClient::nameFromAvatar(LLVOAvatar *avatar) -{ - std::string result; - if(avatar) - { - result = nameFromID(avatar->getID()); - } return result; } std::string LLVivoxVoiceClient::nameFromID(const LLUUID &uuid) { std::string result; - + if (uuid.isNull()) { //VIVOX, the uuid emtpy look for the mURIString and return that instead. //result.assign(uuid.mURIStringName); @@ -5306,31 +5146,31 @@ std::string LLVivoxVoiceClient::nameFromID(const LLUUID &uuid) } // Prepending this apparently prevents conflicts with reserved names inside the vivox code. result = "x"; - - // Base64 encode and replace the pieces of base64 that are less compatible + + // Base64 encode and replace the pieces of base64 that are less compatible // with e-mail local-parts. // See RFC-4648 "Base 64 Encoding with URL and Filename Safe Alphabet" result += LLBase64::encode(uuid.mData, UUID_BYTES); LLStringUtil::replaceChar(result, '+', '-'); LLStringUtil::replaceChar(result, '/', '_'); - + // If you need to transform a GUID to this form on the Mac OS X command line, this will do so: // echo -n x && (echo e669132a-6c43-4ee1-a78d-6c82fff59f32 |xxd -r -p |openssl base64|tr '/+' '_-') - + // The reverse transform can be done with: // echo 'x5mkTKmxDTuGnjWyC__WfMg==' |cut -b 2- -|tr '_-' '/+' |openssl base64 -d|xxd -p - + return result; } bool LLVivoxVoiceClient::IDFromName(const std::string inName, LLUUID &uuid) { bool result = false; - + // SLIM SDK: The "name" may actually be a SIP URI such as: "sip:xFnPP04IpREWNkuw1cOXlhw==@bhr.vivox.com" // If it is, convert to a bare name before doing the transform. std::string name = nameFromsipURI(inName); - + // Doesn't look like a SIP URI, assume it's an actual name. if(name.empty()) name = inName; @@ -5338,7 +5178,7 @@ bool LLVivoxVoiceClient::IDFromName(const std::string inName, LLUUID &uuid) // This will only work if the name is of the proper form. // As an example, the account name for Monroe Linden (UUID 1673cfd3-8229-4445-8d92-ec3570e5e587) is: // "xFnPP04IpREWNkuw1cOXlhw==" - + if((name.size() == 25) && (name[0] == 'x') && (name[23] == '=') && (name[24] == '=')) { // The name appears to have the right form. @@ -5348,7 +5188,7 @@ bool LLVivoxVoiceClient::IDFromName(const std::string inName, LLUUID &uuid) LLStringUtil::replaceChar(temp, '-', '+'); LLStringUtil::replaceChar(temp, '_', '/'); - U8 rawuuid[UUID_BYTES + 1]; + U8 rawuuid[UUID_BYTES + 1]; int len = apr_base64_decode_binary(rawuuid, temp.c_str() + 1); if(len == UUID_BYTES) { @@ -5356,21 +5196,16 @@ bool LLVivoxVoiceClient::IDFromName(const std::string inName, LLUUID &uuid) memcpy(uuid.mData, rawuuid, UUID_BYTES); result = true; } - } - + } + if(!result) { // VIVOX: not a standard account name, just copy the URI name mURIString field // and hope for the best. bpj uuid.setNull(); // VIVOX, set the uuid field to nulls } - - return result; -} -std::string LLVivoxVoiceClient::displayNameFromAvatar(LLVOAvatar *avatar) -{ - return avatar->getFullname(); + return result; } std::string LLVivoxVoiceClient::sipURIFromName(std::string &name) @@ -5397,39 +5232,42 @@ std::string LLVivoxVoiceClient::nameFromsipURI(const std::string &uri) { result = uri.substr(sipOffset + 4, atOffset - (sipOffset + 4)); } - + return result; } bool LLVivoxVoiceClient::inSpatialChannel(void) { bool result = false; - + if(mAudioSession) { result = mAudioSession->mIsSpatial; } - + return result; } -std::string LLVivoxVoiceClient::getAudioSessionURI() + +LLSD LLVivoxVoiceClient::getAudioSessionChannelInfo() { - std::string result; - - if(mAudioSession) - result = mAudioSession->mSIPURI; - + LLSD result; + + if (mAudioSession) + { + result = mAudioSession->getVoiceChannelInfo(); + } + return result; } std::string LLVivoxVoiceClient::getAudioSessionHandle() { std::string result; - + if(mAudioSession) result = mAudioSession->mHandle; - + return result; } @@ -5448,11 +5286,11 @@ void LLVivoxVoiceClient::enforceTether(void) F32 camera_distance = (F32)camera_offset.magVec(); if(camera_distance > max_dist) { - tethered = mAvatarPosition + + tethered = mAvatarPosition + (max_dist / camera_distance) * camera_offset; } } - + if(dist_vec_squared(mCameraPosition, tethered) > 0.01) { mCameraPosition = tethered; @@ -5469,19 +5307,19 @@ void LLVivoxVoiceClient::updatePosition(void) LLMatrix3 rot; LLVector3d pos; LLQuaternion qrot; - + // TODO: If camera and avatar velocity are actually used by the voice system, we could compute them here... // They're currently always set to zero. - + // Send the current camera position to the voice code - rot.setRows(LLViewerCamera::getInstance()->getAtAxis(), LLViewerCamera::getInstance()->getLeftAxis (), LLViewerCamera::getInstance()->getUpAxis()); + rot.setRows(LLViewerCamera::getInstance()->getAtAxis(), LLViewerCamera::getInstance()->getLeftAxis (), LLViewerCamera::getInstance()->getUpAxis()); pos = gAgent.getRegion()->getPosGlobalFromRegion(LLViewerCamera::getInstance()->getOrigin()); - + LLVivoxVoiceClient::getInstance()->setCameraPosition( pos, // position LLVector3::zero, // velocity rot); // rotation matrix - + // Send the current avatar position to the voice code qrot = gAgentAvatarp->getRootJoint()->getWorldRotation(); pos = gAgentAvatarp->getPositionGlobal(); @@ -5489,7 +5327,7 @@ void LLVivoxVoiceClient::updatePosition(void) // TODO: Can we get the head offset from outside the LLVOAvatar? // pos += LLVector3d(mHeadOffset); pos += LLVector3d(0.f, 0.f, 1.f); - + LLVivoxVoiceClient::getInstance()->setAvatarPosition( pos, // position LLVector3::zero, // velocity @@ -5500,13 +5338,13 @@ void LLVivoxVoiceClient::updatePosition(void) void LLVivoxVoiceClient::setCameraPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot) { mCameraRequestedPosition = position; - + if(mCameraVelocity != velocity) { mCameraVelocity = velocity; mSpatialCoordsDirty = true; } - + if(mCameraRot != rot) { mCameraRot = rot; @@ -5521,19 +5359,19 @@ void LLVivoxVoiceClient::setAvatarPosition(const LLVector3d &position, const LLV mAvatarPosition = position; mSpatialCoordsDirty = true; } - + if(mAvatarVelocity != velocity) { mAvatarVelocity = velocity; mSpatialCoordsDirty = true; } - + // If the two rotations are not exactly equal test their dot product // to get the cos of the angle between them. // If it is too small, don't update. F32 rot_cos_diff = llabs(dot(mAvatarRot, rot)); if ((mAvatarRot != rot) && (rot_cos_diff < MINUSCULE_ANGLE_COS)) - { + { mAvatarRot = rot; mSpatialCoordsDirty = true; } @@ -5542,15 +5380,15 @@ void LLVivoxVoiceClient::setAvatarPosition(const LLVector3d &position, const LLV bool LLVivoxVoiceClient::channelFromRegion(LLViewerRegion *region, std::string &name) { bool result = false; - + if(region) { name = region->getName(); } - + if(!name.empty()) result = true; - + return result; } @@ -5580,14 +5418,14 @@ void LLVivoxVoiceClient::setVoiceEnabled(bool enabled) << " was "<< (mVoiceEnabled ? "enabled" : "disabled") << " coro "<< (mIsCoroutineActive ? "active" : "inactive") << LL_ENDL; - + if (enabled != mVoiceEnabled) { // TODO: Refactor this so we don't call into LLVoiceChannel, but simply // use the status observer mVoiceEnabled = enabled; LLVoiceClientStatusObserver::EStatusType status; - + if (enabled) { LL_DEBUGS("Voice") << "enabling" << LL_ENDL; @@ -5620,38 +5458,13 @@ void LLVivoxVoiceClient::setVoiceEnabled(bool enabled) } } -bool LLVivoxVoiceClient::voiceEnabled() -{ - return gSavedSettings.getBOOL("EnableVoiceChat") && - !gSavedSettings.getBOOL("CmdLineDisableVoice") && - !gNonInteractive; -} - -void LLVivoxVoiceClient::setLipSyncEnabled(BOOL enabled) -{ - mLipSyncEnabled = enabled; -} - -BOOL LLVivoxVoiceClient::lipSyncEnabled() -{ - - if ( mVoiceEnabled ) - { - return mLipSyncEnabled; - } - else - { - return FALSE; - } -} - void LLVivoxVoiceClient::setEarLocation(S32 loc) { if(mEarLocation != loc) { LL_DEBUGS("Voice") << "Setting mEarLocation to " << loc << LL_ENDL; - + mEarLocation = loc; mSpatialCoordsDirty = true; } @@ -5659,7 +5472,7 @@ void LLVivoxVoiceClient::setEarLocation(S32 loc) void LLVivoxVoiceClient::setVoiceVolume(F32 volume) { - int scaled_volume = scale_speaker_volume(volume); + int scaled_volume = scale_speaker_volume(volume); if(scaled_volume != mSpeakerVolume) { @@ -5677,7 +5490,7 @@ void LLVivoxVoiceClient::setVoiceVolume(F32 volume) void LLVivoxVoiceClient::setMicGain(F32 volume) { int scaled_volume = scale_mic_volume(volume); - + if(scaled_volume != mMicVolume) { mMicVolume = scaled_volume; @@ -5687,29 +5500,19 @@ void LLVivoxVoiceClient::setMicGain(F32 volume) ///////////////////////////// // Accessors for data related to nearby speakers -BOOL LLVivoxVoiceClient::getVoiceEnabled(const LLUUID& id) -{ - BOOL result = FALSE; - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - // I'm not sure what the semantics of this should be. - // For now, if we have any data about the user that came through the chat channel, assume they're voice-enabled. - result = TRUE; - } - - return result; -} std::string LLVivoxVoiceClient::getDisplayName(const LLUUID& id) { std::string result; - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - result = participant->mDisplayName; - } - + if (mProcessChannels) + { + participantStatePtr_t participant(findParticipantByID(id)); + if (participant) + { + result = participant->mDisplayName; + } + } + return result; } @@ -5718,42 +5521,47 @@ std::string LLVivoxVoiceClient::getDisplayName(const LLUUID& id) BOOL LLVivoxVoiceClient::getIsSpeaking(const LLUUID& id) { BOOL result = FALSE; + if (mProcessChannels) + { + participantStatePtr_t participant(findParticipantByID(id)); + if (participant) + { + if (participant->mSpeakingTimeout.getElapsedTimeF32() > SPEAKING_TIMEOUT) + { + participant->mIsSpeaking = FALSE; + } + result = participant->mIsSpeaking; + } + } - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - if (participant->mSpeakingTimeout.getElapsedTimeF32() > SPEAKING_TIMEOUT) - { - participant->mIsSpeaking = FALSE; - } - result = participant->mIsSpeaking; - } - return result; } BOOL LLVivoxVoiceClient::getIsModeratorMuted(const LLUUID& id) { BOOL result = FALSE; - + if (!mProcessChannels) + { + return FALSE; + } participantStatePtr_t participant(findParticipantByID(id)); if(participant) { result = participant->mIsModeratorMuted; } - + return result; } F32 LLVivoxVoiceClient::getCurrentPower(const LLUUID& id) -{ +{ F32 result = 0; participantStatePtr_t participant(findParticipantByID(id)); if(participant) { result = participant->mPower; } - + return result; } @@ -5770,19 +5578,6 @@ BOOL LLVivoxVoiceClient::getUsingPTT(const LLUUID& id) // Does "using PTT" mean they're configured with a push-to-talk button? // For now, we know there's no PTT mechanism in place, so nobody is using it. } - - return result; -} - -BOOL LLVivoxVoiceClient::getOnMuteList(const LLUUID& id) -{ - BOOL result = FALSE; - - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - result = participant->mOnMuteList; - } return result; } @@ -5792,7 +5587,7 @@ F32 LLVivoxVoiceClient::getUserVolume(const LLUUID& id) { // Minimum volume will be returned for users with voice disabled F32 result = LLVoiceClient::VOLUME_MIN; - + participantStatePtr_t participant(findParticipantByID(id)); if(participant) { @@ -5840,26 +5635,21 @@ std::string LLVivoxVoiceClient::getGroupID(const LLUUID& id) { result = participant->mGroupID; } - - return result; -} -BOOL LLVivoxVoiceClient::getAreaVoiceDisabled() -{ - return mAreaVoiceDisabled; + return result; } void LLVivoxVoiceClient::recordingLoopStart(int seconds, int deltaFramesPerControlFrame) { // LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Start)" << LL_ENDL; - + if(!mMainSessionGroupHandle.empty()) { std::ostringstream stream; stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlRecording.1\">" << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>" - << "<RecordingControlType>Start</RecordingControlType>" + << "<RecordingControlType>Start</RecordingControlType>" << "<DeltaFramesPerControlFrame>" << deltaFramesPerControlFrame << "</DeltaFramesPerControlFrame>" << "<Filename>" << "" << "</Filename>" << "<EnableAudioRecordingEvents>false</EnableAudioRecordingEvents>" @@ -5881,7 +5671,7 @@ void LLVivoxVoiceClient::recordingLoopSave(const std::string& filename) stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlRecording.1\">" << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>" - << "<RecordingControlType>Flush</RecordingControlType>" + << "<RecordingControlType>Flush</RecordingControlType>" << "<Filename>" << filename << "</Filename>" << "</Request>\n\n\n"; @@ -5899,7 +5689,7 @@ void LLVivoxVoiceClient::recordingStop() stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlRecording.1\">" << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>" - << "<RecordingControlType>Stop</RecordingControlType>" + << "<RecordingControlType>Stop</RecordingControlType>" << "</Request>\n\n\n"; writeString(stream.str()); @@ -5916,7 +5706,7 @@ void LLVivoxVoiceClient::filePlaybackStart(const std::string& filename) stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlPlayback.1\">" << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>" - << "<RecordingControlType>Start</RecordingControlType>" + << "<RecordingControlType>Start</RecordingControlType>" << "<Filename>" << filename << "</Filename>" << "</Request>\n\n\n"; @@ -5934,7 +5724,7 @@ void LLVivoxVoiceClient::filePlaybackStop() stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlPlayback.1\">" << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>" - << "<RecordingControlType>Stop</RecordingControlType>" + << "<RecordingControlType>Stop</RecordingControlType>" << "</Request>\n\n\n"; writeString(stream.str()); @@ -5975,6 +5765,18 @@ LLVivoxVoiceClient::sessionState::sessionState() : { } +LLSD LLVivoxVoiceClient::sessionState::getVoiceChannelInfo() +{ + LLSD result; + + result["voice_server_type"] = VIVOX_VOICE_SERVER_TYPE; + result["channel_credentials"] = mHash; + result["channel_uri"] = mSIPURI; + result["session_handle"] = mHandle; + + return result; +} + /*static*/ LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::createSession() { @@ -6012,7 +5814,7 @@ bool LLVivoxVoiceClient::sessionState::isTextIMPossible() } -/*static*/ +/*static*/ LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::matchSessionByHandle(const std::string &handle) { sessionStatePtr_t result; @@ -6026,7 +5828,7 @@ LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::matchS return result; } -/*static*/ +/*static*/ LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::matchCreatingSessionByURI(const std::string &uri) { sessionStatePtr_t result; @@ -6073,7 +5875,7 @@ void LLVivoxVoiceClient::sessionState::for_each(sessionFunc_t func) std::for_each(mSession.begin(), mSession.end(), boost::bind(for_eachPredicate, _1, func)); } -// simple test predicates. +// simple test predicates. // *TODO: These should be made into lambdas when we can pull the trigger on newer C++ features. bool LLVivoxVoiceClient::sessionState::testByHandle(const LLVivoxVoiceClient::sessionState::wptr_t &a, std::string handle) { @@ -6127,28 +5929,28 @@ LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::findSession(const std: { result = iter->second; } - + return result; } LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::findSessionBeingCreatedByURI(const std::string &uri) -{ +{ sessionStatePtr_t result = sessionState::matchCreatingSessionByURI(uri); - + return result; } LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::findSession(const LLUUID &participant_id) { sessionStatePtr_t result = sessionState::matchSessionByParticipant(participant_id); - + return result; } LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::addSession(const std::string &uri, const std::string &handle) { sessionStatePtr_t result; - + if(handle.empty()) { // No handle supplied. @@ -6159,7 +5961,7 @@ LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::addSession(const std:: { // Check for an existing session with this handle sessionMap::iterator iter = mSessionsByHandle.find(handle); - + if(iter != mSessionsByHandle.end()) { result = iter->second; @@ -6169,7 +5971,7 @@ LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::addSession(const std:: if(!result) { // No existing session found. - + LL_DEBUGS("Voice") << "adding new session: handle \"" << handle << "\" URI " << uri << LL_ENDL; result = sessionState::createSession(); result->mSIPURI = uri; @@ -6182,8 +5984,8 @@ LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::addSession(const std:: 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 + // *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)); } @@ -6191,7 +5993,7 @@ LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::addSession(const std:: else { // Found an existing session - + if(uri != result->mSIPURI) { // TODO: Should this be an internal error? @@ -6213,12 +6015,12 @@ LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::addSession(const std:: setSessionHandle(result, handle); } } - + LL_DEBUGS("Voice") << "returning existing session: handle " << handle << " URI " << uri << LL_ENDL; } verifySessionState(); - + return result; } @@ -6249,7 +6051,7 @@ void LLVivoxVoiceClient::clearSessionHandle(const sessionStatePtr_t &session) void LLVivoxVoiceClient::setSessionHandle(const sessionStatePtr_t &session, const std::string &handle) { // 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. @@ -6268,7 +6070,7 @@ void LLVivoxVoiceClient::setSessionHandle(const sessionStatePtr_t &session, cons LL_WARNS("Voice") << "Attempt to remove session with handle " << session->mHandle << " not found in map!" << LL_ENDL; } } - + session->mHandle = handle; if(!handle.empty()) @@ -6330,7 +6132,7 @@ void LLVivoxVoiceClient::deleteAllSessions() const sessionStatePtr_t session = mSessionsByHandle.begin()->second; deleteSession(session); } - + } void LLVivoxVoiceClient::verifySessionState(void) @@ -6418,10 +6220,10 @@ void LLVivoxVoiceClient::notifyStatusObservers(LLVoiceClientStatusObserver::ESta } } } - - LL_DEBUGS("Voice") - << " " << LLVoiceClientStatusObserver::status2string(status) - << ", session URI " << getAudioSessionURI() + + LL_DEBUGS("Voice") + << " " << LLVoiceClientStatusObserver::status2string(status) + << ", session channelInfo " << getAudioSessionChannelInfo() << ", proximal is " << inSpatialChannel() << LL_ENDL; @@ -6430,7 +6232,7 @@ void LLVivoxVoiceClient::notifyStatusObservers(LLVoiceClientStatusObserver::ESta ) { LLVoiceClientStatusObserver* observer = *it; - observer->onChange(status, getAudioSessionURI(), inSpatialChannel()); + observer->onChange(status, getAudioSessionChannelInfo(), inSpatialChannel()); // In case onError() deleted an entry. it = mStatusObservers.upper_bound(observer); } @@ -6442,6 +6244,7 @@ void LLVivoxVoiceClient::notifyStatusObservers(LLVoiceClientStatusObserver::ESta { bool voice_status = LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking(); + LL_WARNS("Voice") << "Setting voice connected " << (voice_status ? "True" : "False") << LL_ENDL; gAgent.setVoiceConnected(voice_status); if (voice_status) @@ -6511,12 +6314,11 @@ void LLVivoxVoiceClient::predAvatarNameResolution(const LLVivoxVoiceClient::sess { session->mTextInvitePending = false; - // We don't need to call LLIMMgr::getInstance()->addP2PSession() here. The first incoming message will create the panel. + // We don't need to call LLIMMgr::getInstance()->addP2PSession() here. The first incoming message will create the panel. } if (session->mVoiceInvitePending) { session->mVoiceInvitePending = false; - LLIMMgr::getInstance()->inviteToSession( session->mIMSessionID, session->mName, @@ -6524,10 +6326,8 @@ void LLVivoxVoiceClient::predAvatarNameResolution(const LLVivoxVoiceClient::sess session->mName, IM_SESSION_P2P_INVITE, LLIMMgr::INVITATION_TYPE_VOICE, - session->mHandle, - session->mSIPURI); + session->getVoiceChannelInfo()); } - } } @@ -7068,7 +6868,7 @@ void LLVivoxVoiceClient::onClickVoiceEffect(const std::string& voice_effect_name } } -// it updates VoiceMorphing menu items in accordance with purchased properties +// it updates VoiceMorphing menu items in accordance with purchased properties void LLVivoxVoiceClient::updateVoiceMorphingMenu() { if (mVoiceFontListDirty) @@ -7309,7 +7109,7 @@ void LLVivoxVoiceClient::captureBufferPlayStopSendMessage() LLVivoxProtocolParser::LLVivoxProtocolParser() { parser = XML_ParserCreate(NULL); - + reset(); } @@ -7342,7 +7142,7 @@ void LLVivoxProtocolParser::reset() applicationString.clear(); } -//virtual +//virtual LLVivoxProtocolParser::~LLVivoxProtocolParser() { if (parser) @@ -7368,38 +7168,38 @@ LLIOPipe::EStatus LLVivoxProtocolParser::process_impl( istr.read(buf, sizeof(buf)); mInput.append(buf, istr.gcount()); } - + // Look for input delimiter(s) in the input buffer. If one is found, send the message to the xml parser. int start = 0; int delim; while((delim = mInput.find("\n\n\n", start)) != std::string::npos) - { - + { + // Reset internal state of the LLVivoxProtocolParser (no effect on the expat parser) reset(); - + XML_ParserReset(parser, NULL); XML_SetElementHandler(parser, ExpatStartTag, ExpatEndTag); XML_SetCharacterDataHandler(parser, ExpatCharHandler); - XML_SetUserData(parser, this); + XML_SetUserData(parser, this); XML_Parse(parser, mInput.data() + start, delim - start, false); - + LL_DEBUGS("VivoxProtocolParser") << "parsing: " << mInput.substr(start, delim - start) << LL_ENDL; start = delim + 3; } - + if(start != 0) mInput = mInput.substr(start); - + LL_DEBUGS("VivoxProtocolParser") << "at end, mInput is: " << mInput << LL_ENDL; - + if(!LLVivoxVoiceClient::sConnected) { // If voice has been disabled, we just want to close the socket. This does so. LL_INFOS("Voice") << "returning STATUS_STOP" << LL_ENDL; return STATUS_STOP; } - + return STATUS_OK; } @@ -7443,11 +7243,11 @@ void LLVivoxProtocolParser::StartTag(const char *tag, const char **attr) textBuffer.clear(); // only accumulate text if we're not ignoring tags. accumulateText = !ignoringTags; - + if (responseDepth == 0) - { + { isEvent = !stricmp("Event", tag); - + if (!stricmp("Response", tag) || isEvent) { // Grab the attributes @@ -7455,7 +7255,7 @@ void LLVivoxProtocolParser::StartTag(const char *tag, const char **attr) { const char *key = *attr++; const char *value = *attr++; - + if (!stricmp("requestId", key)) { requestId = value; @@ -7481,20 +7281,20 @@ void LLVivoxProtocolParser::StartTag(const char *tag, const char **attr) else { LL_DEBUGS("VivoxProtocolParser") << tag << " (" << responseDepth << ")" << LL_ENDL; - + // Ignore the InputXml stuff so we don't get confused if (!stricmp("InputXml", tag)) { ignoringTags = true; ignoreDepth = responseDepth; accumulateText = false; - + LL_DEBUGS("VivoxProtocolParser") << "starting ignore, ignoreDepth is " << ignoreDepth << LL_ENDL; } else if (!stricmp("CaptureDevices", tag)) { LLVivoxVoiceClient::getInstance()->clearCaptureDevices(); - } + } else if (!stricmp("RenderDevices", tag)) { LLVivoxVoiceClient::getInstance()->clearRenderDevices(); @@ -7506,7 +7306,7 @@ void LLVivoxProtocolParser::StartTag(const char *tag, const char **attr) else if (!stricmp("RenderDevice", tag)) { deviceString.clear(); - } + } else if (!stricmp("SessionFont", tag)) { id = 0; @@ -7541,9 +7341,9 @@ void LLVivoxProtocolParser::StartTag(const char *tag, const char **attr) void LLVivoxProtocolParser::EndTag(const char *tag) { const std::string& string = textBuffer; - + responseDepth--; - + if (ignoringTags) { if (ignoreDepth == responseDepth) @@ -7556,11 +7356,11 @@ void LLVivoxProtocolParser::EndTag(const char *tag) LL_DEBUGS("VivoxProtocolParser") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL; } } - + if (!ignoringTags) { LL_DEBUGS("VivoxProtocolParser") << "processing tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL; - + // Closing a tag. Finalize the text we've accumulated and reset if (!stricmp("ReturnCode", tag)) returnCode = strtol(string.c_str(), NULL, 10); @@ -7615,7 +7415,7 @@ void LLVivoxProtocolParser::EndTag(const char *tag) else if (!stricmp("DisplayName", tag)) displayNameString = string; else if (!stricmp("Device", tag)) - deviceString = string; + deviceString = string; else if (!stricmp("AccountName", tag)) nameString = string; else if (!stricmp("ParticipantType", tag)) @@ -7713,7 +7513,7 @@ void LLVivoxProtocolParser::EndTag(const char *tag) textBuffer.clear(); accumulateText= false; - + if (responseDepth == 0) { // We finished all of the XML, process the data @@ -7730,7 +7530,7 @@ void LLVivoxProtocolParser::CharData(const char *buffer, int length) This method is called for anything that isn't a tag, which can be text you want that lies between tags, and a lot of stuff you don't want like file formatting (tabs, spaces, CR/LF, etc). - + Only copy text if we are in accumulate mode... */ if (accumulateText) @@ -7757,14 +7557,14 @@ LLDate LLVivoxProtocolParser::expiryTimeStampToLLDate(const std::string& vivox_t void LLVivoxProtocolParser::processResponse(std::string tag) { LL_DEBUGS("VivoxProtocolParser") << tag << LL_ENDL; - + // SLIM SDK: the SDK now returns a statusCode of "200" (OK) for success. This is a change vs. previous SDKs. // According to Mike S., "The actual API convention is that responses with return codes of 0 are successful, regardless of the status code returned", // so I believe this will give correct behavior. - + if(returnCode == 0) statusCode = 0; - + if (isEvent) { const char *eventTypeCstr = eventTypeString.c_str(); @@ -7832,7 +7632,7 @@ void LLVivoxProtocolParser::processResponse(std::string tag) } else if (!stricmp(eventTypeCstr, "ParticipantAddedEvent")) { - /* + /* <Event type="ParticipantAddedEvent"> <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg4</SessionGroupHandle> <SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==4</SessionHandle> @@ -7864,12 +7664,12 @@ void LLVivoxProtocolParser::processResponse(std::string tag) // These are really spammy in tuning mode LLVivoxVoiceClient::getInstance()->auxAudioPropertiesEvent(energy); } - else if (!stricmp(eventTypeCstr, "MessageEvent")) + else if (!stricmp(eventTypeCstr, "MessageEvent")) { //TODO: This probably is not received any more, it was used to support SLim clients LLVivoxVoiceClient::getInstance()->messageEvent(sessionHandle, uriString, alias, messageHeader, messageBody, applicationString); } - else if (!stricmp(eventTypeCstr, "SessionNotificationEvent")) + else if (!stricmp(eventTypeCstr, "SessionNotificationEvent")) { //TODO: This probably is not received any more, it was used to support SLim clients LLVivoxVoiceClient::getInstance()->sessionNotificationEvent(sessionHandle, uriString, notificationType); @@ -7939,23 +7739,23 @@ void LLVivoxProtocolParser::processResponse(std::string tag) } else if (!stricmp(actionCstr, "Session.Create.1")) { - LLVivoxVoiceClient::getInstance()->sessionCreateResponse(requestId, statusCode, statusString, sessionHandle); + LLVivoxVoiceClient::getInstance()->sessionCreateResponse(requestId, statusCode, statusString, sessionHandle); } else if (!stricmp(actionCstr, "SessionGroup.AddSession.1")) { - LLVivoxVoiceClient::getInstance()->sessionGroupAddSessionResponse(requestId, statusCode, statusString, sessionHandle); + LLVivoxVoiceClient::getInstance()->sessionGroupAddSessionResponse(requestId, statusCode, statusString, sessionHandle); } else if (!stricmp(actionCstr, "Session.Connect.1")) { - LLVivoxVoiceClient::getInstance()->sessionConnectResponse(requestId, statusCode, statusString); + LLVivoxVoiceClient::getInstance()->sessionConnectResponse(requestId, statusCode, statusString); } else if (!stricmp(actionCstr, "Account.Logout.1")) { - LLVivoxVoiceClient::getInstance()->logoutResponse(statusCode, statusString); + LLVivoxVoiceClient::getInstance()->logoutResponse(statusCode, statusString); } else if (!stricmp(actionCstr, "Connector.InitiateShutdown.1")) { - LLVivoxVoiceClient::getInstance()->connectorShutdownResponse(statusCode, statusString); + LLVivoxVoiceClient::getInstance()->connectorShutdownResponse(statusCode, statusString); } else if (!stricmp(actionCstr, "Account.GetSessionFonts.1")) { @@ -7984,75 +7784,75 @@ void LLVivoxProtocolParser::processResponse(std::string tag) } else if (!stricmp(actionCstr, "Connector.AccountCreate.1")) { - + } else if (!stricmp(actionCstr, "Connector.MuteLocalMic.1")) { - + } else if (!stricmp(actionCstr, "Connector.MuteLocalSpeaker.1")) { - + } else if (!stricmp(actionCstr, "Connector.SetLocalMicVolume.1")) { - + } else if (!stricmp(actionCstr, "Connector.SetLocalSpeakerVolume.1")) { - + } else if (!stricmp(actionCstr, "Session.ListenerSetPosition.1")) { - + } else if (!stricmp(actionCstr, "Session.SpeakerSetPosition.1")) { - + } else if (!stricmp(actionCstr, "Session.AudioSourceSetPosition.1")) { - + } else if (!stricmp(actionCstr, "Session.GetChannelParticipants.1")) { - + } else if (!stricmp(actionCstr, "Account.ChannelCreate.1")) { - + } else if (!stricmp(actionCstr, "Account.ChannelUpdate.1")) { - + } else if (!stricmp(actionCstr, "Account.ChannelDelete.1")) { - + } else if (!stricmp(actionCstr, "Account.ChannelCreateAndInvite.1")) { - + } else if (!stricmp(actionCstr, "Account.ChannelFolderCreate.1")) { - + } else if (!stricmp(actionCstr, "Account.ChannelFolderUpdate.1")) { - + } else if (!stricmp(actionCstr, "Account.ChannelFolderDelete.1")) { - + } else if (!stricmp(actionCstr, "Account.ChannelAddModerator.1")) { - + } else if (!stricmp(actionCstr, "Account.ChannelDeleteModerator.1")) { - + } */ } @@ -8070,7 +7870,7 @@ LLVivoxSecurity::LLVivoxSecurity() random_value[b] = ll_rand() & 0xff; } mConnectorHandle = LLBase64::encode(random_value, VIVOX_TOKEN_BYTES); - + for (int b = 0; b < VIVOX_TOKEN_BYTES; b++) { random_value[b] = ll_rand() & 0xff; @@ -8081,3 +7881,7 @@ LLVivoxSecurity::LLVivoxSecurity() LLVivoxSecurity::~LLVivoxSecurity() { } + +bool LLVivoxVoiceP2PIncomingCall::answerInvite() { return LLVivoxVoiceClient::getInstance()->answerInvite(mCallInfo["session_handle"]); } + +void LLVivoxVoiceP2PIncomingCall::declineInvite() { LLVivoxVoiceClient::getInstance()->declineInvite(mCallInfo["session_handle"]); } diff --git a/indra/newview/llvoicevivox.h b/indra/newview/llvoicevivox.h index ae2aec0e9c..6b40ad0cf6 100644 --- a/indra/newview/llvoicevivox.h +++ b/indra/newview/llvoicevivox.h @@ -1,25 +1,25 @@ -/** +/** * @file llvoicevivox.h * @brief Declaration of LLDiamondwareVoiceClient class which is the interface to the voice client process. * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ @@ -51,12 +51,27 @@ class LLVivoxProtocolParser; class LLAvatarName; class LLVivoxVoiceClientMuteListObserver; +extern const std::string VIVOX_VOICE_SERVER_TYPE; + +class LLVivoxVoiceP2PIncomingCall : public LLVoiceP2PIncomingCallInterface +{ + public: + LLVivoxVoiceP2PIncomingCall(const LLSD& call_info) : mCallInfo(call_info) {} + ~LLVivoxVoiceP2PIncomingCall() override {} + + bool answerInvite() override; + void declineInvite() override; + + protected: + LLSD mCallInfo; +}; class LLVivoxVoiceClient : public LLSingleton<LLVivoxVoiceClient>, virtual public LLVoiceModuleInterface, - virtual public LLVoiceEffectInterface + virtual public LLVoiceEffectInterface, + virtual public LLVoiceP2POutgoingCallInterface { - LLSINGLETON(LLVivoxVoiceClient); + LLSINGLETON_C11(LLVivoxVoiceClient); LOG_CLASS(LLVivoxVoiceClient); virtual ~LLVivoxVoiceClient(); @@ -64,149 +79,149 @@ public: /// @name LLVoiceModuleInterface virtual implementations /// @see LLVoiceModuleInterface //@{ - virtual void init(LLPumpIO *pump) override; // Call this once at application startup (creates connector) - virtual void terminate() override; // Call this to clean up during shutdown - - virtual const LLVoiceVersionInfo& getVersion() override; - - virtual void updateSettings() override; // call after loading settings and whenever they change + void init(LLPumpIO *pump) override; // Call this once at application startup (creates connector) + void terminate() override; // Call this to clean up during shutdown + + const LLVoiceVersionInfo& getVersion() override; + + void updateSettings() override; // call after loading settings and whenever they change - // Returns true if vivox has successfully logged in and is not in error state - virtual bool isVoiceWorking() const override; + // Returns true if vivox has successfully logged in and is not in error state + bool isVoiceWorking() const override; ///////////////////// /// @name Tuning //@{ - virtual void tuningStart() override; - virtual void tuningStop() override; - virtual bool inTuningMode() override; - - virtual void tuningSetMicVolume(float volume) override; - virtual void tuningSetSpeakerVolume(float volume) override; - virtual float tuningGetEnergy(void) override; + void tuningStart() override; + void tuningStop() override; + bool inTuningMode() override; + + void tuningSetMicVolume(float volume) override; + void tuningSetSpeakerVolume(float volume) override; + float tuningGetEnergy(void) override; + //@} - + ///////////////////// /// @name Devices //@{ // This returns true when it's safe to bring up the "device settings" dialog in the prefs. // i.e. when the daemon is running and connected, and the device lists are populated. - virtual bool deviceSettingsAvailable() override; - virtual bool deviceSettingsUpdated() override; //return if the list has been updated and never fetched, only to be called from the voicepanel. - + bool deviceSettingsAvailable() override; + bool deviceSettingsUpdated() override; //return if the list has been updated and never fetched, only to be called from the voicepanel. + // Requery the vivox daemon for the current list of input/output devices. // If you pass true for clearCurrentList, deviceSettingsAvailable() will be false until the query has completed // (use this if you want to know when it's done). // If you pass false, you'll have no way to know when the query finishes, but the device lists will not appear empty in the interim. - virtual void refreshDeviceLists(bool clearCurrentList = true) override; - - virtual void setCaptureDevice(const std::string& name) override; - virtual void setRenderDevice(const std::string& name) override; - - virtual LLVoiceDeviceList& getCaptureDevices() override; - virtual LLVoiceDeviceList& getRenderDevices() override; - //@} - - virtual void getParticipantList(std::set<LLUUID> &participants) override; - virtual bool isParticipant(const LLUUID& speaker_id) override; + void refreshDeviceLists(bool clearCurrentList = true) override; + + void setCaptureDevice(const std::string& name) override; + void setRenderDevice(const std::string& name) override; + + LLVoiceDeviceList& getCaptureDevices() override; + LLVoiceDeviceList& getRenderDevices() override; + //@} + + void getParticipantList(std::set<LLUUID> &participants) override; + bool isParticipant(const LLUUID& speaker_id) override; // Send a text message to the specified user, initiating the session if necessary. // virtual BOOL sendTextMessage(const LLUUID& participant_id, const std::string& message) const {return false;}; - - // close any existing text IM session with the specified user - virtual void endUserIMSession(const LLUUID &uuid) override; // Returns true if calling back the session URI after the session has closed is possible. - // Currently this will be false only for PSTN P2P calls. - // NOTE: this will return true if the session can't be found. - virtual BOOL isSessionCallBackPossible(const LLUUID &session_id) override; - + // Currently this will be false only for PSTN P2P calls. + // NOTE: this will return true if the session can't be found. + BOOL isSessionCallBackPossible(const LLUUID &session_id) override; + // Returns true if the session can accepte text IM's. // Currently this will be false only for PSTN P2P calls. - // NOTE: this will return true if the session can't be found. - virtual BOOL isSessionTextIMPossible(const LLUUID &session_id) override; - - + // NOTE: this will return true if the session can't be found. + BOOL isSessionTextIMPossible(const LLUUID &session_id) override; + //////////////////////////// /// @name Channel stuff //@{ // returns true iff the user is currently in a proximal (local spatial) channel. // Note that gestures should only fire if this returns true. - virtual bool inProximalChannel() override; - - virtual void setNonSpatialChannel(const std::string &uri, - const std::string &credentials) override; - - virtual bool setSpatialChannel(const std::string &uri, - const std::string &credentials) override; - - virtual void leaveNonSpatialChannel() override; - - virtual void leaveChannel(void) override; - - // Returns the URI of the current channel, or an empty string if not currently in a channel. - // NOTE that it will return an empty string if it's in the process of joining a channel. - virtual std::string getCurrentChannel() override; + bool inProximalChannel() override; + + void setNonSpatialChannel(const LLSD& channelInfo, + bool notify_on_first_join, + bool hangup_on_last_leave) override; + + bool setSpatialChannel(const LLSD& channelInfo) override; + + void leaveNonSpatialChannel() override; + + void processChannels(bool process) override; + + void leaveChannel(void); + + bool isCurrentChannel(const LLSD &channelInfo) override; + bool compareChannels(const LLSD &channelInfo1, const LLSD &channelInfo2) override; + //@} - - + + ////////////////////////// - /// @name invitations + /// @name LLVoiceP2POutgoingCallInterface //@{ // start a voice channel with the specified user - virtual void callUser(const LLUUID &uuid) override; - virtual bool isValidChannel(std::string &channelHandle) override; - virtual bool answerInvite(std::string &channelHandle) override; - virtual void declineInvite(std::string &channelHandle) override; + void callUser(const LLUUID &uuid) override; + void hangup() override; + //@} - + + LLVoiceP2POutgoingCallInterface *getOutgoingCallInterface() override { return this; } + + LLVoiceP2PIncomingCallInterfacePtr getIncomingCallInterface(const LLSD &voice_call_info) override; + + bool answerInvite(const std::string &sessionHandle); + void declineInvite(const std::string &sessionHandle); + ///////////////////////// /// @name Volume/gain //@{ - virtual void setVoiceVolume(F32 volume) override; - virtual void setMicGain(F32 volume) override; + void setVoiceVolume(F32 volume) override; + void setMicGain(F32 volume) override; //@} - + ///////////////////////// /// @name enable disable voice and features //@{ - virtual bool voiceEnabled() override; - virtual void setVoiceEnabled(bool enabled) override; - virtual BOOL lipSyncEnabled() override; - virtual void setLipSyncEnabled(BOOL enabled) override; - virtual void setMuteMic(bool muted) override; // Set the mute state of the local mic. + void setVoiceEnabled(bool enabled) override; + void setMuteMic(bool muted) override; // Set the mute state of the local mic. //@} - + ////////////////////////// /// @name nearby speaker accessors //@{ - virtual BOOL getVoiceEnabled(const LLUUID& id) override; // true if we've received data for this avatar - virtual std::string getDisplayName(const LLUUID& id) override; - virtual BOOL isParticipantAvatar(const LLUUID &id) override; - virtual BOOL getIsSpeaking(const LLUUID& id) override; - virtual BOOL getIsModeratorMuted(const LLUUID& id) override; - virtual F32 getCurrentPower(const LLUUID& id) override; // "power" is related to "amplitude" in a defined way. I'm just not sure what the formula is... - virtual BOOL getOnMuteList(const LLUUID& id) override; - virtual F32 getUserVolume(const LLUUID& id) override; - virtual void setUserVolume(const LLUUID& id, F32 volume) override; // set's volume for specified agent, from 0-1 (where .5 is nominal) + std::string getDisplayName(const LLUUID& id) override; + BOOL isParticipantAvatar(const LLUUID &id) override; + BOOL getIsSpeaking(const LLUUID& id) override; + BOOL getIsModeratorMuted(const LLUUID& id) override; + F32 getCurrentPower(const LLUUID& id) override; // "power" is related to "amplitude" in a defined way. I'm just not sure what the formula is... + F32 getUserVolume(const LLUUID& id) override; + void setUserVolume(const LLUUID& id, F32 volume) override; // set's volume for specified agent, from 0-1 (where .5 is nominal) //@} - + // authorize the user - virtual void userAuthorized(const std::string& user_id, - const LLUUID &agentID) override; - + void userAuthorized(const std::string& user_id, + const LLUUID &agentID) override; + ////////////////////////////// /// @name Status notification //@{ - virtual void addObserver(LLVoiceClientStatusObserver* observer) override; - virtual void removeObserver(LLVoiceClientStatusObserver* observer) override; - virtual void addObserver(LLFriendObserver* observer) override; - virtual void removeObserver(LLFriendObserver* observer) override; - virtual void addObserver(LLVoiceClientParticipantObserver* observer) override; - virtual void removeObserver(LLVoiceClientParticipantObserver* observer) override; + void addObserver(LLVoiceClientStatusObserver* observer) override; + void removeObserver(LLVoiceClientStatusObserver* observer) override; + void addObserver(LLFriendObserver* observer) override; + void removeObserver(LLFriendObserver* observer) override; + void addObserver(LLVoiceClientParticipantObserver* observer) override; + void removeObserver(LLVoiceClientParticipantObserver* observer) override; //@} - - virtual std::string sipURIFromID(const LLUUID &id) override; + + std::string sipURIFromID(const LLUUID &id) override; //@} /// @name LLVoiceEffectInterface virtual implementations @@ -216,32 +231,32 @@ public: ////////////////////////// /// @name Accessors //@{ - virtual bool setVoiceEffect(const LLUUID& id) override; - virtual const LLUUID getVoiceEffect() override; - virtual LLSD getVoiceEffectProperties(const LLUUID& id) override; + bool setVoiceEffect(const LLUUID& id) override; + const LLUUID getVoiceEffect() override; + LLSD getVoiceEffectProperties(const LLUUID& id) override; - virtual void refreshVoiceEffectLists(bool clear_lists) override; - virtual const voice_effect_list_t& getVoiceEffectList() const override; - virtual const voice_effect_list_t& getVoiceEffectTemplateList() const override; + void refreshVoiceEffectLists(bool clear_lists) override; + const voice_effect_list_t& getVoiceEffectList() const override; + const voice_effect_list_t& getVoiceEffectTemplateList() const override; //@} ////////////////////////////// /// @name Status notification //@{ - virtual void addObserver(LLVoiceEffectObserver* observer) override; - virtual void removeObserver(LLVoiceEffectObserver* observer) override; + void addObserver(LLVoiceEffectObserver* observer) override; + void removeObserver(LLVoiceEffectObserver* observer) override; //@} ////////////////////////////// /// @name Effect preview buffer //@{ - virtual void enablePreviewBuffer(bool enable) override; - virtual void recordPreviewBuffer() override; - virtual void playPreviewBuffer(const LLUUID& effect_id = LLUUID::null) override; - virtual void stopPreviewBuffer() override; + void enablePreviewBuffer(bool enable) override; + void recordPreviewBuffer() override; + void playPreviewBuffer(const LLUUID& effect_id = LLUUID::null) override; + void stopPreviewBuffer() override; - virtual bool isPreviewRecording() override; - virtual bool isPreviewPlaying() override; + bool isPreviewRecording() override; + bool isPreviewPlaying() override; //@} //@} @@ -251,12 +266,12 @@ public: protected: ////////////////////// - // Vivox Specific definitions - + // Vivox Specific definitions + friend class LLVivoxVoiceClientMuteListObserver; - friend class LLVivoxVoiceClientFriendsObserver; + friend class LLVivoxVoiceClientFriendsObserver; + - enum streamState { streamStateUnknown = 0, @@ -265,16 +280,16 @@ protected: streamStateRinging = 3, streamStateConnecting = 6, // same as Vivox session_media_connecting enum streamStateDisconnecting = 7, //Same as Vivox session_media_disconnecting enum - }; + }; struct participantState { public: participantState(const std::string &uri); - + bool updateMuteState(); // true if mute state has changed bool isAvatar(); - + std::string mURI; LLUUID mAvatarID; std::string mAccountName; @@ -299,7 +314,7 @@ protected: typedef std::map<const std::string, participantStatePtr_t> participantMap; typedef std::map<const LLUUID, participantStatePtr_t> participantUUIDMap; - + struct sessionState { public: @@ -310,7 +325,9 @@ protected: static ptr_t createSession(); ~sessionState(); - + + LLSD getVoiceChannelInfo(); + participantStatePtr_t addParticipant(const std::string &uri); void removeParticipant(const participantStatePtr_t &participant); void removeAllParticipants(); @@ -325,7 +342,8 @@ protected: bool isCallBackPossible(); bool isTextIMPossible(); - + bool isSpatial() { return mIsSpatial; } + static void for_each(sessionFunc_t func); std::string mHandle; @@ -337,7 +355,7 @@ protected: std::string mHash; // Channel password std::string mErrorStatusString; std::queue<std::string> mTextMsgQueue; - + LLUUID mIMSessionID; LLUUID mCallerID; int mErrorStatusCode; @@ -384,7 +402,7 @@ protected: typedef boost::shared_ptr<sessionState> sessionStatePtr_t; typedef std::map<std::string, sessionStatePtr_t> sessionMap; - + /////////////////////////////////////////////////////// // Private Member Functions ////////////////////////////////////////////////////// @@ -396,17 +414,17 @@ protected: //@{ // 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 giveUp(); + // write to the tvc bool writeString(const std::string &str); - + void connectorCreate(); - void connectorShutdown(); - void closeSocket(void); - + void connectorShutdown(); + void closeSocket(void); + // void requestVoiceAccountProvision(S32 retries = 3); void setLoginInfo( const std::string& account_name, @@ -415,14 +433,14 @@ protected: const std::string& voice_account_server_uri); void loginSendMessage(); void logout(); - void logoutSendMessage(); - - + void logoutSendMessage(); + + //@} - + //------------------------------------ // tuning - + void tuningRenderStartSendMessage(const std::string& name, bool loop); void tuningRenderStopSendMessage(); @@ -435,12 +453,12 @@ protected: void addCaptureDevice(const LLVoiceDevice& device); void clearRenderDevices(); void setDevicesListUpdated(bool state); - void addRenderDevice(const LLVoiceDevice& device); + void addRenderDevice(const LLVoiceDevice& device); void buildSetAudioDevices(std::ostringstream &stream); - + void getCaptureDevicesSendMessage(); void getRenderDevicesSendMessage(); - + // local audio updates, mic mute, speaker mute, mic volume and speaker volumes void sendLocalAudioUpdates(); @@ -467,9 +485,9 @@ protected: void auxAudioPropertiesEvent(F32 energy); void messageEvent(std::string &sessionHandle, std::string &uriString, std::string &alias, std::string &messageHeader, std::string &messageBody, std::string &applicationString); void sessionNotificationEvent(std::string &sessionHandle, std::string &uriString, std::string ¬ificationType); - + void muteListChanged(); - + ///////////////////////////// // VAD changes // disable auto-VAD and configure VAD parameters explicitly @@ -485,7 +503,7 @@ protected: void setEarLocation(S32 loc); - + ///////////////////////////// // Accessors for data related to nearby speakers @@ -494,30 +512,25 @@ protected: std::string getGroupID(const LLUUID& id); // group ID if the user is in group chat (empty string if not applicable) ///////////////////////////// - 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. - - - ///////////////////////////// // Recording controls void recordingLoopStart(int seconds = 3600, int deltaFramesPerControlFrame = 200); void recordingLoopSave(const std::string& filename); void recordingStop(); - + // Playback controls void filePlaybackStart(const std::string& filename); void filePlaybackStop(); void filePlaybackSetPaused(bool paused); void filePlaybackSetMode(bool vox = false, float speed = 1.0f); - + participantStatePtr_t findParticipantByID(const LLUUID& id); - + #if 0 //////////////////////////////////////// // voice sessions. typedef std::set<sessionStatePtr_t> sessionSet; - + typedef sessionSet::iterator sessionIterator; sessionIterator sessionsBegin(void); sessionIterator sessionsEnd(void); @@ -526,7 +539,7 @@ protected: sessionStatePtr_t findSession(const std::string &handle); sessionStatePtr_t findSessionBeingCreatedByURI(const std::string &uri); 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); @@ -542,11 +555,11 @@ 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); - - + + ////////////////////////////////////// // buddy list stuff, needed for SLIM later struct buddyListEntry @@ -566,13 +579,13 @@ protected: }; typedef std::map<std::string, buddyListEntry*> buddyListMap; - + ///////////////////////////// // session control messages void accountListBlockRulesSendMessage(); void accountListAutoAcceptRulesSendMessage(); - + void sessionGroupCreateSendMessage(); void sessionCreateSendMessage(const sessionStatePtr_t &session, bool startAudio = true, bool startText = false); void sessionGroupAddSessionSendMessage(const sessionStatePtr_t &session, bool startAudio = true, bool startText = false); @@ -583,20 +596,20 @@ protected: void sessionMediaDisconnectSendMessage(const sessionStatePtr_t &session); // void sessionTextDisconnectSendMessage(sessionState *session); - - + + // Pokes the state machine to leave the audio session next time around. - void sessionTerminate(); - + void sessionTerminate(); + // Pokes the state machine to shut down the connector and restart it. void requestRelog(); - + // Does the actual work to get out of the audio session void leaveAudioSession(); - + friend class LLVivoxVoiceClientCapResponder; - - + + void lookupName(const LLUUID &id); void onAvatarNameCache(const LLUUID& id, const LLAvatarName& av_name); void avatarNameResolved(const LLUUID &id, const std::string &name); @@ -616,10 +629,10 @@ protected: const S32 font_status, const bool template_font = false); void accountGetSessionFontsResponse(int statusCode, const std::string &statusString); - void accountGetTemplateFontsResponse(int statusCode, const std::string &statusString); + void accountGetTemplateFontsResponse(int statusCode, const std::string &statusString); private: - + LLVoiceVersionInfo mVoiceVersion; // Coroutine support methods @@ -661,21 +674,21 @@ private: // The larger it is the greater is possibility there is a problem with connection to voice server. // Introduced while fixing EXT-4313. 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 + + // We should kill the voice daemon in case of connection alert bool mTerminateDaemon; - + friend class LLVivoxProtocolParser; - + std::string mAccountName; std::string mAccountPassword; std::string mAccountDisplayName; - + bool mTuningMode; float mTuningEnergy; std::string mTuningAudioFile; @@ -685,14 +698,13 @@ private: 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; + + std::string mChannelName; // Name of the channel to be looked up 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. @@ -700,27 +712,27 @@ private: 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 + bool mAccountLoggedIn; // set by login message int mNumberOfAliases; U32 mCommandCookie; std::string mVoiceAccountServerURI; std::string mVoiceSIPURIHostName; - + 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; - + LLVoiceDeviceList mCaptureDevices; LLVoiceDeviceList mRenderDevices; @@ -731,32 +743,30 @@ private: bool mIsInitialized; 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 = ""); void joinSession(const sessionStatePtr_t &session); - - std::string nameFromAvatar(LLVOAvatar *avatar); + std::string nameFromID(const LLUUID &id); bool IDFromName(const std::string name, LLUUID &uuid); - std::string displayNameFromAvatar(LLVOAvatar *avatar); std::string sipURIFromAvatar(LLVOAvatar *avatar); std::string sipURIFromName(std::string &name); - + // Returns the name portion of the SIP URI if the string looks vaguely like a SIP URI, or an empty string if not. - std::string nameFromsipURI(const std::string &uri); + std::string nameFromsipURI(const std::string &uri); bool inSpatialChannel(void); - std::string getAudioSessionURI(); + LLSD getAudioSessionChannelInfo(); std::string getAudioSessionHandle(); - + void setHidden(bool hidden) override; //virtual void sendPositionAndVolumeUpdate(void); - + void sendCaptureAndRenderDevices(); void buildSetCaptureDevice(std::ostringstream &stream); void buildSetRenderDevice(std::ostringstream &stream); - + void sendFriendsListUpdates(); @@ -767,9 +777,9 @@ private: #endif void enforceTether(void); - + bool mSpatialCoordsDirty; - + LLVector3d mCameraPosition; LLVector3d mCameraRequestedPosition; LLVector3 mCameraVelocity; @@ -778,36 +788,35 @@ private: LLVector3d mAvatarPosition; LLVector3 mAvatarVelocity; LLQuaternion mAvatarRot; - + bool mMuteMic; bool mMuteMicDirty; bool mHidden; //Set to true during teleport to hide the agent's position. - + // Set to true when the friends list is known to have changed. bool mFriendsListDirty; - + enum { earLocCamera = 0, // ear at camera earLocAvatar, // ear at avatar earLocMixed // ear at avatar location/camera direction }; - - S32 mEarLocation; - + + S32 mEarLocation; + bool mSpeakerVolumeDirty; bool mSpeakerMuteDirty; int mSpeakerVolume; int mMicVolume; bool mMicVolumeDirty; - + bool mVoiceEnabled; + bool mProcessChannels; bool mWriteInProgress; std::string mWriteString; size_t mWriteOffset; - - BOOL mLipSyncEnabled; typedef std::set<LLVoiceClientParticipantObserver*> observer_set_t; observer_set_t mParticipantObservers; @@ -816,7 +825,7 @@ private: typedef std::set<LLVoiceClientStatusObserver*> status_observer_set_t; status_observer_set_t mStatusObservers; - + void notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status); typedef std::set<LLFriendObserver*> friend_observer_set_t; @@ -923,7 +932,7 @@ private: }; -/** +/** * @class LLVivoxProtocolParser * @brief This class helps construct new LLIOPipe specializations * @see LLIOPipe @@ -936,12 +945,12 @@ class LLVivoxProtocolParser : public LLIOPipe public: LLVivoxProtocolParser(); virtual ~LLVivoxProtocolParser(); - + protected: /* @name LLIOPipe virtual implementations */ //@{ - /** + /** * @brief Process the data in buffer */ virtual EStatus process_impl( @@ -951,16 +960,16 @@ protected: LLSD& context, LLPumpIO* pump); //@} - + std::string mInput; - + // Expat control members XML_Parser parser; int responseDepth; bool ignoringTags; bool isEvent; int ignoreDepth; - + // Members for processing responses. The values are transient and only valid within a call to processResponse(). int returnCode; int statusCode; @@ -975,7 +984,7 @@ protected: std::string sessionGroupHandle; std::string alias; std::string applicationString; - + // Members for processing events. The values are transient and only valid within a call to processResponse(). std::string eventTypeString; int state; @@ -1014,19 +1023,19 @@ protected: S32 fontType; S32 fontStatus; std::string mediaCompletionType; - + // Members for processing text between tags std::string textBuffer; bool accumulateText; - + void reset(); - + void processResponse(std::string tag); - + static void XMLCALL ExpatStartTag(void *data, const char *el, const char **attr); static void XMLCALL ExpatEndTag(void *data, const char *el); static void XMLCALL ExpatCharHandler(void *data, const XML_Char *s, int len); - + void StartTag(const char *tag, const char **attr); void EndTag(const char *tag); void CharData(const char *buffer, int length); @@ -1053,7 +1062,7 @@ class LLVoiceVivoxStats : public LLSingleton<LLVoiceVivoxStats> LLSINGLETON(LLVoiceVivoxStats); LOG_CLASS(LLVoiceVivoxStats); virtual ~LLVoiceVivoxStats(); - + private: F64SecondsImplicit mStartTime; @@ -1061,7 +1070,7 @@ class LLVoiceVivoxStats : public LLSingleton<LLVoiceVivoxStats> F64 mConnectTime; U32 mConnectAttempts; - + F64 mProvisionTime; U32 mProvisionAttempts; diff --git a/indra/newview/llvoicewebrtc.cpp b/indra/newview/llvoicewebrtc.cpp new file mode 100644 index 0000000000..02917d2135 --- /dev/null +++ b/indra/newview/llvoicewebrtc.cpp @@ -0,0 +1,2878 @@ + /** + * @file LLWebRTCVoiceClient.cpp + * @brief Implementation of LLWebRTCVoiceClient class which is the interface to the voice client process. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ +#include <algorithm> +#include "llvoicewebrtc.h" + +#include "llsdutil.h" + +// Linden library includes +#include "llavatarnamecache.h" +#include "llvoavatarself.h" +#include "llbufferstream.h" +#include "llfile.h" +#include "llmenugl.h" +#ifdef LL_USESYSTEMLIBS +# include "expat.h" +#else +# include "expat/expat.h" +#endif +#include "llcallbacklist.h" +#include "llviewernetwork.h" // for gGridChoice +#include "llbase64.h" +#include "llviewercontrol.h" +#include "llappviewer.h" // for gDisconnected, gDisableVoice +#include "llprocess.h" + +// Viewer includes +#include "llmutelist.h" // to check for muted avatars +#include "llagent.h" +#include "llcachename.h" +#include "llimview.h" // for LLIMMgr +#include "llworld.h" +#include "llparcel.h" +#include "llviewerparcelmgr.h" +#include "llfirstuse.h" +#include "llspeakers.h" +#include "lltrans.h" +#include "llrand.h" +#include "llviewerwindow.h" +#include "llviewercamera.h" +#include "llversioninfo.h" + +#include "llviewernetwork.h" +#include "llnotificationsutil.h" + +#include "llcorehttputil.h" +#include "lleventfilter.h" + +#include "stringize.h" + +#include "llwebrtc.h" + +// for base64 decoding +#include "apr_base64.h" + +#include "json/reader.h" +#include "json/writer.h" + +const std::string WEBRTC_VOICE_SERVER_TYPE = "webrtc"; + +namespace { + + const F32 MAX_AUDIO_DIST = 50.0f; + const F32 VOLUME_SCALE_WEBRTC = 0.01f; + const F32 LEVEL_SCALE_WEBRTC = 0.008f; + + const F32 SPEAKING_AUDIO_LEVEL = 0.40; + + static const std::string REPORTED_VOICE_SERVER_TYPE = "Secondlife WebRTC Gateway"; + + // Don't send positional updates more frequently than this: + const F32 UPDATE_THROTTLE_SECONDS = 0.1f; + + // Cosine of a "trivially" small angle + const F32 FOUR_DEGREES = 4.0f * (F_PI / 180.0f); + const F32 MINUSCULE_ANGLE_COS = (F32) cos(0.5f * FOUR_DEGREES); + +} // namespace + + +/////////////////////////////////////////////////////////////////////////////////////////////// + +void LLVoiceWebRTCStats::reset() +{ + mStartTime = -1.0f; + mConnectCycles = 0; + mConnectTime = -1.0f; + mConnectAttempts = 0; + mProvisionTime = -1.0f; + mProvisionAttempts = 0; + mEstablishTime = -1.0f; + mEstablishAttempts = 0; +} + +LLVoiceWebRTCStats::LLVoiceWebRTCStats() +{ + reset(); +} + +LLVoiceWebRTCStats::~LLVoiceWebRTCStats() +{ +} + +void LLVoiceWebRTCStats::connectionAttemptStart() +{ + if (!mConnectAttempts) + { + mStartTime = LLTimer::getTotalTime(); + mConnectCycles++; + } + mConnectAttempts++; +} + +void LLVoiceWebRTCStats::connectionAttemptEnd(bool success) +{ + if ( success ) + { + mConnectTime = (LLTimer::getTotalTime() - mStartTime) / USEC_PER_SEC; + } +} + +void LLVoiceWebRTCStats::provisionAttemptStart() +{ + if (!mProvisionAttempts) + { + mStartTime = LLTimer::getTotalTime(); + } + mProvisionAttempts++; +} + +void LLVoiceWebRTCStats::provisionAttemptEnd(bool success) +{ + if ( success ) + { + mProvisionTime = (LLTimer::getTotalTime() - mStartTime) / USEC_PER_SEC; + } +} + +void LLVoiceWebRTCStats::establishAttemptStart() +{ + if (!mEstablishAttempts) + { + mStartTime = LLTimer::getTotalTime(); + } + mEstablishAttempts++; +} + +void LLVoiceWebRTCStats::establishAttemptEnd(bool success) +{ + if ( success ) + { + mEstablishTime = (LLTimer::getTotalTime() - mStartTime) / USEC_PER_SEC; + } +} + +LLSD LLVoiceWebRTCStats::read() +{ + LLSD stats(LLSD::emptyMap()); + + stats["connect_cycles"] = LLSD::Integer(mConnectCycles); + stats["connect_attempts"] = LLSD::Integer(mConnectAttempts); + stats["connect_time"] = LLSD::Real(mConnectTime); + + stats["provision_attempts"] = LLSD::Integer(mProvisionAttempts); + stats["provision_time"] = LLSD::Real(mProvisionTime); + + stats["establish_attempts"] = LLSD::Integer(mEstablishAttempts); + stats["establish_time"] = LLSD::Real(mEstablishTime); + + return stats; +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + +bool LLWebRTCVoiceClient::sShuttingDown = false; + +LLWebRTCVoiceClient::LLWebRTCVoiceClient() : + mHidden(false), + mTuningMode(false), + mTuningMicGain(0.0), + mTuningSpeakerVolume(50), // Set to 50 so the user can hear themselves when he sets his mic volume + mDevicesListUpdated(false), + + mSpatialCoordsDirty(false), + + mMuteMic(false), + + mEarLocation(0), + mMicGain(0.0), + + mVoiceEnabled(false), + mProcessChannels(false), + + mAvatarNameCacheConnection(), + mIsInTuningMode(false), + mIsProcessingChannels(false), + mIsCoroutineActive(false), + mWebRTCPump("WebRTCClientPump"), + mWebRTCDeviceInterface(nullptr) +{ + sShuttingDown = false; + + mSpeakerVolume = 0.0; + + mVoiceVersion.serverVersion = ""; + mVoiceVersion.voiceServerType = REPORTED_VOICE_SERVER_TYPE; + mVoiceVersion.internalVoiceServerType = WEBRTC_VOICE_SERVER_TYPE; + mVoiceVersion.minorVersion = 0; + mVoiceVersion.majorVersion = 2; + mVoiceVersion.mBuildVersion = ""; +} + +//--------------------------------------------------- + +LLWebRTCVoiceClient::~LLWebRTCVoiceClient() +{ + if (mAvatarNameCacheConnection.connected()) + { + mAvatarNameCacheConnection.disconnect(); + } + sShuttingDown = true; +} + +//--------------------------------------------------- + +void LLWebRTCVoiceClient::init(LLPumpIO* pump) +{ + // constructor will set up LLVoiceClient::getInstance() + llwebrtc::init(); + + mWebRTCDeviceInterface = llwebrtc::getDeviceInterface(); + mWebRTCDeviceInterface->setDevicesObserver(this); +} + +void LLWebRTCVoiceClient::terminate() +{ + if (sShuttingDown) + { + return; + } + + mVoiceEnabled = false; + llwebrtc::terminate(); + + sShuttingDown = true; +} + +//--------------------------------------------------- + +void LLWebRTCVoiceClient::cleanUp() +{ + mNextSession.reset(); + mSession.reset(); + mNeighboringRegions.clear(); + sessionState::for_each(boost::bind(predShutdownSession, _1)); + LL_DEBUGS("Voice") << "Exiting" << LL_ENDL; +} + +//--------------------------------------------------- + +const LLVoiceVersionInfo& LLWebRTCVoiceClient::getVersion() +{ + return mVoiceVersion; +} + +//--------------------------------------------------- + +void LLWebRTCVoiceClient::updateSettings() +{ + setVoiceEnabled(LLVoiceClient::getInstance()->voiceEnabled()); + setEarLocation(gSavedSettings.getS32("VoiceEarLocation")); + + std::string inputDevice = gSavedSettings.getString("VoiceInputAudioDevice"); + setCaptureDevice(inputDevice); + std::string outputDevice = gSavedSettings.getString("VoiceOutputAudioDevice"); + setRenderDevice(outputDevice); + F32 mic_level = gSavedSettings.getF32("AudioLevelMic"); + setMicGain(mic_level); +} + +// Observers +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) << " )" + << " mSession=" << mSession << LL_ENDL; + + LL_DEBUGS("Voice") << " " << LLVoiceClientStatusObserver::status2string(status) << ", session channelInfo " + << getAudioSessionChannelInfo() << ", proximal is " << inSpatialChannel() << LL_ENDL; + + mIsProcessingChannels = status == LLVoiceClientStatusObserver::STATUS_JOINED; + + LLSD channelInfo = getAudioSessionChannelInfo(); + for (status_observer_set_t::iterator it = mStatusObservers.begin(); it != mStatusObservers.end();) + { + LLVoiceClientStatusObserver *observer = *it; + observer->onChange(status, channelInfo, inSpatialChannel()); + // In case onError() deleted an entry. + it = mStatusObservers.upper_bound(observer); + } + + // 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) +{ +} + +void LLWebRTCVoiceClient::removeObserver(LLFriendObserver *observer) +{ +} + +//--------------------------------------------------- +// Primary voice loop. +// This voice loop is called every 100ms plus the time it +// takes to process the various functions called in the loop +// The loop does the following: +// * gates whether we do channel processing depending on +// whether we're running a WebRTC voice channel or +// one from another voice provider. +// * If in spatial voice, it determines whether we've changed +// parcels, whether region/parcel voice settings have changed, +// etc. and manages whether the voice channel needs to change. +// * calls the state machines for the sessions to negotiate +// connection to various voice channels. +// * Sends updates to the voice server when this agent's +// voice levels, or positions have changed. +void LLWebRTCVoiceClient::voiceConnectionCoro() +{ + LL_DEBUGS("Voice") << "starting" << LL_ENDL; + mIsCoroutineActive = true; + LLCoros::set_consuming(true); + try + { + LLMuteList::getInstance()->addObserver(this); + while (!sShuttingDown) + { + // TODO: Doing some measurement and calculation here, + // we could reduce the timeout to take into account the + // time spent on the previous loop to have the loop + // cycle at exactly 100ms, instead of 100ms + loop + // execution time. + // Could help with voice updates making for smoother + // voice when we're busy. + llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); + bool voiceEnabled = mVoiceEnabled; + + if (!isAgentAvatarValid()) + { + continue; + } + + LLViewerRegion *regionp = gAgent.getRegion(); + if (!regionp) + { + continue; + } + + if (!mProcessChannels) + { + // we've switched away from webrtc voice, so shut all channels down. + // leave channel can be called again and again without adverse effects. + // it merely tells channels to shut down if they're not already doing so. + leaveChannel(false); + } + else if (inSpatialChannel()) + { + bool useEstateVoice = true; + // add session for region or parcel voice. + if (!regionp || regionp->getRegionID().isNull()) + { + // no region, no voice. + continue; + } + + voiceEnabled = voiceEnabled && regionp->isVoiceEnabled(); + + if (voiceEnabled) + { + LLParcel *parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + // check to see if parcel changed. + if (parcel && parcel->getLocalID() != INVALID_PARCEL_ID) + { + // parcel voice + if (!parcel->getParcelFlagAllowVoice()) + { + voiceEnabled = false; + } + else if (!parcel->getParcelFlagUseEstateVoiceChannel()) + { + // use the parcel-specific voice channel. + S32 parcel_local_id = parcel->getLocalID(); + std::string channelID = regionp->getRegionID().asString() + "-" + std::to_string(parcel->getLocalID()); + + useEstateVoice = false; + if (!inOrJoiningChannel(channelID)) + { + startParcelSession(channelID, parcel_local_id); + } + } + } + if (useEstateVoice && !inEstateChannel()) + { + // estate voice + if (!inEstateChannel()) + { + startEstateSession(); + } + } + } + if (!voiceEnabled) + { + // voice is disabled, so leave and disable PTT + leaveChannel(true); + } + else + { + // we're in spatial voice, and voice is enabled, so determine positions in order + // to send position updates. + updatePosition(); + } + } + + sessionState::processSessionStates(); + if (mProcessChannels && voiceEnabled) + { + sendPositionUpdate(true); + updateOwnVolume(); + } + } + } + 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") << "voiceConnectionStateMachine crashed" << LL_ENDL; + throw; + } + + cleanUp(); +} + +// For spatial, determine which neighboring regions to connect to +// for cross-region voice. +void LLWebRTCVoiceClient::updateNeighboringRegions() +{ + static const std::vector<LLVector3d> neighbors {LLVector3d(0.0f, 1.0f, 0.0f), LLVector3d(0.707f, 0.707f, 0.0f), + LLVector3d(1.0f, 0.0f, 0.0f), LLVector3d(0.707f, -0.707f, 0.0f), + LLVector3d(0.0f, -1.0f, 0.0f), LLVector3d(-0.707f, -0.707f, 0.0f), + LLVector3d(-1.0f, 0.0f, 0.0f), LLVector3d(-0.707f, 0.707f, 0.0f)}; + + // Estate voice requires connection to neighboring regions. + mNeighboringRegions.clear(); + + mNeighboringRegions.insert(gAgent.getRegion()->getRegionID()); + + // base off of speaker position as it'll move more slowly than camera position. + // Once we have hysteresis, we may be able to track off of speaker and camera position at 50m + // TODO: Add hysteresis so we don't flip-flop connections to neighbors + LLVector3d speaker_pos = LLWebRTCVoiceClient::getInstance()->getSpeakerPosition(); + for (auto &neighbor_pos : neighbors) + { + // include every region within 100m (2*MAX_AUDIO_DIST) to deal witht he fact that the camera + // can stray 50m away from the avatar. + LLViewerRegion *neighbor = LLWorld::instance().getRegionFromPosGlobal(speaker_pos + 2 * MAX_AUDIO_DIST * neighbor_pos); + if (neighbor && !neighbor->getRegionID().isNull()) + { + mNeighboringRegions.insert(neighbor->getRegionID()); + } + } +} + +//========================================================================= +// shut down the current audio session to make room for the next one. +void LLWebRTCVoiceClient::leaveAudioSession() +{ + if(mSession) + { + LL_DEBUGS("Voice") << "leaving session: " << mSession->mChannelID << LL_ENDL; + mSession->shutdownAllConnections(); + } + else + { + LL_WARNS("Voice") << "called with no active session" << LL_ENDL; + } +} + +//========================================================================= +// Device Management +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) +{ + mWebRTCDeviceInterface->setCaptureDevice(name); +} +void LLWebRTCVoiceClient::setDevicesListUpdated(bool state) +{ + mDevicesListUpdated = state; +} + +void LLWebRTCVoiceClient::OnDevicesChanged(const llwebrtc::LLWebRTCVoiceDeviceList &render_devices, + const llwebrtc::LLWebRTCVoiceDeviceList &capture_devices) +{ + std::string inputDevice = gSavedSettings.getString("VoiceInputAudioDevice"); + std::string outputDevice = gSavedSettings.getString("VoiceOutputAudioDevice"); + + clearRenderDevices(); + bool renderDeviceSet = false; + for (auto &device : render_devices) + { + addRenderDevice(LLVoiceDevice(device.mDisplayName, device.mID)); + if (device.mCurrent && outputDevice == device.mID) + { + setRenderDevice(outputDevice); + renderDeviceSet = true; + } + } + if (!renderDeviceSet) + { + setRenderDevice("Default"); + } + + clearCaptureDevices(); + bool captureDeviceSet = false; + for (auto &device : capture_devices) + { + addCaptureDevice(LLVoiceDevice(device.mDisplayName, device.mID)); + if (device.mCurrent && inputDevice == device.mID) + { + setCaptureDevice(outputDevice); + captureDeviceSet = true; + } + } + if (!captureDeviceSet) + { + setCaptureDevice("Default"); + } + + 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) +{ + mTuningMicGain = volume; +} + +void LLWebRTCVoiceClient::tuningSetSpeakerVolume(float volume) +{ + + if (volume != mTuningSpeakerVolume) + { + mTuningSpeakerVolume = volume; + } +} + +float LLWebRTCVoiceClient::getAudioLevel() +{ + if (mIsInTuningMode) + { + return (1.0 - mWebRTCDeviceInterface->getTuningAudioLevel() * LEVEL_SCALE_WEBRTC) * mTuningMicGain / 2.1; + } + else + { + return (1.0 - mWebRTCDeviceInterface->getPeerConnectionAudioLevel() * LEVEL_SCALE_WEBRTC) * mMicGain / 2.1; + } +} + +float LLWebRTCVoiceClient::tuningGetEnergy(void) +{ + return getAudioLevel(); +} + +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::setHidden(bool hidden) +{ + mHidden = hidden; + + if (inSpatialChannel()) + { + if (mHidden) + { + // get out of the channel entirely + leaveAudioSession(); + } + else + { + updatePosition(); + sendPositionUpdate(true); + } + } +} + +///////////////////////////// +// session control messages. +// +// these are called by the sessions to report +// status for a given channel. By filtering +// on channel and region, these functions +// can send various notifications to +// other parts of the viewer, as well as +// managing housekeeping + +// A connection to a channel was successfully established, +// so shut down the current session and move on to the next +// if one is available. +// if the current session is the one that was established, +// notify the observers. +void LLWebRTCVoiceClient::OnConnectionEstablished(const std::string &channelID, const LLUUID ®ionID) +{ + if (gAgent.getRegion()->getRegionID() == regionID) + { + if (mNextSession && mNextSession->mChannelID == channelID) + { + if (mSession) + { + mSession->shutdownAllConnections(); + } + mSession = mNextSession; + mNextSession.reset(); + + // Add ourselves as a participant. + mSession->addParticipant(gAgentID); + } + + // The current session was established. + if (mSession && mSession->mChannelID == channelID) + { + LLWebRTCVoiceClient::getInstance()->notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGGED_IN); + + // only set status to joined if asked to. This will happen in the case where we're not + // doing an ad-hoc based p2p session. Those sessions expect a STATUS_JOINED when the peer + // has, in fact, joined, which we detect elsewhere. + if (!mSession->mNotifyOnFirstJoin) + { + LLWebRTCVoiceClient::getInstance()->notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINED); + } + } + } +} + +void LLWebRTCVoiceClient::OnConnectionShutDown(const std::string &channelID, const LLUUID ®ionID) +{ + if (gAgent.getRegion()->getRegionID() == regionID) + { + if (mSession && mSession->mChannelID == channelID) + { + LL_DEBUGS("Voice") << "Main WebRTC Connection Shut Down." << LL_ENDL; + } + } +} +void LLWebRTCVoiceClient::OnConnectionFailure(const std::string &channelID, const LLUUID ®ionID) +{ + LL_DEBUGS("Voice") << "A connection failed. channel:" << channelID << LL_ENDL; + if (gAgent.getRegion()->getRegionID() == regionID) + { + if (mNextSession && mNextSession->mChannelID == channelID) + { + LLWebRTCVoiceClient::getInstance()->notifyStatusObservers(LLVoiceClientStatusObserver::ERROR_UNKNOWN); + } + else if (mSession && mSession->mChannelID == channelID) + { + LLWebRTCVoiceClient::getInstance()->notifyStatusObservers(LLVoiceClientStatusObserver::ERROR_UNKNOWN); + } + } +} + +// ----------------------------------------------------------- +// positional functionality. +void LLWebRTCVoiceClient::setEarLocation(S32 loc) +{ + if (mEarLocation != loc) + { + LL_DEBUGS("Voice") << "Setting mEarLocation to " << loc << LL_ENDL; + + mEarLocation = loc; + mSpatialCoordsDirty = true; + } +} + +void LLWebRTCVoiceClient::updatePosition(void) +{ + LLViewerRegion *region = gAgent.getRegion(); + if (region && isAgentAvatarValid()) + { + // get the avatar position. + LLVector3d avatar_pos = gAgentAvatarp->getPositionGlobal(); + LLQuaternion avatar_qrot = gAgentAvatarp->getRootJoint()->getWorldRotation(); + + avatar_pos += LLVector3d(0.f, 0.f, 1.f); // bump it up to head height + + LLVector3d earPosition; + LLQuaternion earRot; + switch (mEarLocation) + { + case earLocCamera: + default: + earPosition = region->getPosGlobalFromRegion(LLViewerCamera::getInstance()->getOrigin()); + earRot = LLViewerCamera::getInstance()->getQuaternion(); + break; + + case earLocAvatar: + earPosition = mAvatarPosition; + earRot = mAvatarRot; + break; + + case earLocMixed: + earPosition = mAvatarPosition; + earRot = LLViewerCamera::getInstance()->getQuaternion(); + break; + } + setListenerPosition(earPosition, // position + LLVector3::zero, // velocity + earRot); // rotation matrix + + setAvatarPosition(avatar_pos, // position + LLVector3::zero, // velocity + avatar_qrot); // rotation matrix + + enforceTether(); + + updateNeighboringRegions(); + } +} + +void LLWebRTCVoiceClient::setListenerPosition(const LLVector3d &position, const LLVector3 &velocity, const LLQuaternion &rot) +{ + mListenerRequestedPosition = position; + + if (mListenerVelocity != velocity) + { + mListenerVelocity = velocity; + mSpatialCoordsDirty = true; + } + + if (mListenerRot != rot) + { + mListenerRot = rot; + mSpatialCoordsDirty = true; + } +} + +void LLWebRTCVoiceClient::setAvatarPosition(const LLVector3d &position, const LLVector3 &velocity, const LLQuaternion &rot) +{ + if (dist_vec_squared(mAvatarPosition, position) > 0.01) + { + mAvatarPosition = position; + mSpatialCoordsDirty = true; + } + + if (mAvatarVelocity != velocity) + { + mAvatarVelocity = velocity; + mSpatialCoordsDirty = true; + } + + // If the two rotations are not exactly equal test their dot product + // to get the cos of the angle between them. + // If it is too small, don't update. + F32 rot_cos_diff = llabs(dot(mAvatarRot, rot)); + if ((mAvatarRot != rot) && (rot_cos_diff < MINUSCULE_ANGLE_COS)) + { + mAvatarRot = rot; + mSpatialCoordsDirty = true; + } +} + +// The listener (camera) must be within 50m of the +// avatar. Enforce it on the client. +// This will also be enforced on the voice server +// based on position sent from the simulator to the +// voice server. +void LLWebRTCVoiceClient::enforceTether() +{ + LLVector3d tethered = mListenerRequestedPosition; + + // constrain 'tethered' to within 50m of mAvatarPosition. + { + LLVector3d camera_offset = mListenerRequestedPosition - mAvatarPosition; + F32 camera_distance = (F32) camera_offset.magVec(); + if (camera_distance > MAX_AUDIO_DIST) + { + tethered = mAvatarPosition + (MAX_AUDIO_DIST / camera_distance) * camera_offset; + } + } + + if (dist_vec_squared(mListenerPosition, tethered) > 0.01) + { + mListenerPosition = tethered; + mSpatialCoordsDirty = true; + } +} + +// We send our position via a WebRTC data channel to the WebRTC +// server for fine-grained, low latency updates. On the server, +// these updates will be 'tethered' to the actual position of the avatar. +// Those updates are higher latency, however. +// This mechanism gives low latency spatial updates and server-enforced +// prevention of 'evesdropping' by sending camera updates beyond the +// standard 50m +void LLWebRTCVoiceClient::sendPositionUpdate(bool force) +{ + Json::FastWriter writer; + std::string spatial_data; + + if (mSpatialCoordsDirty || force) + { + Json::Value spatial = Json::objectValue; + + 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) (mListenerPosition[0] * 100); + spatial["lp"]["y"] = (int) (mListenerPosition[1] * 100); + spatial["lp"]["z"] = (int) (mListenerPosition[2] * 100); + spatial["lh"] = Json::objectValue; + spatial["lh"]["x"] = (int) (mListenerRot[0] * 100); + spatial["lh"]["y"] = (int) (mListenerRot[1] * 100); + spatial["lh"]["z"] = (int) (mListenerRot[2] * 100); + spatial["lh"]["w"] = (int) (mListenerRot[3] * 100); + + mSpatialCoordsDirty = false; + spatial_data = writer.write(spatial); + + sessionState::for_each(boost::bind(predSendData, _1, spatial_data)); + } +} + +// Update our own volume on our participant, so it'll show up +// in the UI. This is done on all sessions, so switching +// sessions retains consistent volume levels. +void LLWebRTCVoiceClient::updateOwnVolume() { + F32 audio_level = 0.0; + if (!mMuteMic && !mTuningMode) + { + audio_level = getAudioLevel(); + } + + sessionState::for_each(boost::bind(predUpdateOwnVolume, _1, audio_level)); +} + +//////////////////////////////////// +// Managing list of participants + +// Provider-level participant management + +BOOL LLWebRTCVoiceClient::isParticipantAvatar(const LLUUID &id) +{ + // WebRTC participants are always SL avatars. + return TRUE; +} + +void LLWebRTCVoiceClient::getParticipantList(std::set<LLUUID> &participants) +{ + if (mProcessChannels && mSession) + { + for (participantUUIDMap::iterator iter = mSession->mParticipantsByUUID.begin(); + iter != mSession->mParticipantsByUUID.end(); + iter++) + { + participants.insert(iter->first); + } + } +} + +bool LLWebRTCVoiceClient::isParticipant(const LLUUID &speaker_id) +{ + if (mProcessChannels && mSession) + { + return (mSession->mParticipantsByUUID.find(speaker_id) != mSession->mParticipantsByUUID.end()); + } + return false; +} + +// protected provider-level participant management. +LLWebRTCVoiceClient::participantStatePtr_t LLWebRTCVoiceClient::findParticipantByID(const std::string &channelID, const LLUUID &id) +{ + participantStatePtr_t result; + LLWebRTCVoiceClient::sessionState::ptr_t session = sessionState::matchSessionByChannelID(channelID); + + if (session) + { + result = session->findParticipantByID(id); + } + + return result; +} + +LLWebRTCVoiceClient::participantStatePtr_t LLWebRTCVoiceClient::addParticipantByID(const std::string &channelID, const LLUUID &id) +{ + participantStatePtr_t result; + LLWebRTCVoiceClient::sessionState::ptr_t session = sessionState::matchSessionByChannelID(channelID); + if (session) + { + result = session->addParticipant(id); + if (session->mNotifyOnFirstJoin && (id != gAgentID)) + { + notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINED); + } + } + return result; +} + +void LLWebRTCVoiceClient::removeParticipantByID(const std::string &channelID, const LLUUID &id) +{ + participantStatePtr_t result; + LLWebRTCVoiceClient::sessionState::ptr_t session = sessionState::matchSessionByChannelID(channelID); + if (session) + { + participantStatePtr_t participant = session->findParticipantByID(id); + if (participant) + { + session->removeParticipant(participant); + if (session->mHangupOnLastLeave && (id != gAgentID) && (session->mParticipantsByUUID.size() <= 1)) + { + notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); + } + } + } +} + + +// participantState level participant management +LLWebRTCVoiceClient::participantState::participantState(const LLUUID& agent_id) : + mURI(agent_id.asString()), + mAvatarID(agent_id), + mIsSpeaking(false), + mIsModeratorMuted(false), + mLevel(0.f), + mVolume(LLVoiceClient::VOLUME_DEFAULT) +{ +} + +LLWebRTCVoiceClient::participantStatePtr_t LLWebRTCVoiceClient::sessionState::addParticipant(const LLUUID& agent_id) +{ + participantStatePtr_t result; + + participantUUIDMap::iterator iter = mParticipantsByUUID.find(agent_id); + + if (iter != mParticipantsByUUID.end()) + { + result = iter->second; + } + + if(!result) + { + // participant isn't already in one list or the other. + result.reset(new participantState(agent_id)); + mParticipantsByUUID.insert(participantUUIDMap::value_type(agent_id, result)); + result->mAvatarID = agent_id; + + LLWebRTCVoiceClient::getInstance()->lookupName(agent_id); + + LLSpeakerVolumeStorage::getInstance()->getSpeakerVolume(result->mAvatarID, result->mVolume); + if (!LLWebRTCVoiceClient::sShuttingDown) + { + LLWebRTCVoiceClient::getInstance()->notifyParticipantObservers(); + } + + LL_DEBUGS("Voice") << "Participant \"" << result->mURI << "\" added." << LL_ENDL; + } + + return result; +} + + +// session-level participant management + +LLWebRTCVoiceClient::participantStatePtr_t LLWebRTCVoiceClient::sessionState::findParticipantByID(const LLUUID& id) +{ + participantStatePtr_t result; + participantUUIDMap::iterator iter = mParticipantsByUUID.find(id); + + if(iter != mParticipantsByUUID.end()) + { + result = iter->second; + } + + return result; +} + +void LLWebRTCVoiceClient::sessionState::removeParticipant(const LLWebRTCVoiceClient::participantStatePtr_t &participant) +{ + if (participant) + { + participantUUIDMap::iterator iter = mParticipantsByUUID.find(participant->mAvatarID); + + LL_DEBUGS("Voice") << "participant \"" << participant->mURI << "\" (" << participant->mAvatarID << ") removed." << LL_ENDL; + + if (iter == mParticipantsByUUID.end()) + { + LL_WARNS("Voice") << "Internal error: participant ID " << participant->mAvatarID << " not in UUID map" << LL_ENDL; + } + else + { + mParticipantsByUUID.erase(iter); + if (!LLWebRTCVoiceClient::sShuttingDown) + { + LLWebRTCVoiceClient::getInstance()->notifyParticipantObservers(); + } + } + } +} + +void LLWebRTCVoiceClient::sessionState::removeAllParticipants() +{ + LL_DEBUGS("Voice") << "called" << LL_ENDL; + + while (!mParticipantsByUUID.empty()) + { + removeParticipant(mParticipantsByUUID.begin()->second); + } +} + +// Initiated the various types of sessions. +bool LLWebRTCVoiceClient::startEstateSession() +{ + leaveChannel(false); + mNextSession = addSession("Estate", sessionState::ptr_t(new estateSessionState())); + return true; +} + +bool LLWebRTCVoiceClient::startParcelSession(const std::string &channelID, S32 parcelID) +{ + leaveChannel(false); + mNextSession = addSession(channelID, sessionState::ptr_t(new parcelSessionState(channelID, parcelID))); + return true; +} + +bool LLWebRTCVoiceClient::startAdHocSession(const LLSD& channelInfo, bool notify_on_first_join, bool hangup_on_last_leave) +{ + leaveChannel(false); + LL_WARNS("Voice") << "Start AdHoc Session " << channelInfo << LL_ENDL; + std::string channelID = channelInfo["channel_uri"]; + std::string credentials = channelInfo["channel_credentials"]; + mNextSession = addSession(channelID, + sessionState::ptr_t(new adhocSessionState(channelID, + credentials, + notify_on_first_join, + hangup_on_last_leave))); + return true; +} + +bool LLWebRTCVoiceClient::isVoiceWorking() const +{ + return mIsProcessingChannels; +} + +// Returns true if calling back the session URI after the session has closed is possible. +// Currently this will be false only for PSTN P2P calls. +BOOL LLWebRTCVoiceClient::isSessionCallBackPossible(const LLUUID &session_id) +{ + sessionStatePtr_t session(findP2PSession(session_id)); + return session && session->isCallbackPossible() ? TRUE : FALSE; +} + +// Channel Management +void LLWebRTCVoiceClient::leaveNonSpatialChannel() +{ + LL_DEBUGS("Voice") << "Request to leave non-spatial channel." << LL_ENDL; + + // make sure we're not simply rejoining the current session + deleteSession(mNextSession); + + leaveChannel(true); +} + +// determine whether we're processing channels, or whether +// another voice provider is. +void LLWebRTCVoiceClient::processChannels(bool process) +{ + mProcessChannels = process; +} + +bool LLWebRTCVoiceClient::inProximalChannel() +{ + return inSpatialChannel(); +} + +bool LLWebRTCVoiceClient::inOrJoiningChannel(const std::string& channelID) +{ + return (mSession && mSession->mChannelID == channelID) || (mNextSession && mNextSession->mChannelID == channelID); +} + +bool LLWebRTCVoiceClient::inEstateChannel() +{ + return (mSession && mSession->isEstate()) || (mNextSession && mNextSession->isEstate()); +} + +bool LLWebRTCVoiceClient::inSpatialChannel() +{ + bool result = true; + + if (mNextSession) + { + result = mNextSession->isSpatial(); + } + else if(mSession) + { + result = mSession->isSpatial(); + } + + return result; +} + +// retrieves information used to negotiate p2p, adhoc, and group +// channels +LLSD LLWebRTCVoiceClient::getAudioSessionChannelInfo() +{ + LLSD result; + + if (mSession) + { + result["voice_server_type"] = WEBRTC_VOICE_SERVER_TYPE; + result["channel_uri"] = mSession->mChannelID; + } + + return result; +} + +void LLWebRTCVoiceClient::leaveChannel(bool stopTalking) +{ + if (mSession) + { + deleteSession(mSession); + } + + if (mNextSession) + { + deleteSession(mNextSession); + } + + // If voice was on, turn it off + if (stopTalking && LLVoiceClient::getInstance()->getUserPTTState()) + { + LLVoiceClient::getInstance()->setUserPTTState(false); + } +} + +bool LLWebRTCVoiceClient::isCurrentChannel(const LLSD &channelInfo) +{ + if (!mProcessChannels || (channelInfo["voice_server_type"].asString() != WEBRTC_VOICE_SERVER_TYPE)) + { + return false; + } + + if (mSession) + { + if (!channelInfo["sessionHandle"].asString().empty()) + { + return mSession->mHandle == channelInfo["session_handle"].asString(); + } + return channelInfo["channel_uri"].asString() == mSession->mChannelID; + } + return false; +} + +bool LLWebRTCVoiceClient::compareChannels(const LLSD &channelInfo1, const LLSD &channelInfo2) +{ + return (channelInfo1["voice_server_type"] == WEBRTC_VOICE_SERVER_TYPE) && + (channelInfo1["voice_server_type"] == channelInfo2["voice_server_type"]) && + (channelInfo1["sip_uri"] == channelInfo2["sip_uri"]); +} + + +//---------------------------------------------- +// Audio muting, volume, gain, etc. + +// we're muting the mic, so tell each session such +void LLWebRTCVoiceClient::setMuteMic(bool 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->findParticipantByID(gAgentID); + if (participant) + { + participant->mLevel = 0.0; + } + session->setMuteMic(muted); +} + +void LLWebRTCVoiceClient::setVoiceVolume(F32 volume) +{ + if (volume != mSpeakerVolume) + { + { + mSpeakerVolume = volume; + } + sessionState::for_each(boost::bind(predSetSpeakerVolume, _1, volume)); + } +} + +void LLWebRTCVoiceClient::predSetSpeakerVolume(const LLWebRTCVoiceClient::sessionStatePtr_t &session, F32 volume) +{ + session->setSpeakerVolume(volume); +} + +void LLWebRTCVoiceClient::setMicGain(F32 gain) +{ + if (gain != mMicGain) + { + mMicGain = gain; + sessionState::for_each(boost::bind(predSetMicGain, _1, gain)); + } +} + +void LLWebRTCVoiceClient::predSetMicGain(const LLWebRTCVoiceClient::sessionStatePtr_t &session, F32 gain) +{ + session->setMicGain(gain); +} + +void LLWebRTCVoiceClient::setVoiceEnabled(bool enabled) +{ + LL_DEBUGS("Voice") + << "( " << (enabled ? "enabled" : "disabled") << " )" + << " was "<< (mVoiceEnabled ? "enabled" : "disabled") + << " coro "<< (mIsCoroutineActive ? "active" : "inactive") + << LL_ENDL; + + if (enabled != mVoiceEnabled) + { + // TODO: Refactor this so we don't call into LLVoiceChannel, but simply + // use the status observer + mVoiceEnabled = enabled; + LLVoiceClientStatusObserver::EStatusType status; + + if (enabled) + { + LL_DEBUGS("Voice") << "enabling" << LL_ENDL; + LLVoiceChannel::getCurrentVoiceChannel()->activate(); + status = LLVoiceClientStatusObserver::STATUS_VOICE_ENABLED; + mSpatialCoordsDirty = true; + updatePosition(); + if (!mIsCoroutineActive) + { + LLCoros::instance().launch("LLWebRTCVoiceClient::voiceConnectionCoro", + boost::bind(&LLWebRTCVoiceClient::voiceConnectionCoro, LLWebRTCVoiceClient::getInstance())); + } + else + { + LL_DEBUGS("Voice") << "coro should be active.. not launching" << LL_ENDL; + } + } + else + { + // Turning voice off looses your current channel -- this makes sure the UI isn't out of sync when you re-enable it. + LLVoiceChannel::getCurrentVoiceChannel()->deactivate(); + gAgent.setVoiceConnected(false); + status = LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED; + cleanUp(); + } + + notifyStatusObservers(status); + } + else + { + LL_DEBUGS("Voice") << " no-op" << LL_ENDL; + } +} + + +///////////////////////////// +// Accessors for data related to nearby speakers + +std::string LLWebRTCVoiceClient::getDisplayName(const LLUUID& id) +{ + std::string result; + if (mProcessChannels && mSession) + { + participantStatePtr_t participant(mSession->findParticipantByID(id)); + if (participant) + { + result = participant->mDisplayName; + } + } + return result; +} + +BOOL LLWebRTCVoiceClient::getIsSpeaking(const LLUUID& id) +{ + BOOL result = FALSE; + if (mProcessChannels && mSession) + { + participantStatePtr_t participant(mSession->findParticipantByID(id)); + if (participant) + { + result = participant->mIsSpeaking; + } + } + return result; +} + +// TODO: Need to pull muted status from the webrtc server +BOOL LLWebRTCVoiceClient::getIsModeratorMuted(const LLUUID& id) +{ + BOOL result = FALSE; + if (mProcessChannels && mSession) + { + participantStatePtr_t participant(mSession->findParticipantByID(id)); + if (participant) + { + result = participant->mIsModeratorMuted; + } + } + return result; +} + +F32 LLWebRTCVoiceClient::getCurrentPower(const LLUUID &id) +{ + F32 result = 0.0; + if (!mProcessChannels || !mSession) + { + return result; + } + participantStatePtr_t participant(mSession->findParticipantByID(id)); + if (participant) + { + if (participant->mIsSpeaking) + { + result = participant->mLevel; + } + } + return result; +} + +// External accessors. +F32 LLWebRTCVoiceClient::getUserVolume(const LLUUID& id) +{ + // Minimum volume will be returned for users with voice disabled + F32 result = LLVoiceClient::VOLUME_MIN; + + participantStatePtr_t participant(mSession->findParticipantByID(id)); + if(participant) + { + result = participant->mVolume; + } + + return result; +} + +void LLWebRTCVoiceClient::setUserVolume(const LLUUID& id, F32 volume) +{ + F32 clamped_volume = llclamp(volume, LLVoiceClient::VOLUME_MIN, LLVoiceClient::VOLUME_MAX); + if(mSession) + { + participantStatePtr_t participant(mSession->findParticipantByID(id)); + if (participant && (participant->mAvatarID != gAgentID)) + { + if (!is_approx_equal(volume, LLVoiceClient::VOLUME_DEFAULT)) + { + // Store this volume setting for future sessions if it has been + // changed from the default + LLSpeakerVolumeStorage::getInstance()->storeSpeakerVolume(id, volume); + } + else + { + // Remove stored volume setting if it is returned to the default + LLSpeakerVolumeStorage::getInstance()->removeSpeakerVolume(id); + } + + participant->mVolume = clamped_volume; + } + } + sessionState::for_each(boost::bind(predSetUserVolume, _1, id, clamped_volume)); +} + +// set volume level (gain level) for another user. +void LLWebRTCVoiceClient::predSetUserVolume(const LLWebRTCVoiceClient::sessionStatePtr_t &session, const LLUUID &id, F32 volume) +{ + session->setUserVolume(id, volume); +} + +//////////////////////// +///LLMuteListObserver +/// + +void LLWebRTCVoiceClient::onChange() +{ +} + +void LLWebRTCVoiceClient::onChangeDetailed(const LLMute& mute) +{ + if (mute.mType == LLMute::AGENT) + { + bool muted = ((mute.mFlags & LLMute::flagVoiceChat) == 0); + sessionState::for_each(boost::bind(predSetUserMute, _1, mute.mID, muted)); + } +} + +void LLWebRTCVoiceClient::predSetUserMute(const LLWebRTCVoiceClient::sessionStatePtr_t &session, const LLUUID &id, bool mute) +{ + session->setUserMute(id, mute); +} + +//------------------------------------------------------------------------ +// Sessions + +std::map<std::string, LLWebRTCVoiceClient::sessionState::ptr_t> LLWebRTCVoiceClient::sessionState::mSessions; + + +LLWebRTCVoiceClient::sessionState::sessionState() : + mHangupOnLastLeave(false), + mNotifyOnFirstJoin(false), + mMicGain(1.0), + mMuted(false), + mSpeakerVolume(1.0), + mShuttingDown(false) +{ +} +// ------------------------------------------------------------------ +// Predicates, for calls to all sessions + +void LLWebRTCVoiceClient::predUpdateOwnVolume(const LLWebRTCVoiceClient::sessionStatePtr_t &session, F32 audio_level) +{ + participantStatePtr_t participant = session->findParticipantByID(gAgentID); + if (participant) + { + participant->mLevel = audio_level; + // TODO: Add VAD for our own voice. + participant->mIsSpeaking = audio_level > SPEAKING_AUDIO_LEVEL; + } +} + +void LLWebRTCVoiceClient::predSendData(const LLWebRTCVoiceClient::sessionStatePtr_t &session, const std::string &spatial_data) +{ + if (session->isSpatial() && !spatial_data.empty()) + { + session->sendData(spatial_data); + } +} + +void LLWebRTCVoiceClient::sessionState::sendData(const std::string &data) +{ + for (auto &connection : mWebRTCConnections) + { + connection->sendData(data); + } +} + +void LLWebRTCVoiceClient::sessionState::setMuteMic(bool muted) +{ + mMuted = muted; + for (auto &connection : mWebRTCConnections) + { + connection->setMuteMic(muted); + } +} + +void LLWebRTCVoiceClient::sessionState::setMicGain(F32 gain) +{ + mMicGain = gain; + for (auto &connection : mWebRTCConnections) + { + connection->setMicGain(gain); + } +} + +void LLWebRTCVoiceClient::sessionState::setSpeakerVolume(F32 volume) +{ + mSpeakerVolume = volume; + for (auto &connection : mWebRTCConnections) + { + connection->setSpeakerVolume(volume); + } +} + +void LLWebRTCVoiceClient::sessionState::setUserVolume(const LLUUID &id, F32 volume) +{ + if (mParticipantsByUUID.find(id) == mParticipantsByUUID.end()) + { + return; + } + for (auto &connection : mWebRTCConnections) + { + connection->setUserVolume(id, volume); + } +} + +void LLWebRTCVoiceClient::sessionState::setUserMute(const LLUUID &id, bool mute) +{ + if (mParticipantsByUUID.find(id) == mParticipantsByUUID.end()) + { + return; + } + for (auto &connection : mWebRTCConnections) + { + connection->setUserMute(id, mute); + } +} +/*static*/ +void LLWebRTCVoiceClient::sessionState::addSession( + const std::string & channelID, + LLWebRTCVoiceClient::sessionState::ptr_t& session) +{ + mSessions[channelID] = session; +} + +LLWebRTCVoiceClient::sessionState::~sessionState() +{ + LL_DEBUGS("Voice") << "Destroying session CHANNEL=" << mChannelID << LL_ENDL; + + removeAllParticipants(); +} + +/*static*/ +LLWebRTCVoiceClient::sessionState::ptr_t LLWebRTCVoiceClient::sessionState::matchSessionByChannelID(const std::string& channel_id) +{ + sessionStatePtr_t result; + + // *TODO: My kingdom for a lambda! + std::map<std::string, ptr_t>::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(mSessions.begin(), mSessions.end(), boost::bind(for_eachPredicate, _1, func)); +} + +void LLWebRTCVoiceClient::sessionState::reapEmptySessions() +{ + std::map<std::string, ptr_t>::iterator iter; + for (iter = mSessions.begin(); iter != mSessions.end();) + { + if (iter->second->isEmpty()) + { + iter = mSessions.erase(iter); + } + else + { + ++iter; + } + } +} + +/*static*/ +void LLWebRTCVoiceClient::sessionState::for_eachPredicate(const std::pair<std::string, LLWebRTCVoiceClient::sessionState::wptr_t> &a, sessionFunc_t func) +{ + ptr_t aLock(a.second.lock()); + + if (aLock) + func(aLock); + else + { + LL_WARNS("Voice") << "Stale handle in session map!" << LL_ENDL; + } +} + +LLWebRTCVoiceClient::sessionStatePtr_t LLWebRTCVoiceClient::addSession(const std::string &channel_id, sessionState::ptr_t session) +{ + sessionStatePtr_t existingSession = sessionState::matchSessionByChannelID(channel_id); + if (!existingSession) + { + // No existing session found. + + LL_DEBUGS("Voice") << "adding new session with channel: " << channel_id << LL_ENDL; + session->setMuteMic(mMuteMic); + session->setMicGain(mMicGain); + session->setSpeakerVolume(mSpeakerVolume); + + sessionState::addSession(channel_id, session); + return session; + } + else + { + // Found an existing session + LL_DEBUGS("Voice") << "Attempting to add already-existing session " << channel_id << LL_ENDL; + existingSession->revive(); + + return existingSession; + } +} + +LLWebRTCVoiceClient::sessionStatePtr_t LLWebRTCVoiceClient::findP2PSession(const LLUUID &agent_id) +{ + sessionStatePtr_t result = sessionState::matchSessionByChannelID(agent_id.asString()); + if (result && !result->isSpatial()) + { + return result; + } + + result.reset(); + return result; +} + +void LLWebRTCVoiceClient::sessionState::shutdownAllConnections() +{ + mShuttingDown = true; + for (auto &&connection : mWebRTCConnections) + { + connection->shutDown(); + } +} + +// in case we drop into a session (spatial, etc.) right after +// telling the session to shut down, revive it so it reconnects. +void LLWebRTCVoiceClient::sessionState::revive() +{ + mShuttingDown = false; +} + +//========================================================================= +// the following are methods to support the coroutine implementation of the +// voice connection and processing. They should only be called in the context +// of a coroutine. +// +// + +void LLWebRTCVoiceClient::sessionState::processSessionStates() +{ + auto iter = mSessions.begin(); + while (iter != mSessions.end()) + { + if (!iter->second->processConnectionStates() && iter->second->mShuttingDown) + { + // if the connections associated with a session are gone, + // and this session is shutting down, remove it. + iter = mSessions.erase(iter); + } + else + { + iter++; + } + } +} + +// process the states on each connection associated with a session. +bool LLWebRTCVoiceClient::sessionState::processConnectionStates() +{ + std::list<connectionPtr_t>::iterator iter = mWebRTCConnections.begin(); + while (iter != mWebRTCConnections.end()) + { + if (!iter->get()->connectionStateMachine()) + { + // if the state machine returns false, the connection is shut down + // so delete it. + iter = mWebRTCConnections.erase(iter); + } + else + { + ++iter; + } + } + return !mWebRTCConnections.empty(); +} + +// processing of spatial voice connection states requires special handling. +// as neighboring regions need to be started up or shut down depending +// on our location. +bool LLWebRTCVoiceClient::estateSessionState::processConnectionStates() +{ + if (!mShuttingDown) + { + // Estate voice requires connection to neighboring regions. + std::set<LLUUID> neighbor_ids = LLWebRTCVoiceClient::getInstance()->getNeighboringRegions(); + + for (auto &connection : mWebRTCConnections) + { + boost::shared_ptr<LLVoiceWebRTCSpatialConnection> spatialConnection = + boost::static_pointer_cast<LLVoiceWebRTCSpatialConnection>(connection); + + LLUUID regionID = spatialConnection.get()->getRegionID(); + + if (neighbor_ids.find(regionID) == neighbor_ids.end()) + { + // shut down connections to neighbors that are too far away. + spatialConnection.get()->shutDown(); + } + neighbor_ids.erase(regionID); + } + + // add new connections for new neighbors + for (auto &neighbor : neighbor_ids) + { + connectionPtr_t connection(new LLVoiceWebRTCSpatialConnection(neighbor, INVALID_PARCEL_ID, mChannelID)); + + mWebRTCConnections.push_back(connection); + connection->setMicGain(mMicGain); + connection->setMuteMic(mMuted); + connection->setSpeakerVolume(mSpeakerVolume); + } + } + return LLWebRTCVoiceClient::sessionState::processConnectionStates(); +} + +// Various session state constructors. + +LLWebRTCVoiceClient::estateSessionState::estateSessionState() +{ + mHangupOnLastLeave = false; + mNotifyOnFirstJoin = false; + mChannelID = "Estate"; + LLUUID region_id = gAgent.getRegion()->getRegionID(); + + mWebRTCConnections.emplace_back(new LLVoiceWebRTCSpatialConnection(region_id, INVALID_PARCEL_ID, "Estate")); +} + +LLWebRTCVoiceClient::parcelSessionState::parcelSessionState(const std::string &channelID, S32 parcel_local_id) +{ + mHangupOnLastLeave = false; + mNotifyOnFirstJoin = false; + LLUUID region_id = gAgent.getRegion()->getRegionID(); + mChannelID = channelID; + mWebRTCConnections.emplace_back(new LLVoiceWebRTCSpatialConnection(region_id, parcel_local_id, channelID)); +} + +LLWebRTCVoiceClient::adhocSessionState::adhocSessionState(const std::string &channelID, + const std::string &credentials, + bool notify_on_first_join, + bool hangup_on_last_leave) : + mCredentials(credentials) +{ + mHangupOnLastLeave = hangup_on_last_leave; + mNotifyOnFirstJoin = notify_on_first_join; + LLUUID region_id = gAgent.getRegion()->getRegionID(); + mChannelID = channelID; + mWebRTCConnections.emplace_back(new LLVoiceWebRTCAdHocConnection(region_id, channelID, credentials)); +} + +void LLWebRTCVoiceClient::predShutdownSession(const LLWebRTCVoiceClient::sessionStatePtr_t& session) +{ + session->shutdownAllConnections(); +} + +void LLWebRTCVoiceClient::deleteSession(const sessionStatePtr_t &session) +{ + if (!session) + { + return; + } + + // At this point, the session should be unhooked from all lists and all state should be consistent. + session->shutdownAllConnections(); + // If this is the current audio session, clean up the pointer which will soon be dangling. + bool deleteAudioSession = mSession == session; + bool deleteNextAudioSession = mNextSession == session; + if (deleteAudioSession) + { + mSession.reset(); + } + + // ditto for the next audio session + if (deleteNextAudioSession) + { + mNextSession.reset(); + } +} + + +// Name resolution +void LLWebRTCVoiceClient::lookupName(const LLUUID &id) +{ + if (mAvatarNameCacheConnection.connected()) + { + mAvatarNameCacheConnection.disconnect(); + } + mAvatarNameCacheConnection = LLAvatarNameCache::get(id, boost::bind(&LLWebRTCVoiceClient::onAvatarNameCache, this, _1, _2)); +} + +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::predAvatarNameResolution(const LLWebRTCVoiceClient::sessionStatePtr_t &session, LLUUID id, std::string name) +{ + participantStatePtr_t participant(session->findParticipantByID(id)); + if (participant) + { + // Found -- fill in the name + participant->mDisplayName = name; + // and post a "participants updated" message to listeners later. + LLWebRTCVoiceClient::getInstance()->notifyParticipantObservers(); + } +} + +void LLWebRTCVoiceClient::avatarNameResolved(const LLUUID &id, const std::string &name) +{ + sessionState::for_each(boost::bind(predAvatarNameResolution, _1, id, name)); +} + +// Leftover from vivox PTSN +std::string LLWebRTCVoiceClient::sipURIFromID(const LLUUID& id) +{ + return id.asString(); +} + + +///////////////////////////// +// LLVoiceWebRTCConnection +// These connections manage state transitions, negotiating webrtc connections, +// and other such things for a single connection to a Secondlife WebRTC server. +// Multiple of these connections may be active at once, in the case of +// cross-region voice, or when a new connection is being created before the old +// has a chance to shut down. +LLVoiceWebRTCConnection::LLVoiceWebRTCConnection(const LLUUID ®ionID, const std::string &channelID) : + mWebRTCAudioInterface(nullptr), + mWebRTCDataInterface(nullptr), + mVoiceConnectionState(VOICE_STATE_START_SESSION), + mMuted(true), + mShutDown(false), + mTrickling(false), + mIceCompleted(false), + mSpeakerVolume(0.0), + mMicGain(0.0), + mOutstandingRequests(0), + mChannelID(channelID), + mRegionID(regionID) +{ + mWebRTCPeerConnectionInterface = llwebrtc::newPeerConnection(); + mWebRTCPeerConnectionInterface->setSignalingObserver(this); +} + +LLVoiceWebRTCConnection::~LLVoiceWebRTCConnection() +{ + if (LLWebRTCVoiceClient::isShuttingDown()) + { + // peer connection and observers will be cleaned up + // by llwebrtc::terminate() on shutdown. + return; + } + if (mWebRTCPeerConnectionInterface) + { + llwebrtc::freePeerConnection(mWebRTCPeerConnectionInterface); + mWebRTCPeerConnectionInterface = nullptr; + } +} + + +// ICE (Interactive Connectivity Establishment) +// When WebRTC tries to negotiate a connection to the Secondlife WebRTC Server, +// the negotiation will result in a few updates about the best path +// to which to connect. +// The Secondlife servers are configured for ICE trickling, where, after a session is partially +// negotiated, updates about the best connectivity paths may trickle in. These need to be +// sent to the Secondlife WebRTC server via the simulator so that both sides have a clear +// view of the network environment. +void LLVoiceWebRTCConnection::OnIceGatheringState(llwebrtc::LLWebRTCSignalingObserver::EIceGatheringState state) +{ + LL_DEBUGS("Voice") << "Ice Gathering voice account. " << state << LL_ENDL; + + switch (state) + { + case llwebrtc::LLWebRTCSignalingObserver::EIceGatheringState::ICE_GATHERING_COMPLETE: + { + LLMutexLock lock(&mVoiceStateMutex); + mIceCompleted = true; + break; + } + case llwebrtc::LLWebRTCSignalingObserver::EIceGatheringState::ICE_GATHERING_NEW: + { + LLMutexLock lock(&mVoiceStateMutex); + mIceCompleted = false; + } + default: + break; + } +} + +void LLVoiceWebRTCConnection::OnIceCandidate(const llwebrtc::LLWebRTCIceCandidate &candidate) +{ + LLMutexLock lock(&mVoiceStateMutex); + mIceCandidates.push_back(candidate); +} + +void LLVoiceWebRTCConnection::onIceUpdateComplete(bool ice_completed, const LLSD &result) +{ + mOutstandingRequests--; + 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()) + { + mOutstandingRequests--; + 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)); + return; + } + + LL_WARNS("Voice") << "Unable to complete ice trickling voice account, restarting connection. " << result << LL_ENDL; + if (!mShutDown) + { + setVoiceConnectionState(VOICE_STATE_SESSION_RETRY); + } + mTrickling = false; + + mOutstandingRequests--; +} + + +// Ice candidates may be streamed in before or after the SDP offer is available (see below) +// This function determines whether candidates are available to send to the Secondlife WebRTC +// server via the simulator. If so, and there are no more candidates, this code +// will make the cap call to the server sending up the ICE candidates. +void LLVoiceWebRTCConnection::processIceUpdates() +{ + if (mShutDown || LLWebRTCVoiceClient::isShuttingDown()) + { + return; + } + + bool iceCompleted = false; + LLSD body; + { + if (!mTrickling) + { + if (!mIceCandidates.empty() || mIceCompleted) + { + 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; + if (!mIceCandidates.empty()) + { + LLSD candidates = LLSD::emptyArray(); + for (auto &ice_candidate : mIceCandidates) + { + LLSD body_candidate; + body_candidate["sdpMid"] = ice_candidate.mSdpMid; + body_candidate["sdpMLineIndex"] = ice_candidate.mMLineIndex; + body_candidate["candidate"] = ice_candidate.mCandidate; + 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; + } + + body["viewer_session"] = mViewerSession; + body["voice_server_type"] = WEBRTC_VOICE_SERVER_TYPE; + + 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); + LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpPost( + url, + LLCore::HttpRequest::DEFAULT_POLICY_ID, + body, + boost::bind(&LLVoiceWebRTCSpatialConnection::onIceUpdateComplete, this, iceCompleted, _1), + boost::bind(&LLVoiceWebRTCSpatialConnection::onIceUpdateError, this, 3, url, body, iceCompleted, _1)); + mOutstandingRequests++; + mTrickling = true; + } + } + } +} + + +// An 'Offer' comes in the form of a SDP (Session Description Protocol) +// which contains all sorts of info about the session, from network paths +// to the type of session (audio, video) to characteristics (the encoder type.) +// This SDP also serves as the 'ticket' to the server, security-wise. +// The Offer is retrieved from the WebRTC library on the client, +// and is passed to the simulator via a CAP, which then passes +// it on to the Secondlife WebRTC server. + +void LLVoiceWebRTCConnection::OnOfferAvailable(const std::string &sdp) +{ + LL_DEBUGS("Voice") << "On Offer Available." << LL_ENDL; + LLMutexLock lock(&mVoiceStateMutex); + mChannelSDP = sdp; + if (mVoiceConnectionState == VOICE_STATE_WAIT_FOR_SESSION_START) + { + mVoiceConnectionState = VOICE_STATE_REQUEST_CONNECTION; + } +} + +// Notifications from the webrtc library. +void LLVoiceWebRTCConnection::OnAudioEstablished(llwebrtc::LLWebRTCAudioInterface *audio_interface) +{ + LL_DEBUGS("Voice") << "On AudioEstablished." << LL_ENDL; + mWebRTCAudioInterface = audio_interface; + setVoiceConnectionState(VOICE_STATE_SESSION_ESTABLISHED); +} + +void LLVoiceWebRTCConnection::OnRenegotiationNeeded() +{ + LL_DEBUGS("Voice") << "Voice channel requires renegotiation." << LL_ENDL; + if (!mShutDown) + { + setVoiceConnectionState(VOICE_STATE_SESSION_RETRY); + } +} + +void LLVoiceWebRTCConnection::OnPeerConnectionShutdown() +{ + setVoiceConnectionState(VOICE_STATE_SESSION_EXIT); + mOutstandingRequests--; // shut down is an async call which is handled on a webrtc thread. +} + +void LLVoiceWebRTCConnection::setMuteMic(bool muted) +{ + mMuted = muted; + if (mWebRTCAudioInterface) + { + mWebRTCAudioInterface->setMute(muted); + } +} + +void LLVoiceWebRTCConnection::setMicGain(F32 gain) +{ + mMicGain = gain; + if (mWebRTCAudioInterface) + { + mWebRTCAudioInterface->setSendVolume(gain); + } +} + +void LLVoiceWebRTCConnection::setSpeakerVolume(F32 volume) +{ + mSpeakerVolume = volume; + if (mWebRTCAudioInterface) + { + mWebRTCAudioInterface->setReceiveVolume(volume); + } +} + +void LLVoiceWebRTCConnection::setUserVolume(const LLUUID& id, F32 volume) +{ + Json::Value root = Json::objectValue; + Json::Value user_gain = Json::objectValue; + user_gain[id.asString()] = (uint32_t)(volume*200); // give it two decimal places with a range from 0-200, where 100 is normal + root["ug"] = user_gain; + Json::FastWriter writer; + std::string json_data = writer.write(root); + mWebRTCDataInterface->sendData(json_data, false); +} + +void LLVoiceWebRTCConnection::setUserMute(const LLUUID& id, bool mute) +{ + Json::Value root = Json::objectValue; + Json::Value muted = Json::objectValue; + muted[id.asString()] = mute; + root["m"] = muted; + Json::FastWriter writer; + std::string json_data = writer.write(root); + mWebRTCDataInterface->sendData(json_data, false); +} + + +// Send data to the Secondlife WebRTC server via the webrtc +// data channel. +void LLVoiceWebRTCConnection::sendData(const std::string &data) +{ + if (getVoiceConnectionState() == VOICE_STATE_SESSION_UP && mWebRTCDataInterface) + { + mWebRTCDataInterface->sendData(data, false); + } +} + +// Tell the simulator that we're shutting down a voice connection. +// The simulator will pass this on to the Secondlife WebRTC server. +bool LLVoiceWebRTCConnection::breakVoiceConnection(bool corowait) +{ + LL_DEBUGS("Voice") << "Disconnecting voice." << LL_ENDL; + if (mWebRTCDataInterface) + { + mWebRTCDataInterface->unsetDataObserver(this); + mWebRTCDataInterface = nullptr; + } + mWebRTCAudioInterface = nullptr; + LLViewerRegion *regionp = LLWorld::instance().getRegionFromID(mRegionID); + if (!regionp || !regionp->capabilitiesReceived()) + { + LL_DEBUGS("Voice") << "no capabilities for voice provisioning; waiting " << LL_ENDL; + setVoiceConnectionState(VOICE_STATE_SESSION_EXIT); + return false; + } + + std::string url = regionp->getCapability("ProvisionVoiceAccountRequest"); + if (url.empty()) + { + return false; + } + + 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); + + LLVoiceWebRTCStats::getInstance()->provisionAttemptStart(); + LLSD body; + body["logout"] = TRUE; + body["viewer_session"] = mViewerSession; + body["voice_server_type"] = REPORTED_VOICE_SERVER_TYPE; + + LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpPost( + url, + LLCore::HttpRequest::DEFAULT_POLICY_ID, + body, + boost::bind(&LLVoiceWebRTCSpatialConnection::OnVoiceDisconnectionRequestSuccess, this, _1), + boost::bind(&LLVoiceWebRTCSpatialConnection::OnVoiceDisconnectionRequestFailure, this, url, 3, body, _1)); + setVoiceConnectionState(VOICE_STATE_WAIT_FOR_EXIT); + mOutstandingRequests++; + return true; +} + +void LLVoiceWebRTCConnection::OnVoiceDisconnectionRequestSuccess(const LLSD &result) +{ + mOutstandingRequests--; + if (LLWebRTCVoiceClient::isShuttingDown()) + { + return; + } + + if (mWebRTCPeerConnectionInterface) + { + if (mWebRTCPeerConnectionInterface->shutdownConnection()) + { + mOutstandingRequests++; + } + } + else + { + setVoiceConnectionState(VOICE_STATE_SESSION_EXIT); + } +} + +void LLVoiceWebRTCConnection::OnVoiceDisconnectionRequestFailure(std::string url, int retries, LLSD body, const LLSD &result) +{ + if (LLWebRTCVoiceClient::isShuttingDown()) + { + mOutstandingRequests--; + return; + } + if (retries >= 0) + { + // retry a few times. + LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpPost( + url, + LLCore::HttpRequest::DEFAULT_POLICY_ID, + body, + boost::bind(&LLVoiceWebRTCSpatialConnection::OnVoiceDisconnectionRequestSuccess, this, _1), + boost::bind(&LLVoiceWebRTCSpatialConnection::OnVoiceDisconnectionRequestFailure, this, url, retries - 1, body, _1)); + return; + } + if (mWebRTCPeerConnectionInterface) + { + mOutstandingRequests++; + mWebRTCPeerConnectionInterface->shutdownConnection(); + } + else + { + setVoiceConnectionState(VOICE_STATE_SESSION_EXIT); + } + mOutstandingRequests--; +} + + +// Tell the simulator to tell the Secondlife WebRTC server that we want a voice +// connection. The SDP is sent up as part of this, and the simulator will respond +// with an 'answer' which is in the form of another SDP. The webrtc library +// will use the offer and answer to negotiate the session. +bool LLVoiceWebRTCSpatialConnection::requestVoiceConnection() +{ + LLViewerRegion *regionp = LLWorld::instance().getRegionFromID(mRegionID); + + LL_DEBUGS("Voice") << "Requesting voice connection." << LL_ENDL; + if (!regionp || !regionp->capabilitiesReceived()) + { + LL_DEBUGS("Voice") << "no capabilities for voice provisioning; waiting " << LL_ENDL; + return false; + } + + std::string url = regionp->getCapability("ProvisionVoiceAccountRequest"); + if (url.empty()) + { + return false; + } + + LL_DEBUGS("Voice") << "region ready for voice provisioning; url=" << url << LL_ENDL; + + 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; + } + + body["voice_server_type"] = WEBRTC_VOICE_SERVER_TYPE; + + LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpPost( + url, + LLCore::HttpRequest::DEFAULT_POLICY_ID, + body, + boost::bind(&LLVoiceWebRTCSpatialConnection::OnVoiceConnectionRequestSuccess, this, _1), + boost::bind(&LLVoiceWebRTCSpatialConnection::OnVoiceConnectionRequestFailure, this, url, 3, body, _1)); + mOutstandingRequests++; + return true; +} + +void LLVoiceWebRTCConnection::OnVoiceConnectionRequestSuccess(const LLSD &result) +{ + mOutstandingRequests--; + if (LLWebRTCVoiceClient::isShuttingDown()) + { + return; + } + LLVoiceWebRTCStats::getInstance()->provisionAttemptEnd(true); + + if (result.has("viewer_session") && result.has("jsep") && result["jsep"].has("type") && result["jsep"]["type"] == "answer" && + result["jsep"].has("sdp")) + { + mRemoteChannelSDP = result["jsep"]["sdp"].asString(); + mViewerSession = result["viewer_session"]; + } + else + { + LL_WARNS("Voice") << "Invalid voice provision request result:" << result << LL_ENDL; + setVoiceConnectionState(VOICE_STATE_SESSION_RETRY); + return; + } + + LL_DEBUGS("Voice") << "ProvisionVoiceAccountRequest response" + << " channel sdp " << mRemoteChannelSDP << LL_ENDL; + + mWebRTCPeerConnectionInterface->AnswerAvailable(mRemoteChannelSDP); +} + + +void LLVoiceWebRTCConnection::OnVoiceConnectionRequestFailure(std::string url, int retries, LLSD body, const LLSD &result) +{ + if (LLWebRTCVoiceClient::isShuttingDown()) + { + mOutstandingRequests--; + return; + } + if (retries >= 0) + { + LL_WARNS("Voice") << "Failure connecting to voice, retrying." << body << " RESULT: " << result << LL_ENDL; + 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)); + return; + } + LL_WARNS("Voice") << "Unable to connect voice." << body << " RESULT: " << result << LL_ENDL; + setVoiceConnectionState(VOICE_STATE_SESSION_RETRY); + mOutstandingRequests--; +} + + +// Primary state machine for negotiating a single voice connection to the +// Secondlife WebRTC server. +bool LLVoiceWebRTCConnection::connectionStateMachine() +{ + processIceUpdates(); + + switch (getVoiceConnectionState()) + { + case VOICE_STATE_START_SESSION: + { + if (mShutDown) + { + setVoiceConnectionState(VOICE_STATE_DISCONNECT); + break; + } + mTrickling = false; + mIceCompleted = false; + setVoiceConnectionState(VOICE_STATE_WAIT_FOR_SESSION_START); + // tell the webrtc library that we want a connection. The library will + // respond with an offer on a separate thread, which will cause + // the session state to change. + if (!mWebRTCPeerConnectionInterface->initializeConnection()) + { + setVoiceConnectionState(VOICE_STATE_SESSION_RETRY); + } + break; + } + + case VOICE_STATE_WAIT_FOR_SESSION_START: + { + if (mShutDown) + { + setVoiceConnectionState(VOICE_STATE_DISCONNECT); + } + break; + } + + case VOICE_STATE_REQUEST_CONNECTION: + if (mShutDown) + { + setVoiceConnectionState(VOICE_STATE_DISCONNECT); + break; + } + // Ask the sim to ask the Secondlife WebRTC server for a connection to + // a given voice channel. On completion, we'll move on to the + // VOICE_STATE_SESSION_ESTABLISHED via a callback on a webrtc thread. + if (!requestVoiceConnection()) + { + setVoiceConnectionState(VOICE_STATE_SESSION_RETRY); + } + else + { + setVoiceConnectionState(VOICE_STATE_CONNECTION_WAIT); + } + break; + + case VOICE_STATE_CONNECTION_WAIT: + if (mShutDown) + { + setVoiceConnectionState(VOICE_STATE_DISCONNECT); + } + break; + + case VOICE_STATE_SESSION_ESTABLISHED: + { + if (mShutDown) + { + setVoiceConnectionState(VOICE_STATE_DISCONNECT); + break; + } + // update the peer connection with the various characteristics of + // this connection. + mWebRTCAudioInterface->setMute(mMuted); + mWebRTCAudioInterface->setReceiveVolume(mSpeakerVolume); + mWebRTCAudioInterface->setSendVolume(mMicGain); + LLWebRTCVoiceClient::getInstance()->OnConnectionEstablished(mChannelID, mRegionID); + setVoiceConnectionState(VOICE_STATE_WAIT_FOR_DATA_CHANNEL); + break; + } + + case VOICE_STATE_WAIT_FOR_DATA_CHANNEL: + { + if (mShutDown) + { + setVoiceConnectionState(VOICE_STATE_DISCONNECT); + break; + } + if (mWebRTCDataInterface) // the interface will be set when the session is negotiated. + { + sendJoin(); // tell the Secondlife WebRTC server that we're here via the data channel. + setVoiceConnectionState(VOICE_STATE_SESSION_UP); + } + break; + } + + case VOICE_STATE_SESSION_UP: + { + // we'll stay here as long as the session remains up. + if (mShutDown) + { + setVoiceConnectionState(VOICE_STATE_DISCONNECT); + } + break; + } + + case VOICE_STATE_SESSION_RETRY: + // something went wrong, so notify that the connection has failed. + LLWebRTCVoiceClient::getInstance()->OnConnectionFailure(mChannelID, mRegionID); + setVoiceConnectionState(VOICE_STATE_DISCONNECT); + break; + + case VOICE_STATE_DISCONNECT: + breakVoiceConnection(true); + break; + + case VOICE_STATE_WAIT_FOR_EXIT: + break; + + case VOICE_STATE_SESSION_EXIT: + { + { + LLMutexLock lock(&mVoiceStateMutex); + if (!mShutDown) + { + mVoiceConnectionState = VOICE_STATE_START_SESSION; + } + else + { + // if we still have outstanding http or webrtc calls, wait for them to + // complete so we don't delete objects while they still may be used. + if (mOutstandingRequests <= 0) + { + LLWebRTCVoiceClient::getInstance()->OnConnectionShutDown(mChannelID, mRegionID); + return false; + } + } + } + break; + } + + default: + { + LL_WARNS("Voice") << "Unknown voice control state " << getVoiceConnectionState() << LL_ENDL; + return false; + } + } + return true; +} + +// Data has been received on the webrtc data channel +void LLVoiceWebRTCConnection::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' - object of join data (currently only a boolean 'p' marking a primary participant) + // 'l' - boolean, always true if exists. + // 'v' - boolean - voice activity has been detected. + + 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; + Json::Value mute = Json::objectValue; + Json::Value user_gain = Json::objectValue; + 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 = false; + bool primary = false; // we ignore any 'joins' reported about participants + // that come from voice servers that aren't their primary + // voice server. This will happen with cross-region voice + // where a participant on a neighboring region may be + // connected to multiple servers. We don't want to + // add new identical participants from all of those servers. + if (voice_data[participant_id].isMember("j")) + { + // a new participant has announced that they're joining. + joined = true; + primary = voice_data[participant_id]["j"].get("p", Json::Value(false)).asBool(); + + // track incoming participants that are muted so we can mute their connections (or set their volume) + bool isMuted = LLMuteList::getInstance()->isMuted(agent_id, LLMute::flagVoiceChat); + if (isMuted) + { + mute[participant_id] = true; + } + F32 volume; + if(LLSpeakerVolumeStorage::getInstance()->getSpeakerVolume(agent_id, volume)) + { + user_gain[participant_id] = (uint32_t)(volume * 200); + } + } + + new_participant |= joined; + if (!participant && joined && (primary || !isSpatial())) + { + participant = LLWebRTCVoiceClient::getInstance()->addParticipantByID(mChannelID, agent_id); + } + + if (participant) + { + if (voice_data[participant_id].get("l", Json::Value(false)).asBool()) + { + // an existing participant is leaving. + if (agent_id != gAgentID) + { + LLWebRTCVoiceClient::getInstance()->removeParticipantByID(mChannelID, agent_id); + } + } + else + { + // we got a 'power' update. + F32 level = (F32) (voice_data[participant_id].get("p", Json::Value(participant->mLevel)).asInt()) / 128; + // convert to decibles + participant->mLevel = level; + + if (voice_data[participant_id].isMember("v")) + { + participant->mIsSpeaking = voice_data[participant_id].get("v", Json::Value(false)).asBool(); + } + } + } + } + + // tell the simulator to set the mute and volume data for this + // participant, if there are any updates. + Json::FastWriter writer; + Json::Value root = Json::objectValue; + if (mute.size() > 0) + { + root["m"] = mute; + } + if (user_gain.size() > 0) + { + root["ug"] = user_gain; + } + if (root.size() > 0) + { + std::string json_data = writer.write(root); + mWebRTCDataInterface->sendData(json_data, false); + } + } +} + +void LLVoiceWebRTCConnection::OnDataChannelReady(llwebrtc::LLWebRTCDataInterface *data_interface) +{ + if (data_interface) + { + mWebRTCDataInterface = data_interface; + mWebRTCDataInterface->setDataObserver(this); + } +} + +// tell the Secondlife WebRTC server that +// we're joining and whether we're +// joining a server associated with the +// the region we currently occupy or not (primary) +// The WebRTC voice server will pass this info +// to peers. +void LLVoiceWebRTCConnection::sendJoin() +{ + Json::FastWriter writer; + Json::Value root = Json::objectValue; + Json::Value join_obj = Json::objectValue; + LLUUID regionID = gAgent.getRegion()->getRegionID(); + if ((regionID == mRegionID) || !isSpatial()) + { + join_obj["p"] = true; + } + root["j"] = join_obj; + std::string json_data = writer.write(root); + mWebRTCDataInterface->sendData(json_data, false); +} + +///////////////////////////// +// WebRTC Spatial Connection + +LLVoiceWebRTCSpatialConnection::LLVoiceWebRTCSpatialConnection(const LLUUID ®ionID, + S32 parcelLocalID, + const std::string &channelID) : + LLVoiceWebRTCConnection(regionID, channelID), + mParcelLocalID(parcelLocalID) +{ +} + +LLVoiceWebRTCSpatialConnection::~LLVoiceWebRTCSpatialConnection() +{ + if (LLWebRTCVoiceClient::isShuttingDown()) + { + // peer connection and observers will be cleaned up + // by llwebrtc::terminate() on shutdown. + return; + } + assert(mOutstandingRequests == 0); + mWebRTCPeerConnectionInterface->unsetSignalingObserver(this); +} + +void LLVoiceWebRTCSpatialConnection::setMuteMic(bool muted) +{ + mMuted = muted; + if (mWebRTCAudioInterface) + { + LLViewerRegion *regionp = gAgent.getRegion(); + if (regionp && mRegionID == regionp->getRegionID()) + { + mWebRTCAudioInterface->setMute(muted); + } + else + { + // Always mute this agent with respect to neighboring regions. + // Peers don't want to hear this agent from multiple regions + // as that'll echo. + mWebRTCAudioInterface->setMute(true); + } + } +} + +///////////////////////////// +// WebRTC Spatial Connection + +LLVoiceWebRTCAdHocConnection::LLVoiceWebRTCAdHocConnection(const LLUUID ®ionID, + const std::string& channelID, + const std::string& credentials) : + LLVoiceWebRTCConnection(regionID, channelID), + mCredentials(credentials) +{ +} + +LLVoiceWebRTCAdHocConnection::~LLVoiceWebRTCAdHocConnection() +{ + if (LLWebRTCVoiceClient::isShuttingDown()) + { + // peer connection and observers will be cleaned up + // by llwebrtc::terminate() on shutdown. + return; + } + assert(mOutstandingRequests == 0); + mWebRTCPeerConnectionInterface->unsetSignalingObserver(this); +} + +// Add-hoc connections require a different channel type +// as they go to a different set of Secondlife WebRTC servers. +// They also require credentials for the given channels. +// So, we have a separate requestVoiceConnection call. +bool LLVoiceWebRTCAdHocConnection::requestVoiceConnection() +{ + LLViewerRegion *regionp = LLWorld::instance().getRegionFromID(mRegionID); + + LL_DEBUGS("Voice") << "Requesting voice connection." << LL_ENDL; + if (!regionp || !regionp->capabilitiesReceived()) + { + LL_DEBUGS("Voice") << "no capabilities for voice provisioning; waiting " << LL_ENDL; + return false; + } + + std::string url = regionp->getCapability("ProvisionVoiceAccountRequest"); + if (url.empty()) + { + return false; + } + + LLVoiceWebRTCStats::getInstance()->provisionAttemptStart(); + LLSD body; + LLSD jsep; + jsep["type"] = "offer"; + { + LLMutexLock lock(&mVoiceStateMutex); + jsep["sdp"] = mChannelSDP; + } + body["jsep"] = jsep; + body["credentials"] = mCredentials; + body["channel"] = mChannelID; + body["channel_type"] = "multiagent"; + body["voice_server_type"] = WEBRTC_VOICE_SERVER_TYPE; + + LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpPost( + url, + LLCore::HttpRequest::DEFAULT_POLICY_ID, + body, + boost::bind(&LLVoiceWebRTCAdHocConnection::OnVoiceConnectionRequestSuccess, this, _1), + boost::bind(&LLVoiceWebRTCAdHocConnection::OnVoiceConnectionRequestFailure, this, url, 3, body, _1)); + mOutstandingRequests++; + return true; +} diff --git a/indra/newview/llvoicewebrtc.h b/indra/newview/llvoicewebrtc.h new file mode 100644 index 0000000000..0e8ac3a183 --- /dev/null +++ b/indra/newview/llvoicewebrtc.h @@ -0,0 +1,744 @@ +/** + * @file llvoicewebrtc.h + * @brief Declaration of LLWebRTCVoiceClient class which is the interface to the voice client process. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ +#ifndef LL_VOICE_WEBRTC_H +#define LL_VOICE_WEBRTC_H + +class LLWebRTCProtocolParser; + +#include "lliopipe.h" +#include "llpumpio.h" +#include "llchainio.h" +#include "lliosocket.h" +#include "v3math.h" +#include "llframetimer.h" +#include "llviewerregion.h" +#include "llcallingcard.h" // for LLFriendObserver +#include "lleventcoro.h" +#include "llcoros.h" +#include "llparcel.h" +#include "llmutelist.h" +#include <queue> +#include "json/reader.h" + +#ifdef LL_USESYSTEMLIBS +# include "expat.h" +#else +# include "expat/expat.h" +#endif +#include "llvoiceclient.h" + +// WebRTC Includes +#include <llwebrtc.h> + +class LLAvatarName; +class LLVoiceWebRTCConnection; +typedef boost::shared_ptr<LLVoiceWebRTCConnection> connectionPtr_t; + +extern const std::string WEBRTC_VOICE_SERVER_TYPE; + +class LLWebRTCVoiceClient : public LLSingleton<LLWebRTCVoiceClient>, + virtual public LLVoiceModuleInterface, + public llwebrtc::LLWebRTCDevicesObserver, + public LLMuteListObserver +{ + LLSINGLETON_C11(LLWebRTCVoiceClient); + LOG_CLASS(LLWebRTCVoiceClient); + virtual ~LLWebRTCVoiceClient(); + +public: + /// @name LLVoiceModuleInterface virtual implementations + /// @see LLVoiceModuleInterface + //@{ + 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; + + void updateSettings() override; // call after loading settings and whenever they change + + // Returns true if WebRTC has successfully logged in and is not in error state + bool isVoiceWorking() const override; + + std::string sipURIFromID(const LLUUID &id) override; + + ///////////////////// + /// @name Tuning + //@{ + void tuningStart() override; + void tuningStop() override; + bool inTuningMode() override; + + void tuningSetMicVolume(float volume) override; + void tuningSetSpeakerVolume(float volume) override; + float tuningGetEnergy(void) override; + //@} + + ///////////////////// + /// @name Devices + //@{ + // This returns true when it's safe to bring up the "device settings" dialog in the prefs. + bool deviceSettingsAvailable() override; + bool deviceSettingsUpdated() override; //return if the list has been updated and never fetched, only to be called from the voicepanel. + + // Requery the WebRTC daemon for the current list of input/output devices. + // If you pass true for clearCurrentList, deviceSettingsAvailable() will be false until the query has completed + // (use this if you want to know when it's done). + // If you pass false, you'll have no way to know when the query finishes, but the device lists will not appear empty in the interim. + void refreshDeviceLists(bool clearCurrentList = true) override; + + void setCaptureDevice(const std::string& name) override; + void setRenderDevice(const std::string& name) override; + + LLVoiceDeviceList& getCaptureDevices() override; + LLVoiceDeviceList& getRenderDevices() override; + //@} + + void getParticipantList(std::set<LLUUID> &participants) override; + bool isParticipant(const LLUUID& speaker_id) override; + + // Send a text message to the specified user, initiating the session if necessary. + // virtual BOOL sendTextMessage(const LLUUID& participant_id, const std::string& message) const {return false;}; + + // Returns true if calling back the session URI after the session has closed is possible. + // Currently this will be false only for PSTN P2P calls. + // NOTE: this will return true if the session can't be found. + BOOL isSessionCallBackPossible(const LLUUID &session_id) override; + + // WebRTC doesn't preclude text im + BOOL isSessionTextIMPossible(const LLUUID &session_id) override { return TRUE; } + + //////////////////////////// + /// @name Channel stuff + //@{ + // returns true iff the user is currently in a proximal (local spatial) channel. + // Note that gestures should only fire if this returns true. + bool inProximalChannel() override; + + void setNonSpatialChannel(const LLSD& channelInfo, bool notify_on_first_join, bool hangup_on_last_leave) override + { + startAdHocSession(channelInfo, notify_on_first_join, hangup_on_last_leave); + } + + bool setSpatialChannel(const LLSD &channelInfo) override + { + processChannels(true); + return true; + } + + void leaveNonSpatialChannel() override; + + void processChannels(bool process) override; + + void leaveChannel(bool stopTalking); + + bool isCurrentChannel(const LLSD &channelInfo) override; + bool compareChannels(const LLSD &channelInfo1, const LLSD &channelInfo2) override; + //@} + + LLVoiceP2POutgoingCallInterface *getOutgoingCallInterface() override { return nullptr; } + + LLVoiceP2PIncomingCallInterfacePtr getIncomingCallInterface(const LLSD &voice_call_info) override { return nullptr; } + + ///////////////////////// + /// @name Volume/gain + //@{ + void setVoiceVolume(F32 volume) override; + void setMicGain(F32 volume) override; + //@} + + ///////////////////////// + /// @name enable disable voice and features + //@{ + void setVoiceEnabled(bool enabled) override; + void setMuteMic(bool muted) override; // Set the mute state of the local mic. + //@} + + ////////////////////////// + /// @name nearby speaker accessors + std::string getDisplayName(const LLUUID& id) override; + BOOL isParticipantAvatar(const LLUUID &id) override; + BOOL getIsSpeaking(const LLUUID& id) override; + BOOL getIsModeratorMuted(const LLUUID& id) override; + F32 getCurrentPower(const LLUUID& id) override; // "power" is related to "amplitude" in a defined way. I'm just not sure what the formula is... + F32 getUserVolume(const LLUUID& id) override; + void setUserVolume(const LLUUID& id, F32 volume) override; // set's volume for specified agent, from 0-1 (where .5 is nominal) + //@} + + ////////////////// + /// @name LLMuteListObserver + //@{ + void onChange() override; + void onChangeDetailed(const LLMute& ) override; + //@} + + // authorize the user + void userAuthorized(const std::string &user_id, const LLUUID &agentID) override {}; + + + void OnConnectionEstablished(const std::string& channelID, const LLUUID& regionID); + void OnConnectionShutDown(const std::string &channelID, const LLUUID ®ionID); + void OnConnectionFailure(const std::string &channelID, const LLUUID& regionID); + void sendPositionUpdate(bool force); + void updateOwnVolume(); + + ////////////////////////////// + /// @name Status notification + //@{ + void addObserver(LLVoiceClientStatusObserver* observer) override; + void removeObserver(LLVoiceClientStatusObserver* observer) override; + void addObserver(LLFriendObserver* observer) override; + void removeObserver(LLFriendObserver* observer) override; + void addObserver(LLVoiceClientParticipantObserver* observer) override; + void removeObserver(LLVoiceClientParticipantObserver* observer) override; + //@} + + ////////////////////////////// + /// @name Devices change notification + // LLWebRTCDevicesObserver + //@{ + void OnDevicesChanged(const llwebrtc::LLWebRTCVoiceDeviceList &render_devices, + const llwebrtc::LLWebRTCVoiceDeviceList &capture_devices) override; + //@} + + + struct participantState + { + public: + participantState(const LLUUID& agent_id); + + bool isAvatar(); + + std::string mURI; + LLUUID mAvatarID; + std::string mDisplayName; + LLFrameTimer mSpeakingTimeout; + F32 mLevel; // the current audio level of the participant + F32 mVolume; // the gain applied to the participant + bool mIsSpeaking; + bool mIsModeratorMuted; + }; + typedef boost::shared_ptr<participantState> participantStatePtr_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<const LLUUID, participantStatePtr_t> participantUUIDMap; + + class sessionState + { + public: + typedef boost::shared_ptr<sessionState> ptr_t; + typedef boost::weak_ptr<sessionState> wptr_t; + + typedef boost::function<void(const ptr_t &)> sessionFunc_t; + + static void addSession(const std::string &channelID, ptr_t& session); + virtual ~sessionState(); + + participantStatePtr_t addParticipant(const LLUUID& agent_id); + void removeParticipant(const participantStatePtr_t &participant); + void removeAllParticipants(); + + participantStatePtr_t findParticipantByID(const LLUUID& id); + + static ptr_t matchSessionByChannelID(const std::string& channel_id); + + void shutdownAllConnections(); + void revive(); + + static void processSessionStates(); + + virtual bool processConnectionStates(); + + virtual void sendData(const std::string &data); + + void setMuteMic(bool muted); + void setMicGain(F32 volume); + void setSpeakerVolume(F32 volume); + void setUserVolume(const LLUUID& id, F32 volume); + + void setUserMute(const LLUUID& id, bool mute); + + static void for_each(sessionFunc_t func); + + static void reapEmptySessions(); + + bool isEmpty() { return mWebRTCConnections.empty(); } + + virtual bool isSpatial() = 0; + virtual bool isEstate() = 0; + virtual bool isCallbackPossible() = 0; + + std::string mHandle; + std::string mChannelID; + std::string mName; + + bool mMuted; // this session is muted. + F32 mMicGain; // gain for this session. + F32 mSpeakerVolume; // volume for this session. + + bool mShuttingDown; + + participantUUIDMap mParticipantsByUUID; + + static bool hasSession(const std::string &sessionID) + { return mSessions.find(sessionID) != mSessions.end(); } + + bool mHangupOnLastLeave; // notify observers after the session becomes empty. + bool mNotifyOnFirstJoin; // notify observers when the first peer joins. + + protected: + sessionState(); + std::list<connectionPtr_t> mWebRTCConnections; + + private: + + static std::map<std::string, ptr_t> mSessions; // canonical list of outstanding sessions. + + static void for_eachPredicate(const std::pair<std::string, + LLWebRTCVoiceClient::sessionState::wptr_t> &a, + sessionFunc_t func); + }; + + typedef boost::shared_ptr<sessionState> sessionStatePtr_t; + typedef std::map<std::string, sessionStatePtr_t> sessionMap; + + class estateSessionState : public sessionState + { + public: + estateSessionState(); + bool processConnectionStates() override; + + bool isSpatial() override { return true; } + bool isEstate() override { return true; } + bool isCallbackPossible() override { return false; } + }; + + class parcelSessionState : public sessionState + { + public: + parcelSessionState(const std::string& channelID, S32 parcel_local_id); + + bool isSpatial() override { return true; } + bool isEstate() override { return false; } + bool isCallbackPossible() override { return false; } + }; + + class adhocSessionState : public sessionState + { + public: + adhocSessionState(const std::string &channelID, + const std::string& credentials, + bool notify_on_first_join, + bool hangup_on_last_leave); + + bool isSpatial() override { return false; } + bool isEstate() override { return false; } + + // only p2p-type adhoc sessions allow callback + bool isCallbackPossible() override { return mNotifyOnFirstJoin && mHangupOnLastLeave; } + + // don't send spatial data to adhoc sessions. + void sendData(const std::string &data) override { } + + protected: + std::string mCredentials; + }; + + + /////////////////////////////////////////////////////// + // Private Member Functions + ////////////////////////////////////////////////////// + + static void predSendData(const LLWebRTCVoiceClient::sessionStatePtr_t &session, const std::string& spatial_data); + static void predUpdateOwnVolume(const LLWebRTCVoiceClient::sessionStatePtr_t &session, F32 audio_level); + static void predSetMuteMic(const LLWebRTCVoiceClient::sessionStatePtr_t &session, bool mute); + static void predSetMicGain(const LLWebRTCVoiceClient::sessionStatePtr_t &session, F32 volume); + static void predSetSpeakerVolume(const LLWebRTCVoiceClient::sessionStatePtr_t &session, F32 volume); + static void predShutdownSession(const LLWebRTCVoiceClient::sessionStatePtr_t &session); + static void predSetUserMute(const LLWebRTCVoiceClient::sessionStatePtr_t &session, const LLUUID& id, bool mute); + static void predSetUserVolume(const LLWebRTCVoiceClient::sessionStatePtr_t &session, const LLUUID& id, F32 volume); + + //---------------------------------- + // devices + void clearCaptureDevices(); + void addCaptureDevice(const LLVoiceDevice& device); + + void clearRenderDevices(); + void addRenderDevice(const LLVoiceDevice& device); + void setDevicesListUpdated(bool state); + + ///////////////////////////// + // Sending updates of current state + void updatePosition(void); + void setListenerPosition(const LLVector3d &position, const LLVector3 &velocity, const LLQuaternion &rot); + void setAvatarPosition(const LLVector3d &position, const LLVector3 &velocity, const LLQuaternion &rot); + + LLVector3d getListenerPosition() { return mListenerPosition; } + LLVector3d getSpeakerPosition() { return mAvatarPosition; } + + void setEarLocation(S32 loc); + + + ///////////////////////////// + // Accessors for data related to nearby speakers + + ///////////////////////////// + sessionStatePtr_t findP2PSession(const LLUUID &agent_id); + + sessionStatePtr_t addSession(const std::string &channel_id, sessionState::ptr_t session); + void deleteSession(const sessionStatePtr_t &session); + + // Does the actual work to get out of the audio session + void leaveAudioSession(); + + friend class LLWebRTCVoiceClientCapResponder; + + + void lookupName(const LLUUID &id); + void onAvatarNameCache(const LLUUID& id, const LLAvatarName& av_name); + void avatarNameResolved(const LLUUID &id, const std::string &name); + static void predAvatarNameResolution(const LLWebRTCVoiceClient::sessionStatePtr_t &session, LLUUID id, std::string name); + + boost::signals2::connection mAvatarNameCacheConnection; + +private: + + // helper function to retrieve the audio level + // Used in multiple places. + float getAudioLevel(); + + // Coroutine support methods + //--- + void voiceConnectionCoro(); + + //--- + /// Clean up objects created during a voice session. + void cleanUp(); + + bool mTuningMode; + F32 mTuningMicGain; + int mTuningSpeakerVolume; + 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 mSpatialSessionCredentials; + + std::string mMainSessionGroupHandle; // handle of the "main" session group. + + sessionStatePtr_t mSession; // Session state for the current session + + sessionStatePtr_t mNextSession; // Session state for the session we're trying to join + + llwebrtc::LLWebRTCDeviceInterface *mWebRTCDeviceInterface; + + LLVoiceDeviceList mCaptureDevices; + LLVoiceDeviceList mRenderDevices; + + bool startEstateSession(); + bool startParcelSession(const std::string& channelID, S32 parcelID); + bool startAdHocSession(const LLSD &channelInfo, bool notify_on_first_join, bool hangup_on_last_leave); + + bool inSpatialChannel(); + bool inOrJoiningChannel(const std::string &channelID); + bool inEstateChannel(); + + LLSD getAudioSessionChannelInfo(); + + void setHidden(bool hidden) override; //virtual + + void enforceTether(); + + void updateNeighboringRegions(); + std::set<LLUUID> getNeighboringRegions() { return mNeighboringRegions; } + + LLVoiceVersionInfo mVoiceVersion; + + bool mSpatialCoordsDirty; + + LLVector3d mListenerPosition; + LLVector3d mListenerRequestedPosition; + LLVector3 mListenerVelocity; + LLQuaternion mListenerRot; + + LLVector3d mAvatarPosition; + LLVector3 mAvatarVelocity; + LLQuaternion mAvatarRot; + + std::set<LLUUID> mNeighboringRegions; // includes current region + + bool mMuteMic; + bool mHidden; //Set to true during teleport to hide the agent's position. + + enum + { + earLocCamera = 0, // ear at camera + earLocAvatar, // ear at avatar + earLocMixed // ear at avatar location/camera direction + }; + + S32 mEarLocation; + + float mSpeakerVolume; + + F32 mMicGain; + + bool mVoiceEnabled; + bool mProcessChannels; + + typedef std::set<LLVoiceClientParticipantObserver*> observer_set_t; + observer_set_t mParticipantObservers; + + void notifyParticipantObservers(); + + typedef std::set<LLVoiceClientStatusObserver*> status_observer_set_t; + status_observer_set_t mStatusObservers; + + void notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status); + + bool mIsInTuningMode; + bool mIsProcessingChannels; + bool mIsCoroutineActive; + + // These variables can last longer than WebRTC in coroutines so we need them as static + static bool sShuttingDown; + + LLEventMailDrop mWebRTCPump; +}; + + +class LLVoiceWebRTCStats : public LLSingleton<LLVoiceWebRTCStats> +{ + LLSINGLETON(LLVoiceWebRTCStats); + LOG_CLASS(LLVoiceWebRTCStats); + virtual ~LLVoiceWebRTCStats(); + + private: + F64SecondsImplicit mStartTime; + + U32 mConnectCycles; + + F64 mConnectTime; + U32 mConnectAttempts; + + F64 mProvisionTime; + U32 mProvisionAttempts; + + F64 mEstablishTime; + U32 mEstablishAttempts; + + public: + + void reset(); + void connectionAttemptStart(); + void connectionAttemptEnd(bool success); + void provisionAttemptStart(); + void provisionAttemptEnd(bool success); + void establishAttemptStart(); + void establishAttemptEnd(bool success); + LLSD read(); +}; + +class LLVoiceWebRTCConnection : + public llwebrtc::LLWebRTCSignalingObserver, + public llwebrtc::LLWebRTCDataObserver +{ + public: + LLVoiceWebRTCConnection(const LLUUID ®ionID, const std::string &channelID); + + virtual ~LLVoiceWebRTCConnection() = 0; + + ////////////////////////////// + /// @name Signaling notification + // LLWebRTCSignalingObserver + //@{ + void OnIceGatheringState(EIceGatheringState 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; + void OnPeerConnectionShutdown() override; + //@} + + ///////////////////////// + /// @name Data Notification + /// LLWebRTCDataObserver + //@{ + void OnDataReceived(const std::string &data, bool binary) override; + void OnDataChannelReady(llwebrtc::LLWebRTCDataInterface *data_interface) override; + //@} + + void sendJoin(); + void sendData(const std::string &data); + + virtual void processIceUpdates(); + virtual void onIceUpdateComplete(bool ice_completed, const LLSD &result); + virtual void onIceUpdateError(int retries, std::string url, LLSD body, bool ice_completed, const LLSD &result); + + virtual void setMuteMic(bool muted); + virtual void setMicGain(F32 volume); + virtual void setSpeakerVolume(F32 volume); + + void setUserVolume(const LLUUID& id, F32 volume); + void setUserMute(const LLUUID& id, bool mute); + + bool connectionStateMachine(); + + virtual bool isSpatial() = 0; + + LLUUID getRegionID() { return mRegionID; } + + void shutDown() + { + LLMutexLock lock(&mVoiceStateMutex); + mShutDown = true; + } + + void OnVoiceConnectionRequestSuccess(const LLSD &body); + void OnVoiceConnectionRequestFailure(std::string url, int retries, LLSD body, const LLSD &result); + + 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_WAIT_FOR_DATA_CHANNEL = 0x20, + VOICE_STATE_SESSION_UP = 0x40, + VOICE_STATE_SESSION_RETRY = 0x80, + VOICE_STATE_DISCONNECT = 0x100, + VOICE_STATE_WAIT_FOR_EXIT = 0x200, + VOICE_STATE_SESSION_EXIT = 0x400, + VOICE_STATE_SESSION_STOPPING = 0x780 + } 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() + { + if (mVoiceStateMutex.isLocked()) + { + LL_WARNS("Voice") << "LOCKED." << LL_ENDL; + } + LLMutexLock lock(&mVoiceStateMutex); + return mVoiceConnectionState; + } + + virtual bool requestVoiceConnection() = 0; + + bool breakVoiceConnection(bool wait); + void OnVoiceDisconnectionRequestSuccess(const LLSD &body); + void OnVoiceDisconnectionRequestFailure(std::string url, int retries, LLSD body, const LLSD &result); + + LLUUID mRegionID; + LLUUID mViewerSession; + std::string mChannelID; + + std::string mChannelSDP; + std::string mRemoteChannelSDP; + + bool mMuted; + F32 mMicGain; + F32 mSpeakerVolume; + + bool mShutDown; + S32 mOutstandingRequests; + + std::vector<llwebrtc::LLWebRTCIceCandidate> mIceCandidates; + bool mIceCompleted; + bool mTrickling; + + llwebrtc::LLWebRTCPeerConnectionInterface *mWebRTCPeerConnectionInterface; + llwebrtc::LLWebRTCAudioInterface *mWebRTCAudioInterface; + llwebrtc::LLWebRTCDataInterface *mWebRTCDataInterface; +}; + + +class LLVoiceWebRTCSpatialConnection : + public LLVoiceWebRTCConnection +{ + public: + LLVoiceWebRTCSpatialConnection(const LLUUID ®ionID, S32 parcelLocalID, const std::string &channelID); + + virtual ~LLVoiceWebRTCSpatialConnection(); + + void setMuteMic(bool muted) override; + + bool isSpatial() override { return true; } + + +protected: + + bool requestVoiceConnection() override; + + S32 mParcelLocalID; +}; + +class LLVoiceWebRTCAdHocConnection : public LLVoiceWebRTCConnection +{ + public: + LLVoiceWebRTCAdHocConnection(const LLUUID ®ionID, const std::string &channelID, const std::string& credentials); + + virtual ~LLVoiceWebRTCAdHocConnection(); + + bool isSpatial() override { return false; } + + protected: + bool requestVoiceConnection() override; + + std::string mCredentials; +}; + +#define VOICE_ELAPSED LLVoiceTimer(__FUNCTION__); + +#endif //LL_WebRTC_VOICE_CLIENT_H + diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index c7f32d0da9..bcae75425d 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -547,6 +547,12 @@ class Windows_x86_64_Manifest(ViewerManifest): # Get shared libs from the shared libs staging directory with self.prefix(src=os.path.join(self.args['build'], os.pardir, 'sharedlibs', self.args['buildtype'])): + # WebRTC libraries + for libfile in ( + 'llwebrtc.dll', + ): + self.path(libfile) + # Get fmodstudio dll if needed if self.args['fmodstudio'] == 'ON': if(self.args['buildtype'].lower() == 'debug'): @@ -994,6 +1000,20 @@ class Darwin_x86_64_Manifest(ViewerManifest): print("Skipping %s" % dst) return added + # WebRTC libraries + with self.prefix(src=os.path.join(self.args['build'], os.pardir, + 'sharedlibs', self.args['buildtype'], 'Resources')): + for libfile in ( + 'libllwebrtc.dylib', + ): + self.path(libfile) + + oldpath = os.path.join("@rpath", libfile) + self.run_command( + ['install_name_tool', '-change', oldpath, + '@executable_path/../Resources/%s' % libfile, + executable]) + # dylibs is a list of all the .dylib files we expect to need # in our bundled sub-apps. For each of these we'll create a # symlink from sub-app/Contents/Resources to the real .dylib. |