Merge branch 'release/2026.02' of https://github.com/secondlife/viewer

# Conflicts:
#	indra/newview/app_settings/settings.xml
#	indra/newview/llfloaterimcontainer.cpp
#	indra/newview/llinventorygallerymenu.cpp
#	indra/newview/lllandmarkactions.cpp
#	indra/newview/llviewermenu.cpp
#	indra/newview/llvoicewebrtc.cpp
This commit is contained in:
Ansariel
2026-02-12 11:03:48 +01:00
34 changed files with 997 additions and 155 deletions
+51
View File
@@ -1764,6 +1764,57 @@ void LLWebRTCPeerConnectionImpl::unsetDataObserver(LLWebRTCDataObserver* observe
}
}
class LLStatsCollectorCallback : public webrtc::RTCStatsCollectorCallback
{
public:
typedef std::function<void(const LLWebRTCStatsMap&)> StatsCallback;
LLStatsCollectorCallback(StatsCallback callback) : callback_(callback) {}
void OnStatsDelivered(const webrtc::scoped_refptr<const webrtc::RTCStatsReport>& report) override
{
if (callback_)
{
// Transform RTCStatsReport stats to simple map
LLWebRTCStatsMap stats_map;
for (const auto& stats : *report)
{
std::map<std::string, std::string> stat_attributes;
// Convert each attribute to string format
for (const auto& attribute : stats.Attributes())
{
stat_attributes[attribute.name()] = attribute.ToString();
}
stats_map[stats.id()] = stat_attributes;
}
callback_(stats_map);
}
}
private:
StatsCallback callback_;
};
void LLWebRTCPeerConnectionImpl::gatherConnectionStats()
{
if (!mPeerConnection)
{
return;
}
auto stats_callback = webrtc::make_ref_counted<LLStatsCollectorCallback>(
[this](const LLWebRTCStatsMap& generic_stats)
{
for (auto& observer : mSignalingObserverList)
{
observer->OnStatsDelivered(generic_stats);
}
});
mPeerConnection->GetStats(stats_callback.get());
}
LLWebRTCImpl * gWebRTCImpl = nullptr;
LLWebRTCDeviceInterface * getDeviceInterface()
{
+6
View File
@@ -38,6 +38,7 @@
#ifndef LLWEBRTC_H
#define LLWEBRTC_H
#include <map>
#include <string>
#include <vector>
// <FS:minerjr> [FIRE-36022] - Removing my USB headset crashes entire viewer
@@ -65,6 +66,7 @@ inline std::atomic<bool> gWebRTCUpdateDevices = false;
// </FS:minerjr> [FIRE-36022]
namespace llwebrtc
{
typedef std::map<std::string, std::map<std::string, std::string>> LLWebRTCStatsMap;
class LLWebRTCLogCallback
{
@@ -250,6 +252,8 @@ class LLWebRTCSignalingObserver
// Called when the data channel has been established and data
// transfer can begin.
virtual void OnDataChannelReady(LLWebRTCDataInterface *data_interface) = 0;
virtual void OnStatsDelivered(const LLWebRTCStatsMap& stats_data) {}
};
// LLWebRTCPeerConnectionInterface representsd a connection to a peer,
@@ -283,6 +287,8 @@ class LLWebRTCPeerConnectionInterface
virtual void unsetSignalingObserver(LLWebRTCSignalingObserver* observer) = 0;
virtual void AnswerAvailable(const std::string &sdp) = 0;
virtual void gatherConnectionStats() = 0;
};
// The following define the dynamic linked library
+2
View File
@@ -648,6 +648,8 @@ class LLWebRTCPeerConnectionImpl : public LLWebRTCPeerConnectionInterface,
void enableSenderTracks(bool enable);
void enableReceiverTracks(bool enable);
void gatherConnectionStats() override;
protected:
LLWebRTCImpl * mWebRTCImpl;
+2
View File
@@ -546,6 +546,7 @@ set(viewer_SOURCE_FILES
llfloaterimnearbychat.cpp
llfloaterimnearbychathandler.cpp
llfloaterimnearbychatlistener.cpp
llnearbyvoicemoderation.cpp
llnetmap.cpp
llnotificationalerthandler.cpp
llnotificationgrouphandler.cpp
@@ -1403,6 +1404,7 @@ set(viewer_HEADER_FILES
llnameeditor.h
llnamelistctrl.h
llnavigationbar.h
llnearbyvoicemoderation.h
llnetmap.h
llnotificationhandler.h
llnotificationlistitem.h
+1 -1
View File
@@ -1 +1 @@
26.1.0
26.2.0
+39 -6
View File
@@ -2217,7 +2217,7 @@
<key>Type</key>
<string>String</string>
<key>Value</key>
<string>http://lecs-viewer-web-components.s3.amazonaws.com/v3.0/[GRID_LOWERCASE]/vawp/index.html</string>
<string>https://lecs-viewer-web-components.s3.amazonaws.com/v3.0/[GRID_LOWERCASE]/vawp/index.html</string>
</map>
<!--AvatarBakedTextureUploadTimeout is in use by QA-->
<key>AvatarBakedTextureUploadTimeout</key>
@@ -4858,7 +4858,7 @@
<key>Type</key>
<string>String</string>
<key>Value</key>
<string>http://lecs-viewer-web-components.s3.amazonaws.com/v3.0/[GRID_LOWERCASE]/guide.html</string>
<string>https://lecs-viewer-web-components.s3.amazonaws.com/v3.0/[GRID_LOWERCASE]/guide.html</string>
<key>Backup</key>
<integer>0</integer>
</map>
@@ -5211,7 +5211,7 @@
<key>Type</key>
<string>String</string>
<key>Value</key>
<string>http://events.[GRID]/viewer/embed/event/[EVENT_ID]</string>
<string>https://events.[GRID]/viewer/embed/event/[EVENT_ID]</string>
</map>
<key>FastCacheFetchEnabled</key>
<map>
@@ -6720,7 +6720,7 @@
<key>Type</key>
<string>String</string>
<key>Value</key>
<string>http://wiki.secondlife.com/wiki/[LSL_STRING]</string>
<string>https://wiki.secondlife.com/wiki/[LSL_STRING]</string>
<key>Backup</key>
<integer>0</integer>
</map>
@@ -7323,7 +7323,7 @@
<key>Type</key>
<string>F32</string>
<key>Value</key>
<real>30.0</real>
<real>60.0</real>
</map>
<key>MapScale</key>
<map>
@@ -9454,6 +9454,39 @@ Change of this parameter will affect the layout of buttons in notification toast
<key>Value</key>
<integer>1</integer>
</map>
<key>OpenDebugStatVoice</key>
<map>
<key>Comment</key>
<string>Expand Voice (WebRTC) stats display</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
<integer>1</integer>
</map>
<key>OpenDebugStatVoiceOutgoing</key>
<map>
<key>Comment</key>
<string>Expand Outgoing audio (Voice) stats display</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
<integer>1</integer>
</map>
<key>OpenDebugStatVoiceIncoming</key>
<map>
<key>Comment</key>
<string>Expand Incoming audio (Voice) stats display</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
<integer>1</integer>
</map>
<key>OutBandwidth</key>
<map>
<key>Comment</key>
@@ -19714,7 +19747,7 @@ Change of this parameter will affect the layout of buttons in notification toast
<key>Type</key>
<string>String</string>
<key>Value</key>
<string>http://common-flash-secondlife-com.s3.amazonaws.com/viewer/v2.6/agni/404.html</string>
<string>https://common-flash-secondlife-com.s3.amazonaws.com/viewer/v2.6/agni/404.html</string>
<key>Backup</key>
<integer>0</integer>
</map>
+8 -1
View File
@@ -1574,6 +1574,7 @@ bool LLAppViewer::frame()
bool LLAppViewer::doFrame()
{
resumeMainloopTimeout("Main:doFrameStart");
#ifdef LL_DISCORD
{
LL_PROFILE_ZONE_NAMED("discord_callbacks");
@@ -1958,6 +1959,11 @@ bool LLAppViewer::doFrame()
LL_INFOS() << "Exiting main_loop" << LL_ENDL;
}
}LLPerfStats::StatsRecorder::endFrame();
// Not viewer's fault if something outside frame
// pauses viewer (ex: macOS doesn't call oneFrame),
// so stop tracking on exit.
pauseMainloopTimeout();
LL_PROFILER_FRAME_END;
return ! LLApp::isRunning();
@@ -6860,7 +6866,8 @@ F32 LLAppViewer::getMainloopTimeoutSec() const
if (LLStartUp::getStartupState() == STATE_STARTED
&& gAgent.getTeleportState() == LLAgent::TELEPORT_NONE)
{
static LLCachedControl<F32> mainloop_started(gSavedSettings, "MainloopTimeoutStarted", 30.f);
// consider making this value match 'disconnected' timout.
static LLCachedControl<F32> mainloop_started(gSavedSettings, "MainloopTimeoutStarted", 60.f);
return mainloop_started();
}
else
+3 -1
View File
@@ -505,7 +505,9 @@ bool LLFeatureManager::loadGPUClass()
{
mGPUClass = GPU_CLASS_2;
}
else if (gbps <= class1_gbps*4.f)
else if ((gbps <= class1_gbps*4.f)
// Cap silicon's GPUs at med+ as they have high throughput, low capability
|| gGLManager.mIsApple)
{
mGPUClass = GPU_CLASS_3;
}
+108 -3
View File
@@ -58,6 +58,8 @@
#include "llsdserialize.h"
#include "llviewermenu.h" // is_agent_mappable
#include "llviewerobjectlist.h"
#include "llvoavatar.h"
#include "llnearbyvoicemoderation.h"
// [RLVa:KB] - @pay
#include "rlvactions.h"
// [/RLVa:KB]
@@ -94,6 +96,7 @@ LLFloaterIMContainer::LLFloaterIMContainer(const LLSD& seed, const Params& param
mAutoResize = false;
LLTransientFloaterMgr::getInstance()->addControlView(LLTransientFloaterMgr::IM, this);
LLNearbyVoiceModeration::getInstance();
}
LLFloaterIMContainer::~LLFloaterIMContainer()
@@ -534,6 +537,33 @@ void LLFloaterIMContainer::idleUpdate()
mGeneralTitleInUse = !needs_override;
setTitle(needs_override ? conversation_floaterp->getTitle() : mGeneralTitle);
}
const LLConversationItem* nearby_session = getSessionModel(LLUUID());
if (nearby_session)
{
LLSpeakerMgr* speaker_mgr = (LLSpeakerMgr*)(LLLocalSpeakerMgr::getInstance());
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<LLConversationItemParticipant*>((*current_participant_model).get());
if (participant_model)
{
bool show_moderator_options = LLNearbyVoiceModeration::getInstance()->isNearbyChatModerator();
LLUUID participant_id = participant_model->getUUID();
if (participant_id != gAgentID)
{
// Don't show moderator options if participant is not connected to the same spatial channel
LLSpeaker* speakerp = speaker_mgr->findSpeaker(participant_id).get();
show_moderator_options &= speakerp && speakerp->isInVoiceChannel();
}
participant_model->setModeratorOptionsVisible(show_moderator_options);
}
current_participant_model++;
}
}
}
mParticipantRefreshTimer.setTimerExpirySec(1.0f);
@@ -1697,6 +1727,10 @@ bool LLFloaterIMContainer::visibleContextMenuItem(const LLSD& userdata)
{
return isMuted(conversation_item->getUUID());
}
else if ("can_allow_text_chat" == item)
{
return !isNearbyChatSpeakerSelected();
}
return true;
}
@@ -2026,9 +2060,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 (LLNearbyVoiceModeration::getInstance()->isNearbyChatModerator() && isNearbyChatSpeakerSelected())
{
// Determine here which actions are allowed
if ("can_moderate_voice" == userdata)
{
return true;
}
else if (("can_mute" == userdata))
{
return !is_self;
}
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;
}
@@ -2161,7 +2213,35 @@ 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)
{
// Request a mute/unmute using a capability request via the simulator
LLNearbyVoiceModeration::getInstance()->requestMuteIndividual(userID, !isMuted(userID));
}
else
if ("mute_all" == command)
{
// Send the mute_all request to the server
const bool mute_state = true;
LLNearbyVoiceModeration::getInstance()->requestMuteAll(mute_state);
}
else
if ("unmute_all" == command)
{
// Send the unmute_all request to the server
const bool mute_state = false;
LLNearbyVoiceModeration::getInstance()->requestMuteAll(mute_state);
}
return;
}
if (command.compare("selected"))
{
@@ -2279,6 +2359,31 @@ LLSpeaker * LLFloaterIMContainer::getSpeakerOfSelectedParticipant(LLSpeakerMgr *
return speaker_managerp->findSpeaker(participant_itemp->getUUID());
}
bool LLFloaterIMContainer::isNearbyChatSpeakerSelected()
{
LLFolderViewItem *selectedItem = mConversationsRoot->getCurSelectedItem();
if (!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();
}
void LLFloaterIMContainer::toggleAllowTextChat(const LLUUID& participant_uuid)
{
LLIMSpeakerMgr * speaker_managerp = dynamic_cast<LLIMSpeakerMgr*>(getSpeakerMgrForSelectedParticipant());
+1
View File
@@ -180,6 +180,7 @@ private:
void banSelectedMember(const LLUUID& participant_uuid);
void openNearbyChat();
bool isParticipantListExpanded();
bool isNearbyChatSpeakerSelected();
void idleUpdate(); // for convenience (self) from static idle
void idleProcessEvents();
+16
View File
@@ -2935,6 +2935,22 @@ void LLFloaterPreference::onChangeMaturity()
|| sim_access == SIM_ACCESS_ADULT);
getChild<LLIconCtrl>("rating_icon_adult")->setVisible(sim_access == SIM_ACCESS_ADULT);
// Update Legacy Search maturity settings
bool can_access_mature = gAgent.canAccessMature();
bool can_access_adult = gAgent.canAccessAdult();
if (!can_access_mature)
{
gSavedSettings.setBOOL("ShowMatureSims", false);
gSavedSettings.setBOOL("ShowMatureLand", false);
gSavedSettings.setBOOL("ShowMatureClassifieds", false);
}
if (!can_access_adult)
{
gSavedSettings.setBOOL("ShowAdultSims", false);
gSavedSettings.setBOOL("ShowAdultLand", false);
gSavedSettings.setBOOL("ShowAdultClassifieds", false);
}
}
void LLFloaterPreference::onChangeComplexityMode(const LLSD& newvalue)
+8 -1
View File
@@ -2015,7 +2015,14 @@ void LLItemBridge::performAction(LLInventoryModel* model, std::string action)
{
LLVector3d global_pos;
landmark->getGlobalPos(global_pos);
LLLandmarkActions::getSLURLfromPosGlobal(global_pos, &copy_slurl_to_clipboard_callback_inv, true);
if (!global_pos.isExactlyZero())
{
LLLandmarkActions::getSLURLfromPosGlobal(global_pos, &copy_slurl_to_clipboard_callback_inv, true);
}
else
{
LLNotificationsUtil::add("LandmarkLocationUnknown");
}
}
}
}
+14 -19
View File
@@ -338,26 +338,21 @@ void LLInventoryGalleryContextMenu::doToSelected(const LLSD& userdata)
{
LLVector3d global_pos;
landmark->getGlobalPos(global_pos);
boost::function<void(std::string& slurl)> copy_slurl_to_clipboard_cb = [](const std::string& slurl)
if (!global_pos.isExactlyZero())
{
gViewerWindow->getWindow()->copyTextToClipboard(utf8str_to_wstring(slurl));
LLSD args;
args["SLURL"] = slurl;
LLNotificationsUtil::add("CopySLURL", args);
};
// <FS:Zi> GCC12 warning: maybe-uninitialized - probably bogus
#if defined(__GNUC__) && (__GNUC__ >= 12)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wuninitialized"
#endif
// </FS:Zi>
LLLandmarkActions::getSLURLfromPosGlobal(global_pos, copy_slurl_to_clipboard_cb, true);
// <FS:Zi> GCC12 warning: maybe-uninitialized - probably bogus
#if defined(__GNUC__) && (__GNUC__ >= 12)
#pragma GCC diagnostic pop
#endif
// </FS:Zi>
boost::function<void(std::string& slurl)> copy_slurl_to_clipboard_cb = [](const std::string& slurl)
{
gViewerWindow->getWindow()->copyTextToClipboard(utf8str_to_wstring(slurl));
LLSD args;
args["SLURL"] = slurl;
LLNotificationsUtil::add("CopySLURL", args);
};
LLLandmarkActions::getSLURLfromPosGlobal(global_pos, copy_slurl_to_clipboard_cb, true);
}
else
{
LLNotificationsUtil::add("LandmarkLocationUnknown");
}
};
LLLandmark* landmark = LLLandmarkActions::getLandmark(mUUIDs.front(), copy_slurl_cb);
if (landmark)
+92 -12
View File
@@ -80,6 +80,40 @@ public:
}
};
class LLFetchFirstLandmarkByPos : public LLInventoryCollectFunctor
{
private:
LLVector3d mPos;
bool mFound = false;
public:
LLFetchFirstLandmarkByPos(const LLVector3d& pos) :
mPos(pos), mFound(false)
{
}
/*virtual*/ bool operator()(LLInventoryCategory* cat, LLInventoryItem* item)
{
if (mFound || !item || item->getType() != LLAssetType::AT_LANDMARK)
return false;
LLLandmark* landmark = gLandmarkList.getAsset(item->getAssetUUID());
if (!landmark) // the landmark not been loaded yet
return false;
LLVector3d landmark_global_pos;
if (!landmark->getGlobalPos(landmark_global_pos))
return false;
//we have to round off each coordinates to compare positions properly
mFound = ll_round(mPos.mdV[VX]) == ll_round(landmark_global_pos.mdV[VX])
&& ll_round(mPos.mdV[VY]) == ll_round(landmark_global_pos.mdV[VY])
&& ll_round(mPos.mdV[VZ]) == ll_round(landmark_global_pos.mdV[VZ]);
return mFound;
}
// only care about first found landmark, so stop when found
/*virtual*/ bool exceedsLimit() { return mFound; }
};
class LLFetchLandmarksByName : public LLInventoryCollectFunctor
{
private:
@@ -155,6 +189,9 @@ public:
mFounded = LLViewerParcelMgr::getInstance()->inAgentParcel(landmark_global_pos);
return mFounded;
}
// only care about first found landmark, so stop when found
/*virtual*/ bool exceedsLimit() { return mFounded; }
};
static void fetch_landmarks(LLInventoryModel::cat_array_t& cats,
@@ -197,23 +234,61 @@ bool LLLandmarkActions::landmarkAlreadyExists()
//static
bool LLLandmarkActions::hasParcelLandmark()
{
static LLUUID sLastItemID;
static S32 sLastFrame = -1;
if (sLastItemID.notNull())
{
LLInventoryItem* item = gInventory.getItem(sLastItemID);
if (item)
{
LLLandmark* landmark = gLandmarkList.getAsset(item->getAssetUUID());
if (landmark)
{
LLVector3d landmark_global_pos;
if (landmark->getGlobalPos(landmark_global_pos)
&& LLViewerParcelMgr::getInstance()->inAgentParcel(landmark_global_pos))
{
return true;
}
}
}
// Cached landmark does not match current parcel anymore,
// repeat inventory search to find a replacement landmark
// or to make sure there are none.
sLastItemID.setNull();
sLastFrame = -1;
}
if (sLastFrame == LLFrameTimer::getFrameCount())
{
// Ideally this should also check parcel change and landmark additions,
// not just frame change.
// But should be sufficient to check only frame as this is used
// after inventory and parcel operations.
return false;
}
sLastFrame = LLFrameTimer::getFrameCount();
LLFirstAgentParcelLandmark get_first_agent_landmark;
LLInventoryModel::cat_array_t cats;
LLInventoryModel::item_array_t items;
fetch_landmarks(cats, items, get_first_agent_landmark);
return !items.empty();
if (!items.empty())
{
sLastItemID = items[0]->getUUID();
return true;
}
return false;
}
// *TODO: This could be made more efficient by only fetching the FIRST
// landmark that meets the criteria
LLViewerInventoryItem* LLLandmarkActions::findLandmarkForGlobalPos(const LLVector3d &pos)
{
// Determine whether there are landmarks pointing to the current parcel.
// Will stop after first found landmark.
LLInventoryModel::cat_array_t cats;
LLInventoryModel::item_array_t items;
LLFetchlLandmarkByPos is_current_pos_landmark(pos);
fetch_landmarks(cats, items, is_current_pos_landmark);
LLFetchFirstLandmarkByPos get_landmark_from_pos(pos);
fetch_landmarks(cats, items, get_landmark_from_pos);
if(items.empty())
{
@@ -462,12 +537,17 @@ void LLLandmarkActions::copySLURLtoClipboard(const LLUUID& landmarkInventoryItem
{
LLVector3d global_pos;
landmark->getGlobalPos(global_pos);
// <FS:Beq> FIRE-30534 - changes related to var regions in opensim
// LLLandmarkActions::getSLURLfromPosGlobal(global_pos,&copy_slurl_to_clipboard_callback,true);
LLVector3 region_pos;
region_pos = landmark->getRegionPos();
LLLandmarkActions::getSLURLfromPosGlobalAndLocal(global_pos, region_pos, &copy_slurl_to_clipboard_callback,true);
// </FS:Beq>
if (!global_pos.isExactlyZero())
{
// <FS:Beq> FIRE-30534 - changes related to var regions in opensim
// LLLandmarkActions::getSLURLfromPosGlobal(global_pos, &copy_slurl_to_clipboard_callback, true);
LLLandmarkActions::getSLURLfromPosGlobalAndLocal(global_pos, landmark->getRegionPos(), &copy_slurl_to_clipboard_callback, true);
// </FS:Beq>
}
else
{
LLNotificationsUtil::add("LandmarkLocationUnknown");
}
}
}
+6 -2
View File
@@ -866,6 +866,10 @@ void LLMuteList::processMuteListUpdate(LLMessageSystem* msg, void**)
std::string unclean_filename;
msg->getStringFast(_PREHASH_MuteData, _PREHASH_Filename, unclean_filename);
std::string filename = LLDir::getScrubbedFileName(unclean_filename);
if (filename.empty())
{
LL_WARNS() << "Received empty mute list filename." << LL_ENDL;
}
LLMuteList* mute_list = getInstance();
mute_list->mLoadState = ML_REQUESTED;
@@ -898,16 +902,16 @@ void LLMuteList::processUseCachedMuteList(LLMessageSystem* msg, void**)
void LLMuteList::onFileMuteList(void** user_data, S32 error_code, LLExtStat ext_status)
{
LL_INFOS() << "LLMuteList::processMuteListFile()" << LL_ENDL;
std::string* local_filename_and_path = (std::string*)user_data;
if(local_filename_and_path && !local_filename_and_path->empty() && (error_code == 0))
{
LL_INFOS() << "Received mute list from server" << LL_ENDL;
LLMuteList::getInstance()->loadFromFile(*local_filename_and_path);
LLFile::remove(*local_filename_and_path);
}
else
{
LL_INFOS() << "LLMuteList xfer failed with code " << error_code << LL_ENDL;
LLMuteList::getInstance()->mLoadState = ML_FAILED;
}
delete local_filename_and_path;
+220
View File
@@ -0,0 +1,220 @@
/**
* @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 "llnotificationsutil.h"
#include "llviewerregion.h"
#include "llvoavatar.h"
#include "llvoicechannel.h"
#include "llvoiceclient.h"
#include "llviewerobjectlist.h"
#include "llviewerparcelmgr.h"
#include "roles_constants.h"
#include "llnearbyvoicemoderation.h"
LLNearbyVoiceModeration::LLNearbyVoiceModeration()
{
}
LLNearbyVoiceModeration::~LLNearbyVoiceModeration()
{
}
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;
}
}
const std::string LLNearbyVoiceModeration::getCapUrlFromRegion(LLViewerRegion* region)
{
if (! region || ! region->capabilitiesReceived())
{
return std::string();
}
std::string url = region->getCapability("SpatialVoiceModerationRequest");
if (url.empty())
{
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)
{
const std::string cap_url = getCapUrlFromRegion(avatar->getRegion());
if (cap_url.length())
{
const std::string operand = mute ? "mute" : "unmute";
LLSD body;
body["operand"] = operand;
body["agent_id"] = agent_id;
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);
}
}
}
void LLNearbyVoiceModeration::requestMuteAll(bool mute)
{
// Use our own avatar to get the region name
LLViewerRegion* region = gAgent.getRegion();
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;
LL_INFOS() << "For all residents in this region, applying: " << operand << LL_ENDL;
std::string success_msg =
STRINGIZE("Nearby voice for all residents was set to: " << operand);
std::string failure_msg =
STRINGIZE("Unable to set nearby voice for all residents to: " << operand);
LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(
cap_url,
body,
success_msg,
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;
}
}
if (mute && LLVoiceClient::getInstance()->getUserPTTState())
{
LLVoiceClient::getInstance()->setUserPTTState(false);
}
}
bool LLNearbyVoiceModeration::showNotificationIfNeeded()
{
if (LLVoiceClient::getInstance()->inProximalChannel() &&
LLVoiceClient::getInstance()->getIsModeratorMuted(gAgentID))
{
return showMutedNotification(true);
}
return false;
}
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()
{
// Region doesn't support WebRTC voice
if (!gAgent.getRegion() || !gAgent.getRegion()->isRegionWebRTCEnabled())
{
return false;
}
// Only show moderator options when connected to spatial voice chat
LLVoiceChannel* channel = LLVoiceChannel::getCurrentVoiceChannel();
if (!channel || channel->getSessionID().notNull() || !LLAgent::isActionAllowed("speak"))
{
return false;
}
if (LLViewerParcelMgr::getInstance()->isVoiceRestricted())
{
// Only the parcel owner should have access to moderate parcel voice space
return LLViewerParcelMgr::getInstance()->allowVoiceModeration();
}
else
{
return gAgent.canManageEstate();
}
}
+50
View File
@@ -0,0 +1,50 @@
/**
* @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 <LLNearbyVoiceModeration> {
LLSINGLETON(LLNearbyVoiceModeration);
~LLNearbyVoiceModeration();
public:
void requestMuteIndividual(const LLUUID& userID, bool mute);
void requestMuteAll(bool mute);
void setMutedInfo(const std::string& channelID, bool mute);
bool showMutedNotification(bool is_muted);
bool showNotificationIfNeeded();
bool isNearbyChatModerator();
private:
LLVOAvatar* getVOAvatarFromId(const LLUUID& id);
const std::string getCapUrlFromRegion(LLViewerRegion* region);
boost::signals2::connection mParcelCallbackConnection;
std::map<std::string, bool> mChannelMuteMap;
};
-24
View File
@@ -567,9 +567,6 @@ void LLPanelDirBrowser::processDirEventsReply(LLMessageSystem* msg, void**)
LLUUID owner_id;
std::string name;
std::string date;
bool show_pg = gSavedSettings.getBOOL("ShowPGEvents");
bool show_mature = gSavedSettings.getBOOL("ShowMatureEvents");
bool show_adult = gSavedSettings.getBOOL("ShowAdultEvents");
msg->getUUID("AgentData", "AgentID", agent_id);
msg->getUUID("QueryData", "QueryID", query_id );
@@ -626,27 +623,6 @@ void LLPanelDirBrowser::processDirEventsReply(LLMessageSystem* msg, void**)
LL_WARNS() << "skipped event due to owner_id null, event_id " << event_id << LL_ENDL;
continue;
}
// skip events that don't match the flags
// there's no PG flag, so we make sure neither adult nor mature is set
if (((event_flags & (EVENT_FLAG_ADULT | EVENT_FLAG_MATURE)) == EVENT_FLAG_NONE) && !show_pg)
{
//llwarns << "Skipped pg event because we're not showing pg, event_id " << event_id << llendl;
continue;
}
if ((event_flags & EVENT_FLAG_MATURE) && !show_mature)
{
//llwarns << "Skipped mature event because we're not showing mature, event_id " << event_id << llendl;
continue;
}
if ((event_flags & EVENT_FLAG_ADULT) && !show_adult)
{
//llwarns << "Skipped adult event because we're not showing adult, event_id " << event_id << llendl;
continue;
}
LLSD content;
content["type"] = EVENT_CODE;
+5 -6
View File
@@ -143,18 +143,17 @@ void LLPanelDirEvents::performQueryOrDelete(U32 event_id)
static LLUICachedControl<bool> incpg("ShowPGEvents", true);
static LLUICachedControl<bool> incmature("ShowMatureEvents", false);
static LLUICachedControl<bool> incadult("ShowAdultEvents", false);
if (!(incpg || incmature || incadult))
{
LLNotificationsUtil::add("NoContentToSearch");
return;
}
U32 scope = DFQ_DATE_EVENTS;
if (incpg) scope |= DFQ_INC_PG;
if (incmature && gAgent.canAccessMature()) scope |= DFQ_INC_MATURE;
if (incadult && gAgent.canAccessAdult()) scope |= DFQ_INC_ADULT;
if ( !( scope & (DFQ_INC_PG | DFQ_INC_MATURE | DFQ_INC_ADULT )))
{
LLNotificationsUtil::add("NoContentToSearch");
return;
}
setupNewSearch();
std::ostringstream params;
-14
View File
@@ -62,20 +62,6 @@ bool LLPanelDirLand::postBuild()
childSetValue("type", gSavedSettings.getString("FindLandType"));
bool adult_enabled = gAgent.canAccessAdult();
bool mature_enabled = gAgent.canAccessMature();
childSetVisible("incpg", true);
if (!mature_enabled)
{
childSetValue("incmature", false);
childDisable("incmature");
}
if (!adult_enabled)
{
childSetValue("incadult", false);
childDisable("incadult");
}
childSetCommitCallback("pricecheck", onCommitPrice, this);
childSetCommitCallback("areacheck", onCommitArea, this);
+5 -6
View File
@@ -121,6 +121,11 @@ void LLPanelDirPlaces::performQuery()
static LLUICachedControl<bool> inc_pg("ShowPGSims", true);
static LLUICachedControl<bool> inc_mature("ShowMatureSims", false);
static LLUICachedControl<bool> inc_adult("ShowAdultSims", false);
if (!(inc_pg || inc_mature || inc_adult))
{
LLNotificationsUtil::add("NoContentToSearch");
return;
}
if (inc_pg)
{
@@ -137,12 +142,6 @@ void LLPanelDirPlaces::performQuery()
flags |= DFQ_INC_ADULT;
}
if (0x0 == flags)
{
LLNotificationsUtil::add("NoContentToSearch");
return;
}
queryCore(query_string, category, flags);
}
+8 -5
View File
@@ -8352,11 +8352,8 @@ class LLAvatarEnableResetSkeleton : public view_listener_t
{
bool handleEvent(const LLSD& userdata)
{
if (find_avatar_from_object(LLSelectMgr::getInstance()->getSelection()->getPrimaryObject())) // <FS:Beq/> set but unused.
{
return true;
}
return false;
LLViewerObject* obj = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject();
return obj && obj->getAvatar();
}
};
@@ -11237,6 +11234,12 @@ LLVOAvatar* find_avatar_from_object(LLViewerObject* object)
}
else if( !object->isAvatar() )
{
// Check for animesh objects (animated objects with a control avatar)
LLVOAvatar* avatar = object->getAvatar();
if (avatar)
{
return avatar;
}
object = NULL;
}
}
+10
View File
@@ -750,6 +750,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()
+6
View File
@@ -185,6 +185,12 @@ public:
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;
+11
View File
@@ -3552,6 +3552,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");
@@ -4079,6 +4080,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;
+2
View File
@@ -457,6 +457,8 @@ public:
std::string getSimHostName();
bool isRegionWebRTCEnabled();
static bool isNewObjectCreationThrottleDisabled() {return sNewObjectCreationThrottle < 0;}
// rebuild reflection probe list
+14
View File
@@ -269,6 +269,20 @@ LLTrace::SampleStatHandle<LLUnit<F32, LLUnits::Percent> > HUDS_FRAME_PCT("huds_
LLTrace::SampleStatHandle<LLUnit<F32, LLUnits::Percent> > UI_FRAME_PCT("ui_frame_pct");
LLTrace::SampleStatHandle<LLUnit<F32, LLUnits::Percent> > SWAP_FRAME_PCT("swap_frame_pct");
LLTrace::SampleStatHandle<LLUnit<F32, LLUnits::Percent> > IDLE_FRAME_PCT("idle_frame_pct");
LLTrace::SampleStatHandle<U32> WEBRTC_PACKETS_IN_LOST("webrtc_packets_in_lost", "Lost incoming packets"),
WEBRTC_PACKETS_IN_RECEIVED("webrtc_packets_in_recv", "Incoming packets received"),
WEBRTC_PACKETS_OUT_SENT("webrtc_packets_out_sent", "Outgoing packets sent"),
WEBRTC_PACKETS_OUT_LOST("webrtc_packets_out_lost", "Lost outgoing packets");
LLTrace::SampleStatHandle<F32> WEBRTC_JITTER_OUT("webrtc_jitter_out", "Timing variation of outgoing audio"),
WEBRTC_JITTER_IN("webrtc_jitter_in", "Timing variation of incoming audio"),
WEBRTC_LATENCY("webrtc_latency", "Round-trip audio delay"),
WEBRTC_UPLOAD_BANDWIDTH("webrtc_upload_bandwidth", "Estimated upload bandwidth"),
WEBRTC_JITTER_BUFFER("webrtc_jitter_buffer", "Average delay added to smooth incoming audio");
}
LLViewerStats::LLViewerStats()
+3
View File
@@ -237,6 +237,9 @@ extern LLTrace::EventStatHandle<LLUnit<F32, LLUnits::Percent> > OBJECT_CACHE_HIT
extern LLTrace::SampleStatHandle<F64> NOTRMALIZED_FRAMETIME_JITTER_SESSION;
extern LLTrace::SampleStatHandle<F64> NORMALIZED_FRAMTIME_JITTER_PERIOD;
extern LLTrace::SampleStatHandle<U32> WEBRTC_PACKETS_IN_LOST, WEBRTC_PACKETS_IN_RECEIVED, WEBRTC_PACKETS_OUT_SENT, WEBRTC_PACKETS_OUT_LOST;
extern LLTrace::SampleStatHandle<F32> WEBRTC_JITTER_OUT, WEBRTC_JITTER_IN, WEBRTC_LATENCY, WEBRTC_UPLOAD_BANDWIDTH, WEBRTC_JITTER_BUFFER;
}
class LLViewerStats : public LLSingleton<LLViewerStats>
+8
View File
@@ -39,6 +39,7 @@
#include "llagent.h"
#include "lltrans.h"
#include "lluiusage.h"
#include "llnearbyvoicemoderation.h"
const F32 LLVoiceClient::OVERDRIVEN_POWER_LEVEL = 0.7f;
@@ -733,6 +734,13 @@ bool LLVoiceClient::getPTTIsToggle()
void LLVoiceClient::inputUserControlState(bool down)
{
if (down && !getUserPTTState())
{
// Nearby chat is muted by moderator, don't toggle PTT
if (LLNearbyVoiceModeration::getInstance()->showNotificationIfNeeded())
return;
}
if(mPTTIsToggle)
{
if(down) // toggle open-mic state on 'down'
+162 -2
View File
@@ -61,10 +61,12 @@
#include "llrand.h"
#include "llviewerwindow.h"
#include "llviewercamera.h"
#include "llviewerstats.h"
#include "llversioninfo.h"
#include "llviewernetwork.h"
#include "llnotificationsutil.h"
#include "llnearbyvoicemoderation.h"
#include "llcorehttputil.h"
#include "lleventfilter.h"
@@ -80,6 +82,8 @@
const std::string WEBRTC_VOICE_SERVER_TYPE = "webrtc";
const F32 STATS_TIMER_DELAY = 2.0;
// <FS:minerjr> [FIRE-36022] - Removing my USB headset crashes entire viewer
using namespace std::chrono_literals; // Needed for shared timed mutex to use time
// </FS:minerjr> [FIRE-36022]
@@ -2985,6 +2989,7 @@ bool LLVoiceWebRTCConnection::connectionStateMachine()
}
mWebRTCAudioInterface->setReceiveVolume(mSpeakerVolume);
LLWebRTCVoiceClient::getInstance()->OnConnectionEstablished(mChannelID, mRegionID);
resetConnectionStats();
setVoiceConnectionState(VOICE_STATE_WAIT_FOR_DATA_CHANNEL);
break;
}
@@ -3038,6 +3043,13 @@ bool LLVoiceWebRTCConnection::connectionStateMachine()
sendJoin();
}
}
static LLTimer stats_timer;
if (stats_timer.getElapsedTimeF32() > STATS_TIMER_DELAY)
{
mWebRTCPeerConnectionInterface->gatherConnectionStats();
stats_timer.reset();
}
}
break;
}
@@ -3254,12 +3266,54 @@ 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();
bool is_moderator_muted = participant_obj["m"].as_bool();
if (isSpatial())
{
// 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.
boost::json::object root;
@@ -3331,6 +3385,112 @@ void LLVoiceWebRTCConnection::sendJoin()
mWebRTCDataInterface->sendData(json_data, false);
}
void LLVoiceWebRTCConnection::OnStatsDelivered(const llwebrtc::LLWebRTCStatsMap& stats_data)
{
LL::WorkQueue::postMaybe(mMainQueue, [=, this]
{
if (mShutDown)
{
return;
}
for (const auto& [stats_id, attributes] : stats_data)
{
if (attributes.contains("currentRoundTripTime"))
{
F32 rtt_seconds = 0.0f;
LLStringUtil::convertToF32(attributes.at("currentRoundTripTime"), rtt_seconds);
sample(LLStatViewer::WEBRTC_LATENCY, rtt_seconds * 1000.0f);
}
if (attributes.contains("availableOutgoingBitrate"))
{
F32 bitrate_bps = 0.0f;
LLStringUtil::convertToF32(attributes.at("availableOutgoingBitrate"), bitrate_bps);
sample(LLStatViewer::WEBRTC_UPLOAD_BANDWIDTH, bitrate_bps / 1000.0f);
}
// Stat type detection below is heuristic-based.
// It's relied on specific fields to distinguish outbound-rtp, remote-inbound-rtp, and inbound-rtp.
// This approach works with current WebRTC stats but may need updating later.
// Outbound RTP
if (attributes.contains("mediaSourceId"))
{
U32 out_packets_sent = 0;
LLStringUtil::convertToU32(attributes.at("packetsSent"), out_packets_sent);
sample(LLStatViewer::WEBRTC_PACKETS_OUT_SENT, out_packets_sent);
}
// Remote-Inbound RTP
else if (attributes.contains("localId"))
{
if (attributes.contains("packetsLost"))
{
U32 out_packets_lost = 0;
LLStringUtil::convertToU32(attributes.at("packetsLost"), out_packets_lost);
sample(LLStatViewer::WEBRTC_PACKETS_OUT_LOST, out_packets_lost);
}
if (attributes.contains("jitter"))
{
F32 jitter_seconds = 0.0f;
LLStringUtil::convertToF32(attributes.at("jitter"), jitter_seconds);
sample(LLStatViewer::WEBRTC_JITTER_OUT, jitter_seconds * 1000.0f);
}
}
// Inbound RTP
else if (attributes.contains("jitterBufferDelay"))
{
if (attributes.contains("packetsLost"))
{
U32 in_packets_lost = 0;
LLStringUtil::convertToU32(attributes.at("packetsLost"), in_packets_lost);
sample(LLStatViewer::WEBRTC_PACKETS_IN_LOST, in_packets_lost);
}
if (attributes.contains("packetsReceived"))
{
U32 in_packets_recv = 0;
LLStringUtil::convertToU32(attributes.at("packetsReceived"), in_packets_recv);
sample(LLStatViewer::WEBRTC_PACKETS_IN_RECEIVED, in_packets_recv);
}
if (attributes.contains("jitter"))
{
F32 jitter_seconds = 0.0f;
LLStringUtil::convertToF32(attributes.at("jitter"), jitter_seconds);
sample(LLStatViewer::WEBRTC_JITTER_IN, jitter_seconds * 1000.0f);
}
if (attributes.contains("jitterBufferDelay") && attributes.contains("jitterBufferEmittedCount"))
{
F32 total_delay_seconds = 0.0f;
F32 emitted_count_f = 0.0f;
// total delay in seconds
LLStringUtil::convertToF32(attributes.at("jitterBufferDelay"), total_delay_seconds);
// number of packets played out
LLStringUtil::convertToF32(attributes.at("jitterBufferEmittedCount"), emitted_count_f);
if (emitted_count_f > 0.0f)
{
F32 avg_delay_seconds = total_delay_seconds / emitted_count_f;
F32 avg_delay_ms = avg_delay_seconds * 1000.0f;
sample(LLStatViewer::WEBRTC_JITTER_BUFFER, avg_delay_seconds * 1000.0f);
}
}
}
}
});
}
void LLVoiceWebRTCConnection::resetConnectionStats()
{
sample(LLStatViewer::WEBRTC_JITTER_BUFFER, 0);
sample(LLStatViewer::WEBRTC_JITTER_IN, 0);
sample(LLStatViewer::WEBRTC_JITTER_OUT, 0);
sample(LLStatViewer::WEBRTC_LATENCY, 0);
sample(LLStatViewer::WEBRTC_PACKETS_IN_LOST, 0);
sample(LLStatViewer::WEBRTC_PACKETS_IN_RECEIVED, 0);
sample(LLStatViewer::WEBRTC_PACKETS_OUT_SENT, 0);
sample(LLStatViewer::WEBRTC_PACKETS_OUT_LOST, 0);
sample(LLStatViewer::WEBRTC_UPLOAD_BANDWIDTH, 0);
}
/////////////////////////////
// WebRTC Spatial Connection
+6
View File
@@ -540,6 +540,8 @@ private:
static bool sShuttingDown;
LLEventMailDrop mWebRTCPump;
LLSD mLastWebRTCStats;
};
@@ -603,6 +605,8 @@ class LLVoiceWebRTCConnection :
//@{
void OnDataReceived(const std::string &data, bool binary) override;
void OnDataChannelReady(llwebrtc::LLWebRTCDataInterface *data_interface) override;
void OnStatsDelivered(const llwebrtc::LLWebRTCStatsMap& stats_data) override;
//@}
void OnDataReceivedImpl(const std::string &data, bool binary);
@@ -638,6 +642,8 @@ class LLVoiceWebRTCConnection :
void OnVoiceConnectionRequestSuccess(const LLSD &body);
void resetConnectionStats();
protected:
typedef enum e_voice_connection_state
{
@@ -440,6 +440,64 @@
</stat_view>
</stat_view>
</stat_view>
<stat_view name="voice"
label="Voice"
setting="OpenDebugStatVoice">
<stat_bar
name="webrtc_latency"
label="Latency"
stat="webrtc_latency"
unit_label="ms"
decimal_digits="1"/>
<stat_bar
name="webrtc_upload_bandwidth"
label="Upload bandwidth"
stat="webrtc_upload_bandwidth"
unit_label="kbps"
decimal_digits="0"/>
<stat_view name="incoming_audio"
label="Incoming audio"
setting="OpenDebugStatVoiceIncoming">
<stat_bar
name="incoming_packet_recv"
label="Packets received"
stat="webrtc_packets_in_recv"/>
<stat_bar
name="packets_in_lost"
label="Packets lost"
stat="webrtc_packets_in_lost"/>
<stat_bar
name="jitter_in"
label="Jitter"
stat="webrtc_jitter_in"
unit_label="ms"
decimal_digits="1"/>
<stat_bar
name="jitter_buffer"
label="Jitter buffer"
stat="webrtc_jitter_buffer"
unit_label="ms"
decimal_digits="1"/>
</stat_view>
<stat_view name="outgoing_audio"
label="Outgoing audio"
setting="OpenDebugStatVoiceOutgoing">
<stat_bar
name="packets_out_sent"
label="Packets sent"
stat="webrtc_packets_out_sent"/>
<stat_bar
name="packets_out_lost"
label="Packets lost"
stat="webrtc_packets_out_lost"/>
<stat_bar
name="jitter_out"
label="Jitter"
stat="webrtc_jitter_out"
unit_label="ms"
decimal_digits="1"/>
</stat_view>
</stat_view>
</container_view>
</scroll_container>
</floater>
@@ -176,57 +176,60 @@
<on_click function="Group.DoToSelected" parameter="leave_group"/>
<on_enable function="Avatar.EnableItem" parameter="can_leave_group" />
</menu_item_call>
<menu_item_separator layout="topleft" name="Moderator Options Separator"/>
<context_menu
label="Moderator Options"
layout="topleft"
name="Moderator Options">
<menu_item_check
label="Allow text chat"
layout="topleft"
name="AllowTextChat">
<on_check function="Avatar.CheckItem" parameter="is_allowed_text_chat" />
<on_click function="Avatar.DoToSelected" parameter="toggle_allow_text_chat" />
<on_enable function="Avatar.EnableItem" parameter="can_allow_text_chat" />
</menu_item_check>
<menu_item_separator layout="topleft" name="moderate_voice_separator" />
<menu_item_call
label="Mute this participant"
layout="topleft"
name="ModerateVoiceMuteSelected">
<on_click function="Avatar.DoToSelected" parameter="selected" />
<on_enable function="Avatar.EnableItem" parameter="can_mute" />
<on_visible function="Avatar.VisibleItem" parameter="show_mute" />
</menu_item_call>
<menu_item_separator layout="topleft" name="Moderator Options Separator"/>
<context_menu
label="Moderator Options"
layout="topleft"
name="Moderator Options">
<menu_item_check
label="Allow text chat"
layout="topleft"
name="AllowTextChat">
<on_check function="Avatar.CheckItem" parameter="is_allowed_text_chat" />
<on_click function="Avatar.DoToSelected" parameter="toggle_allow_text_chat" />
<on_enable function="Avatar.EnableItem" parameter="can_allow_text_chat" />
<on_visible function="Avatar.VisibleItem" parameter="can_allow_text_chat" />
</menu_item_check>
<menu_item_separator layout="topleft" name="moderate_voice_separator">
<on_visible function="Avatar.VisibleItem" parameter="can_allow_text_chat" />
</menu_item_separator>
<menu_item_call
label="Mute this participant"
layout="topleft"
name="ModerateVoiceMuteSelected">
<on_click function="Avatar.DoToSelected" parameter="selected" />
<on_enable function="Avatar.EnableItem" parameter="can_mute" />
<on_visible function="Avatar.VisibleItem" parameter="show_mute" />
</menu_item_call>
<menu_item_call
label="Unmute this participant"
layout="topleft"
name="ModerateVoiceUnMuteSelected">
<on_click function="Avatar.DoToSelected" parameter="selected" />
<on_enable function="Avatar.EnableItem" parameter="can_unmute" />
<on_visible function="Avatar.VisibleItem" parameter="show_unmute" />
</menu_item_call>
<menu_item_call
label="Mute everyone"
layout="topleft"
name="ModerateVoiceMute">
<on_click function="Avatar.DoToSelected" parameter="mute_all" />
<on_enable function="Avatar.EnableItem" parameter="can_moderate_voice" />
</menu_item_call>
<menu_item_call
label="Unmute everyone"
layout="topleft"
name="ModerateVoiceUnmute">
<on_click function="Avatar.DoToSelected" parameter="unmute_all" />
<on_enable function="Avatar.EnableItem" parameter="can_moderate_voice" />
</menu_item_call>
</context_menu>
<menu_item_separator layout="topleft" name="Group Ban Separator"/>
<menu_item_call
label="Unmute this participant"
layout="topleft"
name="ModerateVoiceUnMuteSelected">
<on_click function="Avatar.DoToSelected" parameter="selected" />
<on_enable function="Avatar.EnableItem" parameter="can_unmute" />
<on_visible function="Avatar.VisibleItem" parameter="show_unmute" />
</menu_item_call>
<menu_item_call
label="Mute everyone"
layout="topleft"
name="ModerateVoiceMute">
<on_click function="Avatar.DoToSelected" parameter="mute_all" />
<on_enable function="Avatar.EnableItem" parameter="can_moderate_voice" />
</menu_item_call>
<menu_item_call
label="Unmute everyone"
layout="topleft"
name="ModerateVoiceUnmute">
<on_click function="Avatar.DoToSelected" parameter="unmute_all" />
<on_enable function="Avatar.EnableItem" parameter="can_moderate_voice" />
</menu_item_call>
</context_menu>
<menu_item_separator layout="topleft" name="Group Ban Separator"/>
<menu_item_call
label="Ban member"
layout="topleft"
name="BanMember">
<on_click function="Avatar.DoToSelected" parameter="ban_member" />
<on_enable function="Avatar.EnableItem" parameter="can_ban_member" />
</menu_item_call>
label="Ban member"
layout="topleft"
name="BanMember">
<on_click function="Avatar.DoToSelected" parameter="ban_member" />
<on_enable function="Avatar.EnableItem" parameter="can_ban_member" />
</menu_item_call>
</toggleable_menu>
@@ -9871,6 +9871,23 @@ Your voice has been muted by a moderator.
yestext="OK"/>
</notification>
<notification
icon="notifytip.tga"
name="NearbyVoiceMutedByModerator"
type="notifytip">
The moderator has muted your voice chat.
People in this location will not hear you if you speak.
<tag>voice</tag>
</notification>
<notification
icon="notifytip.tga"
name="NearbyVoiceUnmutedByModerator"
type="notifytip">
Your voice chat can now be heard by people in this location.
<tag>voice</tag>
</notification>
<notification
icon="alertmodal.tga"
name="FailedToGetBenefits"