Implement support for PRIM_MEDIA_FIRST_CLICK_INTERACT and autoplay for HUD media

master
Darl 2025-04-25 17:46:38 -05:00
parent aeefc73129
commit ea75bfd410
6 changed files with 503 additions and 94 deletions

View File

@ -0,0 +1,160 @@
# Test plan for PRIM_MEDIA_FIRST_CLICK_INTERACT
## Requirements
- At least two accounts
- At least one group
- Land under your control
## Feature Brief
Historically media-on-a-prim (MOAP) in Second Life has been bound to a focus system which blocks mouse click/hover events, this feature creates exceptions to this focus system for a configurable set of objects to meet user preference.
## Testing
The following scripts and test cases cover each individual operational mode of the feature; in practice these modes can be combined by advanced users in any configuration they desire from debug settings. Even though the intended use case combines multiple modes, individual modes can be tested for functionality when tested as described below.
If testing an arbitrary combination of operational modes beyond what the GUI offers is desired, the parameters of the bitfield for calculation are located in lltoolpie.h under the MediaFirstClickTypes enum. As of writing there exists a total of ~127 possible unique/valid combinations, which is why testing each mode individually is considered the most efficient for a full functionality test.
### Scripts
#### Script A
This script creates a media surface that is eligible for media first-click interact. Depending on test conditions, this will exhibit new behavior.
```lsl
default {
state_entry() {
llSetLinkMedia( LINK_THIS, 0, [
PRIM_MEDIA_CURRENT_URL, "http://lecs-viewer-web-components.s3.amazonaws.com/v3.0/agni/avatars.html",
PRIM_MEDIA_HOME_URL, "http://lecs-viewer-web-components.s3.amazonaws.com/v3.0/agni/avatars.html",
PRIM_MEDIA_FIRST_CLICK_INTERACT, TRUE,
PRIM_MEDIA_AUTO_PLAY, TRUE,
PRIM_MEDIA_CONTROLS, PRIM_MEDIA_CONTROLS_MINI
] );
}
}
```
#### Script B
This script creates a media surface that is NOT eligible for media first-click interact. In all but one test case, this will behave the same way.
```lsl
default {
state_entry() {
llSetLinkMedia( LINK_THIS, 0, [
PRIM_MEDIA_CURRENT_URL, "http://lecs-viewer-web-components.s3.amazonaws.com/v3.0/agni/avatars.html",
PRIM_MEDIA_HOME_URL, "http://lecs-viewer-web-components.s3.amazonaws.com/v3.0/agni/avatars.html",
PRIM_MEDIA_FIRST_CLICK_INTERACT, FALSE,
PRIM_MEDIA_AUTO_PLAY, TRUE,
PRIM_MEDIA_CONTROLS, PRIM_MEDIA_CONTROLS_MINI
] );
}
}
```
## Standard testing procedure
You will be asked to enable media faces on multiple cubes, make sure that the webpage loads on each, and interact with them in the following ways.
1. Enable media for the cube, and verify that it displays a webpage.
2. Click on the terrain to clear any focus.
3. Hover your mouse over UI elements of the webpage, and **observe** if they highlight/react to the mouse cursor.
4. If hover events are not registered, clicking on the webpage and then **observe** if they begin reacting to hover events.
5. Clicking on the terrain to clear any focus once again.
6. Clicking on a UI element of the webpage and **observe** if it reacts to the first click, or requires a second click. *(Maximum of 2 clicks per attempt)*
These steps will be repeated for one or more pairs of cubes per test case to ensure that media first click interact is functioning within expectations. Unless otherwise mentioned for a specific test case, you simply need only be in the same region as the cubes to test with them.
## Test cases
All test cases begin with at least two cubes rezzed, one containing Script A henceforth referred to as Cube A and one with Script B referred to as Cube B. The steps of some test cases may impact the condition of the cubes, so keeping a spare set rezzed or in inventory to rapidly duplicate should improve efficiency if testing cases in series.
### Case 1 (MEDIA_FIRST_CLICK_NONE)
Ensure that debug setting `MediaFirstClickInteract` is set to `0`
Starting with Cube A and Cube B, perform the testing procedure on each.
**Expected observations:** Both webpages do not react to hover events until clicked, both webpages do not react to clicks until clicked once to establish focus
### Case 2 (MEDIA_FIRST_CLICK_HUD)
Ensure that debug setting `MediaFirstClickInteract` is set to `1`
Starting with Cube A and Cube B, attach them both to your HUD and perform the testing procedure on each. You may need to rotate or scale the cubes to fit on your screen before beginning testing. You may attach both at the same time, or only one at a time.
**Expected observations:** The webpage on Cube A will react to mouse cursor hover events and clicks without needing a focus click, but the webpage on Cube B will not.
### Case 3 (MEDIA_FIRST_CLICK_OWN)
Ensure that debug setting `MediaFirstClickInteract` is set to `2`
This test case requires two pairs of cubes, and the second pair must not be owned by your testing account. What owns them is not important, it can be a group or your second testing account.
Perform the testing procedure on both sets of cubes.
**Expected observations:** The webpage on Cube A will react to mouse cursor hover events and clicks without needing a focus click, but the webpage on Cube B will not. The other pair of cubes will react the same as your Cube B.
### Case 4 (MEDIA_FIRST_CLICK_GROUP)
Ensure that debug setting `MediaFirstClickInteract` is set to `4`
This test case requires two pairs of cubes, and the second pair must be deeded or set to a group that your testing account is a member of, but does not have set as active at the beginning of the test. As long as the second set of cubes is set to a group that your primary test account is a member of, the avatar that owns them does not matter.
1. Perform the testing procedure on both sets of cubes.
2. Activate the group that the second set of cubes is set / deeded to
3. Perform the testing procedure on both sets of cubes once more.
**Expected observations:** Both cubes owned by your primary testing account will not react to mouse cursor hover events and clicks without needing a focus click. Cube A set to group will react to mouse cursor hover events and clicks without needing a focus click, but Cube B will not.
### Case 5 (MEDIA_FIRST_CLICK_FRIEND)
Ensure that debug setting `MediaFirstClickInteract` is set to `8`
This test case requires three sets of cubes, one owned by you, one owned by another avatar on your friend list, and a third set owned by an avatar that is not on your friend list, or deeded to group. You can optionally use two sets of cubes, and dissolve friendship with your second account to test non-friend cubes.
Perform the testing procedure on all cubes
**Expected observations:** Cube A owned by a friended avatar will react to mouse cursor hover events and clicks without needing a focus click. All other cubes will not.
### Case 6 (MEDIA_FIRST_CLICK_LAND)
Ensure that debug setting `MediaFirstClickInteract` is set to `16`
This is the most tricky test case due to the multiple combinations that this operational mode considers valid. You will need multiple cubes, and can omit Cube B for brevity unless running a full test pass. This is probably most efficiently tested from your second account, using your first account to adjust the test parameters to fit other sub-cases.
Note: This requires the avatar that is performing the tests to physically be in the same parcel as the test cube(s). If you are standing outside of the parcel the media cubes are in, they will never react to mouse cursor hover events and clicks without needing a focus click under this operational mode.
1. Place down a set of cubes owned by the same avatar as the land
- The second account should see Cube A react to mouse cursor hover events and clicks without needing a focus click
- Cube B if tested, will not react in all further sub-cases and will not be mentioned further.
2. Adjust the conditions of the cubes and parcel such that they are owned by another avatar, but have the same group as the land set
- The second account should see Cube A react to mouse cursor hover events and clicks without needing a focus click
3. Adjust the conditions of the cubes and parcel such that they are deeded to the same group that the parcel is deeded to
- The second account should see Cube A react to mouse cursor hover events and clicks without needing a focus click
4. Adjust the conditions of the cubes and parcel such that the parcel and cubes do not share an owner, or a group
- The second account should see Cube A NOT react to mouse cursor hover events until clicked, and clicks WILL need a focus click before registering.
### Case 7 (MEDIA_FIRST_CLICK_ANY) (optional)
Ensure that debug setting `MediaFirstClickInteract` is set to `31`
Repeat test cases 1-6.
1. Test case 1 should fail
2. Test cases 2-6 should pass
### Case 8 (MEDIA_FIRST_CLICK_ALL) (optional)
Ensure that debug setting `MediaFirstClickInteract` is set to `1073741824`
Repeat test cases 1-6, there is no pass/fail for this run.
All cubes including B types should exhibit the same first-click interact behavior.

View File

@ -16135,5 +16135,27 @@
<key>Value</key>
<integer>1</integer>
</map>
<key>MediaAutoPlayHuds</key>
<map>
<key>Comment</key>
<string>Automatically play HUD media</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
<integer>1</integer>
</map>
<key>MediaFirstClickInteract</key>
<map>
<key>Comment</key>
<string>This setting controls which media (once loaded) does not require a first click to focus before interaction can begin. This allows clicks to be passed directly to media bypassing the focus click requirement. This setting is a bitfield, precomputed values are as follows: Disabled=0; Worn HUDs only=1; Owned objects=3; Friend objects=7; Group objects=15; Landowner objects=31; Any object=31; All MOAP=1073741824. For complete details see lltoolpie.h enum MediaFirstClickTypes.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>S32</string>
<key>Value</key>
<integer>1</integer>
</map>
</map>
</llsd>

View File

@ -72,6 +72,7 @@
#include "llweb.h"
#include "pipeline.h" // setHighlightObject
#include "lluiusage.h"
#include "llcallingcard.h"
extern bool gDebugClicks;
@ -1501,6 +1502,140 @@ static void handle_click_action_play()
}
}
bool LLToolPie::shouldAllowFirstMediaInteraction(const LLPickInfo& pick, bool moap_flag)
{
// Early failure cases
if(!pick.getObject())
{
LL_WARNS() << "pick.getObject() is NULL" << LL_ENDL;
return false;
}
static LLCachedControl<S32> FirstClickPref(gSavedSettings, "MediaFirstClickInteract", 1);
// Special / early-exit cases first, then checks get more complex and needy as we go down
// Feature disabled
if(FirstClickPref == MEDIA_FIRST_CLICK_NONE)
{
LL_DEBUGS_ONCE() << "FirstClickPref == MEDIA_FIRST_CLICK_NONE" << LL_ENDL;
return false;
}
// All objects (overriding PRIM_MEDIA_FIRST_CLICK_INTERACT)
if(FirstClickPref == MEDIA_FIRST_CLICK_ALL)
{
LL_DEBUGS_ONCE() << "FirstClickPref & MEDIA_FIRST_CLICK_ALL" << LL_ENDL;
return true;
}
// Every check beyond this point requires PRIM_MEDIA_FIRST_CLICK_INTERACT to be TRUE
if(!moap_flag)
{
LL_DEBUGS_ONCE() << "PRIM_MEDIA_FIRST_CLICK_INTERACT not set" << LL_ENDL;
return false;
}
// Any object with PRIM_MEDIA_FIRST_CLICK_INTERACT set to TRUE
if(FirstClickPref & MEDIA_FIRST_CLICK_ANY)
{
LL_DEBUGS_ONCE() << "FirstClickPref & MEDIA_FIRST_CLICK_ANY" << LL_ENDL;
return true;
}
// The following checks require some object information so we obtain that
LLPointer<LLViewerObject> object = pick.getObject();
if(object.isNull())
{
LL_WARNS() << "pick.getObject() is NULL" << LL_ENDL;
return false;
}
// Own objects
if((FirstClickPref & MEDIA_FIRST_CLICK_OWN) && object->permYouOwner())
{
LL_DEBUGS_ONCE() << "FirstClickPref & MEDIA_FIRST_CLICK_OWN" << LL_ENDL;
return true;
}
// HUD attachments
if((FirstClickPref & MEDIA_FIRST_CLICK_HUD) && object->isHUDAttachment())
{
LL_DEBUGS_ONCE() << "FirstClickPref & MEDIA_FIRST_CLICK_HUD" << LL_ENDL;
return true;
}
// Further object detail required beyond this point
LLPermissions* perms = LLSelectMgr::getInstance()->getHoverNode()->mPermissions;
if(perms == nullptr)
{
LL_WARNS() << "LLSelectMgr::getInstance()->getHoverNode()->mPermissions is NULL" << LL_ENDL;
return false;
}
LLUUID owner_id = perms->getOwner();
LLUUID group_id = perms->getGroup();
if(owner_id.isNull() && group_id.isNull())
{
LL_WARNS() << "Owner information was not reliably obtained" << LL_ENDL;
return false;
}
// Check if the object is owned by a friend of the agent
if(FirstClickPref & MEDIA_FIRST_CLICK_FRIEND)
{
LL_DEBUGS_ONCE() << "FirstClickPref & MEDIA_FIRST_CLICK_FRIEND. id: " << owner_id << LL_ENDL;
return LLAvatarTracker::instance().isBuddy(owner_id);
}
// Check for objects set to or owned by the active group
if(FirstClickPref & MEDIA_FIRST_CLICK_GROUP)
{
// Get our active group
LLUUID active_group = gAgent.getGroupID();
if(active_group.notNull() && (active_group == group_id || active_group == owner_id))
{
LL_DEBUGS_ONCE() << "FirstClickPref & MEDIA_FIRST_CLICK_GROUP.Active group: " << active_group << ", group_id:" << group_id << ", owner_id: " << owner_id << LL_ENDL;
return true;
}
}
// This check ensures that the following conditions are met:
// 1. The object is located in the same parcel as the agent.
// 2. One of the following is true:
// a. The object is owned by the same group as the parcel.
// b. The object is set to the same group as the parcel.
// c. The object is owned by the same owner as the parcel.
// Conditions 2a and 2b are mutually exclusive, our check is the same for both.
if(FirstClickPref & MEDIA_FIRST_CLICK_LAND)
{
LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel();
if(parcel == nullptr)
{
LL_WARNS() << "LLViewerParcelMgr::getInstance()->getAgentParcel() is NULL" << LL_ENDL;
return false;
}
// Same parcel as the agent only
if(!LLViewerParcelMgr::getInstance()->inAgentParcel(object->getPositionGlobal()))
{
LL_WARNS_ONCE() << "Object is not in the same parcel as the agent" << LL_ENDL;
return false;
}
LLUUID parcel_owner = parcel->getOwnerID();
LLUUID parcel_group = parcel->getGroupID();
// The parcel owner and group can't both be null
if(parcel_owner.isNull() && parcel_group.isNull())
{
LL_WARNS() << "Parcel owner and group are both null" << LL_ENDL;
return false;
}
if(owner_id == parcel_owner || group_id == parcel_group)
{
LL_DEBUGS_ONCE() << "FirstClickPref & MEDIA_FIRST_CLICK_LAND. Parcel owner: " << parcel_owner << ", group_id:" << group_id << ", owner_id: " << owner_id << LL_ENDL;
return true;
}
}
return false;
}
bool LLToolPie::handleMediaClick(const LLPickInfo& pick)
{
//FIXME: how do we handle object in different parcel than us?
@ -1535,6 +1670,16 @@ bool LLToolPie::handleMediaClick(const LLPickInfo& pick)
{
// It's okay to give this a null impl
LLViewerMediaFocus::getInstance()->setFocusFace(pick.getObject(), pick.mObjectFace, media_impl, pick.mNormal);
if (shouldAllowFirstMediaInteraction(pick, mep->getFirstClickInteract()))
{
if (media_impl.notNull())
{
media_impl->mouseDown(pick.mUVCoords, gKeyboard->currentMask(true));
mMediaMouseCaptureID = mep->getMediaID();
setMouseCapture(true);
return true;
}
}
}
else
{
@ -1647,7 +1792,7 @@ bool LLToolPie::handleMediaHover(const LLPickInfo& pick)
}
// If this is the focused media face, send mouse move events.
if (LLViewerMediaFocus::getInstance()->isFocusedOnFace(objectp, pick.mObjectFace))
if (LLViewerMediaFocus::getInstance()->isFocusedOnFace(objectp, pick.mObjectFace) || (shouldAllowFirstMediaInteraction(pick, mep->getFirstClickInteract())))
{
media_impl->mouseMove(pick.mUVCoords, gKeyboard->currentMask(true));
gViewerWindow->setCursor(media_impl->getLastSetCursor());

View File

@ -89,6 +89,26 @@ private:
void showVisualContextMenuEffect();
ECursorType cursorFromObject(LLViewerObject* object);
enum MediaFirstClickTypes
{
MEDIA_FIRST_CLICK_NONE = 0, // Special case: Feature is disabled
MEDIA_FIRST_CLICK_HUD = 1 << 0, // 0b00000001 (1)
MEDIA_FIRST_CLICK_OWN = 1 << 1, // 0b00000010 (2)
MEDIA_FIRST_CLICK_GROUP = 1 << 2, // 0b00000100 (4)
MEDIA_FIRST_CLICK_FRIEND = 1 << 3, // 0b00001000 (8)
MEDIA_FIRST_CLICK_LAND = 1 << 4, // 0b00010000 (16)
// Covers any object with PRIM_MEDIA_FIRST_CLICK_INTERACT (combines all other flags)
MEDIA_FIRST_CLICK_ANY = MEDIA_FIRST_CLICK_HUD &
MEDIA_FIRST_CLICK_OWN &
MEDIA_FIRST_CLICK_GROUP &
MEDIA_FIRST_CLICK_FRIEND &
MEDIA_FIRST_CLICK_LAND,
// Covers all media regardless of other rules or PRIM_MEDIA_FIRST_CLICK_INTERACT
MEDIA_FIRST_CLICK_ALL = 1 << 30 // 0b01000000000000000000000000000000 (1073741824)
};
bool shouldAllowFirstMediaInteraction(const LLPickInfo& info, bool moap_flag);
bool handleMediaClick(const LLPickInfo& info);
bool handleMediaDblClick(const LLPickInfo& info);
bool handleMediaHover(const LLPickInfo& info);

View File

@ -2640,6 +2640,17 @@ void LLVOVolume::syncMediaData(S32 texture_index, const LLSD &media_data, bool m
}
viewer_media_t media_impl = LLViewerMedia::getInstance()->updateMediaImpl(mep, previous_url, update_from_self);
static LLCachedControl<bool> media_autoplay_huds(gSavedSettings, "MediaAutoPlayHuds", true);
bool was_loaded = media_impl->hasMedia();
if (isHUDAttachment() && media_autoplay_huds && !was_loaded)
{
std::string url = mep->getCurrentURL();
if (media_impl->getCurrentMediaURL() != url)
{
media_impl->navigateTo(url, "", false, true);
}
}
addMediaImpl(media_impl, texture_index) ;
}
else

View File

@ -62,20 +62,9 @@
name="mute_when_minimized"
top_delta="3"
left_pad="5"
width="20" />
<!-- *HACK
After storm-1109 will be fixed: instead of using this text_box, word_wrap should be applied for "mute_when_minimized" check_box's label.-->
<text
follows="top|left"
height="15"
layout="topleft"
left_pad="0"
name="mute_chb_label"
top_delta="-1"
width="150"
wrap="true">
Mute when minimized
</text>
width="20"
label="Mute when minimized"
word_wrap="true"/>
<slider
control_name="AudioLevelUI"
disabled_control="MuteAudio"
@ -321,66 +310,45 @@
left_pad="5"
name="enable_voice_check"
width="110"/>
<!-- -->
<text
type="string"
length="1"
follows="left|top"
layout="topleft"
left="23"
top_delta="22"
top_delta="25"
name="Listen media from"
height="15"
word_wrap="true"
width="112">
Hear media and sounds from:
width="165"
halign="right">
Hear media and sounds from
</text>
<radio_group
<combo_box
control_name="MediaSoundsEarLocation"
follows="left|top"
top_delta="-6"
layout="topleft"
left_pad="10"
width="360"
height="40"
name="media_ear_location">
<radio_item
height="19"
label="Camera position"
follows="left|top"
layout="topleft"
name="0"
width="200"/>
<radio_item
height="19"
follows="left|top"
label="Avatar position"
layout="topleft"
left_delta="0"
name="1"
top_delta ="18"
width="200" />
</radio_group>
<check_box
name="media_show_on_others_btn"
control_name="MediaShowOnOthers"
value="true"
follows="left|top"
layout="topleft"
height="15"
top_pad="8"
tool_tip="Uncheck this to hide media attached to other avatars nearby"
label="Play media attached to other avatars"
left="20"
width="230"/>
left_pad="5"
width="130"
height="23"
top_delta="-4"
name="media_ear_location_combo">
<item
label="Camera position"
name="camera_position"
value="0" />
<item
label="Avatar position"
name="avatar_position"
value="1" />
</combo_box>
<text
follows="left|top"
layout="topleft"
height="15"
left="23"
top_pad="8"
width="120"
name="media_autoplay_label">
width="165"
name="media_autoplay_label"
halign="right">
Auto-play media
</text>
<combo_box
@ -389,10 +357,10 @@
follows="left|top"
layout="topleft"
height="23"
left_pad="-15"
left_delta="170"
top_delta="-4"
name="media_auto_play_combo"
width="115">
width="130">
<item
label="Never"
name="autoplay_disabled"
@ -406,23 +374,107 @@
name="autoplay_ask"
value="2"/>
</combo_box>
<text
follows="left|top"
layout="topleft"
height="15"
left="23"
width="165"
name="media_firstinteract_label"
halign="right">
Media first-interact
</text>
<combo_box
control_name="MediaFirstClickInteract"
enabled_control="AudioStreamingMedia"
follows="left|top"
layout="topleft"
height="23"
left_delta="170"
top_delta="-4"
width="130"
name="media_first_interact_combo"
tool_tip="This setting controls which media (once loaded) does not require a first click to focus before interaction can begin. This allows clicks to be passed directly to media bypassing the focus click requirement. Each option also inherits the previous ones.">
<item
label="Disabled"
name="media_first_click_none"
value="0"/>
<item
label="Worn HUDs only"
name="media_first_click_hud"
value="1"/>
<item
label="Owned objects"
name="media_first_click_own"
value="3"/>
<item
label="Friend objects"
name="media_first_click_group"
value="7"/>
<item
label="Group objects"
name="media_first_click_friend"
value="15"/>
<item
label="Landowner objects"
name="media_first_click_land"
value="31"/>
<item
label="Any object"
name="media_first_interact_any"
value="31"/>
<item
label="All MOAP"
name="media_first_click_all"
value="1073741824"
enabled="false"/>
</combo_box>
<check_box
name="media_show_on_others_btn"
control_name="MediaShowOnOthers"
enabled_control="AudioStreamingMedia"
value="true"
follows="left|top"
tool_tip="Uncheck this to hide media attached to other avatars nearby"
label="Play media attached to other avatars"
left="23"
width="15"
top_delta="30"
height="15"/>
<check_box
name="media_huds_autoplay"
control_name="MediaAutoPlayHuds"
enabled_control="AudioStreamingMedia"
value="true"
follows="left|top"
layout="topleft"
tool_tip="Uncheck this to make HUDs follow the standard media auto-play setting"
label="Auto-play media attached to your HUD"
left="260"
top_pad="-15"
width="15"
height="15"/>
<text
layout="topleft"
follows="left"
height="15"
left="260"
top_pad="-18"
width="100"
name="noise_suppression_label">
Noise Suppression
width="165"
name="noise_suppression_label"
left="23"
top_delta="22"
halign="right">
Microphone Noise Suppression
</text>
<combo_box
control_name="VoiceNoiseSuppressionLevel"
enabled_control="EnableVoiceChat"
follows="left|top"
layout="topleft"
left_delta="170"
top_delta="-6"
width="130"
height="23"
left_pad="10"
top_pad="-20"
name="noise_suppression_combo"
width="80">
name="noise_suppression_combo">
<item
label="Off"
name="noise_suppression_none"
@ -452,48 +504,44 @@
left="23"
top_delta="30"
name="Listen from"
width="112">
Hear voice from:
width="165"
height="15"
halign="right">
Hear voice from
</text>
<radio_group
<combo_box
enabled_control="EnableVoiceChat"
control_name="VoiceEarLocation"
follows="left|top"
layout="topleft"
left_pad="10"
left_delta="170"
top_delta="-6"
width="360"
height="40"
name="ear_location">
<radio_item
height="19"
label="Camera position"
follows="left|top"
layout="topleft"
name="0"
width="200"/>
<radio_item
height="19"
follows="left|top"
label="Avatar position"
layout="topleft"
left_delta="0"
name="1"
top_delta ="18"
width="200" />
</radio_group>
width="130"
height="23"
name="ear_location_combo">
<item
label="Camera position"
name="camera_position"
value="0" />
<item
label="Avatar position"
name="avatar_position"
value="1" />
</combo_box>
<check_box
control_name="LipSyncEnabled"
enabled_control="EnableVoiceChat"
follows="left|top"
height="15"
label="Move avatar lips when speaking"
layout="topleft"
left="20"
name="enable_lip_sync"
top_pad="10"
top_pad="8"
width="237"/>
<check_box
control_name="VoiceEchoCancellation"
enabled_control="EnableVoiceChat"
height="15"
tool_tip="Check to enable voice echo cancellation"
label="Echo Cancellation"
@ -516,6 +564,7 @@
top_pad="5"/>
<check_box
control_name="VoiceAutomaticGainControl"
enabled_control="EnableVoiceChat"
height="15"
tool_tip="Check to enable automatic gain control"
label="Automatic Gain Control"
@ -537,6 +586,7 @@
left="20"/>
<check_box
control_name="VoiceVisualizerEnabled"
enabled_control="EnableVoiceChat"
height="15"
tool_tip="Check to show voice dot indicator above avatars"
label="Show voice dot above avatars"
@ -547,6 +597,7 @@
width="200"/>
<button
control_name="ShowDeviceSettings"
enabled_control="EnableVoiceChat"
follows="left|top"
height="23"
is_toggle="true"