diff options
30 files changed, 2092 insertions, 1389 deletions
diff --git a/autobuild.xml b/autobuild.xml index 8cbe12dbf5..85d0ccc5c9 100755 --- a/autobuild.xml +++ b/autobuild.xml @@ -2014,9 +2014,9 @@ <key>archive</key> <map> <key>hash</key> - <string>68a8fab5ad3a180487598d3a3e0d57b1</string> + <string>3dd9bf4185bf2df413d890ca9eeab76c</string> <key>url</key> - <string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/slvoice_3p-update-slvoice/rev/298329/arch/Darwin/installer/slvoice-4.6.0017.21209.298329-darwin-298329.tar.bz2</string> + <string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/vivox_3p-slvoice/rev/302004/arch/Darwin/installer/slvoice-4.6.0017.22050.302004-darwin-302004.tar.bz2</string> </map> <key>name</key> <string>darwin</string> @@ -2026,9 +2026,9 @@ <key>archive</key> <map> <key>hash</key> - <string>48ed7ddcf93fa3c751d677f5eb5f9367</string> + <string>06c3a9b1005249f0c94a8b9f3775f3d3</string> <key>url</key> - <string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/slvoice_3p-update-slvoice/rev/298329/arch/Linux/installer/slvoice-3.2.0002.10426.298329-linux-298329.tar.bz2</string> + <string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/vivox_3p-slvoice/rev/302004/arch/Linux/installer/slvoice-3.2.0002.10426.302004-linux-302004.tar.bz2</string> </map> <key>name</key> <string>linux</string> @@ -2038,16 +2038,16 @@ <key>archive</key> <map> <key>hash</key> - <string>399afab7047e6fa62e7b2fb1768059ea</string> + <string>47a3316dae47cc4e7c1ea7b74ba8dd1e</string> <key>url</key> - <string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/slvoice_3p-update-slvoice/rev/298329/arch/CYGWIN/installer/slvoice-4.6.0017.21209.298329-windows-298329.tar.bz2</string> + <string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/vivox_3p-slvoice/rev/302004/arch/CYGWIN/installer/slvoice-4.6.0017.22050.302004-windows-302004.tar.bz2</string> </map> <key>name</key> <string>windows</string> </map> </map> <key>version</key> - <string>4.6.0017.21209.2988329</string> + <string>3.2.0002.10426.302004</string> </map> <key>tut</key> <map> diff --git a/indra/cmake/Boost.cmake b/indra/cmake/Boost.cmake index 25e54b7cbd..d239e1f3b0 100755 --- a/indra/cmake/Boost.cmake +++ b/indra/cmake/Boost.cmake @@ -123,3 +123,8 @@ else (USESYSTEMLIBS) debug boost_thread-mt-d) endif (WINDOWS) endif (USESYSTEMLIBS) + +if (LINUX) + set(BOOST_SYSTEM_LIBRARY ${BOOST_SYSTEM_LIBRARY} rt) +endif (LINUX) + diff --git a/indra/cmake/Copy3rdPartyLibs.cmake b/indra/cmake/Copy3rdPartyLibs.cmake index a6fd756c88..70d85b864c 100755 --- a/indra/cmake/Copy3rdPartyLibs.cmake +++ b/indra/cmake/Copy3rdPartyLibs.cmake @@ -22,10 +22,8 @@ if(WINDOWS) SLVoice.exe ca-bundle.crt libsndfile-1.dll - vivoxplatform.dll vivoxsdk.dll ortp.dll - zlib1.dll vivoxoal.dll ) diff --git a/indra/cmake/LLCommon.cmake b/indra/cmake/LLCommon.cmake index b52556a73e..b50b4bcdb2 100755 --- a/indra/cmake/LLCommon.cmake +++ b/indra/cmake/LLCommon.cmake @@ -19,9 +19,19 @@ if (LINUX) # In order to support using ld.gold on linux, we need to explicitely # specify all libraries that llcommon uses. # llcommon uses `clock_gettime' which is provided by librt on linux. - set(LLCOMMON_LIBRARIES llcommon rt) + set(LLCOMMON_LIBRARIES llcommon + ${BOOST_COROUTINE_LIBRARY} + ${BOOST_CONTEXT_LIBRARY} + ${BOOST_THREAD_LIBRARY} + ${BOOST_SYSTEM_LIBRARY} + rt + ) else (LINUX) - set(LLCOMMON_LIBRARIES llcommon) + set(LLCOMMON_LIBRARIES llcommon + ${BOOST_COROUTINE_LIBRARY} + ${BOOST_CONTEXT_LIBRARY} + ${BOOST_THREAD_LIBRARY} + ${BOOST_SYSTEM_LIBRARY} ) endif (LINUX) # add_definitions(${TCMALLOC_FLAG}) diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp index 548a6d22be..d16bf0160b 100755 --- a/indra/llcommon/llcoros.cpp +++ b/indra/llcommon/llcoros.cpp @@ -51,14 +51,32 @@ boost::thread_specific_ptr<LLCoros::CoroData> LLCoros::sCurrentCoro(LLCoros::no_cleanup); //static -LLCoros::coro::self& LLCoros::get_self() +LLCoros::CoroData& LLCoros::get_CoroData(const std::string& caller) { CoroData* current = sCurrentCoro.get(); if (! current) { - LL_ERRS("LLCoros") << "Calling get_self() from non-coroutine context!" << LL_ENDL; + LL_ERRS("LLCoros") << "Calling " << caller << " from non-coroutine context!" << LL_ENDL; } - return *current->mSelf; + return *current; +} + +//static +LLCoros::coro::self& LLCoros::get_self() +{ + return *get_CoroData("get_self()").mSelf; +} + +//static +void LLCoros::set_consuming(bool consuming) +{ + get_CoroData("set_consuming()").mConsuming = consuming; +} + +//static +bool LLCoros::get_consuming() +{ + return get_CoroData("get_consuming()").mConsuming; } llcoro::Suspending::Suspending(): @@ -245,6 +263,8 @@ LLCoros::CoroData::CoroData(CoroData* prev, const std::string& name, // Wrap the caller's callable in our toplevel() function so we can manage // sCurrentCoro appropriately at startup and shutdown of each coroutine. mCoro(boost::bind(toplevel, _1, this, callable), stacksize), + // don't consume events unless specifically directed + mConsuming(false), mSelf(0) { } diff --git a/indra/llcommon/llcoros.h b/indra/llcommon/llcoros.h index 56eed8cafe..0b1f58f48e 100755 --- a/indra/llcommon/llcoros.h +++ b/indra/llcommon/llcoros.h @@ -150,6 +150,18 @@ public: /// get the current coro::self& for those who really really care static coro::self& get_self(); + /** + * Most coroutines, most of the time, don't "consume" the events for which + * they're suspending. This way, an arbitrary number of listeners (whether + * coroutines or simple callbacks) can be registered on a particular + * LLEventPump, every listener responding to each of the events on that + * LLEventPump. But a particular coroutine can assert that it will consume + * each event for which it suspends. (See also llcoro::postAndSuspend(), + * llcoro::VoidListener) + */ + static void set_consuming(bool consuming); + static bool get_consuming(); + private: LLCoros(); friend class LLSingleton<LLCoros>; @@ -159,6 +171,7 @@ private: struct CoroData; static void no_cleanup(CoroData*); static void toplevel(coro::self& self, CoroData* data, const callable_t& callable); + static CoroData& get_CoroData(const std::string& caller); S32 mStackSize; @@ -178,6 +191,8 @@ private: const std::string mName; // the actual coroutine instance LLCoros::coro mCoro; + // set_consuming() state + bool mConsuming; // When the dcoroutine library calls a top-level callable, it implicitly // passes coro::self& as the first parameter. All our consumer code used // to explicitly pass coro::self& down through all levels of call stack, diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp index c9bfcacedc..9b7e8fb65f 100755 --- a/indra/llcommon/lleventcoro.cpp +++ b/indra/llcommon/lleventcoro.cpp @@ -41,6 +41,8 @@ #include "llerror.h" #include "llcoros.h" +#include "lleventfilter.h" + namespace { @@ -153,11 +155,19 @@ void llcoro::suspend() suspendUntilEventOn("mainloop"); } +void llcoro::suspendUntilTimeout(float seconds) +{ + LLEventTimeout timeout; + + timeout.eventAfter(seconds, LLSD()); + llcoro::suspendUntilEventOn(timeout); +} + LLSD llcoro::postAndSuspend(const LLSD& event, const LLEventPumpOrPumpName& requestPump, const LLEventPumpOrPumpName& replyPump, const LLSD& replyPumpNamePath) { // declare the future - boost::dcoroutines::future<LLSD> future(LLCoros::get_self()); + boost::dcoroutines::future<LLSD_consumed> future(LLCoros::get_self()); // make a callback that will assign a value to the future, and listen on // the specified LLEventPump with that callback std::string listenerName(listenerNameForCoro()); @@ -183,21 +193,25 @@ LLSD llcoro::postAndSuspend(const LLSD& event, const LLEventPumpOrPumpName& requ << " about to wait on LLEventPump " << replyPump.getPump().getName() << LL_ENDL; // trying to dereference ("resolve") the future makes us wait for it - LLSD value; + LLSD_consumed value; { // instantiate Suspending to manage the "current" coroutine llcoro::Suspending suspended; value = *future; } // destroy Suspending as soon as we're back LL_DEBUGS("lleventcoro") << "postAndSuspend(): coroutine " << listenerName - << " resuming with " << value << LL_ENDL; + << " resuming with " << value.first << LL_ENDL; + // immediately set consumed according to consuming + *value.second = LLCoros::get_consuming(); // returning should disconnect the connection - return value; + return value.first; } namespace { +typedef std::pair<LLEventWithID, bool*> LLEventWithID_consumed; + /** * This helper is specifically for the two-pump version of suspendUntilEventOn(). * We use a single future object, but we want to listen on two pumps with it. @@ -217,13 +231,15 @@ public: mListener(listener), mDiscrim(discriminator) {} + // this signature is required for an LLEventPump listener bool operator()(const LLSD& event) { - // our future object is defined to accept LLEventWithID - mListener(LLEventWithID(event, mDiscrim)); - // don't swallow the event, let other listeners see it - return false; + bool consumed = false; + // our future object is defined to accept LLEventWithID_consumed + mListener(LLEventWithID_consumed(LLEventWithID(event, mDiscrim), &consumed)); + // tell LLEventPump whether or not event was consumed + return consumed; } private: LISTENER mListener; @@ -250,7 +266,7 @@ LLEventWithID postAndSuspend2(const LLSD& event, const LLSD& replyPump1NamePath) { // declare the future - boost::dcoroutines::future<LLEventWithID> future(LLCoros::get_self()); + boost::dcoroutines::future<LLEventWithID_consumed> future(LLCoros::get_self()); // either callback will assign a value to this future; listen on // each specified LLEventPump with a callback std::string name(listenerNameForCoro()); @@ -279,17 +295,19 @@ LLEventWithID postAndSuspend2(const LLSD& event, << " about to wait on LLEventPumps " << replyPump0.getPump().getName() << ", " << replyPump1.getPump().getName() << LL_ENDL; // trying to dereference ("resolve") the future makes us wait for it - LLEventWithID value; + LLEventWithID_consumed value; { // instantiate Suspending to manage "current" coroutine llcoro::Suspending suspended; value = *future; } // destroy Suspending as soon as we're back LL_DEBUGS("lleventcoro") << "postAndSuspend(): coroutine " << name - << " resuming with (" << value.first << ", " << value.second << ")" - << LL_ENDL; + << " resuming with (" << value.first.first + << ", " << value.first.second << ")" << LL_ENDL; + // tell LLEventPump whether we're consuming + *value.second = LLCoros::get_consuming(); // returning should disconnect both connections - return value; + return value.first; } LLSD errorException(const LLEventWithID& result, const std::string& desc) diff --git a/indra/llcommon/lleventcoro.h b/indra/llcommon/lleventcoro.h index 6fda3d2572..acf2ad24a4 100755 --- a/indra/llcommon/lleventcoro.h +++ b/indra/llcommon/lleventcoro.h @@ -32,6 +32,7 @@ #include <boost/optional.hpp> #include <string> #include <stdexcept> +#include <utility> // std::pair #include "llevents.h" #include "llerror.h" @@ -75,6 +76,8 @@ private: namespace llcoro { +typedef std::pair<LLSD, bool*> LLSD_consumed; + /// This is an adapter for a signature like void LISTENER(const LLSD&), which /// isn't a valid LLEventPump listener: such listeners should return bool. template <typename LISTENER> @@ -84,11 +87,13 @@ public: VoidListener(const LISTENER& listener): mListener(listener) {} + bool operator()(const LLSD& event) { - mListener(event); - // don't swallow the event, let other listeners see it - return false; + bool consumed = false; + mListener(LLSD_consumed(event, &consumed)); + // tell upstream LLEventPump whether listener consumed + return consumed; } private: LISTENER mListener; @@ -109,6 +114,11 @@ VoidListener<LISTENER> voidlistener(const LISTENER& listener) void suspend(); /** + * Yield control from a coroutine for at least the specified number of seconds + */ +void suspendUntilTimeout(float seconds); + +/** * Post specified LLSD event on the specified LLEventPump, then suspend for a * response on specified other LLEventPump. This is more than mere * convenience: the difference between this function and the sequence diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp index 1c928b3db8..0c5e55dc76 100755 --- a/indra/llcommon/llevents.cpp +++ b/indra/llcommon/llevents.cpp @@ -57,6 +57,7 @@ #include "stringize.h" #include "llerror.h" #include "llsdutil.h" +#include "llcoros.h" #if LL_MSVC #pragma warning (disable : 4702) #endif @@ -132,6 +133,17 @@ LLEventPump& LLEventPumps::obtain(const std::string& name) return *newInstance; } +bool LLEventPumps::post(const std::string&name, const LLSD&message) +{ + PumpMap::iterator found = mPumpMap.find(name); + + if (found == mPumpMap.end()) + return false; + + return (*found).second->post(message); +} + + void LLEventPumps::flush() { // Flush every known LLEventPump instance. Leave it up to each instance to @@ -497,6 +509,43 @@ bool LLEventStream::post(const LLSD& event) } /***************************************************************************** + * LLEventMailDrop + *****************************************************************************/ +bool LLEventMailDrop::post(const LLSD& event) +{ + bool posted = false; + + if (!mSignal->empty()) + posted = LLEventStream::post(event); + + if (!posted) + { // if the event was not handled we will save it for later so that it can + // be posted to any future listeners when they attach. + mEventHistory.push_back(event); + } + + return posted; +} + +LLBoundListener LLEventMailDrop::listen_impl(const std::string& name, + const LLEventListener& listener, + const NameList& after, + const NameList& before) +{ + if (!mEventHistory.empty()) + { + if (listener(mEventHistory.front()) || LLCoros::get_consuming()) + { + mEventHistory.pop_front(); + } + } + + return LLEventStream::listen_impl(name, listener, after, before); + +} + + +/***************************************************************************** * LLEventQueue *****************************************************************************/ bool LLEventQueue::post(const LLSD& event) diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h index 0cbd1da32d..6175329a9d 100755 --- a/indra/llcommon/llevents.h +++ b/indra/llcommon/llevents.h @@ -217,6 +217,18 @@ public: * an instance without conferring @em ownership. */ LLEventPump& obtain(const std::string& name); + + /** + * Find the named LLEventPump instance. If it exists post the message to it. + * If the pump does not exist, do nothing. + * + * returns the result of the LLEventPump::post. If no pump exists returns false. + * + * This is syntactically similar to LLEventPumps::instance().post(name, message), + * however if the pump does not already exist it will not be created. + */ + bool post(const std::string&, const LLSD&); + /** * Flush all known LLEventPump instances */ @@ -496,7 +508,7 @@ public: // at the boost::bind object itself before that happens. return LLEventDetail::visit_and_connect(name, listener, - boost::bind(&LLEventPump::listen_impl, + boost::bind(&LLEventPump::listen_invoke, this, name, _1, @@ -541,13 +553,23 @@ private: virtual void reset(); + + private: - virtual LLBoundListener listen_impl(const std::string& name, const LLEventListener&, - const NameList& after, - const NameList& before); + LLBoundListener listen_invoke(const std::string& name, const LLEventListener& listener, + const NameList& after, + const NameList& before) + { + return this->listen_impl(name, listener, after, before); + } + std::string mName; protected: + virtual LLBoundListener listen_impl(const std::string& name, const LLEventListener&, + const NameList& after, + const NameList& before); + /// implement the dispatching boost::shared_ptr<LLStandardSignal> mSignal; @@ -586,10 +608,39 @@ public: }; /***************************************************************************** + * LLEventMailDrop + *****************************************************************************/ +/** + * LLEventMailDrop is a specialization of LLEventStream. Events are posted normally, + * however if no listeners return that they have handled the event it is placed in + * a queue. Subsequent attaching listeners will receive stored events from the queue + * until a listener indicates that the event has been handled. In order to receive + * multiple events from a mail drop the listener must disconnect and reconnect. + */ +class LL_COMMON_API LLEventMailDrop : public LLEventStream +{ +public: + LLEventMailDrop(const std::string& name, bool tweak = false) : LLEventStream(name, tweak) {} + virtual ~LLEventMailDrop() {} + + /// Post an event to all listeners + virtual bool post(const LLSD& event); + +protected: + virtual LLBoundListener listen_impl(const std::string& name, const LLEventListener&, + const NameList& after, + const NameList& before); + +private: + typedef std::list<LLSD> EventList; + EventList mEventHistory; +}; + +/***************************************************************************** * LLEventQueue *****************************************************************************/ /** - * LLEventQueue isa LLEventPump whose post() method defers calling registered + * LLEventQueue is a LLEventPump whose post() method defers calling registered * listeners until flush() is called. */ class LL_COMMON_API LLEventQueue: public LLEventPump diff --git a/indra/llcommon/tests/lleventcoro_test.cpp b/indra/llcommon/tests/lleventcoro_test.cpp index 1ee79e9eb6..da1439418f 100755 --- a/indra/llcommon/tests/lleventcoro_test.cpp +++ b/indra/llcommon/tests/lleventcoro_test.cpp @@ -240,7 +240,7 @@ namespace tut // ... do whatever preliminary stuff must happen ... // declare the future - boost::dcoroutines::future<LLSD> future(self); + boost::dcoroutines::future<llcoro::LLSD_consumed> future(self); // tell the future what to suspend for LLTempBoundListener connection( LLEventPumps::instance().obtain("source").listen("coro", voidlistener(boost::dcoroutines::make_callback(future)))); @@ -248,7 +248,7 @@ namespace tut // attempting to dereference ("resolve") the future causes the calling // coroutine to suspend for it debug("about to suspend"); - result = *future; + result = (*future).first; ensure("Got it", future); } END diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt index 78c84f366a..87bec60d95 100755 --- a/indra/llmessage/CMakeLists.txt +++ b/indra/llmessage/CMakeLists.txt @@ -203,6 +203,8 @@ set_source_files_properties(${llmessage_HEADER_FILES} list(APPEND llmessage_SOURCE_FILES ${llmessage_HEADER_FILES}) add_library (llmessage ${llmessage_SOURCE_FILES}) + +if (LINUX) target_link_libraries( llmessage ${CURL_LIBRARIES} @@ -217,7 +219,25 @@ target_link_libraries( ${BOOST_COROUTINE_LIBRARY} ${BOOST_CONTEXT_LIBRARY} ${BOOST_SYSTEM_LIBRARY} + rt ) +else (LINUX) +target_link_libraries( + llmessage + ${CURL_LIBRARIES} + ${LLCOMMON_LIBRARIES} + ${LLVFS_LIBRARIES} + ${LLMATH_LIBRARIES} + ${JSONCPP_LIBRARIES} + ${OPENSSL_LIBRARIES} + ${CRYPTO_LIBRARIES} + ${XMLRPCEPI_LIBRARIES} + ${LLCOREHTTP_LIBRARIES} + ${BOOST_COROUTINE_LIBRARY} + ${BOOST_CONTEXT_LIBRARY} + ${BOOST_SYSTEM_LIBRARY} + ) +endif(LINUX) # tests if (LL_TESTS) @@ -231,6 +251,22 @@ if (LL_TESTS) # set(TEST_DEBUG on) +if (LINUX) + set(test_libs + ${WINDOWS_LIBRARIES} + ${LLVFS_LIBRARIES} + ${LLMATH_LIBRARIES} + ${CURL_LIBRARIES} + ${LLCOMMON_LIBRARIES} + ${LLMESSAGE_LIBRARIES} + ${LLCOREHTTP_LIBRARIES} + ${JSONCPP_LIBRARIES} + ${BOOST_COROUTINE_LIBRARY} + ${BOOST_CONTEXT_LIBRARY} + rt + ${GOOGLEMOCK_LIBRARIES} + ) +else (LINUX) set(test_libs ${WINDOWS_LIBRARIES} ${LLVFS_LIBRARIES} @@ -244,6 +280,7 @@ if (LL_TESTS) ${BOOST_CONTEXT_LIBRARY} ${GOOGLEMOCK_LIBRARIES} ) +endif(LINUX) #LL_ADD_INTEGRATION_TEST(llavatarnamecache "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llhost "" "${test_libs}") diff --git a/indra/llmessage/llexperiencecache.cpp b/indra/llmessage/llexperiencecache.cpp index 92fcd38d3b..779d1d9d99 100644 --- a/indra/llmessage/llexperiencecache.cpp +++ b/indra/llmessage/llexperiencecache.cpp @@ -396,12 +396,9 @@ void LLExperienceCache::idleCoro() const F32 ERASE_EXPIRED_TIMEOUT = 60.f; // seconds LL_INFOS("ExperienceCache") << "Launching Experience cache idle coro." << LL_ENDL; - LLEventTimeout timeout; - do { - timeout.eventAfter(SECS_BETWEEN_REQUESTS, LLSD()); - llcoro::suspendUntilEventOn(timeout); + llcoro::suspendUntilTimeout(SECS_BETWEEN_REQUESTS); if (mEraseExpiredTimer.checkExpirationAndReset(ERASE_EXPIRED_TIMEOUT)) { diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 51efd76484..2661dd759a 100755 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -1736,8 +1736,6 @@ if (WINDOWS) ${SHARED_LIB_STAGING_DIR}/${CMAKE_CFG_INTDIR}/vivoxsdk.dll ${SHARED_LIB_STAGING_DIR}/${CMAKE_CFG_INTDIR}/ortp.dll ${SHARED_LIB_STAGING_DIR}/${CMAKE_CFG_INTDIR}/libsndfile-1.dll - ${SHARED_LIB_STAGING_DIR}/${CMAKE_CFG_INTDIR}/zlib1.dll - ${SHARED_LIB_STAGING_DIR}/${CMAKE_CFG_INTDIR}/vivoxplatform.dll ${SHARED_LIB_STAGING_DIR}/${CMAKE_CFG_INTDIR}/vivoxoal.dll ${SHARED_LIB_STAGING_DIR}/${CMAKE_CFG_INTDIR}/ca-bundle.crt ${GOOGLE_PERF_TOOLS_SOURCE} @@ -2204,6 +2202,7 @@ if (LL_TESTS) ) set(test_libs + ${LLCOMMON_LIBRARIES} ${JSONCPP_LIBRARIES} ${CURL_LIBRARIES} ) diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index be497fffc0..413cc413e6 100755 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -2753,7 +2753,7 @@ bool LLAppViewer::initConfiguration() // gWindowTitle = LLTrans::getString("APP_NAME"); #if LL_DEBUG - gWindowTitle += std::string(" [DEBUG]"); + gWindowTitle += std::string(" [DEBUG]"); #endif if (!gArgs.empty()) { diff --git a/indra/newview/lleventpoll.cpp b/indra/newview/lleventpoll.cpp index 72e159bcec..7178042b32 100755 --- a/indra/newview/lleventpoll.cpp +++ b/indra/newview/lleventpoll.cpp @@ -197,7 +197,6 @@ namespace Details // request. Calculate a timeout and wait for it to expire(sleep) // before trying again. The sleep time is increased by 5 seconds // for each consecutive error. - LLEventTimeout timeout; ++errorCount; F32 waitToRetry = EVENT_POLL_ERROR_RETRY_SECONDS @@ -206,8 +205,7 @@ namespace Details LL_WARNS("LLEventPollImpl") << "<" << counter << "> Retrying in " << waitToRetry << " seconds, error count is now " << errorCount << LL_ENDL; - timeout.eventAfter(waitToRetry, LLSD()); - llcoro::suspendUntilEventOn(timeout); + llcoro::suspendUntilTimeout(waitToRetry); if (mDone) break; diff --git a/indra/newview/llexperienceassociationresponder.cpp b/indra/newview/llexperienceassociationresponder.cpp new file mode 100644 index 0000000000..7f2363aadc --- /dev/null +++ b/indra/newview/llexperienceassociationresponder.cpp @@ -0,0 +1,104 @@ +/** + * @file llexperienceassociationresponder.cpp + * @brief llexperienceassociationresponder implementation. This class combines + * a lookup for a script association and an experience details request. The first + * is always async, but the second may be cached locally. + * + * $LicenseInfo:firstyear=2013&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2013, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llexperienceassociationresponder.h" +#include "llexperiencecache.h" +#include "llviewerobjectlist.h" +#include "llviewerregion.h" +#include "llagent.h" + +ExperienceAssociationResponder::ExperienceAssociationResponder(ExperienceAssociationResponder::callback_t callback):mCallback(callback) +{ + ref(); +} + +void ExperienceAssociationResponder::fetchAssociatedExperience( const LLUUID& object_id, const LLUUID& item_id, callback_t callback ) +{ + LLSD request; + request["object-id"]=object_id; + request["item-id"]=item_id; + fetchAssociatedExperience(request, callback); +} + +void ExperienceAssociationResponder::fetchAssociatedExperience(LLSD& request, callback_t callback) +{ + LLViewerObject* object = gObjectList.findObject(request["object-id"]); + if (!object) + { + LL_WARNS() << "Failed to find object with ID " << request["object-id"] << " in fetchAssociatedExperience" << LL_ENDL; + return; + } + LLViewerRegion* region = object->getRegion(); + if (region) + { + std::string lookup_url=region->getCapability("GetMetadata"); + if(!lookup_url.empty()) + { + LLSD fields; + fields.append("experience"); + request["fields"] = fields; + LLHTTPClient::post(lookup_url, request, new ExperienceAssociationResponder(callback)); + } + } +} + +void ExperienceAssociationResponder::httpFailure() +{ + LLSD msg; + msg["error"]=(LLSD::Integer)getStatus(); + msg["message"]=getReason(); + LL_INFOS("ExperienceAssociation") << "Failed to look up associated experience: " << getStatus() << ": " << getReason() << LL_ENDL; + + sendResult(msg); + +} +void ExperienceAssociationResponder::httpSuccess() +{ + if(!getContent().has("experience")) + { + + LLSD msg; + msg["message"]="no experience"; + msg["error"]=-1; + sendResult(msg); + return; + } + + LLExperienceCache::get(getContent()["experience"].asUUID(), boost::bind(&ExperienceAssociationResponder::sendResult, this, _1)); + +} + +void ExperienceAssociationResponder::sendResult( const LLSD& experience ) +{ + mCallback(experience); + unref(); +} + + + diff --git a/indra/newview/llexperienceassociationresponder.h b/indra/newview/llexperienceassociationresponder.h new file mode 100644 index 0000000000..2bdc3d251b --- /dev/null +++ b/indra/newview/llexperienceassociationresponder.h @@ -0,0 +1,58 @@ +#include "llhttpclient.h" +#include "llsd.h" +/** + * @file llexperienceassociationresponder.h + * @brief llexperienceassociationresponder and related class definitions + * + * $LicenseInfo:firstyear=2013&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2013, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + + + +#ifndef LL_LLEXPERIENCEASSOCIATIONRESPONDER_H +#define LL_LLEXPERIENCEASSOCIATIONRESPONDER_H + +#include "llhttpclient.h" +#include "llsd.h" + +class ExperienceAssociationResponder : public LLHTTPClient::Responder +{ +public: + typedef boost::function<void(const LLSD& experience)> callback_t; + + ExperienceAssociationResponder(callback_t callback); + + /*virtual*/ void httpSuccess(); + /*virtual*/ void httpFailure(); + + static void fetchAssociatedExperience(const LLUUID& object_it, const LLUUID& item_id, callback_t callback); + +private: + static void fetchAssociatedExperience(LLSD& request, callback_t callback); + + void sendResult(const LLSD& experience); + + callback_t mCallback; + +}; + +#endif // LL_LLEXPERIENCEASSOCIATIONRESPONDER_H diff --git a/indra/newview/llfloaterperms.cpp b/indra/newview/llfloaterperms.cpp index cb67787af3..b0b2770c6e 100755 --- a/indra/newview/llfloaterperms.cpp +++ b/indra/newview/llfloaterperms.cpp @@ -232,8 +232,6 @@ void LLFloaterPermsDefault::updateCapCoro(std::string url) if (!status) { - LLEventTimeout timeout; - const std::string& reason = status.toString(); // Do not display the same error more than once in a row if (reason != previousReason) @@ -244,8 +242,7 @@ void LLFloaterPermsDefault::updateCapCoro(std::string url) LLNotificationsUtil::add("DefaultObjectPermissions", args); } - timeout.eventAfter(RETRY_TIMEOUT, LLSD()); - llcoro::suspendUntilEventOn(timeout); + llcoro::suspendUntilTimeout(RETRY_TIMEOUT); if (retryCount < MAX_HTTP_RETRIES) continue; diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index 53b97a58c5..951389b856 100755 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -1388,12 +1388,12 @@ void LLIMModel::sendMessage(const std::string& utf8_text, info = LLAvatarTracker::instance().getBuddyInfo(other_participant_id); U8 offline = (!info || info->isOnline()) ? IM_ONLINE : IM_OFFLINE; - - if((offline == IM_OFFLINE) && (LLVoiceClient::getInstance()->isOnlineSIP(other_participant_id))) - { - // User is online through the OOW connector, but not with a regular viewer. Try to send the message via SLVoice. - sent = LLVoiceClient::getInstance()->sendTextMessage(other_participant_id, utf8_text); - } + // Old call to send messages to SLim client, no longer supported. + //if((offline == IM_OFFLINE) && (LLVoiceClient::getInstance()->isOnlineSIP(other_participant_id))) + //{ + // // User is online through the OOW connector, but not with a regular viewer. Try to send the message via SLVoice. + // sent = LLVoiceClient::getInstance()->sendTextMessage(other_participant_id, utf8_text); + //} if(!sent) { diff --git a/indra/newview/llpanelvoicedevicesettings.cpp b/indra/newview/llpanelvoicedevicesettings.cpp index 3946d6a63b..07f8045546 100755 --- a/indra/newview/llpanelvoicedevicesettings.cpp +++ b/indra/newview/llpanelvoicedevicesettings.cpp @@ -51,7 +51,7 @@ LLPanelVoiceDeviceSettings::LLPanelVoiceDeviceSettings() mCtrlOutputDevices = NULL; mInputDevice = gSavedSettings.getString("VoiceInputAudioDevice"); mOutputDevice = gSavedSettings.getString("VoiceOutputAudioDevice"); - mDevicesUpdated = FALSE; + mDevicesUpdated = FALSE; //obsolete mUseTuningMode = true; // grab "live" mic volume level @@ -80,6 +80,10 @@ BOOL LLPanelVoiceDeviceSettings::postBuild() mLocalizedDeviceNames[DEFAULT_DEVICE] = getString("default_text"); mLocalizedDeviceNames["No Device"] = getString("name_no_device"); mLocalizedDeviceNames["Default System Device"] = getString("name_default_system_device"); + + mCtrlOutputDevices->setMouseDownCallback(boost::bind(&LLPanelVoiceDeviceSettings::onOutputDevicesClicked, this)); + mCtrlInputDevices->setMouseDownCallback(boost::bind(&LLPanelVoiceDeviceSettings::onInputDevicesClicked, this)); + return TRUE; } @@ -226,9 +230,8 @@ void LLPanelVoiceDeviceSettings::refresh() mCtrlOutputDevices->add(getLocalizedDeviceName(mOutputDevice), mOutputDevice, ADD_BOTTOM); mCtrlOutputDevices->setValue(mOutputDevice); } - mDevicesUpdated = FALSE; } - else if (!mDevicesUpdated) + else if (LLVoiceClient::getInstance()->deviceSettingsUpdated()) { LLVoiceDeviceList::const_iterator iter; @@ -272,7 +275,6 @@ void LLPanelVoiceDeviceSettings::refresh() mOutputDevice = DEFAULT_DEVICE; } } - mDevicesUpdated = TRUE; } } @@ -281,7 +283,6 @@ void LLPanelVoiceDeviceSettings::initialize() mInputDevice = gSavedSettings.getString("VoiceInputAudioDevice"); mOutputDevice = gSavedSettings.getString("VoiceOutputAudioDevice"); mMicVolume = gSavedSettings.getF32("AudioLevelMic"); - mDevicesUpdated = FALSE; // ask for new device enumeration LLVoiceClient::getInstance()->refreshDeviceLists(); @@ -314,8 +315,8 @@ void LLPanelVoiceDeviceSettings::onCommitInputDevice() { if(LLVoiceClient::getInstance()) { - LLVoiceClient::getInstance()->setCaptureDevice( - mCtrlInputDevices->getValue().asString()); + mInputDevice = mCtrlInputDevices->getValue().asString(); + LLVoiceClient::getInstance()->setRenderDevice(mInputDevice); } } @@ -323,7 +324,18 @@ void LLPanelVoiceDeviceSettings::onCommitOutputDevice() { if(LLVoiceClient::getInstance()) { - LLVoiceClient::getInstance()->setRenderDevice( - mCtrlInputDevices->getValue().asString()); + + mOutputDevice = mCtrlOutputDevices->getValue().asString(); + LLVoiceClient::getInstance()->setRenderDevice(mOutputDevice); } } + +void LLPanelVoiceDeviceSettings::onOutputDevicesClicked() +{ + LLVoiceClient::getInstance()->refreshDeviceLists(false); // fill in the pop up menus again if needed. +} + +void LLPanelVoiceDeviceSettings::onInputDevicesClicked() +{ + LLVoiceClient::getInstance()->refreshDeviceLists(false); // fill in the pop up menus again if needed. +} diff --git a/indra/newview/llpanelvoicedevicesettings.h b/indra/newview/llpanelvoicedevicesettings.h index 83464f476a..355bc02b05 100755 --- a/indra/newview/llpanelvoicedevicesettings.h +++ b/indra/newview/llpanelvoicedevicesettings.h @@ -53,6 +53,8 @@ protected: void onCommitInputDevice(); void onCommitOutputDevice(); + void onOutputDevicesClicked(); + void onInputDevicesClicked(); F32 mMicVolume; std::string mInputDevice; diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index b0280ef3e0..a4109d5885 100755 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -310,7 +310,7 @@ void LLViewerRegionImpl::requestBaseCapabilitiesCoro(U64 regionHandle) << "Capability '" << iter->first << "' is '" << iter->second << "'" << LL_ENDL; } -#if 0 +#if 1 log_capabilities(mCapabilities); #endif diff --git a/indra/newview/llvoiceclient.cpp b/indra/newview/llvoiceclient.cpp index e24884fe81..56c0910983 100755 --- a/indra/newview/llvoiceclient.cpp +++ b/indra/newview/llvoiceclient.cpp @@ -266,6 +266,18 @@ bool LLVoiceClient::deviceSettingsAvailable() } } +bool LLVoiceClient::deviceSettingsUpdated() +{ + if (mVoiceModule) + { + return mVoiceModule->deviceSettingsUpdated(); + } + else + { + return false; + } +} + void LLVoiceClient::refreshDeviceLists(bool clearCurrentList) { if (mVoiceModule) mVoiceModule->refreshDeviceLists(clearCurrentList); @@ -363,6 +375,7 @@ BOOL LLVoiceClient::isSessionCallBackPossible(const LLUUID& id) } } +/* obsolete BOOL LLVoiceClient::sendTextMessage(const LLUUID& participant_id, const std::string& message) { if (mVoiceModule) @@ -374,12 +387,13 @@ BOOL LLVoiceClient::sendTextMessage(const LLUUID& participant_id, const std::str return FALSE; } } +*/ void LLVoiceClient::endUserIMSession(const LLUUID& participant_id) { if (mVoiceModule) { - mVoiceModule->endUserIMSession(participant_id); + // mVoiceModule->endUserIMSession(participant_id); // A SLim leftover } } @@ -645,9 +659,8 @@ void LLVoiceClient::keyDown(KEY key, MASK mask) if(!mPTTIsMiddleMouse && LLAgent::isActionAllowed("speak")) { - bool down = (mPTTKey != KEY_NONE) - && gKeyboard->getKeyDown(mPTTKey); - inputUserControlState(down); + bool down = (mPTTKey != KEY_NONE) && gKeyboard->getKeyDown(mPTTKey); + if (down) { inputUserControlState(down); } } } @@ -655,9 +668,8 @@ void LLVoiceClient::keyUp(KEY key, MASK mask) { if(!mPTTIsMiddleMouse) { - bool down = (mPTTKey != KEY_NONE) - && gKeyboard->getKeyDown(mPTTKey); - inputUserControlState(down); + bool down = (mPTTKey != KEY_NONE) && gKeyboard->getKeyDown(mPTTKey); + if (down) { inputUserControlState(down); } } } void LLVoiceClient::middleMouseState(bool down) diff --git a/indra/newview/llvoiceclient.h b/indra/newview/llvoiceclient.h index fb387301be..b05bcb23b7 100755 --- a/indra/newview/llvoiceclient.h +++ b/indra/newview/llvoiceclient.h @@ -128,6 +128,7 @@ public: // This returns true when it's safe to bring up the "device settings" dialog in the prefs. // i.e. when the daemon is running and connected, and the device lists are populated. virtual bool deviceSettingsAvailable()=0; + virtual bool deviceSettingsUpdated() = 0; // Requery the vivox daemon for the current list of input/output devices. // If you pass true for clearCurrentList, deviceSettingsAvailable() will be false until the query has completed @@ -155,7 +156,7 @@ public: virtual void setNonSpatialChannel(const std::string &uri, const std::string &credentials)=0; - virtual void setSpatialChannel(const std::string &uri, + virtual bool setSpatialChannel(const std::string &uri, const std::string &credentials)=0; virtual void leaveNonSpatialChannel()=0; @@ -214,7 +215,7 @@ public: //@{ virtual BOOL isSessionTextIMPossible(const LLUUID& id)=0; virtual BOOL isSessionCallBackPossible(const LLUUID& id)=0; - virtual BOOL sendTextMessage(const LLUUID& participant_id, const std::string& message)=0; + //virtual BOOL sendTextMessage(const LLUUID& participant_id, const std::string& message)=0; virtual void endUserIMSession(const LLUUID &uuid)=0; //@} @@ -335,6 +336,7 @@ public: // This returns true when it's safe to bring up the "device settings" dialog in the prefs. // i.e. when the daemon is running and connected, and the device lists are populated. bool deviceSettingsAvailable(); + bool deviceSettingsUpdated(); // returns true when the device list has been updated recently. // Requery the vivox daemon for the current list of input/output devices. // If you pass true for clearCurrentList, deviceSettingsAvailable() will be false until the query has completed @@ -431,7 +433,7 @@ public: //@{ BOOL isSessionTextIMPossible(const LLUUID& id); BOOL isSessionCallBackPossible(const LLUUID& id); - BOOL sendTextMessage(const LLUUID& participant_id, const std::string& message); + //BOOL sendTextMessage(const LLUUID& participant_id, const std::string& message) const {return true;} ; void endUserIMSession(const LLUUID &uuid); //@} diff --git a/indra/newview/llvoicevivox.cpp b/indra/newview/llvoicevivox.cpp index 1425499b12..a27afef052 100755 --- a/indra/newview/llvoicevivox.cpp +++ b/indra/newview/llvoicevivox.cpp @@ -60,11 +60,13 @@ #include "lltrans.h" #include "llviewerwindow.h" #include "llviewercamera.h" +#include "llversioninfo.h" #include "llviewernetwork.h" #include "llnotificationsutil.h" #include "llcorehttputil.h" +#include "lleventfilter.h" #include "stringize.h" @@ -93,11 +95,10 @@ const F32 LOGIN_RETRY_SECONDS = 10.0f; const int MAX_LOGIN_RETRIES = 12; // Defines the maximum number of times(in a row) "stateJoiningSession" case for spatial channel is reached in stateMachine() -// which is treated as normal. If this number is exceeded we suspect there is a problem with connection -// to voice server (EXT-4313). When voice works correctly, there is from 1 to 15 times. 50 was chosen -// to make sure we don't make mistake when slight connection problems happen- situation when connection to server is -// blocked is VERY rare and it's better to sacrifice response time in this situation for the sake of stability. -const int MAX_NORMAL_JOINING_SPATIAL_NUM = 50; +// which is treated as normal. The is the number of frames to wait for a channel join before giving up. This was changed +// from the original count of 50 for two reason. Modern PCs have higher frame rates and sometimes the SLVoice process +// backs up processing join requests. There is a log statement that records when channel joins take longer than 100 frames. +const int MAX_NORMAL_JOINING_SPATIAL_NUM = 1500; // How often to check for expired voice fonts in seconds const F32 VOICE_FONT_EXPIRY_INTERVAL = 10.f; @@ -166,9 +167,10 @@ LLVivoxVoiceClient::LLVivoxVoiceClient() : mTuningEnergy(0.0f), mTuningMicVolume(0), mTuningMicVolumeDirty(true), - mTuningSpeakerVolume(0), + mTuningSpeakerVolume(50), // Set to 50 so the user can hear himself when he sets his mic volume mTuningSpeakerVolumeDirty(true), mTuningExitState(stateDisabled), + mDevicesListUpdated(false), mAreaVoiceDisabled(false), mAudioSession(NULL), @@ -215,7 +217,12 @@ LLVivoxVoiceClient::LLVivoxVoiceClient() : mShutdownComplete(true), mPlayRequestCount(0), - mAvatarNameCacheConnection() + mAvatarNameCacheConnection(), + mIsInTuningMode(false), + mIsInChannel(false), + mIsJoiningSession(false), + + mVivoxPump("vivoxClientPump") { mSpeakerVolume = scale_speaker_volume(0); @@ -238,6 +245,7 @@ LLVivoxVoiceClient::LLVivoxVoiceClient() : signal(SIGCHLD, SIG_IGN); #endif + // set up state machine setState(stateDisabled); @@ -279,6 +287,7 @@ void LLVivoxVoiceClient::terminate() #endif closeSocket(); // Need to do this now -- bad things happen if the destructor does it later. cleanUp(); + mConnected = false; } else { @@ -339,17 +348,20 @@ bool LLVivoxVoiceClient::writeString(const std::string &str) (const char*)str.data(), &written); - if(err == 0) + if(err == 0 && written == size) { // Success. result = true; } - // TODO: handle partial writes (written is number of bytes written) - // Need to set socket to non-blocking before this will work. -// else if(APR_STATUS_IS_EAGAIN(err)) -// { -// // -// } + else if (err == 0 && written != size) { + // Did a short write, log it for now + LL_WARNS("Voice") << ") short write on socket sending data to vivox daemon." << "Sent " << written << "bytes instead of " << size <<LL_ENDL; + } + else if(APR_STATUS_IS_EAGAIN(err)) + { + char buf[MAX_STRING]; + LL_WARNS("Voice") << "EAGAIN error " << err << " (" << apr_strerror(err, buf, MAX_STRING) << ") sending data to vivox daemon." << LL_ENDL; + } else { // Assume any socket error means something bad. For now, just close the socket. @@ -372,8 +384,6 @@ void LLVivoxVoiceClient::connectorCreate() std::string loglevel = "0"; // Transition to stateConnectorStarted when the connector handle comes back. - setState(stateConnectorStarting); - std::string savedLogLevel = gSavedSettings.getString("VivoxDebugLevel"); if(savedLogLevel != "0") @@ -387,14 +397,15 @@ void LLVivoxVoiceClient::connectorCreate() << "<AccountManagementServer>" << mVoiceAccountServerURI << "</AccountManagementServer>" << "<Mode>Normal</Mode>" << "<Logging>" - << "<Folder>" << logpath << "</Folder>" - << "<FileNamePrefix>Connector</FileNamePrefix>" - << "<FileNameSuffix>.log</FileNameSuffix>" - << "<LogLevel>" << loglevel << "</LogLevel>" + << "<Folder>" << logpath << "</Folder>" + << "<FileNamePrefix>Connector</FileNamePrefix>" + << "<FileNameSuffix>.log</FileNameSuffix>" + << "<LogLevel>" << loglevel << "</LogLevel>" << "</Logging>" - << "<Application></Application>" //Name can cause problems per vivox. - << "<MaxCalls>12</MaxCalls>" - << "</Request>\n\n\n"; + << "<Application>" << LLVersionInfo::getChannel().c_str() << " " << LLVersionInfo::getVersion().c_str() << "</Application>" + //<< "<Application></Application>" //Name can cause problems per vivox. + << "<MaxCalls>12</MaxCalls>" + << "</Request>\n\n\n"; writeString(stream.str()); } @@ -429,68 +440,7 @@ void LLVivoxVoiceClient::userAuthorized(const std::string& user_id, const LLUUID mAccountName = nameFromID(agentID); } -void LLVivoxVoiceClient::requestVoiceAccountProvision(S32 retries) -{ - LLViewerRegion *region = gAgent.getRegion(); - - // If we've not received the capability yet, return. - // the password will remain empty, so we'll remain in - // stateIdle - if ( region && - region->capabilitiesReceived() && - (mVoiceEnabled || !mIsInitialized)) - { - std::string url = - region->getCapability("ProvisionVoiceAccountRequest"); - - if ( !url.empty() ) - { - LLCoros::instance().launch("LLVivoxVoiceClient::voiceAccountProvisionCoro", - boost::bind(&LLVivoxVoiceClient::voiceAccountProvisionCoro, this, url, retries)); - setState(stateConnectorStart); - } - } -} - -void LLVivoxVoiceClient::voiceAccountProvisionCoro(std::string url, S32 retries) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("voiceAccountProvision", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpOptions::ptr_t httpOpts = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); - - httpOpts->setRetries(retries); - - LLSD result = httpAdapter->postAndSuspend(httpRequest, url, LLSD(), httpOpts); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - LL_WARNS("Voice") << "Unable to provision voice account." << LL_ENDL; - giveUp(); - return; - } - - std::string voice_sip_uri_hostname; - std::string voice_account_server_uri; - - //LL_DEBUGS("Voice") << "ProvisionVoiceAccountRequest response:" << dumpResponse() << LL_ENDL; - - if (result.has("voice_sip_uri_hostname")) - voice_sip_uri_hostname = result["voice_sip_uri_hostname"].asString(); - - // this key is actually misnamed -- it will be an entire URI, not just a hostname. - if (result.has("voice_account_server_name")) - voice_account_server_uri = result["voice_account_server_name"].asString(); - - login(result["username"].asString(), result["password"].asString(), - voice_sip_uri_hostname, voice_account_server_uri); -} - -void LLVivoxVoiceClient::login( +void LLVivoxVoiceClient::setLoginInfo( const std::string& account_name, const std::string& password, const std::string& voice_sip_uri_hostname, @@ -639,9 +589,12 @@ void LLVivoxVoiceClient::stateMachine() setVoiceEnabled(false); } - if(mVoiceEnabled || (!mIsInitialized &&!mTerminateDaemon) ) + if ((getState() == stateRunning) && inSpatialChannel() && /*mUpdateTimer.hasExpired() &&*/ !mTerminateDaemon) { +#if 0 + // poll the avatar position so its available in various states when a 3d position is sent. updatePosition(); +#endif } else if(mTuningMode) { @@ -649,24 +602,31 @@ void LLVivoxVoiceClient::stateMachine() } else { - if((getState() != stateDisabled) && (getState() != stateDisableCleanup)) + if (!gSavedSettings.getBOOL("EnableVoiceChat")) + //if((getState() != stateDisabled) && (getState() != stateDisableCleanup)) { // User turned off voice support. Send the cleanup messages, close the socket, and reset. - if(!mConnected || mTerminateDaemon) + if(!mConnected && mTerminateDaemon) { // if voice was turned off after the daemon was launched but before we could connect to it, we may need to issue a kill. LL_INFOS("Voice") << "Disabling voice before connection to daemon, terminating." << LL_ENDL; killGateway(); mTerminateDaemon = false; + mConnected = false; + } + else { + logout(); + connectorShutdown(); } - - logout(); - connectorShutdown(); setState(stateDisableCleanup); } } + + // send any requests to adjust mic and speaker settings if they have changed + sendLocalAudioUpdates(); + switch(getState()) { @@ -687,516 +647,62 @@ void LLVivoxVoiceClient::stateMachine() case stateDisabled: if(mTuningMode || ((mVoiceEnabled || !mIsInitialized) && !mAccountName.empty())) { - setState(stateStart); - } - break; - - //MARK: stateStart - case stateStart: - if(gSavedSettings.getBOOL("CmdLineDisableVoice")) - { - // Voice is locked out, we must not launch the vivox daemon. - setState(stateJail); - } - else if(!isGatewayRunning() && gSavedSettings.getBOOL("EnableVoiceChat")) - { - if (true) // production build, not test - { - // Launch the voice daemon - - // *FIX:Mani - Using the executable dir instead - // of mAppRODataDir, the working directory from which the app - // is launched. - //std::string exe_path = gDirUtilp->getAppRODataDir(); - std::string exe_path = gDirUtilp->getExecutableDir(); - exe_path += gDirUtilp->getDirDelimiter(); -#if LL_WINDOWS - exe_path += "SLVoice.exe"; -#elif LL_DARWIN - exe_path += "../Resources/SLVoice"; -#else - exe_path += "SLVoice"; -#endif - // See if the vivox executable exists - llstat s; - if (!LLFile::stat(exe_path, &s)) - { - // vivox executable exists. Build the command line and launch the daemon. - LLProcess::Params params; - params.executable = exe_path; - - std::string loglevel = gSavedSettings.getString("VivoxDebugLevel"); - std::string shutdown_timeout = gSavedSettings.getString("VivoxShutdownTimeout"); - if(loglevel.empty()) - { - loglevel = "0"; // turn logging off completely - } - - params.args.add("-ll"); - params.args.add(loglevel); - - std::string log_folder = gSavedSettings.getString("VivoxLogDirectory"); - - if (log_folder.empty()) - { - log_folder = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ""); - } - - params.args.add("-lf"); - params.args.add(log_folder); - - if(!shutdown_timeout.empty()) - { - params.args.add("-st"); - params.args.add(shutdown_timeout); - } - params.cwd = gDirUtilp->getAppRODataDir(); - sGatewayPtr = LLProcess::create(params); - - mDaemonHost = LLHost(gSavedSettings.getString("VivoxVoiceHost").c_str(), gSavedSettings.getU32("VivoxVoicePort")); - } - else - { - LL_INFOS("Voice") << exe_path << " not found." << LL_ENDL; - } - } - else - { - // SLIM SDK: port changed from 44124 to 44125. - // We can connect to a client gateway running on another host. This is useful for testing. - // To do this, launch the gateway on a nearby host like this: - // vivox-gw.exe -p tcp -i 0.0.0.0:44125 - // and put that host's IP address here. - mDaemonHost = LLHost(gSavedSettings.getString("VivoxVoiceHost"), gSavedSettings.getU32("VivoxVoicePort")); - } - - mUpdateTimer.start(); - mUpdateTimer.setTimerExpirySec(CONNECT_THROTTLE_SECONDS); - - setState(stateDaemonLaunched); - - // Dirty the states we'll need to sync with the daemon when it comes up. - mMuteMicDirty = true; - mMicVolumeDirty = true; - mSpeakerVolumeDirty = true; - mSpeakerMuteDirty = true; - // These only need to be set if they're not default (i.e. empty string). - mCaptureDeviceDirty = !mCaptureDevice.empty(); - mRenderDeviceDirty = !mRenderDevice.empty(); - - mMainSessionGroupHandle.clear(); - } - break; - - //MARK: stateDaemonLaunched - case stateDaemonLaunched: - if(mUpdateTimer.hasExpired()) - { - LL_DEBUGS("Voice") << "Connecting to vivox daemon:" << mDaemonHost << LL_ENDL; - - mUpdateTimer.setTimerExpirySec(CONNECT_THROTTLE_SECONDS); - - if(!mSocket) - { - mSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP); - } - - mConnected = mSocket->blockingConnect(mDaemonHost); - if(mConnected) - { - setState(stateConnecting); - } - else - { - // If the connect failed, the socket may have been put into a bad state. Delete it. - closeSocket(); - } - } - break; - - //MARK: stateConnecting - case stateConnecting: - // Can't do this until we have the pump available. - if(mPump) - { - // MBW -- Note to self: pumps and pipes examples in - // indra/test/io.cpp - // indra/test/llpipeutil.{cpp|h} - - // Attach the pumps and pipes - - LLPumpIO::chain_t readChain; - - readChain.push_back(LLIOPipe::ptr_t(new LLIOSocketReader(mSocket))); - readChain.push_back(LLIOPipe::ptr_t(new LLVivoxProtocolParser())); - - mPump->addChain(readChain, NEVER_CHAIN_EXPIRY_SECS); - - setState(stateConnected); - } - - break; - - //MARK: stateConnected - case stateConnected: - // Initial devices query - getCaptureDevicesSendMessage(); - getRenderDevicesSendMessage(); - - mLoginRetryCount = 0; - - setState(stateIdle); - break; - - //MARK: stateIdle - case stateIdle: - // This is the idle state where we're connected to the daemon but haven't set up a connector yet. - if(mTuningMode) - { - mTuningExitState = stateIdle; - setState(stateMicTuningStart); - } - else if(!mVoiceEnabled && mIsInitialized) - { - // We never started up the connector. This will shut down the daemon. - setState(stateConnectorStopped); - } - else if(!mAccountName.empty()) - { - if ( mAccountPassword.empty() ) - { - requestVoiceAccountProvision(); - } - } - break; - - //MARK: stateMicTuningStart - case stateMicTuningStart: - if(mUpdateTimer.hasExpired()) - { - if(mCaptureDeviceDirty || mRenderDeviceDirty) - { - // These can't be changed while in tuning mode. Set them before starting. - std::ostringstream stream; - - buildSetCaptureDevice(stream); - buildSetRenderDevice(stream); - - if(!stream.str().empty()) - { - writeString(stream.str()); - } - - // This will come around again in the same state and start the capture, after the timer expires. - mUpdateTimer.start(); - mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS); - } - else - { - // duration parameter is currently unused, per Mike S. - tuningCaptureStartSendMessage(10000); - - setState(stateMicTuningRunning); - } - } - - break; - - //MARK: stateMicTuningRunning - case stateMicTuningRunning: - if(!mTuningMode || mCaptureDeviceDirty || mRenderDeviceDirty) - { - // All of these conditions make us leave tuning mode. - setState(stateMicTuningStop); - } - else - { - // process mic/speaker volume changes - if(mTuningMicVolumeDirty || mTuningSpeakerVolumeDirty) - { - std::ostringstream stream; - - if(mTuningMicVolumeDirty) - { - LL_INFOS("Voice") << "setting tuning mic level to " << mTuningMicVolume << LL_ENDL; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetMicLevel.1\">" - << "<Level>" << mTuningMicVolume << "</Level>" - << "</Request>\n\n\n"; - } - - if(mTuningSpeakerVolumeDirty) - { - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetSpeakerLevel.1\">" - << "<Level>" << mTuningSpeakerVolume << "</Level>" - << "</Request>\n\n\n"; - } - - mTuningMicVolumeDirty = false; - mTuningSpeakerVolumeDirty = false; - - if(!stream.str().empty()) - { - writeString(stream.str()); - } - } - } - break; - - //MARK: stateMicTuningStop - case stateMicTuningStop: - { - // transition out of mic tuning - tuningCaptureStopSendMessage(); - - setState(mTuningExitState); - - // if we exited just to change devices, this will keep us from re-entering too fast. - mUpdateTimer.start(); - mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS); - - } - break; - - //MARK: stateCaptureBufferPaused - case stateCaptureBufferPaused: - if (!mCaptureBufferMode) - { - // Leaving capture mode. - - mCaptureBufferRecording = false; - mCaptureBufferRecorded = false; - mCaptureBufferPlaying = false; - - // Return to stateNoChannel to trigger reconnection to a channel. - setState(stateNoChannel); - } - else if (mCaptureBufferRecording) - { - setState(stateCaptureBufferRecStart); - } - else if (mCaptureBufferPlaying) - { - setState(stateCaptureBufferPlayStart); - } - break; - - //MARK: stateCaptureBufferRecStart - case stateCaptureBufferRecStart: - captureBufferRecordStartSendMessage(); - - // Flag that something is recorded to allow playback. - mCaptureBufferRecorded = true; - - // Start the timer, recording will be stopped when it expires. - mCaptureTimer.start(); - mCaptureTimer.setTimerExpirySec(CAPTURE_BUFFER_MAX_TIME); - - // Update UI, should really use a separate callback. - notifyVoiceFontObservers(); - - setState(stateCaptureBufferRecording); - break; - - //MARK: stateCaptureBufferRecording - case stateCaptureBufferRecording: - if (!mCaptureBufferMode || !mCaptureBufferRecording || - mCaptureBufferPlaying || mCaptureTimer.hasExpired()) - { - // Stop recording - captureBufferRecordStopSendMessage(); - mCaptureBufferRecording = false; - - // Update UI, should really use a separate callback. - notifyVoiceFontObservers(); - - setState(stateCaptureBufferPaused); + LLCoros::instance().launch("LLVivoxVoiceClient::voiceControlCoro();", + boost::bind(&LLVivoxVoiceClient::voiceControlCoro, this)); } break; - //MARK: stateCaptureBufferPlayStart - case stateCaptureBufferPlayStart: - captureBufferPlayStartSendMessage(mPreviewVoiceFont); - - // Store the voice font being previewed, so that we know to restart if it changes. - mPreviewVoiceFontLast = mPreviewVoiceFont; - - // Update UI, should really use a separate callback. - notifyVoiceFontObservers(); - - setState(stateCaptureBufferPlaying); - break; - - //MARK: stateCaptureBufferPlaying - case stateCaptureBufferPlaying: - if (mCaptureBufferPlaying && mPreviewVoiceFont != mPreviewVoiceFontLast) - { - // If the preview voice font changes, restart playing with the new font. - setState(stateCaptureBufferPlayStart); - } - else if (!mCaptureBufferMode || !mCaptureBufferPlaying || mCaptureBufferRecording) - { - // Stop playing. - captureBufferPlayStopSendMessage(); - mCaptureBufferPlaying = false; - - // Update UI, should really use a separate callback. - notifyVoiceFontObservers(); - - setState(stateCaptureBufferPaused); - } - break; - - //MARK: stateConnectorStart - case stateConnectorStart: - if(!mVoiceEnabled && mIsInitialized) - { - // We were never logged in. This will shut down the connector. - setState(stateLoggedOut); - } - else if(!mVoiceAccountServerURI.empty()) - { - connectorCreate(); - } - break; - - //MARK: stateConnectorStarting - case stateConnectorStarting: // waiting for connector handle - // connectorCreateResponse() will transition from here to stateConnectorStarted. - break; - - //MARK: stateConnectorStarted - case stateConnectorStarted: // connector handle received - if(!mVoiceEnabled && mIsInitialized) - { - // We were never logged in. This will shut down the connector. - setState(stateLoggedOut); - } - else - { - // The connector is started. Send a login message. - setState(stateNeedsLogin); - } - break; - - //MARK: stateLoginRetry - case stateLoginRetry: - if(mLoginRetryCount == 0) - { - // First retry -- display a message to the user - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGIN_RETRY); - } - - mLoginRetryCount++; - - if(mLoginRetryCount > MAX_LOGIN_RETRIES) - { - LL_WARNS("Voice") << "too many login retries, giving up." << LL_ENDL; - setState(stateLoginFailed); - LLSD args; - std::stringstream errs; - errs << mVoiceAccountServerURI << "\n:UDP: 3478, 3479, 5060, 5062, 12000-17000"; - args["HOSTID"] = errs.str(); - mTerminateDaemon = true; - if (LLGridManager::getInstance()->isSystemGrid()) - { - LLNotificationsUtil::add("NoVoiceConnect", args); - } - else - { - LLNotificationsUtil::add("NoVoiceConnect-GIAB", args); - } - } - else - { - LL_INFOS("Voice") << "will retry login in " << LOGIN_RETRY_SECONDS << " seconds." << LL_ENDL; - mUpdateTimer.start(); - mUpdateTimer.setTimerExpirySec(LOGIN_RETRY_SECONDS); - setState(stateLoginRetryWait); - } - break; - - //MARK: stateLoginRetryWait - case stateLoginRetryWait: - if(mUpdateTimer.hasExpired()) - { - setState(stateNeedsLogin); - } - break; - - //MARK: stateNeedsLogin - case stateNeedsLogin: - if(!mAccountPassword.empty()) - { - setState(stateLoggingIn); - loginSendMessage(); - } - break; - - //MARK: stateLoggingIn - case stateLoggingIn: // waiting for account handle - // loginResponse() will transition from here to stateLoggedIn. - break; - - //MARK: stateLoggedIn - case stateLoggedIn: // account handle received - - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGGED_IN); - - if (LLVoiceClient::instance().getVoiceEffectEnabled()) - { - // Request the set of available voice fonts. - setState(stateVoiceFontsWait); - refreshVoiceEffectLists(true); - } - else - { - // If voice effects are disabled, pretend we've received them and carry on. - setState(stateVoiceFontsReceived); - } - - // Set up the mute list observer if it hasn't been set up already. - if((!sMuteListListener_listening)) - { - LLMuteList::getInstance()->addObserver(&mutelist_listener); - sMuteListListener_listening = true; - } - - // Set the initial state of mic mute, local speaker volume, etc. - { - std::ostringstream stream; - - buildLocalAudioUpdates(stream); - - if(!stream.str().empty()) - { - writeString(stream.str()); - } - } - break; - - //MARK: stateVoiceFontsWait - case stateVoiceFontsWait: // Await voice font list - // accountGetSessionFontsResponse() will transition from here to - // stateVoiceFontsReceived, to ensure we have the voice font list - // before attempting to create a session. - break; - - //MARK: stateVoiceFontsReceived - case stateVoiceFontsReceived: // Voice font list received - // Set up the timer to check for expiring voice fonts - mVoiceFontExpiryTimer.start(); - mVoiceFontExpiryTimer.setTimerExpirySec(VOICE_FONT_EXPIRY_INTERVAL); - -#if USE_SESSION_GROUPS - // create the main session group - setState(stateCreatingSessionGroup); - sessionGroupCreateSendMessage(); -#else - setState(stateNoChannel); -#endif - break; +//-------------------------------------------------------------------------- + case stateStart: + case stateDaemonLaunched: + case stateConnecting: + case stateConnected: + // moved to coroutine LLVivoxVoiceClient::startAndLaunchDaemon + break; +//-------------------------------------------------------------------------- + + case stateIdle: + break; +//-------------------------------------------------------------------------- + case stateMicTuningStart: + case stateMicTuningRunning: + case stateMicTuningStop: + // moved to coroutine LLVivoxVoiceClient::performMicTuning + break; +//-------------------------------------------------------------------------- + + // *TODO: Not working yet.... + + //MARK: stateCaptureBufferPaused + case stateCaptureBufferPaused: + // moved to recordingAndPlaybackMode() + case stateCaptureBufferRecStart: + case stateCaptureBufferRecording: + // moved to voiceRecordBuffer() + case stateCaptureBufferPlayStart: + case stateCaptureBufferPlaying: + // moved to voicePlaybackBuffer() + break; +//------------------------------------------------------------------------- + case stateConnectorStart: + case stateConnectorStarting: + case stateConnectorStarted: + // moved to establishVoiceConnection + break; + +//------------------------------------------------------------------------- + case stateLoginRetry: + case stateLoginRetryWait: + case stateNeedsLogin: + case stateLoggingIn: + case stateLoggedIn: + // moved to loginToVivox + break; + + case stateVoiceFontsWait: // Await voice font list + case stateVoiceFontsReceived: // Voice font list received + // moved to retrieveVoiceFonts + break; //MARK: stateCreatingSessionGroup case stateCreatingSessionGroup: @@ -1213,272 +719,25 @@ void LLVivoxVoiceClient::stateMachine() } break; - //MARK: stateRetrievingParcelVoiceInfo - case stateRetrievingParcelVoiceInfo: - // wait until parcel voice info is received. - if(mSessionTerminateRequested || (!mVoiceEnabled && mIsInitialized)) - { - // if a terminate request has been received, - // bail and go to the stateSessionTerminated - // state. If the cap request is still pending, - // the responder will check to see if we've moved - // to a new session and won't change any state. - setState(stateSessionTerminated); - } - break; - - - //MARK: stateNoChannel - case stateNoChannel: - LL_DEBUGS("Voice") << "State No Channel" << LL_ENDL; - mSpatialJoiningNum = 0; - - - if(mSessionTerminateRequested || (!mVoiceEnabled && mIsInitialized)) - { - // TODO: Question: Is this the right way out of this state? - setState(stateSessionTerminated); - } - else if(mTuningMode) - { - mTuningExitState = stateNoChannel; - setState(stateMicTuningStart); - } - else if(mCaptureBufferMode) - { - setState(stateCaptureBufferPaused); - } - else if(checkParcelChanged() || (mNextAudioSession == NULL)) - { - // the parcel is changed, or we have no pending audio sessions, - // so try to request the parcel voice info - // if we have the cap, we move to the appropriate state - if(requestParcelVoiceInfo()) - { - setState(stateRetrievingParcelVoiceInfo); - } - } - else if(sessionNeedsRelog(mNextAudioSession)) - { - requestRelog(); - setState(stateSessionTerminated); - } - else if(mNextAudioSession) - { - sessionState *oldSession = mAudioSession; - - mAudioSession = mNextAudioSession; - mAudioSessionChanged = true; - if(!mAudioSession->mReconnect) - { - mNextAudioSession = NULL; - } - - // The old session may now need to be deleted. - reapSession(oldSession); - - if(!mAudioSession->mHandle.empty()) - { - // Connect to a session by session handle - - sessionMediaConnectSendMessage(mAudioSession); - } - else - { - // Connect to a session by URI - sessionCreateSendMessage(mAudioSession, true, false); - } - - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINING); - setState(stateJoiningSession); - } - break; - - //MARK: stateJoiningSession - case stateJoiningSession: // waiting for session handle - - // If this is true we have problem with connection to voice server (EXT-4313). - // See descriptions of mSpatialJoiningNum and MAX_NORMAL_JOINING_SPATIAL_NUM. - if(mSpatialJoiningNum == MAX_NORMAL_JOINING_SPATIAL_NUM) - { - // Notify observers to let them know there is problem with voice - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED); - LL_WARNS() << "There seems to be problem with connection to voice server. Disabling voice chat abilities." << LL_ENDL; - } - - // Increase mSpatialJoiningNum only for spatial sessions- it's normal to reach this case for - // example for p2p many times while waiting for response, so it can't be used to detect errors - if(mAudioSession && mAudioSession->mIsSpatial) - { - - mSpatialJoiningNum++; - } - - // joinedAudioSession() will transition from here to stateSessionJoined. - if(!mVoiceEnabled && mIsInitialized) - { - // User bailed out during connect -- jump straight to teardown. - setState(stateSessionTerminated); - } - else if(mSessionTerminateRequested) - { - if(mAudioSession && !mAudioSession->mHandle.empty()) - { - // Only allow direct exits from this state in p2p calls (for cancelling an invite). - // Terminating a half-connected session on other types of calls seems to break something in the vivox gateway. - if(mAudioSession->mIsP2P) - { - sessionMediaDisconnectSendMessage(mAudioSession); - setState(stateSessionTerminated); - } - } - } - break; - - //MARK: stateSessionJoined - case stateSessionJoined: // session handle received - - - mSpatialJoiningNum = 0; - // It appears that I need to wait for BOTH the SessionGroup.AddSession response and the SessionStateChangeEvent with state 4 - // before continuing from this state. They can happen in either order, and if I don't wait for both, things can get stuck. - // For now, the SessionGroup.AddSession response handler sets mSessionHandle and the SessionStateChangeEvent handler transitions to stateSessionJoined. - // This is a cheap way to make sure both have happened before proceeding. - if(mAudioSession && mAudioSession->mVoiceEnabled) - { - // Dirty state that may need to be sync'ed with the daemon. - mMuteMicDirty = true; - mSpeakerVolumeDirty = true; - mSpatialCoordsDirty = true; - - setState(stateRunning); - - // Start the throttle timer - mUpdateTimer.start(); - mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS); - - // Events that need to happen when a session is joined could go here. - // Maybe send initial spatial data? - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINED); - - } - else if(!mVoiceEnabled && mIsInitialized) - { - // User bailed out during connect -- jump straight to teardown. - setState(stateSessionTerminated); - } - else if(mSessionTerminateRequested) - { - // Only allow direct exits from this state in p2p calls (for cancelling an invite). - // Terminating a half-connected session on other types of calls seems to break something in the vivox gateway. - if(mAudioSession && mAudioSession->mIsP2P) - { - sessionMediaDisconnectSendMessage(mAudioSession); - setState(stateSessionTerminated); - } - } - break; - - //MARK: stateRunning - case stateRunning: // steady state - // Disabling voice or disconnect requested. - if((!mVoiceEnabled && mIsInitialized) || mSessionTerminateRequested) - { - leaveAudioSession(); - } - else - { - - if(!inSpatialChannel()) - { - // When in a non-spatial channel, never send positional updates. - mSpatialCoordsDirty = false; - } - else - { - if(checkParcelChanged()) - { - // if the parcel has changed, attempted to request the - // cap for the parcel voice info. If we can't request it - // then we don't have the cap URL so we do nothing and will - // recheck next time around - if(requestParcelVoiceInfo()) - { - // we did get the cap, and we made the request, - // so go wait for the response. - setState(stateRetrievingParcelVoiceInfo); - } - } - // Do the calculation that enforces the listener<->speaker tether (and also updates the real camera position) - enforceTether(); - - } - - // Do notifications for expiring Voice Fonts. - if (mVoiceFontExpiryTimer.hasExpired()) - { - expireVoiceFonts(); - mVoiceFontExpiryTimer.setTimerExpirySec(VOICE_FONT_EXPIRY_INTERVAL); - } - - // Send an update only if the ptt or mute state has changed (which shouldn't be able to happen that often - // -- the user can only click so fast) or every 10hz, whichever is sooner. - // Sending for every volume update causes an excessive flood of messages whenever a volume slider is dragged. - if((mAudioSession && mAudioSession->mMuteDirty) || mMuteMicDirty || mUpdateTimer.hasExpired()) - { - mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS); - sendPositionalUpdate(); - } - mIsInitialized = true; - } - break; - - //MARK: stateLeavingSession + case stateRetrievingParcelVoiceInfo: + break; + + case stateNoChannel: + // moved to waitForChannel + break; + case stateJoiningSession: // waiting for session handle + case stateSessionJoined: // session handle received + // moved to addAndJoinSession() + break; + //MARK: stateRunning + case stateRunning: // steady state + //MARK: stateRunning + // moved to runSession + break; case stateLeavingSession: // waiting for terminate session response - // The handler for the Session.Terminate response will transition from here to stateSessionTerminated. - break; - - //MARK: stateSessionTerminated - case stateSessionTerminated: - - // Must do this first, since it uses mAudioSession. - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); - - if(mAudioSession) - { - sessionState *oldSession = mAudioSession; - - mAudioSession = NULL; - // We just notified status observers about this change. Don't do it again. - mAudioSessionChanged = false; - - // The old session may now need to be deleted. - reapSession(oldSession); - } - else - { - LL_WARNS("Voice") << "stateSessionTerminated with NULL mAudioSession" << LL_ENDL; - } - - // Always reset the terminate request flag when we get here. - mSessionTerminateRequested = false; - - if((mVoiceEnabled || !mIsInitialized) && !mRelogRequested && !LLApp::isExiting()) - { - // Just leaving a channel, go back to stateNoChannel (the "logged in but have no channel" state). - setState(stateNoChannel); - } - else - { - // Shutting down voice, continue with disconnecting. - logout(); - - // The state machine will take it from here - mRelogRequested = false; - } - - break; - + case stateSessionTerminated: + // moved to terminateAudioSession + break; //MARK: stateLoggingOut case stateLoggingOut: // waiting for logout response // The handler for the AccountLoginStateChangeEvent will transition from here to stateLoggedOut. @@ -1503,7 +762,8 @@ void LLVivoxVoiceClient::stateMachine() } break; - //MARK: stateConnectorStopping +//------------------------------------------------------------------------- + //MARK: stateConnectorStopping case stateConnectorStopping: // waiting for connector stop // The handler for the Connector.InitiateShutdown response will transition from here to stateConnectorStopped. mShutdownComplete = true; @@ -1514,7 +774,8 @@ void LLVivoxVoiceClient::stateMachine() setState(stateDisableCleanup); break; - //MARK: stateConnectorFailed +//------------------------------------------------------------------------- + //MARK: stateConnectorFailed case stateConnectorFailed: setState(stateConnectorFailedWaiting); break; @@ -1525,6 +786,7 @@ void LLVivoxVoiceClient::stateMachine() setState(stateDisableCleanup); } break; +//------------------------------------------------------------------------- //MARK: stateLoginFailed case stateLoginFailed: @@ -1537,33 +799,9 @@ void LLVivoxVoiceClient::stateMachine() setState(stateDisableCleanup); } break; - - //MARK: stateJoinSessionFailed - case stateJoinSessionFailed: - // Transition to error state. Send out any notifications here. - if(mAudioSession) - { - LL_WARNS("Voice") << "stateJoinSessionFailed: (" << mAudioSession->mErrorStatusCode << "): " << mAudioSession->mErrorStatusString << LL_ENDL; - } - else - { - LL_WARNS("Voice") << "stateJoinSessionFailed with no current session" << LL_ENDL; - } - - notifyStatusObservers(LLVoiceClientStatusObserver::ERROR_UNKNOWN); - setState(stateJoinSessionFailedWaiting); - break; - - //MARK: stateJoinSessionFailedWaiting - case stateJoinSessionFailedWaiting: - // Joining a channel failed, either due to a failed channel name -> sip url lookup or an error from the join message. - // Region crossings may leave this state and try the join again. - if(mSessionTerminateRequested) - { - setState(stateSessionTerminated); - } - break; - + case stateJoinSessionFailed: + case stateJoinSessionFailedWaiting: + break; //MARK: stateJail case stateJail: // We have given up. Do nothing. @@ -1571,19 +809,1159 @@ void LLVivoxVoiceClient::stateMachine() } - if (mAudioSessionChanged) - { - mAudioSessionChanged = false; - notifyParticipantObservers(); - notifyVoiceFontObservers(); - } - else if (mAudioSession && mAudioSession->mParticipantsChanged) - { - mAudioSession->mParticipantsChanged = false; - notifyParticipantObservers(); - } +// if (mAudioSessionChanged) +// { +// mAudioSessionChanged = false; +// notifyParticipantObservers(); +// notifyVoiceFontObservers(); +// } +// else if (mAudioSession && mAudioSession->mParticipantsChanged) +// { +// mAudioSession->mParticipantsChanged = false; +// notifyParticipantObservers(); +// } +} + +//========================================================================= +// the following are methods to support the coroutine implementation of the +// voice connection and processing. They should only be called in the context +// of a coroutine. +// +// calls to setState() in these are historical and used because some of the other +// query routines will ask what state the state machine is in. +// +void LLVivoxVoiceClient::voiceControlCoro() +{ + LLCoros::set_consuming(true); + startAndConnectSession(); + + if (mTuningMode) + { + performMicTuning(stateIdle); + } + else if (mVoiceEnabled) + { + waitForChannel(); + } + +} + + +bool LLVivoxVoiceClient::startAndConnectSession() +{ + if (!startAndLaunchDaemon()) + { + setState(stateJail); + return false; + } + + if (!provisionVoiceAccount()) + { + giveUp(); + return false; + } + + if (!establishVoiceConnection()) + { + if (getState() != stateConnectorFailed) + { + setState(stateLoggedOut); + } + giveUp(); + return false; + } + + setState(stateIdle); + + return true; +} + +bool LLVivoxVoiceClient::startAndLaunchDaemon() +{ + //--------------------------------------------------------------------- + setState(stateStart); + + if (gSavedSettings.getBOOL("CmdLineDisableVoice")) + { + // Voice is locked out, we must not launch the vivox daemon. + setState(stateJail); + return false; + } + + if (!isGatewayRunning() && gSavedSettings.getBOOL("EnableVoiceChat")) + { +#ifndef VIVOXDAEMON_REMOTEHOST + // Launch the voice daemon + + // *FIX:Mani - Using the executable dir instead + // of mAppRODataDir, the working directory from which the app + // is launched. + //std::string exe_path = gDirUtilp->getAppRODataDir(); + std::string exe_path = gDirUtilp->getExecutableDir(); + exe_path += gDirUtilp->getDirDelimiter(); +#if LL_WINDOWS + exe_path += "SLVoice.exe"; +#elif LL_DARWIN + exe_path += "../Resources/SLVoice"; +#else + exe_path += "SLVoice"; +#endif + // See if the vivox executable exists + llstat s; + if (!LLFile::stat(exe_path, &s)) + { + // vivox executable exists. Build the command line and launch the daemon. + LLProcess::Params params; + params.executable = exe_path; + + std::string loglevel = gSavedSettings.getString("VivoxDebugLevel"); + std::string shutdown_timeout = gSavedSettings.getString("VivoxShutdownTimeout"); + if (loglevel.empty()) + { + loglevel = "-1"; // turn logging off completely, was 0 for error level logging. + } + + params.args.add("-ll"); + params.args.add(loglevel); + + std::string log_folder = gSavedSettings.getString("VivoxLogDirectory"); + + if (log_folder.empty()) + { + log_folder = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ""); + } + + params.args.add("-lf"); + params.args.add(log_folder); + + if (!shutdown_timeout.empty()) + { + params.args.add("-st"); + params.args.add(shutdown_timeout); + } + params.cwd = gDirUtilp->getAppRODataDir(); + sGatewayPtr = LLProcess::create(params); + + mDaemonHost = LLHost(gSavedSettings.getString("VivoxVoiceHost").c_str(), gSavedSettings.getU32("VivoxVoicePort")); + } + else + { + LL_INFOS("Voice") << exe_path << " not found." << LL_ENDL; + return false; + } +#else + // SLIM SDK: port changed from 44124 to 44125. + // We can connect to a client gateway running on another host. This is useful for testing. + // To do this, launch the gateway on a nearby host like this: + // vivox-gw.exe -p tcp -i 0.0.0.0:44125 + // and put that host's IP address here. + mDaemonHost = LLHost(gSavedSettings.getString("VivoxVoiceHost"), gSavedSettings.getU32("VivoxVoicePort")); +#endif + + // Dirty the states we'll need to sync with the daemon when it comes up. + mMuteMicDirty = true; + mMicVolumeDirty = true; + mSpeakerVolumeDirty = true; + mSpeakerMuteDirty = true; + // These only need to be set if they're not default (i.e. empty string). + mCaptureDeviceDirty = !mCaptureDevice.empty(); + mRenderDeviceDirty = !mRenderDevice.empty(); + + mMainSessionGroupHandle.clear(); + } + + //--------------------------------------------------------------------- + llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); + setState(stateDaemonLaunched); + + LL_DEBUGS("Voice") << "Connecting to vivox daemon:" << mDaemonHost << LL_ENDL; + + int connectAttempt = 0; + + while (!mConnected) + { + ++connectAttempt; + LL_DEBUGS("Voice") << "Connecting to vivox daemon:" << mDaemonHost << " (#" << connectAttempt << ")" << LL_ENDL; + closeSocket(); + if (!mSocket) + { + mSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP); + } + + mConnected = mSocket->blockingConnect(mDaemonHost); + if (!mConnected) + llcoro::suspendUntilTimeout(CONNECT_THROTTLE_SECONDS); + } + + //--------------------------------------------------------------------- + llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); + setState(stateConnecting); + + + while (!mPump) + { // Can't do this until we have the pump available. + llcoro::suspend(); + } + + + // MBW -- Note to self: pumps and pipes examples in + // indra/test/io.cpp + // indra/test/llpipeutil.{cpp|h} + + // Attach the pumps and pipes + + LLPumpIO::chain_t readChain; + + readChain.push_back(LLIOPipe::ptr_t(new LLIOSocketReader(mSocket))); + readChain.push_back(LLIOPipe::ptr_t(new LLVivoxProtocolParser())); + + mPump->addChain(readChain, NEVER_CHAIN_EXPIRY_SECS); + + //--------------------------------------------------------------------- + llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); + setState(stateConnected); + + // Initial devices query + getCaptureDevicesSendMessage(); + getRenderDevicesSendMessage(); + + mLoginRetryCount = 0; + + return true; +} + +bool LLVivoxVoiceClient::provisionVoiceAccount() +{ + + while (!gAgent.getRegion()) + { + // *TODO* Set up a call back on agent that sends a message to a pump we can use to wake up. + llcoro::suspend(); + } + + LLViewerRegion *region = gAgent.getRegion(); + + while (!region->capabilitiesReceived()) + { + // *TODO* Pump a message for wake up. + llcoro::suspend(); + } + + std::string url = region->getCapability("ProvisionVoiceAccountRequest"); + + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("voiceAccountProvision", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpOptions::ptr_t httpOpts = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); + int retryCount(0); + + LLSD result; + + do + { + result = httpAdapter->postAndSuspend(httpRequest, url, LLSD(), httpOpts); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (status == LLCore::HttpStatus(404)) + { + if (++retryCount > 5) + { + LL_WARNS("Voice") << "Could not access voice provision cap after 5 attempts." << LL_ENDL; + return false; + } + LL_WARNS("Voice") << "Provision CAP 404. Retrying in 1.0" << LL_ENDL; + llcoro::suspendUntilTimeout(1.0); + + continue; + } + else if (!status) + { + LL_WARNS("Voice") << "Unable to provision voice account." << LL_ENDL; + return false; + } + break; + } while (true); + + std::string voiceSipUriHostname; + std::string voiceAccountServerUri; + std::string voiceUserName = result["username"].asString(); + std::string voicePassword = result["password"].asString(); + + //LL_DEBUGS("Voice") << "ProvisionVoiceAccountRequest response:" << dumpResponse() << LL_ENDL; + + if (result.has("voice_sip_uri_hostname")) + voiceSipUriHostname = result["voice_sip_uri_hostname"].asString(); + + // this key is actually misnamed -- it will be an entire URI, not just a hostname. + if (result.has("voice_account_server_name")) + voiceAccountServerUri = result["voice_account_server_name"].asString(); + + setLoginInfo(voiceUserName, voicePassword, voiceSipUriHostname, voiceAccountServerUri); + + return true; +} + +bool LLVivoxVoiceClient::establishVoiceConnection() +{ + LLEventPump &voiceConnectPump = LLEventPumps::instance().obtain("vivoxClientPump"); + + if (!mVoiceEnabled && mIsInitialized) + return false; + + setState(stateConnectorStart); + + connectorCreate(); + + setState(stateConnectorStarting); + + LLSD result; + do + { + result = llcoro::suspendUntilEventOn(voiceConnectPump); + LL_INFOS("Voice") << "event=" << ll_pretty_print_sd(result) << LL_ENDL; + } + while (!result.has("connector")); + + if (!result["connector"]) + { + + setState(stateConnectorFailed); + return false; + } + + setState(stateConnectorStarted); + if (!mVoiceEnabled && mIsInitialized) + return false; + + return true; +} + +bool LLVivoxVoiceClient::loginToVivox() +{ + int loginRetryCount(0); + LLEventPump &voicePump = LLEventPumps::instance().obtain("vivoxClientPump"); + + bool response_ok(false); + bool account_login(false); + bool send_login(true); + + do + { + setState(stateLoggingIn); + if (send_login) + loginSendMessage(); + + send_login = false; + + LLSD result; + + result = llcoro::suspendUntilEventOn(voicePump); + LL_INFOS("Voice") << "event=" << ll_pretty_print_sd(result) << LL_ENDL; + + if (result.has("login")) + { + std::string loginresp = result["login"]; + + if (loginresp == "retry") + { + if (!loginRetryCount) + { // on first retry notify user + notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGIN_RETRY); + } + + if ((++loginRetryCount > MAX_LOGIN_RETRIES) || (!result["login_retry"])) + { + LL_WARNS("Voice") << "too many login retries, giving up." << LL_ENDL; + LLSD args; + std::stringstream errs; + errs << mVoiceAccountServerURI << "\n:UDP: 3478, 3479, 5060, 5062, 12000-17000"; + args["HOSTID"] = errs.str(); + mTerminateDaemon = true; + if (LLGridManager::getInstance()->isSystemGrid()) + { + LLNotificationsUtil::add("NoVoiceConnect", args); + } + else + { + LLNotificationsUtil::add("NoVoiceConnect-GIAB", args); + } + + setState(stateLoginFailed); + return false; + } + + response_ok = false; + account_login = false; + send_login = true; + + LL_INFOS("Voice") << "will retry login in " << LOGIN_RETRY_SECONDS << " seconds." << LL_ENDL; + setState(stateLoginRetryWait); + llcoro::suspendUntilTimeout(LOGIN_RETRY_SECONDS); + } + else if (loginresp == "failed") + { + setState(stateLoginFailed); + return false; + } + else if (loginresp == "response_ok") + { + response_ok = true; + } + else if (loginresp == "account_login") + { + account_login = true; + } + } + + } while (!response_ok || !account_login); + + setState(stateLoggedIn); + notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGGED_IN); + + // Set up the mute list observer if it hasn't been set up already. + if ((!sMuteListListener_listening)) + { + LLMuteList::getInstance()->addObserver(&mutelist_listener); + sMuteListListener_listening = true; + } + + // Set the initial state of mic mute, local speaker volume, etc. + sendLocalAudioUpdates(); + + return true; +} + +bool LLVivoxVoiceClient::retrieveVoiceFonts() +{ + LLEventPump &voicePump = LLEventPumps::instance().obtain("vivoxClientPump"); + + // Request the set of available voice fonts. + setState(stateVoiceFontsWait); + refreshVoiceEffectLists(true); + + LLSD result; + do + { + result = llcoro::suspendUntilEventOn(voicePump); + + LL_INFOS("Voice") << "event=" << ll_pretty_print_sd(result) << LL_ENDL; + if (result.has("voice_fonts")) + break; + } while (true); + + + mVoiceFontExpiryTimer.start(); + mVoiceFontExpiryTimer.setTimerExpirySec(VOICE_FONT_EXPIRY_INTERVAL); + + return result["voice_fonts"].asBoolean(); +} + +bool LLVivoxVoiceClient::requestParcelVoiceInfo() +{ + setState(stateRetrievingParcelVoiceInfo); + + LL_INFOS("Voice") << "Requesting voice info for Parcel" << LL_ENDL; + + LLViewerRegion * region = gAgent.getRegion(); + if (region == NULL || !region->capabilitiesReceived()) + { + LL_DEBUGS("Voice") << "ParcelVoiceInfoRequest capability not yet available, deferring" << LL_ENDL; + return false; + } + + // grab the cap. + std::string url = gAgent.getRegion()->getCapability("ParcelVoiceInfoRequest"); + if (url.empty()) + { + // Region dosn't have the cap. Stop probing. + LL_DEBUGS("Voice") << "ParcelVoiceInfoRequest capability not available in this region" << LL_ENDL; + setState(stateDisableCleanup); + return false; + } + + // update the parcel + checkParcelChanged(true); + + LL_DEBUGS("Voice") << "sending ParcelVoiceInfoRequest (" << mCurrentRegionName << ", " << mCurrentParcelLocalID << ")" << LL_ENDL; + + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("parcelVoiceInfoRequest", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + + LLSD result = httpAdapter->postAndSuspend(httpRequest, url, LLSD()); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (mSessionTerminateRequested || (!mVoiceEnabled && mIsInitialized)) + { + // if a terminate request has been received, + // bail and go to the stateSessionTerminated + // state. If the cap request is still pending, + // the responder will check to see if we've moved + // to a new session and won't change any state. + terminateAudioSession(true); + return false; + } + + if ((!status) || (mSessionTerminateRequested || (!mVoiceEnabled && mIsInitialized))) + { + if (mSessionTerminateRequested || (!mVoiceEnabled && mIsInitialized)) + { + LL_WARNS("Voice") << "Session terminated." << LL_ENDL; + } + + LL_WARNS("Voice") << "No voice on parcel" << LL_ENDL; + sessionTerminate(); + return false; + } + + std::string uri; + std::string credentials; + + if (result.has("voice_credentials")) + { + LLSD voice_credentials = result["voice_credentials"]; + if (voice_credentials.has("channel_uri")) + { + uri = voice_credentials["channel_uri"].asString(); + } + if (voice_credentials.has("channel_credentials")) + { + credentials = + voice_credentials["channel_credentials"].asString(); + } + } + + if (!uri.empty()) + LL_INFOS("Voice") << "Voice URI is " << uri << LL_ENDL; + + // set the spatial channel. If no voice credentials or uri are + // available, then we simply drop out of voice spatially. + return !setSpatialChannel(uri, credentials); +} + +bool LLVivoxVoiceClient::addAndJoinSession(sessionState *nextSession) +{ + LLEventPump &voicePump = LLEventPumps::instance().obtain("vivoxClientPump"); + mIsJoiningSession = true; + + sessionState *oldSession = mAudioSession; + + LL_INFOS("Voice") << "Adding or joining voice session " << nextSession->mHandle << LL_ENDL; + + mAudioSession = nextSession; + mAudioSessionChanged = true; + if (!mAudioSession->mReconnect) + { + mNextAudioSession = NULL; + } + + // The old session may now need to be deleted. + reapSession(oldSession); + + if (!mAudioSession->mHandle.empty()) + { + // Connect to a session by session handle + + sessionMediaConnectSendMessage(mAudioSession); + } + else + { + // Connect to a session by URI + sessionCreateSendMessage(mAudioSession, true, false); + } + + notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINING); + + llcoro::suspend(); + + LLSD result; + + if (mSpatialJoiningNum == MAX_NORMAL_JOINING_SPATIAL_NUM) + { + // Notify observers to let them know there is problem with voice + notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED); + LL_WARNS() << "There seems to be problem with connection to voice server. Disabling voice chat abilities." << LL_ENDL; + } + + // Increase mSpatialJoiningNum only for spatial sessions- it's normal to reach this case for + // example for p2p many times while waiting for response, so it can't be used to detect errors + if (mAudioSession && mAudioSession->mIsSpatial) + { + mSpatialJoiningNum++; + } + + // joinedAudioSession() will transition from here to stateSessionJoined. + if (!mVoiceEnabled && mIsInitialized) + { + mIsJoiningSession = false; + // User bailed out during connect -- jump straight to teardown. + terminateAudioSession(true); + return false; + } + else if (mSessionTerminateRequested) + { + if (mAudioSession && !mAudioSession->mHandle.empty()) + { + // Only allow direct exits from this state in p2p calls (for cancelling an invite). + // Terminating a half-connected session on other types of calls seems to break something in the vivox gateway. + if (mAudioSession->mIsP2P) + { + terminateAudioSession(true); + mIsJoiningSession = false; + return false; + } + } + } + + bool added(true); + bool joined(false); + + // It appears that I need to wait for BOTH the SessionGroup.AddSession response and the SessionStateChangeEvent with state 4 + // before continuing from this state. They can happen in either order, and if I don't wait for both, things can get stuck. + // For now, the SessionGroup.AddSession response handler sets mSessionHandle and the SessionStateChangeEvent handler transitions to stateSessionJoined. + // This is a cheap way to make sure both have happened before proceeding. + do + { + result = llcoro::suspendUntilEventOn(voicePump); + + LL_INFOS("Voice") << "event=" << ll_pretty_print_sd(result) << LL_ENDL; + if (result.has("session")) + { + if (result.has("handle")) + { + if (result["handle"] != mAudioSession->mHandle) + { + LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while waiting for \"" << mAudioSession->mHandle << "\"." << LL_ENDL; + continue; + } + } + + std::string message = result["session"].asString(); + if ((message == "added") || (message == "created")) + added = true; + else if (message == "joined") + joined = true; + else if (message == "failed") + { + mIsJoiningSession = false; + setState(stateJoinSessionFailed); + return false; + } + } + } while (!added || !joined); + + mIsJoiningSession = false; + + if (mSpatialJoiningNum > 100) + { + LL_WARNS("Voice") << "There seems to be problem with connecting to a voice channel. Frames to join were " << mSpatialJoiningNum << LL_ENDL; + } + + mSpatialJoiningNum = 0; + // Dirty state that may need to be sync'ed with the daemon. + mMuteMicDirty = true; + mSpeakerVolumeDirty = true; + mSpatialCoordsDirty = true; + + // Events that need to happen when a session is joined could go here. + // Maybe send initial spatial data? + notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINED); + + return true; +} + +bool LLVivoxVoiceClient::terminateAudioSession(bool wait) +{ + + if (mAudioSession) + { + LL_INFOS("Voice") << "Terminating current voice session " << mAudioSession->mHandle << LL_ENDL; + + if (!mAudioSession->mHandle.empty()) + { + +#if RECORD_EVERYTHING + // HACK: for testing only + // Save looped recording + std::string savepath("/tmp/vivoxrecording"); + { + time_t now = time(NULL); + const size_t BUF_SIZE = 64; + char time_str[BUF_SIZE]; /* Flawfinder: ignore */ + + strftime(time_str, BUF_SIZE, "%Y-%m-%dT%H:%M:%SZ", gmtime(&now)); + savepath += time_str; + } + recordingLoopSave(savepath); +#endif + + sessionMediaDisconnectSendMessage(mAudioSession); + + if (wait) + { + LLEventPump &voicePump = LLEventPumps::instance().obtain("vivoxClientPump"); + LLSD result; + do + { + result = llcoro::suspendUntilEventOn(voicePump); + + LL_INFOS("Voice") << "event=" << ll_pretty_print_sd(result) << LL_ENDL; + if (result.has("session")) + { + if (result.has("handle")) + { + if (result["handle"] != mAudioSession->mHandle) + { + LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while waiting for \"" << mAudioSession->mHandle << "\"." << LL_ENDL; + continue; + } + } + + std::string message = result["session"].asString(); + if (message == "removed") + break; + } + } while (true); + + } + } + else + { + LL_WARNS("Voice") << "called with no session handle" << LL_ENDL; + } + + sessionState *oldSession = mAudioSession; + + mAudioSession = NULL; + // We just notified status observers about this change. Don't do it again. + mAudioSessionChanged = false; + + // The old session may now need to be deleted. + reapSession(oldSession); + + } + else + { + LL_WARNS("Voice") << "stateSessionTerminated with NULL mAudioSession" << LL_ENDL; + } + + notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); + setState(stateSessionTerminated); + + // Always reset the terminate request flag when we get here. + // Some slower PCs have a race condition where they can switch to an incoming P2P call faster than the state machine leaves + // the region chat. + mSessionTerminateRequested = false; + + if ((mVoiceEnabled || !mIsInitialized) && !mRelogRequested && !LLApp::isExiting()) + { + // Just leaving a channel, go back to stateNoChannel (the "logged in but have no channel" state). + return true; + } + + return false; +} + +bool LLVivoxVoiceClient::waitForChannel() +{ + LL_INFOS("Voice") << "Waiting for channel" << LL_ENDL; + + do + { + if (!loginToVivox()) + { + setState(stateLoginFailed); + return false; + } + + if (LLVoiceClient::instance().getVoiceEffectEnabled()) + { + retrieveVoiceFonts(); + + // Request the set of available voice fonts. + refreshVoiceEffectLists(true); + } + +#if USE_SESSION_GROUPS + // create the main session group + setState(stateCreatingSessionGroup); + sessionGroupCreateSendMessage(); +#endif + + do + { + LL_INFOS("Voice") << "Waiting for channel" << LL_ENDL; + setState(stateNoChannel); + llcoro::suspend(); + + if (mTuningMode) + { + performMicTuning(stateNoChannel); + } + else if (mCaptureBufferMode) + { + recordingAndPlaybackMode(); + } + else if (checkParcelChanged() || (mNextAudioSession == NULL)) + { + // the parcel is changed, or we have no pending audio sessions, + // so try to request the parcel voice info + // if we have the cap, we move to the appropriate state + requestParcelVoiceInfo(); + } + else if (sessionNeedsRelog(mNextAudioSession)) + { + requestRelog(); + terminateAudioSession(true); + break; + } + else if (mNextAudioSession) + { + runSession(mNextAudioSession); + } + + if (!mNextAudioSession) + llcoro::suspendUntilTimeout(1.0); + } while (mVoiceEnabled && !mRelogRequested); + + } while (mVoiceEnabled && mRelogRequested); + + return true; +} + +bool LLVivoxVoiceClient::runSession(sessionState *session) +{ + LL_INFOS("Voice") << "running new voice session " << session->mHandle << LL_ENDL; + bool doTerminate(true); + + if (!addAndJoinSession(session)) + { + notifyStatusObservers(LLVoiceClientStatusObserver::ERROR_UNKNOWN); + + if (mSessionTerminateRequested) + terminateAudioSession(true); + + return false; + } + + notifyParticipantObservers(); + notifyVoiceFontObservers(); + + LLSD timeoutEvent = LLSD::emptyMap(); + timeoutEvent["timeout"] = LLSD::Boolean(true); + + LLEventPump &voicePump = LLEventPumps::instance().obtain("vivoxClientPump"); + LLEventTimeout timeout(voicePump); + mIsInChannel = true; + + while (mVoiceEnabled && !mSessionTerminateRequested && !mTuningMode) + { + setState(stateRunning); + + if (!inSpatialChannel()) + { + // When in a non-spatial channel, never send positional updates. + mSpatialCoordsDirty = false; + } + else + { + updatePosition(); + + if (checkParcelChanged()) + { + // if the parcel has changed, attempted to request the + // cap for the parcel voice info. If we can't request it + // then we don't have the cap URL so we do nothing and will + // recheck next time around + if (requestParcelVoiceInfo()) + { // The parcel voice URI has changed.. break out and reconnect. + break; + } + } + // Do the calculation that enforces the listener<->speaker tether (and also updates the real camera position) + enforceTether(); + } + + // Do notifications for expiring Voice Fonts. + if (mVoiceFontExpiryTimer.hasExpired()) + { + expireVoiceFonts(); + mVoiceFontExpiryTimer.setTimerExpirySec(VOICE_FONT_EXPIRY_INTERVAL); + } + + // Send an update only if the ptt or mute state has changed (which shouldn't be able to happen that often + // -- the user can only click so fast) or every 10hz, whichever is sooner. + // Sending for every volume update causes an excessive flood of messages whenever a volume slider is dragged. + if ((mAudioSession && mAudioSession->mMuteDirty) || mMuteMicDirty) + { + sendPositionalUpdate(); + } + mIsInitialized = true; + timeout.eventAfter(UPDATE_THROTTLE_SECONDS, timeoutEvent); + LLSD result = llcoro::suspendUntilEventOn(timeout); + if (!result.has("timeout")) // logging the timeout event spamms the log + LL_INFOS("Voice") << "event=" << ll_pretty_print_sd(result) << LL_ENDL; + if (result.has("session")) + { + if (result.has("handle")) + { + if (result["handle"] != mAudioSession->mHandle) + { + LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while waiting for \"" << mAudioSession->mHandle << "\"." << LL_ENDL; + continue; + } + } + + std::string message = result["session"]; + + if (message == "removed") + { + doTerminate = false; + break; + } + } + + if (mAudioSession && mAudioSession->mParticipantsChanged) + { + mAudioSession->mParticipantsChanged = false; + notifyParticipantObservers(); + } + } + + mIsInChannel = false; + if (doTerminate) + terminateAudioSession(true); + return true; +} + +void LLVivoxVoiceClient::recordingAndPlaybackMode() +{ + LL_INFOS("Voice") << "In voice capture/playback mode." << LL_ENDL; + LLEventPump &voicePump = LLEventPumps::instance().obtain("vivoxClientPump"); + + while (true) + { + setState(stateCaptureBufferPaused); + + LLSD command; + do + { + command = llcoro::suspendUntilEventOn(voicePump); + LL_INFOS("Voice") << "event=" << ll_pretty_print_sd(command) << LL_ENDL; + } while (!command.has("recplay")); + + if (command["recplay"].asString() == "quit") + { + mCaptureBufferMode = false; + break; + } + else if (command["recplay"].asString() == "record") + { + voiceRecordBuffer(); + } + else if (command["recplay"].asString() == "playback") + { + voicePlaybackBuffer(); + } + } + + LL_INFOS("Voice") << "Leaving capture/playback mode." << LL_ENDL; + mCaptureBufferRecording = false; + mCaptureBufferRecorded = false; + mCaptureBufferPlaying = false; + + return; } +int LLVivoxVoiceClient::voiceRecordBuffer() +{ + setState(stateCaptureBufferRecStart); + + LLSD timeoutResult; + timeoutResult["recplay"] = LLSD::String("stop"); + + LL_INFOS("Voice") << "Recording voice buffer" << LL_ENDL; + + LLEventPump &voicePump = LLEventPumps::instance().obtain("vivoxClientPump"); + LLEventTimeout timeout(voicePump); + timeout.eventAfter(CAPTURE_BUFFER_MAX_TIME, timeoutResult); + LLSD result; + + setState(stateCaptureBufferRecording); + captureBufferRecordStartSendMessage(); + + notifyVoiceFontObservers(); + do + { + result = llcoro::suspendUntilEventOn(voicePump); + LL_INFOS("Voice") << "event=" << ll_pretty_print_sd(result) << LL_ENDL; + } while (!result.has("recplay")); + + mCaptureBufferRecorded = true; + + captureBufferRecordStopSendMessage(); + mCaptureBufferRecording = false; + + // Update UI, should really use a separate callback. + notifyVoiceFontObservers(); + + return true; + /*TODO expand return to move directly into play*/ +} + +int LLVivoxVoiceClient::voicePlaybackBuffer() +{ + setState(stateCaptureBufferPlayStart); + + LLSD timeoutResult; + timeoutResult["recplay"] = LLSD::String("stop"); + + LL_INFOS("Voice") << "Playing voice buffer" << LL_ENDL; + + LLEventPump &voicePump = LLEventPumps::instance().obtain("vivoxClientPump"); + LLEventTimeout timeout(voicePump); + timeout.eventAfter(CAPTURE_BUFFER_MAX_TIME, timeoutResult); + LLSD result; + + setState(stateCaptureBufferPlaying); + do + { + captureBufferPlayStartSendMessage(mPreviewVoiceFont); + + // Store the voice font being previewed, so that we know to restart if it changes. + mPreviewVoiceFontLast = mPreviewVoiceFont; + + do + { + // Update UI, should really use a separate callback. + notifyVoiceFontObservers(); + + result = llcoro::suspendUntilEventOn(voicePump); + LL_INFOS("Voice") << "event=" << ll_pretty_print_sd(result) << LL_ENDL; + } while (!result.has("recplay")); + + if (result["recplay"] == "playback") + continue; // restart playback... May be a font change. + + break; + } while (true); + + // Stop playing. + captureBufferPlayStopSendMessage(); + mCaptureBufferPlaying = false; + + // Update UI, should really use a separate callback. + notifyVoiceFontObservers(); + + return true; +} + + +bool LLVivoxVoiceClient::performMicTuning(LLVivoxVoiceClient::state exitState) +{ + LL_INFOS("Voice") << "Entering voice tuning mode." << LL_ENDL; + + mIsInTuningMode = true; + llcoro::suspend(); + + while (mTuningMode) + { + + if (mCaptureDeviceDirty || mRenderDeviceDirty) + { + // These can't be changed while in tuning mode. Set them before starting. + std::ostringstream stream; + + buildSetCaptureDevice(stream); + buildSetRenderDevice(stream); + + if (!stream.str().empty()) + { + writeString(stream.str()); + } + + llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); + } + + // loop mic back to render device. + //setMuteMic(0); // make sure the mic is not muted + std::ostringstream stream; + + stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.MuteLocalMic.1\">" + << "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>" + << "<Value>false</Value>" + << "</Request>\n\n\n"; + + // Dirty the mute mic state so that it will get reset when we finishing previewing + mMuteMicDirty = true; + mTuningSpeakerVolumeDirty = true; + + writeString(stream.str()); + tuningCaptureStartSendMessage(1); // 1-loop, zero, don't loop + + //--------------------------------------------------------------------- + llcoro::suspend(); + + while (mTuningMode && !mCaptureDeviceDirty && !mRenderDeviceDirty) + { + // process mic/speaker volume changes + if (mTuningMicVolumeDirty || mTuningSpeakerVolumeDirty) + { + std::ostringstream stream; + + if (mTuningMicVolumeDirty) + { + LL_INFOS("Voice") << "setting tuning mic level to " << mTuningMicVolume << LL_ENDL; + stream + << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetMicLevel.1\">" + << "<Level>" << mTuningMicVolume << "</Level>" + << "</Request>\n\n\n"; + } + + if (mTuningSpeakerVolumeDirty) + { + stream + << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetSpeakerLevel.1\">" + << "<Level>" << mTuningSpeakerVolume << "</Level>" + << "</Request>\n\n\n"; + } + + mTuningMicVolumeDirty = false; + mTuningSpeakerVolumeDirty = false; + + if (!stream.str().empty()) + { + writeString(stream.str()); + } + } + llcoro::suspend(); + } + + //--------------------------------------------------------------------- + + // transition out of mic tuning + tuningCaptureStopSendMessage(); + if (mCaptureDeviceDirty || mRenderDeviceDirty) + { + llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); + } + } + + mIsInTuningMode = false; + + //--------------------------------------------------------------------- + setState(exitState); + return true; +} + +//========================================================================= + void LLVivoxVoiceClient::closeSocket(void) { mSocket.reset(); @@ -1604,7 +1982,8 @@ void LLVivoxVoiceClient::loginSendMessage() << "<AccountName>" << mAccountName << "</AccountName>" << "<AccountPassword>" << mAccountPassword << "</AccountPassword>" << "<AudioSessionAnswerMode>VerifyAnswer</AudioSessionAnswerMode>" - << "<EnableBuddiesAndPresence>false</EnableBuddiesAndPresence>" + << "<EnableBuddiesAndPresence>false</EnableBuddiesAndPresence>" + << "<EnablePresencePersistence>0</EnablePresencePersistence>" << "<BuddyManagementMode>Application</BuddyManagementMode>" << "<ParticipantPropertyFrequency>5</ParticipantPropertyFrequency>" << (autoPostCrashDumps?"<AutopostCrashDumps>true</AutopostCrashDumps>":"") @@ -1802,9 +2181,11 @@ void LLVivoxVoiceClient::leaveAudioSession() // Skip the join failed transition state so we don't send out error notifications. setState(stateJoinSessionFailedWaiting); break; +#if 0 case stateJoiningSession: case stateSessionJoined: case stateRunning: + case stateSessionTerminated: if(!mAudioSession->mHandle.empty()) { @@ -1835,10 +2216,13 @@ void LLVivoxVoiceClient::leaveAudioSession() case stateJoinSessionFailed: case stateJoinSessionFailedWaiting: setState(stateSessionTerminated); + break; + case stateLeavingSession: // managed to get back to this case statement before the media gets disconnected. break; +#endif default: - LL_WARNS("Voice") << "called from unknown state" << LL_ENDL; + LL_WARNS("Voice") << "called from unknown state " << getState() << LL_ENDL; break; } } @@ -1852,7 +2236,10 @@ void LLVivoxVoiceClient::leaveAudioSession() void LLVivoxVoiceClient::sessionTerminateSendMessage(sessionState *session) { std::ostringstream stream; - + + sessionGroupTerminateSendMessage(session); + return; + /* LL_DEBUGS("Voice") << "Sending Session.Terminate with handle " << session->mHandle << LL_ENDL; stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Terminate.1\">" @@ -1860,6 +2247,7 @@ void LLVivoxVoiceClient::sessionTerminateSendMessage(sessionState *session) << "</Request>\n\n\n"; writeString(stream.str()); + */ } void LLVivoxVoiceClient::sessionGroupTerminateSendMessage(sessionState *session) @@ -1878,7 +2266,9 @@ void LLVivoxVoiceClient::sessionGroupTerminateSendMessage(sessionState *session) void LLVivoxVoiceClient::sessionMediaDisconnectSendMessage(sessionState *session) { std::ostringstream stream; - + sessionGroupTerminateSendMessage(session); + return; + /* LL_DEBUGS("Voice") << "Sending Session.MediaDisconnect with handle " << session->mHandle << LL_ENDL; stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.MediaDisconnect.1\">" @@ -1888,22 +2278,10 @@ void LLVivoxVoiceClient::sessionMediaDisconnectSendMessage(sessionState *session << "</Request>\n\n\n"; writeString(stream.str()); + */ } -void LLVivoxVoiceClient::sessionTextDisconnectSendMessage(sessionState *session) -{ - std::ostringstream stream; - - LL_DEBUGS("Voice") << "Sending Session.TextDisconnect with handle " << session->mHandle << LL_ENDL; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.TextDisconnect.1\">" - << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>" - << "<SessionHandle>" << session->mHandle << "</SessionHandle>" - << "</Request>\n\n\n"; - - writeString(stream.str()); -} void LLVivoxVoiceClient::getCaptureDevicesSendMessage() { @@ -1962,6 +2340,10 @@ void LLVivoxVoiceClient::setCaptureDevice(const std::string& name) } } } +void LLVivoxVoiceClient::setDevicesListUpdated(bool state) +{ + mDevicesListUpdated = state; +} void LLVivoxVoiceClient::clearRenderDevices() { @@ -2003,9 +2385,9 @@ void LLVivoxVoiceClient::setRenderDevice(const std::string& name) void LLVivoxVoiceClient::tuningStart() { - mTuningMode = true; - LL_DEBUGS("Voice") << "Starting tuning" << LL_ENDL; - if(getState() >= stateNoChannel) + LL_DEBUGS("Voice") << "Starting tuning" << LL_ENDL; + mTuningMode = true; + if (mIsInChannel) { LL_DEBUGS("Voice") << "no channel" << LL_ENDL; sessionTerminate(); @@ -2019,16 +2401,7 @@ void LLVivoxVoiceClient::tuningStop() bool LLVivoxVoiceClient::inTuningMode() { - bool result = false; - switch(getState()) - { - case stateMicTuningRunning: - result = true; - break; - default: - break; - } - return result; + return mIsInTuningMode; } void LLVivoxVoiceClient::tuningRenderStartSendMessage(const std::string& name, bool loop) @@ -2055,14 +2428,15 @@ void LLVivoxVoiceClient::tuningRenderStopSendMessage() writeString(stream.str()); } -void LLVivoxVoiceClient::tuningCaptureStartSendMessage(int duration) +void LLVivoxVoiceClient::tuningCaptureStartSendMessage(int loop) { LL_DEBUGS("Voice") << "sending CaptureAudioStart" << LL_ENDL; std::ostringstream stream; stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.CaptureAudioStart.1\">" - << "<Duration>" << duration << "</Duration>" + << "<Duration>-1</Duration>" + << "<LoopToRenderDevice>" << loop << "</LoopToRenderDevice>" << "</Request>\n\n\n"; writeString(stream.str()); @@ -2121,6 +2495,16 @@ bool LLVivoxVoiceClient::deviceSettingsAvailable() return result; } +bool LLVivoxVoiceClient::deviceSettingsUpdated() +{ + if (mDevicesListUpdated) + { + // a hot swap event or a polling of the audio devices has been parsed since the last redraw of the input and output device panel. + mDevicesListUpdated = !mDevicesListUpdated; // toggle the setting + return true; + } + return false; +} void LLVivoxVoiceClient::refreshDeviceLists(bool clearCurrentList) { @@ -2284,6 +2668,7 @@ void LLVivoxVoiceClient::sendPositionalUpdate(void) { std::ostringstream stream; + if(mSpatialCoordsDirty) { LLVector3 l, u, a, vel; @@ -2423,6 +2808,7 @@ void LLVivoxVoiceClient::sendPositionalUpdate(void) stream << "</ListenerPosition>"; + stream << "<ReqDispositionType>1</ReqDispositionType>"; //do not generate responses for update requests stream << "</Request>\n\n\n"; } @@ -2491,7 +2877,7 @@ void LLVivoxVoiceClient::sendPositionalUpdate(void) } } - buildLocalAudioUpdates(stream); + //sendLocalAudioUpdates(); obsolete, used to send volume setting on position updates if(!stream.str().empty()) { @@ -2531,68 +2917,73 @@ void LLVivoxVoiceClient::buildSetRenderDevice(std::ostringstream &stream) } } -void LLVivoxVoiceClient::buildLocalAudioUpdates(std::ostringstream &stream) +void LLVivoxVoiceClient::sendLocalAudioUpdates() { - buildSetCaptureDevice(stream); + // Check all of the dirty states and then send messages to those needing to be changed. + // Tuningmode hands its own mute settings. - buildSetRenderDevice(stream); + std::ostringstream stream; - if(mMuteMicDirty) + if (mMuteMicDirty && !mTuningMode) { mMuteMicDirty = false; // Send a local mute command. - - LL_DEBUGS("Voice") << "Sending MuteLocalMic command with parameter " << (mMuteMic?"true":"false") << LL_ENDL; + + LL_DEBUGS("Voice") << "Sending MuteLocalMic command with parameter " << (mMuteMic ? "true" : "false") << LL_ENDL; stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.MuteLocalMic.1\">" << "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>" - << "<Value>" << (mMuteMic?"true":"false") << "</Value>" + << "<Value>" << (mMuteMic ? "true" : "false") << "</Value>" << "</Request>\n\n\n"; - + } - if(mSpeakerMuteDirty) + if (mSpeakerMuteDirty && !mTuningMode) { - const char *muteval = ((mSpeakerVolume <= scale_speaker_volume(0))?"true":"false"); + const char *muteval = ((mSpeakerVolume <= scale_speaker_volume(0)) ? "true" : "false"); mSpeakerMuteDirty = false; - LL_INFOS("Voice") << "Setting speaker mute to " << muteval << LL_ENDL; - + LL_INFOS("Voice") << "Setting speaker mute to " << muteval << LL_ENDL; + stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.MuteLocalSpeaker.1\">" << "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>" << "<Value>" << muteval << "</Value>" - << "</Request>\n\n\n"; - + << "</Request>\n\n\n"; + } - - if(mSpeakerVolumeDirty) + + if (mSpeakerVolumeDirty) { mSpeakerVolumeDirty = false; - LL_INFOS("Voice") << "Setting speaker volume to " << mSpeakerVolume << LL_ENDL; + LL_INFOS("Voice") << "Setting speaker volume to " << mSpeakerVolume << LL_ENDL; stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.SetLocalSpeakerVolume.1\">" << "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>" << "<Value>" << mSpeakerVolume << "</Value>" << "</Request>\n\n\n"; - + } - - if(mMicVolumeDirty) + + if (mMicVolumeDirty) { mMicVolumeDirty = false; - LL_INFOS("Voice") << "Setting mic volume to " << mMicVolume << LL_ENDL; + LL_INFOS("Voice") << "Setting mic volume to " << mMicVolume << LL_ENDL; stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.SetLocalMicVolume.1\">" << "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>" << "<Value>" << mMicVolume << "</Value>" - << "</Request>\n\n\n"; + << "</Request>\n\n\n"; } - + + if (!stream.str().empty()) + { + writeString(stream.str()); + } } ///////////////////////////// @@ -2600,10 +2991,11 @@ void LLVivoxVoiceClient::buildLocalAudioUpdates(std::ostringstream &stream) void LLVivoxVoiceClient::connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle, std::string &versionID) { + LLSD result = LLSD::emptyMap(); + if(statusCode != 0) { LL_WARNS("Voice") << "Connector.Create response failure: " << statusString << LL_ENDL; - setState(stateConnectorFailed); LLSD args; std::stringstream errs; errs << mVoiceAccountServerURI << "\n:UDP: 3478, 3479, 5060, 5062, 12000-17000"; @@ -2617,6 +3009,8 @@ void LLVivoxVoiceClient::connectorCreateResponse(int statusCode, std::string &st { LLNotificationsUtil::add("NoVoiceConnect-GIAB", args); } + + result["connector"] = LLSD::Boolean(false); } else { @@ -2625,16 +3019,18 @@ void LLVivoxVoiceClient::connectorCreateResponse(int statusCode, std::string &st mVoiceVersion.serverVersion = versionID; mConnectorHandle = connectorHandle; mTerminateDaemon = false; - if(getState() == stateConnectorStarting) - { - setState(stateConnectorStarted); - } + + result["connector"] = LLSD::Boolean(true); } + + LLEventPumps::instance().post("vivoxClientPump", result); } void LLVivoxVoiceClient::loginResponse(int statusCode, std::string &statusString, std::string &accountHandle, int numberOfAliases) { - LL_DEBUGS("Voice") << "Account.Login response (" << statusCode << "): " << statusString << LL_ENDL; + LLSD result = LLSD::emptyMap(); + + LL_DEBUGS("Voice") << "Account.Login response (" << statusCode << "): " << statusString << LL_ENDL; // Status code of 20200 means "bad password". We may want to special-case that at some point. @@ -2642,24 +3038,28 @@ void LLVivoxVoiceClient::loginResponse(int statusCode, std::string &statusString { // Login failure which is probably caused by the delay after a user's password being updated. LL_INFOS("Voice") << "Account.Login response failure (" << statusCode << "): " << statusString << LL_ENDL; - setState(stateLoginRetry); + result["login"] = LLSD::String("retry"); } else if(statusCode != 0) { LL_WARNS("Voice") << "Account.Login response failure (" << statusCode << "): " << statusString << LL_ENDL; - setState(stateLoginFailed); + result["login"] = LLSD::String("failed"); } else { // Login succeeded, move forward. mAccountHandle = accountHandle; mNumberOfAliases = numberOfAliases; + result["login"] = LLSD::String("response_ok"); // This needs to wait until the AccountLoginStateChangeEvent is received. // if(getState() == stateLoggingIn) // { // setState(stateLoggedIn); // } } + + LLEventPumps::instance().post("vivoxClientPump", result); + } void LLVivoxVoiceClient::sessionCreateResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle) @@ -2680,8 +3080,13 @@ void LLVivoxVoiceClient::sessionCreateResponse(std::string &requestId, int statu session->mErrorStatusString = statusString; if(session == mAudioSession) { - setState(stateJoinSessionFailed); - } + LLSD vivoxevent = LLSD::emptyMap(); + + vivoxevent["handle"] = LLSD::String(sessionHandle); + vivoxevent["session"] = LLSD::String("failed"); + + LLEventPumps::instance().post("vivoxClientPump", vivoxevent); + } else { reapSession(session); @@ -2695,6 +3100,12 @@ void LLVivoxVoiceClient::sessionCreateResponse(std::string &requestId, int statu { setSessionHandle(session, sessionHandle); } + LLSD vivoxevent = LLSD::emptyMap(); + + vivoxevent["handle"] = LLSD::String(sessionHandle); + vivoxevent["session"] = LLSD::String("created"); + + LLEventPumps::instance().post("vivoxClientPump", vivoxevent); } } @@ -2716,7 +3127,12 @@ void LLVivoxVoiceClient::sessionGroupAddSessionResponse(std::string &requestId, session->mErrorStatusString = statusString; if(session == mAudioSession) { - setState(stateJoinSessionFailed); + LLSD vivoxevent = LLSD::emptyMap(); + + vivoxevent["handle"] = LLSD::String(sessionHandle); + vivoxevent["session"] = LLSD::String("failed"); + + LLEventPumps::instance().post("vivoxClientPump", vivoxevent); } else { @@ -2731,22 +3147,42 @@ void LLVivoxVoiceClient::sessionGroupAddSessionResponse(std::string &requestId, { setSessionHandle(session, sessionHandle); } + + LLSD vivoxevent = LLSD::emptyMap(); + + vivoxevent["handle"] = LLSD::String(sessionHandle); + vivoxevent["session"] = LLSD::String("added"); + + LLEventPumps::instance().post("vivoxClientPump", vivoxevent); + } } void LLVivoxVoiceClient::sessionConnectResponse(std::string &requestId, int statusCode, std::string &statusString) { sessionState *session = findSession(requestId); - if(statusCode != 0) + // 1026 is session already has media, somehow mediaconnect was called twice on the same session. + // set the session info to reflect that the user is already connected. + if (statusCode == 1026) + { + session->mVoiceEnabled = true; + session->mMediaConnectInProgress = false; + session->mMediaStreamState = streamStateConnected; + //session->mTextStreamState = streamStateConnected; + session->mErrorStatusCode = 0; + } + else if (statusCode != 0) { LL_WARNS("Voice") << "Session.Connect response failure (" << statusCode << "): " << statusString << LL_ENDL; - if(session) + if (session) { session->mMediaConnectInProgress = false; - session->mErrorStatusCode = statusCode; + session->mErrorStatusCode = statusCode; session->mErrorStatusString = statusString; - if(session == mAudioSession) + if (session == mAudioSession) + { setState(stateJoinSessionFailed); + } } } else @@ -2853,7 +3289,7 @@ void LLVivoxVoiceClient::sessionGroupAddedEvent(std::string &sessionGroupHandle) { LL_DEBUGS("Voice") << "handle " << sessionGroupHandle << LL_ENDL; -#if USE_SESSION_GROUPS +#if USE_SESSION_GROUPS if(mMainSessionGroupHandle.empty()) { // This is the first (i.e. "main") session group. Save its handle. @@ -2881,11 +3317,15 @@ void LLVivoxVoiceClient::joinedAudioSession(sessionState *session) } // This is the session we're joining. - if(getState() == stateJoiningSession) + if(mIsJoiningSession) { - setState(stateSessionJoined); - - // SLIM SDK: we don't always receive a participant state change for ourselves when joining a channel now. + LLSD vivoxevent = LLSD::emptyMap(); + + vivoxevent["handle"] = LLSD::String(session->mHandle); + vivoxevent["session"] = LLSD::String("joined"); + + LLEventPumps::instance().post("vivoxClientPump", vivoxevent); + // Add the current user as a participant here. participantState *participant = session->addParticipant(sipURIFromName(mAccountName)); if(participant) @@ -2941,26 +3381,25 @@ void LLVivoxVoiceClient::sessionRemovedEvent( // Reset the media state (we now have no info) session->mMediaStreamState = streamStateUnknown; - session->mTextStreamState = streamStateUnknown; + //session->mTextStreamState = streamStateUnknown; // Conditionally delete the session reapSession(session); } else { - LL_WARNS("Voice") << "unknown session " << sessionHandle << " removed" << LL_ENDL; + // Already reaped this session. + LL_DEBUGS("Voice") << "unknown session " << sessionHandle << " removed" << LL_ENDL; } + } void LLVivoxVoiceClient::reapSession(sessionState *session) { if(session) { - if(!session->mHandle.empty()) - { - LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (non-null session handle)" << LL_ENDL; - } - else if(session->mCreateInProgress) + + if(session->mCreateInProgress) { LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (create in progress)" << LL_ENDL; } @@ -2978,7 +3417,6 @@ void LLVivoxVoiceClient::reapSession(sessionState *session) } else { - // TODO: Question: Should we check for queued text messages here? // We don't have a reason to keep tracking this session, so just delete it. LL_DEBUGS("Voice") << "deleting session " << session->mSIPURI << LL_ENDL; deleteSession(session); @@ -3027,6 +3465,17 @@ bool LLVivoxVoiceClient::sessionNeedsRelog(sessionState *session) void LLVivoxVoiceClient::leftAudioSession( sessionState *session) { +#if 1 + if (mAudioSession == session) + { + LLSD vivoxevent = LLSD::emptyMap(); + + vivoxevent["handle"] = LLSD::String(session->mHandle); + vivoxevent["session"] = LLSD::String("removed"); + + LLEventPumps::instance().post("vivoxClientPump", vivoxevent); + } +#else if(mAudioSession == session) { switch(getState()) @@ -3053,6 +3502,10 @@ void LLVivoxVoiceClient::leftAudioSession( break; } } + else if ( mAudioSession == NULL && (getState() == stateSessionTerminated) ){ + setState(stateNoChannel); + } +#endif } void LLVivoxVoiceClient::accountLoginStateChangeEvent( @@ -3061,6 +3514,10 @@ void LLVivoxVoiceClient::accountLoginStateChangeEvent( std::string &statusString, int state) { +#if 1 + LLSD levent = LLSD::emptyMap(); + +#endif /* According to Mike S., status codes for this event are: login_state_logged_out=0, @@ -3075,34 +3532,37 @@ void LLVivoxVoiceClient::accountLoginStateChangeEvent( switch(state) { case 1: - if(getState() == stateLoggingIn) - { - setState(stateLoggedIn); - } - break; + levent["login"] = LLSD::String("account_login"); + LLEventPumps::instance().post("vivoxClientPump", levent); + break; case 3: - // The user is in the process of logging out. - setState(stateLoggingOut); - break; + levent["login"] = LLSD::String("account_loggingOut"); + + LLEventPumps::instance().post("vivoxClientPump", levent); + break; case 0: - // The user has been logged out. - setState(stateLoggedOut); - break; + levent["login"] = LLSD::String("account_logout"); + + LLEventPumps::instance().post("vivoxClientPump", levent); + break; default: //Used to be a commented out warning LL_DEBUGS("Voice") << "unknown state: " << state << LL_ENDL; - break; + break; } } void LLVivoxVoiceClient::mediaCompletionEvent(std::string &sessionGroupHandle, std::string &mediaCompletionType) { + LLSD result; + if (mediaCompletionType == "AuxBufferAudioCapture") { mCaptureBufferRecording = false; + result["recplay"] = "end"; } else if (mediaCompletionType == "AuxBufferAudioRender") { @@ -3110,12 +3570,17 @@ void LLVivoxVoiceClient::mediaCompletionEvent(std::string &sessionGroupHandle, s if (--mPlayRequestCount <= 0) { mCaptureBufferPlaying = false; - } + result["recplay"] = "end"; +// result["recplay"] = "done"; + } } else { LL_DEBUGS("Voice") << "Unknown MediaCompletionType: " << mediaCompletionType << LL_ENDL; } + + if (!result.isUndefined()) + LLEventPumps::instance().post("vivoxClientPump", result); } void LLVivoxVoiceClient::mediaStreamUpdatedEvent( @@ -3152,8 +3617,9 @@ void LLVivoxVoiceClient::mediaStreamUpdatedEvent( switch(state) { - case streamStateIdle: - // Standard "left audio session" + case streamStateDisconnecting: + case streamStateIdle: + // Standard "left audio session", Vivox state 'disconnected' session->mVoiceEnabled = false; session->mMediaConnectInProgress = false; leftAudioSession(session); @@ -3163,6 +3629,7 @@ void LLVivoxVoiceClient::mediaStreamUpdatedEvent( session->mVoiceEnabled = true; session->mMediaConnectInProgress = false; joinedAudioSession(session); + case streamStateConnecting: // do nothing, but prevents a warning getting into the logs. break; case streamStateRinging: @@ -3193,54 +3660,8 @@ void LLVivoxVoiceClient::mediaStreamUpdatedEvent( } else { - LL_WARNS("Voice") << "session " << sessionHandle << "not found"<< LL_ENDL; - } -} - -void LLVivoxVoiceClient::textStreamUpdatedEvent( - std::string &sessionHandle, - std::string &sessionGroupHandle, - bool enabled, - int state, - bool incoming) -{ - sessionState *session = findSession(sessionHandle); - - if(session) - { - // Save the state for later use - session->mTextStreamState = state; - - // We know about this session - switch(state) - { - case 0: // We see this when the text stream closes - LL_DEBUGS("Voice") << "stream closed" << LL_ENDL; - break; - - case 1: // We see this on an incoming call from the Connector - // Try to send any text messages queued for this session. - sendQueuedTextMessages(session); - - // Send the text chat invite to the GUI layer - // TODO: Question: Should we correlate with the mute list here? - session->mTextInvitePending = true; - if(session->mName.empty()) - { - lookupName(session->mCallerID); - } - else - { - // Act like we just finished resolving the name - avatarNameResolved(session->mCallerID, session->mName); - } - break; - - default: - LL_WARNS("Voice") << "unknown state " << state << LL_ENDL; - break; - - } + // session disconnectintg and disconnected events arriving after we have already left the session. + LL_DEBUGS("Voice") << "session " << sessionHandle << " not found"<< LL_ENDL; } } @@ -3314,6 +3735,7 @@ void LLVivoxVoiceClient::participantRemovedEvent( } else { + // a late arriving event on a session we have already left. LL_DEBUGS("Voice") << "unknown session " << sessionHandle << LL_ENDL; } } @@ -3398,7 +3820,7 @@ void LLVivoxVoiceClient::participantUpdatedEvent( } else { - LL_INFOS("Voice") << "unknown session " << sessionHandle << LL_ENDL; + LL_DEBUGS("Voice") << "unknown session " << sessionHandle << LL_ENDL; } } @@ -3880,109 +4302,7 @@ bool LLVivoxVoiceClient::checkParcelChanged(bool update) return false; } -bool LLVivoxVoiceClient::parcelVoiceInfoReceived(state requesting_state) -{ - // pop back to the state we were in when the parcel changed and we managed to - // do the request. - if(getState() == stateRetrievingParcelVoiceInfo) - { - setState(requesting_state); - return true; - } - else - { - // we've dropped out of stateRetrievingParcelVoiceInfo - // before we received the cap result, due to a terminate - // or transition to a non-voice channel. Don't switch channels. - return false; - } -} - - -bool LLVivoxVoiceClient::requestParcelVoiceInfo() -{ - LLViewerRegion * region = gAgent.getRegion(); - if (region == NULL || !region->capabilitiesReceived()) - { - // we don't have the cap yet, so return false so the caller can try again later. - - LL_DEBUGS("Voice") << "ParcelVoiceInfoRequest capability not yet available, deferring" << LL_ENDL; - return false; - } - - // grab the cap. - std::string url = gAgent.getRegion()->getCapability("ParcelVoiceInfoRequest"); - if (url.empty()) - { - // Region dosn't have the cap. Stop probing. - LL_DEBUGS("Voice") << "ParcelVoiceInfoRequest capability not available in this region" << LL_ENDL; - setState(stateDisableCleanup); - return false; - } - else - { - // if we've already retrieved the cap from the region, go ahead and make the request, - // and return true so we can go into the state that waits for the response. - checkParcelChanged(true); - LLSD data; - LL_DEBUGS("Voice") << "sending ParcelVoiceInfoRequest (" << mCurrentRegionName << ", " << mCurrentParcelLocalID << ")" << LL_ENDL; - - LLCoros::instance().launch("LLVivoxVoiceClient::parcelVoiceInfoRequestCoro", - boost::bind(&LLVivoxVoiceClient::parcelVoiceInfoRequestCoro, this, url)); - return true; - } -} - -void LLVivoxVoiceClient::parcelVoiceInfoRequestCoro(std::string url) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("parcelVoiceInfoRequest", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - state requestingState = getState(); - - LLSD result = httpAdapter->postAndSuspend(httpRequest, url, LLSD()); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - LL_WARNS("Voice") << "No voice on parcel" << LL_ENDL; - sessionTerminate(); - return; - } - - std::string uri; - std::string credentials; - - if (result.has("voice_credentials")) - { - LLSD voice_credentials = result["voice_credentials"]; - if (voice_credentials.has("channel_uri")) - { - uri = voice_credentials["channel_uri"].asString(); - } - if (voice_credentials.has("channel_credentials")) - { - credentials = - voice_credentials["channel_credentials"].asString(); - } - } - - if (!uri.empty()) - LL_INFOS("Voice") << "Voice URI is " << uri << LL_ENDL; - - // set the spatial channel. If no voice credentials or uri are - // available, then we simply drop out of voice spatially. - if (parcelVoiceInfoReceived(requestingState)) - { - setSpatialChannel(uri, credentials); - } - -} - -void LLVivoxVoiceClient::switchChannel( +bool LLVivoxVoiceClient::switchChannel( std::string uri, bool spatial, bool no_reconnect, @@ -4082,6 +4402,8 @@ void LLVivoxVoiceClient::switchChannel( sessionTerminate(); } } + + return needsSwitch; } void LLVivoxVoiceClient::joinSession(sessionState *session) @@ -4106,7 +4428,7 @@ void LLVivoxVoiceClient::setNonSpatialChannel( switchChannel(uri, false, false, false, credentials); } -void LLVivoxVoiceClient::setSpatialChannel( +bool LLVivoxVoiceClient::setSpatialChannel( const std::string &uri, const std::string &credentials) { @@ -4116,14 +4438,15 @@ void LLVivoxVoiceClient::setSpatialChannel( LL_DEBUGS("Voice") << "got spatial channel uri: \"" << uri << "\"" << LL_ENDL; - if((mAudioSession && !(mAudioSession->mIsSpatial)) || (mNextAudioSession && !(mNextAudioSession->mIsSpatial))) + if((mIsInChannel && mAudioSession && !(mAudioSession->mIsSpatial)) || (mNextAudioSession && !(mNextAudioSession->mIsSpatial))) { // User is in a non-spatial chat or joining a non-spatial chat. Don't switch channels. LL_INFOS("Voice") << "in non-spatial chat, not switching channels" << LL_ENDL; + return false; } else { - switchChannel(mSpatialSessionURI, true, false, false, mSpatialSessionCredentials); + return switchChannel(mSpatialSessionURI, true, false, false, mSpatialSessionCredentials); } } @@ -4156,7 +4479,7 @@ LLVivoxVoiceClient::sessionState* LLVivoxVoiceClient::startUserIMSession(const L if(session->mHandle.empty()) { // Session isn't active -- start it up. - sessionCreateSendMessage(session, false, true); + sessionCreateSendMessage(session, false, false); } else { @@ -4167,59 +4490,6 @@ LLVivoxVoiceClient::sessionState* LLVivoxVoiceClient::startUserIMSession(const L return session; } -BOOL LLVivoxVoiceClient::sendTextMessage(const LLUUID& participant_id, const std::string& message) -{ - bool result = false; - - // Attempt to locate the indicated session - sessionState *session = startUserIMSession(participant_id); - if(session) - { - // found the session, attempt to send the message - session->mTextMsgQueue.push(message); - - // Try to send queued messages (will do nothing if the session is not open yet) - sendQueuedTextMessages(session); - - // The message is queued, so we succeed. - result = true; - } - else - { - LL_DEBUGS("Voice") << "Session not found for participant ID " << participant_id << LL_ENDL; - } - - return result; -} - -void LLVivoxVoiceClient::sendQueuedTextMessages(sessionState *session) -{ - if(session->mTextStreamState == 1) - { - if(!session->mTextMsgQueue.empty()) - { - std::ostringstream stream; - - while(!session->mTextMsgQueue.empty()) - { - std::string message = session->mTextMsgQueue.front(); - session->mTextMsgQueue.pop(); - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.SendMessage.1\">" - << "<SessionHandle>" << session->mHandle << "</SessionHandle>" - << "<MessageHeader>text/HTML</MessageHeader>" - << "<MessageBody>" << message << "</MessageBody>" - << "</Request>" - << "\n\n\n"; - } - writeString(stream.str()); - } - } - else - { - // Session isn't connected yet, defer until later. - } -} void LLVivoxVoiceClient::endUserIMSession(const LLUUID &uuid) { @@ -4230,7 +4500,7 @@ void LLVivoxVoiceClient::endUserIMSession(const LLUUID &uuid) // found the session if(!session->mHandle.empty()) { - sessionTextDisconnectSendMessage(session); + // sessionTextDisconnectSendMessage(session); // a SLim leftover, not used any more. } } else @@ -4581,7 +4851,7 @@ void LLVivoxVoiceClient::enforceTether(void) void LLVivoxVoiceClient::updatePosition(void) { - + LLViewerRegion *region = gAgent.getRegion(); if(region && isAgentAvatarValid()) { @@ -5044,7 +5314,7 @@ void LLVivoxVoiceClient::filePlaybackSetMode(bool vox, float speed) LLVivoxVoiceClient::sessionState::sessionState() : mErrorStatusCode(0), mMediaStreamState(streamStateUnknown), - mTextStreamState(streamStateUnknown), + //mTextStreamState(streamStateUnknown), mCreateInProgress(false), mMediaConnectInProgress(false), mVoiceInvitePending(false), @@ -6010,12 +6280,16 @@ void LLVivoxVoiceClient::sessionSetVoiceFontSendMessage(sessionState *session) void LLVivoxVoiceClient::accountGetSessionFontsResponse(int statusCode, const std::string &statusString) { - // Voice font list entries were updated via addVoiceFont() during parsing. - if(getState() == stateVoiceFontsWait) - { - setState(stateVoiceFontsReceived); - } + if (getState() == stateVoiceFontsWait) + { + // *TODO: We seem to get multiple events of this type. Should figure a way to advance only after + // receiving the last one. + LLSD result = LLSD::emptyMap(); + + result["voice_fonts"] = LLSD::Boolean(true); + LLEventPumps::instance().post("vivoxClientPump", result); + } notifyVoiceFontObservers(); mVoiceFontsReceived = true; } @@ -6158,7 +6432,16 @@ void LLVivoxVoiceClient::notifyVoiceFontObservers() void LLVivoxVoiceClient::enablePreviewBuffer(bool enable) { - mCaptureBufferMode = enable; + LLSD result; + mCaptureBufferMode = enable; + + if (enable) + result["recplay"] = "start"; + else + result["recplay"] = "quit"; + + LLEventPumps::instance().post("vivoxClientPump", result); + if(mCaptureBufferMode && getState() >= stateNoChannel) { LL_DEBUGS("Voice") << "no channel" << LL_ENDL; @@ -6176,6 +6459,10 @@ void LLVivoxVoiceClient::recordPreviewBuffer() } mCaptureBufferRecording = true; + + LLSD result; + result["recplay"] = "record"; + LLEventPumps::instance().post("vivoxClientPump", result); } void LLVivoxVoiceClient::playPreviewBuffer(const LLUUID& effect_id) @@ -6196,12 +6483,20 @@ void LLVivoxVoiceClient::playPreviewBuffer(const LLUUID& effect_id) mPreviewVoiceFont = effect_id; mCaptureBufferPlaying = true; + + LLSD result; + result["recplay"] = "playback"; + LLEventPumps::instance().post("vivoxClientPump", result); } void LLVivoxVoiceClient::stopPreviewBuffer() { mCaptureBufferRecording = false; mCaptureBufferPlaying = false; + + LLSD result; + result["recplay"] = "quit"; + LLEventPumps::instance().post("vivoxClientPump", result); } bool LLVivoxVoiceClient::isPreviewRecording() @@ -6640,6 +6935,10 @@ void LLVivoxProtocolParser::EndTag(const char *tag) uriString = string; else if (!stricmp("Presence", tag)) statusString = string; + else if (!stricmp("CaptureDevices", tag)) + LLVivoxVoiceClient::getInstance()->setDevicesListUpdated(true); + else if (!stricmp("RenderDevices", tag)) + LLVivoxVoiceClient::getInstance()->setDevicesListUpdated(true); else if (!stricmp("CaptureDevice", tag)) { LLVivoxVoiceClient::getInstance()->addCaptureDevice(deviceString); @@ -6768,7 +7067,13 @@ void LLVivoxProtocolParser::processResponse(std::string tag) if (isEvent) { const char *eventTypeCstr = eventTypeString.c_str(); - if (!stricmp(eventTypeCstr, "AccountLoginStateChangeEvent")) + if (!stricmp(eventTypeCstr, "ParticipantUpdatedEvent")) + { + // These happen so often that logging them is pretty useless. + squelchDebugOutput = true; + LLVivoxVoiceClient::getInstance()->participantUpdatedEvent(sessionHandle, sessionGroupHandle, uriString, alias, isModeratorMuted, isSpeaking, volume, energy); + } + else if (!stricmp(eventTypeCstr, "AccountLoginStateChangeEvent")) { LLVivoxVoiceClient::getInstance()->accountLoginStateChangeEvent(accountHandle, statusCode, statusString, state); } @@ -6790,6 +7095,10 @@ void LLVivoxProtocolParser::processResponse(std::string tag) { LLVivoxVoiceClient::getInstance()->sessionRemovedEvent(sessionHandle, sessionGroupHandle); } + else if (!stricmp(eventTypeCstr, "SessionGroupUpdatedEvent")) + { + //nothng useful to process for this event, but we should not WARN that we have received it. + } else if (!stricmp(eventTypeCstr, "SessionGroupAddedEvent")) { LLVivoxVoiceClient::getInstance()->sessionGroupAddedEvent(sessionGroupHandle); @@ -6818,19 +7127,6 @@ void LLVivoxProtocolParser::processResponse(std::string tag) */ LLVivoxVoiceClient::getInstance()->mediaCompletionEvent(sessionGroupHandle, mediaCompletionType); } - else if (!stricmp(eventTypeCstr, "TextStreamUpdatedEvent")) - { - /* - <Event type="TextStreamUpdatedEvent"> - <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg1</SessionGroupHandle> - <SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==1</SessionHandle> - <Enabled>true</Enabled> - <State>1</State> - <Incoming>true</Incoming> - </Event> - */ - LLVivoxVoiceClient::getInstance()->textStreamUpdatedEvent(sessionHandle, sessionGroupHandle, enabled, state, incoming); - } else if (!stricmp(eventTypeCstr, "ParticipantAddedEvent")) { /* @@ -6857,52 +7153,20 @@ void LLVivoxProtocolParser::processResponse(std::string tag) */ LLVivoxVoiceClient::getInstance()->participantRemovedEvent(sessionHandle, sessionGroupHandle, uriString, alias, nameString); } - else if (!stricmp(eventTypeCstr, "ParticipantUpdatedEvent")) - { - /* - <Event type="ParticipantUpdatedEvent"> - <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0</SessionGroupHandle> - <SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==0</SessionHandle> - <ParticipantUri>sip:xFnPP04IpREWNkuw1cOXlhw==@bhr.vivox.com</ParticipantUri> - <IsModeratorMuted>false</IsModeratorMuted> - <IsSpeaking>true</IsSpeaking> - <Volume>44</Volume> - <Energy>0.0879437</Energy> - </Event> - */ - - // These happen so often that logging them is pretty useless. - squelchDebugOutput = true; - - LLVivoxVoiceClient::getInstance()->participantUpdatedEvent(sessionHandle, sessionGroupHandle, uriString, alias, isModeratorMuted, isSpeaking, volume, energy); - } else if (!stricmp(eventTypeCstr, "AuxAudioPropertiesEvent")) { // These are really spammy in tuning mode squelchDebugOutput = true; - LLVivoxVoiceClient::getInstance()->auxAudioPropertiesEvent(energy); } - else if (!stricmp(eventTypeCstr, "BuddyChangedEvent")) - { - /* - <Event type="BuddyChangedEvent"> - <AccountHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==</AccountHandle> - <BuddyURI>sip:x9fFHFZjOTN6OESF1DUPrZQ==@bhr.vivox.com</BuddyURI> - <DisplayName>Monroe Tester</DisplayName> - <BuddyData /> - <GroupID>0</GroupID> - <ChangeType>Set</ChangeType> - </Event> - */ - // TODO: Question: Do we need to process this at all? - } else if (!stricmp(eventTypeCstr, "MessageEvent")) { + //TODO: This probably is not received any more, it was used to support SLim clients LLVivoxVoiceClient::getInstance()->messageEvent(sessionHandle, uriString, alias, messageHeader, messageBody, applicationString); } else if (!stricmp(eventTypeCstr, "SessionNotificationEvent")) { + //TODO: This probably is not received any more, it was used to support SLim clients LLVivoxVoiceClient::getInstance()->sessionNotificationEvent(sessionHandle, uriString, notificationType); } else if (!stricmp(eventTypeCstr, "SessionUpdatedEvent")) @@ -6922,19 +7186,29 @@ void LLVivoxProtocolParser::processResponse(std::string tag) */ // We don't need to process this, but we also shouldn't warn on it, since that confuses people. } - - else if (!stricmp(eventTypeCstr, "SessionGroupRemovedEvent")) + else if (!stricmp(eventTypeCstr, "SessionGroupRemovedEvent")) { - /* - <Event type="SessionGroupRemovedEvent"> - <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0</SessionGroupHandle> - </Event> - */ // We don't need to process this, but we also shouldn't warn on it, since that confuses people. } - else if (!stricmp(eventTypeCstr, "VoiceServiceConnectionStateChangedEvent")) + else if (!stricmp(eventTypeCstr, "VoiceServiceConnectionStateChangedEvent")) { // Yet another ignored event } + else if (!stricmp(eventTypeCstr, "AudioDeviceHotSwapEvent")) + { + /* + <Event type = "AudioDeviceHotSwapEvent"> + <EventType>RenderDeviceChanged< / EventType> + <RelevantDevice> + <Device>Speakers(Turtle Beach P11 Headset)< / Device> + <DisplayName>Speakers(Turtle Beach P11 Headset)< / DisplayName> + <Type>SpecificDevice< / Type> + < / RelevantDevice> + < / Event> + */ + // an audio device was removed or added, fetch and update the local list of audio devices. + LLVivoxVoiceClient::getInstance()->getCaptureDevicesSendMessage(); + LLVivoxVoiceClient::getInstance()->getRenderDevicesSendMessage(); + } else { LL_WARNS("VivoxProtocolParser") << "Unknown event type " << eventTypeString << LL_ENDL; @@ -6943,7 +7217,12 @@ void LLVivoxProtocolParser::processResponse(std::string tag) else { const char *actionCstr = actionString.c_str(); - if (!stricmp(actionCstr, "Connector.Create.1")) + if (!stricmp(actionCstr, "Session.Set3DPosition.1")) + { + // We don't need to process these, but they're so spammy we don't want to log them. + squelchDebugOutput = true; + } + else if (!stricmp(actionCstr, "Connector.Create.1")) { LLVivoxVoiceClient::getInstance()->connectorCreateResponse(statusCode, statusString, connectorHandle, versionID); } @@ -6971,11 +7250,6 @@ void LLVivoxProtocolParser::processResponse(std::string tag) { LLVivoxVoiceClient::getInstance()->connectorShutdownResponse(statusCode, statusString); } - else if (!stricmp(actionCstr, "Session.Set3DPosition.1")) - { - // We don't need to process these, but they're so spammy we don't want to log them. - squelchDebugOutput = true; - } else if (!stricmp(actionCstr, "Account.GetSessionFonts.1")) { LLVivoxVoiceClient::getInstance()->accountGetSessionFontsResponse(statusCode, statusString); diff --git a/indra/newview/llvoicevivox.h b/indra/newview/llvoicevivox.h index b12ed80e41..6cbd5efdcd 100755 --- a/indra/newview/llvoicevivox.h +++ b/indra/newview/llvoicevivox.h @@ -93,6 +93,7 @@ public: // This returns true when it's safe to bring up the "device settings" dialog in the prefs. // i.e. when the daemon is running and connected, and the device lists are populated. virtual bool deviceSettingsAvailable(); + virtual bool deviceSettingsUpdated(); //return if the list has been updated and never fetched, only to be called from the voicepanel. // Requery the vivox daemon for the current list of input/output devices. // If you pass true for clearCurrentList, deviceSettingsAvailable() will be false until the query has completed @@ -111,7 +112,7 @@ public: virtual bool isParticipant(const LLUUID& speaker_id); // Send a text message to the specified user, initiating the session if necessary. - virtual BOOL sendTextMessage(const LLUUID& participant_id, const std::string& message); + // virtual BOOL sendTextMessage(const LLUUID& participant_id, const std::string& message) const {return false;}; // close any existing text IM session with the specified user virtual void endUserIMSession(const LLUUID &uuid); @@ -137,7 +138,7 @@ public: virtual void setNonSpatialChannel(const std::string &uri, const std::string &credentials); - virtual void setSpatialChannel(const std::string &uri, + virtual bool setSpatialChannel(const std::string &uri, const std::string &credentials); virtual void leaveNonSpatialChannel(); @@ -262,6 +263,8 @@ protected: streamStateIdle = 1, streamStateConnected = 2, streamStateRinging = 3, + streamStateConnecting = 6, // same as Vivox session_media_connecting enum + streamStateDisconnecting = 7, //Same as Vivox session_media_disconnecting enum }; struct participantState { @@ -326,7 +329,6 @@ protected: LLUUID mCallerID; int mErrorStatusCode; int mMediaStreamState; - int mTextStreamState; bool mCreateInProgress; // True if a Session.Create has been sent for this session and no response has been received yet. bool mMediaConnectInProgress; // True if a Session.MediaConnect has been sent for this session and no response has been received yet. bool mVoiceInvitePending; // True if a voice invite is pending for this session (usually waiting on a name lookup) @@ -431,8 +433,8 @@ protected: void connectorShutdown(); void closeSocket(void); - void requestVoiceAccountProvision(S32 retries = 3); - void login( +// void requestVoiceAccountProvision(S32 retries = 3); + void setLoginInfo( const std::string& account_name, const std::string& password, const std::string& voice_sip_uri_hostname, @@ -458,14 +460,15 @@ protected: void clearCaptureDevices(); void addCaptureDevice(const std::string& name); void clearRenderDevices(); + void setDevicesListUpdated(bool state); void addRenderDevice(const std::string& name); void buildSetAudioDevices(std::ostringstream &stream); void getCaptureDevicesSendMessage(); void getRenderDevicesSendMessage(); - // local audio updates - void buildLocalAudioUpdates(std::ostringstream &stream); + // local audio updates, mic mute, speaker mute, mic volume and speaker volumes + void sendLocalAudioUpdates(); ///////////////////////////// @@ -481,7 +484,6 @@ protected: void accountLoginStateChangeEvent(std::string &accountHandle, int statusCode, std::string &statusString, int state); void mediaCompletionEvent(std::string &sessionGroupHandle, std::string &mediaCompletionType); void mediaStreamUpdatedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, int statusCode, std::string &statusString, int state, bool incoming); - void textStreamUpdatedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, bool enabled, int state, bool incoming); void sessionAddedEvent(std::string &uriString, std::string &alias, std::string &sessionHandle, std::string &sessionGroupHandle, bool isChannel, bool incoming, std::string &nameString, std::string &applicationString); void sessionGroupAddedEvent(std::string &sessionGroupHandle); void sessionRemovedEvent(std::string &sessionHandle, std::string &sessionGroupHandle); @@ -597,7 +599,7 @@ protected: void sessionTerminateSendMessage(sessionState *session); void sessionGroupTerminateSendMessage(sessionState *session); void sessionMediaDisconnectSendMessage(sessionState *session); - void sessionTextDisconnectSendMessage(sessionState *session); + // void sessionTextDisconnectSendMessage(sessionState *session); @@ -610,9 +612,6 @@ protected: // Does the actual work to get out of the audio session void leaveAudioSession(); - // notifies the voice client that we've received parcel voice info - bool parcelVoiceInfoReceived(state requesting_state); - friend class LLVivoxVoiceClientCapResponder; @@ -637,11 +636,37 @@ protected: private: - void voiceAccountProvisionCoro(std::string url, S32 retries); - void parcelVoiceInfoRequestCoro(std::string url); +// void voiceAccountProvisionCoro(std::string url, S32 retries); +// void parcelVoiceInfoRequestCoro(std::string url); LLVoiceVersionInfo mVoiceVersion; + // Coroutine support methods + void voiceControlCoro(); + + bool startAndConnectSession(); + + bool startAndLaunchDaemon(); + bool provisionVoiceAccount(); + bool establishVoiceConnection(); + bool loginToVivox(); + bool retrieveVoiceFonts(); + + bool requestParcelVoiceInfo(); + + bool addAndJoinSession(sessionState *nextSession); + bool terminateAudioSession(bool wait); + + + bool waitForChannel(); + bool runSession(sessionState *session); + + void recordingAndPlaybackMode(); + int voiceRecordBuffer(); + int voicePlaybackBuffer(); + + bool performMicTuning(state exitState); + /// Clean up objects created during a voice session. void cleanUp(); @@ -681,7 +706,9 @@ private: bool mTuningMicVolumeDirty; int mTuningSpeakerVolume; bool mTuningSpeakerVolumeDirty; - state mTuningExitState; // state to return to when we leave tuning mode. + state mTuningExitState; // state to return to when we leave tuning mode. + bool mDevicesListUpdated; // set to true when the device list has been updated + // and false when the panelvoicedevicesettings has queried for an update status. std::string mSpatialSessionURI; std::string mSpatialSessionCredentials; @@ -734,9 +761,11 @@ private: bool checkParcelChanged(bool update = false); // This should be called when the code detects we have changed parcels. // It initiates the call to the server that gets the parcel channel. +#if 0 bool requestParcelVoiceInfo(); - - void switchChannel(std::string uri = std::string(), bool spatial = true, bool no_reconnect = false, bool is_p2p = false, std::string hash = ""); +#endif + + bool switchChannel(std::string uri = std::string(), bool spatial = true, bool no_reconnect = false, bool is_p2p = false, std::string hash = ""); void joinSession(sessionState *session); std::string nameFromAvatar(LLVOAvatar *avatar); @@ -765,7 +794,6 @@ private: // start a text IM session with the specified user // This will be asynchronous, the session may be established at a future time. sessionState* startUserIMSession(const LLUUID& uuid); - void sendQueuedTextMessages(sessionState *session); void enforceTether(void); @@ -808,8 +836,6 @@ private: std::string mWriteString; size_t mWriteOffset; - LLTimer mUpdateTimer; - BOOL mLipSyncEnabled; typedef std::set<LLVoiceClientParticipantObserver*> observer_set_t; @@ -904,10 +930,15 @@ private: bool mCaptureBufferRecorded; // A voice sample is captured in the buffer ready to play. bool mCaptureBufferPlaying; // A voice sample is being played. - LLTimer mCaptureTimer; - LLUUID mPreviewVoiceFont; - LLUUID mPreviewVoiceFontLast; - S32 mPlayRequestCount; + LLTimer mCaptureTimer; + LLUUID mPreviewVoiceFont; + LLUUID mPreviewVoiceFontLast; + S32 mPlayRequestCount; + bool mIsInTuningMode; + bool mIsInChannel; + bool mIsJoiningSession; + + LLEventMailDrop mVivoxPump; }; /** @@ -1018,6 +1049,7 @@ protected: void EndTag(const char *tag); void CharData(const char *buffer, int length); LLDate expiryTimeStampToLLDate(const std::string& vivox_ts); + }; diff --git a/indra/newview/skins/default/xui/en/panel_sound_devices.xml b/indra/newview/skins/default/xui/en/panel_sound_devices.xml index 46cbc1e87f..3dbb7fb7fc 100755 --- a/indra/newview/skins/default/xui/en/panel_sound_devices.xml +++ b/indra/newview/skins/default/xui/en/panel_sound_devices.xml @@ -98,7 +98,7 @@ name="My volume label" top_pad="14" width="200"> - My volume: + Mic volume: </text> <slider_bar control_name="AudioLevelMic" @@ -110,7 +110,7 @@ left_delta="95" max_val="2" name="mic_volume_slider" - tool_tip="Change the volume using this slider" + tool_tip="Change the mic level using this slider" top_pad="-18" width="110" /> <text diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index f7992dba90..9bcb6fce9f 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -400,8 +400,6 @@ class Windows_i686_Manifest(ViewerManifest): self.path("vivoxsdk.dll") self.path("ortp.dll") self.path("libsndfile-1.dll") - self.path("zlib1.dll") - self.path("vivoxplatform.dll") self.path("vivoxoal.dll") self.path("ca-bundle.crt") diff --git a/indra/viewer_components/updater/CMakeLists.txt b/indra/viewer_components/updater/CMakeLists.txt index 48c065c2ed..7f866336d9 100755 --- a/indra/viewer_components/updater/CMakeLists.txt +++ b/indra/viewer_components/updater/CMakeLists.txt @@ -73,18 +73,23 @@ if(LL_TESTS) llupdaterservice.cpp ) +set(test_libs + ${LLCOMMON_LIBRARIES}) + set_source_files_properties( llupdaterservice.cpp PROPERTIES - LL_TEST_ADDITIONAL_LIBRARIES "${BOOST_SYSTEM_LIBRARY}" + LL_TEST_ADDITIONAL_LIBRARIES ${test_libs} # *NOTE:Mani - I was trying to use the preprocessor seam to mock out # llifstream (and other) llcommon classes. It didn't work # because of the windows declspec(dllimport)attribute. # LL_TEST_ADDITIONAL_CFLAGS "-Dllifstream=llus_mock_llifstream" ) - LL_ADD_PROJECT_UNIT_TESTS(llupdaterservice "${llupdater_service_TEST_SOURCE_FILES}") +if (NOT LINUX) + LL_ADD_PROJECT_UNIT_TESTS(llupdaterservice "${llupdater_service_TEST_SOURCE_FILES}" ${test_libs}) +endif (NOT LINUX) endif(LL_TESTS) set(UPDATER_INCLUDE_DIRS |