summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoxanne Skelly <roxie@lindenlab.com>2026-10-21 10:21:55 -0700
committerGitHub <noreply@github.com>2026-10-21 10:21:55 -0700
commit14539cf4ad4e3cbcb345f062f400c1fc2d1f9afc (patch)
tree857cd769e56f29700fdd800c0dad5d730a403436
parentf78a4b1ba9bfe27c8ae9aac61cf1cd8fee940839 (diff)
parent2f9cd9b6605d790577ddda32c8d9cb487e6c9363 (diff)
Merge pull request #6447 from secondlife/roxie/mic_stop_on_mute
Add 'Mic Stop On Mute' checkbox to change behavior of handsfree mode for bluetooth headsets
-rw-r--r--autobuild.xml14
-rw-r--r--indra/llwebrtc/CMakeLists.txt4
-rw-r--r--indra/llwebrtc/llwebrtc.cpp175
-rw-r--r--indra/llwebrtc/llwebrtc.h10
-rw-r--r--indra/llwebrtc/llwebrtc_impl.h22
-rw-r--r--indra/newview/app_settings/settings.xml11
-rw-r--r--indra/newview/llvoicewebrtc.cpp31
-rw-r--r--indra/newview/llvoicewebrtc.h1
-rw-r--r--indra/newview/skins/default/xui/en/panel_preferences_sound.xml41
9 files changed, 251 insertions, 58 deletions
diff --git a/autobuild.xml b/autobuild.xml
index fe13859583..19db4348b6 100644
--- a/autobuild.xml
+++ b/autobuild.xml
@@ -2454,11 +2454,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<key>archive</key>
<map>
<key>hash</key>
- <string>9622dbeb12d1c9592de3ea3f83c2c1a95b3c0822</string>
+ <string>516b69b841109d38bd14286d28a8d77ee67e1c52</string>
<key>hash_algorithm</key>
<string>sha1</string>
<key>url</key>
- <string>https://github.com/secondlife/3p-webrtc-build/releases/download/m144.7559.06.19.hot-mic/webrtc-m144.7559.06.19.hot-mic.29761579711-darwin64-29761579711.tar.zst</string>
+ <string>https://github.com/secondlife/3p-webrtc-build/releases/download/m144.7559.06.22/webrtc-m144.7559.06.22.38629291819-darwin64-38629291819.tar.zst</string>
</map>
<key>name</key>
<string>darwin64</string>
@@ -2468,11 +2468,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<key>archive</key>
<map>
<key>hash</key>
- <string>9113fdfafca5ca750e415abafaf080540346e068</string>
+ <string>69429645f9f5eb0080546af0f4e32082f285a852</string>
<key>hash_algorithm</key>
<string>sha1</string>
<key>url</key>
- <string>https://github.com/secondlife/3p-webrtc-build/releases/download/m144.7559.06.19.hot-mic/webrtc-m144.7559.06.19.hot-mic.29761579711-linux64-29761579711.tar.zst</string>
+ <string>https://github.com/secondlife/3p-webrtc-build/releases/download/m144.7559.06.22/webrtc-m144.7559.06.22.38629291819-linux64-38629291819.tar.zst</string>
</map>
<key>name</key>
<string>linux64</string>
@@ -2482,11 +2482,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<key>archive</key>
<map>
<key>hash</key>
- <string>1d8a82b72887abfb1c5014fcca4337703dfdcd25</string>
+ <string>a9ef8bafede7a26d561faca7d770a31aebeda3a5</string>
<key>hash_algorithm</key>
<string>sha1</string>
<key>url</key>
- <string>https://github.com/secondlife/3p-webrtc-build/releases/download/m144.7559.06.19.hot-mic/webrtc-m144.7559.06.19.hot-mic.29761579711-windows64-29761579711.tar.zst</string>
+ <string>https://github.com/secondlife/3p-webrtc-build/releases/download/m144.7559.06.22/webrtc-m144.7559.06.22.38629291819-windows64-38629291819.tar.zst</string>
</map>
<key>name</key>
<string>windows64</string>
@@ -2499,7 +2499,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<key>copyright</key>
<string>Copyright (c) 2011, The WebRTC project authors. All rights reserved.</string>
<key>version</key>
- <string>m144.7559.06.19.hot-mic.29761579711</string>
+ <string>m144.7559.06.22.38629291819</string>
<key>name</key>
<string>webrtc</string>
<key>vcs_branch</key>
diff --git a/indra/llwebrtc/CMakeLists.txt b/indra/llwebrtc/CMakeLists.txt
index 9d5cde0e6a..04989d7b42 100644
--- a/indra/llwebrtc/CMakeLists.txt
+++ b/indra/llwebrtc/CMakeLists.txt
@@ -40,7 +40,8 @@ if (WINDOWS)
msdmo
strmiids
iphlpapi
- libcmt)
+ libcmt
+ crypt32)
# as the webrtc libraries are release, build this binary as release as well.
target_compile_options(llwebrtc PRIVATE "/MT" "/Zc:wchar_t")
if (USE_BUGSPLAT)
@@ -54,6 +55,7 @@ elseif (DARWIN)
"-framework CoreMedia"
"-framework CoreVideo"
"-framework Foundation"
+ "-framework Security"
)
if (USE_BUGSPLAT)
set_target_properties(llwebrtc PROPERTIES XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT "dwarf-with-dsym"
diff --git a/indra/llwebrtc/llwebrtc.cpp b/indra/llwebrtc/llwebrtc.cpp
index 87ed21291f..ae1980234c 100644
--- a/indra/llwebrtc/llwebrtc.cpp
+++ b/indra/llwebrtc/llwebrtc.cpp
@@ -45,13 +45,15 @@
namespace llwebrtc
{
#if WEBRTC_WIN
-static int16_t PLAYOUT_DEVICE_DEFAULT = webrtc::AudioDeviceModule::kDefaultCommunicationDevice;
-static int16_t RECORD_DEVICE_DEFAULT = webrtc::AudioDeviceModule::kDefaultCommunicationDevice;
+static int16_t PLAYOUT_DEVICE_DEFAULT = webrtc::AudioDeviceModule::kDefaultDevice;
+static int16_t RECORD_DEVICE_DEFAULT = webrtc::AudioDeviceModule::kDefaultDevice;
#else
static int16_t PLAYOUT_DEVICE_DEFAULT = 0;
static int16_t RECORD_DEVICE_DEFAULT = 0;
#endif
+constexpr int32_t MIC_CAPTURE_STOP_DELAY_MS = 40;
+
//
// LLWebRTCAudioTransport implementation
@@ -265,19 +267,18 @@ void LLWebRTCAudioDeviceModule::SetTuning(bool tuning, bool mute)
tuning_ = tuning;
if (tuning)
{
- // Ensure capture is running (it's normally already running -- capture is
- // session-long) so the mic-level meter works, and stop rendering the
- // call while tuning. The recording calls are no-ops if capture is
- // already active, so this won't cold-start it.
+ // Ensure capture is running so the mic-level meter works, and stop rendering
+ // the call while tuning. The recording calls are no-ops if capture is already
+ // active (it may not be, with "stop capture while muted" enabled).
inner_->InitMicrophone();
inner_->InitRecording();
inner_->StartRecording();
inner_->StopPlayout();
}
- // On exit, capture is deliberately left running (mute is handled by gain,
- // not by stopping the device, so there's no AEC cold-start hiss). Playout
- // is restored by the caller via workerOpenPlayout(), keeping it gated on
- // there being a connection to render.
+ // On exit, capture is left running here; with "stop capture while muted" the
+ // caller (setTuningMode) stops it again if we're muted or have no connection.
+ // Playout is restored by the caller via workerOpenPlayout(), keeping it gated
+ // on there being a connection to render.
}
//
@@ -638,16 +639,21 @@ void LLWebRTCImpl::unsetDevicesObserver(LLWebRTCDevicesObserver *observer)
}
// must be run in the worker thread. Selects the configured capture device and
-// starts recording. Capture runs the whole time voice is enabled (it's never
-// stopped for mute or between calls, so the AEC never cold-starts -- there's no
-// hiss on unmute), so this is a no-op when already recording. Device changes
-// go through workerDeployDevices(), which stops recording first to force a
-// clean re-select; voice off goes through setVoiceEnabled(false).
+// starts recording. By default capture runs the whole time voice is enabled
+// (never stopped for mute or between calls, so the AEC never cold-starts -- no
+// hiss on unmute), so this is a no-op when already recording. With "stop capture
+// while muted" it's only started while unmuted and once a connection's audio is
+// established. Device changes go through workerDeployDevices(), which stops
+// recording first to force a clean re-select; voice off goes through setVoiceEnabled(false).
void LLWebRTCImpl::workerStartRecording()
{
// Only run capture while voice is enabled, and never cold-start it when
- // it's already running (that would cause the unmute hiss).
- if (!mDeviceModule || !mVoiceEnabled || mDeviceModule->Recording())
+ // it's already running (that would cause the unmute hiss). With "stop
+ // capture while muted", also hold it off while muted or with no connection's
+ // audio is established.
+ if (!mDeviceModule || !mVoiceEnabled || mDeviceModule->Recording() ||
+ (mStopCaptureOnMute.load() && !mTuningMode &&
+ (mMute || !mAudioEstablished)))
{
return;
}
@@ -689,7 +695,9 @@ void LLWebRTCImpl::workerStartRecording()
// disable before InitRecording so the recording stream is configured to
// use only WebRTC's software APM.
workerDisableBuiltInAudioProcessing();
- mDeviceModule->InitRecording();
+ // With "stop capture while muted", capture as media; otherwise as communications.
+ mDeviceModule->ForceInitRecording(mStopCaptureOnMute.load() ? webrtc::AudioDeviceModule::AudioCategory::kMedia
+ : webrtc::AudioDeviceModule::AudioCategory::kCommunications);
mDeviceModule->ForceStartRecording();
}
@@ -833,9 +841,9 @@ void LLWebRTCImpl::setVoiceEnabled(bool enable)
}
if (enable)
{
- // Voice on: start the capture device (it then stays running
- // across calls and mute/unmute), and start playout if there's
- // already a connection to render.
+ // Voice on: start the capture device (by default it then stays
+ // running across calls and mute/unmute), and start playout if
+ // there's already a connection to render.
mDeviceModule->Init();
workerDeployDevices();
}
@@ -850,6 +858,33 @@ void LLWebRTCImpl::setVoiceEnabled(bool enable)
});
}
+void LLWebRTCImpl::setStopCaptureOnMute(bool stop)
+{
+ if (mStopCaptureOnMute.exchange(stop) == stop)
+ {
+ return;
+ }
+
+ mWorkerThread->PostTask([this, stop]
+ {
+ if (!mVoiceEnabled || !mDeviceModule || mTuningMode)
+ {
+ return;
+ }
+ if (stop)
+ {
+ if (mMute || !mAudioEstablished)
+ {
+ intSetMute(true, MIC_CAPTURE_STOP_DELAY_MS);
+ }
+ }
+ else
+ {
+ intSetMute(false, 0);
+ }
+ });
+}
+
// updateDevices needs to happen on the worker thread.
void LLWebRTCImpl::updateDevices()
{
@@ -930,6 +965,11 @@ void LLWebRTCImpl::setTuningMode(bool enable)
mDeviceModule->SetTuning(mTuningMode, mMute);
if (!mTuningMode)
{
+ if (mStopCaptureOnMute.load() && (mMute || !mAudioEstablished))
+ {
+ intSetMute(true, MIC_CAPTURE_STOP_DELAY_MS);
+ }
+
// Restore playout after tuning, gated on there being a
// connection to render (so the output device isn't left
// spinning with no engine data).
@@ -1007,15 +1047,53 @@ void LLWebRTCImpl::setMute(bool mute, int delay_ms)
void LLWebRTCImpl::intSetMute(bool mute, int delay_ms)
{
// Mute by zeroing the captured (post-APM) gain; the sender track is also
- // disabled per connection (see LLWebRTCPeerConnectionImpl::setMute). The
- // capture device deliberately stays running for the whole session, so
+ // disabled per connection (see LLWebRTCPeerConnectionImpl::setMute). By
+ // default the capture device stays running for the whole session, so
// muting/unmuting never stops or starts it -- that's what avoids the AEC
- // cold-start hiss on unmute. Capture start/stop is tied to device
- // selection (workerStartRecording) and shutdown, not to mute.
+ // cold-start hiss on unmute. With "stop capture while muted", it's stopped
+ // after the mute delay and restarted on unmute (below).
if (mPeerCustomProcessor)
{
mPeerCustomProcessor->setGain(mMute ? 0.0f : mGain);
}
+
+ if (mute && !mStopCaptureOnMute.load())
+ {
+ return;
+ }
+
+ // Prevent a delayed stop from an earlier mute from stopping a newly restarted recording after a rapid mute/unmute.
+ static std::atomic<uint32_t> mute_sequence(0);
+ uint32_t current_sequence = ++mute_sequence;
+
+ auto setRecording = [this, current_sequence](bool start)
+ {
+ if (!mDeviceModule || !mVoiceEnabled || mTuningMode ||
+ current_sequence != mute_sequence.load())
+ {
+ return;
+ }
+
+ mDeviceModule->ForceStopRecording();
+ if (start)
+ {
+ mDeviceModule->ForceInitRecording(mStopCaptureOnMute.load() ? webrtc::AudioDeviceModule::AudioCategory::kMedia
+ : webrtc::AudioDeviceModule::AudioCategory::kCommunications);
+ mDeviceModule->ForceStartRecording();
+ }
+ };
+
+ if (mute)
+ {
+ mWorkerThread->PostDelayedTask([setRecording]
+ {
+ setRecording(false);
+ }, webrtc::TimeDelta::Millis(delay_ms));
+ }
+ else
+ {
+ mWorkerThread->PostTask([setRecording] { setRecording(true); });
+ }
}
//
@@ -1026,18 +1104,20 @@ LLWebRTCPeerConnectionInterface *LLWebRTCImpl::newPeerConnection()
{
webrtc::scoped_refptr<LLWebRTCPeerConnectionImpl> peerConnection = webrtc::scoped_refptr<LLWebRTCPeerConnectionImpl>(new webrtc::RefCountedObject<LLWebRTCPeerConnectionImpl>(mEnv));
peerConnection->init(this);
- if (mPeerConnections.empty())
+ bool first_connection = mPeerConnections.empty();
+ mPeerConnections.emplace_back(peerConnection);
+ if (first_connection)
{
intSetMute(mMute);
}
- mPeerConnections.emplace_back(peerConnection);
// Playout is intentionally NOT started here. This runs when the connection
// is created/connecting; starting the output device now leaves it spinning
// with no decoded audio during the handshake, which is heard as a buzz.
// Playout is started from OnConnectionChange(kConnected) instead, once audio
- // is actually established (see startPlayout()). Capture follows
- // voice-enabled state, so it's not touched here either.
+ // is actually established (see startPlayout()). Capture isn't started here
+ // either: with "stop capture while muted" it waits for audio to be
+ // established, and the first connection's intSetMute() above may only stop it.
peerConnection->enableSenderTracks(false);
peerConnection->resetMute();
@@ -1054,10 +1134,12 @@ void LLWebRTCImpl::freePeerConnection(LLWebRTCPeerConnectionInterface* peer_conn
mPeerConnections.erase(it);
if (mPeerConnections.empty())
{
+ mAudioEstablished = false;
intSetMute(true);
// Last connection gone: stop playout (there's nothing to render).
- // Capture stays running while voice is enabled so it's ready -- with
- // no cold-start hiss -- when the next call comes up. But if voice
+ // Capture stays running while voice is enabled so it's ready -- with no
+ // cold-start hiss -- when the next call comes up, unless "stop capture while
+ // muted" is on (intSetMute(true) above stops it). But if voice
// has been disabled, stop capture now: setVoiceEnabled(false) tried
// to, but the engine's send stream was still active then (and the
// engine's own StopRecording is intentionally a no-op), so the stop
@@ -1083,17 +1165,27 @@ void LLWebRTCImpl::freePeerConnection(LLWebRTCPeerConnectionInterface* peer_conn
void LLWebRTCImpl::startPlayout()
{
- // Called when a connection's audio is established. Only playout is started
- // here: it's gated on there being a connection to render, because running
- // the output device with no engine data is heard as a buzz. Capture is
- // NOT touched here -- it follows voice-enabled state (setVoiceEnabled), so
- // it's already running if voice is on and must stay off if voice is off.
- // Starting it here would also let a stray kConnected during voice-disable
- // teardown re-open the mic.
+ // Called when a connection's audio is established. Playout is started here:
+ // it's gated on there being a connection to render, because running the
+ // output device with no engine data is heard as a buzz.
+ // Capture normally follows voice-enabled state (setVoiceEnabled), so it's
+ // already running if voice is on. The exception is "stop capture while
+ // muted", where it's held off until unmuted and audio is established.
+ // workerStartRecording() is gated on mVoiceEnabled, so this can't re-open
+ // the mic during voice-disable teardown.
mWorkerThread->PostTask(
[this]()
{
+ if (mPeerConnections.empty())
+ {
+ return;
+ }
+ mAudioEstablished = true;
workerStartPlayout();
+ if (mStopCaptureOnMute.load())
+ {
+ workerStartRecording();
+ }
});
}
@@ -1583,9 +1675,10 @@ void LLWebRTCPeerConnectionImpl::OnConnectionChange(webrtc::PeerConnectionInterf
case webrtc::PeerConnectionInterface::PeerConnectionState::kConnected:
{
// Audio is established now -- start playout for this connection.
- // (Capture follows voice-enabled state, so it's already running and
- // isn't touched here.) Doing playout here rather than at connection
- // creation avoids running the output device with no decoded audio
+ // (Capture normally follows voice-enabled state, so it's already running;
+ // startPlayout() starts it only for "stop capture while muted".)
+ // Doing playout here rather than at connection creation
+ // avoids running the output device with no decoded audio
// during the handshake (heard as a buzz).
mWebRTCImpl->startPlayout();
mPendingJobs++;
diff --git a/indra/llwebrtc/llwebrtc.h b/indra/llwebrtc/llwebrtc.h
index 821400cfe8..13078e39ea 100644
--- a/indra/llwebrtc/llwebrtc.h
+++ b/indra/llwebrtc/llwebrtc.h
@@ -156,10 +156,14 @@ class LLWebRTCDeviceInterface
// Enable/disable the audio devices, set when voice is enabled/disabled.
// The capture (microphone) and playout (speaker) devices only run while this
// is enabled, so neither is held open when the user has voice off. While
- // enabled, capture stays running across calls and mute/unmute so the AEC
- // never cold-starts (no unmute hiss); playout still only runs when there's a
- // connection to render.
+ // enabled, capture normally stays running across calls and mute/unmute so the
+ // AEC never cold-starts (no unmute hiss), unless setStopCaptureOnMute() is on;
+ // playout still only runs when there's a connection to render.
virtual void setVoiceEnabled(bool enable) = 0;
+ // When true, capture is stopped while muted (or with no connection) and
+ // restarted on unmute, so Bluetooth headsets aren't held in call mode.
+ // May cause a brief hiss on unmute.
+ virtual void setStopCaptureOnMute(bool stop) = 0;
// Device observers for device change callbacks.
virtual void setDevicesObserver(LLWebRTCDevicesObserver *observer) = 0;
diff --git a/indra/llwebrtc/llwebrtc_impl.h b/indra/llwebrtc/llwebrtc_impl.h
index c980b28d6e..567948ece2 100644
--- a/indra/llwebrtc/llwebrtc_impl.h
+++ b/indra/llwebrtc/llwebrtc_impl.h
@@ -234,7 +234,7 @@ public:
int32_t SetRecordingDevice(WindowsDeviceType type) override { return inner_->SetRecordingDevice(type); }
// --- Init/start/stop (forward) ---
- int32_t InitPlayout() override { return inner_->InitPlayout(); }
+ int32_t InitPlayout(AudioCategory category = AudioCategory::kMedia) override { return inner_->InitPlayout(category); }
bool PlayoutIsInitialized() const override { return inner_->PlayoutIsInitialized(); }
int32_t StartPlayout() override {
if (tuning_) return 0; // For tuning, don't allow playout
@@ -243,7 +243,14 @@ public:
int32_t StopPlayout() override { return inner_->StopPlayout(); }
bool Playing() const override { return inner_->Playing(); }
- int32_t InitRecording() override { return inner_->InitRecording(); }
+ int32_t InitRecording(AudioCategory category = AudioCategory::kMedia) override
+ {
+ // Ignore webrtc.lib's InitRecording (sent when a send stream starts), as
+ // with StartRecording/StopRecording: it would leave capture initialised even
+ // while we hold capture off. The app initialises capture itself via ForceInitRecording.
+ return 0;
+ }
+ int32_t ForceInitRecording(AudioCategory category = AudioCategory::kMedia) { return inner_->InitRecording(category); }
bool RecordingIsInitialized() const override { return inner_->RecordingIsInitialized(); }
int32_t StartRecording() override {
// ignore start recording as webrtc.lib will
@@ -442,6 +449,7 @@ class LLWebRTCImpl : public LLWebRTCDeviceInterface, public webrtc::AudioDeviceO
void setRenderDevice(const std::string& id) override;
void setVoiceEnabled(bool enable) override;
+ void setStopCaptureOnMute(bool stop) override;
void setTuningMode(bool enable) override;
float getTuningAudioLevel() override;
@@ -521,8 +529,9 @@ class LLWebRTCImpl : public LLWebRTCDeviceInterface, public webrtc::AudioDeviceO
void freePeerConnection(LLWebRTCPeerConnectionInterface* peer_connection);
// Start playout once a connection's audio is established (playout is gated
- // on there being a connection to render). Capture is not touched here --
- // it follows voice-enabled state, not connection state. Safe to call from
+ // on there being a connection to render). Capture normally follows
+ // voice-enabled state, not connection state; the exception is "stop capture
+ // while muted", where it's also started here if unmuted. Safe to call from
// any thread (work is posted to the worker thread).
void startPlayout();
@@ -572,6 +581,11 @@ class LLWebRTCImpl : public LLWebRTCDeviceInterface, public webrtc::AudioDeviceO
// Whether voice is enabled; gates whether the capture/playout devices run.
bool mVoiceEnabled;
float mGain;
+ std::atomic<bool> mStopCaptureOnMute{ false };
+ // True once any connection's audio has been established, until the last
+ // connection is freed (not a count of currently-connected peers). Used to
+ // hold capture off until a call is actually up.
+ std::atomic<bool> mAudioEstablished{ false };
LLCustomProcessorStatePtr mPeerCustomProcessor;
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index d476848e14..c3c59a3e29 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -16792,5 +16792,16 @@
<key>Value</key>
<integer>0</integer>
</map>
+ <key>StopMicCaptureWhenMuted</key>
+ <map>
+ <key>Comment</key>
+ <string>Check to stop microphone capture while muted. May cause a hiss on unmute but prevents bluetooth devices always being in voice mode.</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>1</integer>
+ </map>
</map>
</llsd>
diff --git a/indra/newview/llvoicewebrtc.cpp b/indra/newview/llvoicewebrtc.cpp
index 413f6270c6..cd5a15e891 100644
--- a/indra/newview/llvoicewebrtc.cpp
+++ b/indra/newview/llvoicewebrtc.cpp
@@ -95,6 +95,7 @@ namespace {
const uint32_t SET_HIDDEN_RESTORE_DELAY_MS = 200; // 200 ms to unmute again after hiding during teleport
const uint32_t MUTE_FADE_DELAY_MS = 500; // 20ms fade followed by 480ms silence gets rid of the click just after unmuting.
// This is because the buffers and processing is cleared by the silence.
+ // With StopMicCaptureWhenMuted, this is also the delay before capture is actually stopped.
const F32 SPEAKING_AUDIO_LEVEL = 0.30;
@@ -260,6 +261,11 @@ void LLWebRTCVoiceClient::cleanupSingleton()
mAvatarNameCacheConnection.disconnect();
}
+ if (mStopMicCaptureOnMuteConnection.connected())
+ {
+ mStopMicCaptureOnMuteConnection.disconnect();
+ }
+
sShuttingDown = true;
if (mSession)
{
@@ -309,6 +315,21 @@ void LLWebRTCVoiceClient::initWebRTC()
});
}
+ // Connect StopMicCaptureWhenMuted control so the changes apply instantly
+ if (!mStopMicCaptureOnMuteConnection.connected())
+ {
+ mStopMicCaptureOnMuteConnection =
+ gSavedSettings.getControl("StopMicCaptureWhenMuted")->getSignal()->connect(
+ [this](LLControlVariable*, const LLSD& new_val, const LLSD&)
+ {
+ if (mWebRTCDeviceInterface)
+ {
+ mWebRTCDeviceInterface->setStopCaptureOnMute(new_val.asBoolean());
+ }
+ });
+ }
+ mWebRTCDeviceInterface->setStopCaptureOnMute(gSavedSettings.getBOOL("StopMicCaptureWhenMuted"));
+
refreshDeviceLists();
}
@@ -1736,14 +1757,20 @@ void LLWebRTCVoiceClient::setMuteMic(bool muted)
mMuteMic = muted;
- if (mIsInTuningMode)
+ if (mIsInTuningMode &&!gSavedSettings.getBOOL("StopMicCaptureWhenMuted"))
{
return;
}
if (mWebRTCDeviceInterface)
{
- mWebRTCDeviceInterface->setMute(muted, muted ? MUTE_FADE_DELAY_MS : 0); // delay for 40ms on mute to allow buffers to empty
+ // Delay on mute to let the fade and silence flush buffers (and, if enabled, before capture stops)
+ mWebRTCDeviceInterface->setMute(muted, muted ? MUTE_FADE_DELAY_MS : 0);
+ }
+
+ if (mIsInTuningMode)
+ {
+ return;
}
// when you're hidden, your mic is always muted.
diff --git a/indra/newview/llvoicewebrtc.h b/indra/newview/llvoicewebrtc.h
index 76e172ff3d..b507644503 100644
--- a/indra/newview/llvoicewebrtc.h
+++ b/indra/newview/llvoicewebrtc.h
@@ -454,6 +454,7 @@ public:
static void predAvatarNameResolution(const LLWebRTCVoiceClient::sessionStatePtr_t &session, LLUUID id, std::string name);
boost::signals2::connection mAvatarNameCacheConnection;
+ boost::signals2::connection mStopMicCaptureOnMuteConnection;
private:
// init or restart the WebRTC device interface.
diff --git a/indra/newview/skins/default/xui/en/panel_preferences_sound.xml b/indra/newview/skins/default/xui/en/panel_preferences_sound.xml
index 18fd7fe2d3..cdc0ed157a 100644
--- a/indra/newview/skins/default/xui/en/panel_preferences_sound.xml
+++ b/indra/newview/skins/default/xui/en/panel_preferences_sound.xml
@@ -456,4 +456,45 @@
top_delta="25"
width="15"
height="15"/>
+ <text
+ type="string"
+ length="1"
+ follows="left|top"
+ layout="topleft"
+ left="15"
+ top_delta="30"
+ font.style="BOLD"
+ name="bluetooth_lbl"
+ height="15"
+ width="110">
+ Bluetooth only
+ </text>
+ <radio_group
+ control_name="StopMicCaptureWhenMuted"
+ follows="left|top"
+ height="60"
+ layout="topleft"
+ left="25"
+ name="bluetooth_behavior"
+ top_pad="8"
+ width="480">
+ <radio_item
+ height="20"
+ label="Better voice"
+ layout="topleft"
+ left="0"
+ name="better_voice"
+ value="0"
+ top="0"
+ width="480" />
+ <radio_item
+ height="20"
+ label="Better in-world audio"
+ layout="topleft"
+ left_delta="0"
+ name="better_audio"
+ value="1"
+ top_delta="24"
+ width="480" />
+ </radio_group>
</panel>