From 0a14318dd6cb9e2eae142956126cfd000c3027d9 Mon Sep 17 00:00:00 2001 From: Callum Prentice Date: Wed, 8 Oct 2025 18:58:27 -0700 Subject: [PATCH 01/10] First commit for a second experiment, investigating how nearby voice/chat moderation might work --- indra/newview/CMakeLists.txt | 2 + indra/newview/llfloaterimcontainer.cpp | 96 +++++++++++++++- indra/newview/llfloaterimcontainer.h | 2 + indra/newview/llnearbyvoicemoderation.cpp | 102 +++++++++++++++++ indra/newview/llnearbyvoicemoderation.h | 41 +++++++ .../default/xui/en/menu_conversation.xml | 105 +++++++++--------- 6 files changed, 291 insertions(+), 57 deletions(-) create mode 100644 indra/newview/llnearbyvoicemoderation.cpp create mode 100644 indra/newview/llnearbyvoicemoderation.h diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index c727d5ae572..239193001cf 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -414,6 +414,7 @@ set(viewer_SOURCE_FILES llfloaterimnearbychat.cpp llfloaterimnearbychathandler.cpp llfloaterimnearbychatlistener.cpp + llnearbyvoicemoderation.cpp llnetmap.cpp llnotificationalerthandler.cpp llnotificationgrouphandler.cpp @@ -1087,6 +1088,7 @@ set(viewer_HEADER_FILES llnameeditor.h llnamelistctrl.h llnavigationbar.h + llnearbyvoicemoderation.h llnetmap.h llnotificationhandler.h llnotificationlistitem.h diff --git a/indra/newview/llfloaterimcontainer.cpp b/indra/newview/llfloaterimcontainer.cpp index d821d9a4a56..9a016f6286a 100644 --- a/indra/newview/llfloaterimcontainer.cpp +++ b/indra/newview/llfloaterimcontainer.cpp @@ -57,6 +57,8 @@ #include "llsdserialize.h" #include "llviewermenu.h" // is_agent_mappable #include "llviewerobjectlist.h" +#include "llvoavatar.h" +#include "llnearbyvoicemoderation.h" const S32 EVENTS_PER_IDLE_LOOP_CURRENT_SESSION = 80; @@ -502,12 +504,13 @@ void LLFloaterIMContainer::idleUpdate() const LLConversationItem *current_session = getCurSelectedViewModelItem(); if (current_session) { - if (current_session->getType() == LLConversationItem::CONV_SESSION_GROUP) + bool is_nearby_chat = current_session->getType() == LLConversationItem::CONV_SESSION_NEARBY; + if (current_session->getType() == LLConversationItem::CONV_SESSION_GROUP || is_nearby_chat) { // Update moderator options visibility LLFolderViewModelItemCommon::child_list_t::const_iterator current_participant_model = current_session->getChildrenBegin(); LLFolderViewModelItemCommon::child_list_t::const_iterator end_participant_model = current_session->getChildrenEnd(); - bool is_moderator = isGroupModerator(); + bool is_moderator = isGroupModerator() || (is_nearby_chat && isNearbyChatModerator()); bool can_ban = haveAbilityToBan(); while (current_participant_model != end_participant_model) { @@ -1685,6 +1688,10 @@ bool LLFloaterIMContainer::visibleContextMenuItem(const LLSD& userdata) { return isMuted(conversation_item->getUUID()); } + else if ("can_allow_text_chat" == item) + { + return !isNearbyChatSpeakerSelected(); + } return true; } @@ -2009,9 +2016,27 @@ LLConversationViewParticipant* LLFloaterIMContainer::createConversationViewParti bool LLFloaterIMContainer::enableModerateContextMenuItem(const std::string& userdata, bool is_self) { - // only group moderators can perform actions related to this "enable callback" - if (!isGroupModerator()) + if (isNearbyChatModerator() && isNearbyChatSpeakerSelected()) + { + // Determine here which actions are allowed + if ("can_moderate_voice" == userdata) + { + return true; + } + else if (("can_mute" == userdata)) + { + return true; + } + else if ("can_unmute" == userdata) + { + return true; + } + + return false; + } + else if (!isGroupModerator()) { + // only group moderators can perform actions related to this "enable callback" return false; } @@ -2144,7 +2169,37 @@ void LLFloaterIMContainer::banSelectedMember(const LLUUID& participant_uuid) void LLFloaterIMContainer::moderateVoice(const std::string& command, const LLUUID& userID) { - if (!gAgent.getRegion()) return; + if (!gAgent.getRegion()) + { + return; + } + + if (isNearbyChatSpeakerSelected()) + { + if ("selected" == command) + { + // Toggle the voice icon display + LLAvatarActions::toggleMuteVoice(userID); + + // Request a mute/unmute using a capability request via the simulator + const bool mute_state = LLAvatarActions::isVoiceMuted(userID); + LLNearbyVoiceModeration::getInstance()->requestMuteChange(userID, mute_state); + } + else + if ("mute_all" == command) + { + // TODO: the SpatialVoiceModerationRequest has an mute_all/unmute_all + // verb but we do not have an equivalent of LLAvatarActions::toggleMuteVoice(userID); + // to visually mute all the speaker icons in the conversation floater + } + else + if ("unmute_all" == command) + { + // TODO: same idea as "mute_all" above + } + + return; + } if (command.compare("selected")) { @@ -2262,6 +2317,37 @@ LLSpeaker * LLFloaterIMContainer::getSpeakerOfSelectedParticipant(LLSpeakerMgr * return speaker_managerp->findSpeaker(participant_itemp->getUUID()); } +bool LLFloaterIMContainer::isNearbyChatSpeakerSelected() +{ + LLFolderViewItem *selectedItem = mConversationsRoot->getCurSelectedItem(); + if (NULL == selectedItem) + { + LL_WARNS() << "Current selected item is null" << LL_ENDL; + return NULL; + } + + conversations_widgets_map::const_iterator iter = mConversationsWidgets.begin(); + conversations_widgets_map::const_iterator end = mConversationsWidgets.end(); + const LLUUID * conversation_uuidp = NULL; + while(iter != end) + { + if (iter->second == selectedItem || iter->second == selectedItem->getParentFolder()) + { + conversation_uuidp = &iter->first; + break; + } + ++iter; + } + // Nearby chat ID is LLUUID::null + return conversation_uuidp->isNull(); +} + +bool LLFloaterIMContainer::isNearbyChatModerator() +{ + // TODO: Need a better heurestic for determining if this person is a moderator :) + return true; +} + void LLFloaterIMContainer::toggleAllowTextChat(const LLUUID& participant_uuid) { LLIMSpeakerMgr * speaker_managerp = dynamic_cast(getSpeakerMgrForSelectedParticipant()); diff --git a/indra/newview/llfloaterimcontainer.h b/indra/newview/llfloaterimcontainer.h index 30eed8be365..c91093c1073 100644 --- a/indra/newview/llfloaterimcontainer.h +++ b/indra/newview/llfloaterimcontainer.h @@ -178,6 +178,8 @@ class LLFloaterIMContainer void banSelectedMember(const LLUUID& participant_uuid); void openNearbyChat(); bool isParticipantListExpanded(); + bool isNearbyChatSpeakerSelected(); + bool isNearbyChatModerator(); void idleUpdate(); // for convenience (self) from static idle void idleProcessEvents(); diff --git a/indra/newview/llnearbyvoicemoderation.cpp b/indra/newview/llnearbyvoicemoderation.cpp new file mode 100644 index 00000000000..d714fc36b4f --- /dev/null +++ b/indra/newview/llnearbyvoicemoderation.cpp @@ -0,0 +1,102 @@ +/** + * @file llnearbyvoicemoderation.cpp + * + * $LicenseInfo:firstyear=2008&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 "llagent.h" +#include "llviewerregion.h" +#include "llvoavatar.h" +#include "llviewerobjectlist.h" + +#include "llnearbyvoicemoderation.h" + +LLVOAvatar* LLNearbyVoiceModeration::getVOAvatarFromId(const LLUUID& agent_id) +{ + LLViewerObject *obj = gObjectList.findObject(agent_id); + while (obj && obj->isAttachment()) + { + obj = (LLViewerObject*)obj->getParent(); + } + + if (obj && obj->isAvatar()) + { + return (LLVOAvatar*)obj; + } + else + { + return NULL; + } +} + +void LLNearbyVoiceModeration::requestMuteChange(const LLUUID& agent_id, bool mute) +{ + LLVOAvatar* avatar = getVOAvatarFromId(agent_id); + if (avatar) + { + LLViewerRegion* region = avatar->getRegion(); + if (! region || ! region->capabilitiesReceived()) + { + // TODO: Retry if fails since the capabilities may not have been received + // if this is called early into a region entry + LL_INFOS() << "Region or region capabilities unavailable" << LL_ENDL; + return; + } + LL_INFOS() << "Region name is " << region->getName() << LL_ENDL; + + std::string url = region->getCapability("SpatialVoiceModerationRequest"); + if (url.empty()) + { + // TODO: Retry if fails since URL may not have not be available + // if this is called early into a region entry + LL_INFOS() << "Capability URL is empty" << LL_ENDL; + return; + } + LL_INFOS() << "Capability URL is " << url << LL_ENDL; + + const std::string agent_name = avatar->getFullname(); + + const std::string operand = mute ? "mute" : "unmute"; + + LLSD body; + body["operand"] = operand; + body["agent_id"] = agent_id; + body["moderator_id"] = gAgent.getID(); + + LL_INFOS() << "Resident " << agent_name + << " (" << agent_id << ")" << " applying " << operand << LL_ENDL; + + std::string success_msg = + STRINGIZE("Resident " << agent_name + << " (" << agent_id << ")" << " nearby voice was set to " << operand); + + std::string failure_msg = + STRINGIZE("Unable to change voice muting for resident " + << agent_name << " (" << agent_id << ")"); + + LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, body, + success_msg, + failure_msg); + } +} diff --git a/indra/newview/llnearbyvoicemoderation.h b/indra/newview/llnearbyvoicemoderation.h new file mode 100644 index 00000000000..4275754dbff --- /dev/null +++ b/indra/newview/llnearbyvoicemoderation.h @@ -0,0 +1,41 @@ +/** + * @file llnearbyvoicemoderation.h + * + * $LicenseInfo:firstyear=2008&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$ + */ + +#pragma once + +class LLVOAvatar; + +class LLNearbyVoiceModeration : + public LLSingleton { + LLSINGLETON(LLNearbyVoiceModeration) { + }; + + ~LLNearbyVoiceModeration() { + }; + + public: + LLVOAvatar* getVOAvatarFromId(const LLUUID& id); + void requestMuteChange(const LLUUID& userID, bool mute); +}; diff --git a/indra/newview/skins/default/xui/en/menu_conversation.xml b/indra/newview/skins/default/xui/en/menu_conversation.xml index 62cdaa5886d..cf9b8959ce4 100644 --- a/indra/newview/skins/default/xui/en/menu_conversation.xml +++ b/indra/newview/skins/default/xui/en/menu_conversation.xml @@ -176,57 +176,58 @@ - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - From 219da2a16b3ae6e1d2e02b05159b37fa1aca1f47 Mon Sep 17 00:00:00 2001 From: Callum Prentice Date: Thu, 9 Oct 2025 10:44:32 -0700 Subject: [PATCH 02/10] Remove separator bar for Nearby Voice --- indra/newview/skins/default/xui/en/menu_conversation.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/indra/newview/skins/default/xui/en/menu_conversation.xml b/indra/newview/skins/default/xui/en/menu_conversation.xml index cf9b8959ce4..5a28f0dde57 100644 --- a/indra/newview/skins/default/xui/en/menu_conversation.xml +++ b/indra/newview/skins/default/xui/en/menu_conversation.xml @@ -190,7 +190,9 @@ - + + + Date: Mon, 13 Oct 2025 14:57:01 -0700 Subject: [PATCH 03/10] Improve robustness of when moderator options appear and add some initial code for muting indivudual / everyone via the capability --- indra/newview/llfloaterimcontainer.cpp | 42 +++++++++- indra/newview/llnearbyvoicemoderation.cpp | 97 ++++++++++++++++------- indra/newview/llnearbyvoicemoderation.h | 6 +- 3 files changed, 111 insertions(+), 34 deletions(-) diff --git a/indra/newview/llfloaterimcontainer.cpp b/indra/newview/llfloaterimcontainer.cpp index 9a016f6286a..2b9fb9dd375 100644 --- a/indra/newview/llfloaterimcontainer.cpp +++ b/indra/newview/llfloaterimcontainer.cpp @@ -504,13 +504,12 @@ void LLFloaterIMContainer::idleUpdate() const LLConversationItem *current_session = getCurSelectedViewModelItem(); if (current_session) { - bool is_nearby_chat = current_session->getType() == LLConversationItem::CONV_SESSION_NEARBY; - if (current_session->getType() == LLConversationItem::CONV_SESSION_GROUP || is_nearby_chat) + if (current_session->getType() == LLConversationItem::CONV_SESSION_GROUP) { // Update moderator options visibility LLFolderViewModelItemCommon::child_list_t::const_iterator current_participant_model = current_session->getChildrenBegin(); LLFolderViewModelItemCommon::child_list_t::const_iterator end_participant_model = current_session->getChildrenEnd(); - bool is_moderator = isGroupModerator() || (is_nearby_chat && isNearbyChatModerator()); + bool is_moderator = isGroupModerator(); bool can_ban = haveAbilityToBan(); while (current_participant_model != end_participant_model) { @@ -533,6 +532,23 @@ void LLFloaterIMContainer::idleUpdate() mGeneralTitleInUse = !needs_override; setTitle(needs_override ? conversation_floaterp->getTitle() : mGeneralTitle); } + const LLConversationItem* nearby_session = getSessionModel(LLUUID()); + if (nearby_session) + { + LLFolderViewModelItemCommon::child_list_t::const_iterator current_participant_model = nearby_session->getChildrenBegin(); + LLFolderViewModelItemCommon::child_list_t::const_iterator end_participant_model = nearby_session->getChildrenEnd(); + while (current_participant_model != end_participant_model) + { + LLConversationItemParticipant* participant_model = + dynamic_cast((*current_participant_model).get()); + if (participant_model) + { + participant_model->setModeratorOptionsVisible(isNearbyChatModerator()); + } + + current_participant_model++; + } + } } mParticipantRefreshTimer.setTimerExpirySec(1.0f); @@ -2183,7 +2199,7 @@ void LLFloaterIMContainer::moderateVoice(const std::string& command, const LLUUI // Request a mute/unmute using a capability request via the simulator const bool mute_state = LLAvatarActions::isVoiceMuted(userID); - LLNearbyVoiceModeration::getInstance()->requestMuteChange(userID, mute_state); + LLNearbyVoiceModeration::getInstance()->requestMuteIndividual(userID, mute_state); } else if ("mute_all" == command) @@ -2191,11 +2207,29 @@ void LLFloaterIMContainer::moderateVoice(const std::string& command, const LLUUI // TODO: the SpatialVoiceModerationRequest has an mute_all/unmute_all // verb but we do not have an equivalent of LLAvatarActions::toggleMuteVoice(userID); // to visually mute all the speaker icons in the conversation floater + + // Mute visually too + conversations_widgets_map::const_iterator iter = mConversationsWidgets.begin(); + conversations_widgets_map::const_iterator end = mConversationsWidgets.end(); + const LLUUID * conversation_uuidp = NULL; + while(iter != end) + { + const LLUUID id = (*iter).first; + ++iter; + } + + // Send the mute_all request to the server + const bool mute_state = true; + LLNearbyVoiceModeration::getInstance()->requestMuteAll(mute_state); } else if ("unmute_all" == command) { // TODO: same idea as "mute_all" above + + // Send the unmute_all request to the server + const bool mute_state = false; + LLNearbyVoiceModeration::getInstance()->requestMuteAll(mute_state); } return; diff --git a/indra/newview/llnearbyvoicemoderation.cpp b/indra/newview/llnearbyvoicemoderation.cpp index d714fc36b4f..5ae8feba08c 100644 --- a/indra/newview/llnearbyvoicemoderation.cpp +++ b/indra/newview/llnearbyvoicemoderation.cpp @@ -50,53 +50,92 @@ LLVOAvatar* LLNearbyVoiceModeration::getVOAvatarFromId(const LLUUID& agent_id) } } -void LLNearbyVoiceModeration::requestMuteChange(const LLUUID& agent_id, bool mute) +const std::string LLNearbyVoiceModeration::getCapUrlFromRegion(LLViewerRegion* region) +{ + if (! region || ! region->capabilitiesReceived()) + { + // TODO: Retry if fails since the capabilities may not have been received + // if this is called early into a region entry + LL_INFOS() << "Region or region capabilities unavailable." << LL_ENDL; + return std::string(); + } + LL_INFOS() << "Capabilities for region " << region->getName() << " received." << LL_ENDL; + + std::string url = region->getCapability("SpatialVoiceModerationRequest"); + if (url.empty()) + { + // TODO: Retry if fails since URL may not have not be available + // if this is called early into a region entry + LL_INFOS() << "Capability URL for region " << region->getName() << " is empty" << LL_ENDL; + return std::string(); + } + LL_INFOS() << "Capability URL for region " << region->getName() << " is " << url << LL_ENDL; + + return url; +} + +void LLNearbyVoiceModeration::requestMuteIndividual(const LLUUID& agent_id, bool mute) { LLVOAvatar* avatar = getVOAvatarFromId(agent_id); if (avatar) { - LLViewerRegion* region = avatar->getRegion(); - if (! region || ! region->capabilitiesReceived()) + const std::string cap_url = getCapUrlFromRegion(avatar->getRegion()); + if (cap_url.length()) { - // TODO: Retry if fails since the capabilities may not have been received - // if this is called early into a region entry - LL_INFOS() << "Region or region capabilities unavailable" << LL_ENDL; - return; - } - LL_INFOS() << "Region name is " << region->getName() << LL_ENDL; + const std::string operand = mute ? "mute" : "unmute"; - std::string url = region->getCapability("SpatialVoiceModerationRequest"); - if (url.empty()) - { - // TODO: Retry if fails since URL may not have not be available - // if this is called early into a region entry - LL_INFOS() << "Capability URL is empty" << LL_ENDL; - return; + LLSD body; + body["operand"] = operand; + body["agent_id"] = agent_id; + body["moderator_id"] = gAgent.getID(); + + const std::string agent_name = avatar->getFullname(); + LL_INFOS() << "Resident " << agent_name + << " (" << agent_id << ")" << " applying " << operand << LL_ENDL; + + std::string success_msg = + STRINGIZE("Resident " << agent_name + << " (" << agent_id << ")" << " nearby voice was set to " << operand); + + std::string failure_msg = + STRINGIZE("Unable to change voice muting for resident " + << agent_name << " (" << agent_id << ")"); + + LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost( + cap_url, + body, + success_msg, + failure_msg); } - LL_INFOS() << "Capability URL is " << url << LL_ENDL; + } +} - const std::string agent_name = avatar->getFullname(); +void LLNearbyVoiceModeration::requestMuteAll(bool mute) +{ + // Use our own avatar to get the region name + LLViewerRegion* region = gAgent.getRegion(); - const std::string operand = mute ? "mute" : "unmute"; + const std::string cap_url = getCapUrlFromRegion(region); + if (cap_url.length()) + { + const std::string operand = mute ? "mute_all" : "unmute_all"; LLSD body; body["operand"] = operand; - body["agent_id"] = agent_id; body["moderator_id"] = gAgent.getID(); - LL_INFOS() << "Resident " << agent_name - << " (" << agent_id << ")" << " applying " << operand << LL_ENDL; + LL_INFOS() << "For all residents in this region, applying: " << operand << LL_ENDL; std::string success_msg = - STRINGIZE("Resident " << agent_name - << " (" << agent_id << ")" << " nearby voice was set to " << operand); + STRINGIZE("Nearby voice for all residents was set to: " << operand); std::string failure_msg = - STRINGIZE("Unable to change voice muting for resident " - << agent_name << " (" << agent_id << ")"); + STRINGIZE("Unable to set nearby voice for all residents to: " << operand); - LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, body, - success_msg, - failure_msg); + LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost( + cap_url, + body, + success_msg, + failure_msg); } } diff --git a/indra/newview/llnearbyvoicemoderation.h b/indra/newview/llnearbyvoicemoderation.h index 4275754dbff..8a1ca5af6bd 100644 --- a/indra/newview/llnearbyvoicemoderation.h +++ b/indra/newview/llnearbyvoicemoderation.h @@ -37,5 +37,9 @@ class LLNearbyVoiceModeration : public: LLVOAvatar* getVOAvatarFromId(const LLUUID& id); - void requestMuteChange(const LLUUID& userID, bool mute); + void requestMuteIndividual(const LLUUID& userID, bool mute); + void requestMuteAll(bool mute); + + private: + const std::string getCapUrlFromRegion(LLViewerRegion* region); }; From cf048cf9c3ebd9ca8ee1e606b69bc459b62ed1c5 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Thu, 30 Oct 2025 16:59:51 +0200 Subject: [PATCH 04/10] #4013 Update voice moderator options; show notifications when muted; add stub code for info request --- indra/newview/llfloaterimcontainer.cpp | 35 +----- indra/newview/llfloaterimcontainer.h | 1 - indra/newview/llnearbyvoicemoderation.cpp | 113 +++++++++++++++++- indra/newview/llnearbyvoicemoderation.h | 26 ++-- indra/newview/llviewerregion.cpp | 1 + indra/newview/llvoiceclient.cpp | 4 + indra/newview/llvoicewebrtc.cpp | 8 +- .../skins/default/xui/en/notifications.xml | 23 ++++ 8 files changed, 165 insertions(+), 46 deletions(-) diff --git a/indra/newview/llfloaterimcontainer.cpp b/indra/newview/llfloaterimcontainer.cpp index c25fbdcc51e..ac8234bf24c 100644 --- a/indra/newview/llfloaterimcontainer.cpp +++ b/indra/newview/llfloaterimcontainer.cpp @@ -92,6 +92,7 @@ LLFloaterIMContainer::LLFloaterIMContainer(const LLSD& seed, const Params& param mAutoResize = false; LLTransientFloaterMgr::getInstance()->addControlView(LLTransientFloaterMgr::IM, this); + LLNearbyVoiceModeration::getInstance(); } LLFloaterIMContainer::~LLFloaterIMContainer() @@ -543,7 +544,7 @@ void LLFloaterIMContainer::idleUpdate() dynamic_cast((*current_participant_model).get()); if (participant_model) { - participant_model->setModeratorOptionsVisible(isNearbyChatModerator()); + participant_model->setModeratorOptionsVisible(LLNearbyVoiceModeration::getInstance()->isNearbyChatModerator()); } current_participant_model++; @@ -2037,7 +2038,7 @@ LLConversationViewParticipant* LLFloaterIMContainer::createConversationViewParti bool LLFloaterIMContainer::enableModerateContextMenuItem(const std::string& userdata, bool is_self) { - if (isNearbyChatModerator() && isNearbyChatSpeakerSelected()) + if (LLNearbyVoiceModeration::getInstance()->isNearbyChatModerator() && isNearbyChatSpeakerSelected()) { // Determine here which actions are allowed if ("can_moderate_voice" == userdata) @@ -2046,7 +2047,7 @@ bool LLFloaterIMContainer::enableModerateContextMenuItem(const std::string& user } else if (("can_mute" == userdata)) { - return true; + return !is_self; } else if ("can_unmute" == userdata) { @@ -2199,30 +2200,12 @@ void LLFloaterIMContainer::moderateVoice(const std::string& command, const LLUUI { if ("selected" == command) { - // Toggle the voice icon display - LLAvatarActions::toggleMuteVoice(userID); - // Request a mute/unmute using a capability request via the simulator - const bool mute_state = LLAvatarActions::isVoiceMuted(userID); - LLNearbyVoiceModeration::getInstance()->requestMuteIndividual(userID, mute_state); + LLNearbyVoiceModeration::getInstance()->requestMuteIndividual(userID, !isMuted(userID)); } else if ("mute_all" == command) { - // TODO: the SpatialVoiceModerationRequest has an mute_all/unmute_all - // verb but we do not have an equivalent of LLAvatarActions::toggleMuteVoice(userID); - // to visually mute all the speaker icons in the conversation floater - - // Mute visually too - conversations_widgets_map::const_iterator iter = mConversationsWidgets.begin(); - conversations_widgets_map::const_iterator end = mConversationsWidgets.end(); - const LLUUID * conversation_uuidp = NULL; - while(iter != end) - { - const LLUUID id = (*iter).first; - ++iter; - } - // Send the mute_all request to the server const bool mute_state = true; LLNearbyVoiceModeration::getInstance()->requestMuteAll(mute_state); @@ -2230,8 +2213,6 @@ void LLFloaterIMContainer::moderateVoice(const std::string& command, const LLUUI else if ("unmute_all" == command) { - // TODO: same idea as "mute_all" above - // Send the unmute_all request to the server const bool mute_state = false; LLNearbyVoiceModeration::getInstance()->requestMuteAll(mute_state); @@ -2381,12 +2362,6 @@ bool LLFloaterIMContainer::isNearbyChatSpeakerSelected() return conversation_uuidp->isNull(); } -bool LLFloaterIMContainer::isNearbyChatModerator() -{ - // TODO: Need a better heurestic for determining if this person is a moderator :) - return true; -} - void LLFloaterIMContainer::toggleAllowTextChat(const LLUUID& participant_uuid) { LLIMSpeakerMgr * speaker_managerp = dynamic_cast(getSpeakerMgrForSelectedParticipant()); diff --git a/indra/newview/llfloaterimcontainer.h b/indra/newview/llfloaterimcontainer.h index c91093c1073..9f1690a9b9c 100644 --- a/indra/newview/llfloaterimcontainer.h +++ b/indra/newview/llfloaterimcontainer.h @@ -179,7 +179,6 @@ class LLFloaterIMContainer void openNearbyChat(); bool isParticipantListExpanded(); bool isNearbyChatSpeakerSelected(); - bool isNearbyChatModerator(); void idleUpdate(); // for convenience (self) from static idle void idleProcessEvents(); diff --git a/indra/newview/llnearbyvoicemoderation.cpp b/indra/newview/llnearbyvoicemoderation.cpp index 5ae8feba08c..ec0e1c04312 100644 --- a/indra/newview/llnearbyvoicemoderation.cpp +++ b/indra/newview/llnearbyvoicemoderation.cpp @@ -26,12 +26,29 @@ #include "llviewerprecompiledheaders.h" #include "llagent.h" +#include "llnotificationsutil.h" #include "llviewerregion.h" #include "llvoavatar.h" +#include "llvoiceclient.h" #include "llviewerobjectlist.h" #include "llnearbyvoicemoderation.h" +LLNearbyVoiceModeration::LLNearbyVoiceModeration() +{ + // TODO: default to false, when appropriate info cap is added + mIsNearbyChatModerator = true; + mParcelCallbackConnection = gAgent.addParcelChangedCallback([this]() { updateModeratorStatus(); }); +} + +LLNearbyVoiceModeration::~LLNearbyVoiceModeration() +{ + if (mParcelCallbackConnection.connected()) + { + mParcelCallbackConnection.disconnect(); + } +} + LLVOAvatar* LLNearbyVoiceModeration::getVOAvatarFromId(const LLUUID& agent_id) { LLViewerObject *obj = gObjectList.findObject(agent_id); @@ -54,18 +71,12 @@ const std::string LLNearbyVoiceModeration::getCapUrlFromRegion(LLViewerRegion* r { if (! region || ! region->capabilitiesReceived()) { - // TODO: Retry if fails since the capabilities may not have been received - // if this is called early into a region entry - LL_INFOS() << "Region or region capabilities unavailable." << LL_ENDL; return std::string(); } - LL_INFOS() << "Capabilities for region " << region->getName() << " received." << LL_ENDL; std::string url = region->getCapability("SpatialVoiceModerationRequest"); if (url.empty()) { - // TODO: Retry if fails since URL may not have not be available - // if this is called early into a region entry LL_INFOS() << "Capability URL for region " << region->getName() << " is empty" << LL_ENDL; return std::string(); } @@ -139,3 +150,93 @@ void LLNearbyVoiceModeration::requestMuteAll(bool mute) failure_msg); } } + +void LLNearbyVoiceModeration::setMutedInfo(const std::string& channelID, bool mute) +{ + auto it = mChannelMuteMap.find(channelID); + if (it == mChannelMuteMap.end()) + { + if (mute) + { + // Channel is new and being muted + showMutedNotification(true); + } + mChannelMuteMap[channelID] = mute; + } + else + { + if (it->second != mute) + { + // Flag changed + showMutedNotification(mute); + it->second = mute; + } + } +} + +void LLNearbyVoiceModeration::showNotificationIfNeeded() +{ + if (LLVoiceClient::getInstance()->inProximalChannel() && + LLVoiceClient::getInstance()->getIsModeratorMuted(gAgentID)) + { + showMutedNotification(true); + } +} + +void LLNearbyVoiceModeration::showMutedNotification(bool is_muted) +{ + // Check if the current voice channel is nearby chat + if (LLVoiceClient::getInstance()->inProximalChannel()) + { + LLNotificationsUtil::add(is_muted ? "NearbyVoiceMutedByModerator" : "NearbyVoiceUnmutedByModerator"); + } +} + +void LLNearbyVoiceModeration::updateModeratorStatus() +{ + LL_WARNS() << "Request moderator status info" << LL_ENDL; + // TODO: Uncomment and set correct capability name, when appropriate info cap is added + /* + if (LLViewerRegion* region = gAgent.getRegion()) + { + + std::string url = region->getCapability("SpatialVoiceModerationInfoRequest"); + if (!url.empty()) + { + LLCoros::instance().launch("getModeratorStatusCoro", [url]() { getModeratorStatusCoro(url); }); + } + } + */ +} + +void LLNearbyVoiceModeration::getModeratorStatusCoro(std::string cap_url) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getModeratorStatusCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); + LLCore::HttpHeaders::ptr_t httpHeaders; + + httpOpts->setFollowRedirects(true); + + LLSD result = httpAdapter->getAndSuspend(httpRequest, cap_url, httpOpts, httpHeaders); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + LL_WARNS() << "Failed to get nearby voice moderator info" << LL_ENDL; + return; + } + else if (!result["success"].asBoolean()) + { + LL_WARNS() << "Failed to get nearby voice moderator info: " << result["message"] << LL_ENDL; + return; + } + + // TODO: update the field, when appropriate info cap is added + bool is_moderator = result["moderator"].asBoolean(); + LLNearbyVoiceModeration::getInstance()->setNearbyChatModerator(is_moderator); +} + diff --git a/indra/newview/llnearbyvoicemoderation.h b/indra/newview/llnearbyvoicemoderation.h index 8a1ca5af6bd..21d185db54b 100644 --- a/indra/newview/llnearbyvoicemoderation.h +++ b/indra/newview/llnearbyvoicemoderation.h @@ -27,19 +27,29 @@ class LLVOAvatar; -class LLNearbyVoiceModeration : - public LLSingleton { - LLSINGLETON(LLNearbyVoiceModeration) { - }; - - ~LLNearbyVoiceModeration() { - }; +class LLNearbyVoiceModeration : public LLSingleton { + LLSINGLETON(LLNearbyVoiceModeration); + ~LLNearbyVoiceModeration(); public: - LLVOAvatar* getVOAvatarFromId(const LLUUID& id); void requestMuteIndividual(const LLUUID& userID, bool mute); void requestMuteAll(bool mute); + void setMutedInfo(const std::string& channelID, bool mute); + void showMutedNotification(bool is_muted); + void showNotificationIfNeeded(); + + void updateModeratorStatus(); + static void getModeratorStatusCoro(std::string cap_url); + + bool isNearbyChatModerator() { return mIsNearbyChatModerator; }; + void setNearbyChatModerator(bool moderator) { mIsNearbyChatModerator = moderator; } + private: + LLVOAvatar* getVOAvatarFromId(const LLUUID& id); const std::string getCapUrlFromRegion(LLViewerRegion* region); + + boost::signals2::connection mParcelCallbackConnection; + std::map mChannelMuteMap; + bool mIsNearbyChatModerator; }; diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index a085bc4d914..fa60c368341 100755 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -3300,6 +3300,7 @@ void LLViewerRegionImpl::buildCapabilityNames(LLSD& capabilityNames) capabilityNames.append("SetDisplayName"); capabilityNames.append("SimConsoleAsync"); capabilityNames.append("SimulatorFeatures"); + capabilityNames.append("SpatialVoiceModerationRequest"); capabilityNames.append("StartGroupProposal"); capabilityNames.append("TerrainNavMeshProperties"); capabilityNames.append("TextureStats"); diff --git a/indra/newview/llvoiceclient.cpp b/indra/newview/llvoiceclient.cpp index 71a9e71a9fd..5a2ef248ba3 100644 --- a/indra/newview/llvoiceclient.cpp +++ b/indra/newview/llvoiceclient.cpp @@ -39,6 +39,7 @@ #include "llagent.h" #include "lltrans.h" #include "lluiusage.h" +#include "llnearbyvoicemoderation.h" const F32 LLVoiceClient::OVERDRIVEN_POWER_LEVEL = 0.7f; @@ -712,6 +713,9 @@ bool LLVoiceClient::getPTTIsToggle() void LLVoiceClient::inputUserControlState(bool down) { + if (down && !getUserPTTState()) + LLNearbyVoiceModeration::getInstance()->showNotificationIfNeeded(); + if(mPTTIsToggle) { if(down) // toggle open-mic state on 'down' diff --git a/indra/newview/llvoicewebrtc.cpp b/indra/newview/llvoicewebrtc.cpp index 93f8f0d14aa..1d78ca002ad 100644 --- a/indra/newview/llvoicewebrtc.cpp +++ b/indra/newview/llvoicewebrtc.cpp @@ -65,6 +65,7 @@ #include "llviewernetwork.h" #include "llnotificationsutil.h" +#include "llnearbyvoicemoderation.h" #include "llcorehttputil.h" #include "lleventfilter.h" @@ -3194,14 +3195,19 @@ void LLVoiceWebRTCConnection::OnDataReceivedImpl(const std::string &data, bool b participant->mIsSpeaking = participant_obj["v"].as_bool(); } + // Currently, viewer doesn't receive this info when the user is muted in Nearby chat, + // but it *does* receive it when muted in Group chat. A server-side change is required. if (participant_obj.contains("m") && participant_obj["m"].is_bool()) { participant->mIsModeratorMuted = participant_obj["m"].as_bool(); + if (isSpatial() && (gAgentID == agent_id)) + { + LLNearbyVoiceModeration::getInstance()->setMutedInfo(mChannelID, participant->mIsModeratorMuted); + } } } } } - // tell the simulator to set the mute and volume data for this // participant, if there are any updates. boost::json::object root; diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index dbd513afe8b..3f9a2ba13e2 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -9192,6 +9192,29 @@ Your voice has been muted by moderator. yestext="OK"/> + + The moderator has muted your voice chat. +People in this location will not hear you if you speak. + voice + + + + + Your voice chat can now be heard by people in this location. + voice + + + Date: Fri, 7 Nov 2025 17:19:56 +0200 Subject: [PATCH 05/10] #4013 add simple voice moderation permission check --- indra/newview/llnearbyvoicemoderation.cpp | 56 ++--------------------- indra/newview/llnearbyvoicemoderation.h | 7 +-- indra/newview/llviewerparcelmgr.cpp | 10 ++++ indra/newview/llviewerparcelmgr.h | 6 +++ 4 files changed, 21 insertions(+), 58 deletions(-) diff --git a/indra/newview/llnearbyvoicemoderation.cpp b/indra/newview/llnearbyvoicemoderation.cpp index ec0e1c04312..2b4fd26afc0 100644 --- a/indra/newview/llnearbyvoicemoderation.cpp +++ b/indra/newview/llnearbyvoicemoderation.cpp @@ -31,22 +31,17 @@ #include "llvoavatar.h" #include "llvoiceclient.h" #include "llviewerobjectlist.h" +#include "llviewerparcelmgr.h" +#include "roles_constants.h" #include "llnearbyvoicemoderation.h" LLNearbyVoiceModeration::LLNearbyVoiceModeration() { - // TODO: default to false, when appropriate info cap is added - mIsNearbyChatModerator = true; - mParcelCallbackConnection = gAgent.addParcelChangedCallback([this]() { updateModeratorStatus(); }); } LLNearbyVoiceModeration::~LLNearbyVoiceModeration() { - if (mParcelCallbackConnection.connected()) - { - mParcelCallbackConnection.disconnect(); - } } LLVOAvatar* LLNearbyVoiceModeration::getVOAvatarFromId(const LLUUID& agent_id) @@ -192,51 +187,8 @@ void LLNearbyVoiceModeration::showMutedNotification(bool is_muted) } } -void LLNearbyVoiceModeration::updateModeratorStatus() -{ - LL_WARNS() << "Request moderator status info" << LL_ENDL; - // TODO: Uncomment and set correct capability name, when appropriate info cap is added - /* - if (LLViewerRegion* region = gAgent.getRegion()) - { - - std::string url = region->getCapability("SpatialVoiceModerationInfoRequest"); - if (!url.empty()) - { - LLCoros::instance().launch("getModeratorStatusCoro", [url]() { getModeratorStatusCoro(url); }); - } - } - */ -} - -void LLNearbyVoiceModeration::getModeratorStatusCoro(std::string cap_url) +bool LLNearbyVoiceModeration::isNearbyChatModerator() { - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getModeratorStatusCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); - LLCore::HttpHeaders::ptr_t httpHeaders; - - httpOpts->setFollowRedirects(true); - - LLSD result = httpAdapter->getAndSuspend(httpRequest, cap_url, httpOpts, httpHeaders); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - LL_WARNS() << "Failed to get nearby voice moderator info" << LL_ENDL; - return; - } - else if (!result["success"].asBoolean()) - { - LL_WARNS() << "Failed to get nearby voice moderator info: " << result["message"] << LL_ENDL; - return; - } - - // TODO: update the field, when appropriate info cap is added - bool is_moderator = result["moderator"].asBoolean(); - LLNearbyVoiceModeration::getInstance()->setNearbyChatModerator(is_moderator); + return gAgent.canManageEstate() || LLViewerParcelMgr::getInstance()->allowVoiceModeration(); } diff --git a/indra/newview/llnearbyvoicemoderation.h b/indra/newview/llnearbyvoicemoderation.h index 21d185db54b..95bae9ca2ad 100644 --- a/indra/newview/llnearbyvoicemoderation.h +++ b/indra/newview/llnearbyvoicemoderation.h @@ -39,11 +39,7 @@ class LLNearbyVoiceModeration : public LLSingleton { void showMutedNotification(bool is_muted); void showNotificationIfNeeded(); - void updateModeratorStatus(); - static void getModeratorStatusCoro(std::string cap_url); - - bool isNearbyChatModerator() { return mIsNearbyChatModerator; }; - void setNearbyChatModerator(bool moderator) { mIsNearbyChatModerator = moderator; } + bool isNearbyChatModerator(); private: LLVOAvatar* getVOAvatarFromId(const LLUUID& id); @@ -51,5 +47,4 @@ class LLNearbyVoiceModeration : public LLSingleton { boost::signals2::connection mParcelCallbackConnection; std::map mChannelMuteMap; - bool mIsNearbyChatModerator; }; diff --git a/indra/newview/llviewerparcelmgr.cpp b/indra/newview/llviewerparcelmgr.cpp index 432da2e9905..452c666652e 100644 --- a/indra/newview/llviewerparcelmgr.cpp +++ b/indra/newview/llviewerparcelmgr.cpp @@ -702,6 +702,16 @@ bool LLViewerParcelMgr::allowAgentVoice() const return allowAgentVoice(gAgent.getRegion(), mAgentParcel); } +bool LLViewerParcelMgr::isVoiceRestricted() const +{ + return mAgentParcel && !mAgentParcel->getParcelFlagUseEstateVoiceChannel(); +} + +bool LLViewerParcelMgr::allowVoiceModeration() const +{ + return isVoiceRestricted() && isParcelOwnedByAgent(mAgentParcel, GP_SESSION_MODERATOR); +} + bool LLViewerParcelMgr::allowAgentVoice(const LLViewerRegion* region, const LLParcel* parcel) const { return region && region->isVoiceEnabled() diff --git a/indra/newview/llviewerparcelmgr.h b/indra/newview/llviewerparcelmgr.h index 1925cd23ed2..8863bc7a062 100644 --- a/indra/newview/llviewerparcelmgr.h +++ b/indra/newview/llviewerparcelmgr.h @@ -173,6 +173,12 @@ class LLViewerParcelMgr : public LLSingleton bool allowAgentVoice() const; bool allowAgentVoice(const LLViewerRegion* region, const LLParcel* parcel) const; + // Returns true if this parcel is using private voice channel + bool isVoiceRestricted() const; + + // Can this agent moderate Nearby voice chat on this parcel? + bool allowVoiceModeration() const; + // Can this agent start flying on this parcel? // Used for parcel property icons in nav bar. bool allowAgentFly(const LLViewerRegion* region, const LLParcel* parcel) const; From a4d01edef6288528647968ea8d97fee8a553891d Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Tue, 11 Nov 2025 23:34:05 +0200 Subject: [PATCH 06/10] Show moderator options only on webrtc region --- indra/newview/llnearbyvoicemoderation.cpp | 3 ++- indra/newview/llviewerregion.cpp | 10 ++++++++++ indra/newview/llviewerregion.h | 2 ++ indra/newview/llvoicewebrtc.cpp | 2 -- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/indra/newview/llnearbyvoicemoderation.cpp b/indra/newview/llnearbyvoicemoderation.cpp index 2b4fd26afc0..10f92502a51 100644 --- a/indra/newview/llnearbyvoicemoderation.cpp +++ b/indra/newview/llnearbyvoicemoderation.cpp @@ -189,6 +189,7 @@ void LLNearbyVoiceModeration::showMutedNotification(bool is_muted) bool LLNearbyVoiceModeration::isNearbyChatModerator() { - return gAgent.canManageEstate() || LLViewerParcelMgr::getInstance()->allowVoiceModeration(); + return gAgent.getRegion() && gAgent.getRegion()->isRegionWebRTCEnabled() && + (gAgent.canManageEstate() || LLViewerParcelMgr::getInstance()->allowVoiceModeration()); } diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index fa60c368341..98440d96ae5 100755 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -3798,6 +3798,16 @@ std::string LLViewerRegion::getSimHostName() return std::string("..."); } + +bool LLViewerRegion::isRegionWebRTCEnabled() +{ + if (mSimulatorFeaturesReceived && mSimulatorFeatures.has("VoiceServerType")) + { + return mSimulatorFeatures["VoiceServerType"].asString() == "webrtc"; + } + return false; +} + void LLViewerRegion::applyCacheMiscExtras(LLViewerObject* obj) { LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY; diff --git a/indra/newview/llviewerregion.h b/indra/newview/llviewerregion.h index 244e2b7835c..bde0fa13ce5 100644 --- a/indra/newview/llviewerregion.h +++ b/indra/newview/llviewerregion.h @@ -424,6 +424,8 @@ class LLViewerRegion: public LLCapabilityProvider // implements this interface std::string getSimHostName(); + bool isRegionWebRTCEnabled(); + static bool isNewObjectCreationThrottleDisabled() {return sNewObjectCreationThrottle < 0;} // rebuild reflection probe list diff --git a/indra/newview/llvoicewebrtc.cpp b/indra/newview/llvoicewebrtc.cpp index c57557079e2..be2e840f630 100644 --- a/indra/newview/llvoicewebrtc.cpp +++ b/indra/newview/llvoicewebrtc.cpp @@ -3168,8 +3168,6 @@ void LLVoiceWebRTCConnection::OnDataReceivedImpl(const std::string &data, bool b participant->mIsSpeaking = participant_obj["v"].as_bool(); } - // Currently, viewer doesn't receive this info when the user is muted in Nearby chat, - // but it *does* receive it when muted in Group chat. A server-side change is required. if (participant_obj.contains("m") && participant_obj["m"].is_bool()) { participant->mIsModeratorMuted = participant_obj["m"].as_bool(); From d9ec89ac0a0ae2e44f00f524678d18cf66c52b13 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Wed, 12 Nov 2025 20:24:42 +0200 Subject: [PATCH 07/10] Ignore muted flags from non-primary voice server --- indra/newview/llvoicewebrtc.cpp | 45 ++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/indra/newview/llvoicewebrtc.cpp b/indra/newview/llvoicewebrtc.cpp index be2e840f630..e4fdf85d12d 100644 --- a/indra/newview/llvoicewebrtc.cpp +++ b/indra/newview/llvoicewebrtc.cpp @@ -3170,14 +3170,53 @@ void LLVoiceWebRTCConnection::OnDataReceivedImpl(const std::string &data, bool b if (participant_obj.contains("m") && participant_obj["m"].is_bool()) { - participant->mIsModeratorMuted = participant_obj["m"].as_bool(); - if (isSpatial() && (gAgentID == agent_id)) + bool is_moderator_muted = participant_obj["m"].as_bool(); + if (isSpatial()) { - LLNearbyVoiceModeration::getInstance()->setMutedInfo(mChannelID, participant->mIsModeratorMuted); + // ignore muted flags from non-primary server + if (mPrimary || primary) + { + participant->mIsModeratorMuted = is_moderator_muted; + if (gAgentID == agent_id) + { + LLNearbyVoiceModeration::getInstance()->setMutedInfo(mChannelID, is_moderator_muted); + } + } + } + else + { + participant->mIsModeratorMuted = is_moderator_muted; } } } } + else + { + if (isSpatial() && (mPrimary || primary)) + { + // mute info message can be received before join message, so try to mute again later + if (participant_obj.contains("m") && participant_obj["m"].is_bool()) + { + bool is_moderator_muted = participant_obj["m"].as_bool(); + std::string channel_id = mChannelID; + F32 delay { 1.5f }; + doAfterInterval( + [channel_id, agent_id, is_moderator_muted]() + { + LLWebRTCVoiceClient::participantStatePtr_t participant = + LLWebRTCVoiceClient::getInstance()->findParticipantByID(channel_id, agent_id); + if (participant) + { + participant->mIsModeratorMuted = is_moderator_muted; + if (gAgentID == agent_id) + { + LLNearbyVoiceModeration::getInstance()->setMutedInfo(channel_id, is_moderator_muted); + } + } + }, delay); + } + } + } } // tell the simulator to set the mute and volume data for this // participant, if there are any updates. From 811105270d2cede0a99efbb501792d35ef61a530 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Thu, 13 Nov 2025 18:48:24 +0200 Subject: [PATCH 08/10] #4994 remove redundant moderator_id key --- indra/newview/llnearbyvoicemoderation.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/indra/newview/llnearbyvoicemoderation.cpp b/indra/newview/llnearbyvoicemoderation.cpp index 10f92502a51..5a6d7e1c6bb 100644 --- a/indra/newview/llnearbyvoicemoderation.cpp +++ b/indra/newview/llnearbyvoicemoderation.cpp @@ -93,7 +93,6 @@ void LLNearbyVoiceModeration::requestMuteIndividual(const LLUUID& agent_id, bool LLSD body; body["operand"] = operand; body["agent_id"] = agent_id; - body["moderator_id"] = gAgent.getID(); const std::string agent_name = avatar->getFullname(); LL_INFOS() << "Resident " << agent_name @@ -128,7 +127,6 @@ void LLNearbyVoiceModeration::requestMuteAll(bool mute) LLSD body; body["operand"] = operand; - body["moderator_id"] = gAgent.getID(); LL_INFOS() << "For all residents in this region, applying: " << operand << LL_ENDL; From bee23b4956b424f99765fb099906d3cb8250ce63 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Thu, 13 Nov 2025 19:02:49 +0200 Subject: [PATCH 09/10] #4995 change muted/unmuted alerts to non-modal toast --- .../skins/default/xui/en/notifications.xml | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index 3f9a2ba13e2..14e9211b9c4 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -9193,26 +9193,20 @@ Your voice has been muted by moderator. + type="notifytip"> The moderator has muted your voice chat. People in this location will not hear you if you speak. - voice - + voice + type="notifytip"> Your voice chat can now be heard by people in this location. - voice - + voice Date: Fri, 14 Nov 2025 16:19:57 +0200 Subject: [PATCH 10/10] Toggle off 'Speak' button when muted by moderator --- indra/newview/llfloaterimcontainer.cpp | 2 +- indra/newview/llnearbyvoicemoderation.cpp | 13 ++++++++++--- indra/newview/llnearbyvoicemoderation.h | 4 ++-- indra/newview/llvoiceclient.cpp | 6 +++++- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/indra/newview/llfloaterimcontainer.cpp b/indra/newview/llfloaterimcontainer.cpp index ac8234bf24c..00b9c0b0529 100644 --- a/indra/newview/llfloaterimcontainer.cpp +++ b/indra/newview/llfloaterimcontainer.cpp @@ -2340,7 +2340,7 @@ LLSpeaker * LLFloaterIMContainer::getSpeakerOfSelectedParticipant(LLSpeakerMgr * bool LLFloaterIMContainer::isNearbyChatSpeakerSelected() { LLFolderViewItem *selectedItem = mConversationsRoot->getCurSelectedItem(); - if (NULL == selectedItem) + if (!selectedItem) { LL_WARNS() << "Current selected item is null" << LL_ENDL; return NULL; diff --git a/indra/newview/llnearbyvoicemoderation.cpp b/indra/newview/llnearbyvoicemoderation.cpp index 5a6d7e1c6bb..d3e540e838f 100644 --- a/indra/newview/llnearbyvoicemoderation.cpp +++ b/indra/newview/llnearbyvoicemoderation.cpp @@ -165,24 +165,31 @@ void LLNearbyVoiceModeration::setMutedInfo(const std::string& channelID, bool mu it->second = mute; } } + if (mute && LLVoiceClient::getInstance()->getUserPTTState()) + { + LLVoiceClient::getInstance()->setUserPTTState(false); + } } -void LLNearbyVoiceModeration::showNotificationIfNeeded() +bool LLNearbyVoiceModeration::showNotificationIfNeeded() { if (LLVoiceClient::getInstance()->inProximalChannel() && LLVoiceClient::getInstance()->getIsModeratorMuted(gAgentID)) { - showMutedNotification(true); + return showMutedNotification(true); } + return false; } -void LLNearbyVoiceModeration::showMutedNotification(bool is_muted) +bool LLNearbyVoiceModeration::showMutedNotification(bool is_muted) { // Check if the current voice channel is nearby chat if (LLVoiceClient::getInstance()->inProximalChannel()) { LLNotificationsUtil::add(is_muted ? "NearbyVoiceMutedByModerator" : "NearbyVoiceUnmutedByModerator"); + return true; } + return false; } bool LLNearbyVoiceModeration::isNearbyChatModerator() diff --git a/indra/newview/llnearbyvoicemoderation.h b/indra/newview/llnearbyvoicemoderation.h index 95bae9ca2ad..619f1698839 100644 --- a/indra/newview/llnearbyvoicemoderation.h +++ b/indra/newview/llnearbyvoicemoderation.h @@ -36,8 +36,8 @@ class LLNearbyVoiceModeration : public LLSingleton { void requestMuteAll(bool mute); void setMutedInfo(const std::string& channelID, bool mute); - void showMutedNotification(bool is_muted); - void showNotificationIfNeeded(); + bool showMutedNotification(bool is_muted); + bool showNotificationIfNeeded(); bool isNearbyChatModerator(); diff --git a/indra/newview/llvoiceclient.cpp b/indra/newview/llvoiceclient.cpp index 5a2ef248ba3..2d732d0a71e 100644 --- a/indra/newview/llvoiceclient.cpp +++ b/indra/newview/llvoiceclient.cpp @@ -714,7 +714,11 @@ bool LLVoiceClient::getPTTIsToggle() void LLVoiceClient::inputUserControlState(bool down) { if (down && !getUserPTTState()) - LLNearbyVoiceModeration::getInstance()->showNotificationIfNeeded(); + { + // Nearby chat is muted by moderator, don't toggle PTT + if (LLNearbyVoiceModeration::getInstance()->showNotificationIfNeeded()) + return; + } if(mPTTIsToggle) {