From 753ddd771499257297fa8dfda6749854b4b2eeb9 Mon Sep 17 00:00:00 2001 From: Tofu Linden Date: Sat, 27 Feb 2010 17:29:30 +0000 Subject: give audio visualization on faces with audio-only media. linux/gstreamer only. --- .../gstreamer010/media_plugin_gstreamer010.cpp | 40 ++++++++++++++++++++++ 1 file changed, 40 insertions(+) (limited to 'indra/media_plugins') diff --git a/indra/media_plugins/gstreamer010/media_plugin_gstreamer010.cpp b/indra/media_plugins/gstreamer010/media_plugin_gstreamer010.cpp index 26173314a7..fb13b025c0 100644 --- a/indra/media_plugins/gstreamer010/media_plugin_gstreamer010.cpp +++ b/indra/media_plugins/gstreamer010/media_plugin_gstreamer010.cpp @@ -141,6 +141,7 @@ private: // Very GStreamer-specific GMainLoop *mPump; // event pump for this media GstElement *mPlaybin; + GstElement *mVisualizer; GstSLVideo *mVideoSink; }; @@ -159,6 +160,7 @@ MediaPluginGStreamer010::MediaPluginGStreamer010( mSeekDestination(0.0), mPump ( NULL ), mPlaybin ( NULL ), + mVisualizer ( NULL ), mVideoSink ( NULL ), mCommand ( COMMAND_NONE ) { @@ -686,6 +688,33 @@ MediaPluginGStreamer010::load() this); llgst_object_unref (bus); + // get a visualizer element (bonus feature!) + char* vis_name = getenv("LL_GST_VIS_NAME"); + if (!visname || + (vis_name && std::string(vis_name)!="none")) + { + if (vis_name) + { + mVisualizer = llgst_element_factory_make (vis_name, "vis"); + } + if (!mVisualizer) + { + mVisualizer = llgst_element_factory_make ("libvisual_jess", "vis"); + if (!mVisualizer) + { + mVisualizer = llgst_element_factory_make ("goom", "vis"); + if (!mVisualizer) + { + mVisualizer = llgst_element_factory_make ("libvisual_lv_scope", "vis"); + if (!mVisualizer) + { + // That's okay, we don't NEED this. + } + } + } + } + } + if (NULL == getenv("LL_GSTREAMER_EXTERNAL")) { // instantiate a custom video sink mVideoSink = @@ -702,6 +731,11 @@ MediaPluginGStreamer010::load() g_object_set(mPlaybin, "video-sink", mVideoSink, NULL); } + if (mVisualizer) + { + g_object_set(mPlaybin, "vis-plugin", mVisualizer, NULL); + } + return true; } @@ -724,6 +758,12 @@ MediaPluginGStreamer010::unload () mPlaybin = NULL; } + if (mVisualizer) + { + llgst_object_unref (GST_OBJECT (mVisualizer)); + mVisualizer = NULL; + } + if (mPump) { g_main_loop_quit(mPump); -- cgit v1.3 From f0c897230fe28a0a6c597593b3e8a15b118c01af Mon Sep 17 00:00:00 2001 From: Tofu Linden Date: Sat, 27 Feb 2010 17:31:27 +0000 Subject: duh, with added buildability. --- indra/media_plugins/gstreamer010/media_plugin_gstreamer010.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/media_plugins') diff --git a/indra/media_plugins/gstreamer010/media_plugin_gstreamer010.cpp b/indra/media_plugins/gstreamer010/media_plugin_gstreamer010.cpp index fb13b025c0..4c2dd64bb6 100644 --- a/indra/media_plugins/gstreamer010/media_plugin_gstreamer010.cpp +++ b/indra/media_plugins/gstreamer010/media_plugin_gstreamer010.cpp @@ -690,7 +690,7 @@ MediaPluginGStreamer010::load() // get a visualizer element (bonus feature!) char* vis_name = getenv("LL_GST_VIS_NAME"); - if (!visname || + if (!vis_name || (vis_name && std::string(vis_name)!="none")) { if (vis_name) -- cgit v1.3 From 4e7838fb004f67c51d1b9991ba6782be7036bd7e Mon Sep 17 00:00:00 2001 From: Monroe Linden Date: Fri, 26 Mar 2010 15:39:21 -0700 Subject: Implemented central storage mechanism for media plugin cookies. Added LLPluginCookieStore, which manages the central list of cookies. New Mac and Windows versions of llqtwebkit, built from the tip of the cookie-api branch on http://bitbucket.org/lindenlab/llqtwebkit/ (currently revision f35a5eab8c2f). Added "set_cookies" and "cookie_set" messages to the media_browser message class in the plugin API, and made the webkit plugin use them appropriately. Added methods to LLViewerMedia to read/write the cookie file and add/remove individual cookies. Added hooks to read/write the cookie file (plugin_cookies.txt) in the same places as the location history (idle_startup() in llstartup.cpp and LLAppViewer::cleanup(), respectively). Reviewed by Richard at http://codereview.lindenlab.com/1006003 --- indra/llplugin/CMakeLists.txt | 2 + indra/llplugin/llpluginclassmedia.cpp | 14 + indra/llplugin/llpluginclassmedia.h | 1 + indra/llplugin/llpluginclassmediaowner.h | 2 + indra/llplugin/llplugincookiestore.cpp | 600 +++++++++++++++++++++ indra/llplugin/llplugincookiestore.h | 122 +++++ indra/media_plugins/webkit/media_plugin_webkit.cpp | 17 + indra/newview/llappviewer.cpp | 2 + indra/newview/llstartup.cpp | 3 + indra/newview/llviewermedia.cpp | 150 +++++- indra/newview/llviewermedia.h | 11 + install.xml | 8 +- 12 files changed, 927 insertions(+), 5 deletions(-) create mode 100644 indra/llplugin/llplugincookiestore.cpp create mode 100644 indra/llplugin/llplugincookiestore.h (limited to 'indra/media_plugins') diff --git a/indra/llplugin/CMakeLists.txt b/indra/llplugin/CMakeLists.txt index 6706775d4f..def9fcbeae 100644 --- a/indra/llplugin/CMakeLists.txt +++ b/indra/llplugin/CMakeLists.txt @@ -23,6 +23,7 @@ include_directories( set(llplugin_SOURCE_FILES llpluginclassmedia.cpp + llplugincookiestore.cpp llplugininstance.cpp llpluginmessage.cpp llpluginmessagepipe.cpp @@ -36,6 +37,7 @@ set(llplugin_HEADER_FILES llpluginclassmedia.h llpluginclassmediaowner.h + llplugincookiestore.h llplugininstance.h llpluginmessage.h llpluginmessageclasses.h diff --git a/indra/llplugin/llpluginclassmedia.cpp b/indra/llplugin/llpluginclassmedia.cpp index bf0e19473e..e09b511a6e 100644 --- a/indra/llplugin/llpluginclassmedia.cpp +++ b/indra/llplugin/llpluginclassmedia.cpp @@ -993,6 +993,13 @@ void LLPluginClassMedia::receivePluginMessage(const LLPluginMessage &message) mClickTargetType = TARGET_NONE; mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_CLICK_LINK_NOFOLLOW); } + else if(message_name == "cookie_set") + { + if(mOwner) + { + mOwner->handleCookieSet(this, message.getValue("cookie")); + } + } else { LL_WARNS("Plugin") << "Unknown " << message_name << " class message: " << message_name << LL_ENDL; @@ -1076,6 +1083,13 @@ void LLPluginClassMedia::clear_cookies() sendMessage(message); } +void LLPluginClassMedia::set_cookies(const std::string &cookies) +{ + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "set_cookies"); + message.setValue("cookies", cookies); + sendMessage(message); +} + void LLPluginClassMedia::enable_cookies(bool enable) { LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "enable_cookies"); diff --git a/indra/llplugin/llpluginclassmedia.h b/indra/llplugin/llpluginclassmedia.h index 79356beb68..8c7b00f45b 100644 --- a/indra/llplugin/llpluginclassmedia.h +++ b/indra/llplugin/llpluginclassmedia.h @@ -189,6 +189,7 @@ public: void focus(bool focused); void clear_cache(); void clear_cookies(); + void set_cookies(const std::string &cookies); void enable_cookies(bool enable); void proxy_setup(bool enable, const std::string &host = LLStringUtil::null, int port = 0); void browse_stop(); diff --git a/indra/llplugin/llpluginclassmediaowner.h b/indra/llplugin/llpluginclassmediaowner.h index 6d369cd51a..5669b81fd1 100644 --- a/indra/llplugin/llpluginclassmediaowner.h +++ b/indra/llplugin/llpluginclassmediaowner.h @@ -39,6 +39,7 @@ #include class LLPluginClassMedia; +class LLPluginCookieStore; class LLPluginClassMediaOwner { @@ -78,6 +79,7 @@ public: virtual ~LLPluginClassMediaOwner() {}; virtual void handleMediaEvent(LLPluginClassMedia* /*self*/, EMediaEvent /*event*/) {}; + virtual void handleCookieSet(LLPluginClassMedia* /*self*/, const std::string &/*cookie*/) {}; }; #endif // LL_LLPLUGINCLASSMEDIAOWNER_H diff --git a/indra/llplugin/llplugincookiestore.cpp b/indra/llplugin/llplugincookiestore.cpp new file mode 100644 index 0000000000..1964b8d789 --- /dev/null +++ b/indra/llplugin/llplugincookiestore.cpp @@ -0,0 +1,600 @@ +/** + * @file llplugincookiestore.cpp + * @brief LLPluginCookieStore provides central storage for http cookies used by plugins + * + * @cond + * $LicenseInfo:firstyear=2010&license=viewergpl$ + * + * Copyright (c) 2010, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + * @endcond + */ + +#include "linden_common.h" +#include "indra_constants.h" + +#include "llplugincookiestore.h" +#include + +// for curl_getdate() (apparently parsing RFC 1123 dates is hard) +#include + +LLPluginCookieStore::LLPluginCookieStore(): + mHasChangedCookies(false) +{ +} + + +LLPluginCookieStore::~LLPluginCookieStore() +{ + clearCookies(); +} + + +LLPluginCookieStore::Cookie::Cookie(const std::string &s, std::string::size_type cookie_start, std::string::size_type cookie_end): + mCookie(s, cookie_start, cookie_end), + mNameStart(0), mNameEnd(0), + mValueStart(0), mValueEnd(0), + mDomainStart(0), mDomainEnd(0), + mPathStart(0), mPathEnd(0), + mDead(false), mChanged(true) +{ +} + +LLPluginCookieStore::Cookie *LLPluginCookieStore::Cookie::createFromString(const std::string &s, std::string::size_type cookie_start, std::string::size_type cookie_end) +{ + Cookie *result = new Cookie(s, cookie_start, cookie_end); + + if(!result->parse()) + { + delete result; + result = NULL; + } + + return result; +} + +std::string LLPluginCookieStore::Cookie::getKey() const +{ + std::string result; + if(mDomainEnd > mDomainStart) + { + result += mCookie.substr(mDomainStart, mDomainEnd - mDomainStart); + } + result += ';'; + if(mPathEnd > mPathStart) + { + result += mCookie.substr(mPathStart, mPathEnd - mPathStart); + } + result += ';'; + result += mCookie.substr(mNameStart, mNameEnd - mNameStart); + return result; +} + +bool LLPluginCookieStore::Cookie::parse() +{ + bool first_field = true; + + std::string::size_type cookie_end = mCookie.size(); + std::string::size_type field_start = 0; + + lldebugs << "parsing cookie: " << mCookie << llendl; + while(field_start < cookie_end) + { + // Finding the start of the next field requires honoring special quoting rules + // see the definition of 'quoted-string' in rfc2616 for details + std::string::size_type next_field_start = findFieldEnd(field_start); + + // The end of this field should not include the terminating ';' or any trailing whitespace + std::string::size_type field_end = mCookie.find_last_not_of("; ", next_field_start); + if(field_end == std::string::npos || field_end < field_start) + { + // This field was empty or all whitespace. Set end = start so it shows as empty. + field_end = field_start; + } + else if (field_end < next_field_start) + { + // we actually want the index of the char _after_ what 'last not of' found + ++field_end; + } + + // find the start of the actual name (skip separator and possible whitespace) + std::string::size_type name_start = mCookie.find_first_not_of("; ", field_start); + if(name_start == std::string::npos || name_start > next_field_start) + { + // Again, nothing but whitespace. + name_start = field_start; + } + + // the name and value are separated by the first equals sign + std::string::size_type name_value_sep = mCookie.find_first_of("=", name_start); + if(name_value_sep == std::string::npos || name_value_sep > field_end) + { + // No separator found, so this is a field without an = + name_value_sep = field_end; + } + + // the name end is before the name-value separator + std::string::size_type name_end = mCookie.find_last_not_of("= ", name_value_sep); + if(name_end == std::string::npos || name_end < name_start) + { + // I'm not sure how we'd hit this case... it seems like it would have to be an empty name. + name_end = name_start; + } + else if (name_end < name_value_sep) + { + // we actually want the index of the char _after_ what 'last not of' found + ++name_end; + } + + // Value is between the name-value sep and the end of the field. + std::string::size_type value_start = mCookie.find_first_not_of("= ", name_value_sep); + if(value_start == std::string::npos || value_start > field_end) + { + // All whitespace or empty value + value_start = field_end; + } + std::string::size_type value_end = mCookie.find_last_not_of("; ", field_end); + if(value_end == std::string::npos || value_end < value_start) + { + // All whitespace or empty value + value_end = value_start; + } + else if (value_end < field_end) + { + // we actually want the index of the char _after_ what 'last not of' found + ++value_end; + } + + lldebugs + << " field name: \"" << mCookie.substr(name_start, name_end - name_start) + << "\", value: \"" << mCookie.substr(value_start, value_end - value_start) << "\"" + << llendl; + + // See whether this field is one we know + if(first_field) + { + // The first field is the name=value pair + mNameStart = name_start; + mNameEnd = name_end; + mValueStart = value_start; + mValueEnd = value_end; + first_field = false; + } + else + { + // Subsequent fields must come from the set in rfc2109 + if(matchName(name_start, name_end, "expires")) + { + std::string date_string(mCookie, value_start, value_end - value_start); + // If the cookie contains an "expires" field, it MUST contain a parsable date. + + // HACK: LLDate apparently can't PARSE an rfc1123-format date, even though it can GENERATE one. + // The curl function curl_getdate can do this, but I'm hesitant to unilaterally introduce a curl dependency in LLDate. +#if 1 + time_t date = curl_getdate(date_string.c_str(), NULL ); + mDate.secondsSinceEpoch((F64)date); + lldebugs << " expire date parsed to: " << mDate.asRFC1123() << llendl; +#else + // This doesn't work (rfc1123-format dates cause it to fail) + if(!mDate.fromString(date_string)) + { + // Date failed to parse. + llwarns << "failed to parse cookie's expire date: " << date << llendl; + return false; + } +#endif + } + else if(matchName(name_start, name_end, "domain")) + { + mDomainStart = value_start; + mDomainEnd = value_end; + } + else if(matchName(name_start, name_end, "path")) + { + mPathStart = value_start; + mPathEnd = value_end; + } + else if(matchName(name_start, name_end, "max-age")) + { + // TODO: how should we handle this? + } + else if(matchName(name_start, name_end, "secure")) + { + // We don't care about the value of this field (yet) + } + else if(matchName(name_start, name_end, "version")) + { + // We don't care about the value of this field (yet) + } + else if(matchName(name_start, name_end, "comment")) + { + // We don't care about the value of this field (yet) + } + else + { + // An unknown field is a parse failure + return false; + } + + } + + + // move on to the next field, skipping this field's separator and any leading whitespace + field_start = mCookie.find_first_not_of("; ", next_field_start); + } + + // The cookie MUST have a name + if(mNameEnd <= mNameStart) + return false; + + return true; +} + +std::string::size_type LLPluginCookieStore::Cookie::findFieldEnd(std::string::size_type start, std::string::size_type end) +{ + std::string::size_type result = start; + + if(end == std::string::npos) + end = mCookie.size(); + + bool in_quotes = false; + for(; (result < end); result++) + { + switch(mCookie[result]) + { + case '\\': + if(in_quotes) + result++; // The next character is backslash-quoted. Skip over it. + break; + case '"': + in_quotes = !in_quotes; + break; + case ';': + if(!in_quotes) + return result; + break; + } + } + + // If we got here, no ';' was found. + return end; +} + +bool LLPluginCookieStore::Cookie::matchName(std::string::size_type start, std::string::size_type end, const char *name) +{ + // NOTE: this assumes 'name' is already in lowercase. The code which uses it should be able to arrange this... + + while((start < end) && (*name != '\0')) + { + if(tolower(mCookie[start]) != *name) + return false; + + start++; + name++; + } + + // iff both strings hit the end at the same time, they're equal. + return ((start == end) && (*name == '\0')); +} + +std::string LLPluginCookieStore::getAllCookies() +{ + std::stringstream result; + writeAllCookies(result); + return result.str(); +} + +void LLPluginCookieStore::writeAllCookies(std::ostream& s) +{ + cookie_map_t::iterator iter; + for(iter = mCookies.begin(); iter != mCookies.end(); iter++) + { + // Don't return expired cookies + if(!iter->second->isDead()) + { + s << (iter->second->getCookie()) << "\n"; + } + } + +} + +std::string LLPluginCookieStore::getPersistentCookies() +{ + std::stringstream result; + writePersistentCookies(result); + return result.str(); +} + +void LLPluginCookieStore::writePersistentCookies(std::ostream& s) +{ + cookie_map_t::iterator iter; + for(iter = mCookies.begin(); iter != mCookies.end(); iter++) + { + // Don't return expired cookies or session cookies + if(!iter->second->isDead() && !iter->second->isSessionCookie()) + { + s << iter->second->getCookie() << "\n"; + } + } +} + +std::string LLPluginCookieStore::getChangedCookies(bool clear_changed) +{ + std::stringstream result; + writeChangedCookies(result, clear_changed); + + return result.str(); +} + +void LLPluginCookieStore::writeChangedCookies(std::ostream& s, bool clear_changed) +{ + if(mHasChangedCookies) + { + lldebugs << "returning changed cookies: " << llendl; + cookie_map_t::iterator iter; + for(iter = mCookies.begin(); iter != mCookies.end(); ) + { + cookie_map_t::iterator next = iter; + next++; + + // Only return cookies marked as "changed" + if(iter->second->isChanged()) + { + s << iter->second->getCookie() << "\n"; + + lldebugs << " " << iter->second->getCookie() << llendl; + + // If requested, clear the changed mark + if(clear_changed) + { + if(iter->second->isDead()) + { + // If this cookie was previously marked dead, it needs to be removed entirely. + delete iter->second; + mCookies.erase(iter); + } + else + { + // Not dead, just mark as not changed. + iter->second->setChanged(false); + } + } + } + + iter = next; + } + } + + if(clear_changed) + mHasChangedCookies = false; +} + +void LLPluginCookieStore::setAllCookies(const std::string &cookies, bool mark_changed) +{ + clearCookies(); + setCookies(cookies, mark_changed); +} + +void LLPluginCookieStore::readAllCookies(std::istream& s, bool mark_changed) +{ + clearCookies(); + readCookies(s, mark_changed); +} + +void LLPluginCookieStore::setCookies(const std::string &cookies, bool mark_changed) +{ + std::string::size_type start = 0; + + while(start != std::string::npos) + { + std::string::size_type end = cookies.find('\n', start); + if(end > start) + { + // The line is non-empty. Try to create a cookie from it. + setOneCookie(cookies, start, end, mark_changed); + } + start = cookies.find_first_not_of("\n ", end); + } +} + +void LLPluginCookieStore::readCookies(std::istream& s, bool mark_changed) +{ + std::string line; + while(s.good() && !s.eof()) + { + std::getline(s, line); + if(!line.empty()) + { + // Try to create a cookie from this line. + setOneCookie(line, 0, std::string::npos, mark_changed); + } + } +} + +std::string LLPluginCookieStore::quoteString(const std::string &s) +{ + std::stringstream result; + + result << '"'; + + for(std::string::size_type i = 0; i < s.size(); ++i) + { + char c = s[i]; + switch(c) + { + // All these separators need to be quoted in HTTP headers, according to section 2.2 of rfc 2616: + case '(': case ')': case '<': case '>': case '@': + case ',': case ';': case ':': case '\\': case '"': + case '/': case '[': case ']': case '?': case '=': + case '{': case '}': case ' ': case '\t': + result << '\\'; + break; + } + + result << c; + } + + result << '"'; + + return result.str(); +} + +std::string LLPluginCookieStore::unquoteString(const std::string &s) +{ + std::stringstream result; + + bool in_quotes = false; + + for(std::string::size_type i = 0; i < s.size(); ++i) + { + char c = s[i]; + switch(c) + { + case '\\': + if(in_quotes) + { + // The next character is backslash-quoted. Pass it through untouched. + ++i; + if(i < s.size()) + { + result << s[i]; + } + continue; + } + break; + case '"': + in_quotes = !in_quotes; + continue; + break; + } + + result << c; + } + + return result.str(); +} + +// The flow for deleting a cookie is non-obvious enough that I should call it out here... +// Deleting a cookie is done by setting a cookie with the same name, path, and domain, but with an expire timestamp in the past. +// (This is exactly how a web server tells a browser to delete a cookie.) +// When deleting with mark_changed set to true, this replaces the existing cookie in the list with an entry that's marked both dead and changed. +// Some time later when writeChangedCookies() is called with clear_changed set to true, the dead cookie is deleted from the list after being returned, so that the +// delete operation (in the form of the expired cookie) is passed along. +void LLPluginCookieStore::setOneCookie(const std::string &s, std::string::size_type cookie_start, std::string::size_type cookie_end, bool mark_changed) +{ + Cookie *cookie = Cookie::createFromString(s, cookie_start, cookie_end); + if(cookie) + { + lldebugs << "setting cookie: " << cookie->getCookie() << llendl; + + // Create a key for this cookie + std::string key = cookie->getKey(); + + // Check to see whether this cookie should have expired + if(!cookie->isSessionCookie() && (cookie->getDate() < LLDate::now())) + { + // This cookie has expired. + if(mark_changed) + { + // If we're marking cookies as changed, we should keep it anyway since we'll need to send it out with deltas. + cookie->setDead(true); + lldebugs << " marking dead" << llendl; + } + else + { + // If we're not marking cookies as changed, we don't need to keep this cookie at all. + // If the cookie was already in the list, delete it. + removeCookie(key); + + delete cookie; + cookie = NULL; + + lldebugs << " removing" << llendl; + } + } + + if(cookie) + { + // If it already exists in the map, replace it. + cookie_map_t::iterator iter = mCookies.find(key); + if(iter != mCookies.end()) + { + if(iter->second->getCookie() == cookie->getCookie()) + { + // The new cookie is identical to the old -- don't mark as changed. + // Just leave the old one in the map. + delete cookie; + cookie = NULL; + + lldebugs << " unchanged" << llendl; + } + else + { + // A matching cookie was already in the map. Replace it. + delete iter->second; + iter->second = cookie; + + cookie->setChanged(mark_changed); + if(mark_changed) + mHasChangedCookies = true; + + lldebugs << " replacing" << llendl; + } + } + else + { + // The cookie wasn't in the map. Insert it. + mCookies.insert(std::make_pair(key, cookie)); + + cookie->setChanged(mark_changed); + if(mark_changed) + mHasChangedCookies = true; + + lldebugs << " adding" << llendl; + } + } + } +} + +void LLPluginCookieStore::clearCookies() +{ + while(!mCookies.empty()) + { + cookie_map_t::iterator iter = mCookies.begin(); + delete iter->second; + mCookies.erase(iter); + } +} + +void LLPluginCookieStore::removeCookie(const std::string &key) +{ + cookie_map_t::iterator iter = mCookies.find(key); + if(iter != mCookies.end()) + { + delete iter->second; + mCookies.erase(iter); + } +} + diff --git a/indra/llplugin/llplugincookiestore.h b/indra/llplugin/llplugincookiestore.h new file mode 100644 index 0000000000..5250f008b6 --- /dev/null +++ b/indra/llplugin/llplugincookiestore.h @@ -0,0 +1,122 @@ +/** + * @file llplugincookiestore.h + * @brief LLPluginCookieStore provides central storage for http cookies used by plugins + * + * @cond + * $LicenseInfo:firstyear=2010&license=viewergpl$ + * + * Copyright (c) 2010, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + * @endcond + */ + +#ifndef LL_LLPLUGINCOOKIESTORE_H +#define LL_LLPLUGINCOOKIESTORE_H + +#include "lldate.h" +#include +#include +#include + +class LLPluginCookieStore +{ + LOG_CLASS(LLPluginCookieStore); +public: + LLPluginCookieStore(); + ~LLPluginCookieStore(); + + // gets all cookies currently in storage -- use when initializing a plugin + std::string getAllCookies(); + void writeAllCookies(std::ostream& s); + + // gets only persistent cookies (i.e. not session cookies) -- use when writing cookies to a file + std::string getPersistentCookies(); + void writePersistentCookies(std::ostream& s); + + // gets cookies which are marked as "changed" -- use when sending periodic updates to plugins + std::string getChangedCookies(bool clear_changed = true); + void writeChangedCookies(std::ostream& s, bool clear_changed = true); + + // (re)initializes internal data structures and bulk-sets cookies -- use when reading cookies from a file + void setAllCookies(const std::string &cookies, bool mark_changed = false); + void readAllCookies(std::istream& s, bool mark_changed = false); + + // sets one or more cookies (without reinitializing anything) -- use when receiving cookies from a plugin + void setCookies(const std::string &cookies, bool mark_changed = true); + void readCookies(std::istream& s, bool mark_changed = true); + + // quote or unquote a string as per the definition of 'quoted-string' in rfc2616 + static std::string quoteString(const std::string &s); + static std::string unquoteString(const std::string &s); + +private: + + void setOneCookie(const std::string &s, std::string::size_type cookie_start, std::string::size_type cookie_end, bool mark_changed); + + class Cookie + { + public: + static Cookie *createFromString(const std::string &s, std::string::size_type cookie_start = 0, std::string::size_type cookie_end = std::string::npos); + + // Construct a string from the cookie that uniquely represents it, to be used as a key in a std::map. + std::string getKey() const; + + const std::string &getCookie() const { return mCookie; }; + bool isSessionCookie() const { return mDate.isNull(); }; + + bool isDead() const { return mDead; }; + void setDead(bool dead) { mDead = dead; }; + + bool isChanged() const { return mChanged; }; + void setChanged(bool changed) { mChanged = changed; }; + + const LLDate &getDate() const { return mDate; }; + + private: + Cookie(const std::string &s, std::string::size_type cookie_start = 0, std::string::size_type cookie_end = std::string::npos); + bool parse(); + std::string::size_type findFieldEnd(std::string::size_type start = 0, std::string::size_type end = std::string::npos); + bool matchName(std::string::size_type start, std::string::size_type end, const char *name); + + std::string mCookie; // The full cookie, in RFC 2109 string format + LLDate mDate; // The expiration date of the cookie. For session cookies, this will be a null date (mDate.isNull() is true). + // Start/end indices of various parts of the cookie string. Stored as indices into the string to save space and time. + std::string::size_type mNameStart, mNameEnd; + std::string::size_type mValueStart, mValueEnd; + std::string::size_type mDomainStart, mDomainEnd; + std::string::size_type mPathStart, mPathEnd; + bool mDead; + bool mChanged; + }; + + typedef std::map cookie_map_t; + + cookie_map_t mCookies; + bool mHasChangedCookies; + + void clearCookies(); + void removeCookie(const std::string &key); +}; + +#endif // LL_LLPLUGINCOOKIESTORE_H diff --git a/indra/media_plugins/webkit/media_plugin_webkit.cpp b/indra/media_plugins/webkit/media_plugin_webkit.cpp index 0462fce236..85d6b2f5ff 100644 --- a/indra/media_plugins/webkit/media_plugin_webkit.cpp +++ b/indra/media_plugins/webkit/media_plugin_webkit.cpp @@ -507,6 +507,19 @@ private: sendMessage(message); } + + //////////////////////////////////////////////////////////////////////////////// + // virtual + void onCookieChanged(const EventType& event) + { + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "cookie_set"); + message.setValue("cookie", event.getStringValue()); + // These could be passed through as well, but aren't really needed. +// message.setValue("uri", event.getEventUri()); +// message.setValueBoolean("dead", (event.getIntValue() != 0)) + sendMessage(message); + } + LLQtWebKit::EKeyboardModifier decodeModifiers(std::string &modifiers) { int result = 0; @@ -1051,6 +1064,10 @@ void MediaPluginWebKit::receiveMessage(const char *message_string) mJavascriptEnabled = message_in.getValueBoolean("enable"); //LLQtWebKit::getInstance()->enableJavascript( mJavascriptEnabled ); } + else if(message_name == "set_cookies") + { + LLQtWebKit::getInstance()->setCookies(message_in.getValue("cookies")); + } else if(message_name == "proxy_setup") { bool val = message_in.getValueBoolean("enable"); diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index d30d7fd26d..a3d0b8d8d9 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -1526,6 +1526,8 @@ bool LLAppViewer::cleanup() LLLocationHistory::getInstance()->save(); LLAvatarIconIDCache::getInstance()->save(); + + LLViewerMedia::saveCookieFile(); llinfos << "Shutting down Threads" << llendflush; diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index edd03dc836..59d118abe2 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -938,6 +938,9 @@ bool idle_startup() // Load Avatars icons cache LLAvatarIconIDCache::getInstance()->load(); + + // Load media plugin cookies + LLViewerMedia::loadCookieFile(); //------------------------------------------------- // Handle startup progress screen diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp index 2fcd3f1114..8863bf187d 100644 --- a/indra/newview/llviewermedia.cpp +++ b/indra/newview/llviewermedia.cpp @@ -45,6 +45,7 @@ #include "llviewertexturelist.h" #include "llvovolume.h" #include "llpluginclassmedia.h" +#include "llplugincookiestore.h" #include "llviewerwindow.h" #include "llfocusmgr.h" #include "llcallbacklist.h" @@ -256,6 +257,7 @@ public: LLViewerMediaImpl *mMediaImpl; bool mInitialized; }; +LLPluginCookieStore *LLViewerMedia::sCookieStore = NULL; static LLViewerMedia::impl_list sViewerMediaImplList; static LLViewerMedia::impl_id_map sViewerMediaTextureIDMap; static LLTimer sMediaCreateTimer; @@ -264,6 +266,8 @@ static F32 sGlobalVolume = 1.0f; static F64 sLowestLoadableImplInterest = 0.0f; static bool sAnyMediaShowing = false; static boost::signals2::connection sTeleportFinishConnection; +static std::string sUpdatedCookies; +static const char *PLUGIN_COOKIE_FILE_NAME = "plugin_cookies.txt"; ////////////////////////////////////////////////////////////////////////////////////////// static void add_media_impl(LLViewerMediaImpl* media) @@ -399,7 +403,6 @@ viewer_media_t LLViewerMedia::updateMediaImpl(LLMediaEntry* media_entry, const s media_impl->setHomeURL(media_entry->getHomeURL()); media_impl->mMediaAutoPlay = media_entry->getAutoPlay(); media_impl->mMediaEntryURL = media_entry->getCurrentURL(); - if(media_impl->isAutoPlayable()) { needs_navigate = true; @@ -698,6 +701,13 @@ static bool proximity_comparitor(const LLViewerMediaImpl* i1, const LLViewerMedi void LLViewerMedia::updateMedia(void *dummy_arg) { sAnyMediaShowing = false; + sUpdatedCookies = getCookieStore()->getChangedCookies(); + if(!sUpdatedCookies.empty()) + { + lldebugs << "updated cookies will be sent to all loaded plugins: " << llendl; + lldebugs << sUpdatedCookies << llendl; + } + impl_list::iterator iter = sViewerMediaImplList.begin(); impl_list::iterator end = sViewerMediaImplList.end(); @@ -1095,6 +1105,116 @@ void LLViewerMedia::setProxyConfig(bool enable, const std::string &host, int por ///////////////////////////////////////////////////////////////////////////////////////// // static +///////////////////////////////////////////////////////////////////////////////////////// +// static +LLPluginCookieStore *LLViewerMedia::getCookieStore() +{ + if(sCookieStore == NULL) + { + sCookieStore = new LLPluginCookieStore; + } + + return sCookieStore; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// static +void LLViewerMedia::loadCookieFile() +{ + // build filename for each user + std::string resolved_filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, PLUGIN_COOKIE_FILE_NAME); + + if (resolved_filename.empty()) + { + llinfos << "can't get path to plugin cookie file - probably not logged in yet." << llendl; + return; + } + + // open the file for reading + llifstream file(resolved_filename); + if (!file.is_open()) + { + llwarns << "can't load plugin cookies from file \"" << PLUGIN_COOKIE_FILE_NAME << "\"" << llendl; + return; + } + + getCookieStore()->readAllCookies(file, true); + + file.close(); + + // TODO: send the clear_cookies message to all loaded plugins +} + + +///////////////////////////////////////////////////////////////////////////////////////// +// static +void LLViewerMedia::saveCookieFile() +{ + // build filename for each user + std::string resolved_filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, PLUGIN_COOKIE_FILE_NAME); + + if (resolved_filename.empty()) + { + llinfos << "can't get path to plugin cookie file - probably not logged in yet." << llendl; + return; + } + + // open a file for writing + llofstream file (resolved_filename); + if (!file.is_open()) + { + llwarns << "can't open plugin cookie file \"" << PLUGIN_COOKIE_FILE_NAME << "\" for writing" << llendl; + return; + } + + getCookieStore()->writePersistentCookies(file); + + file.close(); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// static +void LLViewerMedia::addCookie(const std::string &name, const std::string &value, const std::string &domain, const LLDate &expires, const std::string &path, bool secure) +{ + std::stringstream cookie; + + cookie << name << "=" << LLPluginCookieStore::quoteString(value); + + if(expires.notNull()) + { + cookie << "; expires=" << expires.asRFC1123(); + } + + cookie << "; domain=" << domain; + + cookie << "; path=" << path; + + if(secure) + { + cookie << "; secure"; + } + + getCookieStore()->setCookies(cookie.str()); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// static +void LLViewerMedia::addSessionCookie(const std::string &name, const std::string &value, const std::string &domain, const std::string &path, bool secure) +{ + // A session cookie just has a NULL date. + addCookie(name, value, domain, LLDate(), path, secure); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// static +void LLViewerMedia::removeCookie(const std::string &name, const std::string &domain, const std::string &path ) +{ + // To remove a cookie, add one with the same name, domain, and path that expires in the past. + + addCookie(name, "", domain, LLDate(LLDate::now().secondsSinceEpoch() - 1.0), path); +} + + bool LLViewerMedia::hasInWorldMedia() { if (sInWorldMediaDisabled) return false; @@ -1455,6 +1575,17 @@ bool LLViewerMediaImpl::initializePlugin(const std::string& media_type) media_source->clear_cache(); } + // TODO: Only send cookies to plugins that need them + // Ideally, the plugin should tell us whether it handles cookies or not -- either via the init response or through a separate message. + // Due to the ordering of messages, it's possible we wouldn't get that information back in time to send cookies before sending a navigate message, + // which could cause odd race conditions. + std::string all_cookies = LLViewerMedia::getCookieStore()->getAllCookies(); + lldebugs << "setting cookies: " << all_cookies << llendl; + if(!all_cookies.empty()) + { + media_source->set_cookies(all_cookies); + } + mMediaSource = media_source; updateVolume(); @@ -2152,6 +2283,16 @@ void LLViewerMediaImpl::update() } } } + else + { + // If we didn't just create the impl, it may need to get cookie updates. + if(!sUpdatedCookies.empty()) + { + // TODO: Only send cookies to plugins that need them + mMediaSource->set_cookies(sUpdatedCookies); + } + } + if(mMediaSource == NULL) { @@ -2612,6 +2753,13 @@ void LLViewerMediaImpl::handleMediaEvent(LLPluginClassMedia* plugin, LLPluginCla emitEvent(plugin, event); } +//////////////////////////////////////////////////////////////////////////////// +// virtual +void LLViewerMediaImpl::handleCookieSet(LLPluginClassMedia* self, const std::string &cookie) +{ + LLViewerMedia::getCookieStore()->setCookies(cookie); +} + //////////////////////////////////////////////////////////////////////////////// // virtual void diff --git a/indra/newview/llviewermedia.h b/indra/newview/llviewermedia.h index f9870fb3b9..10dacf9532 100644 --- a/indra/newview/llviewermedia.h +++ b/indra/newview/llviewermedia.h @@ -50,6 +50,7 @@ class LLViewerMediaTexture; class LLMediaEntry; class LLVOVolume; class LLMimeDiscoveryResponder; +class LLPluginCookieStore; typedef LLPointer viewer_media_t; /////////////////////////////////////////////////////////////////////////////// @@ -145,8 +146,17 @@ public: // Set the proxy config for all loaded plugins static void setProxyConfig(bool enable, const std::string &host, int port); + static LLPluginCookieStore *getCookieStore(); + static void loadCookieFile(); + static void saveCookieFile(); + static void addCookie(const std::string &name, const std::string &value, const std::string &domain, const LLDate &expires, const std::string &path = std::string("/"), bool secure = false ); + static void addSessionCookie(const std::string &name, const std::string &value, const std::string &domain, const std::string &path = std::string("/"), bool secure = false ); + static void removeCookie(const std::string &name, const std::string &domain, const std::string &path = std::string("/") ); + private: static void onTeleportFinished(); + + static LLPluginCookieStore *sCookieStore; }; // Implementation functions not exported into header file @@ -294,6 +304,7 @@ public: // Inherited from LLPluginClassMediaOwner /*virtual*/ void handleMediaEvent(LLPluginClassMedia* plugin, LLPluginClassMediaOwner::EMediaEvent); + /*virtual*/ void handleCookieSet(LLPluginClassMedia* self, const std::string &cookie); // LLEditMenuHandler overrides /*virtual*/ void cut(); diff --git a/install.xml b/install.xml index 0f4ab373f5..25c9fdd045 100644 --- a/install.xml +++ b/install.xml @@ -948,9 +948,9 @@ anguage Infrstructure (CLI) international standard darwin md5sum - ada82d41467cdb7f8e25a442f58b6963 + 171bd85ebb81d319e1f15fab8092f8cd url - http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/llqtwebkit-4.6-darwin-20100318.tar.bz2 + http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/llqtwebkit-4.6-darwin-20100326.tar.bz2 linux @@ -962,9 +962,9 @@ anguage Infrstructure (CLI) international standard windows md5sum - 93b49cfea365fe082eda3e466a9beec3 + 04d86bb2eeed4f928d155cb5598ca6b5 url - http://viewer-source-downloads.s3.amazonaws.com/install_pkgs/llqtwebkit-windows-qt4.6-20100318.tar.bz2 + http://viewer-source-downloads.s3.amazonaws.com/install_pkgs/llqtwebkit-windows-qt4.6-20100326.tar.bz2 -- cgit v1.3 From e1517318c58d6796b5566d5cf96c02474fd7376e Mon Sep 17 00:00:00 2001 From: Monroe Linden Date: Mon, 29 Mar 2010 18:36:47 -0700 Subject: Fix for EXT-6615 (SL webkit no longer reports "Second Life" in the HTTP_USER_AGENT) The issue here is that initBrowserWindow() was doing the wrong thing if it got called after the set_user_agent message was received. Made the handler for the set_user_agent message save the string in a member variable (mUserAgent). Initialize mUserAgent to the default string in the MediaPluginWebKit constructor. initBrowserWindow() now sets the user agent string from mUserAgent instead of from a literal. --- indra/media_plugins/webkit/media_plugin_webkit.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'indra/media_plugins') diff --git a/indra/media_plugins/webkit/media_plugin_webkit.cpp b/indra/media_plugins/webkit/media_plugin_webkit.cpp index 85d6b2f5ff..436e077e9b 100644 --- a/indra/media_plugins/webkit/media_plugin_webkit.cpp +++ b/indra/media_plugins/webkit/media_plugin_webkit.cpp @@ -89,6 +89,7 @@ private: std::string mProfileDir; std::string mHostLanguage; + std::string mUserAgent; bool mCookiesEnabled; bool mJavascriptEnabled; bool mPluginsEnabled; @@ -300,7 +301,7 @@ private: LLQtWebKit::getInstance()->addObserver( mBrowserWindowId, this ); // append details to agent string - LLQtWebKit::getInstance()->setBrowserAgentId( "LLPluginMedia Web Browser" ); + LLQtWebKit::getInstance()->setBrowserAgentId( mUserAgent ); #if !LL_QTWEBKIT_USES_PIXMAPS // don't flip bitmap @@ -688,6 +689,7 @@ MediaPluginWebKit::MediaPluginWebKit(LLPluginInstance::sendMessageFunction host_ mHostLanguage = "en"; // default to english mJavascriptEnabled = true; // default to on mPluginsEnabled = true; // default to on + mUserAgent = "LLPluginMedia Web Browser"; } MediaPluginWebKit::~MediaPluginWebKit() @@ -1103,8 +1105,8 @@ void MediaPluginWebKit::receiveMessage(const char *message_string) } else if(message_name == "set_user_agent") { - std::string user_agent = message_in.getValue("user_agent"); - LLQtWebKit::getInstance()->setBrowserAgentId( user_agent ); + mUserAgent = message_in.getValue("user_agent"); + LLQtWebKit::getInstance()->setBrowserAgentId( mUserAgent ); } else if(message_name == "init_history") { -- cgit v1.3 From db3641363d9b9d1dcf31a54897d24c65225155c2 Mon Sep 17 00:00:00 2001 From: Monroe Linden Date: Fri, 9 Apr 2010 12:34:24 -0700 Subject: Set up volume control code in webkit plugin for platform-specific implementations Renamed LinuxVolumeCatcher class to VolumeCatcher. Everything in the header except the class name was already platform-agnostic, so I just renamed it to volume_catcher.h. Moved the stub implementation from the end of linux_volume_catcher.cpp (previously only used if LL_PULSEAUDIO_ENABLED was not defined) to dummy_volume_catcher.cpp, and made CMakeList.txt file use the dummy impl on Linux if PULSEAUDIO is not defined. Removed the ifdefs on the use of VolumeCatcher in MediaPluginWebkit, so it gets called on all platforms. Added a setPan() function to VolumeCatcher (currently unimplemented, but we'd like to have this capability in the future). --- indra/media_plugins/webkit/CMakeLists.txt | 8 ++- .../media_plugins/webkit/dummy_volume_catcher.cpp | 63 +++++++++++++++++++ .../media_plugins/webkit/linux_volume_catcher.cpp | 72 ++++++++-------------- indra/media_plugins/webkit/media_plugin_webkit.cpp | 16 ++--- indra/media_plugins/webkit/volume_catcher.h | 59 ++++++++++++++++++ 5 files changed, 158 insertions(+), 60 deletions(-) create mode 100644 indra/media_plugins/webkit/dummy_volume_catcher.cpp create mode 100644 indra/media_plugins/webkit/volume_catcher.h (limited to 'indra/media_plugins') diff --git a/indra/media_plugins/webkit/CMakeLists.txt b/indra/media_plugins/webkit/CMakeLists.txt index 9f66a77c64..c3a3f8e2b2 100644 --- a/indra/media_plugins/webkit/CMakeLists.txt +++ b/indra/media_plugins/webkit/CMakeLists.txt @@ -45,12 +45,16 @@ set(media_plugin_webkit_LINK_LIBRARIES ${PULSEAUDIO_LIBRARIES} ) -if (LINUX) +# Select which VolumeCatcher implementation to use +if (LINUX AND PULSEAUDIO) list(APPEND media_plugin_webkit_SOURCE_FILES linux_volume_catcher.cpp) list(APPEND media_plugin_webkit_LINK_LIBRARIES ${UI_LIBRARIES} # for glib/GTK ) -endif (LINUX) +else (LINUX AND PULSEAUDIO) + # All other platforms use the dummy volume catcher for now. + list(APPEND media_plugin_webkit_SOURCE_FILES dummy_volume_catcher.cpp) +endif (LINUX AND PULSEAUDIO) add_library(media_plugin_webkit SHARED diff --git a/indra/media_plugins/webkit/dummy_volume_catcher.cpp b/indra/media_plugins/webkit/dummy_volume_catcher.cpp new file mode 100644 index 0000000000..45b2c62eba --- /dev/null +++ b/indra/media_plugins/webkit/dummy_volume_catcher.cpp @@ -0,0 +1,63 @@ +/** + * @file dummy_volume_catcher.cpp + * @brief A null implementation of the "VolumeCatcher" class for platforms where it's not implemented yet. + * + * @cond + * $LicenseInfo:firstyear=2010&license=viewergpl$ + * + * Copyright (c) 2010, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + * @endcond + */ + +#include "volume_catcher.h" + + +class VolumeCatcherImpl +{ +}; + +///////////////////////////////////////////////////// + +VolumeCatcher::VolumeCatcher() +{ + pimpl = NULL; +} + +VolumeCatcher::~VolumeCatcher() +{ +} + +void VolumeCatcher::setVolume(F32 volume) +{ +} + +void VolumeCatcher::setPan(F32 pan) +{ +} + +void VolumeCatcher::pump() +{ +} + diff --git a/indra/media_plugins/webkit/linux_volume_catcher.cpp b/indra/media_plugins/webkit/linux_volume_catcher.cpp index 52ab766f7f..2e7fda865e 100644 --- a/indra/media_plugins/webkit/linux_volume_catcher.cpp +++ b/indra/media_plugins/webkit/linux_volume_catcher.cpp @@ -42,11 +42,9 @@ #include "linden_common.h" -#include "linux_volume_catcher.h" +#include "volume_catcher.h" -#if LL_PULSEAUDIO_ENABLED - extern "C" { #include @@ -161,11 +159,11 @@ extern "C" { } -class LinuxVolumeCatcherImpl +class VolumeCatcherImpl { public: - LinuxVolumeCatcherImpl(); - ~LinuxVolumeCatcherImpl(); + VolumeCatcherImpl(); + ~VolumeCatcherImpl(); void setVolume(F32 volume); void pump(void); @@ -189,7 +187,7 @@ public: bool mGotSyms; }; -LinuxVolumeCatcherImpl::LinuxVolumeCatcherImpl() +VolumeCatcherImpl::VolumeCatcherImpl() : mDesiredVolume(0.0f), mMainloop(NULL), mPAContext(NULL), @@ -199,17 +197,17 @@ LinuxVolumeCatcherImpl::LinuxVolumeCatcherImpl() init(); } -LinuxVolumeCatcherImpl::~LinuxVolumeCatcherImpl() +VolumeCatcherImpl::~VolumeCatcherImpl() { cleanup(); } -bool LinuxVolumeCatcherImpl::loadsyms(std::string pulse_dso_name) +bool VolumeCatcherImpl::loadsyms(std::string pulse_dso_name) { return grab_pa_syms(pulse_dso_name); } -void LinuxVolumeCatcherImpl::init() +void VolumeCatcherImpl::init() { // try to be as defensive as possible because PA's interface is a // bit fragile and (for our purposes) we'd rather simply not function @@ -262,7 +260,7 @@ void LinuxVolumeCatcherImpl::init() } } -void LinuxVolumeCatcherImpl::cleanup() +void VolumeCatcherImpl::cleanup() { mConnected = false; @@ -280,7 +278,7 @@ void LinuxVolumeCatcherImpl::cleanup() mMainloop = NULL; } -void LinuxVolumeCatcherImpl::setVolume(F32 volume) +void VolumeCatcherImpl::setVolume(F32 volume) { mDesiredVolume = volume; @@ -294,13 +292,13 @@ void LinuxVolumeCatcherImpl::setVolume(F32 volume) pump(); } -void LinuxVolumeCatcherImpl::pump() +void VolumeCatcherImpl::pump() { gboolean may_block = FALSE; g_main_context_iteration(g_main_context_default(), may_block); } -void LinuxVolumeCatcherImpl::connected_okay() +void VolumeCatcherImpl::connected_okay() { pa_operation *op; @@ -324,7 +322,7 @@ void LinuxVolumeCatcherImpl::connected_okay() } } -void LinuxVolumeCatcherImpl::update_all_volumes(F32 volume) +void VolumeCatcherImpl::update_all_volumes(F32 volume) { for (std::set::iterator it = mSinkInputIndices.begin(); it != mSinkInputIndices.end(); ++it) @@ -333,7 +331,7 @@ void LinuxVolumeCatcherImpl::update_all_volumes(F32 volume) } } -void LinuxVolumeCatcherImpl::update_index_volume(U32 index, F32 volume) +void VolumeCatcherImpl::update_index_volume(U32 index, F32 volume) { static pa_cvolume cvol; llpa_cvolume_set(&cvol, mSinkInputNumChannels[index], @@ -355,7 +353,7 @@ void LinuxVolumeCatcherImpl::update_index_volume(U32 index, F32 volume) void callback_discovered_sinkinput(pa_context *context, const pa_sink_input_info *sii, int eol, void *userdata) { - LinuxVolumeCatcherImpl *impl = dynamic_cast((LinuxVolumeCatcherImpl*)userdata); + VolumeCatcherImpl *impl = dynamic_cast((VolumeCatcherImpl*)userdata); llassert(impl); if (0 == eol) @@ -386,7 +384,7 @@ void callback_discovered_sinkinput(pa_context *context, const pa_sink_input_info void callback_subscription_alert(pa_context *context, pa_subscription_event_type_t t, uint32_t index, void *userdata) { - LinuxVolumeCatcherImpl *impl = dynamic_cast((LinuxVolumeCatcherImpl*)userdata); + VolumeCatcherImpl *impl = dynamic_cast((VolumeCatcherImpl*)userdata); llassert(impl); switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) { @@ -420,7 +418,7 @@ void callback_subscription_alert(pa_context *context, pa_subscription_event_type void callback_context_state(pa_context *context, void *userdata) { - LinuxVolumeCatcherImpl *impl = dynamic_cast((LinuxVolumeCatcherImpl*)userdata); + VolumeCatcherImpl *impl = dynamic_cast((VolumeCatcherImpl*)userdata); llassert(impl); switch (llpa_context_get_state(context)) @@ -441,48 +439,30 @@ void callback_context_state(pa_context *context, void *userdata) ///////////////////////////////////////////////////// -LinuxVolumeCatcher::LinuxVolumeCatcher() +VolumeCatcher::VolumeCatcher() { - pimpl = new LinuxVolumeCatcherImpl(); + pimpl = new VolumeCatcherImpl(); } -LinuxVolumeCatcher::~LinuxVolumeCatcher() +VolumeCatcher::~VolumeCatcher() { delete pimpl; pimpl = NULL; } -void LinuxVolumeCatcher::setVolume(F32 volume) +void VolumeCatcher::setVolume(F32 volume) { llassert(pimpl); pimpl->setVolume(volume); } -void LinuxVolumeCatcher::pump() -{ - llassert(pimpl); - pimpl->pump(); -} - -#else // !LL_PULSEAUDIO_ENABLED - -// stub. - -LinuxVolumeCatcher::LinuxVolumeCatcher() -{ - pimpl = NULL; -} - -LinuxVolumeCatcher::~LinuxVolumeCatcher() +void VolumeCatcher::setPan(F32 pan) { + // TODO: implement this (if possible) } -void LinuxVolumeCatcher::setVolume(F32 volume) -{ -} - -void LinuxVolumeCatcher::pump() +void VolumeCatcher::pump() { + llassert(pimpl); + pimpl->pump(); } - -#endif // LL_PULSEAUDIO_ENABLED diff --git a/indra/media_plugins/webkit/media_plugin_webkit.cpp b/indra/media_plugins/webkit/media_plugin_webkit.cpp index 436e077e9b..47766a24cb 100644 --- a/indra/media_plugins/webkit/media_plugin_webkit.cpp +++ b/indra/media_plugins/webkit/media_plugin_webkit.cpp @@ -50,9 +50,7 @@ # define LL_QTWEBKIT_USES_PIXMAPS 0 #endif // LL_LINUX -#if LL_LINUX -# include "linux_volume_catcher.h" -#endif // LL_LINUX +# include "volume_catcher.h" #if LL_WINDOWS # include @@ -119,9 +117,7 @@ private: F32 mBackgroundG; F32 mBackgroundB; -#if LL_LINUX - LinuxVolumeCatcher mLinuxVolumeCatcher; -#endif // LL_LINUX + VolumeCatcher mVolumeCatcher; void setInitState(int state) { @@ -135,9 +131,7 @@ private: { LLQtWebKit::getInstance()->pump( milliseconds ); -#if LL_LINUX - mLinuxVolumeCatcher.pump(); -#endif // LL_LINUX + mVolumeCatcher.pump(); checkEditState(); @@ -1139,9 +1133,7 @@ void MediaPluginWebKit::receiveMessage(const char *message_string) void MediaPluginWebKit::setVolume(F32 volume) { -#if LL_LINUX - mLinuxVolumeCatcher.setVolume(volume); -#endif // LL_LINUX + mVolumeCatcher.setVolume(volume); } int init_media_plugin(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data, LLPluginInstance::sendMessageFunction *plugin_send_func, void **plugin_user_data) diff --git a/indra/media_plugins/webkit/volume_catcher.h b/indra/media_plugins/webkit/volume_catcher.h new file mode 100644 index 0000000000..77b10cfed0 --- /dev/null +++ b/indra/media_plugins/webkit/volume_catcher.h @@ -0,0 +1,59 @@ +/** + * @file volume_catcher.h + * @brief Interface to a class with platform-specific implementations that allows control of the audio volume of all sources in the current process. + * + * @cond + * $LicenseInfo:firstyear=2010&license=viewergpl$ + * + * Copyright (c) 2010, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + * @endcond + */ + +#ifndef VOLUME_CATCHER_H +#define VOLUME_CATCHER_H + +#include "linden_common.h" + +class VolumeCatcherImpl; + +class VolumeCatcher +{ + public: + VolumeCatcher(); + ~VolumeCatcher(); + + void setVolume(F32 volume); // 0.0 - 1.0 + + // Set the left-right pan of audio sources + // where -1.0 = left, 0 = center, and 1.0 = right + void setPan(F32 pan); + + void pump(); // call this at least a few times a second if you can - it affects how quickly we can 'catch' a new audio source and adjust its volume + + private: + VolumeCatcherImpl *pimpl; +}; + +#endif // VOLUME_CATCHER_H -- cgit v1.3 From 157bc3071da89b15f1393475e906e7301698cc8a Mon Sep 17 00:00:00 2001 From: Monroe Linden Date: Tue, 13 Apr 2010 10:10:58 -0700 Subject: Initial implementation of mac_volume_catcher.cpp. --- indra/media_plugins/webkit/CMakeLists.txt | 8 + indra/media_plugins/webkit/mac_volume_catcher.cpp | 275 ++++++++++++++++++++++ 2 files changed, 283 insertions(+) create mode 100644 indra/media_plugins/webkit/mac_volume_catcher.cpp (limited to 'indra/media_plugins') diff --git a/indra/media_plugins/webkit/CMakeLists.txt b/indra/media_plugins/webkit/CMakeLists.txt index c3a3f8e2b2..4f183cddeb 100644 --- a/indra/media_plugins/webkit/CMakeLists.txt +++ b/indra/media_plugins/webkit/CMakeLists.txt @@ -51,6 +51,14 @@ if (LINUX AND PULSEAUDIO) list(APPEND media_plugin_webkit_LINK_LIBRARIES ${UI_LIBRARIES} # for glib/GTK ) +elseif (DARWIN) + list(APPEND media_plugin_webkit_SOURCE_FILES mac_volume_catcher.cpp) + find_library(CORESERVICES_LIBRARY CoreServices) + find_library(AUDIOUNIT_LIBRARY AudioUnit) + list(APPEND media_plugin_webkit_LINK_LIBRARIES + ${CORESERVICES_LIBRARY} # for Component Manager calls + ${AUDIOUNIT_LIBRARY} # for AudioUnit calls + ) else (LINUX AND PULSEAUDIO) # All other platforms use the dummy volume catcher for now. list(APPEND media_plugin_webkit_SOURCE_FILES dummy_volume_catcher.cpp) diff --git a/indra/media_plugins/webkit/mac_volume_catcher.cpp b/indra/media_plugins/webkit/mac_volume_catcher.cpp new file mode 100644 index 0000000000..03249f9e83 --- /dev/null +++ b/indra/media_plugins/webkit/mac_volume_catcher.cpp @@ -0,0 +1,275 @@ +/** + * @file dummy_volume_catcher.cpp + * @brief A Mac OS X specific hack to control the volume level of all audio channels opened by a process. + * + * @cond + * $LicenseInfo:firstyear=2010&license=viewergpl$ + * + * Copyright (c) 2010, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + * @endcond + */ + +/************************************************************************************************************** + This code works by using CaptureComponent to capture the "Default Output" audio component + (kAudioUnitType_Output/kAudioUnitSubType_DefaultOutput) and delegating all calls to the original component. + It does this just to keep track of all instances of the default output component, so that it can set the + kHALOutputParam_Volume parameter on all of them to adjust the output volume. +**************************************************************************************************************/ + +#include "volume_catcher.h" + +#include +#include +#include + +struct VolumeCatcherStorage; + +class VolumeCatcherImpl +{ +public: + + void setVolume(F32 volume); + void setPan(F32 pan); + void pump(void); + + void setDelegateVolume(ComponentInstance delegate); + + std::list mComponentInstances; + Component mOriginalDefaultOutput; + Component mVolumeAdjuster; + + static VolumeCatcherImpl *getInstance(); +private: + VolumeCatcherImpl(); + ~VolumeCatcherImpl(); + + // The component entry point needs to be able to find the instance. + static VolumeCatcherImpl *sInstance; + + F32 mVolume; + F32 mPan; +}; + +VolumeCatcherImpl *VolumeCatcherImpl::sInstance = NULL;; + +struct VolumeCatcherStorage +{ + ComponentInstance self; + ComponentInstance delegate; +}; + +static ComponentResult volume_catcher_component_entry(ComponentParameters *cp, Handle componentStorage); +static ComponentResult volume_catcher_component_open(VolumeCatcherStorage *storage, ComponentInstance self); +static ComponentResult volume_catcher_component_close(VolumeCatcherStorage *storage, ComponentInstance self); + +VolumeCatcherImpl *VolumeCatcherImpl::getInstance() +{ + if(!sInstance) + { + // The constructor will set up the instance pointer. + new VolumeCatcherImpl; + } + + return sInstance; +} + +VolumeCatcherImpl::VolumeCatcherImpl() +{ + sInstance = this; + + mVolume = 1.0; // default to full volume + mPan = 0.5; // and center pan + + // Register a component which can delegate + + // Capture the default audio output component. + ComponentDescription desc; + desc.componentType = kAudioUnitType_Output; + desc.componentSubType = kAudioUnitSubType_DefaultOutput; + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + + // Find the original default output component + mOriginalDefaultOutput = FindNextComponent(NULL, &desc); + + // Register our own output component with the same parameters + mVolumeAdjuster = RegisterComponent(&desc, NewComponentRoutineUPP(volume_catcher_component_entry), 0, NULL, NULL, NULL); + + // Capture the original component, so we always get found instead. + CaptureComponent(mOriginalDefaultOutput, mVolumeAdjuster); + +} + +static ComponentResult volume_catcher_component_entry(ComponentParameters *cp, Handle componentStorage) +{ + ComponentResult result = badComponentSelector; + VolumeCatcherStorage *storage = (VolumeCatcherStorage*)componentStorage; + + switch(cp->what) + { + case kComponentOpenSelect: +// std::cerr << "kComponentOpenSelect" << std::endl; + result = CallComponentFunctionWithStorageProcInfo((Handle)storage, cp, (ProcPtr)volume_catcher_component_open, uppCallComponentOpenProcInfo); + break; + + case kComponentCloseSelect: +// std::cerr << "kComponentCloseSelect" << std::endl; + result = CallComponentFunctionWithStorageProcInfo((Handle)storage, cp, (ProcPtr)volume_catcher_component_close, uppCallComponentCloseProcInfo); + // CallComponentFunctionWithStorageProcInfo + break; + + default: +// std::cerr << "Delegating selector: " << cp->what << " to component instance " << storage->delegate << std::endl; + result = DelegateComponentCall(cp, storage->delegate); + break; + } + + return result; +} + +static ComponentResult volume_catcher_component_open(VolumeCatcherStorage *storage, ComponentInstance self) +{ + ComponentResult result = noErr; + + storage = new VolumeCatcherStorage; + SetComponentInstanceStorage(self, (Handle)storage); + + + VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance(); + + impl->mComponentInstances.push_back(storage); + + storage->self = self; + storage->delegate = NULL; + + result = OpenAComponent(impl->mOriginalDefaultOutput, &(storage->delegate)); + +// std::cerr << "OpenAComponent result = " << result << ", component ref = " << storage->delegate << std::endl; + + impl->setDelegateVolume(storage->delegate); + + return result; +} + +static ComponentResult volume_catcher_component_close(VolumeCatcherStorage *storage, ComponentInstance self) +{ + ComponentResult result = noErr; + + if(storage) + { + if(storage->delegate) + { + CloseComponent(storage->delegate); + storage->delegate = NULL; + } + + VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance(); + impl->mComponentInstances.remove(storage); + delete[] storage; + } + + return result; +} + +VolumeCatcherImpl::~VolumeCatcherImpl() +{ + // We expect to persist until the process exits. This should never be called. + abort(); +} + +void VolumeCatcherImpl::setVolume(F32 volume) +{ + VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance(); + impl->mVolume = volume; + + std::list::iterator iter; + + for(iter = mComponentInstances.begin(); iter != mComponentInstances.end(); ++iter) + { + impl->setDelegateVolume((*iter)->delegate); + } +} + +void VolumeCatcherImpl::setPan(F32 pan) +{ + VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance(); + impl->mPan = pan; + + // TODO: implement this. + // This will probably require adding a "panner" audio unit to the chain somehow. + // There's also a "3d mixer" component that we might be able to use... +} + +void VolumeCatcherImpl::pump(void) +{ +} + +void VolumeCatcherImpl::setDelegateVolume(ComponentInstance delegate) +{ +// std::cerr << "Setting volume on component instance: " << (delegate) << " to " << mVolume << std::endl; + + OSStatus err; + err = AudioUnitSetParameter( + delegate, + kHALOutputParam_Volume, + kAudioUnitScope_Global, + 0, + mVolume, + 0); + + if(err) + { +// std::cerr << " AudioUnitSetParameter returned " << err << std::endl; + } +} + +///////////////////////////////////////////////////// + +VolumeCatcher::VolumeCatcher() +{ + pimpl = VolumeCatcherImpl::getInstance(); +} + +VolumeCatcher::~VolumeCatcher() +{ + // Let the instance persist until exit. +} + +void VolumeCatcher::setVolume(F32 volume) +{ + VolumeCatcherImpl::getInstance()->setVolume(volume); +} + +void VolumeCatcher::setPan(F32 pan) +{ + VolumeCatcherImpl::getInstance()->setPan(pan); +} + +void VolumeCatcher::pump() +{ + VolumeCatcherImpl::getInstance()->pump(); +} + -- cgit v1.3 From d7e4fa17e80072d609aaadaf060ee8f51f1069eb Mon Sep 17 00:00:00 2001 From: Monroe Linden Date: Tue, 13 Apr 2010 15:16:03 -0700 Subject: Minor cleanup in mac_volume_catcher.cpp. --- indra/media_plugins/webkit/mac_volume_catcher.cpp | 100 +++++++++++----------- 1 file changed, 49 insertions(+), 51 deletions(-) (limited to 'indra/media_plugins') diff --git a/indra/media_plugins/webkit/mac_volume_catcher.cpp b/indra/media_plugins/webkit/mac_volume_catcher.cpp index 03249f9e83..9788f10a58 100644 --- a/indra/media_plugins/webkit/mac_volume_catcher.cpp +++ b/indra/media_plugins/webkit/mac_volume_catcher.cpp @@ -52,9 +52,8 @@ public: void setVolume(F32 volume); void setPan(F32 pan); - void pump(void); - void setDelegateVolume(ComponentInstance delegate); + void setInstanceVolume(VolumeCatcherStorage *instance); std::list mComponentInstances; Component mOriginalDefaultOutput; @@ -62,12 +61,14 @@ public: static VolumeCatcherImpl *getInstance(); private: + // This is a singleton class -- both callers and the component implementation should use getInstance() to find the instance. VolumeCatcherImpl(); - ~VolumeCatcherImpl(); - - // The component entry point needs to be able to find the instance. static VolumeCatcherImpl *sInstance; + // The singlar instance of this class is expected to last until the process exits. + // To ensure this, we declare the destructor here but never define it, so any code which attempts to destroy the instance will not link. + ~VolumeCatcherImpl(); + F32 mVolume; F32 mPan; }; @@ -88,8 +89,7 @@ VolumeCatcherImpl *VolumeCatcherImpl::getInstance() { if(!sInstance) { - // The constructor will set up the instance pointer. - new VolumeCatcherImpl; + sInstance = new VolumeCatcherImpl; } return sInstance; @@ -97,14 +97,9 @@ VolumeCatcherImpl *VolumeCatcherImpl::getInstance() VolumeCatcherImpl::VolumeCatcherImpl() { - sInstance = this; - mVolume = 1.0; // default to full volume mPan = 0.5; // and center pan - - // Register a component which can delegate - - // Capture the default audio output component. + ComponentDescription desc; desc.componentType = kAudioUnitType_Output; desc.componentSubType = kAudioUnitSubType_DefaultOutput; @@ -153,23 +148,33 @@ static ComponentResult volume_catcher_component_entry(ComponentParameters *cp, H static ComponentResult volume_catcher_component_open(VolumeCatcherStorage *storage, ComponentInstance self) { ComponentResult result = noErr; + VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance(); storage = new VolumeCatcherStorage; - SetComponentInstanceStorage(self, (Handle)storage); - - VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance(); - - impl->mComponentInstances.push_back(storage); - storage->self = self; storage->delegate = NULL; result = OpenAComponent(impl->mOriginalDefaultOutput, &(storage->delegate)); -// std::cerr << "OpenAComponent result = " << result << ", component ref = " << storage->delegate << std::endl; + if(result != noErr) + { +// std::cerr << "OpenAComponent result = " << result << ", component ref = " << storage->delegate << std::endl; + + // If we failed to open the delagate component, our open is going to fail. Clean things up. + delete storage; + } + else + { + // Success -- set up this component's storage + SetComponentInstanceStorage(self, (Handle)storage); - impl->setDelegateVolume(storage->delegate); + // add this instance to the global list + impl->mComponentInstances.push_back(storage); + + // and set up the initial volume + impl->setInstanceVolume(storage); + } return result; } @@ -194,22 +199,15 @@ static ComponentResult volume_catcher_component_close(VolumeCatcherStorage *stor return result; } -VolumeCatcherImpl::~VolumeCatcherImpl() -{ - // We expect to persist until the process exits. This should never be called. - abort(); -} - void VolumeCatcherImpl::setVolume(F32 volume) { VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance(); impl->mVolume = volume; - - std::list::iterator iter; - - for(iter = mComponentInstances.begin(); iter != mComponentInstances.end(); ++iter) + + // Iterate through all known instances, setting the volume on each. + for(std::list::iterator iter = mComponentInstances.begin(); iter != mComponentInstances.end(); ++iter) { - impl->setDelegateVolume((*iter)->delegate); + impl->setInstanceVolume(*iter); } } @@ -223,23 +221,23 @@ void VolumeCatcherImpl::setPan(F32 pan) // There's also a "3d mixer" component that we might be able to use... } -void VolumeCatcherImpl::pump(void) -{ -} - -void VolumeCatcherImpl::setDelegateVolume(ComponentInstance delegate) +void VolumeCatcherImpl::setInstanceVolume(VolumeCatcherStorage *instance) { -// std::cerr << "Setting volume on component instance: " << (delegate) << " to " << mVolume << std::endl; - - OSStatus err; - err = AudioUnitSetParameter( - delegate, - kHALOutputParam_Volume, - kAudioUnitScope_Global, - 0, - mVolume, - 0); - +// std::cerr << "Setting volume on component instance: " << (instance->delegate) << " to " << mVolume << std::endl; + + OSStatus err = noErr; + + if(instance && instance->delegate) + { + err = AudioUnitSetParameter( + instance->delegate, + kHALOutputParam_Volume, + kAudioUnitScope_Global, + 0, + mVolume, + 0); + } + if(err) { // std::cerr << " AudioUnitSetParameter returned " << err << std::endl; @@ -260,16 +258,16 @@ VolumeCatcher::~VolumeCatcher() void VolumeCatcher::setVolume(F32 volume) { - VolumeCatcherImpl::getInstance()->setVolume(volume); + pimpl->setVolume(volume); } void VolumeCatcher::setPan(F32 pan) { - VolumeCatcherImpl::getInstance()->setPan(pan); + pimpl->setPan(pan); } void VolumeCatcher::pump() { - VolumeCatcherImpl::getInstance()->pump(); + // No periodic tasks are necessary for this implementation. } -- cgit v1.3 From ba35fdd97c0eedb698a026f889092caa1c41b61b Mon Sep 17 00:00:00 2001 From: Richard Linden Date: Thu, 15 Apr 2010 17:22:35 -0700 Subject: EXT-6888 - Implement per-media instance volume control --- indra/media_plugins/webkit/CMakeLists.txt | 2 + .../webkit/windows_volume_catcher.cpp | 291 +++++++++++++++++++++ indra/newview/llpanelprimmediacontrols.cpp | 2 +- 3 files changed, 294 insertions(+), 1 deletion(-) create mode 100644 indra/media_plugins/webkit/windows_volume_catcher.cpp (limited to 'indra/media_plugins') diff --git a/indra/media_plugins/webkit/CMakeLists.txt b/indra/media_plugins/webkit/CMakeLists.txt index 4f183cddeb..40722622a4 100644 --- a/indra/media_plugins/webkit/CMakeLists.txt +++ b/indra/media_plugins/webkit/CMakeLists.txt @@ -59,6 +59,8 @@ elseif (DARWIN) ${CORESERVICES_LIBRARY} # for Component Manager calls ${AUDIOUNIT_LIBRARY} # for AudioUnit calls ) +elseif (WINDOWS) + list(APPEND media_plugin_webkit_SOURCE_FILES windows_volume_catcher.cpp) else (LINUX AND PULSEAUDIO) # All other platforms use the dummy volume catcher for now. list(APPEND media_plugin_webkit_SOURCE_FILES dummy_volume_catcher.cpp) diff --git a/indra/media_plugins/webkit/windows_volume_catcher.cpp b/indra/media_plugins/webkit/windows_volume_catcher.cpp new file mode 100644 index 0000000000..b01986db56 --- /dev/null +++ b/indra/media_plugins/webkit/windows_volume_catcher.cpp @@ -0,0 +1,291 @@ +/** + * @file windows_volume_catcher.cpp + * @brief A Windows implementation of volume level control of all audio channels opened by a process. + * + * @cond + * $LicenseInfo:firstyear=2010&license=viewergpl$ + * + * Copyright (c) 2010, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + * @endcond + */ + +#include "volume_catcher.h" +#include + +class Mixer +{ +public: + static Mixer* create(U32 index); + ~Mixer(); + + void setMute(bool mute); + void setVolume(F32 volume_left, F32 volume_right); + +private: + Mixer(HMIXER handle, U32 mute_control_id, U32 volume_control_id, U32 min_volume, U32 max_volume); + + HMIXER mHandle; + U32 mMuteControlID; + U32 mVolumeControlID; + U32 mMinVolume; + U32 mMaxVolume; +}; + +Mixer* Mixer::create(U32 index) +{ + HMIXER mixer_handle; + MMRESULT result = mixerOpen( &mixer_handle, + index, + 0, // HWND to call when state changes - not used + 0, // user data for callback - not used + MIXER_OBJECTF_MIXER ); + + if (result == MMSYSERR_NOERROR) + { + MIXERLINE mixer_line; + mixer_line.cbStruct = sizeof( MIXERLINE ); + // try speakers first + mixer_line.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_SPEAKERS; + + MMRESULT result = mixerGetLineInfo( reinterpret_cast< HMIXEROBJ >( mixer_handle ), + &mixer_line, + MIXER_OBJECTF_HMIXER | MIXER_GETLINEINFOF_COMPONENTTYPE ); + if (result != MMSYSERR_NOERROR) + { + // failed - try headphones next + mixer_line.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_HEADPHONES; + result = mixerGetLineInfo( reinterpret_cast< HMIXEROBJ >( mixer_handle ), + &mixer_line, + MIXER_OBJECTF_HMIXER | MIXER_GETLINEINFOF_COMPONENTTYPE ); + } + + if (result == MMSYSERR_NOERROR) + { + // get control id + MIXERCONTROL mixer_control; + MIXERLINECONTROLS mixer_line_controls; + mixer_line_controls.cbStruct = sizeof( MIXERLINECONTROLS ); + mixer_line_controls.dwLineID = mixer_line.dwLineID; + mixer_line_controls.dwControlType = MIXERCONTROL_CONTROLTYPE_MUTE; + mixer_line_controls.cControls = 1; + mixer_line_controls.cbmxctrl = sizeof( MIXERCONTROL ); + mixer_line_controls.pamxctrl = &mixer_control; + + result = mixerGetLineControls( reinterpret_cast< HMIXEROBJ >( mixer_handle ), + &mixer_line_controls, + MIXER_OBJECTF_HMIXER | MIXER_GETLINECONTROLSF_ONEBYTYPE ); + if (result == MMSYSERR_NOERROR ) + { + // We have a mute mixer. Remember the mute control id + U32 mute_control_id = mixer_control.dwControlID; + + // now query for volume controls + mixer_line_controls.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME; + result = mixerGetLineControls( reinterpret_cast< HMIXEROBJ >( mixer_handle ), + &mixer_line_controls, + MIXER_OBJECTF_HMIXER | MIXER_GETLINECONTROLSF_ONEBYTYPE ); + + if (result == MMSYSERR_NOERROR) + { + // we have both mute and volume controls for this mixer, so remember it + return new Mixer(mixer_handle, + mute_control_id, + mixer_control.dwControlID, + mixer_control.Bounds.dwMinimum, + mixer_control.Bounds.dwMaximum); + } + } + } + } + return NULL; +} + +Mixer::Mixer(HMIXER handle, U32 mute_control_id, U32 volume_control_id, U32 min_volume, U32 max_volume) +: mHandle(handle), + mMuteControlID(mute_control_id), + mVolumeControlID(volume_control_id), + mMinVolume(min_volume), + mMaxVolume(max_volume) +{} + +Mixer::~Mixer() +{ +} + +void Mixer::setMute(bool mute) +{ + MIXERCONTROLDETAILS_BOOLEAN mixer_control_details_bool = { mute }; + MIXERCONTROLDETAILS mixer_control_details; + mixer_control_details.cbStruct = sizeof( MIXERCONTROLDETAILS ); + mixer_control_details.dwControlID = mMuteControlID; + mixer_control_details.cChannels = 1; + mixer_control_details.cMultipleItems = 0; + mixer_control_details.cbDetails = sizeof( MIXERCONTROLDETAILS_BOOLEAN ); + mixer_control_details.paDetails = &mixer_control_details_bool; + + mixerSetControlDetails( reinterpret_cast< HMIXEROBJ >( mHandle ), + &mixer_control_details, + MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE ); +} + +void Mixer::setVolume(F32 volume_left, F32 volume_right) +{ + U32 volume_left_mixer = (U32) + ((F32)mMinVolume + + (volume_left * ((F32)mMaxVolume - (F32)mMinVolume))); + U32 volume_right_mixer = (U32) + ((F32)mMinVolume + + (volume_right * ((F32)mMaxVolume - (F32)mMinVolume))); + MIXERCONTROLDETAILS_UNSIGNED mixer_control_details_unsigned[ 2 ] = { volume_left_mixer, volume_right_mixer }; + MIXERCONTROLDETAILS mixer_control_details; + mixer_control_details.cbStruct = sizeof( MIXERCONTROLDETAILS ); + mixer_control_details.dwControlID = mVolumeControlID; + mixer_control_details.cChannels = 2; + mixer_control_details.cMultipleItems = 0; + mixer_control_details.cbDetails = sizeof( MIXERCONTROLDETAILS_UNSIGNED ); + mixer_control_details.paDetails = &mixer_control_details_unsigned; + + mixerSetControlDetails( reinterpret_cast< HMIXEROBJ >( mHandle ), + &mixer_control_details, + MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE ); +} + + +class VolumeCatcherImpl +{ +public: + + void setVolume(F32 volume); + void setPan(F32 pan); + + static VolumeCatcherImpl *getInstance(); +private: + // This is a singleton class -- both callers and the component implementation should use getInstance() to find the instance. + VolumeCatcherImpl(); + ~VolumeCatcherImpl(); + + static VolumeCatcherImpl *sInstance; + + F32 mVolume; + F32 mPan; + typedef std::vector mixer_vector_t; + mixer_vector_t mMixers; +}; + +VolumeCatcherImpl *VolumeCatcherImpl::sInstance = NULL; + +VolumeCatcherImpl *VolumeCatcherImpl::getInstance() +{ + if(!sInstance) + { + sInstance = new VolumeCatcherImpl; + } + + return sInstance; +} + +VolumeCatcherImpl::VolumeCatcherImpl() +: mVolume(1.0f), + mPan(0.f) // 0 is centered +{ + U32 num_mixers = mixerGetNumDevs(); + for (U32 mixer_index = 0; mixer_index < num_mixers; ++mixer_index) + { + Mixer* mixerp = Mixer::create(mixer_index); + if (mixerp) + { + mMixers.push_back(mixerp); + } + } +} + +VolumeCatcherImpl::~VolumeCatcherImpl() +{ + for(mixer_vector_t::iterator it = mMixers.begin(), end_it = mMixers.end(); + it != end_it; + ++it) + { + delete *it; + *it = NULL; + } +} + + +void VolumeCatcherImpl::setVolume(F32 volume) +{ + F32 left_volume = volume * min(1.f, 1.f - mPan); + F32 right_volume = volume * max(0.f, 1.f + mPan); + + for(mixer_vector_t::iterator it = mMixers.begin(), end_it = mMixers.end(); + it != end_it; + ++it) + { + (*it)->setVolume(left_volume, right_volume); + + if (volume == 0.f && mVolume != 0.f) + { + (*it)->setMute(true); + } + else if (mVolume == 0.f && volume != 0.f) + { + (*it)->setMute(false); + } + + } + mVolume = volume; +} + +void VolumeCatcherImpl::setPan(F32 pan) +{ + mPan = pan; +} + +///////////////////////////////////////////////////// + +VolumeCatcher::VolumeCatcher() +{ + pimpl = VolumeCatcherImpl::getInstance(); +} + +VolumeCatcher::~VolumeCatcher() +{ + // Let the instance persist until exit. +} + +void VolumeCatcher::setVolume(F32 volume) +{ + pimpl->setVolume(volume); +} + +void VolumeCatcher::setPan(F32 pan) +{ + pimpl->setPan(pan); +} + +void VolumeCatcher::pump() +{ + // No periodic tasks are necessary for this implementation. +} + diff --git a/indra/newview/llpanelprimmediacontrols.cpp b/indra/newview/llpanelprimmediacontrols.cpp index 83244edb8e..f9ca05f178 100644 --- a/indra/newview/llpanelprimmediacontrols.cpp +++ b/indra/newview/llpanelprimmediacontrols.cpp @@ -66,7 +66,7 @@ #include "llwindow.h" #include "llfloatertools.h" // to enable hide if build tools are up -#if defined(LL_DARWIN) || (defined(LL_WINDOW) && (! defined(LL_RELEASE_FOR_DOWNLOAD)) ) +#if defined(LL_DARWIN) || (LL_WINDOWS && !LL_RELEASE_FOR_DOWNLOAD ) #define PER_MEDIA_VOLUME #endif -- cgit v1.3 From e6daa50c5878885b27f4145eeeeee9745e12086a Mon Sep 17 00:00:00 2001 From: Richard Linden Date: Fri, 16 Apr 2010 12:10:57 -0700 Subject: windows_volume_catcher.cpp, now with comments! also added volume_catcher.h to project --- indra/media_plugins/webkit/CMakeLists.txt | 9 + .../webkit/windows_volume_catcher.cpp | 209 ++++++++++++--------- 2 files changed, 126 insertions(+), 92 deletions(-) (limited to 'indra/media_plugins') diff --git a/indra/media_plugins/webkit/CMakeLists.txt b/indra/media_plugins/webkit/CMakeLists.txt index 40722622a4..c33a26b637 100644 --- a/indra/media_plugins/webkit/CMakeLists.txt +++ b/indra/media_plugins/webkit/CMakeLists.txt @@ -36,6 +36,10 @@ set(media_plugin_webkit_SOURCE_FILES media_plugin_webkit.cpp ) +set(media_plugin_webkit_HEADER_FILES + volume_catcher.h + ) + set(media_plugin_webkit_LINK_LIBRARIES ${LLPLUGIN_LIBRARIES} ${MEDIA_PLUGIN_BASE_LIBRARIES} @@ -66,6 +70,11 @@ else (LINUX AND PULSEAUDIO) list(APPEND media_plugin_webkit_SOURCE_FILES dummy_volume_catcher.cpp) endif (LINUX AND PULSEAUDIO) +set_source_files_properties(${media_plugin_webkit_HEADER_FILES} + PROPERTIES HEADER_FILE_ONLY TRUE) + +list(APPEND media_plugin_webkit_SOURCE_FILES ${media_plugin_webkit_HEADER_FILES}) + add_library(media_plugin_webkit SHARED ${media_plugin_webkit_SOURCE_FILES} diff --git a/indra/media_plugins/webkit/windows_volume_catcher.cpp b/indra/media_plugins/webkit/windows_volume_catcher.cpp index b01986db56..1c1ef0b42f 100644 --- a/indra/media_plugins/webkit/windows_volume_catcher.cpp +++ b/indra/media_plugins/webkit/windows_volume_catcher.cpp @@ -32,8 +32,12 @@ */ #include "volume_catcher.h" -#include +#include +// +// Abstracts a Win32 mixer line and associated state +// for muting and changing volume on a given output +// class Mixer { public: @@ -44,81 +48,91 @@ public: void setVolume(F32 volume_left, F32 volume_right); private: + // use create(index) to create a Mixer Mixer(HMIXER handle, U32 mute_control_id, U32 volume_control_id, U32 min_volume, U32 max_volume); - HMIXER mHandle; - U32 mMuteControlID; - U32 mVolumeControlID; - U32 mMinVolume; - U32 mMaxVolume; + HMIXER mHandle; + U32 mMuteControlID; // handle to mixer controller for muting + U32 mVolumeControlID; // handle to mixer controller for changing volume + U32 mMinVolume; // value that specifies minimum volume as reported by mixer + U32 mMaxVolume; // value that specifies maximum volume as reported by mixer }; +// factory function that attempts to create a Mixer object associated with a given mixer line index +// returns NULL if creation failed +// static Mixer* Mixer::create(U32 index) { + // get handle to mixer object HMIXER mixer_handle; - MMRESULT result = mixerOpen( &mixer_handle, - index, - 0, // HWND to call when state changes - not used - 0, // user data for callback - not used - MIXER_OBJECTF_MIXER ); + MMRESULT result = mixerOpen( &mixer_handle, + index, + 0, // HWND to call when state changes - not used + 0, // user data for callback - not used + MIXER_OBJECTF_MIXER ); if (result == MMSYSERR_NOERROR) { - MIXERLINE mixer_line; - mixer_line.cbStruct = sizeof( MIXERLINE ); - // try speakers first + MIXERLINE mixer_line; + mixer_line.cbStruct = sizeof( MIXERLINE ); + + // try speakers first mixer_line.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_SPEAKERS; - MMRESULT result = mixerGetLineInfo( reinterpret_cast< HMIXEROBJ >( mixer_handle ), - &mixer_line, - MIXER_OBJECTF_HMIXER | MIXER_GETLINEINFOF_COMPONENTTYPE ); - if (result != MMSYSERR_NOERROR) - { - // failed - try headphones next - mixer_line.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_HEADPHONES; - result = mixerGetLineInfo( reinterpret_cast< HMIXEROBJ >( mixer_handle ), - &mixer_line, - MIXER_OBJECTF_HMIXER | MIXER_GETLINEINFOF_COMPONENTTYPE ); - } + MMRESULT result = mixerGetLineInfo( reinterpret_cast< HMIXEROBJ >( mixer_handle ), + &mixer_line, + MIXER_OBJECTF_HMIXER | MIXER_GETLINEINFOF_COMPONENTTYPE ); + if (result != MMSYSERR_NOERROR) + { // failed - try headphones next + mixer_line.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_HEADPHONES; + result = mixerGetLineInfo( reinterpret_cast< HMIXEROBJ >( mixer_handle ), + &mixer_line, + MIXER_OBJECTF_HMIXER | MIXER_GETLINEINFOF_COMPONENTTYPE ); + } if (result == MMSYSERR_NOERROR) - { - // get control id - MIXERCONTROL mixer_control; - MIXERLINECONTROLS mixer_line_controls; - mixer_line_controls.cbStruct = sizeof( MIXERLINECONTROLS ); - mixer_line_controls.dwLineID = mixer_line.dwLineID; - mixer_line_controls.dwControlType = MIXERCONTROL_CONTROLTYPE_MUTE; - mixer_line_controls.cControls = 1; - mixer_line_controls.cbmxctrl = sizeof( MIXERCONTROL ); - mixer_line_controls.pamxctrl = &mixer_control; - - result = mixerGetLineControls( reinterpret_cast< HMIXEROBJ >( mixer_handle ), - &mixer_line_controls, - MIXER_OBJECTF_HMIXER | MIXER_GETLINECONTROLSF_ONEBYTYPE ); - if (result == MMSYSERR_NOERROR ) - { - // We have a mute mixer. Remember the mute control id - U32 mute_control_id = mixer_control.dwControlID; - - // now query for volume controls - mixer_line_controls.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME; - result = mixerGetLineControls( reinterpret_cast< HMIXEROBJ >( mixer_handle ), - &mixer_line_controls, - MIXER_OBJECTF_HMIXER | MIXER_GETLINECONTROLSF_ONEBYTYPE ); - - if (result == MMSYSERR_NOERROR) - { - // we have both mute and volume controls for this mixer, so remember it - return new Mixer(mixer_handle, - mute_control_id, - mixer_control.dwControlID, - mixer_control.Bounds.dwMinimum, - mixer_control.Bounds.dwMaximum); - } - } - } + { // successfully found mixer line object, now use it to get volume and mute controls + + // reuse these objects to query for both volume and mute controls + MIXERCONTROL mixer_control; + MIXERLINECONTROLS mixer_line_controls; + mixer_line_controls.cbStruct = sizeof( MIXERLINECONTROLS ); + mixer_line_controls.dwLineID = mixer_line.dwLineID; + mixer_line_controls.cControls = 1; + mixer_line_controls.cbmxctrl = sizeof( MIXERCONTROL ); + mixer_line_controls.pamxctrl = &mixer_control; + + // first, query for mute + mixer_line_controls.dwControlType = MIXERCONTROL_CONTROLTYPE_MUTE; + + // get control id for mute controls + result = mixerGetLineControls( reinterpret_cast< HMIXEROBJ >( mixer_handle ), + &mixer_line_controls, + MIXER_OBJECTF_HMIXER | MIXER_GETLINECONTROLSF_ONEBYTYPE ); + if (result == MMSYSERR_NOERROR ) + { // we have a mute controls. Remember the mute control id and then query for + // volume controls using the same struct, but different dwControlType + + U32 mute_control_id = mixer_control.dwControlID; + mixer_line_controls.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME; + result = mixerGetLineControls( reinterpret_cast< HMIXEROBJ >( mixer_handle ), + &mixer_line_controls, + MIXER_OBJECTF_HMIXER | MIXER_GETLINECONTROLSF_ONEBYTYPE ); + + if (result == MMSYSERR_NOERROR) + { // we have both mute and volume controls for this mixer, so we're keeping it + return new Mixer(mixer_handle, + mute_control_id, + mixer_control.dwControlID, + mixer_control.Bounds.dwMinimum, + mixer_control.Bounds.dwMaximum); + } + } + } } + + // if we got here, we didn't successfully create a Mixer object + mixerClose(mixer_handle); return NULL; } @@ -131,47 +145,55 @@ Mixer::Mixer(HMIXER handle, U32 mute_control_id, U32 volume_control_id, U32 min_ {} Mixer::~Mixer() -{ -} +{} +// toggle mute for this mixer +// if mute is set, then volume level will be ignored void Mixer::setMute(bool mute) { - MIXERCONTROLDETAILS_BOOLEAN mixer_control_details_bool = { mute }; - MIXERCONTROLDETAILS mixer_control_details; - mixer_control_details.cbStruct = sizeof( MIXERCONTROLDETAILS ); - mixer_control_details.dwControlID = mMuteControlID; - mixer_control_details.cChannels = 1; - mixer_control_details.cMultipleItems = 0; - mixer_control_details.cbDetails = sizeof( MIXERCONTROLDETAILS_BOOLEAN ); - mixer_control_details.paDetails = &mixer_control_details_bool; - - mixerSetControlDetails( reinterpret_cast< HMIXEROBJ >( mHandle ), - &mixer_control_details, - MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE ); + MIXERCONTROLDETAILS_BOOLEAN mixer_control_details_bool = { mute }; + MIXERCONTROLDETAILS mixer_control_details; + mixer_control_details.cbStruct = sizeof( MIXERCONTROLDETAILS ); + mixer_control_details.dwControlID = mMuteControlID; + mixer_control_details.cChannels = 1; + mixer_control_details.cMultipleItems = 0; + mixer_control_details.cbDetails = sizeof( MIXERCONTROLDETAILS_BOOLEAN ); + mixer_control_details.paDetails = &mixer_control_details_bool; + + mixerSetControlDetails( reinterpret_cast< HMIXEROBJ >( mHandle ), + &mixer_control_details, + MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE ); } +// set individual volume levels for left and right channels +// if mute is set, then these values will apply once mute is unset void Mixer::setVolume(F32 volume_left, F32 volume_right) { + // assuming pan is in range [-1, 1] set volume levels accordingly + // if pan == -1 then volume_left_mixer = volume_left && volume_right_mixer = 0 + // if pan == 0 then volume_left_mixer = volume_left && volume_right_mixer = volume_right + // if pan == 1 then volume_left_mixer = 0 && volume_right_mixer = volume_right U32 volume_left_mixer = (U32) ((F32)mMinVolume + (volume_left * ((F32)mMaxVolume - (F32)mMinVolume))); U32 volume_right_mixer = (U32) ((F32)mMinVolume + (volume_right * ((F32)mMaxVolume - (F32)mMinVolume))); - MIXERCONTROLDETAILS_UNSIGNED mixer_control_details_unsigned[ 2 ] = { volume_left_mixer, volume_right_mixer }; - MIXERCONTROLDETAILS mixer_control_details; - mixer_control_details.cbStruct = sizeof( MIXERCONTROLDETAILS ); - mixer_control_details.dwControlID = mVolumeControlID; - mixer_control_details.cChannels = 2; - mixer_control_details.cMultipleItems = 0; - mixer_control_details.cbDetails = sizeof( MIXERCONTROLDETAILS_UNSIGNED ); - mixer_control_details.paDetails = &mixer_control_details_unsigned; - - mixerSetControlDetails( reinterpret_cast< HMIXEROBJ >( mHandle ), - &mixer_control_details, - MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE ); -} + // pass volume levels on to mixer + MIXERCONTROLDETAILS_UNSIGNED mixer_control_details_unsigned[ 2 ] = { volume_left_mixer, volume_right_mixer }; + MIXERCONTROLDETAILS mixer_control_details; + mixer_control_details.cbStruct = sizeof( MIXERCONTROLDETAILS ); + mixer_control_details.dwControlID = mVolumeControlID; + mixer_control_details.cChannels = 2; + mixer_control_details.cMultipleItems = 0; + mixer_control_details.cbDetails = sizeof( MIXERCONTROLDETAILS_UNSIGNED ); + mixer_control_details.paDetails = &mixer_control_details_unsigned; + + mixerSetControlDetails( reinterpret_cast< HMIXEROBJ >( mHandle ), + &mixer_control_details, + MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE ); +} class VolumeCatcherImpl { @@ -207,9 +229,10 @@ VolumeCatcherImpl *VolumeCatcherImpl::getInstance() } VolumeCatcherImpl::VolumeCatcherImpl() -: mVolume(1.0f), - mPan(0.f) // 0 is centered +: mVolume(1.0f), // default volume is max + mPan(0.f) // default pan is centered { + // for each reported mixer "device", create a proxy object and add to list U32 num_mixers = mixerGetNumDevs(); for (U32 mixer_index = 0; mixer_index < num_mixers; ++mixer_index) { @@ -241,7 +264,9 @@ void VolumeCatcherImpl::setVolume(F32 volume) for(mixer_vector_t::iterator it = mMixers.begin(), end_it = mMixers.end(); it != end_it; ++it) - { + { // set volume levels and mute for each mixer + // note that a muted mixer will ignore this volume level + (*it)->setVolume(left_volume, right_volume); if (volume == 0.f && mVolume != 0.f) @@ -258,7 +283,7 @@ void VolumeCatcherImpl::setVolume(F32 volume) } void VolumeCatcherImpl::setPan(F32 pan) -{ +{ // remember pan for calculating individual channel levels later mPan = pan; } -- cgit v1.3 From 8cb7af4f73c0a703b5842a708e94d80aea26b6db Mon Sep 17 00:00:00 2001 From: Richard Linden Date: Fri, 16 Apr 2010 14:48:05 -0700 Subject: removed tabs from cmakelist.txt --- indra/media_plugins/webkit/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'indra/media_plugins') diff --git a/indra/media_plugins/webkit/CMakeLists.txt b/indra/media_plugins/webkit/CMakeLists.txt index c33a26b637..1a559ed39c 100644 --- a/indra/media_plugins/webkit/CMakeLists.txt +++ b/indra/media_plugins/webkit/CMakeLists.txt @@ -37,9 +37,9 @@ set(media_plugin_webkit_SOURCE_FILES ) set(media_plugin_webkit_HEADER_FILES - volume_catcher.h - ) - + volume_catcher.h + ) + set(media_plugin_webkit_LINK_LIBRARIES ${LLPLUGIN_LIBRARIES} ${MEDIA_PLUGIN_BASE_LIBRARIES} -- cgit v1.3 From 3afda3d1c020647604748ef5001526ca3fcf0759 Mon Sep 17 00:00:00 2001 From: Richard Nelson Date: Mon, 19 Apr 2010 17:01:47 -0700 Subject: EXT-6921 - Media Volume Changes changes system volume on WinXP reviewed by Callum --- .../media_plugins/webkit/windows_volume_catcher.cpp | 21 +++++++++++++++------ indra/newview/llpanelnearbymedia.cpp | 5 ----- indra/newview/llpanelprimmediacontrols.cpp | 19 +++++-------------- indra/newview/llviewermedia.h | 6 ------ 4 files changed, 20 insertions(+), 31 deletions(-) (limited to 'indra/media_plugins') diff --git a/indra/media_plugins/webkit/windows_volume_catcher.cpp b/indra/media_plugins/webkit/windows_volume_catcher.cpp index 1c1ef0b42f..8debe8fac6 100644 --- a/indra/media_plugins/webkit/windows_volume_catcher.cpp +++ b/indra/media_plugins/webkit/windows_volume_catcher.cpp @@ -232,14 +232,22 @@ VolumeCatcherImpl::VolumeCatcherImpl() : mVolume(1.0f), // default volume is max mPan(0.f) // default pan is centered { - // for each reported mixer "device", create a proxy object and add to list - U32 num_mixers = mixerGetNumDevs(); - for (U32 mixer_index = 0; mixer_index < num_mixers; ++mixer_index) + OSVERSIONINFOEX V = {sizeof(OSVERSIONINFOEX)}; //EX for NT 5.0 and later + + ::GetVersionEx((POSVERSIONINFO)&V); + + // disable volume on XP and below + if (V.dwPlatformId == VER_PLATFORM_WIN32_NT && V.dwMajorVersion >= 6) { - Mixer* mixerp = Mixer::create(mixer_index); - if (mixerp) + // for each reported mixer "device", create a proxy object and add to list + U32 num_mixers = mixerGetNumDevs(); + for (U32 mixer_index = 0; mixer_index < num_mixers; ++mixer_index) { - mMixers.push_back(mixerp); + Mixer* mixerp = Mixer::create(mixer_index); + if (mixerp) + { + mMixers.push_back(mixerp); + } } } } @@ -279,6 +287,7 @@ void VolumeCatcherImpl::setVolume(F32 volume) } } + mVolume = volume; } diff --git a/indra/newview/llpanelnearbymedia.cpp b/indra/newview/llpanelnearbymedia.cpp index 79786c06d9..6411cd802d 100644 --- a/indra/newview/llpanelnearbymedia.cpp +++ b/indra/newview/llpanelnearbymedia.cpp @@ -1062,15 +1062,10 @@ void LLPanelNearByMedia::showBasicControls(bool playing, bool include_zoom, bool mStopCtrl->setVisible(playing); mPlayCtrl->setVisible(!playing); mPauseCtrl->setVisible(false); -#ifdef PER_MEDIA_VOLUME mVolumeSliderCtrl->setVisible(true); mMuteCtrl->setVisible(true); mMuteBtn->setValue(muted); mVolumeSlider->setValue(volume); -#else - mVolumeSliderCtrl->setVisible(false); - mMuteCtrl->setVisible(false); -#endif mZoomCtrl->setVisible(include_zoom && !is_zoomed); mUnzoomCtrl->setVisible(include_zoom && is_zoomed); mStopCtrl->setEnabled(true); diff --git a/indra/newview/llpanelprimmediacontrols.cpp b/indra/newview/llpanelprimmediacontrols.cpp index 5209d50755..0648d99685 100644 --- a/indra/newview/llpanelprimmediacontrols.cpp +++ b/indra/newview/llpanelprimmediacontrols.cpp @@ -350,6 +350,11 @@ void LLPanelPrimMediaControls::updateShape() mHomeCtrl->setEnabled(has_focus && can_navigate); LLPluginClassMediaOwner::EMediaStatus result = ((media_impl != NULL) && media_impl->hasMedia()) ? media_plugin->getStatus() : LLPluginClassMediaOwner::MEDIA_NONE; + mVolumeCtrl->setVisible(has_focus); + mVolumeCtrl->setEnabled(has_focus); + mVolumeSliderCtrl->setEnabled(has_focus && shouldVolumeSliderBeVisible()); + mVolumeSliderCtrl->setVisible(has_focus && shouldVolumeSliderBeVisible()); + if(media_plugin && media_plugin->pluginSupportsMediaTime()) { mReloadCtrl->setEnabled(false); @@ -462,14 +467,6 @@ void LLPanelPrimMediaControls::updateShape() mSkipBackCtrl->setVisible(FALSE); mSkipBackCtrl->setEnabled(FALSE); -#ifdef PER_MEDIA_VOLUME - // these should be pulled up above the pluginSupportsMediaTime - // if check once we always have PER_MEDIA_VOLUME turned on - mVolumeCtrl->setVisible(has_focus); - mVolumeCtrl->setEnabled(has_focus); - mVolumeSliderCtrl->setEnabled(has_focus && shouldVolumeSliderBeVisible()); - mVolumeSliderCtrl->setVisible(has_focus && shouldVolumeSliderBeVisible()); - if(media_impl->getVolume() <= 0.0) { mMuteBtn->setToggleState(true); @@ -478,12 +475,6 @@ void LLPanelPrimMediaControls::updateShape() { mMuteBtn->setToggleState(false); } -#else - mVolumeCtrl->setVisible(FALSE); - mVolumeSliderCtrl->setVisible(FALSE); - mVolumeCtrl->setEnabled(FALSE); - mVolumeSliderCtrl->setEnabled(FALSE); -#endif if (mMediaPanelScroll) { diff --git a/indra/newview/llviewermedia.h b/indra/newview/llviewermedia.h index bc6716697e..e829d7a5b4 100644 --- a/indra/newview/llviewermedia.h +++ b/indra/newview/llviewermedia.h @@ -46,12 +46,6 @@ #include "llurl.h" - -#if defined(LL_DARWIN) || (LL_WINDOWS && !LL_RELEASE_FOR_DOWNLOAD ) -#define PER_MEDIA_VOLUME -#endif - - class LLViewerMediaImpl; class LLUUID; class LLViewerMediaTexture; -- cgit v1.3