Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0a14318
First commit for a second experiment, investigating how nearby voice/…
callumlinden Oct 9, 2025
219da2a
Remove separator bar for Nearby Voice
callumlinden Oct 9, 2025
53d8310
Improve robustness of when moderator options appear and add some init…
callumlinden Oct 13, 2025
c64c16a
Merge branch 'develop' of https://github.com/secondlife/viewer into c…
callumlinden Oct 20, 2025
21e9b38
Merge branch 'develop' of https://github.com/secondlife/viewer into c…
callumlinden Oct 23, 2025
cf048cf
#4013 Update voice moderator options; show notifications when muted; …
maxim-productengine Oct 30, 2025
c39135c
#4013 add simple voice moderation permission check
maxim-productengine Nov 7, 2025
74a64d2
Merge branch 'develop' into maxim/voice-moderation
maxim-productengine Nov 7, 2025
a4d01ed
Show moderator options only on webrtc region
maxim-productengine Nov 11, 2025
d9ec89a
Ignore muted flags from non-primary voice server
maxim-productengine Nov 12, 2025
8111052
#4994 remove redundant moderator_id key
maxim-productengine Nov 13, 2025
bee23b4
#4995 change muted/unmuted alerts to non-modal toast
maxim-productengine Nov 13, 2025
e740bd2
Toggle off 'Speak' button when muted by moderator
maxim-productengine Nov 14, 2025
6ee41d6
#5018 add webrtc connection statistics
maxim-productengine Nov 26, 2025
ec149b5
#5018 mac build fix
maxim-productengine Nov 26, 2025
a9e8676
#5055 don't show moderate menu if the user is not parcel owner within…
maxim-productengine Nov 27, 2025
1565e46
Merge branch 'develop' into maxim/voice-moderation
maxim-productengine Dec 1, 2025
8b1e44e
Merge branch 'develop' into maxim/voice-moderation
maxim-productengine Dec 2, 2025
88a3d95
#5088 Hide 'Moderation options' menu when disconnected from spatial v…
maxim-productengine Dec 2, 2025
3647e95
Merge pull request #5096 from secondlife/develop
Geenz Dec 3, 2025
c4ec3d8
Merge pull request #5100 from secondlife/develop
Geenz Dec 3, 2025
9f82b90
#3612 "Copy SLURL" from Favorites bar not working
akleshchev Dec 5, 2025
4f22c12
#5109 LLExperienceCache crashes on a coroutine
akleshchev Dec 5, 2025
3fd68bc
#4931 Fix missed name cache connection #2
akleshchev Dec 5, 2025
49c73ac
#3612 "Copy SLURL" from Favorites bar not working #2
akleshchev Dec 8, 2025
cbe606d
Merge branch 'release/2026.01' into maxim/voice-moderation
maxim-productengine Dec 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 20 additions & 13 deletions indra/llmessage/llexperiencecache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,7 @@ void LLExperienceCache::initSingleton()
constexpr size_t CORO_QUEUE_SIZE = 2048;
LLCoprocedureManager::instance().initializePool("ExpCache", CORO_QUEUE_SIZE);

LLCoros::instance().launch("LLExperienceCache::idleCoro",
boost::bind(&LLExperienceCache::idleCoro, this));

LLCoros::instance().launch("LLExperienceCache::idleCoro", LLExperienceCache::idleCoro);
}

void LLExperienceCache::cleanup()
Expand Down Expand Up @@ -246,6 +244,7 @@ const LLExperienceCache::cache_t& LLExperienceCache::getCached()
return mCache;
}

// static because used by coroutine and can outlive the instance
void LLExperienceCache::requestExperiencesCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t &httpAdapter, std::string url, RequestQueue_t requests)
{
LLCore::HttpRequest::ptr_t httpRequest = std::make_shared<LLCore::HttpRequest>();
Expand All @@ -254,6 +253,13 @@ void LLExperienceCache::requestExperiencesCoro(LLCoreHttpUtil::HttpCoroutineAdap

LLSD result = httpAdapter->getAndSuspend(httpRequest, url);

if (sShutdown)
{
return;
}

LLExperienceCache* self = LLExperienceCache::getInstance();

LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);

Expand All @@ -265,7 +271,7 @@ void LLExperienceCache::requestExperiencesCoro(LLCoreHttpUtil::HttpCoroutineAdap
// build dummy entries for the failed requests
for (RequestQueue_t::const_iterator it = requests.begin(); it != requests.end(); ++it)
{
LLSD exp = get(*it);
LLSD exp = self->get(*it);
//leave the properties alone if we already have a cache entry for this xp
if (exp.isUndefined())
{
Expand All @@ -278,7 +284,7 @@ void LLExperienceCache::requestExperiencesCoro(LLCoreHttpUtil::HttpCoroutineAdap
exp["error"] = (LLSD::Integer)status.getType();
exp[QUOTA] = DEFAULT_QUOTA;

processExperience(*it, exp);
self->processExperience(*it, exp);
}
return;
}
Expand All @@ -294,7 +300,7 @@ void LLExperienceCache::requestExperiencesCoro(LLCoreHttpUtil::HttpCoroutineAdap
LL_DEBUGS("ExperienceCache") << "Received result for " << public_key
<< " display '" << row[LLExperienceCache::NAME].asString() << "'" << LL_ENDL;

processExperience(public_key, row);
self->processExperience(public_key, row);
}

LLSD error_ids = result["error_ids"];
Expand All @@ -310,7 +316,7 @@ void LLExperienceCache::requestExperiencesCoro(LLCoreHttpUtil::HttpCoroutineAdap
exp[MISSING] = true;
exp[QUOTA] = DEFAULT_QUOTA;

processExperience(id, exp);
self->processExperience(id, exp);
LL_WARNS("ExperienceCache") << "LLExperienceResponder::result() error result for " << id << LL_ENDL;
}

Expand Down Expand Up @@ -361,7 +367,7 @@ void LLExperienceCache::requestExperiences()
if (mRequestQueue.empty() || (ostr.tellp() > EXP_URL_SEND_THRESHOLD))
{ // request is placed in the coprocedure pool for the ExpCache cache. Throttling is done by the pool itself.
LLCoprocedureManager::instance().enqueueCoprocedure("ExpCache", "RequestExperiences",
boost::bind(&LLExperienceCache::requestExperiencesCoro, this, _1, ostr.str(), requests) );
boost::bind(&LLExperienceCache::requestExperiencesCoro, _1, ostr.str(), requests) );

ostr.str(std::string());
ostr << urlBase << "?page_size=" << PAGE_SIZE1;
Expand Down Expand Up @@ -393,7 +399,7 @@ void LLExperienceCache::setCapabilityQuery(LLExperienceCache::CapabilityQuery_t
mCapability = queryfn;
}


// static, because coro can outlive the instance
void LLExperienceCache::idleCoro()
{
const F32 SECS_BETWEEN_REQUESTS = 0.5f;
Expand All @@ -402,14 +408,15 @@ void LLExperienceCache::idleCoro()
LL_INFOS("ExperienceCache") << "Launching Experience cache idle coro." << LL_ENDL;
do
{
if (mEraseExpiredTimer.checkExpirationAndReset(ERASE_EXPIRED_TIMEOUT))
LLExperienceCache* self = LLExperienceCache::getInstance();
if (self->mEraseExpiredTimer.checkExpirationAndReset(ERASE_EXPIRED_TIMEOUT))
{
eraseExpired();
self->eraseExpired();
}

if (!mRequestQueue.empty())
if (!self->mRequestQueue.empty())
{
requestExperiences();
self->requestExperiences();
}

llcoro::suspendUntilTimeout(SECS_BETWEEN_REQUESTS);
Expand Down
4 changes: 2 additions & 2 deletions indra/llmessage/llexperiencecache.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,9 @@ class LLExperienceCache: public LLSingleton < LLExperienceCache >
std::string mCacheFileName;
static bool sShutdown; // control for coroutines, they exist out of LLExperienceCache's scope, so they need a static control

void idleCoro();
static void idleCoro();
void eraseExpired();
void requestExperiencesCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t &, std::string, RequestQueue_t);
static void requestExperiencesCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t &, std::string, RequestQueue_t);
void requestExperiences();

void fetchAssociatedExperienceCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t &, LLUUID, LLUUID, std::string, ExperienceGetFn_t);
Expand Down
51 changes: 51 additions & 0 deletions indra/llwebrtc/llwebrtc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1530,6 +1530,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()
{
Expand Down
6 changes: 6 additions & 0 deletions indra/llwebrtc/llwebrtc.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#ifndef LLWEBRTC_H
#define LLWEBRTC_H

#include <map>
#include <string>
#include <vector>

Expand All @@ -55,6 +56,7 @@

namespace llwebrtc
{
typedef std::map<std::string, std::map<std::string, std::string>> LLWebRTCStatsMap;

class LLWebRTCLogCallback
{
Expand Down Expand Up @@ -240,6 +242,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,
Expand Down Expand Up @@ -273,6 +277,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
Expand Down
2 changes: 2 additions & 0 deletions indra/llwebrtc/llwebrtc_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,8 @@ class LLWebRTCPeerConnectionImpl : public LLWebRTCPeerConnectionInterface,
void enableSenderTracks(bool enable);
void enableReceiverTracks(bool enable);

void gatherConnectionStats() override;

protected:

LLWebRTCImpl * mWebRTCImpl;
Expand Down
2 changes: 2 additions & 0 deletions indra/newview/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,7 @@ set(viewer_SOURCE_FILES
llfloaterimnearbychat.cpp
llfloaterimnearbychathandler.cpp
llfloaterimnearbychatlistener.cpp
llnearbyvoicemoderation.cpp
llnetmap.cpp
llnotificationalerthandler.cpp
llnotificationgrouphandler.cpp
Expand Down Expand Up @@ -1103,6 +1104,7 @@ set(viewer_HEADER_FILES
llnameeditor.h
llnamelistctrl.h
llnavigationbar.h
llnearbyvoicemoderation.h
llnetmap.h
llnotificationhandler.h
llnotificationlistitem.h
Expand Down
33 changes: 33 additions & 0 deletions indra/newview/app_settings/settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6093,6 +6093,39 @@
<key>Value</key>
<integer>0</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>
Expand Down
36 changes: 33 additions & 3 deletions indra/newview/llfavoritesbar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1395,6 +1395,19 @@ bool LLFavoritesBarCtrl::enableSelected(const LLSD& userdata)
{
return !LLAgentPicksInfo::getInstance()->isPickLimitReached();
}
else if (param == "copy_slurl"
|| param == "show_on_map")
{
LLViewerInventoryItem* item = gInventory.getItem(mSelectedItemID);
if (nullptr == item)
return false; // shouldn't happen as it is selected from existing items

const LLUUID& asset_id = item->getAssetUUID();

// Favorites are supposed to be loaded first, it should be here already
LLLandmark* landmark = gLandmarkList.getAsset(asset_id, NULL /*callback*/);
return nullptr != landmark;
}

return false;
}
Expand Down Expand Up @@ -1425,10 +1438,17 @@ void LLFavoritesBarCtrl::doToSelected(const LLSD& userdata)
LLVector3d posGlobal;
LLLandmarkActions::getLandmarkGlobalPos(mSelectedItemID, posGlobal);

// inventory item and asset exist, otherwise
// enableSelected wouldn't have let it get here,
// only need to check location validity
if (!posGlobal.isExactlyZero())
{
LLLandmarkActions::getSLURLfromPosGlobal(posGlobal, copy_slurl_to_clipboard_cb);
}
else
{
LLNotificationsUtil::add("LandmarkLocationUnknown");
}
}
else if (action == "show_on_map")
{
Expand All @@ -1437,10 +1457,20 @@ void LLFavoritesBarCtrl::doToSelected(const LLSD& userdata)
LLVector3d posGlobal;
LLLandmarkActions::getLandmarkGlobalPos(mSelectedItemID, posGlobal);

if (!posGlobal.isExactlyZero() && worldmap_instance)
if (worldmap_instance)
{
worldmap_instance->trackLocation(posGlobal);
LLFloaterReg::showInstance("world_map", "center");
// inventory item and asset exist, otherwise
// enableSelected wouldn't have let it get here,
// only need to check location validity
if (!posGlobal.isExactlyZero())
{
worldmap_instance->trackLocation(posGlobal);
LLFloaterReg::showInstance("world_map", "center");
}
else
{
LLNotificationsUtil::add("LandmarkLocationUnknown");
}
}
}
else if (action == "create_pick")
Expand Down
Loading
Loading