diff options
Diffstat (limited to 'indra/newview/llviewermedia.cpp')
-rw-r--r-- | indra/newview/llviewermedia.cpp | 3732 |
1 files changed, 3278 insertions, 454 deletions
diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp index 1b3fd5d49b..48ab122edf 100644 --- a/indra/newview/llviewermedia.cpp +++ b/indra/newview/llviewermedia.cpp @@ -2,705 +2,3529 @@ * @file llviewermedia.cpp * @brief Client interface to the media engine * - * $LicenseInfo:firstyear=2007&license=viewergpl$ - * - * Copyright (c) 2007-2009, Linden Research, Inc. - * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ * 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://secondlifegrid.net/programs/open_source/licensing/gplv2 + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. * - * 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://secondlifegrid.net/programs/open_source/licensing/flossexception + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. * - * 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. + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * - * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO - * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, - * COMPLETENESS OR PERFORMANCE. + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "llviewerprecompiledheaders.h" +#include "llagent.h" +#include "llagentcamera.h" #include "llviewermedia.h" - +#include "llviewermediafocus.h" #include "llmimetypes.h" +#include "llmediaentry.h" +#include "llversioninfo.h" #include "llviewercontrol.h" -#include "llviewerimage.h" +#include "llviewertexture.h" +#include "llviewerparcelmedia.h" +#include "llviewerparcelmgr.h" +#include "llviewertexturelist.h" +#include "llvovolume.h" +#include "llpluginclassmedia.h" +#include "llplugincookiestore.h" #include "llviewerwindow.h" -#include "llversionviewer.h" -#include "llviewerimagelist.h" +#include "llfocusmgr.h" +#include "llcallbacklist.h" +#include "llparcel.h" +#include "llaudioengine.h" // for gAudiop +#include "llurldispatcher.h" +#include "llvoavatar.h" +#include "llvoavatarself.h" +#include "llviewerregion.h" +#include "llwebsharing.h" // For LLWebSharing::setOpenIDCookie(), *TODO: find a better way to do this! +#include "llfilepicker.h" #include "llevent.h" // LLSimpleListener -#include "llmediamanager.h" +#include "llnotificationsutil.h" #include "lluuid.h" +#include "llkeyboard.h" +#include "llmutelist.h" +//#include "llfirstuse.h" +#include "llwindow.h" + +#include "llfloatermediabrowser.h" // for handling window close requests and geometry change requests in media browser windows. #include <boost/bind.hpp> // for SkinFolder listener #include <boost/signals2.hpp> +/*static*/ const char* LLViewerMedia::AUTO_PLAY_MEDIA_SETTING = "ParcelMediaAutoPlayEnable"; +/*static*/ const char* LLViewerMedia::SHOW_MEDIA_ON_OTHERS_SETTING = "MediaShowOnOthers"; +/*static*/ const char* LLViewerMedia::SHOW_MEDIA_WITHIN_PARCEL_SETTING = "MediaShowWithinParcel"; +/*static*/ const char* LLViewerMedia::SHOW_MEDIA_OUTSIDE_PARCEL_SETTING = "MediaShowOutsideParcel"; + + +// Move this to its own file. + +LLViewerMediaEventEmitter::~LLViewerMediaEventEmitter() +{ + observerListType::iterator iter = mObservers.begin(); + + while( iter != mObservers.end() ) + { + LLViewerMediaObserver *self = *iter; + iter++; + remObserver(self); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// +bool LLViewerMediaEventEmitter::addObserver( LLViewerMediaObserver* observer ) +{ + if ( ! observer ) + return false; + + if ( std::find( mObservers.begin(), mObservers.end(), observer ) != mObservers.end() ) + return false; + + mObservers.push_back( observer ); + observer->mEmitters.push_back( this ); + + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// +bool LLViewerMediaEventEmitter::remObserver( LLViewerMediaObserver* observer ) +{ + if ( ! observer ) + return false; + + mObservers.remove( observer ); + observer->mEmitters.remove(this); + + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// +void LLViewerMediaEventEmitter::emitEvent( LLPluginClassMedia* media, LLViewerMediaObserver::EMediaEvent event ) +{ + // Broadcast the event to any observers. + observerListType::iterator iter = mObservers.begin(); + while( iter != mObservers.end() ) + { + LLViewerMediaObserver *self = *iter; + ++iter; + self->handleMediaEvent( media, event ); + } +} + +// Move this to its own file. +LLViewerMediaObserver::~LLViewerMediaObserver() +{ + std::list<LLViewerMediaEventEmitter *>::iterator iter = mEmitters.begin(); + + while( iter != mEmitters.end() ) + { + LLViewerMediaEventEmitter *self = *iter; + iter++; + self->remObserver( this ); + } +} + + +// Move this to its own file. +// helper class that tries to download a URL from a web site and calls a method +// on the Panel Land Media and to discover the MIME type +class LLMimeDiscoveryResponder : public LLHTTPClient::Responder +{ +LOG_CLASS(LLMimeDiscoveryResponder); +public: + LLMimeDiscoveryResponder( viewer_media_t media_impl) + : mMediaImpl(media_impl), + mInitialized(false) + { + if(mMediaImpl->mMimeTypeProbe != NULL) + { + llerrs << "impl already has an outstanding responder" << llendl; + } + + mMediaImpl->mMimeTypeProbe = this; + } + + ~LLMimeDiscoveryResponder() + { + disconnectOwner(); + } + + virtual void completedHeader(U32 status, const std::string& reason, const LLSD& content) + { + std::string media_type = content["content-type"].asString(); + std::string::size_type idx1 = media_type.find_first_of(";"); + std::string mime_type = media_type.substr(0, idx1); + + lldebugs << "status is " << status << ", media type \"" << media_type << "\"" << llendl; + + // 2xx status codes indicate success. + // Most 4xx status codes are successful enough for our purposes. + // 499 is the error code for host not found, timeout, etc. + // 500 means "Internal Server error" but we decided it's okay to + // accept this and go past it in the MIME type probe + // 302 means the resource can be found temporarily in a different place - added this for join.secondlife.com + // 499 is a code specifc to join.secondlife.com (????) apparently safe to ignore +// if( ((status >= 200) && (status < 300)) || +// ((status >= 400) && (status < 499)) || +// (status == 500) || +// (status == 302) || +// (status == 499) +// ) + // We now no longer check the error code returned from the probe. + // If we have a mime type, use it. If not, default to the web plugin and let it handle error reporting. + if(1) + { + // The probe was successful. + if(mime_type.empty()) + { + // Some sites don't return any content-type header at all. + // Treat an empty mime type as text/html. + mime_type = "text/html"; + } + + completeAny(status, mime_type); + } + else + { + llwarns << "responder failed with status " << status << ", reason " << reason << llendl; + + if(mMediaImpl) + { + mMediaImpl->mMediaSourceFailed = true; + } + } + + } + + void completeAny(U32 status, const std::string& mime_type) + { + // the call to initializeMedia may disconnect the responder, which will clear mMediaImpl. + // Make a local copy so we can call loadURI() afterwards. + LLViewerMediaImpl *impl = mMediaImpl; + + if(impl && !mInitialized && ! mime_type.empty()) + { + if(impl->initializeMedia(mime_type)) + { + mInitialized = true; + impl->loadURI(); + disconnectOwner(); + } + } + } + + void cancelRequest() + { + disconnectOwner(); + } + +private: + void disconnectOwner() + { + if(mMediaImpl) + { + if(mMediaImpl->mMimeTypeProbe != this) + { + llerrs << "internal error: mMediaImpl->mMimeTypeProbe != this" << llendl; + } + + mMediaImpl->mMimeTypeProbe = NULL; + } + mMediaImpl = NULL; + } + + +public: + LLViewerMediaImpl *mMediaImpl; + bool mInitialized; +}; + +class LLViewerMediaOpenIDResponder : public LLHTTPClient::Responder +{ +LOG_CLASS(LLViewerMediaOpenIDResponder); +public: + LLViewerMediaOpenIDResponder( ) + { + } + + ~LLViewerMediaOpenIDResponder() + { + } + + /* virtual */ void completedHeader(U32 status, const std::string& reason, const LLSD& content) + { + LL_DEBUGS("MediaAuth") << "status = " << status << ", reason = " << reason << LL_ENDL; + LL_DEBUGS("MediaAuth") << content << LL_ENDL; + std::string cookie = content["set-cookie"].asString(); + + LLViewerMedia::openIDCookieResponse(cookie); + } + + /* virtual */ void completedRaw( + U32 status, + const std::string& reason, + const LLChannelDescriptors& channels, + const LLIOPipe::buffer_ptr_t& buffer) + { + // This is just here to disable the default behavior (attempting to parse the response as llsd). + // We don't care about the content of the response, only the set-cookie header. + } -// Implementation functions not exported into header file -class LLViewerMediaImpl - : public LLMediaObserver -{ - public: - LLViewerMediaImpl() - : mMediaSource( NULL ), - mMovieImageID(), - mMovieImageHasMips(false) - { } - - void destroyMediaSource(); - - void play(const std::string& media_url, - const std::string& mime_type, - const LLUUID& placeholder_texture_id, - S32 media_width, S32 media_height, U8 media_auto_scale, - U8 media_loop); - - void stop(); - void pause(); - void start(); - void seek(F32 time); - void setVolume(F32 volume); - LLMediaBase::EStatus getStatus(); - - /*virtual*/ void onMediaSizeChange(const EventType& event_in); - /*virtual*/ void onMediaContentsChange(const EventType& event_in); - - void updateMovieImage(const LLUUID& image_id, BOOL active); - void updateImagesMediaStreams(); - LLUUID getMediaTextureID(); - - // Internally set our desired browser user agent string, including - // the Second Life version and skin name. Used because we can - // switch skins without restarting the app. - static void updateBrowserUserAgent(); - - // Callback for when the SkinCurrent control is changed to - // switch the user agent string to indicate the new skin. - static bool handleSkinCurrentChanged(const LLSD& newvalue); - - public: - - // a single media url with some data and an impl. - LLMediaBase* mMediaSource; - LLUUID mMovieImageID; - bool mMovieImageHasMips; - std::string mMediaURL; - std::string mMimeType; - private: - void initializePlaceholderImage(LLViewerImage *placeholder_image, LLMediaBase *media_source); }; -static LLViewerMediaImpl sViewerMediaImpl; +LLPluginCookieStore *LLViewerMedia::sCookieStore = NULL; +LLURL LLViewerMedia::sOpenIDURL; +std::string LLViewerMedia::sOpenIDCookie; +static LLViewerMedia::impl_list sViewerMediaImplList; +static LLViewerMedia::impl_id_map sViewerMediaTextureIDMap; +static LLTimer sMediaCreateTimer; +static const F32 LLVIEWERMEDIA_CREATE_DELAY = 1.0f; +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) +{ + sViewerMediaImplList.push_back(media); +} -void LLViewerMediaImpl::destroyMediaSource() +////////////////////////////////////////////////////////////////////////////////////////// +static void remove_media_impl(LLViewerMediaImpl* media) { - LLMediaManager* mgr = LLMediaManager::getInstance(); - if ( mMediaSource ) + LLViewerMedia::impl_list::iterator iter = sViewerMediaImplList.begin(); + LLViewerMedia::impl_list::iterator end = sViewerMediaImplList.end(); + + for(; iter != end; iter++) { - bool was_playing = LLViewerMedia::isMediaPlaying(); - mMediaSource->remObserver(this); - mgr->destroySource( mMediaSource ); + if(media == *iter) + { + sViewerMediaImplList.erase(iter); + return; + } + } +} + +class LLViewerMediaMuteListObserver : public LLMuteListObserver +{ + /* virtual */ void onChange() { LLViewerMedia::muteListChanged();} +}; + +static LLViewerMediaMuteListObserver sViewerMediaMuteListObserver; +static bool sViewerMediaMuteListObserverInitialized = false; +static bool sInWorldMediaDisabled = false; - // Restore the texture - updateMovieImage(LLUUID::null, was_playing); +////////////////////////////////////////////////////////////////////////////////////////// +// LLViewerMedia + +////////////////////////////////////////////////////////////////////////////////////////// +// static +viewer_media_t LLViewerMedia::newMediaImpl( + const LLUUID& texture_id, + S32 media_width, + S32 media_height, + U8 media_auto_scale, + U8 media_loop) +{ + LLViewerMediaImpl* media_impl = getMediaImplFromTextureID(texture_id); + if(media_impl == NULL || texture_id.isNull()) + { + // Create the media impl + media_impl = new LLViewerMediaImpl(texture_id, media_width, media_height, media_auto_scale, media_loop); + } + else + { + media_impl->unload(); + media_impl->setTextureID(texture_id); + media_impl->mMediaWidth = media_width; + media_impl->mMediaHeight = media_height; + media_impl->mMediaAutoScale = media_auto_scale; + media_impl->mMediaLoop = media_loop; } - mMediaSource = NULL; + + return media_impl; } -void LLViewerMediaImpl::play(const std::string& media_url, - const std::string& mime_type, - const LLUUID& placeholder_texture_id, - S32 media_width, S32 media_height, U8 media_auto_scale, - U8 media_loop) -{ - // first stop any previously playing media - stop(); +viewer_media_t LLViewerMedia::updateMediaImpl(LLMediaEntry* media_entry, const std::string& previous_url, bool update_from_self) +{ + // Try to find media with the same media ID + viewer_media_t media_impl = getMediaImplFromTextureID(media_entry->getMediaID()); + + lldebugs << "called, current URL is \"" << media_entry->getCurrentURL() + << "\", previous URL is \"" << previous_url + << "\", update_from_self is " << (update_from_self?"true":"false") + << llendl; + + bool was_loaded = false; + bool needs_navigate = false; + + if(media_impl) + { + was_loaded = media_impl->hasMedia(); + + media_impl->setHomeURL(media_entry->getHomeURL()); + + media_impl->mMediaAutoScale = media_entry->getAutoScale(); + media_impl->mMediaLoop = media_entry->getAutoLoop(); + media_impl->mMediaWidth = media_entry->getWidthPixels(); + media_impl->mMediaHeight = media_entry->getHeightPixels(); + media_impl->mMediaAutoPlay = media_entry->getAutoPlay(); + media_impl->mMediaEntryURL = media_entry->getCurrentURL(); + if (media_impl->mMediaSource) + { + media_impl->mMediaSource->setAutoScale(media_impl->mMediaAutoScale); + media_impl->mMediaSource->setLoop(media_impl->mMediaLoop); + media_impl->mMediaSource->setSize(media_entry->getWidthPixels(), media_entry->getHeightPixels()); + } + + bool url_changed = (media_impl->mMediaEntryURL != previous_url); + if(media_impl->mMediaEntryURL.empty()) + { + if(url_changed) + { + // The current media URL is now empty. Unload the media source. + media_impl->unload(); + + lldebugs << "Unloading media instance (new current URL is empty)." << llendl; + } + } + else + { + // The current media URL is not empty. + // If (the media was already loaded OR the media was set to autoplay) AND this update didn't come from this agent, + // do a navigate. + bool auto_play = media_impl->isAutoPlayable(); + if((was_loaded || auto_play) && !update_from_self) + { + needs_navigate = url_changed; + } + + lldebugs << "was_loaded is " << (was_loaded?"true":"false") + << ", auto_play is " << (auto_play?"true":"false") + << ", needs_navigate is " << (needs_navigate?"true":"false") << llendl; + } + } + else + { + media_impl = newMediaImpl( + media_entry->getMediaID(), + media_entry->getWidthPixels(), + media_entry->getHeightPixels(), + media_entry->getAutoScale(), + media_entry->getAutoLoop()); + + 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; + } + } + + if(media_impl) + { + if(needs_navigate) + { + media_impl->navigateTo(media_impl->mMediaEntryURL, "", true, true); + lldebugs << "navigating to URL " << media_impl->mMediaEntryURL << llendl; + } + else if(!media_impl->mMediaURL.empty() && (media_impl->mMediaURL != media_impl->mMediaEntryURL)) + { + // If we already have a non-empty media URL set and we aren't doing a navigate, update the media URL to match the media entry. + media_impl->mMediaURL = media_impl->mMediaEntryURL; - // Save this first, as init/load below may fire events - mMovieImageID = placeholder_texture_id; + // If this causes a navigate at some point (such as after a reload), it should be considered server-driven so it isn't broadcast. + media_impl->mNavigateServerRequest = true; - // If the mime_type passed in is different than the cached one, and - // Auto-discovery is turned OFF, replace the cached mime_type with the new one. - if(mime_type != mMimeType && - ! gSavedSettings.getBOOL("AutoMimeDiscovery")) + lldebugs << "updating URL in the media impl to " << media_impl->mMediaEntryURL << llendl; + } + } + + return media_impl; +} + +////////////////////////////////////////////////////////////////////////////////////////// +// static +LLViewerMediaImpl* LLViewerMedia::getMediaImplFromTextureID(const LLUUID& texture_id) +{ + LLViewerMediaImpl* result = NULL; + + // Look up the texture ID in the texture id->impl map. + impl_id_map::iterator iter = sViewerMediaTextureIDMap.find(texture_id); + if(iter != sViewerMediaTextureIDMap.end()) { - mMimeType = mime_type; + result = iter->second; } - LLURI url(media_url); - std::string scheme = url.scheme() != "" ? url.scheme() : "http"; - LLMediaManager* mgr = LLMediaManager::getInstance(); - mMediaSource = mgr->createSourceFromMimeType(scheme, mMimeType ); - if ( !mMediaSource ) + return result; +} + +////////////////////////////////////////////////////////////////////////////////////////// +// static +std::string LLViewerMedia::getCurrentUserAgent() +{ + // Don't use user-visible string to avoid + // punctuation and strange characters. + std::string skin_name = gSavedSettings.getString("SkinCurrent"); + + // Just in case we need to check browser differences in A/B test + // builds. + std::string channel = gSavedSettings.getString("VersionChannelName"); + + // append our magic version number string to the browser user agent id + // See the HTTP 1.0 and 1.1 specifications for allowed formats: + // http://www.ietf.org/rfc/rfc1945.txt section 10.15 + // http://www.ietf.org/rfc/rfc2068.txt section 3.8 + // This was also helpful: + // http://www.mozilla.org/build/revised-user-agent-strings.html + std::ostringstream codec; + codec << "SecondLife/"; + codec << LLVersionInfo::getVersion(); + codec << " (" << channel << "; " << skin_name << " skin)"; + llinfos << codec.str() << llendl; + + return codec.str(); +} + +////////////////////////////////////////////////////////////////////////////////////////// +// static +void LLViewerMedia::updateBrowserUserAgent() +{ + std::string user_agent = getCurrentUserAgent(); + + impl_list::iterator iter = sViewerMediaImplList.begin(); + impl_list::iterator end = sViewerMediaImplList.end(); + + for(; iter != end; iter++) { - if (mMimeType != "none/none") + LLViewerMediaImpl* pimpl = *iter; + if(pimpl->mMediaSource && pimpl->mMediaSource->pluginSupportsMediaBrowser()) { - llwarns << "media source create failed " << media_url - << " type " << mMimeType - << llendl; + pimpl->mMediaSource->setBrowserUserAgent(user_agent); } - return; } - // Store the URL and Mime Type - mMediaURL = media_url; +} - if ((media_width != 0) && (media_height != 0)) +////////////////////////////////////////////////////////////////////////////////////////// +// static +bool LLViewerMedia::handleSkinCurrentChanged(const LLSD& /*newvalue*/) +{ + // gSavedSettings is already updated when this function is called. + updateBrowserUserAgent(); + return true; +} + +////////////////////////////////////////////////////////////////////////////////////////// +// static +bool LLViewerMedia::textureHasMedia(const LLUUID& texture_id) +{ + impl_list::iterator iter = sViewerMediaImplList.begin(); + impl_list::iterator end = sViewerMediaImplList.end(); + + for(; iter != end; iter++) { - mMediaSource->setRequestedMediaSize(media_width, media_height); + LLViewerMediaImpl* pimpl = *iter; + if(pimpl->getMediaTextureID() == texture_id) + { + return true; + } } + return false; +} - mMediaSource->setLooping(media_loop); - mMediaSource->setAutoScaled(media_auto_scale); - mMediaSource->addObserver( this ); - mMediaSource->navigateTo( media_url ); - mMediaSource->addCommand(LLMediaBase::COMMAND_START); +////////////////////////////////////////////////////////////////////////////////////////// +// static +void LLViewerMedia::setVolume(F32 volume) +{ + if(volume != sGlobalVolume) + { + sGlobalVolume = volume; + impl_list::iterator iter = sViewerMediaImplList.begin(); + impl_list::iterator end = sViewerMediaImplList.end(); + + for(; iter != end; iter++) + { + LLViewerMediaImpl* pimpl = *iter; + pimpl->updateVolume(); + } + } } -void LLViewerMediaImpl::stop() +////////////////////////////////////////////////////////////////////////////////////////// +// static +F32 LLViewerMedia::getVolume() { - destroyMediaSource(); + return sGlobalVolume; } -void LLViewerMediaImpl::pause() +////////////////////////////////////////////////////////////////////////////////////////// +// static +void LLViewerMedia::muteListChanged() { - if(mMediaSource) + // When the mute list changes, we need to check mute status on all impls. + impl_list::iterator iter = sViewerMediaImplList.begin(); + impl_list::iterator end = sViewerMediaImplList.end(); + + for(; iter != end; iter++) { - mMediaSource->addCommand(LLMediaBase::COMMAND_PAUSE); + LLViewerMediaImpl* pimpl = *iter; + pimpl->mNeedsMuteCheck = true; } } -void LLViewerMediaImpl::start() +////////////////////////////////////////////////////////////////////////////////////////// +// static +void LLViewerMedia::setInWorldMediaDisabled(bool disabled) { - if(mMediaSource) + sInWorldMediaDisabled = disabled; +} + +////////////////////////////////////////////////////////////////////////////////////////// +// static +bool LLViewerMedia::getInWorldMediaDisabled() +{ + return sInWorldMediaDisabled; +} + +////////////////////////////////////////////////////////////////////////////////////////// +// static +bool LLViewerMedia::isInterestingEnough(const LLVOVolume *object, const F64 &object_interest) +{ + bool result = false; + + if (NULL == object) { - mMediaSource->addCommand(LLMediaBase::COMMAND_START); + result = false; } + // Focused? Then it is interesting! + else if (LLViewerMediaFocus::getInstance()->getFocusedObjectID() == object->getID()) + { + result = true; + } + // Selected? Then it is interesting! + // XXX Sadly, 'contains()' doesn't take a const :( + else if (LLSelectMgr::getInstance()->getSelection()->contains(const_cast<LLVOVolume*>(object))) + { + result = true; + } + else + { + lldebugs << "object interest = " << object_interest << ", lowest loadable = " << sLowestLoadableImplInterest << llendl; + if(object_interest >= sLowestLoadableImplInterest) + result = true; + } + + return result; } -void LLViewerMediaImpl::seek(F32 time) +LLViewerMedia::impl_list &LLViewerMedia::getPriorityList() { - if(mMediaSource) + return sViewerMediaImplList; +} + +// This is the predicate function used to sort sViewerMediaImplList by priority. +bool LLViewerMedia::priorityComparitor(const LLViewerMediaImpl* i1, const LLViewerMediaImpl* i2) +{ + if(i1->isForcedUnloaded() && !i2->isForcedUnloaded()) { - mMediaSource->seek(time); + // Muted or failed items always go to the end of the list, period. + return false; + } + else if(i2->isForcedUnloaded() && !i1->isForcedUnloaded()) + { + // Muted or failed items always go to the end of the list, period. + return true; + } + else if(i1->hasFocus()) + { + // The item with user focus always comes to the front of the list, period. + return true; + } + else if(i2->hasFocus()) + { + // The item with user focus always comes to the front of the list, period. + return false; + } + else if(i1->isParcelMedia()) + { + // The parcel media impl sorts above all other inworld media, unless one has focus. + return true; + } + else if(i2->isParcelMedia()) + { + // The parcel media impl sorts above all other inworld media, unless one has focus. + return false; + } + else if(i1->getUsedInUI() && !i2->getUsedInUI()) + { + // i1 is a UI element, i2 is not. This makes i1 "less than" i2, so it sorts earlier in our list. + return true; + } + else if(i2->getUsedInUI() && !i1->getUsedInUI()) + { + // i2 is a UI element, i1 is not. This makes i2 "less than" i1, so it sorts earlier in our list. + return false; + } + else if(i1->isPlayable() && !i2->isPlayable()) + { + // Playable items sort above ones that wouldn't play even if they got high enough priority + return true; + } + else if(!i1->isPlayable() && i2->isPlayable()) + { + // Playable items sort above ones that wouldn't play even if they got high enough priority + return false; + } + else if(i1->getInterest() == i2->getInterest()) + { + // Generally this will mean both objects have zero interest. In this case, sort on distance. + return (i1->getProximityDistance() < i2->getProximityDistance()); + } + else + { + // The object with the larger interest value should be earlier in the list, so we reverse the sense of the comparison here. + return (i1->getInterest() > i2->getInterest()); } } -void LLViewerMediaImpl::setVolume(F32 volume) +static bool proximity_comparitor(const LLViewerMediaImpl* i1, const LLViewerMediaImpl* i2) { - if(mMediaSource) + if(i1->getProximityDistance() < i2->getProximityDistance()) { - mMediaSource->setVolume( volume); + return true; + } + else if(i1->getProximityDistance() > i2->getProximityDistance()) + { + return false; + } + else + { + // Both objects have the same distance. This most likely means they're two faces of the same object. + // They may also be faces on different objects with exactly the same distance (like HUD objects). + // We don't actually care what the sort order is for this case, as long as it's stable and doesn't change when you enable/disable media. + // Comparing the impl pointers gives a completely arbitrary ordering, but it will be stable. + return (i1 < i2); } } -LLMediaBase::EStatus LLViewerMediaImpl::getStatus() +static LLFastTimer::DeclareTimer FTM_MEDIA_UPDATE("Update Media"); + +////////////////////////////////////////////////////////////////////////////////////////// +// static +void LLViewerMedia::updateMedia(void *dummy_arg) { - if (mMediaSource) + LLFastTimer t1(FTM_MEDIA_UPDATE); + + // Enable/disable the plugin read thread + LLPluginProcessParent::setUseReadThread(gSavedSettings.getBOOL("PluginUseReadThread")); + + 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(); + + for(; iter != end;) + { + LLViewerMediaImpl* pimpl = *iter++; + pimpl->update(); + pimpl->calculateInterest(); + } + + // Sort the static instance list using our interest criteria + sViewerMediaImplList.sort(priorityComparitor); + + // Go through the list again and adjust according to priority. + iter = sViewerMediaImplList.begin(); + end = sViewerMediaImplList.end(); + + F64 total_cpu = 0.0f; + int impl_count_total = 0; + int impl_count_interest_low = 0; + int impl_count_interest_normal = 0; + + std::vector<LLViewerMediaImpl*> proximity_order; + + bool inworld_media_enabled = gSavedSettings.getBOOL("AudioStreamingMedia"); + bool inworld_audio_enabled = gSavedSettings.getBOOL("AudioStreamingMusic"); + U32 max_instances = gSavedSettings.getU32("PluginInstancesTotal"); + U32 max_normal = gSavedSettings.getU32("PluginInstancesNormal"); + U32 max_low = gSavedSettings.getU32("PluginInstancesLow"); + F32 max_cpu = gSavedSettings.getF32("PluginInstancesCPULimit"); + // Setting max_cpu to 0.0 disables CPU usage checking. + bool check_cpu_usage = (max_cpu != 0.0f); + + LLViewerMediaImpl* lowest_interest_loadable = NULL; + + // Notes on tweakable params: + // max_instances must be set high enough to allow the various instances used in the UI (for the help browser, search, etc.) to be loaded. + // If max_normal + max_low is less than max_instances, things will tend to get unloaded instead of being set to slideshow. + + for(; iter != end; iter++) + { + LLViewerMediaImpl* pimpl = *iter; + + LLPluginClassMedia::EPriority new_priority = LLPluginClassMedia::PRIORITY_NORMAL; + + if(pimpl->isForcedUnloaded() || (impl_count_total >= (int)max_instances)) + { + // Never load muted or failed impls. + // Hard limit on the number of instances that will be loaded at one time + new_priority = LLPluginClassMedia::PRIORITY_UNLOADED; + } + else if(!pimpl->getVisible()) + { + new_priority = LLPluginClassMedia::PRIORITY_HIDDEN; + } + else if(pimpl->hasFocus()) + { + new_priority = LLPluginClassMedia::PRIORITY_HIGH; + impl_count_interest_normal++; // count this against the count of "normal" instances for priority purposes + } + else if(pimpl->getUsedInUI()) + { + new_priority = LLPluginClassMedia::PRIORITY_NORMAL; + impl_count_interest_normal++; + } + else if(pimpl->isParcelMedia()) + { + new_priority = LLPluginClassMedia::PRIORITY_NORMAL; + impl_count_interest_normal++; + } + else + { + // Look at interest and CPU usage for instances that aren't in any of the above states. + + // Heuristic -- if the media texture's approximate screen area is less than 1/4 of the native area of the texture, + // turn it down to low instead of normal. This may downsample for plugins that support it. + bool media_is_small = false; + F64 approximate_interest = pimpl->getApproximateTextureInterest(); + if(approximate_interest == 0.0f) + { + // this media has no current size, which probably means it's not loaded. + media_is_small = true; + } + else if(pimpl->getInterest() < (approximate_interest / 4)) + { + media_is_small = true; + } + + if(pimpl->getInterest() == 0.0f) + { + // This media is completely invisible, due to being outside the view frustrum or out of range. + new_priority = LLPluginClassMedia::PRIORITY_HIDDEN; + } + else if(check_cpu_usage && (total_cpu > max_cpu)) + { + // Higher priority plugins have already used up the CPU budget. Set remaining ones to slideshow priority. + new_priority = LLPluginClassMedia::PRIORITY_SLIDESHOW; + } + else if((impl_count_interest_normal < (int)max_normal) && !media_is_small) + { + // Up to max_normal inworld get normal priority + new_priority = LLPluginClassMedia::PRIORITY_NORMAL; + impl_count_interest_normal++; + } + else if (impl_count_interest_low + impl_count_interest_normal < (int)max_low + (int)max_normal) + { + // The next max_low inworld get turned down + new_priority = LLPluginClassMedia::PRIORITY_LOW; + impl_count_interest_low++; + + // Set the low priority size for downsampling to approximately the size the texture is displayed at. + { + F32 approximate_interest_dimension = fsqrtf(pimpl->getInterest()); + + pimpl->setLowPrioritySizeLimit(llround(approximate_interest_dimension)); + } + } + else + { + // Any additional impls (up to max_instances) get very infrequent time + new_priority = LLPluginClassMedia::PRIORITY_SLIDESHOW; + } + } + + if(!pimpl->getUsedInUI() && (new_priority != LLPluginClassMedia::PRIORITY_UNLOADED)) + { + // This is a loadable inworld impl -- the last one in the list in this class defines the lowest loadable interest. + lowest_interest_loadable = pimpl; + + impl_count_total++; + } + + // Overrides if the window is minimized or we lost focus (taking care + // not to accidentally "raise" the priority either) + if (!gViewerWindow->getActive() /* viewer window minimized? */ + && new_priority > LLPluginClassMedia::PRIORITY_HIDDEN) + { + new_priority = LLPluginClassMedia::PRIORITY_HIDDEN; + } + else if (!gFocusMgr.getAppHasFocus() /* viewer window lost focus? */ + && new_priority > LLPluginClassMedia::PRIORITY_LOW) + { + new_priority = LLPluginClassMedia::PRIORITY_LOW; + } + + if(!inworld_media_enabled) + { + // If inworld media is locked out, force all inworld media to stay unloaded. + if(!pimpl->getUsedInUI()) + { + new_priority = LLPluginClassMedia::PRIORITY_UNLOADED; + } + } + // update the audio stream here as well + if( !inworld_audio_enabled) + { + if(LLViewerMedia::isParcelAudioPlaying() && gAudiop && LLViewerMedia::hasParcelAudio()) + { + gAudiop->stopInternetStream(); + } + } + pimpl->setPriority(new_priority); + + if(pimpl->getUsedInUI()) + { + // Any impls used in the UI should not be in the proximity list. + pimpl->mProximity = -1; + } + else + { + proximity_order.push_back(pimpl); + } + + total_cpu += pimpl->getCPUUsage(); + + if (!pimpl->getUsedInUI() && pimpl->hasMedia()) + { + sAnyMediaShowing = true; + } + + } + + // Re-calculate this every time. + sLowestLoadableImplInterest = 0.0f; + + // Only do this calculation if we've hit the impl count limit -- up until that point we always need to load media data. + if(lowest_interest_loadable && (impl_count_total >= (int)max_instances)) { - return mMediaSource->getStatus(); + // Get the interest value of this impl's object for use by isInterestingEnough + LLVOVolume *object = lowest_interest_loadable->getSomeObject(); + if(object) + { + // NOTE: Don't use getMediaInterest() here. We want the pixel area, not the total media interest, + // so that we match up with the calculation done in LLMediaDataClient. + sLowestLoadableImplInterest = object->getPixelArea(); + } + } + + if(gSavedSettings.getBOOL("MediaPerformanceManagerDebug")) + { + // Give impls the same ordering as the priority list + // they're already in the right order for this. } else { - return LLMediaBase::STATUS_UNKNOWN; + // Use a distance-based sort for proximity values. + std::stable_sort(proximity_order.begin(), proximity_order.end(), proximity_comparitor); + } + + // Transfer the proximity order to the proximity fields in the objects. + for(int i = 0; i < (int)proximity_order.size(); i++) + { + proximity_order[i]->mProximity = i; + } + + LL_DEBUGS("PluginPriority") << "Total reported CPU usage is " << total_cpu << llendl; + +} + +////////////////////////////////////////////////////////////////////////////////////////// +// static +bool LLViewerMedia::isAnyMediaShowing() +{ + return sAnyMediaShowing; +} + +////////////////////////////////////////////////////////////////////////////////////////// +// static +void LLViewerMedia::setAllMediaEnabled(bool val) +{ + // Set "tentative" autoplay first. We need to do this here or else + // re-enabling won't start up the media below. + gSavedSettings.setBOOL("MediaTentativeAutoPlay", val); + + // Then + impl_list::iterator iter = sViewerMediaImplList.begin(); + impl_list::iterator end = sViewerMediaImplList.end(); + + for(; iter != end; iter++) + { + LLViewerMediaImpl* pimpl = *iter; + if (!pimpl->getUsedInUI()) + { + pimpl->setDisabled(!val); + } + } + + // Also do Parcel Media and Parcel Audio + if (val) + { + if (!LLViewerMedia::isParcelMediaPlaying() && LLViewerMedia::hasParcelMedia()) + { + LLViewerParcelMedia::play(LLViewerParcelMgr::getInstance()->getAgentParcel()); + } + + if (gSavedSettings.getBOOL("AudioStreamingMusic") && + !LLViewerMedia::isParcelAudioPlaying() && + gAudiop && + LLViewerMedia::hasParcelAudio()) + { + gAudiop->startInternetStream(LLViewerMedia::getParcelAudioURL()); + } + } + else { + // This actually unloads the impl, as opposed to "stop"ping the media + LLViewerParcelMedia::stop(); + if (gAudiop) gAudiop->stopInternetStream(); } } ////////////////////////////////////////////////////////////////////////////////////////// // static -void LLViewerMediaImpl::updateMovieImage(const LLUUID& uuid, BOOL active) +bool LLViewerMedia::isParcelMediaPlaying() +{ + return (LLViewerMedia::hasParcelMedia() && LLViewerParcelMedia::getParcelMedia() && LLViewerParcelMedia::getParcelMedia()->hasMedia()); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// static +bool LLViewerMedia::isParcelAudioPlaying() +{ + return (LLViewerMedia::hasParcelAudio() && gAudiop && LLAudioEngine::AUDIO_PLAYING == gAudiop->isInternetStreamPlaying()); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// static +void LLViewerMedia::clearAllCookies() +{ + // Clear all cookies for all plugins + impl_list::iterator iter = sViewerMediaImplList.begin(); + impl_list::iterator end = sViewerMediaImplList.end(); + for (; iter != end; iter++) + { + LLViewerMediaImpl* pimpl = *iter; + if(pimpl->mMediaSource) + { + pimpl->mMediaSource->clear_cookies(); + } + } + + // Clear all cookies from the cookie store + getCookieStore()->setAllCookies(""); + + // FIXME: this may not be sufficient, since the on-disk cookie file won't get written until some browser instance exits cleanly. + // It also won't clear cookies for other accounts, or for any account if we're not logged in, and won't do anything at all if there are no webkit plugins loaded. + // Until such time as we can centralize cookie storage, the following hack should cover these cases: + + // HACK: Look for cookie files in all possible places and delete them. + // NOTE: this assumes knowledge of what happens inside the webkit plugin (it's what adds 'browser_profile' to the path and names the cookie file) + + // Places that cookie files can be: + // <getOSUserAppDir>/browser_profile/cookies + // <getOSUserAppDir>/first_last/browser_profile/cookies (note that there may be any number of these!) + // <getOSUserAppDir>/first_last/plugin_cookies.txt (note that there may be any number of these!) + + std::string base_dir = gDirUtilp->getOSUserAppDir() + gDirUtilp->getDirDelimiter(); + std::string target; + std::string filename; + + lldebugs << "base dir = " << base_dir << llendl; + + // The non-logged-in version is easy + target = base_dir; + target += "browser_profile"; + target += gDirUtilp->getDirDelimiter(); + target += "cookies"; + lldebugs << "target = " << target << llendl; + if(LLFile::isfile(target)) + { + LLFile::remove(target); + } + + // the hard part: iterate over all user directories and delete the cookie file from each one + while(gDirUtilp->getNextFileInDir(base_dir, "*_*", filename, false)) + { + target = base_dir; + target += filename; + target += gDirUtilp->getDirDelimiter(); + target += "browser_profile"; + target += gDirUtilp->getDirDelimiter(); + target += "cookies"; + lldebugs << "target = " << target << llendl; + if(LLFile::isfile(target)) + { + LLFile::remove(target); + } + + // Other accounts may have new-style cookie files too -- delete them as well + target = base_dir; + target += filename; + target += gDirUtilp->getDirDelimiter(); + target += PLUGIN_COOKIE_FILE_NAME; + lldebugs << "target = " << target << llendl; + if(LLFile::isfile(target)) + { + LLFile::remove(target); + } + } + + // If we have an OpenID cookie, re-add it to the cookie store. + setOpenIDCookie(); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// static +void LLViewerMedia::clearAllCaches() +{ + // Clear all plugins' caches + impl_list::iterator iter = sViewerMediaImplList.begin(); + impl_list::iterator end = sViewerMediaImplList.end(); + for (; iter != end; iter++) + { + LLViewerMediaImpl* pimpl = *iter; + pimpl->clearCache(); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +// static +void LLViewerMedia::setCookiesEnabled(bool enabled) +{ + // Set the "cookies enabled" flag for all loaded plugins + impl_list::iterator iter = sViewerMediaImplList.begin(); + impl_list::iterator end = sViewerMediaImplList.end(); + for (; iter != end; iter++) + { + LLViewerMediaImpl* pimpl = *iter; + if(pimpl->mMediaSource) + { + pimpl->mMediaSource->enable_cookies(enabled); + } + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +// static +void LLViewerMedia::setProxyConfig(bool enable, const std::string &host, int port) +{ + // Set the proxy config for all loaded plugins + impl_list::iterator iter = sViewerMediaImplList.begin(); + impl_list::iterator end = sViewerMediaImplList.end(); + for (; iter != end; iter++) + { + LLViewerMediaImpl* pimpl = *iter; + if(pimpl->mMediaSource) + { + pimpl->mMediaSource->proxy_setup(enable, host, port); + } + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +// 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(); + + // send the clear_cookies message to all loaded plugins + impl_list::iterator iter = sViewerMediaImplList.begin(); + impl_list::iterator end = sViewerMediaImplList.end(); + for (; iter != end; iter++) + { + LLViewerMediaImpl* pimpl = *iter; + if(pimpl->mMediaSource) + { + pimpl->mMediaSource->clear_cookies(); + } + } + + // If we have an OpenID cookie, re-add it to the cookie store. + setOpenIDCookie(); +} + + +///////////////////////////////////////////////////////////////////////////////////////// +// 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) { - // IF the media image hasn't changed, do nothing - if (mMovieImageID == uuid) + // 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); +} + + +///////////////////////////////////////////////////////////////////////////////////////// +// static +void LLViewerMedia::setOpenIDCookie() +{ + if(!sOpenIDCookie.empty()) { + // The LLURL can give me the 'authority', which is of the form: [username[:password]@]hostname[:port] + // We want just the hostname for the cookie code, but LLURL doesn't seem to have a way to extract that. + // We therefore do it here. + std::string authority = sOpenIDURL.mAuthority; + std::string::size_type host_start = authority.find('@'); + if(host_start == std::string::npos) + { + // no username/password + host_start = 0; + } + else + { + // Hostname starts after the @. + // (If the hostname part is empty, this may put host_start at the end of the string. In that case, it will end up passing through an empty hostname, which is correct.) + ++host_start; + } + std::string::size_type host_end = authority.rfind(':'); + if((host_end == std::string::npos) || (host_end < host_start)) + { + // no port + host_end = authority.size(); + } + + getCookieStore()->setCookiesFromHost(sOpenIDCookie, authority.substr(host_start, host_end - host_start)); + + // *HACK: Doing this here is nasty, find a better way. + LLWebSharing::instance().setOpenIDCookie(sOpenIDCookie); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +// static +void LLViewerMedia::openIDSetup(const std::string &openid_url, const std::string &openid_token) +{ + LL_DEBUGS("MediaAuth") << "url = \"" << openid_url << "\", token = \"" << openid_token << "\"" << LL_ENDL; + + // post the token to the url + // the responder will need to extract the cookie(s). + + // Save the OpenID URL for later -- we may need the host when adding the cookie. + sOpenIDURL.init(openid_url.c_str()); + + // We shouldn't ever do this twice, but just in case this code gets repurposed later, clear existing cookies. + sOpenIDCookie.clear(); + + LLSD headers = LLSD::emptyMap(); + // Keep LLHTTPClient from adding an "Accept: application/llsd+xml" header + headers["Accept"] = "*/*"; + // and use the expected content-type for a post, instead of the LLHTTPClient::postRaw() default of "application/octet-stream" + headers["Content-Type"] = "application/x-www-form-urlencoded"; + + // postRaw() takes ownership of the buffer and releases it later, so we need to allocate a new buffer here. + size_t size = openid_token.size(); + U8 *data = new U8[size]; + memcpy(data, openid_token.data(), size); + + LLHTTPClient::postRaw( + openid_url, + data, + size, + new LLViewerMediaOpenIDResponder(), + headers); + +} + +///////////////////////////////////////////////////////////////////////////////////////// +// static +void LLViewerMedia::openIDCookieResponse(const std::string &cookie) +{ + LL_DEBUGS("MediaAuth") << "Cookie received: \"" << cookie << "\"" << LL_ENDL; + + sOpenIDCookie += cookie; + + setOpenIDCookie(); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// static +void LLViewerMedia::proxyWindowOpened(const std::string &target, const std::string &uuid) +{ + if(uuid.empty()) return; + + for (impl_list::iterator iter = sViewerMediaImplList.begin(); iter != sViewerMediaImplList.end(); iter++) + { + if((*iter)->mMediaSource && (*iter)->mMediaSource->pluginSupportsMediaBrowser()) + { + (*iter)->mMediaSource->proxyWindowOpened(target, uuid); + } } - // If we have changed media uuid, restore the old one - if (!mMovieImageID.isNull()) +} + +///////////////////////////////////////////////////////////////////////////////////////// +// static +void LLViewerMedia::proxyWindowClosed(const std::string &uuid) +{ + if(uuid.empty()) + return; + + for (impl_list::iterator iter = sViewerMediaImplList.begin(); iter != sViewerMediaImplList.end(); iter++) { - LLViewerImage* oldImage = LLViewerImage::getImage( mMovieImageID ); - if (oldImage) + if((*iter)->mMediaSource && (*iter)->mMediaSource->pluginSupportsMediaBrowser()) { - oldImage->reinit(mMovieImageHasMips); - oldImage->mIsMediaTexture = FALSE; + (*iter)->mMediaSource->proxyWindowClosed(uuid); } - mMovieImageID.setNull(); } - // If the movie is playing, set the new media image - if (active && !uuid.isNull()) +} + +bool LLViewerMedia::hasInWorldMedia() +{ + if (sInWorldMediaDisabled) return false; + impl_list::iterator iter = sViewerMediaImplList.begin(); + impl_list::iterator end = sViewerMediaImplList.end(); + // This should be quick, because there should be very few non-in-world-media impls + for (; iter != end; iter++) { - LLViewerImage* viewerImage = LLViewerImage::getImage( uuid ); - if( viewerImage ) + LLViewerMediaImpl* pimpl = *iter; + if (!pimpl->getUsedInUI() && !pimpl->isParcelMedia()) { - mMovieImageID = uuid; - // Can't use mipmaps for movies because they don't update the full image - mMovieImageHasMips = viewerImage->getUseMipMaps(); - viewerImage->reinit(FALSE); - viewerImage->mIsMediaTexture = TRUE; + // Found an in-world media impl + return true; } } + return false; } +////////////////////////////////////////////////////////////////////////////////////////// +// static +bool LLViewerMedia::hasParcelMedia() +{ + return !LLViewerParcelMedia::getURL().empty(); +} ////////////////////////////////////////////////////////////////////////////////////////// // static -void LLViewerMediaImpl::updateImagesMediaStreams() +bool LLViewerMedia::hasParcelAudio() +{ + return !LLViewerMedia::getParcelAudioURL().empty(); +} + +////////////////////////////////////////////////////////////////////////////////////////// +// static +std::string LLViewerMedia::getParcelAudioURL() +{ + return LLViewerParcelMgr::getInstance()->getAgentParcel()->getMusicURL(); +} + +////////////////////////////////////////////////////////////////////////////////////////// +// static +void LLViewerMedia::initClass() +{ + gIdleCallbacks.addFunction(LLViewerMedia::updateMedia, NULL); + sTeleportFinishConnection = LLViewerParcelMgr::getInstance()-> + setTeleportFinishedCallback(boost::bind(&LLViewerMedia::onTeleportFinished)); +} + +////////////////////////////////////////////////////////////////////////////////////////// +// static +void LLViewerMedia::cleanupClass() +{ + gIdleCallbacks.deleteFunction(LLViewerMedia::updateMedia, NULL); + sTeleportFinishConnection.disconnect(); +} + +////////////////////////////////////////////////////////////////////////////////////////// +// static +void LLViewerMedia::onTeleportFinished() +{ + // On teleport, clear this setting (i.e. set it to true) + gSavedSettings.setBOOL("MediaTentativeAutoPlay", true); +} + +////////////////////////////////////////////////////////////////////////////////////////// +// LLViewerMediaImpl +////////////////////////////////////////////////////////////////////////////////////////// +LLViewerMediaImpl::LLViewerMediaImpl( const LLUUID& texture_id, + S32 media_width, + S32 media_height, + U8 media_auto_scale, + U8 media_loop) +: + mMediaSource( NULL ), + mMovieImageHasMips(false), + mMediaWidth(media_width), + mMediaHeight(media_height), + mMediaAutoScale(media_auto_scale), + mMediaLoop(media_loop), + mNeedsNewTexture(true), + mTextureUsedWidth(0), + mTextureUsedHeight(0), + mSuspendUpdates(false), + mVisible(true), + mLastSetCursor( UI_CURSOR_ARROW ), + mMediaNavState( MEDIANAVSTATE_NONE ), + mInterest(0.0f), + mUsedInUI(false), + mHasFocus(false), + mPriority(LLPluginClassMedia::PRIORITY_UNLOADED), + mNavigateRediscoverType(false), + mNavigateServerRequest(false), + mMediaSourceFailed(false), + mRequestedVolume(1.0f), + mIsMuted(false), + mNeedsMuteCheck(false), + mPreviousMediaState(MEDIA_NONE), + mPreviousMediaTime(0.0f), + mIsDisabled(false), + mIsParcelMedia(false), + mProximity(-1), + mProximityDistance(0.0f), + mMimeTypeProbe(NULL), + mMediaAutoPlay(false), + mInNearbyMediaList(false), + mClearCache(false), + mBackgroundColor(LLColor4::white), + mNavigateSuspended(false), + mNavigateSuspendedDeferred(false), + mIsUpdated(false), + mTrustedBrowser(false) +{ + + // Set up the mute list observer if it hasn't been set up already. + if(!sViewerMediaMuteListObserverInitialized) + { + LLMuteList::getInstance()->addObserver(&sViewerMediaMuteListObserver); + sViewerMediaMuteListObserverInitialized = true; + } + + add_media_impl(this); + + setTextureID(texture_id); + + // connect this media_impl to the media texture, creating it if it doesn't exist.0 + // This is necessary because we need to be able to use getMaxVirtualSize() even if the media plugin is not loaded. + LLViewerMediaTexture* media_tex = LLViewerTextureManager::getMediaTexture(mTextureId); + if(media_tex) + { + media_tex->setMediaImpl(); + } + +} + +////////////////////////////////////////////////////////////////////////////////////////// +LLViewerMediaImpl::~LLViewerMediaImpl() { - LLMediaManager::updateClass(); + destroyMediaSource(); + + LLViewerMediaTexture::removeMediaImplFromTexture(mTextureId) ; + + setTextureID(); + remove_media_impl(this); } -void LLViewerMediaImpl::initializePlaceholderImage(LLViewerImage *placeholder_image, LLMediaBase *media_source) +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::emitEvent(LLPluginClassMedia* plugin, LLViewerMediaObserver::EMediaEvent event) { - int media_width = media_source->getMediaWidth(); - int media_height = media_source->getMediaHeight(); - //int media_rowspan = media_source->getMediaRowSpan(); + // Broadcast to observers using the superclass version + LLViewerMediaEventEmitter::emitEvent(plugin, event); + + // If this media is on one or more LLVOVolume objects, tell them about the event as well. + std::list< LLVOVolume* >::iterator iter = mObjectList.begin() ; + while(iter != mObjectList.end()) + { + LLVOVolume *self = *iter; + ++iter; + self->mediaEvent(this, plugin, event); + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMediaImpl::initializeMedia(const std::string& mime_type) +{ + bool mimeTypeChanged = (mMimeType != mime_type); + bool pluginChanged = (LLMIMETypes::implType(mCurrentMimeType) != LLMIMETypes::implType(mime_type)); + + if(!mMediaSource || pluginChanged) + { + // We don't have a plugin at all, or the new mime type is handled by a different plugin than the old mime type. + (void)initializePlugin(mime_type); + } + else if(mimeTypeChanged) + { + // The same plugin should be able to handle the new media -- just update the stored mime type. + mMimeType = mime_type; + } + + return (mMediaSource != NULL); +} - // if width & height are invalid, don't bother doing anything - if ( media_width < 1 || media_height < 1 ) +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::createMediaSource() +{ + if(mPriority == LLPluginClassMedia::PRIORITY_UNLOADED) + { + // This media shouldn't be created yet. return; + } + + if(! mMediaURL.empty()) + { + navigateInternal(); + } + else if(! mMimeType.empty()) + { + if (!initializeMedia(mMimeType)) + { + LL_WARNS("Media") << "Failed to initialize media for mime type " << mMimeType << LL_ENDL; + } + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::destroyMediaSource() +{ + mNeedsNewTexture = true; + + // Tell the viewer media texture it's no longer active + LLViewerMediaTexture* oldImage = LLViewerTextureManager::findMediaTexture( mTextureId ); + if (oldImage) + { + oldImage->setPlaying(FALSE) ; + } + + cancelMimeTypeProbe(); + + if(mMediaSource) + { + mMediaSource->setDeleteOK(true) ; + delete mMediaSource; + mMediaSource = NULL; + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::setMediaType(const std::string& media_type) +{ + mMimeType = media_type; +} + +////////////////////////////////////////////////////////////////////////////////////////// +/*static*/ +LLPluginClassMedia* LLViewerMediaImpl::newSourceFromMediaType(std::string media_type, LLPluginClassMediaOwner *owner /* may be NULL */, S32 default_width, S32 default_height, const std::string target) +{ + std::string plugin_basename = LLMIMETypes::implType(media_type); + + if(plugin_basename.empty()) + { + LL_WARNS("Media") << "Couldn't find plugin for media type " << media_type << LL_ENDL; + } + else + { + std::string launcher_name = gDirUtilp->getLLPluginLauncher(); + std::string plugin_name = gDirUtilp->getLLPluginFilename(plugin_basename); + std::string user_data_path = gDirUtilp->getOSUserAppDir(); + user_data_path += gDirUtilp->getDirDelimiter(); + + // Fix for EXT-5960 - make browser profile specific to user (cache, cookies etc.) + // If the linden username returned is blank, that can only mean we are + // at the login page displaying login Web page or Web browser test via Develop menu. + // In this case we just use whatever gDirUtilp->getOSUserAppDir() gives us (this + // is what we always used before this change) + std::string linden_user_dir = gDirUtilp->getLindenUserDir(); + if ( ! linden_user_dir.empty() ) + { + // gDirUtilp->getLindenUserDir() is whole path, not just Linden name + user_data_path = linden_user_dir; + user_data_path += gDirUtilp->getDirDelimiter(); + }; + + // See if the plugin executable exists + llstat s; + if(LLFile::stat(launcher_name, &s)) + { + LL_WARNS("Media") << "Couldn't find launcher at " << launcher_name << LL_ENDL; + } + else if(LLFile::stat(plugin_name, &s)) + { + LL_WARNS("Media") << "Couldn't find plugin at " << plugin_name << LL_ENDL; + } + else + { + LLPluginClassMedia* media_source = new LLPluginClassMedia(owner); + media_source->setSize(default_width, default_height); + media_source->setUserDataPath(user_data_path); + media_source->setLanguageCode(LLUI::getLanguage()); + + // collect 'cookies enabled' setting from prefs and send to embedded browser + bool cookies_enabled = gSavedSettings.getBOOL( "CookiesEnabled" ); + media_source->enable_cookies( cookies_enabled ); + + // collect 'plugins enabled' setting from prefs and send to embedded browser + bool plugins_enabled = gSavedSettings.getBOOL( "BrowserPluginsEnabled" ); + media_source->setPluginsEnabled( plugins_enabled ); + + // collect 'javascript enabled' setting from prefs and send to embedded browser + bool javascript_enabled = gSavedSettings.getBOOL( "BrowserJavascriptEnabled" ); + media_source->setJavascriptEnabled( javascript_enabled ); + + media_source->setTarget(target); + + if (media_source->init(launcher_name, plugin_name, gSavedSettings.getBOOL("PluginAttachDebuggerToPlugins"))) + { + return media_source; + } + else + { + LL_WARNS("Media") << "Failed to init plugin. Destroying." << LL_ENDL; + delete media_source; + } + } + } + + LL_WARNS("Plugin") << "plugin intialization failed for mime type: " << media_type << LL_ENDL; + LLSD args; + args["MIME_TYPE"] = media_type; + LLNotificationsUtil::add("NoPlugin", args); - llinfos << "initializing media placeholder" << llendl; - llinfos << "movie image id " << mMovieImageID << llendl; + return NULL; +} - int texture_width = LLMediaManager::textureWidthFromMediaWidth( media_width ); - int texture_height = LLMediaManager::textureHeightFromMediaHeight( media_height ); - int texture_depth = media_source->getMediaDepth(); +////////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMediaImpl::initializePlugin(const std::string& media_type) +{ + if(mMediaSource) + { + // Save the previous media source's last set size before destroying it. + mMediaWidth = mMediaSource->getSetWidth(); + mMediaHeight = mMediaSource->getSetHeight(); + } + + // Always delete the old media impl first. + destroyMediaSource(); + + // and unconditionally set the mime type + mMimeType = media_type; - // MEDIAOPT: check to see if size actually changed before doing work - placeholder_image->destroyGLTexture(); - // MEDIAOPT: apparently just calling setUseMipMaps(FALSE) doesn't work? - placeholder_image->reinit(FALSE); // probably not needed + if(mPriority == LLPluginClassMedia::PRIORITY_UNLOADED) + { + // This impl should not be loaded at this time. + LL_DEBUGS("PluginPriority") << this << "Not loading (PRIORITY_UNLOADED)" << LL_ENDL; + + return false; + } + + // If we got here, we want to ignore previous init failures. + mMediaSourceFailed = false; - // MEDIAOPT: seems insane that we actually have to make an imageraw then - // immediately discard it - LLPointer<LLImageRaw> raw = new LLImageRaw(texture_width, texture_height, texture_depth); - raw->clear(0x0f, 0x0f, 0x0f, 0xff); - int discard_level = 0; + // Save the MIME type that really caused the plugin to load + mCurrentMimeType = mMimeType; - // ask media source for correct GL image format constants - placeholder_image->setExplicitFormat(media_source->getTextureFormatInternal(), - media_source->getTextureFormatPrimary(), - media_source->getTextureFormatType()); + LLPluginClassMedia* media_source = newSourceFromMediaType(mMimeType, this, mMediaWidth, mMediaHeight, mTarget); + + if (media_source) + { + media_source->setDisableTimeout(gSavedSettings.getBOOL("DebugPluginDisableTimeout")); + media_source->setLoop(mMediaLoop); + media_source->setAutoScale(mMediaAutoScale); + media_source->setBrowserUserAgent(LLViewerMedia::getCurrentUserAgent()); + media_source->focus(mHasFocus); + media_source->setBackgroundColor(mBackgroundColor); + + media_source->proxy_setup(gSavedSettings.getBOOL("BrowserProxyEnabled"), gSavedSettings.getString("BrowserProxyAddress"), gSavedSettings.getS32("BrowserProxyPort")); + + if(mClearCache) + { + mClearCache = false; + 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; + mMediaSource->setDeleteOK(false) ; + updateVolume(); - placeholder_image->createGLTexture(discard_level, raw); + return true; + } - // placeholder_image->setExplicitFormat() - placeholder_image->setUseMipMaps(FALSE); + // Make sure the timer doesn't try re-initing this plugin repeatedly until something else changes. + mMediaSourceFailed = true; - // MEDIAOPT: set this dynamically on play/stop - placeholder_image->mIsMediaTexture = true; + return false; } +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::loadURI() +{ + if(mMediaSource) + { + // trim whitespace from front and back of URL - fixes EXT-5363 + LLStringUtil::trim( mMediaURL ); + + // *HACK: we don't know if the URI coming in is properly escaped + // (the contract doesn't specify whether it is escaped or not. + // but LLQtWebKit expects it to be, so we do our best to encode + // special characters) + // The strings below were taken right from http://www.ietf.org/rfc/rfc1738.txt + // Note especially that '%' and '/' are there. + std::string uri = LLURI::escape(mMediaURL, + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789" + "$-_.+" + "!*'()," + "{}|\\^~[]`" + "<>#%" + ";/?:@&=", + false); + llinfos << "Asking media source to load URI: " << uri << llendl; + + mMediaSource->loadURI( uri ); + + // A non-zero mPreviousMediaTime means that either this media was previously unloaded by the priority code while playing/paused, + // or a seek happened before the media loaded. In either case, seek to the saved time. + if(mPreviousMediaTime != 0.0f) + { + seek(mPreviousMediaTime); + } + + if(mPreviousMediaState == MEDIA_PLAYING) + { + // This media was playing before this instance was unloaded. + start(); + } + else if(mPreviousMediaState == MEDIA_PAUSED) + { + // This media was paused before this instance was unloaded. + pause(); + } + else + { + // No relevant previous media play state -- if we're loading the URL, we want to start playing. + start(); + } + } +} +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::setSize(int width, int height) +{ + mMediaWidth = width; + mMediaHeight = height; + if(mMediaSource) + { + mMediaSource->setSize(width, height); + } +} -// virtual -void LLViewerMediaImpl::onMediaContentsChange(const EventType& event_in) +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::play() { - LLMediaBase* media_source = event_in.getSubject(); - LLViewerImage* placeholder_image = gImageList.getImage( mMovieImageID ); - if ((placeholder_image) && (placeholder_image->getHasGLTexture())) + // If the media source isn't there, try to initialize it and load an URL. + if(mMediaSource == NULL) { - if (placeholder_image->getUseMipMaps()) + if(!initializeMedia(mMimeType)) { - // bad image! NO MIPMAPS! - initializePlaceholderImage(placeholder_image, media_source); + // This may be the case where the plugin's priority is PRIORITY_UNLOADED + return; } + + // Only do this if the media source was just loaded. + loadURI(); + } + + // always start the media + start(); +} - U8* data = media_source->getMediaData(); - S32 x_pos = 0; - S32 y_pos = 0; - S32 width = media_source->getMediaWidth(); - S32 height = media_source->getMediaHeight(); - S32 data_width = media_source->getMediaDataWidth(); - S32 data_height = media_source->getMediaDataHeight(); - placeholder_image->setSubImage(data, data_width, data_height, - x_pos, y_pos, width, height); +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::stop() +{ + if(mMediaSource) + { + mMediaSource->stop(); + // destroyMediaSource(); } } +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::pause() +{ + if(mMediaSource) + { + mMediaSource->pause(); + } + else + { + mPreviousMediaState = MEDIA_PAUSED; + } +} -// virtual -void LLViewerMediaImpl::onMediaSizeChange(const EventType& event_in) +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::start() { - LLMediaBase* media_source = event_in.getSubject(); - LLViewerImage* placeholder_image = gImageList.getImage( mMovieImageID ); - if (placeholder_image) + if(mMediaSource) { - initializePlaceholderImage(placeholder_image, media_source); + mMediaSource->start(); } else { - llinfos << "no placeholder image" << llendl; + mPreviousMediaState = MEDIA_PLAYING; } } +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::seek(F32 time) +{ + if(mMediaSource) + { + mMediaSource->seek(time); + } + else + { + // Save the seek time to be set when the media is loaded. + mPreviousMediaTime = time; + } +} - // Get the image we're using +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::skipBack(F32 step_scale) +{ + if(mMediaSource) + { + if(mMediaSource->pluginSupportsMediaTime()) + { + F64 back_step = mMediaSource->getCurrentTime() - (mMediaSource->getDuration()*step_scale); + if(back_step < 0.0) + { + back_step = 0.0; + } + mMediaSource->seek(back_step); + } + } +} - /* - // update media stream if required - LLMediaEngine* media_engine = LLMediaEngine::getInstance(); - if (media_engine) +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::skipForward(F32 step_scale) +{ + if(mMediaSource) { - if ( media_engine->update() ) + if(mMediaSource->pluginSupportsMediaTime()) { - LLUUID media_uuid = media_engine->getImageUUID(); - updateMovieImage(media_uuid, TRUE); - if (!media_uuid.isNull()) + F64 forward_step = mMediaSource->getCurrentTime() + (mMediaSource->getDuration()*step_scale); + if(forward_step > mMediaSource->getDuration()) { - LLViewerImage* viewerImage = getImage( media_uuid ); - if( viewerImage ) - { - LLMediaBase* renderer = media_engine->getMediaRenderer(); - if ((renderer->getTextureWidth() != viewerImage->getWidth()) || - (renderer->getTextureHeight() != viewerImage->getHeight()) || - (renderer->getTextureDepth() != viewerImage->getComponents()) || - (viewerImage->getHasGLTexture() == FALSE)) - { - // destroy existing GL image - viewerImage->destroyGLTexture(); - - // set new size - viewerImage->setSize( renderer->getTextureWidth(), - renderer->getTextureHeight(), - renderer->getTextureDepth() ); - - LLPointer<LLImageRaw> raw = new LLImageRaw(renderer->getTextureWidth(), - renderer->getTextureHeight(), - renderer->getTextureDepth()); - raw->clear(0x7f,0x7f,0x7f,0xff); - viewerImage->createGLTexture(0, raw); - } + forward_step = mMediaSource->getDuration(); + } + mMediaSource->seek(forward_step); + } + } +} - // Set the explicit format the instance wants - viewerImage->setExplicitFormat(renderer->getTextureFormatInternal(), - renderer->getTextureFormatPrimary(), - renderer->getTextureFormatType(), - renderer->getTextureFormatSwapBytes()); - // This should be redundant, but just in case: - viewerImage->setUseMipMaps(FALSE); +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::setVolume(F32 volume) +{ + mRequestedVolume = volume; + updateVolume(); +} - LLImageRaw* rawImage = media_engine->getImageRaw(); - if ( rawImage ) - { - viewerImage->setSubImage(rawImage, 0, 0, - renderer->getMediaWidth(), - renderer->getMediaHeight()); - } - } - else - { - llwarns << "MediaEngine update unable to get viewer image for GL texture" << llendl; - } +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::updateVolume() +{ + if(mMediaSource) + { + // always scale the volume by the global media volume + F32 volume = mRequestedVolume * LLViewerMedia::getVolume(); + + if (mProximityCamera > 0) + { + if (mProximityCamera > gSavedSettings.getF32("MediaRollOffMax")) + { + volume = 0; + } + else if (mProximityCamera > gSavedSettings.getF32("MediaRollOffMin")) + { + // attenuated_volume = 1 / (roll_off_rate * (d - min))^2 + // the +1 is there so that for distance 0 the volume stays the same + F64 adjusted_distance = mProximityCamera - gSavedSettings.getF32("MediaRollOffMin"); + F64 attenuation = 1.0 + (gSavedSettings.getF32("MediaRollOffRate") * adjusted_distance); + attenuation = 1.0 / (attenuation * attenuation); + // the attenuation multiplier should never be more than one since that would increase volume + volume = volume * llmin(1.0, attenuation); } } - else + + mMediaSource->setVolume(volume); + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +F32 LLViewerMediaImpl::getVolume() +{ + return mRequestedVolume; +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::focus(bool focus) +{ + mHasFocus = focus; + + if (mMediaSource) + { + // call focus just for the hell of it, even though this apopears to be a nop + mMediaSource->focus(focus); + if (focus) { - LLUUID media_uuid = media_engine->getImageUUID(); - updateMovieImage(media_uuid, FALSE); + // spoof a mouse click to *actually* pass focus + // Don't do this anymore -- it actually clicks through now. +// mMediaSource->mouseEvent(LLPluginClassMedia::MOUSE_EVENT_DOWN, 1, 1, 0); +// mMediaSource->mouseEvent(LLPluginClassMedia::MOUSE_EVENT_UP, 1, 1, 0); } } - */ +} +////////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMediaImpl::hasFocus() const +{ + // FIXME: This might be able to be a bit smarter by hooking into LLViewerMediaFocus, etc. + return mHasFocus; +} -LLUUID LLViewerMediaImpl::getMediaTextureID() +std::string LLViewerMediaImpl::getCurrentMediaURL() { - return mMovieImageID; + if(!mCurrentMediaURL.empty()) + { + return mCurrentMediaURL; + } + + return mMediaURL; } -// static -void LLViewerMediaImpl::updateBrowserUserAgent() +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::clearCache() { - // Don't use user-visible string to avoid - // punctuation and strange characters. - std::string skin_name = gSavedSettings.getString("SkinCurrent"); + if(mMediaSource) + { + mMediaSource->clear_cache(); + } + else + { + mClearCache = true; + } +} - // Just in case we need to check browser differences in A/B test - // builds. - std::string channel = gSavedSettings.getString("VersionChannelName"); +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::mouseDown(S32 x, S32 y, MASK mask, S32 button) +{ + scaleMouse(&x, &y); + mLastMouseX = x; + mLastMouseY = y; +// llinfos << "mouse down (" << x << ", " << y << ")" << llendl; + if (mMediaSource) + { + mMediaSource->mouseEvent(LLPluginClassMedia::MOUSE_EVENT_DOWN, button, x, y, mask); + } +} - // append our magic version number string to the browser user agent id - // See the HTTP 1.0 and 1.1 specifications for allowed formats: - // http://www.ietf.org/rfc/rfc1945.txt section 10.15 - // http://www.ietf.org/rfc/rfc2068.txt section 3.8 - // This was also helpful: - // http://www.mozilla.org/build/revised-user-agent-strings.html - std::ostringstream codec; - codec << "SecondLife/"; - codec << LL_VERSION_MAJOR << "." << LL_VERSION_MINOR << "." << LL_VERSION_PATCH << "." << LL_VERSION_BUILD; - codec << " (" << channel << "; " << skin_name << " skin)"; - llinfos << codec.str() << llendl; - LLMediaManager::setBrowserUserAgent( codec.str() ); +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::mouseUp(S32 x, S32 y, MASK mask, S32 button) +{ + scaleMouse(&x, &y); + mLastMouseX = x; + mLastMouseY = y; +// llinfos << "mouse up (" << x << ", " << y << ")" << llendl; + if (mMediaSource) + { + mMediaSource->mouseEvent(LLPluginClassMedia::MOUSE_EVENT_UP, button, x, y, mask); + } } -// static -bool LLViewerMediaImpl::handleSkinCurrentChanged(const LLSD& /*newvalue*/) +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::mouseMove(S32 x, S32 y, MASK mask) { - // gSavedSettings is already updated when this function is called. - updateBrowserUserAgent(); - return true; + scaleMouse(&x, &y); + mLastMouseX = x; + mLastMouseY = y; +// llinfos << "mouse move (" << x << ", " << y << ")" << llendl; + if (mMediaSource) + { + mMediaSource->mouseEvent(LLPluginClassMedia::MOUSE_EVENT_MOVE, 0, x, y, mask); + } } ////////////////////////////////////////////////////////////////////////////////////////// -// Wrapper class +//static +void LLViewerMediaImpl::scaleTextureCoords(const LLVector2& texture_coords, S32 *x, S32 *y) +{ + F32 texture_x = texture_coords.mV[VX]; + F32 texture_y = texture_coords.mV[VY]; + + // Deal with repeating textures by wrapping the coordinates into the range [0, 1.0) + texture_x = fmodf(texture_x, 1.0f); + if(texture_x < 0.0f) + texture_x = 1.0 + texture_x; + + texture_y = fmodf(texture_y, 1.0f); + if(texture_y < 0.0f) + texture_y = 1.0 + texture_y; + + // scale x and y to texel units. + *x = llround(texture_x * mMediaSource->getTextureWidth()); + *y = llround((1.0f - texture_y) * mMediaSource->getTextureHeight()); + + // Adjust for the difference between the actual texture height and the amount of the texture in use. + *y -= (mMediaSource->getTextureHeight() - mMediaSource->getHeight()); +} + ////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::mouseDown(const LLVector2& texture_coords, MASK mask, S32 button) +{ + if(mMediaSource) + { + S32 x, y; + scaleTextureCoords(texture_coords, &x, &y); + + mouseDown(x, y, mask, button); + } +} + +void LLViewerMediaImpl::mouseUp(const LLVector2& texture_coords, MASK mask, S32 button) +{ + if(mMediaSource) + { + S32 x, y; + scaleTextureCoords(texture_coords, &x, &y); + + mouseUp(x, y, mask, button); + } +} +void LLViewerMediaImpl::mouseMove(const LLVector2& texture_coords, MASK mask) +{ + if(mMediaSource) + { + S32 x, y; + scaleTextureCoords(texture_coords, &x, &y); + + mouseMove(x, y, mask); + } +} ////////////////////////////////////////////////////////////////////////////////////////// -// The viewer takes a long time to load the start screen. Part of the problem -// is media initialization -- in particular, QuickTime loads many DLLs and -// hits the disk heavily. So we initialize only the browser component before -// the login screen, then do the rest later when we have a progress bar. JC -// static -void LLViewerMedia::initBrowser() +void LLViewerMediaImpl::mouseDoubleClick(S32 x, S32 y, MASK mask, S32 button) { - LLMediaManagerData* init_data = new LLMediaManagerData; - buildMediaManagerData( init_data ); - LLMediaManager::initBrowser( init_data ); - delete init_data; + scaleMouse(&x, &y); + mLastMouseX = x; + mLastMouseY = y; + if (mMediaSource) + { + mMediaSource->mouseEvent(LLPluginClassMedia::MOUSE_EVENT_DOUBLE_CLICK, button, x, y, mask); + } +} - // We use a custom user agent with viewer version and skin name. - LLViewerMediaImpl::updateBrowserUserAgent(); +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::scrollWheel(S32 x, S32 y, MASK mask) +{ + scaleMouse(&x, &y); + mLastMouseX = x; + mLastMouseY = y; + if (mMediaSource) + { + mMediaSource->scrollEvent(x, y, mask); + } } ////////////////////////////////////////////////////////////////////////////////////////// -// static -void LLViewerMedia::initClass() +void LLViewerMediaImpl::onMouseCaptureLost() { - // *TODO: This looks like a memory leak to me. JC - LLMediaManagerData* init_data = new LLMediaManagerData; - buildMediaManagerData( init_data ); - LLMediaManager::initClass( init_data ); - delete init_data; - - LLMediaManager* mm = LLMediaManager::getInstance(); - LLMIMETypes::mime_info_map_t::const_iterator it; - for (it = LLMIMETypes::sMap.begin(); it != LLMIMETypes::sMap.end(); ++it) - { - const std::string& mime_type = it->first; - const LLMIMETypes::LLMIMEInfo& info = it->second; - mm->addMimeTypeImplNameMap( mime_type, info.mImpl ); - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -// static -void LLViewerMedia::buildMediaManagerData( LLMediaManagerData* init_data ) -{ -// std::string executable_dir = std::string( arg0 ).substr( 0, std::string( arg0 ).find_last_of("\\/") ); -// std::string component_dir = std::string( executable_dir ).substr( 0, std::string( executable_dir ).find_last_of("\\/") ); -// component_dir = std::string( component_dir ).substr( 0, std::string( component_dir ).find_last_of("\\/") ); -// component_dir = std::string( component_dir ).substr( 0, std::string( component_dir ).find_last_of("\\/") ); -// component_dir += "\\newview\\app_settings\\mozilla"; - - -#if LL_DARWIN - // For Mac OS, we store both the shared libraries and the runtime files (chrome/, plugins/, etc) in - // Second Life.app/Contents/MacOS/. This matches the way Firefox is distributed on the Mac. - std::string component_dir(gDirUtilp->getExecutableDir()); -#elif LL_WINDOWS - std::string component_dir( gDirUtilp->getExpandedFilename( LL_PATH_APP_SETTINGS, "" ) ); - component_dir += gDirUtilp->getDirDelimiter(); - #ifdef LL_DEBUG - component_dir += "mozilla_debug"; - #else // LL_DEBUG - component_dir += "mozilla"; - #endif // LL_DEBUG -#elif LL_LINUX - std::string component_dir( gDirUtilp->getExpandedFilename( LL_PATH_APP_SETTINGS, "" ) ); - component_dir += gDirUtilp->getDirDelimiter(); - component_dir += "mozilla-runtime-linux-i686"; -#elif LL_SOLARIS - std::string component_dir( gDirUtilp->getExpandedFilename( LL_PATH_APP_SETTINGS, "" ) ); - component_dir += gDirUtilp->getDirDelimiter(); - #ifdef __sparc - component_dir += "mozilla-solaris-sparc"; - #else - component_dir += "mozilla-solaris-i686"; - #endif -#else - std::string component_dir( gDirUtilp->getExpandedFilename( LL_PATH_APP_SETTINGS, "" ) ); - component_dir += gDirUtilp->getDirDelimiter(); - component_dir += "mozilla"; -#endif + if (mMediaSource) + { + mMediaSource->mouseEvent(LLPluginClassMedia::MOUSE_EVENT_UP, 0, mLastMouseX, mLastMouseY, 0); + } +} - std::string application_dir = gDirUtilp->getExecutableDir(); +////////////////////////////////////////////////////////////////////////////////////////// +BOOL LLViewerMediaImpl::handleMouseUp(S32 x, S32 y, MASK mask) +{ + // NOTE: this is called when the mouse is released when we have capture. + // Due to the way mouse coordinates are mapped to the object, we can't use the x and y coordinates that come in with the event. + + if(hasMouseCapture()) + { + // Release the mouse -- this will also send a mouseup to the media + gFocusMgr.setMouseCapture( FALSE ); + } - init_data->setBrowserApplicationDir( application_dir ); - std::string profile_dir = gDirUtilp->getExpandedFilename( LL_PATH_MOZILLA_PROFILE, "" ); - init_data->setBrowserProfileDir( profile_dir ); - init_data->setBrowserComponentDir( component_dir ); - std::string profile_name("Second Life"); - init_data->setBrowserProfileName( profile_name ); - init_data->setBrowserParentWindow( gViewerWindow->getMediaWindow() ); + return TRUE; +} - // Users can change skins while client is running, so make sure - // we pick up on changes. - gSavedSettings.getControl("SkinCurrent")->getSignal()->connect( - boost::bind( LLViewerMediaImpl::handleSkinCurrentChanged, _2 ) ); +////////////////////////////////////////////////////////////////////////////////////////// +std::string LLViewerMediaImpl::getName() const +{ + if (mMediaSource) + { + return mMediaSource->getMediaName(); + } + + return LLStringUtil::null; +}; +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::navigateBack() +{ + if (mMediaSource) + { + mMediaSource->browse_back(); + } } ////////////////////////////////////////////////////////////////////////////////////////// -// static -void LLViewerMedia::cleanupClass() +void LLViewerMediaImpl::navigateForward() { - stop() ; - LLMediaManager::cleanupClass(); + if (mMediaSource) + { + mMediaSource->browse_forward(); + } } -// static -void LLViewerMedia::play(const std::string& media_url, - const std::string& mime_type, - const LLUUID& placeholder_texture_id, - S32 media_width, S32 media_height, U8 media_auto_scale, - U8 media_loop) +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::navigateReload() { - sViewerMediaImpl.play(media_url, mime_type, placeholder_texture_id, - media_width, media_height, media_auto_scale, media_loop); + navigateTo(getCurrentMediaURL(), "", true, false); } -// static -void LLViewerMedia::stop() +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::navigateHome() { - sViewerMediaImpl.stop(); + bool rediscover_mimetype = mHomeMimeType.empty(); + navigateTo(mHomeURL, mHomeMimeType, rediscover_mimetype, false); } -// static -void LLViewerMedia::pause() +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::unload() { - sViewerMediaImpl.pause(); + // Unload the media impl and clear its state. + destroyMediaSource(); + resetPreviousMediaState(); + mMediaURL.clear(); + mMimeType.clear(); + mCurrentMediaURL.clear(); + mCurrentMimeType.clear(); } -// static -void LLViewerMedia::start() +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::navigateTo(const std::string& url, const std::string& mime_type, bool rediscover_type, bool server_request) { - sViewerMediaImpl.start(); + cancelMimeTypeProbe(); + + if(mMediaURL != url) + { + // Don't carry media play state across distinct URLs. + resetPreviousMediaState(); + } + + // Always set the current URL and MIME type. + mMediaURL = url; + mMimeType = mime_type; + + // Clear the current media URL, since it will no longer be correct. + mCurrentMediaURL.clear(); + + // if mime type discovery was requested, we'll need to do it when the media loads + mNavigateRediscoverType = rediscover_type; + + // and if this was a server request, the navigate on load will also need to be one. + mNavigateServerRequest = server_request; + + // An explicit navigate resets the "failed" flag. + mMediaSourceFailed = false; + + if(mPriority == LLPluginClassMedia::PRIORITY_UNLOADED) + { + // Helpful to have media urls in log file. Shouldn't be spammy. + llinfos << "NOT LOADING media id= " << mTextureId << " url=" << url << " mime_type=" << mime_type << llendl; + + // This impl should not be loaded at this time. + LL_DEBUGS("PluginPriority") << this << "Not loading (PRIORITY_UNLOADED)" << LL_ENDL; + + return; + } + + navigateInternal(); } -// static -void LLViewerMedia::seek(F32 time) +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::navigateInternal() { - sViewerMediaImpl.seek(time); + // Helpful to have media urls in log file. Shouldn't be spammy. + llinfos << "media id= " << mTextureId << " url=" << mMediaURL << " mime_type=" << mMimeType << llendl; + + if(mNavigateSuspended) + { + llwarns << "Deferring navigate." << llendl; + mNavigateSuspendedDeferred = true; + return; + } + + if(mMimeTypeProbe != NULL) + { + llwarns << "MIME type probe already in progress -- bailing out." << llendl; + return; + } + + if(mNavigateServerRequest) + { + setNavState(MEDIANAVSTATE_SERVER_SENT); + } + else + { + setNavState(MEDIANAVSTATE_NONE); + } + + // If the caller has specified a non-empty MIME type, look that up in our MIME types list. + // If we have a plugin for that MIME type, use that instead of attempting auto-discovery. + // This helps in supporting legacy media content where the server the media resides on returns a bogus MIME type + // but the parcel owner has correctly set the MIME type in the parcel media settings. + + if(!mMimeType.empty() && (mMimeType != LLMIMETypes::getDefaultMimeType())) + { + std::string plugin_basename = LLMIMETypes::implType(mMimeType); + if(!plugin_basename.empty()) + { + // We have a plugin for this mime type + mNavigateRediscoverType = false; + } + } + + if(mNavigateRediscoverType) + { + + LLURI uri(mMediaURL); + std::string scheme = uri.scheme(); + + if(scheme.empty() || "http" == scheme || "https" == scheme) + { + // If we don't set an Accept header, LLHTTPClient will add one like this: + // Accept: application/llsd+xml + // which is really not what we want. + LLSD headers = LLSD::emptyMap(); + headers["Accept"] = "*/*"; + // Allow cookies in the response, to prevent a redirect loop when accessing join.secondlife.com + headers["Cookie"] = ""; + LLHTTPClient::getHeaderOnly( mMediaURL, new LLMimeDiscoveryResponder(this), headers, 10.0f); + } + else if("data" == scheme || "file" == scheme || "about" == scheme) + { + // FIXME: figure out how to really discover the type for these schemes + // We use "data" internally for a text/html url for loading the login screen + if(initializeMedia("text/html")) + { + loadURI(); + } + } + else + { + // This catches 'rtsp://' urls + if(initializeMedia(scheme)) + { + loadURI(); + } + } + } + else if(initializeMedia(mMimeType)) + { + loadURI(); + } + else + { + LL_WARNS("Media") << "Couldn't navigate to: " << mMediaURL << " as there is no media type for: " << mMimeType << LL_ENDL; + } } -// static -void LLViewerMedia::setVolume(F32 volume) +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::navigateStop() +{ + if(mMediaSource) + { + mMediaSource->browse_stop(); + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMediaImpl::handleKeyHere(KEY key, MASK mask) { - sViewerMediaImpl.setVolume(volume); + bool result = false; + + if (mMediaSource) + { + // FIXME: THIS IS SO WRONG. + // Menu keys should be handled by the menu system and not passed to UI elements, but this is how LLTextEditor and LLLineEditor do it... + if( MASK_CONTROL & mask ) + { + if( 'C' == key ) + { + mMediaSource->copy(); + result = true; + } + else + if( 'V' == key ) + { + mMediaSource->paste(); + result = true; + } + else + if( 'X' == key ) + { + mMediaSource->cut(); + result = true; + } + } + + if(!result) + { + + LLSD native_key_data = gViewerWindow->getWindow()->getNativeKeyData(); + + result = mMediaSource->keyEvent(LLPluginClassMedia::KEY_EVENT_DOWN ,key, mask, native_key_data); + // Since the viewer internal event dispatching doesn't give us key-up events, simulate one here. + (void)mMediaSource->keyEvent(LLPluginClassMedia::KEY_EVENT_UP ,key, mask, native_key_data); + } + } + + return result; } -// static -LLMediaBase::EStatus LLViewerMedia::getStatus() +////////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMediaImpl::handleUnicodeCharHere(llwchar uni_char) { - return sViewerMediaImpl.getStatus(); + bool result = false; + + if (mMediaSource) + { + // only accept 'printable' characters, sigh... + if (uni_char >= 32 // discard 'control' characters + && uni_char != 127) // SDL thinks this is 'delete' - yuck. + { + LLSD native_key_data = gViewerWindow->getWindow()->getNativeKeyData(); + + mMediaSource->textInput(wstring_to_utf8str(LLWString(1, uni_char)), gKeyboard->currentMask(FALSE), native_key_data); + } + } + + return result; } ////////////////////////////////////////////////////////////////////////////////////////// -// static -LLUUID LLViewerMedia::getMediaTextureID() +bool LLViewerMediaImpl::canNavigateForward() { - return sViewerMediaImpl.getMediaTextureID(); + BOOL result = FALSE; + if (mMediaSource) + { + result = mMediaSource->getHistoryForwardAvailable(); + } + return result; } ////////////////////////////////////////////////////////////////////////////////////////// -// static -bool LLViewerMedia::getMediaSize(S32 *media_width, S32 *media_height) +bool LLViewerMediaImpl::canNavigateBack() +{ + BOOL result = FALSE; + if (mMediaSource) + { + result = mMediaSource->getHistoryBackAvailable(); + } + return result; +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::update() +{ + if(mMediaSource == NULL) + { + if(mPriority == LLPluginClassMedia::PRIORITY_UNLOADED) + { + // This media source should not be loaded. + } + else if(mPriority <= LLPluginClassMedia::PRIORITY_SLIDESHOW) + { + // Don't load new instances that are at PRIORITY_SLIDESHOW or below. They're just kept around to preserve state. + } + else if(mMimeTypeProbe != NULL) + { + // this media source is doing a MIME type probe -- don't try loading it again. + } + else + { + // This media may need to be loaded. + if(sMediaCreateTimer.hasExpired()) + { + LL_DEBUGS("PluginPriority") << this << ": creating media based on timer expiration" << LL_ENDL; + createMediaSource(); + sMediaCreateTimer.setTimerExpirySec(LLVIEWERMEDIA_CREATE_DELAY); + } + else + { + LL_DEBUGS("PluginPriority") << this << ": NOT creating media (waiting on timer)" << LL_ENDL; + } + } + } + else + { + updateVolume(); + + // 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) + { + return; + } + + // Make sure a navigate doesn't happen during the idle -- it can cause mMediaSource to get destroyed, which can cause a crash. + setNavigateSuspended(true); + + mMediaSource->idle(); + + setNavigateSuspended(false); + + if(mMediaSource == NULL) + { + return; + } + + if(mMediaSource->isPluginExited()) + { + resetPreviousMediaState(); + destroyMediaSource(); + return; + } + + if(!mMediaSource->textureValid()) + { + return; + } + + if(mSuspendUpdates || !mVisible) + { + return; + } + + LLViewerMediaTexture* placeholder_image = updatePlaceholderImage(); + + if(placeholder_image) + { + LLRect dirty_rect; + + // Since we're updating this texture, we know it's playing. Tell the texture to do its replacement magic so it gets rendered. + placeholder_image->setPlaying(TRUE); + + if(mMediaSource->getDirty(&dirty_rect)) + { + // Constrain the dirty rect to be inside the texture + S32 x_pos = llmax(dirty_rect.mLeft, 0); + S32 y_pos = llmax(dirty_rect.mBottom, 0); + S32 width = llmin(dirty_rect.mRight, placeholder_image->getWidth()) - x_pos; + S32 height = llmin(dirty_rect.mTop, placeholder_image->getHeight()) - y_pos; + + if(width > 0 && height > 0) + { + + U8* data = mMediaSource->getBitsData(); + + // Offset the pixels pointer to match x_pos and y_pos + data += ( x_pos * mMediaSource->getTextureDepth() * mMediaSource->getBitsWidth() ); + data += ( y_pos * mMediaSource->getTextureDepth() ); + + placeholder_image->setSubImage( + data, + mMediaSource->getBitsWidth(), + mMediaSource->getBitsHeight(), + x_pos, + y_pos, + width, + height); + + } + + mMediaSource->resetDirty(); + } + } +} + + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::updateImagesMediaStreams() +{ +} + + +////////////////////////////////////////////////////////////////////////////////////////// +LLViewerMediaTexture* LLViewerMediaImpl::updatePlaceholderImage() +{ + if(mTextureId.isNull()) + { + // The code that created this instance will read from the plugin's bits. + return NULL; + } + + LLViewerMediaTexture* placeholder_image = LLViewerTextureManager::getMediaTexture( mTextureId ); + + if (mNeedsNewTexture + || placeholder_image->getUseMipMaps() + || (placeholder_image->getWidth() != mMediaSource->getTextureWidth()) + || (placeholder_image->getHeight() != mMediaSource->getTextureHeight()) + || (mTextureUsedWidth != mMediaSource->getWidth()) + || (mTextureUsedHeight != mMediaSource->getHeight()) + ) + { + LL_DEBUGS("Media") << "initializing media placeholder" << LL_ENDL; + LL_DEBUGS("Media") << "movie image id " << mTextureId << LL_ENDL; + + int texture_width = mMediaSource->getTextureWidth(); + int texture_height = mMediaSource->getTextureHeight(); + int texture_depth = mMediaSource->getTextureDepth(); + + // MEDIAOPT: check to see if size actually changed before doing work + placeholder_image->destroyGLTexture(); + // MEDIAOPT: apparently just calling setUseMipMaps(FALSE) doesn't work? + placeholder_image->reinit(FALSE); // probably not needed + + // MEDIAOPT: seems insane that we actually have to make an imageraw then + // immediately discard it + LLPointer<LLImageRaw> raw = new LLImageRaw(texture_width, texture_height, texture_depth); + // Clear the texture to the background color, ignoring alpha. + // convert background color channels from [0.0, 1.0] to [0, 255]; + raw->clear(int(mBackgroundColor.mV[VX] * 255.0f), int(mBackgroundColor.mV[VY] * 255.0f), int(mBackgroundColor.mV[VZ] * 255.0f), 0xff); + int discard_level = 0; + + // ask media source for correct GL image format constants + placeholder_image->setExplicitFormat(mMediaSource->getTextureFormatInternal(), + mMediaSource->getTextureFormatPrimary(), + mMediaSource->getTextureFormatType(), + mMediaSource->getTextureFormatSwapBytes()); + + placeholder_image->createGLTexture(discard_level, raw); + + // MEDIAOPT: set this dynamically on play/stop + // FIXME +// placeholder_image->mIsMediaTexture = true; + mNeedsNewTexture = false; + + // If the amount of the texture being drawn by the media goes down in either width or height, + // recreate the texture to avoid leaving parts of the old image behind. + mTextureUsedWidth = mMediaSource->getWidth(); + mTextureUsedHeight = mMediaSource->getHeight(); + } + + return placeholder_image; +} + + +////////////////////////////////////////////////////////////////////////////////////////// +LLUUID LLViewerMediaImpl::getMediaTextureID() const { - // make sure we're valid + return mTextureId; +} - if ( sViewerMediaImpl.mMediaSource != NULL ) +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::setVisible(bool visible) +{ + mVisible = visible; + + if(mVisible) + { + if(mMediaSource && mMediaSource->isPluginExited()) + { + destroyMediaSource(); + } + + if(!mMediaSource) + { + createMediaSource(); + } + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::mouseCapture() +{ + gFocusMgr.setMouseCapture(this); +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::scaleMouse(S32 *mouse_x, S32 *mouse_y) +{ +#if 0 + S32 media_width, media_height; + S32 texture_width, texture_height; + getMediaSize( &media_width, &media_height ); + getTextureSize( &texture_width, &texture_height ); + S32 y_delta = texture_height - media_height; + + *mouse_y -= y_delta; +#endif +} + + + +////////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMediaImpl::isMediaTimeBased() +{ + bool result = false; + + if(mMediaSource) + { + result = mMediaSource->pluginSupportsMediaTime(); + } + + return result; +} + +////////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMediaImpl::isMediaPlaying() +{ + bool result = false; + + if(mMediaSource) + { + EMediaStatus status = mMediaSource->getStatus(); + if(status == MEDIA_PLAYING || status == MEDIA_LOADING) + result = true; + } + + return result; +} +////////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMediaImpl::isMediaPaused() +{ + bool result = false; + + if(mMediaSource) + { + if(mMediaSource->getStatus() == MEDIA_PAUSED) + result = true; + } + + return result; +} + +////////////////////////////////////////////////////////////////////////////////////////// +// +bool LLViewerMediaImpl::hasMedia() const +{ + return mMediaSource != NULL; +} + +////////////////////////////////////////////////////////////////////////////////////////// +// +void LLViewerMediaImpl::resetPreviousMediaState() +{ + mPreviousMediaState = MEDIA_NONE; + mPreviousMediaTime = 0.0f; +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// +void LLViewerMediaImpl::setDisabled(bool disabled, bool forcePlayOnEnable) +{ + if(mIsDisabled != disabled) + { + // Only do this on actual state transitions. + mIsDisabled = disabled; + + if(mIsDisabled) + { + // We just disabled this media. Clear all state. + unload(); + } + else + { + // We just (re)enabled this media. Do a navigate if auto-play is in order. + if(isAutoPlayable() || forcePlayOnEnable) + { + navigateTo(mMediaEntryURL, "", true, true); + } + } + + } +}; + +////////////////////////////////////////////////////////////////////////////////////////// +// +bool LLViewerMediaImpl::isForcedUnloaded() const +{ + if(mIsMuted || mMediaSourceFailed || mIsDisabled) { - *media_width = sViewerMediaImpl.mMediaSource->getMediaWidth(); - *media_height = sViewerMediaImpl.mMediaSource->getMediaHeight(); return true; } + + if(sInWorldMediaDisabled) + { + // When inworld media is disabled, all instances that aren't marked as "used in UI" will not be loaded. + if(!mUsedInUI) + { + return true; + } + } + + // If this media's class is not supposed to be shown, unload + if (!shouldShowBasedOnClass()) + { + return true; + } + return false; } ////////////////////////////////////////////////////////////////////////////////////////// -// static -bool LLViewerMedia::getTextureSize(S32 *texture_width, S32 *texture_height) +// +bool LLViewerMediaImpl::isPlayable() const { - if ( sViewerMediaImpl.mMediaSource != NULL ) + if(isForcedUnloaded()) + { + // All of the forced-unloaded criteria also imply not playable. + return false; + } + + if(hasMedia()) { - S32 media_width = sViewerMediaImpl.mMediaSource->getMediaWidth(); - S32 media_height = sViewerMediaImpl.mMediaSource->getMediaHeight(); - *texture_width = LLMediaManager::textureWidthFromMediaWidth( media_width ); - *texture_height = LLMediaManager::textureHeightFromMediaHeight( media_height ); + // Anything that's already playing is, by definition, playable. return true; } + + if(!mMediaURL.empty()) + { + // If something has navigated the instance, it's ready to be played. + return true; + } + return false; } - ////////////////////////////////////////////////////////////////////////////////////////// -// static -void LLViewerMedia::updateImagesMediaStreams() +void LLViewerMediaImpl::handleMediaEvent(LLPluginClassMedia* plugin, LLPluginClassMediaOwner::EMediaEvent event) +{ + bool pass_through = true; + switch(event) + { + case MEDIA_EVENT_CLICK_LINK_NOFOLLOW: + { + LL_DEBUGS("Media") << "MEDIA_EVENT_CLICK_LINK_NOFOLLOW, uri is: " << plugin->getClickURL() << LL_ENDL; + std::string url = plugin->getClickURL(); + LLURLDispatcher::dispatch(url, NULL, mTrustedBrowser); + + } + break; + case MEDIA_EVENT_CLICK_LINK_HREF: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CLICK_LINK_HREF, target is \"" << plugin->getClickTarget() << "\", uri is " << plugin->getClickURL() << LL_ENDL; + }; + break; + case MEDIA_EVENT_PLUGIN_FAILED_LAUNCH: + { + // The plugin failed to load properly. Make sure the timer doesn't retry. + // TODO: maybe mark this plugin as not loadable somehow? + mMediaSourceFailed = true; + + // Reset the last known state of the media to defaults. + resetPreviousMediaState(); + + // TODO: may want a different message for this case? + LLSD args; + args["PLUGIN"] = LLMIMETypes::implType(mCurrentMimeType); + LLNotificationsUtil::add("MediaPluginFailed", args); + } + break; + + case MEDIA_EVENT_PLUGIN_FAILED: + { + // The plugin crashed. + mMediaSourceFailed = true; + + // Reset the last known state of the media to defaults. + resetPreviousMediaState(); + + LLSD args; + args["PLUGIN"] = LLMIMETypes::implType(mCurrentMimeType); + // SJB: This is getting called every frame if the plugin fails to load, continuously respawining the alert! + //LLNotificationsUtil::add("MediaPluginFailed", args); + } + break; + + case MEDIA_EVENT_CURSOR_CHANGED: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CURSOR_CHANGED, new cursor is " << plugin->getCursorName() << LL_ENDL; + + std::string cursor = plugin->getCursorName(); + + if(cursor == "arrow") + mLastSetCursor = UI_CURSOR_ARROW; + else if(cursor == "ibeam") + mLastSetCursor = UI_CURSOR_IBEAM; + else if(cursor == "splith") + mLastSetCursor = UI_CURSOR_SIZEWE; + else if(cursor == "splitv") + mLastSetCursor = UI_CURSOR_SIZENS; + else if(cursor == "hand") + mLastSetCursor = UI_CURSOR_HAND; + else // for anything else, default to the arrow + mLastSetCursor = UI_CURSOR_ARROW; + } + break; + + case LLViewerMediaObserver::MEDIA_EVENT_NAVIGATE_BEGIN: + { + LL_DEBUGS("Media") << "MEDIA_EVENT_NAVIGATE_BEGIN, uri is: " << plugin->getNavigateURI() << LL_ENDL; + + if(getNavState() == MEDIANAVSTATE_SERVER_SENT) + { + setNavState(MEDIANAVSTATE_SERVER_BEGUN); + } + else + { + setNavState(MEDIANAVSTATE_BEGUN); + } + } + break; + + case LLViewerMediaObserver::MEDIA_EVENT_NAVIGATE_COMPLETE: + { + LL_DEBUGS("Media") << "MEDIA_EVENT_NAVIGATE_COMPLETE, uri is: " << plugin->getNavigateURI() << LL_ENDL; + + std::string url = plugin->getNavigateURI(); + if(getNavState() == MEDIANAVSTATE_BEGUN) + { + if(mCurrentMediaURL == url) + { + // This is a navigate that takes us to the same url as the previous navigate. + setNavState(MEDIANAVSTATE_COMPLETE_BEFORE_LOCATION_CHANGED_SPURIOUS); + } + else + { + mCurrentMediaURL = url; + setNavState(MEDIANAVSTATE_COMPLETE_BEFORE_LOCATION_CHANGED); + } + } + else if(getNavState() == MEDIANAVSTATE_SERVER_BEGUN) + { + mCurrentMediaURL = url; + setNavState(MEDIANAVSTATE_SERVER_COMPLETE_BEFORE_LOCATION_CHANGED); + } + else + { + // all other cases need to leave the state alone. + } + } + break; + + case LLViewerMediaObserver::MEDIA_EVENT_LOCATION_CHANGED: + { + LL_DEBUGS("Media") << "MEDIA_EVENT_LOCATION_CHANGED, uri is: " << plugin->getLocation() << LL_ENDL; + + std::string url = plugin->getLocation(); + + if(getNavState() == MEDIANAVSTATE_BEGUN) + { + if(mCurrentMediaURL == url) + { + // This is a navigate that takes us to the same url as the previous navigate. + setNavState(MEDIANAVSTATE_FIRST_LOCATION_CHANGED_SPURIOUS); + } + else + { + mCurrentMediaURL = url; + setNavState(MEDIANAVSTATE_FIRST_LOCATION_CHANGED); + } + } + else if(getNavState() == MEDIANAVSTATE_SERVER_BEGUN) + { + mCurrentMediaURL = url; + setNavState(MEDIANAVSTATE_SERVER_FIRST_LOCATION_CHANGED); + } + else + { + // Don't track redirects. + setNavState(MEDIANAVSTATE_NONE); + } + } + break; + + case LLViewerMediaObserver::MEDIA_EVENT_PICK_FILE_REQUEST: + { + // Display a file picker + std::string response; + + LLFilePicker& picker = LLFilePicker::instance(); + if (!picker.getOpenFile(LLFilePicker::FFLOAD_ALL)) + { + // The user didn't pick a file -- the empty response string will indicate this. + } + + response = picker.getFirstFile(); + + plugin->sendPickFileResponse(response); + } + break; + + case LLViewerMediaObserver::MEDIA_EVENT_CLOSE_REQUEST: + { + std::string uuid = plugin->getClickUUID(); + + llinfos << "MEDIA_EVENT_CLOSE_REQUEST for uuid " << uuid << llendl; + + if(uuid.empty()) + { + // This close request is directed at this instance, let it fall through. + } + else + { + // This close request is directed at another instance + pass_through = false; + LLFloaterMediaBrowser::closeRequest(uuid); + } + } + break; + + case LLViewerMediaObserver::MEDIA_EVENT_GEOMETRY_CHANGE: + { + std::string uuid = plugin->getClickUUID(); + + llinfos << "MEDIA_EVENT_GEOMETRY_CHANGE for uuid " << uuid << llendl; + + if(uuid.empty()) + { + // This geometry change request is directed at this instance, let it fall through. + } + else + { + // This request is directed at another instance + pass_through = false; + LLFloaterMediaBrowser::geometryChanged(uuid, plugin->getGeometryX(), plugin->getGeometryY(), plugin->getGeometryWidth(), plugin->getGeometryHeight()); + } + } + break; + + default: + break; + } + + if(pass_through) + { + // Just chain the event to observers. + emitEvent(plugin, event); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// virtual +void LLViewerMediaImpl::handleCookieSet(LLPluginClassMedia* self, const std::string &cookie) { - sViewerMediaImpl.updateImagesMediaStreams(); + LLViewerMedia::getCookieStore()->setCookies(cookie); } -////////////////////////////////////////////////////////////////////////////////////////// -// static -bool LLViewerMedia::isMediaPlaying() + +//////////////////////////////////////////////////////////////////////////////// +// virtual +void +LLViewerMediaImpl::cut() { - LLMediaBase::EStatus status = sViewerMediaImpl.getStatus(); - return (status == LLMediaBase::STATUS_STARTED ); + if (mMediaSource) + mMediaSource->cut(); +} + +//////////////////////////////////////////////////////////////////////////////// +// virtual +BOOL +LLViewerMediaImpl::canCut() const +{ + if (mMediaSource) + return mMediaSource->canCut(); + else + return FALSE; } + +//////////////////////////////////////////////////////////////////////////////// +// virtual +void +LLViewerMediaImpl::copy() +{ + if (mMediaSource) + mMediaSource->copy(); +} + +//////////////////////////////////////////////////////////////////////////////// +// virtual +BOOL +LLViewerMediaImpl::canCopy() const +{ + if (mMediaSource) + return mMediaSource->canCopy(); + else + return FALSE; +} + +//////////////////////////////////////////////////////////////////////////////// +// virtual +void +LLViewerMediaImpl::paste() +{ + if (mMediaSource) + mMediaSource->paste(); +} + +//////////////////////////////////////////////////////////////////////////////// +// virtual +BOOL +LLViewerMediaImpl::canPaste() const +{ + if (mMediaSource) + return mMediaSource->canPaste(); + else + return FALSE; +} + +void LLViewerMediaImpl::setUpdated(BOOL updated) +{ + mIsUpdated = updated ; +} + +BOOL LLViewerMediaImpl::isUpdated() +{ + return mIsUpdated ; +} + +void LLViewerMediaImpl::calculateInterest() +{ + LLViewerMediaTexture* texture = LLViewerTextureManager::findMediaTexture( mTextureId ); + + if(texture != NULL) + { + mInterest = texture->getMaxVirtualSize(); + } + else + { + // This will be a relatively common case now, since it will always be true for unloaded media. + mInterest = 0.0f; + } + + // Calculate distance from the avatar, for use in the proximity calculation. + mProximityDistance = 0.0f; + mProximityCamera = 0.0f; + if(!mObjectList.empty()) + { + // Just use the first object in the list. We could go through the list and find the closest object, but this should work well enough. + std::list< LLVOVolume* >::iterator iter = mObjectList.begin() ; + LLVOVolume* objp = *iter ; + llassert_always(objp != NULL) ; + + // The distance calculation is invalid for HUD attachments -- leave both mProximityDistance and mProximityCamera at 0 for them. + if(!objp->isHUDAttachment()) + { + LLVector3d obj_global = objp->getPositionGlobal() ; + LLVector3d agent_global = gAgent.getPositionGlobal() ; + LLVector3d global_delta = agent_global - obj_global ; + mProximityDistance = global_delta.magVecSquared(); // use distance-squared because it's cheaper and sorts the same. + + LLVector3d camera_delta = gAgentCamera.getCameraPositionGlobal() - obj_global; + mProximityCamera = camera_delta.magVec(); + } + } + + if(mNeedsMuteCheck) + { + // Check all objects this instance is associated with, and those objects' owners, against the mute list + mIsMuted = false; + + std::list< LLVOVolume* >::iterator iter = mObjectList.begin() ; + for(; iter != mObjectList.end() ; ++iter) + { + LLVOVolume *obj = *iter; + llassert(obj); + if (!obj) continue; + if(LLMuteList::getInstance() && + LLMuteList::getInstance()->isMuted(obj->getID())) + { + mIsMuted = true; + } + else + { + // We won't have full permissions data for all objects. Attempt to mute objects when we can tell their owners are muted. + if (LLSelectMgr::getInstance()) + { + LLPermissions* obj_perm = LLSelectMgr::getInstance()->findObjectPermissions(obj); + if(obj_perm) + { + if(LLMuteList::getInstance() && + LLMuteList::getInstance()->isMuted(obj_perm->getOwner())) + mIsMuted = true; + } + } + } + } + + mNeedsMuteCheck = false; + } +} + +F64 LLViewerMediaImpl::getApproximateTextureInterest() +{ + F64 result = 0.0f; + + if(mMediaSource) + { + result = mMediaSource->getFullWidth(); + result *= mMediaSource->getFullHeight(); + } + else + { + // No media source is loaded -- all we have to go on is the texture size that has been set on the impl, if any. + result = mMediaWidth; + result *= mMediaHeight; + } + + return result; +} + +void LLViewerMediaImpl::setUsedInUI(bool used_in_ui) +{ + mUsedInUI = used_in_ui; + + // HACK: Force elements used in UI to load right away. + // This fixes some issues where UI code that uses the browser instance doesn't expect it to be unloaded. + if(mUsedInUI && (mPriority == LLPluginClassMedia::PRIORITY_UNLOADED)) + { + if(getVisible()) + { + setPriority(LLPluginClassMedia::PRIORITY_NORMAL); + } + else + { + setPriority(LLPluginClassMedia::PRIORITY_HIDDEN); + } + + createMediaSource(); + } +}; + +void LLViewerMediaImpl::setBackgroundColor(LLColor4 color) +{ + mBackgroundColor = color; + + if(mMediaSource) + { + mMediaSource->setBackgroundColor(mBackgroundColor); + } +}; + +F64 LLViewerMediaImpl::getCPUUsage() const +{ + F64 result = 0.0f; + + if(mMediaSource) + { + result = mMediaSource->getCPUUsage(); + } + + return result; +} + +void LLViewerMediaImpl::setPriority(LLPluginClassMedia::EPriority priority) +{ + if(mPriority != priority) + { + LL_DEBUGS("PluginPriority") + << "changing priority of media id " << mTextureId + << " from " << LLPluginClassMedia::priorityToString(mPriority) + << " to " << LLPluginClassMedia::priorityToString(priority) + << LL_ENDL; + } + + mPriority = priority; + + if(priority == LLPluginClassMedia::PRIORITY_UNLOADED) + { + if(mMediaSource) + { + // Need to unload the media source + + // First, save off previous media state + mPreviousMediaState = mMediaSource->getStatus(); + mPreviousMediaTime = mMediaSource->getCurrentTime(); + + destroyMediaSource(); + } + } + + if(mMediaSource) + { + mMediaSource->setPriority(mPriority); + } + + // NOTE: loading (or reloading) media sources whose priority has risen above PRIORITY_UNLOADED is done in update(). +} + +void LLViewerMediaImpl::setLowPrioritySizeLimit(int size) +{ + if(mMediaSource) + { + mMediaSource->setLowPrioritySizeLimit(size); + } +} + +void LLViewerMediaImpl::setNavState(EMediaNavState state) +{ + mMediaNavState = state; + + switch (state) + { + case MEDIANAVSTATE_NONE: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_NONE" << llendl; break; + case MEDIANAVSTATE_BEGUN: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_BEGUN" << llendl; break; + case MEDIANAVSTATE_FIRST_LOCATION_CHANGED: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_FIRST_LOCATION_CHANGED" << llendl; break; + case MEDIANAVSTATE_FIRST_LOCATION_CHANGED_SPURIOUS: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_FIRST_LOCATION_CHANGED_SPURIOUS" << llendl; break; + case MEDIANAVSTATE_COMPLETE_BEFORE_LOCATION_CHANGED: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_COMPLETE_BEFORE_LOCATION_CHANGED" << llendl; break; + case MEDIANAVSTATE_COMPLETE_BEFORE_LOCATION_CHANGED_SPURIOUS: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_COMPLETE_BEFORE_LOCATION_CHANGED_SPURIOUS" << llendl; break; + case MEDIANAVSTATE_SERVER_SENT: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_SERVER_SENT" << llendl; break; + case MEDIANAVSTATE_SERVER_BEGUN: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_SERVER_BEGUN" << llendl; break; + case MEDIANAVSTATE_SERVER_FIRST_LOCATION_CHANGED: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_SERVER_FIRST_LOCATION_CHANGED" << llendl; break; + case MEDIANAVSTATE_SERVER_COMPLETE_BEFORE_LOCATION_CHANGED: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_SERVER_COMPLETE_BEFORE_LOCATION_CHANGED" << llendl; break; + } +} + +void LLViewerMediaImpl::setNavigateSuspended(bool suspend) +{ + if(mNavigateSuspended != suspend) + { + mNavigateSuspended = suspend; + if(!suspend) + { + // We're coming out of suspend. If someone tried to do a navigate while suspended, do one now instead. + if(mNavigateSuspendedDeferred) + { + mNavigateSuspendedDeferred = false; + navigateInternal(); + } + } + } +} + +void LLViewerMediaImpl::cancelMimeTypeProbe() +{ + if(mMimeTypeProbe != NULL) + { + // There doesn't seem to be a way to actually cancel an outstanding request. + // Simulate it by telling the LLMimeDiscoveryResponder not to write back any results. + mMimeTypeProbe->cancelRequest(); + + // The above should already have set mMimeTypeProbe to NULL. + if(mMimeTypeProbe != NULL) + { + llerrs << "internal error: mMimeTypeProbe is not NULL after cancelling request." << llendl; + } + } +} + +void LLViewerMediaImpl::addObject(LLVOVolume* obj) +{ + std::list< LLVOVolume* >::iterator iter = mObjectList.begin() ; + for(; iter != mObjectList.end() ; ++iter) + { + if(*iter == obj) + { + return ; //already in the list. + } + } + + mObjectList.push_back(obj) ; + mNeedsMuteCheck = true; +} + +void LLViewerMediaImpl::removeObject(LLVOVolume* obj) +{ + mObjectList.remove(obj) ; + mNeedsMuteCheck = true; +} + +const std::list< LLVOVolume* >* LLViewerMediaImpl::getObjectList() const +{ + return &mObjectList ; +} + +LLVOVolume *LLViewerMediaImpl::getSomeObject() +{ + LLVOVolume *result = NULL; + + std::list< LLVOVolume* >::iterator iter = mObjectList.begin() ; + if(iter != mObjectList.end()) + { + result = *iter; + } + + return result; +} + +void LLViewerMediaImpl::setTextureID(LLUUID id) +{ + if(id != mTextureId) + { + if(mTextureId.notNull()) + { + // Remove this item's entry from the map + sViewerMediaTextureIDMap.erase(mTextureId); + } + + if(id.notNull()) + { + sViewerMediaTextureIDMap.insert(LLViewerMedia::impl_id_map::value_type(id, this)); + } + + mTextureId = id; + } +} + ////////////////////////////////////////////////////////////////////////////////////////// -// static -bool LLViewerMedia::isMediaPaused() +// +bool LLViewerMediaImpl::isAutoPlayable() const { - LLMediaBase::EStatus status = sViewerMediaImpl.getStatus(); - return (status == LLMediaBase::STATUS_PAUSED); + return (mMediaAutoPlay && + gSavedSettings.getBOOL(LLViewerMedia::AUTO_PLAY_MEDIA_SETTING) && + gSavedSettings.getBOOL("MediaTentativeAutoPlay")); } + ////////////////////////////////////////////////////////////////////////////////////////// -// static -bool LLViewerMedia::hasMedia() +// +bool LLViewerMediaImpl::shouldShowBasedOnClass() const { - return sViewerMediaImpl.mMediaSource != NULL; + // If this is parcel media or in the UI, return true always + if (getUsedInUI() || isParcelMedia()) return true; + + bool attached_to_another_avatar = isAttachedToAnotherAvatar(); + bool inside_parcel = isInAgentParcel(); + + // llinfos << " hasFocus = " << hasFocus() << + // " others = " << (attached_to_another_avatar && gSavedSettings.getBOOL(LLViewerMedia::SHOW_MEDIA_ON_OTHERS_SETTING)) << + // " within = " << (inside_parcel && gSavedSettings.getBOOL(LLViewerMedia::SHOW_MEDIA_WITHIN_PARCEL_SETTING)) << + // " outside = " << (!inside_parcel && gSavedSettings.getBOOL(LLViewerMedia::SHOW_MEDIA_OUTSIDE_PARCEL_SETTING)) << llendl; + + // If it has focus, we should show it + // This is incorrect, and causes EXT-6750 (disabled attachment media still plays) +// if (hasFocus()) +// return true; + + // If it is attached to an avatar and the pref is off, we shouldn't show it + if (attached_to_another_avatar) + return gSavedSettings.getBOOL(LLViewerMedia::SHOW_MEDIA_ON_OTHERS_SETTING); + + if (inside_parcel) + return gSavedSettings.getBOOL(LLViewerMedia::SHOW_MEDIA_WITHIN_PARCEL_SETTING); + else + return gSavedSettings.getBOOL(LLViewerMedia::SHOW_MEDIA_OUTSIDE_PARCEL_SETTING); } ////////////////////////////////////////////////////////////////////////////////////////// -//static -bool LLViewerMedia::isActiveMediaTexture(const LLUUID& id) +// +bool LLViewerMediaImpl::isAttachedToAnotherAvatar() const { - return (id.notNull() - && id == getMediaTextureID() - && isMediaPlaying()); + bool result = false; + + std::list< LLVOVolume* >::const_iterator iter = mObjectList.begin(); + std::list< LLVOVolume* >::const_iterator end = mObjectList.end(); + for ( ; iter != end; iter++) + { + if (isObjectAttachedToAnotherAvatar(*iter)) + { + result = true; + break; + } + } + return result; } ////////////////////////////////////////////////////////////////////////////////////////// -// static -std::string LLViewerMedia::getMediaURL() +// +//static +bool LLViewerMediaImpl::isObjectAttachedToAnotherAvatar(LLVOVolume *obj) { - return sViewerMediaImpl.mMediaURL; + bool result = false; + LLXform *xform = obj; + // Walk up parent chain + while (NULL != xform) + { + LLViewerObject *object = dynamic_cast<LLViewerObject*> (xform); + if (NULL != object) + { + LLVOAvatar *avatar = object->asAvatar(); + if ((NULL != avatar) && (avatar != gAgentAvatarp)) + { + result = true; + break; + } + } + xform = xform->getParent(); + } + return result; } + ////////////////////////////////////////////////////////////////////////////////////////// -// static -std::string LLViewerMedia::getMimeType() +// +bool LLViewerMediaImpl::isInAgentParcel() const { - return sViewerMediaImpl.mMimeType; + bool result = false; + + std::list< LLVOVolume* >::const_iterator iter = mObjectList.begin(); + std::list< LLVOVolume* >::const_iterator end = mObjectList.end(); + for ( ; iter != end; iter++) + { + LLVOVolume *object = *iter; + if (LLViewerMediaImpl::isObjectInAgentParcel(object)) + { + result = true; + break; + } + } + return result; } + ////////////////////////////////////////////////////////////////////////////////////////// +// // static -void LLViewerMedia::setMimeType(std::string mime_type) +bool LLViewerMediaImpl::isObjectInAgentParcel(LLVOVolume *obj) { - sViewerMediaImpl.mMimeType = mime_type; + return (LLViewerParcelMgr::getInstance()->inAgentParcel(obj->getPositionGlobal())); } |