diff options
Diffstat (limited to '')
-rw-r--r-- | linden/indra/newview/llfloateractivespeakers.cpp | 831 |
1 files changed, 831 insertions, 0 deletions
diff --git a/linden/indra/newview/llfloateractivespeakers.cpp b/linden/indra/newview/llfloateractivespeakers.cpp new file mode 100644 index 0000000..1c3990e --- /dev/null +++ b/linden/indra/newview/llfloateractivespeakers.cpp | |||
@@ -0,0 +1,831 @@ | |||
1 | /** | ||
2 | * @file llfloateractivespeakers.cpp | ||
3 | * @brief Management interface for muting and controlling volume of residents currently speaking | ||
4 | * | ||
5 | * Copyright (c) 2005-2007, Linden Research, Inc. | ||
6 | * | ||
7 | * Second Life Viewer Source Code | ||
8 | * The source code in this file ("Source Code") is provided by Linden Lab | ||
9 | * to you under the terms of the GNU General Public License, version 2.0 | ||
10 | * ("GPL"), unless you have obtained a separate licensing agreement | ||
11 | * ("Other License"), formally executed by you and Linden Lab. Terms of | ||
12 | * the GPL can be found in doc/GPL-license.txt in this distribution, or | ||
13 | * online at http://secondlife.com/developers/opensource/gplv2 | ||
14 | * | ||
15 | * There are special exceptions to the terms and conditions of the GPL as | ||
16 | * it is applied to this Source Code. View the full text of the exception | ||
17 | * in the file doc/FLOSS-exception.txt in this software distribution, or | ||
18 | * online at http://secondlife.com/developers/opensource/flossexception | ||
19 | * | ||
20 | * By copying, modifying or distributing this software, you acknowledge | ||
21 | * that you have read and understood your obligations described above, | ||
22 | * and agree to abide by those obligations. | ||
23 | * | ||
24 | * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO | ||
25 | * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, | ||
26 | * COMPLETENESS OR PERFORMANCE. | ||
27 | */ | ||
28 | |||
29 | #include "llviewerprecompiledheaders.h" | ||
30 | |||
31 | #include "llfloateractivespeakers.h" | ||
32 | |||
33 | #include "llagent.h" | ||
34 | #include "llvoavatar.h" | ||
35 | #include "llfloateravatarinfo.h" | ||
36 | #include "llvieweruictrlfactory.h" | ||
37 | #include "llviewercontrol.h" | ||
38 | #include "llscrolllistctrl.h" | ||
39 | #include "llbutton.h" | ||
40 | #include "lltextbox.h" | ||
41 | #include "llmutelist.h" | ||
42 | #include "llviewerobjectlist.h" | ||
43 | #include "llimpanel.h" // LLVoiceChannel | ||
44 | #include "llsdutil.h" | ||
45 | |||
46 | const F32 SPEAKER_TIMEOUT = 10.f; // seconds of not being on voice channel before removed from list of active speakers | ||
47 | const LLColor4 INACTIVE_COLOR(0.3f, 0.3f, 0.3f, 0.5f); | ||
48 | const LLColor4 ACTIVE_COLOR(0.5f, 0.5f, 0.5f, 1.f); | ||
49 | const F32 TYPING_ANIMATION_FPS = 2.5f; | ||
50 | |||
51 | LLLocalSpeakerMgr* gLocalSpeakerMgr = NULL; | ||
52 | LLActiveSpeakerMgr* gActiveChannelSpeakerMgr = NULL; | ||
53 | |||
54 | LLSpeaker::speaker_map_t LLSpeaker::sSpeakers; | ||
55 | |||
56 | LLSpeaker::LLSpeaker(const LLUUID& id, const LLString& name, const ESpeakerType type) : | ||
57 | mStatus(LLSpeaker::STATUS_TEXT_ONLY), | ||
58 | mLastSpokeTime(0.f), | ||
59 | mSpeechVolume(0.f), | ||
60 | mHasSpoken(FALSE), | ||
61 | mDotColor(LLColor4::white), | ||
62 | mID(id), | ||
63 | mTyping(FALSE), | ||
64 | mSortIndex(0), | ||
65 | mType(type) | ||
66 | { | ||
67 | mHandle.init(); | ||
68 | sSpeakers.insert(std::make_pair(mHandle, this)); | ||
69 | if (name.empty() && type == SPEAKER_AGENT) | ||
70 | { | ||
71 | lookupName(); | ||
72 | } | ||
73 | else | ||
74 | { | ||
75 | mDisplayName = name; | ||
76 | } | ||
77 | mActivityTimer.resetWithExpiry(SPEAKER_TIMEOUT); | ||
78 | } | ||
79 | |||
80 | LLSpeaker::~LLSpeaker() | ||
81 | { | ||
82 | sSpeakers.erase(mHandle); | ||
83 | } | ||
84 | |||
85 | void LLSpeaker::lookupName() | ||
86 | { | ||
87 | gCacheName->getName(mID, onAvatarNameLookup, new LLViewHandle(mHandle)); | ||
88 | } | ||
89 | |||
90 | //static | ||
91 | void LLSpeaker::onAvatarNameLookup(const LLUUID& id, const char* first, const char* last, BOOL is_group, void* user_data) | ||
92 | { | ||
93 | LLViewHandle speaker_handle = *(LLViewHandle*)user_data; | ||
94 | delete (LLViewHandle*)user_data; | ||
95 | |||
96 | speaker_map_t::iterator found_it = sSpeakers.find(speaker_handle); | ||
97 | if (found_it != sSpeakers.end()) | ||
98 | { | ||
99 | LLSpeaker* speakerp = found_it->second; | ||
100 | if (speakerp) | ||
101 | { | ||
102 | speakerp->mDisplayName = llformat("%s %s", first, last); | ||
103 | } | ||
104 | } | ||
105 | } | ||
106 | |||
107 | |||
108 | // helper sort class | ||
109 | struct LLSortRecentSpeakers | ||
110 | { | ||
111 | bool operator()(const LLPointer<LLSpeaker> lhs, const LLPointer<LLSpeaker> rhs) const; | ||
112 | }; | ||
113 | |||
114 | bool LLSortRecentSpeakers::operator()(const LLPointer<LLSpeaker> lhs, const LLPointer<LLSpeaker> rhs) const | ||
115 | { | ||
116 | // Sort first on status | ||
117 | if (lhs->mStatus != rhs->mStatus) | ||
118 | { | ||
119 | return (lhs->mStatus < rhs->mStatus); | ||
120 | } | ||
121 | |||
122 | // and then on last speaking time | ||
123 | if(lhs->mLastSpokeTime != rhs->mLastSpokeTime) | ||
124 | { | ||
125 | return (lhs->mLastSpokeTime > rhs->mLastSpokeTime); | ||
126 | } | ||
127 | |||
128 | // and finally (only if those are both equal), on name. | ||
129 | return( lhs->mDisplayName.compare(rhs->mDisplayName) < 0 ); | ||
130 | } | ||
131 | |||
132 | LLFloaterActiveSpeakers::LLFloaterActiveSpeakers(const LLSD& seed) : mPanel(NULL) | ||
133 | { | ||
134 | mFactoryMap["active_speakers_panel"] = LLCallbackMap(createSpeakersPanel, NULL); | ||
135 | // do not automatically open singleton floaters (as result of getInstance()) | ||
136 | BOOL no_open = FALSE; | ||
137 | gUICtrlFactory->buildFloater(this, "floater_active_speakers.xml", &getFactoryMap(), no_open); | ||
138 | //RN: for now, we poll voice client every frame to get voice amplitude feedback | ||
139 | //gVoiceClient->addObserver(this); | ||
140 | mPanel->refreshSpeakers(); | ||
141 | } | ||
142 | |||
143 | LLFloaterActiveSpeakers::~LLFloaterActiveSpeakers() | ||
144 | { | ||
145 | } | ||
146 | |||
147 | void LLFloaterActiveSpeakers::onClose(bool app_quitting) | ||
148 | { | ||
149 | setVisible(FALSE); | ||
150 | } | ||
151 | |||
152 | void LLFloaterActiveSpeakers::draw() | ||
153 | { | ||
154 | // update state every frame to get live amplitude feedback | ||
155 | mPanel->refreshSpeakers(); | ||
156 | LLFloater::draw(); | ||
157 | } | ||
158 | |||
159 | BOOL LLFloaterActiveSpeakers::postBuild() | ||
160 | { | ||
161 | mPanel = (LLPanelActiveSpeakers*)LLUICtrlFactory::getPanelByName(this, "active_speakers_panel"); | ||
162 | return TRUE; | ||
163 | } | ||
164 | |||
165 | void LLFloaterActiveSpeakers::onChange() | ||
166 | { | ||
167 | //refresh(); | ||
168 | } | ||
169 | |||
170 | //static | ||
171 | void* LLFloaterActiveSpeakers::createSpeakersPanel(void* data) | ||
172 | { | ||
173 | // don't show text only speakers | ||
174 | return new LLPanelActiveSpeakers(gActiveChannelSpeakerMgr, FALSE); | ||
175 | } | ||
176 | |||
177 | |||
178 | // | ||
179 | // LLPanelActiveSpeakers | ||
180 | // | ||
181 | LLPanelActiveSpeakers::LLPanelActiveSpeakers(LLSpeakerMgr* data_source, BOOL show_text_chatters) : | ||
182 | mSpeakerList(NULL), | ||
183 | mMuteVoiceCtrl(NULL), | ||
184 | mMuteTextCtrl(NULL), | ||
185 | mNameText(NULL), | ||
186 | mProfileBtn(NULL), | ||
187 | mShowTextChatters(show_text_chatters), | ||
188 | mSpeakerMgr(data_source) | ||
189 | { | ||
190 | setMouseOpaque(FALSE); | ||
191 | } | ||
192 | |||
193 | LLPanelActiveSpeakers::~LLPanelActiveSpeakers() | ||
194 | { | ||
195 | |||
196 | } | ||
197 | |||
198 | BOOL LLPanelActiveSpeakers::postBuild() | ||
199 | { | ||
200 | mSpeakerList = LLUICtrlFactory::getScrollListByName(this, "speakers_list"); | ||
201 | |||
202 | mMuteTextCtrl = (LLUICtrl*)getCtrlByNameAndType("mute_text_btn", WIDGET_TYPE_DONTCARE); | ||
203 | childSetCommitCallback("mute_text_btn", onClickMuteTextCommit, this); | ||
204 | |||
205 | mMuteVoiceCtrl = (LLUICtrl*)getCtrlByNameAndType("mute_btn", WIDGET_TYPE_DONTCARE); | ||
206 | childSetCommitCallback("mute_btn", onClickMuteVoiceCommit, this); | ||
207 | childSetAction("mute_btn", onClickMuteVoice, this); | ||
208 | |||
209 | childSetCommitCallback("speaker_volume", onVolumeChange, this); | ||
210 | |||
211 | mNameText = LLUICtrlFactory::getTextBoxByName(this, "resident_name"); | ||
212 | |||
213 | mProfileBtn = LLUICtrlFactory::getButtonByName(this, "profile_btn"); | ||
214 | childSetAction("profile_btn", onClickProfile, this); | ||
215 | return TRUE; | ||
216 | } | ||
217 | |||
218 | void LLPanelActiveSpeakers::refreshSpeakers() | ||
219 | { | ||
220 | // store off current selection and scroll state to preserve across list rebuilds | ||
221 | LLUUID selected_id = mSpeakerList->getSimpleSelectedValue().asUUID(); | ||
222 | S32 scroll_pos = mSpeakerList->getScrollInterface()->getScrollPos(); | ||
223 | |||
224 | BOOL sort_ascending = mSpeakerList->getSortAscending(); | ||
225 | LLString sort_column = mSpeakerList->getSortColumnName(); | ||
226 | // TODO: put this in xml | ||
227 | // enforces default sort column of speaker status | ||
228 | if (sort_column.empty()) | ||
229 | { | ||
230 | sort_column = "speaking_status"; | ||
231 | } | ||
232 | |||
233 | mSpeakerMgr->update(); | ||
234 | |||
235 | // clear scrolling list widget of names | ||
236 | mSpeakerList->clearRows(); | ||
237 | |||
238 | LLSpeakerMgr::speaker_list_t speaker_list; | ||
239 | mSpeakerMgr->getSpeakerList(&speaker_list, mShowTextChatters); | ||
240 | for (LLSpeakerMgr::speaker_list_t::const_iterator speaker_it = speaker_list.begin(); speaker_it != speaker_list.end(); ++speaker_it) | ||
241 | { | ||
242 | LLUUID speaker_id = (*speaker_it)->mID; | ||
243 | LLPointer<LLSpeaker> speakerp = (*speaker_it); | ||
244 | |||
245 | // since we are forced to sort by text, encode sort order as string | ||
246 | LLString speaking_order_sort_string = llformat("%010d", speakerp->mSortIndex); | ||
247 | |||
248 | LLSD row; | ||
249 | row["id"] = speaker_id; | ||
250 | |||
251 | row["columns"][0]["column"] = "icon_speaking_status"; | ||
252 | row["columns"][0]["type"] = "icon"; | ||
253 | row["columns"][0]["color"] = speakerp->mDotColor.getValue(); | ||
254 | LLString icon_image_id; | ||
255 | |||
256 | S32 icon_image_idx = llmin(2, llfloor((speakerp->mSpeechVolume / LLVoiceClient::OVERDRIVEN_POWER_LEVEL) * 3.f)); | ||
257 | switch(icon_image_idx) | ||
258 | { | ||
259 | case 0: | ||
260 | icon_image_id = gViewerArt.getString("icn_active-speakers-dot-lvl0.tga"); | ||
261 | break; | ||
262 | case 1: | ||
263 | icon_image_id = gViewerArt.getString("icn_active-speakers-dot-lvl1.tga"); | ||
264 | break; | ||
265 | case 2: | ||
266 | icon_image_id = gViewerArt.getString("icn_active-speakers-dot-lvl2.tga"); | ||
267 | break; | ||
268 | } | ||
269 | //if (speakerp->mTyping) | ||
270 | //{ | ||
271 | // S32 typing_anim_idx = llround(mIconAnimationTimer.getElapsedTimeF32() * TYPING_ANIMATION_FPS) % 3; | ||
272 | // switch(typing_anim_idx) | ||
273 | // { | ||
274 | // case 0: | ||
275 | // row["columns"][0]["overlay"] = LLUUID(gViewerArt.getString("icn_active-speakers-typing1.tga")); | ||
276 | // break; | ||
277 | // case 1: | ||
278 | // row["columns"][0]["overlay"] = LLUUID(gViewerArt.getString("icn_active-speakers-typing2.tga")); | ||
279 | // break; | ||
280 | // case 2: | ||
281 | // row["columns"][0]["overlay"] = LLUUID(gViewerArt.getString("icn_active-speakers-typing3.tga")); | ||
282 | // break; | ||
283 | // default: | ||
284 | // break; | ||
285 | // } | ||
286 | //} | ||
287 | |||
288 | row["columns"][0]["value"] = speakerp->mStatus == LLSpeaker::STATUS_MUTED ? | ||
289 | gViewerArt.getString("mute_icon.tga") : icon_image_id; | ||
290 | if (speakerp->mStatus > LLSpeaker::STATUS_VOICE_ACTIVE) // if voice is disabled for this speaker | ||
291 | { | ||
292 | // non voice speakers have hidden icons, render as transparent | ||
293 | row["columns"][0]["color"] = LLColor4(0.f, 0.f, 0.f, 0.f).getValue(); | ||
294 | } | ||
295 | row["columns"][1]["column"] = "speaker_name"; | ||
296 | row["columns"][1]["type"] = "text"; | ||
297 | if (speakerp->mStatus == LLSpeaker::STATUS_NOT_IN_CHANNEL) | ||
298 | { | ||
299 | // draw inactive speakers in gray | ||
300 | row["columns"][1]["color"] = LLColor4::grey4.getValue(); | ||
301 | } | ||
302 | |||
303 | if (speakerp->mDisplayName.empty()) | ||
304 | { | ||
305 | row["columns"][1]["value"] = LLCacheName::getDefaultName(); | ||
306 | } | ||
307 | else | ||
308 | { | ||
309 | row["columns"][1]["value"] = speakerp->mDisplayName; | ||
310 | } | ||
311 | |||
312 | row["columns"][2]["column"] = "speaking_status"; | ||
313 | row["columns"][2]["type"] = "text"; | ||
314 | |||
315 | // print speaking ordinal in a text-sorting friendly manner | ||
316 | row["columns"][2]["value"] = speaking_order_sort_string; | ||
317 | |||
318 | mSpeakerList->addElement(row); | ||
319 | } | ||
320 | |||
321 | //restore sort order, selection, etc | ||
322 | mSpeakerList->sortByColumn(sort_column, sort_ascending); | ||
323 | // make sure something is selected | ||
324 | if (selected_id.isNull()) | ||
325 | { | ||
326 | mSpeakerList->selectFirstItem(); | ||
327 | } | ||
328 | else | ||
329 | { | ||
330 | mSpeakerList->selectByValue(selected_id); | ||
331 | } | ||
332 | |||
333 | LLPointer<LLSpeaker> speakerp = mSpeakerMgr->findSpeaker(selected_id); | ||
334 | |||
335 | if (gMuteListp) | ||
336 | { | ||
337 | // update UI for selected participant | ||
338 | if (mMuteVoiceCtrl) | ||
339 | { | ||
340 | mMuteVoiceCtrl->setValue(gMuteListp->isMuted(selected_id, LLMute::flagVoiceChat)); | ||
341 | mMuteVoiceCtrl->setEnabled(selected_id.notNull() | ||
342 | && selected_id != gAgent.getID() | ||
343 | && mSpeakerMgr->isVoiceActive() | ||
344 | && (speakerp.notNull() && speakerp->mType == LLSpeaker::SPEAKER_AGENT)); | ||
345 | } | ||
346 | if (mMuteTextCtrl) | ||
347 | { | ||
348 | mMuteTextCtrl->setValue(gMuteListp->isMuted(selected_id, LLMute::flagTextChat)); | ||
349 | mMuteTextCtrl->setEnabled(selected_id.notNull() && selected_id != gAgent.getID() && speakerp.notNull() && !gMuteListp->isLinden(speakerp->mDisplayName)); | ||
350 | } | ||
351 | childSetValue("speaker_volume", gVoiceClient->getUserVolume(selected_id)); | ||
352 | childSetEnabled("speaker_volume", selected_id.notNull() | ||
353 | && selected_id != gAgent.getID() | ||
354 | && mSpeakerMgr->isVoiceActive() | ||
355 | && (speakerp.notNull() && speakerp->mType == LLSpeaker::SPEAKER_AGENT)); | ||
356 | if (mProfileBtn) | ||
357 | { | ||
358 | mProfileBtn->setEnabled(selected_id.notNull()); | ||
359 | } | ||
360 | } | ||
361 | |||
362 | // show selected user name in large font | ||
363 | if (mNameText) | ||
364 | { | ||
365 | if (speakerp) | ||
366 | { | ||
367 | mNameText->setValue(speakerp->mDisplayName); | ||
368 | } | ||
369 | else | ||
370 | { | ||
371 | mNameText->setValue(""); | ||
372 | } | ||
373 | } | ||
374 | |||
375 | // keep scroll value stable | ||
376 | mSpeakerList->getScrollInterface()->setScrollPos(scroll_pos); | ||
377 | } | ||
378 | |||
379 | void LLPanelActiveSpeakers::setSpeaker(const LLUUID& id, const LLString& name, LLSpeaker::ESpeakerStatus status, LLSpeaker::ESpeakerType type) | ||
380 | { | ||
381 | mSpeakerMgr->setSpeaker(id, name, status, type); | ||
382 | } | ||
383 | |||
384 | |||
385 | //static | ||
386 | void LLPanelActiveSpeakers::onClickMuteTextCommit(LLUICtrl* ctrl, void* user_data) | ||
387 | { | ||
388 | LLPanelActiveSpeakers* panelp = (LLPanelActiveSpeakers*)user_data; | ||
389 | LLUUID speaker_id = panelp->mSpeakerList->getValue().asUUID(); | ||
390 | BOOL is_muted = gMuteListp->isMuted(speaker_id, LLMute::flagTextChat); | ||
391 | std::string name; | ||
392 | |||
393 | //fill in name using voice client's copy of name cache | ||
394 | LLPointer<LLSpeaker> speakerp = panelp->mSpeakerMgr->findSpeaker(speaker_id); | ||
395 | if (speakerp.isNull()) | ||
396 | { | ||
397 | return; | ||
398 | } | ||
399 | |||
400 | name = speakerp->mDisplayName; | ||
401 | |||
402 | LLMute mute(speaker_id, name, speakerp->mType == LLSpeaker::SPEAKER_AGENT ? LLMute::AGENT : LLMute::OBJECT); | ||
403 | |||
404 | if (!is_muted) | ||
405 | { | ||
406 | gMuteListp->add(mute, LLMute::flagTextChat); | ||
407 | } | ||
408 | else | ||
409 | { | ||
410 | gMuteListp->remove(mute, LLMute::flagTextChat); | ||
411 | } | ||
412 | } | ||
413 | |||
414 | //static | ||
415 | void LLPanelActiveSpeakers::onClickMuteVoice(void* user_data) | ||
416 | { | ||
417 | onClickMuteVoiceCommit(NULL, user_data); | ||
418 | } | ||
419 | |||
420 | //static | ||
421 | void LLPanelActiveSpeakers::onClickMuteVoiceCommit(LLUICtrl* ctrl, void* user_data) | ||
422 | { | ||
423 | LLPanelActiveSpeakers* panelp = (LLPanelActiveSpeakers*)user_data; | ||
424 | LLUUID speaker_id = panelp->mSpeakerList->getValue().asUUID(); | ||
425 | BOOL is_muted = gMuteListp->isMuted(speaker_id, LLMute::flagVoiceChat); | ||
426 | std::string name; | ||
427 | |||
428 | LLPointer<LLSpeaker> speakerp = panelp->mSpeakerMgr->findSpeaker(speaker_id); | ||
429 | if (speakerp.isNull()) | ||
430 | { | ||
431 | return; | ||
432 | } | ||
433 | |||
434 | name = speakerp->mDisplayName; | ||
435 | |||
436 | // muting voice means we're dealing with an agent | ||
437 | LLMute mute(speaker_id, name, LLMute::AGENT); | ||
438 | |||
439 | if (!is_muted) | ||
440 | { | ||
441 | gMuteListp->add(mute, LLMute::flagVoiceChat); | ||
442 | } | ||
443 | else | ||
444 | { | ||
445 | gMuteListp->remove(mute, LLMute::flagVoiceChat); | ||
446 | } | ||
447 | } | ||
448 | |||
449 | |||
450 | //static | ||
451 | void LLPanelActiveSpeakers::onVolumeChange(LLUICtrl* source, void* user_data) | ||
452 | { | ||
453 | LLPanelActiveSpeakers* panelp = (LLPanelActiveSpeakers*)user_data; | ||
454 | LLUUID speaker_id = panelp->mSpeakerList->getValue().asUUID(); | ||
455 | |||
456 | gVoiceClient->setUserVolume(speaker_id, (F32)panelp->childGetValue("speaker_volume").asReal()); | ||
457 | } | ||
458 | |||
459 | //static | ||
460 | void LLPanelActiveSpeakers::onClickProfile(void* user_data) | ||
461 | { | ||
462 | LLPanelActiveSpeakers* panelp = (LLPanelActiveSpeakers*)user_data; | ||
463 | LLUUID speaker_id = panelp->mSpeakerList->getValue().asUUID(); | ||
464 | |||
465 | LLFloaterAvatarInfo::showFromDirectory(speaker_id); | ||
466 | } | ||
467 | |||
468 | // | ||
469 | // LLSpeakerMgr | ||
470 | // | ||
471 | |||
472 | LLSpeakerMgr::LLSpeakerMgr(LLVoiceChannel* channelp) : | ||
473 | mVoiceChannel(channelp) | ||
474 | { | ||
475 | } | ||
476 | |||
477 | LLSpeakerMgr::~LLSpeakerMgr() | ||
478 | { | ||
479 | } | ||
480 | |||
481 | LLPointer<LLSpeaker> LLSpeakerMgr::setSpeaker(const LLUUID& id, const LLString& name, LLSpeaker::ESpeakerStatus status, LLSpeaker::ESpeakerType type) | ||
482 | { | ||
483 | if (id.isNull()) return NULL; | ||
484 | |||
485 | LLPointer<LLSpeaker> speakerp; | ||
486 | if (mSpeakers.find(id) == mSpeakers.end()) | ||
487 | { | ||
488 | speakerp = new LLSpeaker(id, name, type); | ||
489 | speakerp->mStatus = status; | ||
490 | mSpeakers.insert(std::make_pair(speakerp->mID, speakerp)); | ||
491 | mSpeakersSorted.push_back(speakerp); | ||
492 | } | ||
493 | else | ||
494 | { | ||
495 | speakerp = findSpeaker(id); | ||
496 | if (speakerp.notNull()) | ||
497 | { | ||
498 | // keep highest priority status (lowest value) instead of overriding current value | ||
499 | speakerp->mStatus = llmin(speakerp->mStatus, status); | ||
500 | speakerp->mActivityTimer.resetWithExpiry(SPEAKER_TIMEOUT); | ||
501 | // RN: due to a weird behavior where IMs from attached objects come from the wearer's agent_id | ||
502 | // we need to override speakers that we think are objects when we find out they are really | ||
503 | // residents | ||
504 | if (type == LLSpeaker::SPEAKER_AGENT) | ||
505 | { | ||
506 | speakerp->mType = LLSpeaker::SPEAKER_AGENT; | ||
507 | speakerp->lookupName(); | ||
508 | } | ||
509 | } | ||
510 | } | ||
511 | |||
512 | return speakerp; | ||
513 | } | ||
514 | |||
515 | void LLSpeakerMgr::update() | ||
516 | { | ||
517 | if (!gVoiceClient) | ||
518 | { | ||
519 | return; | ||
520 | } | ||
521 | |||
522 | LLColor4 speaking_color = gSavedSettings.getColor4("SpeakingColor"); | ||
523 | LLColor4 overdriven_color = gSavedSettings.getColor4("OverdrivenColor"); | ||
524 | |||
525 | updateSpeakerList(); | ||
526 | |||
527 | // update status of all current speakers | ||
528 | BOOL voice_channel_active = (!mVoiceChannel && gVoiceClient->inProximalChannel()) || (mVoiceChannel && mVoiceChannel->isActive()); | ||
529 | for (speaker_map_t::iterator speaker_it = mSpeakers.begin(); speaker_it != mSpeakers.end();) | ||
530 | { | ||
531 | LLUUID speaker_id = speaker_it->first; | ||
532 | LLSpeaker* speakerp = speaker_it->second; | ||
533 | |||
534 | speaker_map_t::iterator cur_speaker_it = speaker_it++; | ||
535 | |||
536 | if (voice_channel_active && gVoiceClient->getVoiceEnabled(speaker_id)) | ||
537 | { | ||
538 | speakerp->mSpeechVolume = gVoiceClient->getCurrentPower(speaker_id); | ||
539 | |||
540 | if (gVoiceClient->getOnMuteList(speaker_id)) | ||
541 | { | ||
542 | speakerp->mStatus = LLSpeaker::STATUS_MUTED; | ||
543 | speakerp->mDotColor = LLColor4::white; | ||
544 | } | ||
545 | else if (gVoiceClient->getIsSpeaking(speaker_id)) | ||
546 | { | ||
547 | // reset inactivity expiration | ||
548 | if (speakerp->mStatus != LLSpeaker::STATUS_SPEAKING) | ||
549 | { | ||
550 | speakerp->mLastSpokeTime = mSpeechTimer.getElapsedTimeF32(); | ||
551 | speakerp->mHasSpoken = TRUE; | ||
552 | } | ||
553 | speakerp->mStatus = LLSpeaker::STATUS_SPEAKING; | ||
554 | // interpolate between active color and full speaking color based on power of speech output | ||
555 | speakerp->mDotColor = speaking_color; | ||
556 | if (speakerp->mSpeechVolume > LLVoiceClient::OVERDRIVEN_POWER_LEVEL) | ||
557 | { | ||
558 | speakerp->mDotColor = overdriven_color; | ||
559 | } | ||
560 | } | ||
561 | else | ||
562 | { | ||
563 | speakerp->mSpeechVolume = 0.f; | ||
564 | speakerp->mDotColor = ACTIVE_COLOR; | ||
565 | |||
566 | if (speakerp->mHasSpoken) | ||
567 | { | ||
568 | // have spoken once, not currently speaking | ||
569 | speakerp->mStatus = LLSpeaker::STATUS_HAS_SPOKEN; | ||
570 | } | ||
571 | else | ||
572 | { | ||
573 | // default state for being in voice channel | ||
574 | speakerp->mStatus = LLSpeaker::STATUS_VOICE_ACTIVE; | ||
575 | } | ||
576 | } | ||
577 | } | ||
578 | // speaker no longer registered in voice channel, demote to text only | ||
579 | else if (speakerp->mStatus != LLSpeaker::STATUS_NOT_IN_CHANNEL) | ||
580 | { | ||
581 | speakerp->mStatus = LLSpeaker::STATUS_TEXT_ONLY; | ||
582 | speakerp->mSpeechVolume = 0.f; | ||
583 | speakerp->mDotColor = ACTIVE_COLOR; | ||
584 | } | ||
585 | } | ||
586 | |||
587 | // sort by status then time last spoken | ||
588 | std::sort(mSpeakersSorted.begin(), mSpeakersSorted.end(), LLSortRecentSpeakers()); | ||
589 | |||
590 | // for recent speakers who are not currently speaking, show "recent" color dot for most recent | ||
591 | // fading to "active" color | ||
592 | |||
593 | S32 recent_speaker_count = 0; | ||
594 | S32 sort_index = 0; | ||
595 | speaker_list_t::iterator sorted_speaker_it; | ||
596 | for(sorted_speaker_it = mSpeakersSorted.begin(); | ||
597 | sorted_speaker_it != mSpeakersSorted.end(); ) | ||
598 | { | ||
599 | LLPointer<LLSpeaker> speakerp = *sorted_speaker_it; | ||
600 | |||
601 | // color code recent speakers who are not currently speaking | ||
602 | if (speakerp->mStatus == LLSpeaker::STATUS_HAS_SPOKEN) | ||
603 | { | ||
604 | speakerp->mDotColor = lerp(speaking_color, ACTIVE_COLOR, clamp_rescale((F32)recent_speaker_count, -2.f, 3.f, 0.f, 1.f)); | ||
605 | recent_speaker_count++; | ||
606 | } | ||
607 | |||
608 | // stuff sort ordinal into speaker so the ui can sort by this value | ||
609 | speakerp->mSortIndex = sort_index++; | ||
610 | |||
611 | // remove speakers that have been gone too long | ||
612 | if (speakerp->mStatus == LLSpeaker::STATUS_NOT_IN_CHANNEL && speakerp->mActivityTimer.hasExpired()) | ||
613 | { | ||
614 | mSpeakers.erase(speakerp->mID); | ||
615 | sorted_speaker_it = mSpeakersSorted.erase(sorted_speaker_it); | ||
616 | } | ||
617 | else | ||
618 | { | ||
619 | ++sorted_speaker_it; | ||
620 | } | ||
621 | } | ||
622 | } | ||
623 | |||
624 | void LLSpeakerMgr::updateSpeakerList() | ||
625 | { | ||
626 | // are we bound to the currently active voice channel? | ||
627 | if ((!mVoiceChannel && gVoiceClient->inProximalChannel()) || (mVoiceChannel && mVoiceChannel->isActive())) | ||
628 | { | ||
629 | LLVoiceClient::participantMap* participants = gVoiceClient->getParticipantList(); | ||
630 | LLVoiceClient::participantMap::iterator participant_it; | ||
631 | |||
632 | // add new participants to our list of known speakers | ||
633 | for (participant_it = participants->begin(); participant_it != participants->end(); ++participant_it) | ||
634 | { | ||
635 | LLVoiceClient::participantState* participantp = participant_it->second; | ||
636 | setSpeaker(participantp->mAvatarID, "", LLSpeaker::STATUS_VOICE_ACTIVE); | ||
637 | } | ||
638 | } | ||
639 | } | ||
640 | |||
641 | const LLPointer<LLSpeaker> LLSpeakerMgr::findSpeaker(const LLUUID& speaker_id) | ||
642 | { | ||
643 | speaker_map_t::iterator found_it = mSpeakers.find(speaker_id); | ||
644 | if (found_it == mSpeakers.end()) | ||
645 | { | ||
646 | return NULL; | ||
647 | } | ||
648 | return found_it->second; | ||
649 | } | ||
650 | |||
651 | void LLSpeakerMgr::getSpeakerList(speaker_list_t* speaker_list, BOOL include_text) | ||
652 | { | ||
653 | speaker_list->clear(); | ||
654 | for (speaker_map_t::iterator speaker_it = mSpeakers.begin(); speaker_it != mSpeakers.end(); ++speaker_it) | ||
655 | { | ||
656 | LLPointer<LLSpeaker> speakerp = speaker_it->second; | ||
657 | // what about text only muted or inactive? | ||
658 | if (include_text || speakerp->mStatus != LLSpeaker::STATUS_TEXT_ONLY) | ||
659 | { | ||
660 | speaker_list->push_back(speakerp); | ||
661 | } | ||
662 | } | ||
663 | } | ||
664 | |||
665 | void LLSpeakerMgr::setSpeakerTyping(const LLUUID& speaker_id, BOOL typing) | ||
666 | { | ||
667 | LLPointer<LLSpeaker> speakerp = findSpeaker(speaker_id); | ||
668 | if (speakerp.notNull()) | ||
669 | { | ||
670 | speakerp->mTyping = typing; | ||
671 | } | ||
672 | } | ||
673 | |||
674 | // speaker has chatted via either text or voice | ||
675 | void LLSpeakerMgr::speakerChatted(const LLUUID& speaker_id) | ||
676 | { | ||
677 | LLPointer<LLSpeaker> speakerp = findSpeaker(speaker_id); | ||
678 | if (speakerp.notNull()) | ||
679 | { | ||
680 | speakerp->mLastSpokeTime = mSpeechTimer.getElapsedTimeF32(); | ||
681 | speakerp->mHasSpoken = TRUE; | ||
682 | } | ||
683 | } | ||
684 | |||
685 | BOOL LLSpeakerMgr::isVoiceActive() | ||
686 | { | ||
687 | // mVoiceChannel = NULL means current voice channel, whatever it is | ||
688 | return LLVoiceClient::voiceEnabled() && mVoiceChannel && mVoiceChannel->isActive(); | ||
689 | } | ||
690 | |||
691 | |||
692 | // | ||
693 | // LLIMSpeakerMgr | ||
694 | // | ||
695 | LLIMSpeakerMgr::LLIMSpeakerMgr(LLVoiceChannel* channel) : LLSpeakerMgr(channel) | ||
696 | { | ||
697 | } | ||
698 | |||
699 | void LLIMSpeakerMgr::updateSpeakerList() | ||
700 | { | ||
701 | // don't do normal updates which are pulled from voice channel | ||
702 | // rely on user list reported by sim | ||
703 | return; | ||
704 | } | ||
705 | |||
706 | void LLIMSpeakerMgr::processSpeakerList(LLSD list) | ||
707 | { | ||
708 | for(LLSD::array_iterator list_it = list.beginArray(); | ||
709 | list_it != list.endArray(); | ||
710 | ++list_it) | ||
711 | { | ||
712 | LLUUID agent_id(list_it->asUUID()); | ||
713 | |||
714 | setSpeaker(agent_id, "", LLSpeaker::STATUS_TEXT_ONLY); | ||
715 | } | ||
716 | } | ||
717 | |||
718 | void LLIMSpeakerMgr::processSpeakerMap(LLSD map) | ||
719 | { | ||
720 | for(LLSD::map_iterator map_it = map.beginMap(); | ||
721 | map_it != map.endMap(); | ||
722 | ++map_it) | ||
723 | { | ||
724 | // add as new speaker | ||
725 | setSpeaker(LLUUID(map_it->first)); | ||
726 | } | ||
727 | } | ||
728 | |||
729 | |||
730 | |||
731 | void LLIMSpeakerMgr::processSpeakerListUpdate(LLSD update) | ||
732 | { | ||
733 | for(LLSD::map_iterator update_it = update.beginMap(); | ||
734 | update_it != update.endMap(); | ||
735 | ++update_it) | ||
736 | { | ||
737 | LLUUID agent_id(update_it->first); | ||
738 | |||
739 | if (update_it->second.asString() == "LEAVE") | ||
740 | { | ||
741 | LLPointer<LLSpeaker> speakerp = findSpeaker(agent_id); | ||
742 | if (speakerp) | ||
743 | { | ||
744 | speakerp->mStatus = LLSpeaker::STATUS_NOT_IN_CHANNEL; | ||
745 | speakerp->mDotColor = INACTIVE_COLOR; | ||
746 | speakerp->mActivityTimer.resetWithExpiry(SPEAKER_TIMEOUT); | ||
747 | } | ||
748 | } | ||
749 | else if (update_it->second.asString() == "ENTER") | ||
750 | { | ||
751 | // add or update speaker | ||
752 | setSpeaker(agent_id); | ||
753 | } | ||
754 | else | ||
755 | { | ||
756 | llwarns << "LLIMSpeakerMgr::processSpeakerListUpdate() : bad membership list update " << ll_print_sd(update_it->second) << llendl; | ||
757 | } | ||
758 | } | ||
759 | } | ||
760 | |||
761 | |||
762 | // | ||
763 | // LLActiveSpeakerMgr | ||
764 | // | ||
765 | |||
766 | LLActiveSpeakerMgr::LLActiveSpeakerMgr() : LLSpeakerMgr(NULL) | ||
767 | { | ||
768 | } | ||
769 | |||
770 | void LLActiveSpeakerMgr::updateSpeakerList() | ||
771 | { | ||
772 | // point to whatever the current voice channel is | ||
773 | mVoiceChannel = LLVoiceChannel::getCurrentVoiceChannel(); | ||
774 | |||
775 | // always populate from active voice channel | ||
776 | if (LLVoiceChannel::getCurrentVoiceChannel() != mVoiceChannel) | ||
777 | { | ||
778 | mSpeakers.clear(); | ||
779 | mSpeakersSorted.clear(); | ||
780 | mVoiceChannel = LLVoiceChannel::getCurrentVoiceChannel(); | ||
781 | } | ||
782 | LLSpeakerMgr::updateSpeakerList(); | ||
783 | } | ||
784 | |||
785 | |||
786 | |||
787 | // | ||
788 | // LLLocalSpeakerMgr | ||
789 | // | ||
790 | |||
791 | LLLocalSpeakerMgr::LLLocalSpeakerMgr() : LLSpeakerMgr(LLVoiceChannelProximal::getInstance()) | ||
792 | { | ||
793 | } | ||
794 | |||
795 | LLLocalSpeakerMgr::~LLLocalSpeakerMgr () | ||
796 | { | ||
797 | } | ||
798 | |||
799 | void LLLocalSpeakerMgr::updateSpeakerList() | ||
800 | { | ||
801 | // pull speakers from voice channel | ||
802 | LLSpeakerMgr::updateSpeakerList(); | ||
803 | |||
804 | // add non-voice speakers in chat range | ||
805 | std::vector< LLCharacter* >::iterator avatar_it; | ||
806 | for(avatar_it = LLCharacter::sInstances.begin(); avatar_it != LLCharacter::sInstances.end(); ++avatar_it) | ||
807 | { | ||
808 | LLVOAvatar* avatarp = (LLVOAvatar*)*avatar_it; | ||
809 | if (dist_vec(avatarp->getPositionAgent(), gAgent.getPositionAgent()) <= CHAT_NORMAL_RADIUS) | ||
810 | { | ||
811 | setSpeaker(avatarp->getID()); | ||
812 | } | ||
813 | } | ||
814 | |||
815 | // check if text only speakers have moved out of chat range | ||
816 | for (speaker_map_t::iterator speaker_it = mSpeakers.begin(); speaker_it != mSpeakers.end(); ++speaker_it) | ||
817 | { | ||
818 | LLUUID speaker_id = speaker_it->first; | ||
819 | LLSpeaker* speakerp = speaker_it->second; | ||
820 | if (speakerp->mStatus == LLSpeaker::STATUS_TEXT_ONLY) | ||
821 | { | ||
822 | LLVOAvatar* avatarp = (LLVOAvatar*)gObjectList.findObject(speaker_id); | ||
823 | if (!avatarp || dist_vec(avatarp->getPositionAgent(), gAgent.getPositionAgent()) > CHAT_NORMAL_RADIUS) | ||
824 | { | ||
825 | speakerp->mStatus = LLSpeaker::STATUS_NOT_IN_CHANNEL; | ||
826 | speakerp->mDotColor = INACTIVE_COLOR; | ||
827 | speakerp->mActivityTimer.resetWithExpiry(SPEAKER_TIMEOUT); | ||
828 | } | ||
829 | } | ||
830 | } | ||
831 | } | ||