aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/linden/indra/newview/llvoiceclient.cpp
diff options
context:
space:
mode:
authorJacek Antonelli2008-08-15 23:45:04 -0500
committerJacek Antonelli2008-08-15 23:45:04 -0500
commit117e22047c5752352342d64e3fb7ce00a4eb8113 (patch)
treee32de2cfba0dda8705ae528fcd1fbe23ba075685 /linden/indra/newview/llvoiceclient.cpp
parentSecond Life viewer sources 1.18.0.6 (diff)
downloadmeta-impy-117e22047c5752352342d64e3fb7ce00a4eb8113.zip
meta-impy-117e22047c5752352342d64e3fb7ce00a4eb8113.tar.gz
meta-impy-117e22047c5752352342d64e3fb7ce00a4eb8113.tar.bz2
meta-impy-117e22047c5752352342d64e3fb7ce00a4eb8113.tar.xz
Second Life viewer sources 1.18.1.2
Diffstat (limited to 'linden/indra/newview/llvoiceclient.cpp')
-rw-r--r--linden/indra/newview/llvoiceclient.cpp4076
1 files changed, 4076 insertions, 0 deletions
diff --git a/linden/indra/newview/llvoiceclient.cpp b/linden/indra/newview/llvoiceclient.cpp
new file mode 100644
index 0000000..0bc42c6
--- /dev/null
+++ b/linden/indra/newview/llvoiceclient.cpp
@@ -0,0 +1,4076 @@
1/**
2 * @file llvoiceclient.cpp
3 * @brief Implementation of LLVoiceClient class which is the interface to the voice client process.
4 *
5 * Copyright (c) 2001-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 <boost/tokenizer.hpp>
30
31#include "llviewerprecompiledheaders.h"
32#include "llvoiceclient.h"
33
34#include "llsdutil.h"
35
36#include "llvoavatar.h"
37#include "llbufferstream.h"
38#include "llfile.h"
39#include "expat/expat.h"
40#include "llcallbacklist.h"
41#include "llviewerregion.h"
42#include "llviewernetwork.h" // for gUserServerChoice
43#include "llfloateractivespeakers.h" // for LLSpeakerMgr
44#include "llbase64.h"
45#include "llviewercontrol.h"
46#include "llkeyboard.h"
47#include "viewer.h" // for gDisconnected, gDisableVoice
48#include "llmutelist.h" // to check for muted avatars
49#include "llagent.h"
50#include "llcachename.h"
51#include "llimview.h" // for LLIMMgr
52#include "llimpanel.h" // for LLVoiceChannel
53#include "llparcel.h"
54#include "llviewerparcelmgr.h"
55#include "llfirstuse.h"
56#include "llviewerwindow.h"
57
58// for base64 decoding
59#include "apr-1/apr_base64.h"
60
61// for SHA1 hash
62#include "apr-1/apr_sha1.h"
63
64// If we are connecting to agni AND the user's last name is "Linden", join this channel instead of looking up the sim name.
65// If we are connecting to agni and the user's last name is NOT "Linden", disable voice.
66#define AGNI_LINDENS_ONLY_CHANNEL "SL"
67static bool sConnectingToAgni = false;
68F32 LLVoiceClient::OVERDRIVEN_POWER_LEVEL = 0.7f;
69
70const F32 SPEAKING_TIMEOUT = 1.f;
71
72const int VOICE_MAJOR_VERSION = 1;
73const int VOICE_MINOR_VERSION = 0;
74
75LLVoiceClient *gVoiceClient = NULL;
76
77// Don't retry connecting to the daemon more frequently than this:
78const F32 CONNECT_THROTTLE_SECONDS = 1.0f;
79
80// Don't send positional updates more frequently than this:
81const F32 UPDATE_THROTTLE_SECONDS = 0.1f;
82
83const F32 LOGIN_RETRY_SECONDS = 10.0f;
84const int MAX_LOGIN_RETRIES = 12;
85
86class LLViewerVoiceAccountProvisionResponder :
87 public LLHTTPClient::Responder
88{
89public:
90 LLViewerVoiceAccountProvisionResponder(int retries)
91 {
92 mRetries = retries;
93 }
94
95 virtual void error(U32 status, const std::string& reason)
96 {
97 if ( mRetries > 0 )
98 {
99 if ( gVoiceClient ) gVoiceClient->requestVoiceAccountProvision(
100 mRetries - 1);
101 }
102 else
103 {
104 //TODO: throw an error message?
105 if ( gVoiceClient ) gVoiceClient->giveUp();
106 }
107 }
108
109 virtual void result(const LLSD& content)
110 {
111 if ( gVoiceClient )
112 {
113 gVoiceClient->login(
114 content["username"].asString(),
115 content["password"].asString());
116 }
117 }
118
119private:
120 int mRetries;
121};
122
123/**
124 * @class LLVivoxProtocolParser
125 * @brief This class helps construct new LLIOPipe specializations
126 * @see LLIOPipe
127 *
128 * THOROUGH_DESCRIPTION
129 */
130class LLVivoxProtocolParser : public LLIOPipe
131{
132 LOG_CLASS(LLVivoxProtocolParser);
133public:
134 LLVivoxProtocolParser();
135 virtual ~LLVivoxProtocolParser();
136
137protected:
138 /* @name LLIOPipe virtual implementations
139 */
140 //@{
141 /**
142 * @brief Process the data in buffer
143 */
144 virtual EStatus process_impl(
145 const LLChannelDescriptors& channels,
146 buffer_ptr_t& buffer,
147 bool& eos,
148 LLSD& context,
149 LLPumpIO* pump);
150 //@}
151
152 std::string mInput;
153
154 // Expat control members
155 XML_Parser parser;
156 int responseDepth;
157 bool ignoringTags;
158 bool isEvent;
159 int ignoreDepth;
160
161 // Members for processing responses. The values are transient and only valid within a call to processResponse().
162 int returnCode;
163 int statusCode;
164 std::string statusString;
165 std::string uuidString;
166 std::string actionString;
167 std::string connectorHandle;
168 std::string accountHandle;
169 std::string sessionHandle;
170 std::string eventSessionHandle;
171
172 // Members for processing events. The values are transient and only valid within a call to processResponse().
173 std::string eventTypeString;
174 int state;
175 std::string uriString;
176 bool isChannel;
177 std::string nameString;
178 std::string audioMediaString;
179 std::string displayNameString;
180 int participantType;
181 bool isLocallyMuted;
182 bool isModeratorMuted;
183 bool isSpeaking;
184 int volume;
185 F32 energy;
186
187 // Members for processing text between tags
188 std::string textBuffer;
189 bool accumulateText;
190
191 void reset();
192
193 void processResponse(std::string tag);
194
195static void XMLCALL ExpatStartTag(void *data, const char *el, const char **attr);
196static void XMLCALL ExpatEndTag(void *data, const char *el);
197static void XMLCALL ExpatCharHandler(void *data, const XML_Char *s, int len);
198
199 void StartTag(const char *tag, const char **attr);
200 void EndTag(const char *tag);
201 void CharData(const char *buffer, int length);
202
203};
204
205LLVivoxProtocolParser::LLVivoxProtocolParser()
206{
207 parser = NULL;
208 parser = XML_ParserCreate(NULL);
209
210 reset();
211}
212
213void LLVivoxProtocolParser::reset()
214{
215 responseDepth = 0;
216 ignoringTags = false;
217 accumulateText = false;
218 textBuffer.clear();
219}
220
221//virtual
222LLVivoxProtocolParser::~LLVivoxProtocolParser()
223{
224 if (parser)
225 XML_ParserFree(parser);
226}
227
228// virtual
229LLIOPipe::EStatus LLVivoxProtocolParser::process_impl(
230 const LLChannelDescriptors& channels,
231 buffer_ptr_t& buffer,
232 bool& eos,
233 LLSD& context,
234 LLPumpIO* pump)
235{
236 LLBufferStream istr(channels, buffer.get());
237 std::ostringstream ostr;
238 while (istr.good())
239 {
240 char buf[1024];
241 istr.read(buf, sizeof(buf));
242 mInput.append(buf, istr.gcount());
243 }
244
245 // MBW -- XXX -- This should no longer be necessary. Or even possible.
246 // We've read all the data out of the buffer. Make sure it doesn't accumulate.
247// buffer->clear();
248
249 // Look for input delimiter(s) in the input buffer. If one is found, send the message to the xml parser.
250 int start = 0;
251 int delim;
252 while((delim = mInput.find("\n\n\n", start)) != std::string::npos)
253 {
254 // Turn this on to log incoming XML
255 if(0)
256 {
257 int foo = mInput.find("Set3DPosition", start);
258 int bar = mInput.find("ParticipantPropertiesEvent", start);
259 if(foo != std::string::npos && (foo < delim))
260 {
261 // This is a Set3DPosition response. Don't print it, since these are way too spammy.
262 }
263 else if(bar != std::string::npos && (bar < delim))
264 {
265 // This is a ParticipantPropertiesEvent response. Don't print it, since these are way too spammy.
266 }
267 else
268 {
269 llinfos << "parsing: " << mInput.substr(start, delim - start) << llendl;
270 }
271 }
272
273 // Reset internal state of the LLVivoxProtocolParser (no effect on the expat parser)
274 reset();
275
276 XML_ParserReset(parser, NULL);
277 XML_SetElementHandler(parser, ExpatStartTag, ExpatEndTag);
278 XML_SetCharacterDataHandler(parser, ExpatCharHandler);
279 XML_SetUserData(parser, this);
280 XML_Parse(parser, mInput.data() + start, delim - start, false);
281
282 start = delim + 3;
283 }
284
285 if(start != 0)
286 mInput = mInput.substr(start);
287
288// llinfos << "at end, mInput is: " << mInput << llendl;
289
290 if(!gVoiceClient->mConnected)
291 {
292 // If voice has been disabled, we just want to close the socket. This does so.
293 llinfos << "returning STATUS_STOP" << llendl;
294 return STATUS_STOP;
295 }
296
297 return STATUS_OK;
298}
299
300void XMLCALL LLVivoxProtocolParser::ExpatStartTag(void *data, const char *el, const char **attr)
301{
302 if (data)
303 {
304 LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data;
305 object->StartTag(el, attr);
306 }
307}
308
309// --------------------------------------------------------------------------------
310
311void XMLCALL LLVivoxProtocolParser::ExpatEndTag(void *data, const char *el)
312{
313 if (data)
314 {
315 LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data;
316 object->EndTag(el);
317 }
318}
319
320// --------------------------------------------------------------------------------
321
322void XMLCALL LLVivoxProtocolParser::ExpatCharHandler(void *data, const XML_Char *s, int len)
323{
324 if (data)
325 {
326 LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data;
327 object->CharData(s, len);
328 }
329}
330
331// --------------------------------------------------------------------------------
332
333
334void LLVivoxProtocolParser::StartTag(const char *tag, const char **attr)
335{
336 // Reset the text accumulator. We shouldn't have strings that are inturrupted by new tags
337 textBuffer.clear();
338 // only accumulate text if we're not ignoring tags.
339 accumulateText = !ignoringTags;
340
341 if (responseDepth == 0)
342 {
343 isEvent = strcmp("Event", tag) == 0;
344
345 if (strcmp("Response", tag) == 0 || isEvent)
346 {
347 // Grab the attributes
348 while (*attr)
349 {
350 const char *key = *attr++;
351 const char *value = *attr++;
352
353 if (strcmp("requestId", key) == 0)
354 {
355 uuidString = value;
356 }
357 else if (strcmp("action", key) == 0)
358 {
359 actionString = value;
360 }
361 else if (strcmp("type", key) == 0)
362 {
363 eventTypeString = value;
364 }
365 }
366 }
367 //llinfos << tag << " (" << responseDepth << ")" << llendl;
368 }
369 else
370 {
371 if (ignoringTags)
372 {
373 //llinfos << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << llendl;
374 }
375 else
376 {
377 //llinfos << tag << " (" << responseDepth << ")" << llendl;
378
379 // Ignore the InputXml stuff so we don't get confused
380 if (strcmp("InputXml", tag) == 0)
381 {
382 ignoringTags = true;
383 ignoreDepth = responseDepth;
384 accumulateText = false;
385
386 //llinfos << "starting ignore, ignoreDepth is " << ignoreDepth << llendl;
387 }
388 else if (strcmp("CaptureDevices", tag) == 0)
389 {
390 gVoiceClient->clearCaptureDevices();
391 }
392 else if (strcmp("RenderDevices", tag) == 0)
393 {
394 gVoiceClient->clearRenderDevices();
395 }
396 }
397 }
398 responseDepth++;
399}
400
401// --------------------------------------------------------------------------------
402
403void LLVivoxProtocolParser::EndTag(const char *tag)
404{
405 const char *string = textBuffer.c_str();
406 bool clearbuffer = true;
407
408 responseDepth--;
409
410 if (ignoringTags)
411 {
412 if (ignoreDepth == responseDepth)
413 {
414 //llinfos << "end of ignore" << llendl;
415 ignoringTags = false;
416 }
417 else
418 {
419 //llinfos << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << llendl;
420 }
421 }
422
423 if (!ignoringTags)
424 {
425 //llinfos << "processing tag " << tag << " (depth = " << responseDepth << ")" << llendl;
426
427 // Closing a tag. Finalize the text we've accumulated and reset
428 if (strcmp("ReturnCode", tag) == 0)
429 returnCode = strtol(string, NULL, 10);
430 else if (strcmp("StatusCode", tag) == 0)
431 statusCode = strtol(string, NULL, 10);
432 else if (strcmp("ConnectorHandle", tag) == 0)
433 connectorHandle = string;
434 else if (strcmp("AccountHandle", tag) == 0)
435 accountHandle = string;
436 else if (strcmp("SessionHandle", tag) == 0)
437 {
438 if (isEvent)
439 eventSessionHandle = string;
440 else
441 sessionHandle = string;
442 }
443 else if (strcmp("StatusString", tag) == 0)
444 statusString = string;
445 else if (strcmp("State", tag) == 0)
446 state = strtol(string, NULL, 10);
447 else if (strcmp("URI", tag) == 0)
448 uriString = string;
449 else if (strcmp("IsChannel", tag) == 0)
450 isChannel = strcmp(string, "true") == 0;
451 else if (strcmp("Name", tag) == 0)
452 nameString = string;
453 else if (strcmp("AudioMedia", tag) == 0)
454 audioMediaString = string;
455 else if (strcmp("ChannelName", tag) == 0)
456 nameString = string;
457 else if (strcmp("ParticipantURI", tag) == 0)
458 uriString = string;
459 else if (strcmp("DisplayName", tag) == 0)
460 displayNameString = string;
461 else if (strcmp("AccountName", tag) == 0)
462 nameString = string;
463 else if (strcmp("ParticipantTyppe", tag) == 0)
464 participantType = strtol(string, NULL, 10);
465 else if (strcmp("IsLocallyMuted", tag) == 0)
466 isLocallyMuted = strcmp(string, "true") == 0;
467 else if (strcmp("IsModeratorMuted", tag) == 0)
468 isModeratorMuted = strcmp(string, "true") == 0;
469 else if (strcmp("IsSpeaking", tag) == 0)
470 isSpeaking = strcmp(string, "true") == 0;
471 else if (strcmp("Volume", tag) == 0)
472 volume = strtol(string, NULL, 10);
473 else if (strcmp("Energy", tag) == 0)
474 energy = (F32)strtod(string, NULL);
475 else if (strcmp("MicEnergy", tag) == 0)
476 energy = (F32)strtod(string, NULL);
477 else if (strcmp("ChannelName", tag) == 0)
478 nameString = string;
479 else if (strcmp("ChannelURI", tag) == 0)
480 uriString = string;
481 else if (strcmp("ChannelListResult", tag) == 0)
482 {
483 gVoiceClient->addChannelMapEntry(nameString, uriString);
484 }
485 else if (strcmp("Device", tag) == 0)
486 {
487 // This closing tag shouldn't clear the accumulated text.
488 clearbuffer = false;
489 }
490 else if (strcmp("CaptureDevice", tag) == 0)
491 {
492 gVoiceClient->addCaptureDevice(textBuffer);
493 }
494 else if (strcmp("RenderDevice", tag) == 0)
495 {
496 gVoiceClient->addRenderDevice(textBuffer);
497 }
498
499 if(clearbuffer)
500 {
501 textBuffer.clear();
502 accumulateText= false;
503 }
504
505 if (responseDepth == 0)
506 {
507 // We finished all of the XML, process the data
508 processResponse(tag);
509 }
510 }
511}
512
513// --------------------------------------------------------------------------------
514
515void LLVivoxProtocolParser::CharData(const char *buffer, int length)
516{
517 /*
518 This method is called for anything that isn't a tag, which can be text you
519 want that lies between tags, and a lot of stuff you don't want like file formatting
520 (tabs, spaces, CR/LF, etc).
521
522 Only copy text if we are in accumulate mode...
523 */
524 if (accumulateText)
525 textBuffer.append(buffer, length);
526}
527
528// --------------------------------------------------------------------------------
529
530void LLVivoxProtocolParser::processResponse(std::string tag)
531{
532// llinfos << tag << llendl;
533
534 if (isEvent)
535 {
536 if (eventTypeString == "LoginStateChangeEvent")
537 {
538 gVoiceClient->loginStateChangeEvent(accountHandle, statusCode, statusString, state);
539 }
540 else if (eventTypeString == "SessionNewEvent")
541 {
542 gVoiceClient->sessionNewEvent(accountHandle, eventSessionHandle, state, nameString, uriString);
543 }
544 else if (eventTypeString == "SessionStateChangeEvent")
545 {
546 gVoiceClient->sessionStateChangeEvent(uriString, statusCode, statusString, eventSessionHandle, state, isChannel, nameString);
547 }
548 else if (eventTypeString == "ParticipantStateChangeEvent")
549 {
550 gVoiceClient->participantStateChangeEvent(uriString, statusCode, statusString, state, nameString, displayNameString, participantType);
551
552 }
553 else if (eventTypeString == "ParticipantPropertiesEvent")
554 {
555 gVoiceClient->participantPropertiesEvent(uriString, statusCode, statusString, isLocallyMuted, isModeratorMuted, isSpeaking, volume, energy);
556 }
557 else if (eventTypeString == "AuxAudioPropertiesEvent")
558 {
559 gVoiceClient->auxAudioPropertiesEvent(energy);
560 }
561 }
562 else
563 {
564 if (actionString == "Connector.Create.1")
565 {
566 gVoiceClient->connectorCreateResponse(statusCode, statusString, connectorHandle);
567 }
568 else if (actionString == "Account.Login.1")
569 {
570 gVoiceClient->loginResponse(statusCode, statusString, accountHandle);
571 }
572 else if (actionString == "Session.Create.1")
573 {
574 gVoiceClient->sessionCreateResponse(statusCode, statusString, sessionHandle);
575 }
576 else if (actionString == "Session.Connect.1")
577 {
578 gVoiceClient->sessionConnectResponse(statusCode, statusString);
579 }
580 else if (actionString == "Session.Terminate.1")
581 {
582 gVoiceClient->sessionTerminateResponse(statusCode, statusString);
583 }
584 else if (actionString == "Account.Logout.1")
585 {
586 gVoiceClient->logoutResponse(statusCode, statusString);
587 }
588 else if (actionString == "Connector.InitiateShutdown.1")
589 {
590 gVoiceClient->connectorShutdownResponse(statusCode, statusString);
591 }
592 else if (actionString == "Account.ChannelGetList.1")
593 {
594 gVoiceClient->channelGetListResponse(statusCode, statusString);
595 }
596/*
597 else if (actionString == "Connector.AccountCreate.1")
598 {
599
600 }
601 else if (actionString == "Connector.MuteLocalMic.1")
602 {
603
604 }
605 else if (actionString == "Connector.MuteLocalSpeaker.1")
606 {
607
608 }
609 else if (actionString == "Connector.SetLocalMicVolume.1")
610 {
611
612 }
613 else if (actionString == "Connector.SetLocalSpeakerVolume.1")
614 {
615
616 }
617 else if (actionString == "Session.ListenerSetPosition.1")
618 {
619
620 }
621 else if (actionString == "Session.SpeakerSetPosition.1")
622 {
623
624 }
625 else if (actionString == "Session.Set3DPosition.1")
626 {
627
628 }
629 else if (actionString == "Session.AudioSourceSetPosition.1")
630 {
631
632 }
633 else if (actionString == "Session.GetChannelParticipants.1")
634 {
635
636 }
637 else if (actionString == "Account.ChannelCreate.1")
638 {
639
640 }
641 else if (actionString == "Account.ChannelUpdate.1")
642 {
643
644 }
645 else if (actionString == "Account.ChannelDelete.1")
646 {
647
648 }
649 else if (actionString == "Account.ChannelCreateAndInvite.1")
650 {
651
652 }
653 else if (actionString == "Account.ChannelFolderCreate.1")
654 {
655
656 }
657 else if (actionString == "Account.ChannelFolderUpdate.1")
658 {
659
660 }
661 else if (actionString == "Account.ChannelFolderDelete.1")
662 {
663
664 }
665 else if (actionString == "Account.ChannelAddModerator.1")
666 {
667
668 }
669 else if (actionString == "Account.ChannelDeleteModerator.1")
670 {
671
672 }
673*/
674 }
675}
676
677///////////////////////////////////////////////////////////////////////////////////////////////
678
679class LLVoiceClientPrefsListener: public LLSimpleListener
680{
681 bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata)
682 {
683 // Note: Ignore the specific event value, look up the ones we want
684
685 gVoiceClient->setVoiceEnabled(gSavedSettings.getBOOL("EnableVoiceChat"));
686 gVoiceClient->setUsePTT(gSavedSettings.getBOOL("PTTCurrentlyEnabled"));
687 std::string keyString = gSavedSettings.getString("PushToTalkButton");
688 gVoiceClient->setPTTKey(keyString);
689 gVoiceClient->setPTTIsToggle(gSavedSettings.getBOOL("PushToTalkToggle"));
690 gVoiceClient->setEarLocation(gSavedSettings.getS32("VoiceEarLocation"));
691 std::string serverName = gSavedSettings.getString("VivoxDebugServerName");
692 gVoiceClient->setVivoxDebugServerName(serverName);
693
694 std::string inputDevice = gSavedSettings.getString("VoiceInputAudioDevice");
695 gVoiceClient->setCaptureDevice(inputDevice);
696 std::string outputDevice = gSavedSettings.getString("VoiceOutputAudioDevice");
697 gVoiceClient->setRenderDevice(outputDevice);
698
699 return true;
700 }
701};
702static LLVoiceClientPrefsListener voice_prefs_listener;
703
704class LLVoiceClientMuteListObserver : public LLMuteListObserver
705{
706 /* virtual */ void onChange() { gVoiceClient->muteListChanged();}
707};
708static LLVoiceClientMuteListObserver mutelist_listener;
709static bool sMuteListListener_listening = false;
710
711///////////////////////////////////////////////////////////////////////////////////////////////
712
713class LLVoiceClientCapResponder : public LLHTTPClient::Responder
714{
715public:
716 LLVoiceClientCapResponder(void){};
717
718 virtual void error(U32 status, const std::string& reason); // called with bad status codes
719 virtual void result(const LLSD& content);
720
721private:
722};
723
724void LLVoiceClientCapResponder::error(U32 status, const std::string& reason)
725{
726 llwarns << "LLVoiceClientCapResponder::error("
727 << status << ": " << reason << ")"
728 << llendl;
729}
730
731void LLVoiceClientCapResponder::result(const LLSD& content)
732{
733 LLSD::map_const_iterator iter;
734 for(iter = content.beginMap(); iter != content.endMap(); ++iter)
735 {
736 llinfos << "LLVoiceClientCapResponder::result got "
737 << iter->first << llendl;
738 }
739
740 if ( content.has("voice_credentials") )
741 {
742 LLSD voice_credentials = content["voice_credentials"];
743 std::string uri;
744 std::string credentials;
745
746 if ( voice_credentials.has("channel_uri") )
747 {
748 uri = voice_credentials["channel_uri"].asString();
749 }
750 if ( voice_credentials.has("channel_credentials") )
751 {
752 credentials =
753 voice_credentials["channel_credentials"].asString();
754 }
755
756 gVoiceClient->setSpatialChannel(uri, credentials);
757 }
758}
759
760
761
762#if LL_WINDOWS
763static HANDLE sGatewayHandle = 0;
764
765static bool isGatewayRunning()
766{
767 bool result = false;
768 if(sGatewayHandle != 0)
769 {
770 DWORD waitresult = WaitForSingleObject(sGatewayHandle, 0);
771 if(waitresult != WAIT_OBJECT_0)
772 {
773 result = true;
774 }
775 }
776 return result;
777}
778static void killGateway()
779{
780 if(sGatewayHandle != 0)
781 {
782 TerminateProcess(sGatewayHandle,0);
783 }
784}
785
786#else // Mac and linux
787
788static pid_t sGatewayPID = 0;
789static bool isGatewayRunning()
790{
791 bool result = false;
792 if(sGatewayPID != 0)
793 {
794 // A kill with signal number 0 has no effect, just does error checking. It should return an error if the process no longer exists.
795 if(kill(sGatewayPID, 0) == 0)
796 {
797 result = true;
798 }
799 }
800 return result;
801}
802
803static void killGateway()
804{
805 if(sGatewayPID != 0)
806 {
807 kill(sGatewayPID, SIGTERM);
808 }
809}
810
811#endif
812
813///////////////////////////////////////////////////////////////////////////////////////////////
814
815LLVoiceClient::LLVoiceClient()
816{
817 gVoiceClient = this;
818 mWriteInProgress = false;
819 mAreaVoiceDisabled = false;
820 mPTT = true;
821 mUserPTTState = false;
822 mMuteMic = false;
823 mSessionTerminateRequested = false;
824 mCommandCookie = 0;
825 mNonSpatialChannel = false;
826 mNextSessionSpatial = true;
827 mNextSessionNoReconnect = false;
828 mSessionP2P = false;
829 mCurrentParcelLocalID = 0;
830 mLoginRetryCount = 0;
831 mVivoxErrorStatusCode = 0;
832
833 mNextSessionResetOnClose = false;
834 mSessionResetOnClose = false;
835 mSpeakerVolume = 0;
836 mMicVolume = 0;
837
838 // Initial dirty state
839 mSpatialCoordsDirty = false;
840 mPTTDirty = true;
841 mVolumeDirty = true;
842 mSpeakerVolumeDirty = true;
843 mMicVolumeDirty = true;
844 mCaptureDeviceDirty = false;
845 mRenderDeviceDirty = false;
846
847 // Load initial state from prefs.
848 mVoiceEnabled = gSavedSettings.getBOOL("EnableVoiceChat");
849 mUsePTT = gSavedSettings.getBOOL("EnablePushToTalk");
850 std::string keyString = gSavedSettings.getString("PushToTalkButton");
851 setPTTKey(keyString);
852 mPTTIsToggle = gSavedSettings.getBOOL("PushToTalkToggle");
853 mEarLocation = gSavedSettings.getS32("VoiceEarLocation");
854 setVoiceVolume(gSavedSettings.getF32("AudioLevelVoice"));
855 std::string captureDevice = gSavedSettings.getString("VoiceInputAudioDevice");
856 setCaptureDevice(captureDevice);
857 std::string renderDevice = gSavedSettings.getString("VoiceOutputAudioDevice");
858 setRenderDevice(renderDevice);
859
860 // Set up our listener to get updates on all prefs values we care about.
861 gSavedSettings.getControl("EnableVoiceChat")->addListener(&voice_prefs_listener);
862 gSavedSettings.getControl("PTTCurrentlyEnabled")->addListener(&voice_prefs_listener);
863 gSavedSettings.getControl("PushToTalkButton")->addListener(&voice_prefs_listener);
864 gSavedSettings.getControl("PushToTalkToggle")->addListener(&voice_prefs_listener);
865 gSavedSettings.getControl("VoiceEarLocation")->addListener(&voice_prefs_listener);
866 gSavedSettings.getControl("VivoxDebugServerName")->addListener(&voice_prefs_listener);
867 gSavedSettings.getControl("VoiceInputAudioDevice")->addListener(&voice_prefs_listener);
868 gSavedSettings.getControl("VoiceOutputAudioDevice")->addListener(&voice_prefs_listener);
869
870 mTuningMode = false;
871 mTuningEnergy = 0.0f;
872 mTuningMicVolume = 0;
873 mTuningMicVolumeDirty = true;
874 mTuningSpeakerVolume = 0;
875 mTuningSpeakerVolumeDirty = true;
876 mTuningCaptureRunning = false;
877
878 // gMuteListp isn't set up at this point, so we defer this until later.
879// gMuteListp->addObserver(&mutelist_listener);
880
881 mParticipantMapChanged = false;
882
883 // stash the pump for later use
884 // This now happens when init() is called instead.
885 mPump = NULL;
886
887#if LL_DARWIN || LL_LINUX
888 // MBW -- XXX -- THIS DOES NOT BELONG HERE
889 // When the vivox daemon dies, the next write attempt on our socket generates a SIGPIPE, which kills us.
890 // This should cause us to ignore SIGPIPE and handle the error through proper channels.
891 // This should really be set up elsewhere. Where should it go?
892 signal(SIGPIPE, SIG_IGN);
893
894 // Since we're now launching the gateway with fork/exec instead of system(), we need to deal with zombie processes.
895 // Ignoring SIGCHLD should prevent zombies from being created. Alternately, we could use wait(), but I'd rather not do that.
896 signal(SIGCHLD, SIG_IGN);
897#endif
898
899 // set up state machine
900 setState(stateDisabled);
901
902 gIdleCallbacks.addFunction(idle, this);
903}
904
905//---------------------------------------------------
906
907LLVoiceClient::~LLVoiceClient()
908{
909}
910
911//----------------------------------------------
912
913
914
915void LLVoiceClient::init(LLPumpIO *pump)
916{
917 // constructor will set up gVoiceClient
918 LLVoiceClient::getInstance()->mPump = pump;
919}
920
921void LLVoiceClient::terminate()
922{
923 if(gVoiceClient)
924 {
925 gVoiceClient->sessionTerminateSendMessage();
926 gVoiceClient->logout();
927 gVoiceClient->connectorShutdown();
928 gVoiceClient->closeSocket(); // Need to do this now -- bad things happen if the destructor does it later.
929
930 // This will do unpleasant things on windows.
931// killGateway();
932
933 // Don't do this anymore -- LLSingleton will take care of deleting the object.
934// delete gVoiceClient;
935
936 // Hint to other code not to access the voice client anymore.
937 gVoiceClient = NULL;
938 }
939}
940
941
942/////////////////////////////
943// utility functions
944
945bool LLVoiceClient::writeString(const std::string &str)
946{
947 bool result = false;
948 if(mConnected)
949 {
950 apr_status_t err;
951 apr_size_t size = (apr_size_t)str.size();
952 apr_size_t written = size;
953
954// llinfos << "sending: " << str << llendl;
955
956 // MBW -- XXX -- check return code - sockets will fail (broken, etc.)
957 err = apr_socket_send(
958 mSocket->getSocket(),
959 (const char*)str.data(),
960 &written);
961
962 if(err == 0)
963 {
964 // Success.
965 result = true;
966 }
967 // MBW -- XXX -- handle partial writes (written is number of bytes written)
968 // Need to set socket to non-blocking before this will work.
969// else if(APR_STATUS_IS_EAGAIN(err))
970// {
971// //
972// }
973 else
974 {
975 // Assume any socket error means something bad. For now, just close the socket.
976 char buf[MAX_STRING];
977 llwarns << "apr error " << err << " ("<< apr_strerror(err, buf, MAX_STRING) << ") sending data to vivox daemon." << llendl;
978 daemonDied();
979 }
980 }
981
982 return result;
983}
984
985
986/////////////////////////////
987// session control messages
988void LLVoiceClient::connectorCreate()
989{
990 std::ostringstream stream;
991 std::string logpath;
992 std::string loglevel = "0";
993
994 // Transition to stateConnectorStarted when the connector handle comes back.
995 setState(stateConnectorStarting);
996
997 std::string savedLogLevel = gSavedSettings.getString("VivoxDebugLevel");
998
999 if(savedLogLevel != "-1")
1000 {
1001 llinfos << "creating connector with logging enabled" << llendl;
1002 loglevel = "10";
1003 logpath = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "");
1004 }
1005
1006 stream
1007 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.Create.1\">"
1008 << "<ClientName>V2 SDK</ClientName>"
1009 << "<AccountManagementServer>" << mAccountServerURI << "</AccountManagementServer>"
1010 << "<Logging>"
1011 << "<Enabled>false</Enabled>"
1012 << "<Folder>" << logpath << "</Folder>"
1013 << "<FileNamePrefix>Connector</FileNamePrefix>"
1014 << "<FileNameSuffix>.log</FileNameSuffix>"
1015 << "<LogLevel>" << loglevel << "</LogLevel>"
1016 << "</Logging>"
1017 << "</Request>\n\n\n";
1018
1019 writeString(stream.str());
1020}
1021
1022void LLVoiceClient::connectorShutdown()
1023{
1024 setState(stateConnectorStopping);
1025
1026 if(!mConnectorHandle.empty())
1027 {
1028 std::ostringstream stream;
1029 stream
1030 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.InitiateShutdown.1\">"
1031 << "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>"
1032 << "</Request>"
1033 << "\n\n\n";
1034
1035 mConnectorHandle.clear();
1036
1037 writeString(stream.str());
1038 }
1039}
1040
1041void LLVoiceClient::userAuthorized(const std::string& firstName, const std::string& lastName, const LLUUID &agentID)
1042{
1043 mAccountFirstName = firstName;
1044 mAccountLastName = lastName;
1045
1046 mAccountDisplayName = firstName;
1047 mAccountDisplayName += " ";
1048 mAccountDisplayName += lastName;
1049
1050 llinfos << "name \"" << mAccountDisplayName << "\" , ID " << agentID << llendl;
1051
1052 std::string userserver = gUserServerName;
1053 LLString::toLower(userserver);
1054 if((gUserServerChoice == USERSERVER_AGNI) ||
1055 ((gUserServerChoice == USERSERVER_OTHER) && (userserver.find("agni") != std::string::npos)))
1056 {
1057 sConnectingToAgni = true;
1058 }
1059
1060 // MBW -- XXX -- Enable this when the bhd.vivox.com server gets a real ssl cert.
1061 if(sConnectingToAgni)
1062 {
1063 // Use the release account server
1064 mAccountServerName = "bhr.vivox.com";
1065 mAccountServerURI = "https://www." + mAccountServerName + "/api2/";
1066 }
1067 else
1068 {
1069 // Use the development account server
1070 mAccountServerName = gSavedSettings.getString("VivoxDebugServerName");
1071 mAccountServerURI = "https://www." + mAccountServerName + "/api2/";
1072 }
1073
1074 mAccountName = nameFromID(agentID);
1075}
1076
1077void LLVoiceClient::requestVoiceAccountProvision(S32 retries)
1078{
1079 if ( gAgent.getRegion() && mVoiceEnabled )
1080 {
1081 std::string url =
1082 gAgent.getRegion()->getCapability(
1083 "ProvisionVoiceAccountRequest");
1084
1085 if ( url == "" ) return;
1086
1087 LLHTTPClient::post(
1088 url,
1089 LLSD(),
1090 new LLViewerVoiceAccountProvisionResponder(retries));
1091 }
1092}
1093
1094void LLVoiceClient::login(
1095 const std::string& accountName,
1096 const std::string &password)
1097{
1098 if((getState() >= stateLoggingIn) && (getState() < stateLoggedOut))
1099 {
1100 // Already logged in. This is an internal error.
1101 llerrs << "called from wrong state." << llendl;
1102 }
1103 else if ( accountName != mAccountName )
1104 {
1105 //TODO: error?
1106 llinfos << "Wrong account name! " << accountName
1107 << " instead of " << mAccountName << llendl;
1108 }
1109 else
1110 {
1111 mAccountPassword = password;
1112 }
1113}
1114
1115void LLVoiceClient::idle(void* user_data)
1116{
1117 LLVoiceClient* self = (LLVoiceClient*)user_data;
1118 self->stateMachine();
1119}
1120
1121const char *LLVoiceClient::state2string(LLVoiceClient::state inState)
1122{
1123 const char *result = "UNKNOWN";
1124
1125 // Prevent copy-paste errors when updating this list...
1126#define CASE(x) case x: result = #x; break
1127
1128 switch(inState)
1129 {
1130 CASE(stateDisabled);
1131 CASE(stateStart);
1132 CASE(stateDaemonLaunched);
1133 CASE(stateConnecting);
1134 CASE(stateIdle);
1135 CASE(stateConnectorStart);
1136 CASE(stateConnectorStarting);
1137 CASE(stateConnectorStarted);
1138 CASE(stateMicTuningNoLogin);
1139 CASE(stateLoginRetry);
1140 CASE(stateLoginRetryWait);
1141 CASE(stateNeedsLogin);
1142 CASE(stateLoggingIn);
1143 CASE(stateLoggedIn);
1144 CASE(stateNoChannel);
1145 CASE(stateMicTuningLoggedIn);
1146 CASE(stateSessionCreate);
1147 CASE(stateSessionConnect);
1148 CASE(stateJoiningSession);
1149 CASE(stateSessionJoined);
1150 CASE(stateRunning);
1151 CASE(stateLeavingSession);
1152 CASE(stateSessionTerminated);
1153 CASE(stateLoggingOut);
1154 CASE(stateLoggedOut);
1155 CASE(stateConnectorStopping);
1156 CASE(stateConnectorStopped);
1157 CASE(stateConnectorFailed);
1158 CASE(stateConnectorFailedWaiting);
1159 CASE(stateLoginFailed);
1160 CASE(stateLoginFailedWaiting);
1161 CASE(stateJoinSessionFailed);
1162 CASE(stateJoinSessionFailedWaiting);
1163 CASE(stateJail);
1164 }
1165
1166#undef CASE
1167
1168 return result;
1169}
1170
1171const char *LLVoiceClientStatusObserver::status2string(LLVoiceClientStatusObserver::EStatusType inStatus)
1172{
1173 const char *result = "UNKNOWN";
1174
1175 // Prevent copy-paste errors when updating this list...
1176#define CASE(x) case x: result = #x; break
1177
1178 switch(inStatus)
1179 {
1180 CASE(STATUS_JOINING);
1181 CASE(STATUS_JOINED);
1182 CASE(STATUS_LEFT_CHANNEL);
1183 CASE(ERROR_CHANNEL_FULL);
1184 CASE(ERROR_CHANNEL_LOCKED);
1185 CASE(ERROR_UNKNOWN);
1186 default:
1187 break;
1188 }
1189
1190#undef CASE
1191
1192 return result;
1193}
1194
1195void LLVoiceClient::setState(state inState)
1196{
1197 llinfos << "entering state " << state2string(inState) << llendl;
1198
1199 mState = inState;
1200}
1201
1202void LLVoiceClient::stateMachine()
1203{
1204 if(gDisconnected)
1205 {
1206 // The viewer has been disconnected from the sim. Disable voice.
1207 setVoiceEnabled(false);
1208 }
1209
1210 if(!mVoiceEnabled)
1211 {
1212 if(getState() != stateDisabled)
1213 {
1214 // User turned off voice support. Send the cleanup messages, close the socket, and reset.
1215 if(!mConnected)
1216 {
1217 // if voice was turned off after the daemon was launched but before we could connect to it, we may need to issue a kill.
1218 llinfos << "Disabling voice before connection to daemon, terminating." << llendl;
1219 killGateway();
1220 }
1221
1222 sessionTerminateSendMessage();
1223 logout();
1224 connectorShutdown();
1225 closeSocket();
1226 removeAllParticipants();
1227
1228 setState(stateDisabled);
1229 }
1230 }
1231
1232 // Check for parcel boundary crossing
1233 {
1234 LLViewerRegion *region = gAgent.getRegion();
1235 LLParcel *parcel = NULL;
1236
1237 if(gParcelMgr)
1238 {
1239 parcel = gParcelMgr->getAgentParcel();
1240 }
1241
1242 if(region && parcel)
1243 {
1244 S32 parcelLocalID = parcel->getLocalID();
1245 std::string regionName = region->getName();
1246 std::string capURI = region->getCapability("ParcelVoiceInfoRequest");
1247
1248// llinfos << "Region name = \"" << regionName <<"\", " << "parcel local ID = " << parcelLocalID << llendl;
1249
1250 // The region name starts out empty and gets filled in later.
1251 // Also, the cap gets filled in a short time after the region cross, but a little too late for our purposes.
1252 // If either is empty, wait for the next time around.
1253 if(!regionName.empty() && !capURI.empty())
1254 {
1255 if((parcelLocalID != mCurrentParcelLocalID) || (regionName != mCurrentRegionName))
1256 {
1257 // We have changed parcels. Initiate a parcel channel lookup.
1258 mCurrentParcelLocalID = parcelLocalID;
1259 mCurrentRegionName = regionName;
1260
1261 parcelChanged();
1262 }
1263 }
1264 }
1265 }
1266
1267 switch(getState())
1268 {
1269 case stateDisabled:
1270 if(mVoiceEnabled && (!mAccountName.empty() || mTuningMode))
1271 {
1272 setState(stateStart);
1273 }
1274 break;
1275
1276 case stateStart:
1277 if(gDisableVoice)
1278 {
1279 // Voice is locked out, we must not launch the vivox daemon.
1280 setState(stateJail);
1281 }
1282 else if(!isGatewayRunning())
1283 {
1284 if(true)
1285 {
1286 // Launch the voice daemon
1287 std::string exe_path = gDirUtilp->getAppRODataDir();
1288 exe_path += gDirUtilp->getDirDelimiter();
1289#if LL_WINDOWS
1290 exe_path += "SLVoice.exe";
1291#else
1292 // This will be the same for mac and linux
1293 exe_path += "SLVoice";
1294#endif
1295 // See if the vivox executable exists
1296 llstat s;
1297 if(!LLFile::stat(exe_path.c_str(), &s))
1298 {
1299 // vivox executable exists. Build the command line and launch the daemon.
1300 std::string args = " -p tcp -h -c";
1301 std::string cmd;
1302 std::string loglevel = gSavedSettings.getString("VivoxDebugLevel");
1303
1304 if(loglevel.empty())
1305 {
1306 loglevel = "-1"; // turn logging off completely
1307 }
1308
1309 args += " -ll ";
1310 args += loglevel;
1311
1312// llinfos << "Args for SLVoice: " << args << llendl;
1313
1314#if LL_WINDOWS
1315 PROCESS_INFORMATION pinfo;
1316 STARTUPINFOA sinfo;
1317 memset(&sinfo, 0, sizeof(sinfo));
1318 std::string exe_dir = gDirUtilp->getAppRODataDir();
1319 cmd = "SLVoice.exe";
1320 cmd += args;
1321
1322 // So retarded. Windows requires that the second parameter to CreateProcessA be a writable (non-const) string...
1323 char *args2 = new char[args.size() + 1];
1324 strcpy(args2, args.c_str());
1325
1326 if(!CreateProcessA(exe_path.c_str(), args2, NULL, NULL, FALSE, 0, NULL, exe_dir.c_str(), &sinfo, &pinfo))
1327 {
1328// DWORD dwErr = GetLastError();
1329 }
1330 else
1331 {
1332 // foo = pinfo.dwProcessId; // get your pid here if you want to use it later on
1333 // CloseHandle(pinfo.hProcess); // stops leaks - nothing else
1334 sGatewayHandle = pinfo.hProcess;
1335 CloseHandle(pinfo.hThread); // stops leaks - nothing else
1336 }
1337
1338 delete args2;
1339#else // LL_WINDOWS
1340 // This should be the same for mac and linux
1341 {
1342 std::vector<std::string> arglist;
1343 arglist.push_back(exe_path.c_str());
1344
1345 // Split the argument string into separate strings for each argument
1346 typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
1347 boost::char_separator<char> sep(" ");
1348 tokenizer tokens(args, sep);
1349 tokenizer::iterator token_iter;
1350
1351 for(token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter)
1352 {
1353 arglist.push_back(*token_iter);
1354 }
1355
1356 // create an argv vector for the child process
1357 char **fakeargv = new char*[arglist.size() + 1];
1358 int i;
1359 for(i=0; i < arglist.size(); i++)
1360 fakeargv[i] = const_cast<char*>(arglist[i].c_str());
1361
1362 fakeargv[i] = NULL;
1363
1364 pid_t id = vfork();
1365 if(id == 0)
1366 {
1367 // child
1368 execv(exe_path.c_str(), fakeargv);
1369
1370 // If we reach this point, the exec failed.
1371 // Use _exit() instead of exit() per the vfork man page.
1372 _exit(0);
1373 }
1374
1375 // parent
1376 delete[] fakeargv;
1377 sGatewayPID = id;
1378 }
1379#endif // LL_WINDOWS
1380 mDaemonHost = LLHost("127.0.0.1", 44124);
1381 }
1382 else
1383 {
1384 llinfos << exe_path << "not found." << llendl
1385 }
1386 }
1387 else
1388 {
1389 // We can connect to a client gateway running on another host. This is useful for testing.
1390 // To do this, launch the gateway on a nearby host like this:
1391 // vivox-gw.exe -p tcp -i 0.0.0.0:44124
1392 // and put that host's IP address here.
1393 mDaemonHost = LLHost("127.0.0.1", 44124);
1394 }
1395
1396 mUpdateTimer.start();
1397 mUpdateTimer.setTimerExpirySec(CONNECT_THROTTLE_SECONDS);
1398
1399 setState(stateDaemonLaunched);
1400
1401 // Dirty the states we'll need to sync with the daemon when it comes up.
1402 mPTTDirty = true;
1403 mSpeakerVolumeDirty = true;
1404 // These only need to be set if they're not default (i.e. empty string).
1405 mCaptureDeviceDirty = !mCaptureDevice.empty();
1406 mRenderDeviceDirty = !mRenderDevice.empty();
1407 }
1408 break;
1409
1410 case stateDaemonLaunched:
1411// llinfos << "Connecting to vivox daemon" << llendl;
1412 if(mUpdateTimer.hasExpired())
1413 {
1414 mUpdateTimer.setTimerExpirySec(CONNECT_THROTTLE_SECONDS);
1415
1416 if(!mSocket)
1417 {
1418 mSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP);
1419 }
1420
1421 mConnected = mSocket->blockingConnect(mDaemonHost);
1422 if(mConnected)
1423 {
1424 setState(stateConnecting);
1425 }
1426 else
1427 {
1428 // If the connect failed, the socket may have been put into a bad state. Delete it.
1429 closeSocket();
1430 }
1431 }
1432 break;
1433
1434 case stateConnecting:
1435 // Can't do this until we have the pump available.
1436 if(mPump)
1437 {
1438 // MBW -- Note to self: pumps and pipes examples in
1439 // indra/test/io.cpp
1440 // indra/test/llpipeutil.{cpp|h}
1441
1442 // Attach the pumps and pipes
1443
1444 LLPumpIO::chain_t readChain;
1445
1446 readChain.push_back(LLIOPipe::ptr_t(new LLIOSocketReader(mSocket)));
1447 readChain.push_back(LLIOPipe::ptr_t(new LLVivoxProtocolParser()));
1448
1449 mPump->addChain(readChain, NEVER_CHAIN_EXPIRY_SECS);
1450
1451 setState(stateIdle);
1452 }
1453
1454 break;
1455
1456 case stateIdle:
1457 // Initial devices query
1458 getCaptureDevicesSendMessage();
1459 getRenderDevicesSendMessage();
1460
1461 mLoginRetryCount = 0;
1462
1463 setState(stateConnectorStart);
1464
1465 break;
1466
1467 case stateConnectorStart:
1468 if(!mVoiceEnabled)
1469 {
1470 // We were never logged in. This will shut down the connector.
1471 setState(stateLoggedOut);
1472 }
1473 else if(!mAccountServerURI.empty())
1474 {
1475 connectorCreate();
1476 }
1477 else if(mTuningMode)
1478 {
1479 setState(stateMicTuningNoLogin);
1480 }
1481 break;
1482
1483 case stateConnectorStarting: // waiting for connector handle
1484 // connectorCreateResponse() will transition from here to stateConnectorStarted.
1485 break;
1486
1487 case stateConnectorStarted: // connector handle received
1488 if(!mVoiceEnabled)
1489 {
1490 // We were never logged in. This will shut down the connector.
1491 setState(stateLoggedOut);
1492 }
1493 else if(!mAccountName.empty())
1494 {
1495 LLViewerRegion *region = gAgent.getRegion();
1496
1497 if(region)
1498 {
1499 if ( region->getCapability("ProvisionVoiceAccountRequest") != "" )
1500 {
1501 if ( mAccountPassword.empty() )
1502 {
1503 requestVoiceAccountProvision();
1504 }
1505 setState(stateNeedsLogin);
1506 }
1507 }
1508 }
1509 break;
1510
1511 case stateMicTuningNoLogin:
1512 case stateMicTuningLoggedIn:
1513 {
1514 // Both of these behave essentially the same. The only difference is where the exit transition goes to.
1515 if(mTuningMode && mVoiceEnabled && !mSessionTerminateRequested)
1516 {
1517 if(!mTuningCaptureRunning)
1518 {
1519 // duration parameter is currently unused, per Mike S.
1520 tuningCaptureStartSendMessage(10000);
1521 }
1522
1523 if(mTuningMicVolumeDirty || mTuningSpeakerVolumeDirty || mCaptureDeviceDirty || mRenderDeviceDirty)
1524 {
1525 std::ostringstream stream;
1526
1527 if(mTuningMicVolumeDirty)
1528 {
1529 stream
1530 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetMicLevel.1\">"
1531 << "<Level>" << mTuningMicVolume << "</Level>"
1532 << "</Request>\n\n\n";
1533 }
1534
1535 if(mTuningSpeakerVolumeDirty)
1536 {
1537 stream
1538 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetSpeakerLevel.1\">"
1539 << "<Level>" << mTuningSpeakerVolume << "</Level>"
1540 << "</Request>\n\n\n";
1541 }
1542
1543 if(mCaptureDeviceDirty)
1544 {
1545 buildSetCaptureDevice(stream);
1546 }
1547
1548 if(mRenderDeviceDirty)
1549 {
1550 buildSetRenderDevice(stream);
1551 }
1552
1553 mTuningMicVolumeDirty = false;
1554 mTuningSpeakerVolumeDirty = false;
1555 mCaptureDeviceDirty = false;
1556 mRenderDeviceDirty = false;
1557
1558 if(!stream.str().empty())
1559 {
1560 writeString(stream.str());
1561 }
1562 }
1563 }
1564 else
1565 {
1566 // transition out of mic tuning
1567 if(mTuningCaptureRunning)
1568 {
1569 tuningCaptureStopSendMessage();
1570 }
1571
1572 if(getState() == stateMicTuningNoLogin)
1573 {
1574 setState(stateConnectorStart);
1575 }
1576 else
1577 {
1578 setState(stateNoChannel);
1579 }
1580 }
1581 }
1582 break;
1583
1584 case stateLoginRetry:
1585 if(mLoginRetryCount == 0)
1586 {
1587 // First retry -- display a message to the user
1588 notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGIN_RETRY);
1589 }
1590
1591 mLoginRetryCount++;
1592
1593 if(mLoginRetryCount > MAX_LOGIN_RETRIES)
1594 {
1595 llinfos << "too many login retries, giving up." << llendl;
1596 setState(stateLoginFailed);
1597 }
1598 else
1599 {
1600 llinfos << "will retry login in " << LOGIN_RETRY_SECONDS << " seconds." << llendl;
1601 mUpdateTimer.start();
1602 mUpdateTimer.setTimerExpirySec(LOGIN_RETRY_SECONDS);
1603 setState(stateLoginRetryWait);
1604 }
1605 break;
1606
1607 case stateLoginRetryWait:
1608 if(mUpdateTimer.hasExpired())
1609 {
1610 setState(stateNeedsLogin);
1611 }
1612 break;
1613
1614 case stateNeedsLogin:
1615 if(!mAccountPassword.empty())
1616 {
1617 setState(stateLoggingIn);
1618 loginSendMessage();
1619 }
1620 break;
1621
1622 case stateLoggingIn: // waiting for account handle
1623 // loginResponse() will transition from here to stateLoggedIn.
1624 break;
1625
1626 case stateLoggedIn: // account handle received
1627 // Initial kick-off of channel lookup logic
1628 parcelChanged();
1629
1630 notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGGED_IN);
1631
1632 // Set up the mute list observer if it hasn't been set up already.
1633 if((!sMuteListListener_listening) && (gMuteListp))
1634 {
1635 gMuteListp->addObserver(&mutelist_listener);
1636 sMuteListListener_listening = true;
1637 }
1638
1639 setState(stateNoChannel);
1640 break;
1641
1642 case stateNoChannel:
1643 if(mSessionTerminateRequested || !mVoiceEnabled)
1644 {
1645 // MBW -- XXX -- Is this the right way out of this state?
1646 setState(stateSessionTerminated);
1647 }
1648 else if(mTuningMode)
1649 {
1650 setState(stateMicTuningLoggedIn);
1651 }
1652 else if(!mNextSessionHandle.empty())
1653 {
1654 setState(stateSessionConnect);
1655 }
1656 else if(!mNextSessionURI.empty())
1657 {
1658 setState(stateSessionCreate);
1659 }
1660 break;
1661
1662 case stateSessionCreate:
1663 sessionCreateSendMessage();
1664 notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINING);
1665 setState(stateJoiningSession);
1666 break;
1667
1668 case stateSessionConnect:
1669 sessionConnectSendMessage();
1670 notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINING);
1671 setState(stateJoiningSession);
1672 break;
1673
1674 case stateJoiningSession: // waiting for session handle
1675 // sessionCreateResponse() will transition from here to stateSessionJoined.
1676 if(!mVoiceEnabled)
1677 {
1678 // User bailed out during connect -- jump straight to teardown.
1679 setState(stateSessionTerminated);
1680 }
1681 else if(mSessionTerminateRequested)
1682 {
1683 if(!mSessionHandle.empty())
1684 {
1685 // Only allow direct exits from this state in p2p calls (for cancelling an invite).
1686 // Terminating a half-connected session on other types of calls seems to break something in the vivox gateway.
1687 if(mSessionP2P)
1688 {
1689 sessionTerminateSendMessage();
1690 setState(stateSessionTerminated);
1691 }
1692 }
1693 }
1694 break;
1695
1696 case stateSessionJoined: // session handle received
1697 // MBW -- XXX -- It appears that I need to wait for BOTH the Session.Create response and the SessionStateChangeEvent with state 4
1698 // before continuing from this state. They can happen in either order, and if I don't wait for both, things can get stuck.
1699 // For now, the Session.Create response handler sets mSessionHandle and the SessionStateChangeEvent handler transitions to stateSessionJoined.
1700 // This is a cheap way to make sure both have happened before proceeding.
1701 if(!mSessionHandle.empty())
1702 {
1703 // Events that need to happen when a session is joined could go here.
1704 // Maybe send initial spatial data?
1705 notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINED);
1706
1707 // Dirty state that may need to be sync'ed with the daemon.
1708 mPTTDirty = true;
1709 mSpeakerVolumeDirty = true;
1710 mSpatialCoordsDirty = true;
1711
1712 setState(stateRunning);
1713
1714 // Start the throttle timer
1715 mUpdateTimer.start();
1716 mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS);
1717 }
1718 else if(!mVoiceEnabled)
1719 {
1720 // User bailed out during connect -- jump straight to teardown.
1721 setState(stateSessionTerminated);
1722 }
1723 else if(mSessionTerminateRequested)
1724 {
1725 // Only allow direct exits from this state in p2p calls (for cancelling an invite).
1726 // Terminating a half-connected session on other types of calls seems to break something in the vivox gateway.
1727 if(mSessionP2P)
1728 {
1729 sessionTerminateSendMessage();
1730 setState(stateSessionTerminated);
1731 }
1732 }
1733 break;
1734
1735 case stateRunning: // steady state
1736 // sessionTerminateSendMessage() will transition from here to stateLeavingSession
1737
1738 // Disabling voice or disconnect requested.
1739 if(!mVoiceEnabled || mSessionTerminateRequested)
1740 {
1741 sessionTerminateSendMessage();
1742 }
1743 else
1744 {
1745
1746 // Figure out whether the PTT state needs to change
1747 {
1748 bool newPTT;
1749 if(mUsePTT)
1750 {
1751 // If configured to use PTT, track the user state.
1752 newPTT = mUserPTTState;
1753 }
1754 else
1755 {
1756 // If not configured to use PTT, it should always be true (otherwise the user will be unable to speak).
1757 newPTT = true;
1758 }
1759
1760 if(mMuteMic)
1761 {
1762 // This always overrides any other PTT setting.
1763 newPTT = false;
1764 }
1765
1766 // Dirty if state changed.
1767 if(newPTT != mPTT)
1768 {
1769 mPTT = newPTT;
1770 mPTTDirty = true;
1771 }
1772 }
1773
1774 if(mNonSpatialChannel)
1775 {
1776 // When in a non-spatial channel, never send positional updates.
1777 mSpatialCoordsDirty = false;
1778 }
1779 else
1780 {
1781 // Do the calculation that enforces the listener<->speaker tether (and also updates the real camera position)
1782 enforceTether();
1783 }
1784
1785 // Send an update if the ptt state has changed (which shouldn't be able to happen that often -- the user can only click so fast)
1786 // or every 10hz, whichever is sooner.
1787 if(mVolumeDirty || mPTTDirty || mSpeakerVolumeDirty || mUpdateTimer.hasExpired())
1788 {
1789 mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS);
1790 sendPositionalUpdate();
1791 }
1792 }
1793 break;
1794
1795 case stateLeavingSession: // waiting for terminate session response
1796 // The handler for the Session.Terminate response will transition from here to stateSessionTerminated.
1797 break;
1798
1799 case stateSessionTerminated:
1800 // Always reset the terminate request flag when we get here.
1801 mSessionTerminateRequested = false;
1802
1803 notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL);
1804
1805 if(mVoiceEnabled)
1806 {
1807 // SPECIAL CASE: if going back to spatial but in a parcel with an empty URI, transfer the non-spatial flag now.
1808 // This fixes the case where you come out of a group chat in a parcel with voice disabled, and get stuck unable to rejoin spatial chat thereafter.
1809 if(mNextSessionSpatial && mNextSessionURI.empty())
1810 {
1811 mNonSpatialChannel = !mNextSessionSpatial;
1812 }
1813
1814 // Just leaving a channel, go back to stateNoChannel (the "logged in but have no channel" state).
1815 setState(stateNoChannel);
1816 }
1817 else
1818 {
1819 // Shutting down voice, continue with disconnecting.
1820 logout();
1821 }
1822
1823 break;
1824
1825 case stateLoggingOut: // waiting for logout response
1826 // The handler for the Account.Logout response will transition from here to stateLoggedOut.
1827 break;
1828 case stateLoggedOut: // logout response received
1829 // shut down the connector
1830 connectorShutdown();
1831 break;
1832
1833 case stateConnectorStopping: // waiting for connector stop
1834 // The handler for the Connector.InitiateShutdown response will transition from here to stateConnectorStopped.
1835 break;
1836
1837 case stateConnectorStopped: // connector stop received
1838 // Clean up and reset everything.
1839 closeSocket();
1840 removeAllParticipants();
1841 setState(stateDisabled);
1842 break;
1843
1844 case stateConnectorFailed:
1845 setState(stateConnectorFailedWaiting);
1846 break;
1847 case stateConnectorFailedWaiting:
1848 break;
1849
1850 case stateLoginFailed:
1851 setState(stateLoginFailedWaiting);
1852 break;
1853 case stateLoginFailedWaiting:
1854 // No way to recover from these. Yet.
1855 break;
1856
1857 case stateJoinSessionFailed:
1858 // Transition to error state. Send out any notifications here.
1859 llwarns << "stateJoinSessionFailed: (" << mVivoxErrorStatusCode << "): " << mVivoxErrorStatusString << llendl;
1860 notifyStatusObservers(LLVoiceClientStatusObserver::ERROR_UNKNOWN);
1861 setState(stateJoinSessionFailedWaiting);
1862 break;
1863
1864 case stateJoinSessionFailedWaiting:
1865 // Joining a channel failed, either due to a failed channel name -> sip url lookup or an error from the join message.
1866 // Region crossings may leave this state and try the join again.
1867 if(mSessionTerminateRequested)
1868 {
1869 setState(stateSessionTerminated);
1870 }
1871 break;
1872
1873 case stateJail:
1874 // We have given up. Do nothing.
1875 break;
1876 }
1877
1878 if(mParticipantMapChanged)
1879 {
1880 mParticipantMapChanged = false;
1881 notifyObservers();
1882 }
1883
1884}
1885
1886void LLVoiceClient::closeSocket(void)
1887{
1888 mSocket.reset();
1889 mConnected = false;
1890}
1891
1892void LLVoiceClient::loginSendMessage()
1893{
1894 std::ostringstream stream;
1895 stream
1896 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.Login.1\">"
1897 << "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>"
1898 << "<AccountName>" << mAccountName << "</AccountName>"
1899 << "<AccountPassword>" << mAccountPassword << "</AccountPassword>"
1900 << "<AudioSessionAnswerMode>VerifyAnswer</AudioSessionAnswerMode>"
1901 << "</Request>\n\n\n";
1902
1903 writeString(stream.str());
1904}
1905
1906void LLVoiceClient::logout()
1907{
1908 mAccountPassword = "";
1909 setState(stateLoggingOut);
1910 logoutSendMessage();
1911}
1912
1913void LLVoiceClient::logoutSendMessage()
1914{
1915 if(!mAccountHandle.empty())
1916 {
1917 std::ostringstream stream;
1918 stream
1919 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.Logout.1\">"
1920 << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
1921 << "</Request>"
1922 << "\n\n\n";
1923
1924 mAccountHandle.clear();
1925
1926 writeString(stream.str());
1927 }
1928}
1929
1930void LLVoiceClient::channelGetListSendMessage()
1931{
1932 std::ostringstream stream;
1933 stream
1934 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.ChannelGetList.1\">"
1935 << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
1936 << "</Request>\n\n\n";
1937
1938 writeString(stream.str());
1939}
1940
1941void LLVoiceClient::sessionCreateSendMessage()
1942{
1943 llinfos << "requesting join: " << mNextSessionURI << llendl;
1944
1945 mSessionURI = mNextSessionURI;
1946 mNonSpatialChannel = !mNextSessionSpatial;
1947 mSessionResetOnClose = mNextSessionResetOnClose;
1948 mNextSessionResetOnClose = false;
1949 if(mNextSessionNoReconnect)
1950 {
1951 // Clear the stashed URI so it can't reconnect
1952 mNextSessionURI.clear();
1953 }
1954 // Only p2p sessions are created with "no reconnect".
1955 mSessionP2P = mNextSessionNoReconnect;
1956
1957 std::ostringstream stream;
1958 stream
1959 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Create.1\">"
1960 << "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
1961 << "<URI>" << mSessionURI << "</URI>";
1962
1963 static const std::string allowed_chars =
1964 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
1965 "0123456789"
1966 "-._~";
1967
1968 if(!mNextSessionHash.empty())
1969 {
1970 stream
1971 << "<Password>" << LLURI::escape(mNextSessionHash, allowed_chars) << "</Password>"
1972 << "<PasswordHashAlgorithm>SHA1UserName</PasswordHashAlgorithm>";
1973 }
1974
1975 stream
1976 << "<Name>" << mChannelName << "</Name>"
1977 << "</Request>\n\n\n";
1978 writeString(stream.str());
1979}
1980
1981void LLVoiceClient::sessionConnectSendMessage()
1982{
1983 llinfos << "connecting to session handle: " << mNextSessionHandle << llendl;
1984
1985 mSessionHandle = mNextSessionHandle;
1986 mSessionURI = mNextP2PSessionURI;
1987 mNextSessionHandle.clear(); // never want to re-use these.
1988 mNextP2PSessionURI.clear();
1989 mNonSpatialChannel = !mNextSessionSpatial;
1990 mSessionResetOnClose = mNextSessionResetOnClose;
1991 mNextSessionResetOnClose = false;
1992 // Joining by session ID is only used to answer p2p invitations, so we know this is a p2p session.
1993 mSessionP2P = true;
1994
1995 std::ostringstream stream;
1996
1997 stream
1998 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Connect.1\">"
1999 << "<SessionHandle>" << mSessionHandle << "</SessionHandle>"
2000 << "<AudioMedia>default</AudioMedia>"
2001 << "</Request>\n\n\n";
2002 writeString(stream.str());
2003}
2004
2005void LLVoiceClient::sessionTerminate()
2006{
2007 mSessionTerminateRequested = true;
2008}
2009
2010void LLVoiceClient::sessionTerminateSendMessage()
2011{
2012 llinfos << "leaving session: " << mSessionURI << llendl;
2013
2014 switch(getState())
2015 {
2016 case stateNoChannel:
2017 // In this case, we want to pretend the join failed so our state machine doesn't get stuck.
2018 // Skip the join failed transition state so we don't send out error notifications.
2019 setState(stateJoinSessionFailedWaiting);
2020 break;
2021 case stateJoiningSession:
2022 case stateSessionJoined:
2023 case stateRunning:
2024 if(!mSessionHandle.empty())
2025 {
2026 sessionTerminateByHandle(mSessionHandle);
2027 setState(stateLeavingSession);
2028 }
2029 else
2030 {
2031 llwarns << "called with no session handle" << llendl;
2032 setState(stateSessionTerminated);
2033 }
2034 break;
2035 case stateJoinSessionFailed:
2036 case stateJoinSessionFailedWaiting:
2037 setState(stateSessionTerminated);
2038 break;
2039
2040 default:
2041 llwarns << "called from unknown state" << llendl;
2042 break;
2043 }
2044}
2045
2046void LLVoiceClient::sessionTerminateByHandle(std::string &sessionHandle)
2047{
2048 llinfos << "Sending Session.Terminate with handle " << sessionHandle << llendl;
2049
2050 std::ostringstream stream;
2051 stream
2052 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Terminate.1\">"
2053 << "<SessionHandle>" << sessionHandle << "</SessionHandle>"
2054 << "</Request>"
2055 << "\n\n\n";
2056
2057 writeString(stream.str());
2058}
2059
2060void LLVoiceClient::getCaptureDevicesSendMessage()
2061{
2062 std::ostringstream stream;
2063 stream
2064 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.GetCaptureDevices.1\">"
2065 << "</Request>\n\n\n";
2066
2067 writeString(stream.str());
2068}
2069
2070void LLVoiceClient::getRenderDevicesSendMessage()
2071{
2072 std::ostringstream stream;
2073 stream
2074 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.GetRenderDevices.1\">"
2075 << "</Request>\n\n\n";
2076
2077 writeString(stream.str());
2078}
2079
2080void LLVoiceClient::clearCaptureDevices()
2081{
2082 // MBW -- XXX -- do something here
2083 llinfos << "called" << llendl;
2084 mCaptureDevices.clear();
2085}
2086
2087void LLVoiceClient::addCaptureDevice(const std::string& name)
2088{
2089 // MBW -- XXX -- do something here
2090 llinfos << name << llendl;
2091
2092 mCaptureDevices.push_back(name);
2093}
2094
2095LLVoiceClient::deviceList *LLVoiceClient::getCaptureDevices()
2096{
2097 return &mCaptureDevices;
2098}
2099
2100void LLVoiceClient::setCaptureDevice(const std::string& name)
2101{
2102 if(name == "Default")
2103 {
2104 if(!mCaptureDevice.empty())
2105 {
2106 mCaptureDevice.clear();
2107 mCaptureDeviceDirty = true;
2108 }
2109 }
2110 else
2111 {
2112 if(mCaptureDevice != name)
2113 {
2114 mCaptureDevice = name;
2115 mCaptureDeviceDirty = true;
2116 }
2117 }
2118}
2119
2120void LLVoiceClient::clearRenderDevices()
2121{
2122 // MBW -- XXX -- do something here
2123 llinfos << "called" << llendl;
2124 mRenderDevices.clear();
2125}
2126
2127void LLVoiceClient::addRenderDevice(const std::string& name)
2128{
2129 // MBW -- XXX -- do something here
2130 llinfos << name << llendl;
2131 mRenderDevices.push_back(name);
2132}
2133
2134LLVoiceClient::deviceList *LLVoiceClient::getRenderDevices()
2135{
2136 return &mRenderDevices;
2137}
2138
2139void LLVoiceClient::setRenderDevice(const std::string& name)
2140{
2141 if(name == "Default")
2142 {
2143 if(!mRenderDevice.empty())
2144 {
2145 mRenderDevice.clear();
2146 mRenderDeviceDirty = true;
2147 }
2148 }
2149 else
2150 {
2151 if(mRenderDevice != name)
2152 {
2153 mRenderDevice = name;
2154 mRenderDeviceDirty = true;
2155 }
2156 }
2157
2158}
2159
2160void LLVoiceClient::tuningStart()
2161{
2162 mTuningMode = true;
2163 if(getState() >= stateNoChannel)
2164 {
2165 sessionTerminate();
2166 }
2167}
2168
2169void LLVoiceClient::tuningStop()
2170{
2171 mTuningMode = false;
2172}
2173
2174bool LLVoiceClient::inTuningMode()
2175{
2176 bool result = false;
2177 switch(getState())
2178 {
2179 case stateMicTuningNoLogin:
2180 case stateMicTuningLoggedIn:
2181 result = true;
2182 default:
2183 break;
2184 }
2185 return result;
2186}
2187
2188void LLVoiceClient::tuningRenderStartSendMessage(const std::string& name, bool loop)
2189{
2190 if(!inTuningMode())
2191 return;
2192
2193 mTuningAudioFile = name;
2194 std::ostringstream stream;
2195 stream
2196 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.RenderAudioStart.1\">"
2197 << "<SoundFilePath>" << mTuningAudioFile << "</SoundFilePath>"
2198 << "<Loop>" << (loop?"1":"0") << "</Loop>"
2199 << "</Request>\n\n\n";
2200
2201 writeString(stream.str());
2202}
2203
2204void LLVoiceClient::tuningRenderStopSendMessage()
2205{
2206 if(!inTuningMode())
2207 return;
2208
2209 std::ostringstream stream;
2210 stream
2211 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.RenderAudioStop.1\">"
2212 << "<SoundFilePath>" << mTuningAudioFile << "</SoundFilePath>"
2213 << "</Request>\n\n\n";
2214
2215 writeString(stream.str());
2216}
2217
2218void LLVoiceClient::tuningCaptureStartSendMessage(int duration)
2219{
2220 if(!inTuningMode())
2221 return;
2222
2223 std::ostringstream stream;
2224 stream
2225 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.CaptureAudioStart.1\">"
2226 << "<Duration>" << duration << "</Duration>"
2227 << "</Request>\n\n\n";
2228
2229 writeString(stream.str());
2230
2231 mTuningCaptureRunning = true;
2232}
2233
2234void LLVoiceClient::tuningCaptureStopSendMessage()
2235{
2236 if(!inTuningMode())
2237 return;
2238
2239 std::ostringstream stream;
2240 stream
2241 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.CaptureAudioStop.1\">"
2242 << "</Request>\n\n\n";
2243
2244 writeString(stream.str());
2245
2246 mTuningCaptureRunning = false;
2247}
2248
2249void LLVoiceClient::tuningSetMicVolume(float volume)
2250{
2251 int scaledVolume = ((int)(volume * 100.0f)) - 100;
2252 if(scaledVolume != mTuningMicVolume)
2253 {
2254 mTuningMicVolume = scaledVolume;
2255 mTuningMicVolumeDirty = true;
2256 }
2257}
2258
2259void LLVoiceClient::tuningSetSpeakerVolume(float volume)
2260{
2261 int scaledVolume = ((int)(volume * 100.0f)) - 100;
2262 if(scaledVolume != mTuningSpeakerVolume)
2263 {
2264 mTuningSpeakerVolume = ((int)(volume * 100.0f)) - 100;
2265 mTuningSpeakerVolumeDirty = true;
2266 }
2267}
2268
2269float LLVoiceClient::tuningGetEnergy(void)
2270{
2271 return mTuningEnergy;
2272}
2273
2274bool LLVoiceClient::deviceSettingsAvailable()
2275{
2276 bool result = true;
2277
2278 if(!mConnected)
2279 result = false;
2280
2281 if(mRenderDevices.empty())
2282 result = false;
2283
2284 return result;
2285}
2286
2287void LLVoiceClient::refreshDeviceLists(bool clearCurrentList)
2288{
2289 if(clearCurrentList)
2290 {
2291 clearCaptureDevices();
2292 clearRenderDevices();
2293 }
2294 getCaptureDevicesSendMessage();
2295 getRenderDevicesSendMessage();
2296}
2297
2298void LLVoiceClient::daemonDied()
2299{
2300 // The daemon died, so the connection is gone. Reset everything and start over.
2301 llwarns << "Connection to vivox daemon lost. Resetting state."<< llendl;
2302
2303 closeSocket();
2304 removeAllParticipants();
2305
2306 // Try to relaunch the daemon
2307 setState(stateDisabled);
2308}
2309
2310void LLVoiceClient::giveUp()
2311{
2312 // All has failed. Clean up and stop trying.
2313 closeSocket();
2314 removeAllParticipants();
2315
2316 setState(stateJail);
2317}
2318
2319void LLVoiceClient::sendPositionalUpdate(void)
2320{
2321 std::ostringstream stream;
2322
2323 if(mSpatialCoordsDirty)
2324 {
2325 LLVector3 l, u, a;
2326
2327 // Always send both speaker and listener positions together.
2328 stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Set3DPosition.1\">"
2329 << "<SessionHandle>" << mSessionHandle << "</SessionHandle>";
2330
2331 stream << "<SpeakerPosition>";
2332
2333 l = mAvatarRot.getLeftRow();
2334 u = mAvatarRot.getUpRow();
2335 a = mAvatarRot.getFwdRow();
2336
2337// llinfos << "Sending speaker position " << mSpeakerPosition << llendl;
2338
2339 stream
2340 << "<Position>"
2341 << "<X>" << mAvatarPosition[VX] << "</X>"
2342 << "<Y>" << mAvatarPosition[VZ] << "</Y>"
2343 << "<Z>" << mAvatarPosition[VY] << "</Z>"
2344 << "</Position>"
2345 << "<Velocity>"
2346 << "<X>" << mAvatarVelocity[VX] << "</X>"
2347 << "<Y>" << mAvatarVelocity[VZ] << "</Y>"
2348 << "<Z>" << mAvatarVelocity[VY] << "</Z>"
2349 << "</Velocity>"
2350 << "<AtOrientation>"
2351 << "<X>" << l.mV[VX] << "</X>"
2352 << "<Y>" << u.mV[VX] << "</Y>"
2353 << "<Z>" << a.mV[VX] << "</Z>"
2354 << "</AtOrientation>"
2355 << "<UpOrientation>"
2356 << "<X>" << l.mV[VZ] << "</X>"
2357 << "<Y>" << u.mV[VY] << "</Y>"
2358 << "<Z>" << a.mV[VZ] << "</Z>"
2359 << "</UpOrientation>"
2360 << "<LeftOrientation>"
2361 << "<X>" << l.mV [VY] << "</X>"
2362 << "<Y>" << u.mV [VZ] << "</Y>"
2363 << "<Z>" << a.mV [VY] << "</Z>"
2364 << "</LeftOrientation>";
2365
2366 stream << "</SpeakerPosition>";
2367
2368 stream << "<ListenerPosition>";
2369
2370 LLVector3d earPosition;
2371 LLVector3 earVelocity;
2372 LLMatrix3 earRot;
2373
2374 switch(mEarLocation)
2375 {
2376 case earLocCamera:
2377 default:
2378 earPosition = mCameraPosition;
2379 earVelocity = mCameraVelocity;
2380 earRot = mCameraRot;
2381 break;
2382
2383 case earLocAvatar:
2384 earPosition = mAvatarPosition;
2385 earVelocity = mAvatarVelocity;
2386 earRot = mAvatarRot;
2387 break;
2388
2389 case earLocMixed:
2390 earPosition = mAvatarPosition;
2391 earVelocity = mAvatarVelocity;
2392 earRot = mCameraRot;
2393 break;
2394 }
2395
2396 l = earRot.getLeftRow();
2397 u = earRot.getUpRow();
2398 a = earRot.getFwdRow();
2399
2400// llinfos << "Sending listener position " << mListenerPosition << llendl;
2401
2402 stream
2403 << "<Position>"
2404 << "<X>" << earPosition[VX] << "</X>"
2405 << "<Y>" << earPosition[VZ] << "</Y>"
2406 << "<Z>" << earPosition[VY] << "</Z>"
2407 << "</Position>"
2408 << "<Velocity>"
2409 << "<X>" << earVelocity[VX] << "</X>"
2410 << "<Y>" << earVelocity[VZ] << "</Y>"
2411 << "<Z>" << earVelocity[VY] << "</Z>"
2412 << "</Velocity>"
2413 << "<AtOrientation>"
2414 << "<X>" << l.mV[VX] << "</X>"
2415 << "<Y>" << u.mV[VX] << "</Y>"
2416 << "<Z>" << a.mV[VX] << "</Z>"
2417 << "</AtOrientation>"
2418 << "<UpOrientation>"
2419 << "<X>" << l.mV[VZ] << "</X>"
2420 << "<Y>" << u.mV[VY] << "</Y>"
2421 << "<Z>" << a.mV[VZ] << "</Z>"
2422 << "</UpOrientation>"
2423 << "<LeftOrientation>"
2424 << "<X>" << l.mV [VY] << "</X>"
2425 << "<Y>" << u.mV [VZ] << "</Y>"
2426 << "<Z>" << a.mV [VY] << "</Z>"
2427 << "</LeftOrientation>";
2428
2429 stream << "</ListenerPosition>";
2430
2431 stream << "</Request>\n\n\n";
2432 }
2433
2434 if(mPTTDirty)
2435 {
2436 // Send a local mute command.
2437 // NOTE that the state of "PTT" is the inverse of "local mute".
2438 // (i.e. when PTT is true, we send a mute command with "false", and vice versa)
2439
2440// llinfos << "Sending MuteLocalMic command with parameter " << (mPTT?"false":"true") << llendl;
2441
2442 stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.MuteLocalMic.1\">"
2443 << "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>"
2444 << "<Value>" << (mPTT?"false":"true") << "</Value>"
2445 << "</Request>\n\n\n";
2446
2447 }
2448
2449 if(mVolumeDirty)
2450 {
2451 participantMap::iterator iter = mParticipantMap.begin();
2452
2453 for(; iter != mParticipantMap.end(); iter++)
2454 {
2455 participantState *p = iter->second;
2456
2457 if(p->mVolumeDirty)
2458 {
2459 int volume = p->mOnMuteList?0:p->mUserVolume;
2460
2461 llinfos << "Setting volume for avatar " << p->mAvatarID << " to " << volume << llendl;
2462
2463 // Send a mute/unumte command for the user (actually "volume for me").
2464 stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.SetParticipantVolumeForMe.1\">"
2465 << "<SessionHandle>" << mSessionHandle << "</SessionHandle>"
2466 << "<ParticipantURI>" << p->mURI << "</ParticipantURI>"
2467 << "<Volume>" << volume << "</Volume>"
2468 << "</Request>\n\n\n";
2469
2470 p->mVolumeDirty = false;
2471 }
2472 }
2473 }
2474
2475 if(mSpeakerMuteDirty)
2476 {
2477 const char *muteval = ((mSpeakerVolume == -100)?"true":"false");
2478 llinfos << "Setting speaker mute to " << muteval << llendl;
2479
2480 stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.MuteLocalSpeaker.1\">"
2481 << "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>"
2482 << "<Value>" << muteval << "</Value>"
2483 << "</Request>\n\n\n";
2484 }
2485
2486 if(mSpeakerVolumeDirty)
2487 {
2488 llinfos << "Setting speaker volume to " << mSpeakerVolume << llendl;
2489
2490 stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.SetLocalSpeakerVolume.1\">"
2491 << "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>"
2492 << "<Value>" << mSpeakerVolume << "</Value>"
2493 << "</Request>\n\n\n";
2494 }
2495
2496 if(mMicVolumeDirty)
2497 {
2498 llinfos << "Setting mic volume to " << mMicVolume << llendl;
2499
2500 stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.SetLocalMicVolume.1\">"
2501 << "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>"
2502 << "<Value>" << mMicVolume << "</Value>"
2503 << "</Request>\n\n\n";
2504 }
2505
2506
2507 // MBW -- XXX -- Maybe check to make sure the capture/render devices are in the current list here?
2508 if(mCaptureDeviceDirty)
2509 {
2510 buildSetCaptureDevice(stream);
2511 }
2512
2513 if(mRenderDeviceDirty)
2514 {
2515 buildSetRenderDevice(stream);
2516 }
2517
2518 mSpatialCoordsDirty = false;
2519 mPTTDirty = false;
2520 mVolumeDirty = false;
2521 mSpeakerVolumeDirty = false;
2522 mMicVolumeDirty = false;
2523 mSpeakerMuteDirty = false;
2524 mCaptureDeviceDirty = false;
2525 mRenderDeviceDirty = false;
2526
2527 if(!stream.str().empty())
2528 {
2529 writeString(stream.str());
2530 }
2531}
2532
2533void LLVoiceClient::buildSetCaptureDevice(std::ostringstream &stream)
2534{
2535 llinfos << "Setting input device = \"" << mCaptureDevice << "\"" << llendl;
2536
2537 stream
2538 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetCaptureDevice.1\">"
2539 << "<CaptureDeviceSpecifier>" << mCaptureDevice << "</CaptureDeviceSpecifier>"
2540 << "</Request>"
2541 << "\n\n\n";
2542}
2543
2544void LLVoiceClient::buildSetRenderDevice(std::ostringstream &stream)
2545{
2546 llinfos << "Setting output device = \"" << mRenderDevice << "\"" << llendl;
2547
2548 stream
2549 << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetRenderDevice.1\">"
2550 << "<RenderDeviceSpecifier>" << mRenderDevice << "</RenderDeviceSpecifier>"
2551 << "</Request>"
2552 << "\n\n\n";
2553}
2554
2555/////////////////////////////
2556// Response/Event handlers
2557
2558void LLVoiceClient::connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle)
2559{
2560 if(statusCode != 0)
2561 {
2562 llwarns << "Connector.Create response failure: " << statusString << llendl;
2563 setState(stateConnectorFailed);
2564 }
2565 else
2566 {
2567 // Connector created, move forward.
2568 mConnectorHandle = connectorHandle;
2569 if(getState() == stateConnectorStarting)
2570 {
2571 setState(stateConnectorStarted);
2572 }
2573 }
2574}
2575
2576void LLVoiceClient::loginResponse(int statusCode, std::string &statusString, std::string &accountHandle)
2577{
2578 llinfos << "Account.Login response (" << statusCode << "): " << statusString << llendl;
2579
2580 // Status code of 20200 means "bad password". We may want to special-case that at some point.
2581
2582 if ( statusCode == 401 )
2583 {
2584 // Login failure which is probably caused by the delay after a user's password being updated.
2585 llinfos << "Account.Login response failure (" << statusCode << "): " << statusString << llendl;
2586 setState(stateLoginRetry);
2587 }
2588 else if(statusCode != 0)
2589 {
2590 llwarns << "Account.Login response failure (" << statusCode << "): " << statusString << llendl;
2591 setState(stateLoginFailed);
2592 }
2593 else
2594 {
2595 // Login succeeded, move forward.
2596 mAccountHandle = accountHandle;
2597 // MBW -- XXX -- This needs to wait until the LoginStateChangeEvent is received.
2598// if(getState() == stateLoggingIn)
2599// {
2600// setState(stateLoggedIn);
2601// }
2602 }
2603}
2604
2605void LLVoiceClient::channelGetListResponse(int statusCode, std::string &statusString)
2606{
2607 if(statusCode != 0)
2608 {
2609 llwarns << "Account.ChannelGetList response failure: " << statusString << llendl;
2610 switchChannel();
2611 }
2612 else
2613 {
2614 // Got the channel list, try to do a lookup.
2615 std::string uri = findChannelURI(mChannelName);
2616 if(uri.empty())
2617 {
2618 // Lookup failed, can't join a channel for this area.
2619 llinfos << "failed to map channel name: " << mChannelName << llendl;
2620 }
2621 else
2622 {
2623 // We have a sip URL for this area.
2624 llinfos << "mapped channel " << mChannelName << " to URI "<< uri << llendl;
2625 }
2626
2627 // switchChannel with an empty uri string will do the right thing (leave channel and not rejoin)
2628 switchChannel(uri);
2629 }
2630}
2631
2632void LLVoiceClient::sessionCreateResponse(int statusCode, std::string &statusString, std::string &sessionHandle)
2633{
2634 if(statusCode != 0)
2635 {
2636 llwarns << "Session.Create response failure (" << statusCode << "): " << statusString << llendl;
2637// if(statusCode == 1015)
2638// {
2639// if(getState() == stateJoiningSession)
2640// {
2641// // this happened during a real join. Going to sessionTerminated should cause a retry in appropriate cases.
2642// llwarns << "session handle \"" << sessionHandle << "\", mSessionStateEventHandle \"" << mSessionStateEventHandle << "\""<< llendl;
2643// if(!sessionHandle.empty())
2644// {
2645// // This session is bad. Terminate it.
2646// mSessionHandle = sessionHandle;
2647// sessionTerminateByHandle(sessionHandle);
2648// setState(stateLeavingSession);
2649// }
2650// else if(!mSessionStateEventHandle.empty())
2651// {
2652// mSessionHandle = mSessionStateEventHandle;
2653// sessionTerminateByHandle(mSessionStateEventHandle);
2654// setState(stateLeavingSession);
2655// }
2656// else
2657// {
2658// setState(stateSessionTerminated);
2659// }
2660// }
2661// else
2662// {
2663// // We didn't think we were in the middle of a join. Don't change state.
2664// llwarns << "Not in stateJoiningSession, ignoring" << llendl;
2665// }
2666// }
2667// else
2668 {
2669 mVivoxErrorStatusCode = statusCode;
2670 mVivoxErrorStatusString = statusString;
2671 setState(stateJoinSessionFailed);
2672 }
2673 }
2674 else
2675 {
2676 llinfos << "Session.Create response received (success), session handle is " << sessionHandle << llendl;
2677 if(getState() == stateJoiningSession)
2678 {
2679 // This is also grabbed in the SessionStateChangeEvent handler, but it might be useful to have it early...
2680 mSessionHandle = sessionHandle;
2681 }
2682 else
2683 {
2684 // We should never get a session.create response in any state except stateJoiningSession. Things are out of sync. Kill this session.
2685 sessionTerminateByHandle(sessionHandle);
2686 }
2687 }
2688}
2689
2690void LLVoiceClient::sessionConnectResponse(int statusCode, std::string &statusString)
2691{
2692 if(statusCode != 0)
2693 {
2694 llwarns << "Session.Connect response failure (" << statusCode << "): " << statusString << llendl;
2695// if(statusCode == 1015)
2696// {
2697// llwarns << "terminating existing session" << llendl;
2698// sessionTerminate();
2699// }
2700// else
2701 {
2702 mVivoxErrorStatusCode = statusCode;
2703 mVivoxErrorStatusString = statusString;
2704 setState(stateJoinSessionFailed);
2705 }
2706 }
2707 else
2708 {
2709 llinfos << "Session.Connect response received (success)" << llendl;
2710 }
2711}
2712
2713void LLVoiceClient::sessionTerminateResponse(int statusCode, std::string &statusString)
2714{
2715 if(statusCode != 0)
2716 {
2717 llwarns << "Session.Terminate response failure: (" << statusCode << "): " << statusString << llendl;
2718 if(getState() == stateLeavingSession)
2719 {
2720 // This is probably "(404): Server reporting Failure. Not a member of this conference."
2721 // Do this so we don't get stuck.
2722 setState(stateSessionTerminated);
2723 }
2724 }
2725
2726}
2727
2728void LLVoiceClient::logoutResponse(int statusCode, std::string &statusString)
2729{
2730 if(statusCode != 0)
2731 {
2732 llwarns << "Account.Logout response failure: " << statusString << llendl;
2733 // MBW -- XXX -- Should this ever fail? do we care if it does?
2734 }
2735
2736 if(getState() == stateLoggingOut)
2737 {
2738 setState(stateLoggedOut);
2739 }
2740}
2741
2742void LLVoiceClient::connectorShutdownResponse(int statusCode, std::string &statusString)
2743{
2744 if(statusCode != 0)
2745 {
2746 llwarns << "Connector.InitiateShutdown response failure: " << statusString << llendl;
2747 // MBW -- XXX -- Should this ever fail? do we care if it does?
2748 }
2749
2750 mConnected = false;
2751
2752 if(getState() == stateConnectorStopping)
2753 {
2754 setState(stateConnectorStopped);
2755 }
2756}
2757
2758void LLVoiceClient::sessionStateChangeEvent(
2759 std::string &uriString,
2760 int statusCode,
2761 std::string &statusString,
2762 std::string &sessionHandle,
2763 int state,
2764 bool isChannel,
2765 std::string &nameString)
2766{
2767 switch(state)
2768 {
2769 case 4: // I see this when joining the session
2770 llinfos << "joined session " << uriString << ", name " << nameString << " handle " << mNextSessionHandle << llendl;
2771
2772 // Session create succeeded, move forward.
2773 mSessionStateEventHandle = sessionHandle;
2774 mSessionStateEventURI = uriString;
2775 if(sessionHandle == mSessionHandle)
2776 {
2777 // This is the session we're joining.
2778 if(getState() == stateJoiningSession)
2779 {
2780 setState(stateSessionJoined);
2781 //RN: the uriString being returned by vivox here is actually your account uri, not the channel
2782 // you are attempting to join, so ignore it
2783 //llinfos << "received URI " << uriString << "(previously " << mSessionURI << ")" << llendl;
2784 //mSessionURI = uriString;
2785 }
2786 }
2787 else if(sessionHandle == mNextSessionHandle)
2788 {
2789// llinfos << "received URI " << uriString << ", name " << nameString << " for next session (handle " << mNextSessionHandle << ")" << llendl;
2790 }
2791 else
2792 {
2793 llwarns << "joining unknown session handle " << sessionHandle << ", URI " << uriString << ", name " << nameString << llendl;
2794 // MBW -- XXX -- Should we send a Session.Terminate here?
2795 }
2796
2797 break;
2798 case 5: // I see this when leaving the session
2799 llinfos << "left session " << uriString << ", name " << nameString << " handle " << mNextSessionHandle << llendl;
2800
2801 // Set the session handle to the empty string. If we get back to stateJoiningSession, we'll want to wait for the new session handle.
2802 if(sessionHandle == mSessionHandle)
2803 {
2804 // MBW -- XXX -- I think this is no longer necessary, now that we've got mNextSessionURI/mNextSessionHandle
2805 // mSessionURI.clear();
2806 // clear the session handle here just for sanity.
2807 mSessionHandle.clear();
2808 if(mSessionResetOnClose)
2809 {
2810 mSessionResetOnClose = false;
2811 mNonSpatialChannel = false;
2812 mNextSessionSpatial = true;
2813 parcelChanged();
2814 }
2815
2816 removeAllParticipants();
2817
2818 switch(getState())
2819 {
2820 case stateJoiningSession:
2821 case stateSessionJoined:
2822 case stateRunning:
2823 case stateLeavingSession:
2824 case stateJoinSessionFailed:
2825 case stateJoinSessionFailedWaiting:
2826 // normal transition
2827 llinfos << "left session " << sessionHandle << "in state " << state2string(getState()) << llendl;
2828 setState(stateSessionTerminated);
2829 break;
2830
2831 case stateSessionTerminated:
2832 // this will happen sometimes -- there are cases where we send the terminate and then go straight to this state.
2833 llwarns << "left session " << sessionHandle << "in state " << state2string(getState()) << llendl;
2834 break;
2835
2836 default:
2837 llwarns << "unexpected SessionStateChangeEvent (left session) in state " << state2string(getState()) << llendl;
2838 setState(stateSessionTerminated);
2839 break;
2840 }
2841
2842 // store status values for later notification of observers
2843 mVivoxErrorStatusCode = statusCode;
2844 mVivoxErrorStatusString = statusString;
2845 }
2846 else
2847 {
2848 llinfos << "leaving unknown session handle " << sessionHandle << ", URI " << uriString << ", name " << nameString << llendl;
2849 }
2850
2851 mSessionStateEventHandle.clear();
2852 mSessionStateEventURI.clear();
2853 break;
2854 default:
2855 llwarns << "unknown state: " << state << llendl;
2856 break;
2857 }
2858}
2859
2860void LLVoiceClient::loginStateChangeEvent(
2861 std::string &accountHandle,
2862 int statusCode,
2863 std::string &statusString,
2864 int state)
2865{
2866 llinfos << "state is " << state << llendl;
2867 /*
2868 According to Mike S., status codes for this event are:
2869 login_state_logged_out=0,
2870 login_state_logged_in = 1,
2871 login_state_logging_in = 2,
2872 login_state_logging_out = 3,
2873 login_state_resetting = 4,
2874 login_state_error=100
2875 */
2876
2877 switch(state)
2878 {
2879 case 1:
2880 if(getState() == stateLoggingIn)
2881 {
2882 setState(stateLoggedIn);
2883 }
2884 break;
2885
2886 default:
2887// llwarns << "unknown state: " << state << llendl;
2888 break;
2889 }
2890}
2891
2892void LLVoiceClient::sessionNewEvent(
2893 std::string &accountHandle,
2894 std::string &eventSessionHandle,
2895 int state,
2896 std::string &nameString,
2897 std::string &uriString)
2898{
2899// llinfos << "state is " << state << llendl;
2900
2901 switch(state)
2902 {
2903 case 0:
2904 {
2905 llinfos << "session handle = " << eventSessionHandle << ", name = " << nameString << ", uri = " << uriString << llendl;
2906
2907 LLUUID caller_id;
2908 if(IDFromName(nameString, caller_id))
2909 {
2910 gIMMgr->inviteToSession(LLIMMgr::computeSessionID(IM_SESSION_P2P_INVITE, caller_id),
2911 LLString::null,
2912 caller_id,
2913 LLString::null,
2914 IM_SESSION_P2P_INVITE,
2915 eventSessionHandle);
2916 }
2917 else
2918 {
2919 llwarns << "Could not generate caller id from uri " << uriString << llendl;
2920 }
2921 }
2922 break;
2923
2924 default:
2925 llwarns << "unknown state: " << state << llendl;
2926 break;
2927 }
2928}
2929
2930void LLVoiceClient::participantStateChangeEvent(
2931 std::string &uriString,
2932 int statusCode,
2933 std::string &statusString,
2934 int state,
2935 std::string &nameString,
2936 std::string &displayNameString,
2937 int participantType)
2938{
2939 participantState *participant = NULL;
2940 llinfos << "state is " << state << llendl;
2941
2942 switch(state)
2943 {
2944 case 7: // I see this when a participant joins
2945 participant = addParticipant(uriString);
2946 if(participant)
2947 {
2948 participant->mName = nameString;
2949 llinfos << "added participant \"" << participant->mName
2950 << "\" (" << participant->mAvatarID << ")"<< llendl;
2951 }
2952 break;
2953 case 9: // I see this when a participant leaves
2954 participant = findParticipant(uriString);
2955 if(participant)
2956 {
2957 removeParticipant(participant);
2958 }
2959 break;
2960 default:
2961// llwarns << "unknown state: " << state << llendl;
2962 break;
2963 }
2964}
2965
2966void LLVoiceClient::participantPropertiesEvent(
2967 std::string &uriString,
2968 int statusCode,
2969 std::string &statusString,
2970 bool isLocallyMuted,
2971 bool isModeratorMuted,
2972 bool isSpeaking,
2973 int volume,
2974 F32 energy)
2975{
2976 participantState *participant = findParticipant(uriString);
2977 if(participant)
2978 {
2979 participant->mPTT = !isLocallyMuted;
2980 participant->mIsSpeaking = isSpeaking;
2981 if (isSpeaking)
2982 {
2983 participant->mSpeakingTimeout.reset();
2984 }
2985 participant->mPower = energy;
2986 participant->mVolume = volume;
2987 }
2988 else
2989 {
2990 llwarns << "unknown participant: " << uriString << llendl;
2991 }
2992}
2993
2994void LLVoiceClient::auxAudioPropertiesEvent(F32 energy)
2995{
2996// llinfos << "got energy " << energy << llendl;
2997 mTuningEnergy = energy;
2998}
2999
3000void LLVoiceClient::muteListChanged()
3001{
3002 // The user's mute list has been updated. Go through the current participant list and sync it with the mute list.
3003
3004 participantMap::iterator iter = mParticipantMap.begin();
3005
3006 for(; iter != mParticipantMap.end(); iter++)
3007 {
3008 participantState *p = iter->second;
3009
3010 // Check to see if this participant is on the mute list already
3011 updateMuteState(p);
3012 }
3013}
3014
3015/////////////////////////////
3016// Managing list of participants
3017LLVoiceClient::participantState::participantState(const std::string &uri) :
3018 mURI(uri), mPTT(false), mIsSpeaking(false), mPower(0.0), mServiceType(serviceTypeUnknown),
3019 mOnMuteList(false), mUserVolume(100), mVolumeDirty(false), mAvatarIDValid(false)
3020{
3021}
3022
3023LLVoiceClient::participantState *LLVoiceClient::addParticipant(const std::string &uri)
3024{
3025 participantState *result = NULL;
3026
3027 participantMap::iterator iter = mParticipantMap.find(uri);
3028
3029 if(iter != mParticipantMap.end())
3030 {
3031 // Found a matching participant already in the map.
3032 result = iter->second;
3033 }
3034
3035 if(!result)
3036 {
3037 // participant isn't already in one list or the other.
3038 result = new participantState(uri);
3039 mParticipantMap.insert(participantMap::value_type(uri, result));
3040 mParticipantMapChanged = true;
3041
3042 // Try to do a reverse transform on the URI to get the GUID back.
3043 {
3044 LLUUID id;
3045 if(IDFromName(uri, id))
3046 {
3047 result->mAvatarIDValid = true;
3048 result->mAvatarID = id;
3049
3050 updateMuteState(result);
3051 }
3052 }
3053
3054 llinfos << "participant \"" << result->mURI << "\" added." << llendl;
3055 }
3056
3057 return result;
3058}
3059
3060void LLVoiceClient::updateMuteState(participantState *p)
3061{
3062 if(p->mAvatarIDValid)
3063 {
3064 bool isMuted = gMuteListp->isMuted(p->mAvatarID, LLMute::flagVoiceChat);
3065 if(p->mOnMuteList != isMuted)
3066 {
3067 p->mOnMuteList = isMuted;
3068 p->mVolumeDirty = true;
3069 mVolumeDirty = true;
3070 }
3071 }
3072}
3073
3074void LLVoiceClient::removeParticipant(LLVoiceClient::participantState *participant)
3075{
3076 if(participant)
3077 {
3078 participantMap::iterator iter = mParticipantMap.find(participant->mURI);
3079
3080 llinfos << "participant \"" << participant->mURI << "\" (" << participant->mAvatarID << ") removed." << llendl;
3081
3082 mParticipantMap.erase(iter);
3083 delete participant;
3084 mParticipantMapChanged = true;
3085 }
3086}
3087
3088void LLVoiceClient::removeAllParticipants()
3089{
3090 llinfos << "called" << llendl;
3091
3092 while(!mParticipantMap.empty())
3093 {
3094 removeParticipant(mParticipantMap.begin()->second);
3095 }
3096}
3097
3098LLVoiceClient::participantMap *LLVoiceClient::getParticipantList(void)
3099{
3100 return &mParticipantMap;
3101}
3102
3103
3104LLVoiceClient::participantState *LLVoiceClient::findParticipant(const std::string &uri)
3105{
3106 participantState *result = NULL;
3107
3108 // Don't find any participants if we're not connected. This is so that we don't continue to get stale data
3109 // after the daemon dies.
3110 if(mConnected)
3111 {
3112 participantMap::iterator iter = mParticipantMap.find(uri);
3113
3114 if(iter != mParticipantMap.end())
3115 {
3116 result = iter->second;
3117 }
3118 }
3119
3120 return result;
3121}
3122
3123
3124LLVoiceClient::participantState *LLVoiceClient::findParticipantByAvatar(LLVOAvatar *avatar)
3125{
3126 participantState * result = NULL;
3127
3128 // You'd think this would work, but it doesn't...
3129// std::string uri = sipURIFromAvatar(avatar);
3130
3131 // Currently, the URI is just the account name.
3132 std::string loginName = nameFromAvatar(avatar);
3133 result = findParticipant(loginName);
3134
3135 if(result != NULL)
3136 {
3137 if(!result->mAvatarIDValid)
3138 {
3139 result->mAvatarID = avatar->getID();
3140 result->mAvatarIDValid = true;
3141
3142 // We just figured out the avatar ID, so the participant list has "changed" from the perspective of anyone who uses that to identify participants.
3143 mParticipantMapChanged = true;
3144
3145 updateMuteState(result);
3146 }
3147
3148
3149 }
3150
3151 return result;
3152}
3153
3154LLVoiceClient::participantState* LLVoiceClient::findParticipantByID(const LLUUID& id)
3155{
3156 participantState * result = NULL;
3157
3158 // Currently, the URI is just the account name.
3159 std::string loginName = nameFromID(id);
3160 result = findParticipant(loginName);
3161
3162 return result;
3163}
3164
3165
3166void LLVoiceClient::clearChannelMap(void)
3167{
3168 mChannelMap.clear();
3169}
3170
3171void LLVoiceClient::addChannelMapEntry(std::string &name, std::string &uri)
3172{
3173// llinfos << "Adding channel name mapping: " << name << " -> " << uri << llendl;
3174 mChannelMap.insert(channelMap::value_type(name, uri));
3175}
3176
3177std::string LLVoiceClient::findChannelURI(std::string &name)
3178{
3179 std::string result;
3180
3181 channelMap::iterator iter = mChannelMap.find(name);
3182
3183 if(iter != mChannelMap.end())
3184 {
3185 result = iter->second;
3186 }
3187
3188 return result;
3189}
3190
3191void LLVoiceClient::parcelChanged()
3192{
3193 if(getState() >= stateLoggedIn)
3194 {
3195 // If the user is logged in, start a channel lookup.
3196 llinfos << "sending ParcelVoiceInfoRequest (" << mCurrentRegionName << ", " << mCurrentParcelLocalID << ")" << llendl;
3197
3198 std::string url = gAgent.getRegion()->getCapability("ParcelVoiceInfoRequest");
3199 LLSD data;
3200 data["method"] = "call";
3201 LLHTTPClient::post(
3202 url,
3203 data,
3204 new LLVoiceClientCapResponder);
3205 }
3206 else
3207 {
3208 // The transition to stateLoggedIn needs to kick this off again.
3209 llinfos << "not logged in yet, deferring" << llendl;
3210 }
3211}
3212
3213void LLVoiceClient::switchChannel(
3214 std::string uri,
3215 bool spatial,
3216 bool noReconnect,
3217 std::string hash)
3218{
3219 bool needsSwitch = false;
3220
3221 llinfos << "called in state " << state2string(getState()) << " with uri \"" << uri << "\"" << llendl;
3222
3223 switch(getState())
3224 {
3225 case stateJoinSessionFailed:
3226 case stateJoinSessionFailedWaiting:
3227 case stateNoChannel:
3228 // Always switch to the new URI from these states.
3229 needsSwitch = true;
3230 break;
3231
3232 default:
3233 if(mSessionTerminateRequested)
3234 {
3235 // If a terminate has been requested, we need to compare against where the URI we're already headed to.
3236 if(mNextSessionURI != uri)
3237 needsSwitch = true;
3238 }
3239 else
3240 {
3241 // Otherwise, compare against the URI we're in now.
3242 if(mSessionURI != uri)
3243 needsSwitch = true;
3244 }
3245 break;
3246 }
3247
3248 if(needsSwitch)
3249 {
3250 mNextSessionURI = uri;
3251 mNextSessionHash = hash;
3252 mNextSessionHandle.clear();
3253 mNextP2PSessionURI.clear();
3254 mNextSessionSpatial = spatial;
3255 mNextSessionNoReconnect = noReconnect;
3256
3257 if(uri.empty())
3258 {
3259 // Leave any channel we may be in
3260 llinfos << "leaving channel" << llendl;
3261 }
3262 else
3263 {
3264 llinfos << "switching to channel " << uri << llendl;
3265 }
3266
3267 if(getState() <= stateNoChannel)
3268 {
3269 // We're already set up to join a channel, just needed to fill in the session URI
3270 }
3271 else
3272 {
3273 // State machine will come around and rejoin if uri/handle is not empty.
3274 sessionTerminate();
3275 }
3276 }
3277}
3278
3279void LLVoiceClient::joinSession(std::string handle, std::string uri)
3280{
3281 mNextSessionURI.clear();
3282 mNextSessionHash.clear();
3283 mNextP2PSessionURI = uri;
3284 mNextSessionHandle = handle;
3285 mNextSessionSpatial = false;
3286 mNextSessionNoReconnect = false;
3287
3288 if(getState() <= stateNoChannel)
3289 {
3290 // We're already set up to join a channel, just needed to fill in the session handle
3291 }
3292 else
3293 {
3294 // State machine will come around and rejoin if uri/handle is not empty.
3295 sessionTerminate();
3296 }
3297}
3298
3299void LLVoiceClient::setNonSpatialChannel(
3300 const std::string &uri,
3301 const std::string &credentials)
3302{
3303 switchChannel(uri, false, false, credentials);
3304}
3305
3306void LLVoiceClient::setSpatialChannel(
3307 const std::string &uri,
3308 const std::string &credentials)
3309{
3310 mSpatialSessionURI = uri;
3311 mAreaVoiceDisabled = mSpatialSessionURI.empty();
3312
3313 llinfos << "got spatial channel uri: \"" << uri << "\"" << llendl;
3314
3315 if(mNonSpatialChannel || !mNextSessionSpatial)
3316 {
3317 // User is in a non-spatial chat or joining a non-spatial chat. Don't switch channels.
3318 llinfos << "in non-spatial chat, not switching channels" << llendl;
3319 }
3320 else
3321 {
3322 switchChannel(mSpatialSessionURI, true, false, credentials);
3323 }
3324}
3325
3326void LLVoiceClient::callUser(LLUUID &uuid)
3327{
3328 std::string userURI = sipURIFromID(uuid);
3329
3330 switchChannel(userURI, false, true);
3331}
3332
3333void LLVoiceClient::answerInvite(std::string &sessionHandle, LLUUID& other_user_id)
3334{
3335 joinSession(sessionHandle, sipURIFromID(other_user_id));
3336}
3337
3338void LLVoiceClient::declineInvite(std::string &sessionHandle)
3339{
3340 sessionTerminateByHandle(sessionHandle);
3341}
3342
3343void LLVoiceClient::leaveNonSpatialChannel()
3344{
3345 switchChannel(mSpatialSessionURI);
3346}
3347
3348std::string LLVoiceClient::getCurrentChannel()
3349{
3350 if((getState() == stateRunning) && !mSessionTerminateRequested)
3351 {
3352 return mSessionURI;
3353 }
3354
3355 return "";
3356}
3357
3358bool LLVoiceClient::inProximalChannel()
3359{
3360 bool result = false;
3361
3362 if((getState() == stateRunning) && !mSessionTerminateRequested)
3363 {
3364 result = !mNonSpatialChannel;
3365 }
3366
3367 return result;
3368}
3369
3370std::string LLVoiceClient::sipURIFromID(const LLUUID &id)
3371{
3372 std::string result;
3373 result = "sip:";
3374 result += nameFromID(id);
3375 result += "@";
3376 result += mAccountServerName;
3377
3378 return result;
3379}
3380
3381std::string LLVoiceClient::sipURIFromAvatar(LLVOAvatar *avatar)
3382{
3383 std::string result;
3384 if(avatar)
3385 {
3386 result = "sip:";
3387 result += nameFromID(avatar->getID());
3388 result += "@";
3389 result += mAccountServerName;
3390 }
3391
3392 return result;
3393}
3394
3395std::string LLVoiceClient::nameFromAvatar(LLVOAvatar *avatar)
3396{
3397 std::string result;
3398 if(avatar)
3399 {
3400 result = nameFromID(avatar->getID());
3401 }
3402 return result;
3403}
3404
3405std::string LLVoiceClient::nameFromID(const LLUUID &uuid)
3406{
3407 std::string result;
3408 U8 rawuuid[UUID_BYTES + 1];
3409 uuid.toCompressedString((char*)rawuuid);
3410
3411 // Prepending this apparently prevents conflicts with reserved names inside the vivox and diamondware code.
3412 result = "x";
3413
3414 // Base64 encode and replace the pieces of base64 that are less compatible
3415 // with e-mail local-parts.
3416 // See RFC-4648 "Base 64 Encoding with URL and Filename Safe Alphabet"
3417 result += LLBase64::encode(rawuuid, UUID_BYTES);
3418 LLString::replaceChar(result, '+', '-');
3419 LLString::replaceChar(result, '/', '_');
3420
3421 // If you need to transform a GUID to this form on the Mac OS X command line, this will do so:
3422 // echo -n x && (echo e669132a-6c43-4ee1-a78d-6c82fff59f32 |xxd -r -p |openssl base64|tr '/+' '_-')
3423
3424 return result;
3425}
3426
3427bool LLVoiceClient::IDFromName(const std::string name, LLUUID &uuid)
3428{
3429 bool result = false;
3430
3431 // This will only work if the name is of the proper form.
3432 // As an example, the account name for Monroe Linden (UUID 1673cfd3-8229-4445-8d92-ec3570e5e587) is:
3433 // "xFnPP04IpREWNkuw1cOXlhw=="
3434
3435 if((name.size() == 25) && (name[0] == 'x') && (name[23] == '=') && (name[24] == '='))
3436 {
3437 // The name appears to have the right form.
3438
3439 // Reverse the transforms done by nameFromID
3440 std::string temp = name;
3441 LLString::replaceChar(temp, '-', '+');
3442 LLString::replaceChar(temp, '_', '/');
3443
3444 U8 rawuuid[UUID_BYTES + 1];
3445 int len = apr_base64_decode_binary(rawuuid, temp.c_str() + 1);
3446 if(len == UUID_BYTES)
3447 {
3448 // The decode succeeded. Stuff the bits into the result's UUID
3449 // MBW -- XXX -- there's no analogue of LLUUID::toCompressedString that allows you to set a UUID from binary data.
3450 // The data field is public, so we cheat thusly:
3451 memcpy(uuid.mData, rawuuid, UUID_BYTES);
3452 result = true;
3453 }
3454 }
3455
3456 return result;
3457}
3458
3459std::string LLVoiceClient::displayNameFromAvatar(LLVOAvatar *avatar)
3460{
3461 return avatar->getFullname();
3462}
3463
3464std::string LLVoiceClient::sipURIFromName(std::string &name)
3465{
3466 std::string result;
3467 result = "sip:";
3468 result += name;
3469 result += "@";
3470 result += mAccountServerName;
3471
3472// LLString::toLower(result);
3473
3474 return result;
3475}
3476
3477/////////////////////////////
3478// Sending updates of current state
3479
3480void LLVoiceClient::enforceTether(void)
3481{
3482 LLVector3d tethered = mCameraRequestedPosition;
3483
3484 // constrain 'tethered' to within 50m of mAvatarPosition.
3485 {
3486 F32 max_dist = 50.0f;
3487 LLVector3d camera_offset = mCameraRequestedPosition - mAvatarPosition;
3488 F32 camera_distance = (F32)camera_offset.magVec();
3489 if(camera_distance > max_dist)
3490 {
3491 tethered = mAvatarPosition +
3492 (max_dist / camera_distance) * camera_offset;
3493 }
3494 }
3495
3496 if(dist_vec(mCameraPosition, tethered) > 0.1)
3497 {
3498 mCameraPosition = tethered;
3499 mSpatialCoordsDirty = true;
3500 }
3501}
3502
3503void LLVoiceClient::setCameraPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot)
3504{
3505 mCameraRequestedPosition = position;
3506
3507 if(mCameraVelocity != velocity)
3508 {
3509 mCameraVelocity = velocity;
3510 mSpatialCoordsDirty = true;
3511 }
3512
3513 if(mCameraRot != rot)
3514 {
3515 mCameraRot = rot;
3516 mSpatialCoordsDirty = true;
3517 }
3518}
3519
3520void LLVoiceClient::setAvatarPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot)
3521{
3522 if(dist_vec(mAvatarPosition, position) > 0.1)
3523 {
3524 mAvatarPosition = position;
3525 mSpatialCoordsDirty = true;
3526 }
3527
3528 if(mAvatarVelocity != velocity)
3529 {
3530 mAvatarVelocity = velocity;
3531 mSpatialCoordsDirty = true;
3532 }
3533
3534 if(mAvatarRot != rot)
3535 {
3536 mAvatarRot = rot;
3537 mSpatialCoordsDirty = true;
3538 }
3539}
3540
3541bool LLVoiceClient::channelFromRegion(LLViewerRegion *region, std::string &name)
3542{
3543 bool result = false;
3544
3545 if(region)
3546 {
3547 name = region->getName();
3548 }
3549
3550 if(!name.empty())
3551 result = true;
3552
3553 return result;
3554}
3555
3556void LLVoiceClient::leaveChannel(void)
3557{
3558 if(getState() == stateRunning)
3559 {
3560// llinfos << "leaving channel for teleport/logout" << llendl;
3561 mChannelName.clear();
3562 sessionTerminate();
3563 }
3564}
3565
3566void LLVoiceClient::setMuteMic(bool muted)
3567{
3568 mMuteMic = muted;
3569}
3570
3571void LLVoiceClient::setUserPTTState(bool ptt)
3572{
3573 mUserPTTState = ptt;
3574}
3575
3576bool LLVoiceClient::getUserPTTState()
3577{
3578 return mUserPTTState;
3579}
3580
3581void LLVoiceClient::toggleUserPTTState(void)
3582{
3583 mUserPTTState = !mUserPTTState;
3584}
3585
3586void LLVoiceClient::setVoiceEnabled(bool enabled)
3587{
3588 if (enabled != mVoiceEnabled)
3589 {
3590 mVoiceEnabled = enabled;
3591 if (enabled)
3592 {
3593 LLVoiceChannel::getCurrentVoiceChannel()->activate();
3594 }
3595 else
3596 {
3597 // for now, leave active channel, to auto join when turning voice back on
3598 //LLVoiceChannel::getCurrentVoiceChannel->deactivate();
3599 }
3600 }
3601}
3602
3603bool LLVoiceClient::voiceEnabled()
3604{
3605 return gSavedSettings.getBOOL("EnableVoiceChat") && !gDisableVoice;
3606}
3607
3608void LLVoiceClient::setUsePTT(bool usePTT)
3609{
3610 if(usePTT && !mUsePTT)
3611 {
3612 // When the user turns on PTT, reset the current state.
3613 mUserPTTState = false;
3614 }
3615 mUsePTT = usePTT;
3616}
3617
3618void LLVoiceClient::setPTTIsToggle(bool PTTIsToggle)
3619{
3620 if(!PTTIsToggle && mPTTIsToggle)
3621 {
3622 // When the user turns off toggle, reset the current state.
3623 mUserPTTState = false;
3624 }
3625
3626 mPTTIsToggle = PTTIsToggle;
3627}
3628
3629
3630void LLVoiceClient::setPTTKey(std::string &key)
3631{
3632 if(key == "MiddleMouse")
3633 {
3634 mPTTIsMiddleMouse = true;
3635 }
3636 else
3637 {
3638 mPTTIsMiddleMouse = false;
3639 if(!LLKeyboard::keyFromString(key, &mPTTKey))
3640 {
3641 // If the call failed, don't match any key.
3642 key = KEY_NONE;
3643 }
3644 }
3645}
3646
3647void LLVoiceClient::setEarLocation(S32 loc)
3648{
3649 if(mEarLocation != loc)
3650 {
3651 llinfos << "Setting mEarLocation to " << loc << llendl;
3652
3653 mEarLocation = loc;
3654 mSpatialCoordsDirty = true;
3655 }
3656}
3657
3658void LLVoiceClient::setVoiceVolume(F32 volume)
3659{
3660 int scaledVolume = ((int)(volume * 100.0f)) - 100;
3661 if(scaledVolume != mSpeakerVolume)
3662 {
3663 if((scaledVolume == -100) || (mSpeakerVolume == -100))
3664 {
3665 mSpeakerMuteDirty = true;
3666 }
3667
3668 mSpeakerVolume = scaledVolume;
3669 mSpeakerVolumeDirty = true;
3670 }
3671}
3672
3673void LLVoiceClient::setMicGain(F32 volume)
3674{
3675 int scaledVolume = ((int)(volume * 100.0f)) - 100;
3676 if(scaledVolume != mMicVolume)
3677 {
3678 mMicVolume = scaledVolume;
3679 mMicVolumeDirty = true;
3680 }
3681}
3682
3683void LLVoiceClient::setVivoxDebugServerName(std::string &serverName)
3684{
3685 if(!mAccountServerName.empty())
3686 {
3687 // The name has been filled in already, which means we know whether we're connecting to agni or not.
3688 if(!sConnectingToAgni)
3689 {
3690 // Only use the setting if we're connecting to a development grid -- always use bhr when on agni.
3691 mAccountServerName = serverName;
3692 }
3693 }
3694}
3695
3696void LLVoiceClient::keyDown(KEY key, MASK mask)
3697{
3698// llinfos << "key is " << LLKeyboard::stringFromKey(key) << llendl;
3699
3700 if (gKeyboard->getKeyRepeated(key))
3701 {
3702 // ignore auto-repeat keys
3703 return;
3704 }
3705
3706 if(!mPTTIsMiddleMouse)
3707 {
3708 if(mPTTIsToggle)
3709 {
3710 if(key == mPTTKey)
3711 {
3712 toggleUserPTTState();
3713 }
3714 }
3715 else if(mPTTKey != KEY_NONE)
3716 {
3717 setUserPTTState(gKeyboard->getKeyDown(mPTTKey));
3718 }
3719 }
3720}
3721void LLVoiceClient::keyUp(KEY key, MASK mask)
3722{
3723 if(!mPTTIsMiddleMouse)
3724 {
3725 if(!mPTTIsToggle && (mPTTKey != KEY_NONE))
3726 {
3727 setUserPTTState(gKeyboard->getKeyDown(mPTTKey));
3728 }
3729 }
3730}
3731void LLVoiceClient::middleMouseState(bool down)
3732{
3733 if(mPTTIsMiddleMouse)
3734 {
3735 if(mPTTIsToggle)
3736 {
3737 if(down)
3738 {
3739 toggleUserPTTState();
3740 }
3741 }
3742 else
3743 {
3744 setUserPTTState(down);
3745 }
3746 }
3747}
3748
3749/////////////////////////////
3750// Accessors for data related to nearby speakers
3751BOOL LLVoiceClient::getVoiceEnabled(const LLUUID& id)
3752{
3753 BOOL result = FALSE;
3754 participantState *participant = findParticipantByID(id);
3755 if(participant)
3756 {
3757 // I'm not sure what the semantics of this should be.
3758 // For now, if we have any data about the user that came through the chat channel, assume they're voice-enabled.
3759 result = TRUE;
3760 }
3761
3762 return result;
3763}
3764
3765BOOL LLVoiceClient::getIsSpeaking(const LLUUID& id)
3766{
3767 BOOL result = FALSE;
3768
3769 participantState *participant = findParticipantByID(id);
3770 if(participant)
3771 {
3772 if (participant->mSpeakingTimeout.getElapsedTimeF32() > SPEAKING_TIMEOUT)
3773 {
3774 participant->mIsSpeaking = FALSE;
3775 }
3776 result = participant->mIsSpeaking;
3777 }
3778
3779 return result;
3780}
3781
3782F32 LLVoiceClient::getCurrentPower(const LLUUID& id)
3783{
3784 F32 result = 0;
3785 participantState *participant = findParticipantByID(id);
3786 if(participant)
3787 {
3788 result = participant->mPower;
3789 }
3790
3791 return result;
3792}
3793
3794
3795LLString LLVoiceClient::getDisplayName(const LLUUID& id)
3796{
3797 LLString result;
3798 participantState *participant = findParticipantByID(id);
3799 if(participant)
3800 {
3801 result = participant->mDisplayName;
3802 }
3803
3804 return result;
3805}
3806
3807
3808BOOL LLVoiceClient::getUsingPTT(const LLUUID& id)
3809{
3810 BOOL result = FALSE;
3811
3812 participantState *participant = findParticipantByID(id);
3813 if(participant)
3814 {
3815 // I'm not sure what the semantics of this should be.
3816 // Does "using PTT" mean they're configured with a push-to-talk button?
3817 // For now, we know there's no PTT mechanism in place, so nobody is using it.
3818 }
3819
3820 return result;
3821}
3822
3823BOOL LLVoiceClient::getPTTPressed(const LLUUID& id)
3824{
3825 BOOL result = FALSE;
3826
3827 participantState *participant = findParticipantByID(id);
3828 if(participant)
3829 {
3830 result = participant->mPTT;
3831 }
3832
3833 return result;
3834}
3835
3836BOOL LLVoiceClient::getOnMuteList(const LLUUID& id)
3837{
3838 BOOL result = FALSE;
3839
3840 participantState *participant = findParticipantByID(id);
3841 if(participant)
3842 {
3843 result = participant->mOnMuteList;
3844 }
3845
3846 return result;
3847}
3848
3849// External accessiors. Maps 0.0 to 1.0 to internal values 0-400 with .5 == 100
3850// internal = 400 * external^2
3851F32 LLVoiceClient::getUserVolume(const LLUUID& id)
3852{
3853 F32 result = 0.0f;
3854
3855 participantState *participant = findParticipantByID(id);
3856 if(participant)
3857 {
3858 S32 ires = participant->mUserVolume; // 0-400
3859 result = sqrtf(((F32)ires) / 400.f);
3860 }
3861
3862 return result;
3863}
3864
3865void LLVoiceClient::setUserVolume(const LLUUID& id, F32 volume)
3866{
3867 participantState *participant = findParticipantByID(id);
3868 if (participant)
3869 {
3870 // volume can amplify by as much as 4x!
3871 S32 ivol = (S32)(400.f * volume * volume);
3872 participant->mUserVolume = llclamp(ivol, 0, 400);
3873 participant->mVolumeDirty = TRUE;
3874 mVolumeDirty = TRUE;
3875 }
3876}
3877
3878
3879
3880LLVoiceClient::serviceType LLVoiceClient::getServiceType(const LLUUID& id)
3881{
3882 serviceType result = serviceTypeUnknown;
3883
3884 participantState *participant = findParticipantByID(id);
3885 if(participant)
3886 {
3887 result = participant->mServiceType;
3888 }
3889
3890 return result;
3891}
3892
3893std::string LLVoiceClient::getGroupID(const LLUUID& id)
3894{
3895 std::string result;
3896
3897 participantState *participant = findParticipantByID(id);
3898 if(participant)
3899 {
3900 result = participant->mGroupID;
3901 }
3902
3903 return result;
3904}
3905
3906BOOL LLVoiceClient::getAreaVoiceDisabled()
3907{
3908 return mAreaVoiceDisabled;
3909}
3910
3911void LLVoiceClient::addObserver(LLVoiceClientParticipantObserver* observer)
3912{
3913 mObservers.insert(observer);
3914}
3915
3916void LLVoiceClient::removeObserver(LLVoiceClientParticipantObserver* observer)
3917{
3918 mObservers.erase(observer);
3919}
3920
3921void LLVoiceClient::notifyObservers()
3922{
3923 for (observer_set_t::iterator it = mObservers.begin();
3924 it != mObservers.end();
3925 )
3926 {
3927 LLVoiceClientParticipantObserver* observer = *it;
3928 observer->onChange();
3929 // In case onChange() deleted an entry.
3930 it = mObservers.upper_bound(observer);
3931 }
3932}
3933
3934void LLVoiceClient::addStatusObserver(LLVoiceClientStatusObserver* observer)
3935{
3936 mStatusObservers.insert(observer);
3937}
3938
3939void LLVoiceClient::removeStatusObserver(LLVoiceClientStatusObserver* observer)
3940{
3941 mStatusObservers.erase(observer);
3942}
3943
3944void LLVoiceClient::notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status)
3945{
3946 if(status == LLVoiceClientStatusObserver::ERROR_UNKNOWN)
3947 {
3948 switch(mVivoxErrorStatusCode)
3949 {
3950 case 20713: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_FULL; break;
3951 case 20714: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_LOCKED; break;
3952 }
3953
3954 // Reset the error code to make sure it won't be reused later by accident.
3955 mVivoxErrorStatusCode = 0;
3956 }
3957
3958 if (status == LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL
3959 //NOT_FOUND || TEMPORARILY_UNAVAILABLE || REQUEST_TIMEOUT
3960 && (mVivoxErrorStatusCode == 404 || mVivoxErrorStatusCode == 480 || mVivoxErrorStatusCode == 408))
3961 {
3962 // call failed because other user was not available
3963 // treat this as an error case
3964 status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE;
3965
3966 // Reset the error code to make sure it won't be reused later by accident.
3967 mVivoxErrorStatusCode = 0;
3968 }
3969
3970 llinfos << " " << LLVoiceClientStatusObserver::status2string(status) << ", session URI " << mSessionURI << llendl;
3971
3972 for (status_observer_set_t::iterator it = mStatusObservers.begin();
3973 it != mStatusObservers.end();
3974 )
3975 {
3976 LLVoiceClientStatusObserver* observer = *it;
3977 observer->onChange(status, mSessionURI, !mNonSpatialChannel);
3978 // In case onError() deleted an entry.
3979 it = mStatusObservers.upper_bound(observer);
3980 }
3981
3982}
3983
3984//static
3985void LLVoiceClient::onAvatarNameLookup(const LLUUID& id, const char* first, const char* last, BOOL is_group, void* user_data)
3986{
3987 participantState* statep = gVoiceClient->findParticipantByID(id);
3988
3989 if (statep)
3990 {
3991 statep->mDisplayName = llformat("%s %s", first, last);
3992 }
3993
3994 gVoiceClient->notifyObservers();
3995}
3996
3997class LLViewerParcelVoiceInfo : public LLHTTPNode
3998{
3999 virtual void post(
4000 LLHTTPNode::ResponsePtr response,
4001 const LLSD& context,
4002 const LLSD& input) const
4003 {
4004 //the parcel you are in has changed something about its
4005 //voice information
4006
4007 if ( input.has("body") )
4008 {
4009 LLSD body = input["body"];
4010
4011 //body has "region_name" (str), "parcel_local_id"(int),
4012 //"voice_credentials" (map).
4013
4014 //body["voice_credentials"] has "channel_uri" (str),
4015 //body["voice_credentials"] has "channel_credentials" (str)
4016 if ( body.has("voice_credentials") )
4017 {
4018 LLSD voice_credentials = body["voice_credentials"];
4019 std::string uri;
4020 std::string credentials;
4021
4022 if ( voice_credentials.has("channel_uri") )
4023 {
4024 uri = voice_credentials["channel_uri"].asString();
4025 }
4026 if ( voice_credentials.has("channel_credentials") )
4027 {
4028 credentials =
4029 voice_credentials["channel_credentials"].asString();
4030 }
4031
4032 gVoiceClient->setSpatialChannel(uri, credentials);
4033 }
4034 }
4035 }
4036};
4037
4038class LLViewerRequiredVoiceVersion : public LLHTTPNode
4039{
4040 static BOOL sAlertedUser;
4041 virtual void post(
4042 LLHTTPNode::ResponsePtr response,
4043 const LLSD& context,
4044 const LLSD& input) const
4045 {
4046 //You received this messsage (most likely on region cross or
4047 //teleport)
4048 if ( input.has("body") && input["body"].has("major_version") )
4049 {
4050 int major_voice_version =
4051 input["body"]["major_version"].asInteger();
4052// int minor_voice_version =
4053// input["body"]["minor_version"].asInteger();
4054
4055 if (gVoiceClient &&
4056 (major_voice_version > VOICE_MAJOR_VERSION) )
4057 {
4058 if (!sAlertedUser)
4059 {
4060 //sAlertedUser = TRUE;
4061 gViewerWindow->alertXml("VoiceVersionMismatch");
4062 gSavedSettings.setBOOL("EnableVoiceChat", FALSE); // toggles listener
4063 }
4064 }
4065 }
4066 }
4067};
4068BOOL LLViewerRequiredVoiceVersion::sAlertedUser = FALSE;
4069
4070LLHTTPRegistration<LLViewerParcelVoiceInfo>
4071 gHTTPRegistrationMessageParcelVoiceInfo(
4072 "/message/ParcelVoiceInfo");
4073
4074LLHTTPRegistration<LLViewerRequiredVoiceVersion>
4075 gHTTPRegistrationMessageRequiredVoiceVersion(
4076 "/message/RequiredVoiceVersion");