/**
 * @file llviewermedia.cpp
 * @brief Client interface to the media engine
 *
 * $LicenseInfo:firstyear=2007&license=viewerlgpl$
 * Second Life Viewer Source Code
 * 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.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include "llviewerprecompiledheaders.h"

#include "llviewermedia.h"

#include "llagent.h"
#include "llagentcamera.h"
#include "llappviewer.h"
#include "llaudioengine.h"  // for gAudiop
#include "llcallbacklist.h"
#include "lldir.h"
#include "lldiriterator.h"
#include "llevent.h"        // LLSimpleListener
#include "llfilepicker.h"
#include "llfloaterwebcontent.h"    // for handling window close requests and geometry change requests in media browser windows.
#include "llfocusmgr.h"
#include "llimagegl.h"
#include "llkeyboard.h"
#include "lllogininstance.h"
#include "llmarketplacefunctions.h"
#include "llmediaentry.h"
#include "llmimetypes.h"
#include "llmutelist.h"
#include "llnotifications.h"
#include "llnotificationsutil.h"
#include "llavataractions.h"
#include "llparcel.h"
#include "llpluginclassmedia.h"
#include "llurldispatcher.h"
#include "lluuid.h"
#include "llversioninfo.h"
#include "llviewermediafocus.h"
#include "llviewercontrol.h"
#include "llviewermenufile.h" // LLFilePickerThread
#include "llviewernetwork.h"
#include "llviewerparcelaskplay.h"
#include "llviewerparcelmedia.h"
#include "llviewerparcelmgr.h"
#include "llviewerregion.h"
#include "llviewertexture.h"
#include "llviewertexturelist.h"
#include "llviewerwindow.h"
#include "llvoavatar.h"
#include "llvoavatarself.h"
#include "llvovolume.h"
#include "llfloaterreg.h"
#include "llwebprofile.h"
#include "llwindow.h"
#include "llvieweraudio.h"
#include "llcorehttputil.h"

#include "llfloaterwebcontent.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>

extern BOOL gCubeSnapshot;

// *TODO: Consider enabling mipmaps (they have been disabled for a long time). Likely has a significant performance impact for tiled/high texture repeat media. Mip generation in a shader may also be an option if necessary.
constexpr BOOL USE_MIPMAPS = FALSE;

void init_threaded_picker_load_dialog(LLPluginClassMedia* plugin, LLFilePicker::ELoadFilter filter, bool get_multiple)
{
    (new LLMediaFilePicker(plugin, filter, get_multiple))->getFile(); // will delete itself
}

///////////////////////////////////////////////////////////////////////////////

// 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 );
    }
}


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 bool sForceUpdate = false;
static LLUUID sOnlyAudibleTextureID = LLUUID::null;
static F64 sLowestLoadableImplInterest = 0.0f;

//////////////////////////////////////////////////////////////////////////////////////////
static void add_media_impl(LLViewerMediaImpl* media)
{
    sViewerMediaImplList.push_back(media);
}

//////////////////////////////////////////////////////////////////////////////////////////
static void remove_media_impl(LLViewerMediaImpl* media)
{
    LLViewerMedia::impl_list::iterator iter = sViewerMediaImplList.begin();
    LLViewerMedia::impl_list::iterator end = sViewerMediaImplList.end();

    for(; iter != end; iter++)
    {
        if(media == *iter)
        {
            sViewerMediaImplList.erase(iter);
            return;
        }
    }
}

class LLViewerMediaMuteListObserver : public LLMuteListObserver
{
    /* virtual */ void onChange()  { LLViewerMedia::getInstance()->muteListChanged();}
};

static LLViewerMediaMuteListObserver sViewerMediaMuteListObserver;
static bool sViewerMediaMuteListObserverInitialized = false;


//////////////////////////////////////////////////////////////////////////////////////////
// LLViewerMedia
//////////////////////////////////////////////////////////////////////////////////////////

/*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";

LLViewerMedia::LLViewerMedia():
mAnyMediaShowing(false),
mAnyMediaPlaying(false),
mSpareBrowserMediaSource(NULL)
{
}

LLViewerMedia::~LLViewerMedia()
{
    gIdleCallbacks.deleteFunction(LLViewerMedia::onIdle, NULL);
    mTeleportFinishConnection.disconnect();
    if (mSpareBrowserMediaSource != NULL)
    {
        delete mSpareBrowserMediaSource;
        mSpareBrowserMediaSource = NULL;
    }
}

// static
void LLViewerMedia::initSingleton()
{
    gIdleCallbacks.addFunction(LLViewerMedia::onIdle, NULL);
    mTeleportFinishConnection = LLViewerParcelMgr::getInstance()->
        setTeleportFinishedCallback(boost::bind(&LLViewerMedia::onTeleportFinished, this));
}

//////////////////////////////////////////////////////////////////////////////////////////
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;
    }

    return media_impl;
}

viewer_media_t LLViewerMedia::updateMediaImpl(LLMediaEntry* media_entry, const std::string& previous_url, bool update_from_self)
{
    llassert(!gCubeSnapshot);
    // Try to find media with the same media ID
    viewer_media_t media_impl = getMediaImplFromTextureID(media_entry->getMediaID());

    LL_DEBUGS() << "called, current URL is \"" << media_entry->getCurrentURL()
            << "\", previous URL is \"" << previous_url
            << "\", update_from_self is " << (update_from_self?"true":"false")
            << LL_ENDL;

    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();

                LL_DEBUGS() << "Unloading media instance (new current URL is empty)." << LL_ENDL;
            }
        }
        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;
            }

            LL_DEBUGS() << "was_loaded is " << (was_loaded?"true":"false")
                    << ", auto_play is " << (auto_play?"true":"false")
                    << ", needs_navigate is " << (needs_navigate?"true":"false") << LL_ENDL;
        }
    }
    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);
            LL_DEBUGS() << "navigating to URL " << media_impl->mMediaEntryURL << LL_ENDL;
        }
        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;

            // 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;

            LL_DEBUGS() << "updating URL in the media impl to " << media_impl->mMediaEntryURL << LL_ENDL;
        }
    }

    return media_impl;
}

//////////////////////////////////////////////////////////////////////////////////////////
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())
    {
        result = iter->second;
    }

    return result;
}

//////////////////////////////////////////////////////////////////////////////////////////
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 = LLVersionInfo::instance().getChannel();

    // 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::instance().getVersion();
    codec << " (" << channel << "; " << skin_name << " skin)";
    LL_INFOS() << codec.str() << LL_ENDL;

    return codec.str();
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMedia::updateBrowserUserAgent()
{
    std::string user_agent = getCurrentUserAgent();

    impl_list::iterator iter = sViewerMediaImplList.begin();
    impl_list::iterator end = sViewerMediaImplList.end();

    for(; iter != end; iter++)
    {
        LLViewerMediaImpl* pimpl = *iter;
        if(pimpl->mMediaSource && pimpl->mMediaSource->pluginSupportsMediaBrowser())
        {
            pimpl->mMediaSource->setBrowserUserAgent(user_agent);
        }
    }

}

//////////////////////////////////////////////////////////////////////////////////////////
bool LLViewerMedia::handleSkinCurrentChanged(const LLSD& /*newvalue*/)
{
    // gSavedSettings is already updated when this function is called.
    updateBrowserUserAgent();
    return true;
}

//////////////////////////////////////////////////////////////////////////////////////////
bool LLViewerMedia::textureHasMedia(const LLUUID& texture_id)
{
    impl_list::iterator iter = sViewerMediaImplList.begin();
    impl_list::iterator end = sViewerMediaImplList.end();

    for(; iter != end; iter++)
    {
        LLViewerMediaImpl* pimpl = *iter;
        if(pimpl->getMediaTextureID() == texture_id)
        {
            return true;
        }
    }
    return false;
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMedia::setVolume(F32 volume)
{
    if(volume != sGlobalVolume || sForceUpdate)
    {
        sGlobalVolume = volume;
        impl_list::iterator iter = sViewerMediaImplList.begin();
        impl_list::iterator end = sViewerMediaImplList.end();

        for(; iter != end; iter++)
        {
            LLViewerMediaImpl* pimpl = *iter;
            pimpl->updateVolume();
        }

        sForceUpdate = false;
    }
}

//////////////////////////////////////////////////////////////////////////////////////////
F32 LLViewerMedia::getVolume()
{
    return sGlobalVolume;
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMedia::muteListChanged()
{
    // 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++)
    {
        LLViewerMediaImpl* pimpl = *iter;
        pimpl->mNeedsMuteCheck = true;
    }
}

//////////////////////////////////////////////////////////////////////////////////////////
bool LLViewerMedia::isInterestingEnough(const LLVOVolume *object, const F64 &object_interest)
{
    bool result = false;

    if (NULL == object)
    {
        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
    {
        LL_DEBUGS() << "object interest = " << object_interest << ", lowest loadable = " << sLowestLoadableImplInterest << LL_ENDL;
        if(object_interest >= sLowestLoadableImplInterest)
            result = true;
    }

    return result;
}

LLViewerMedia::impl_list &LLViewerMedia::getPriorityList()
{
    return sViewerMediaImplList;
}

// static
// 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())
    {
        // 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());
    }
}

static bool proximity_comparitor(const LLViewerMediaImpl* i1, const LLViewerMediaImpl* i2)
{
    if(i1->getProximityDistance() < i2->getProximityDistance())
    {
        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);
    }
}

static LLTrace::BlockTimerStatHandle FTM_MEDIA_UPDATE("Update Media");
static LLTrace::BlockTimerStatHandle FTM_MEDIA_SPARE_IDLE("Spare Idle");
static LLTrace::BlockTimerStatHandle FTM_MEDIA_UPDATE_INTEREST("Update/Interest");
static LLTrace::BlockTimerStatHandle FTM_MEDIA_UPDATE_VOLUME("Update/Volume");
static LLTrace::BlockTimerStatHandle FTM_MEDIA_SORT("Media Sort");
static LLTrace::BlockTimerStatHandle FTM_MEDIA_SORT2("Media Sort 2");
static LLTrace::BlockTimerStatHandle FTM_MEDIA_MISC("Misc");


//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMedia::onIdle(void *dummy_arg)
{
    LLViewerMedia::getInstance()->updateMedia(dummy_arg);
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMedia::updateMedia(void *dummy_arg)
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_MEDIA; //LL_RECORD_BLOCK_TIME(FTM_MEDIA_UPDATE);

    llassert(!gCubeSnapshot);

    // Enable/disable the plugin read thread
    LLPluginProcessParent::setUseReadThread(gSavedSettings.getBOOL("PluginUseReadThread"));

    // SL-16418 We can't call LLViewerMediaImpl->update() if we are in the state of shutting down.
    if(LLApp::isExiting())
    {
        setAllMediaEnabled(false);
        return;
    }

    // HACK: we always try to keep a spare running webkit plugin around to improve launch times.
    // 2017-04-19 Removed CP - this doesn't appear to buy us much and consumes a lot of resources so
    // removing it for now.
    //createSpareBrowserMediaSource();

    mAnyMediaShowing = false;
    mAnyMediaPlaying = false;

    impl_list::iterator iter = sViewerMediaImplList.begin();
    impl_list::iterator end = sViewerMediaImplList.end();

    {
        LL_PROFILE_ZONE_NAMED_CATEGORY_MEDIA("media update interest"); //LL_RECORD_BLOCK_TIME(FTM_MEDIA_UPDATE_INTEREST);
        for(; iter != end;)
        {
            LLViewerMediaImpl* pimpl = *iter++;
            pimpl->update();
            pimpl->calculateInterest();
        }
    }

    // Let the spare media source actually launch
    if(mSpareBrowserMediaSource)
    {
        LL_PROFILE_ZONE_NAMED_CATEGORY_MEDIA("media spare idle"); //LL_RECORD_BLOCK_TIME(FTM_MEDIA_SPARE_IDLE);
        mSpareBrowserMediaSource->idle();
    }

    {
        LL_PROFILE_ZONE_NAMED_CATEGORY_MEDIA("media sort"); //LL_RECORD_BLOCK_TIME(FTM_MEDIA_SORT);
        // 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;

    static LLCachedControl<bool> inworld_media_enabled(gSavedSettings, "AudioStreamingMedia", true);
    static LLCachedControl<bool> inworld_audio_enabled(gSavedSettings, "AudioStreamingMusic", true);
    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.

    {
        LL_PROFILE_ZONE_NAMED_CATEGORY_MEDIA("media misc"); //LL_RECORD_BLOCK_TIME(FTM_MEDIA_MISC);
        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 = (F32) sqrt(pimpl->getInterest());

                        pimpl->setLowPrioritySizeLimit(ll_round(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
            static bool restore_parcel_audio = false;
            if( !inworld_audio_enabled)
            {
                if(LLViewerMedia::isParcelAudioPlaying() && gAudiop && LLViewerMedia::hasParcelAudio())
                {
                    LLViewerAudio::getInstance()->stopInternetStreamWithAutoFade();
                    restore_parcel_audio = true;
                }
            }
            else
            {
                if(gAudiop && LLViewerMedia::hasParcelAudio() && restore_parcel_audio && gSavedSettings.getBOOL("MediaTentativeAutoPlay"))
                {
                    LLViewerAudio::getInstance()->startInternetStreamWithAutoFade(LLViewerMedia::getParcelAudioURL());
                    restore_parcel_audio = false;
                }
            }

            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())
            {
                mAnyMediaShowing = true;
            }

            if (!pimpl->getUsedInUI() && pimpl->hasMedia() && (pimpl->isMediaPlaying() || !pimpl->isMediaTimeBased()))
            {
                // consider visible non-timebased media as playing
                mAnyMediaPlaying = 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))
    {
        // 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
    {
        LL_PROFILE_ZONE_NAMED_CATEGORY_MEDIA("media sort2"); // LL_RECORD_BLOCK_TIME(FTM_MEDIA_SORT2);
        // 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 << LL_ENDL;

}

//////////////////////////////////////////////////////////////////////////////////////////
bool LLViewerMedia::isAnyMediaShowing()
{
    return mAnyMediaShowing;
}

//////////////////////////////////////////////////////////////////////////////////////////
bool LLViewerMedia::isAnyMediaPlaying()
{
    return mAnyMediaPlaying;
}

//////////////////////////////////////////////////////////////////////////////////////////
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::getInstance()->play(LLViewerParcelMgr::getInstance()->getAgentParcel());
        }

        static LLCachedControl<bool> audio_streaming_music(gSavedSettings, "AudioStreamingMusic", true);
        if (audio_streaming_music &&
            !LLViewerMedia::isParcelAudioPlaying() &&
            gAudiop &&
            LLViewerMedia::hasParcelAudio())
        {
            if (LLAudioEngine::AUDIO_PAUSED == gAudiop->isInternetStreamPlaying())
            {
                // 'false' means unpause
                gAudiop->pauseInternetStream(false);
            }
            else
            {
                LLViewerAudio::getInstance()->startInternetStreamWithAutoFade(LLViewerMedia::getParcelAudioURL());
            }
        }
    }
    else {
        // This actually unloads the impl, as opposed to "stop"ping the media
        LLViewerParcelMedia::getInstance()->stop();
        if (gAudiop)
        {
            LLViewerAudio::getInstance()->stopInternetStreamWithAutoFade();
        }
    }
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMedia::setAllMediaPaused(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())
        {
            // upause/pause time based media, enable/disable any other
            if (!val)
            {
                pimpl->setDisabled(val);
                if (pimpl->isMediaTimeBased() && pimpl->isMediaPaused())
                {
                    pimpl->play();
                }
            }
            else if (pimpl->isMediaTimeBased() && pimpl->mMediaSource && (pimpl->isMediaPlaying() || pimpl->isMediaPaused()))
            {
                pimpl->pause();
            }
            else
            {
                pimpl->setDisabled(val);
            }
        }
    }

    LLParcel *agent_parcel = LLViewerParcelMgr::getInstance()->getAgentParcel();

    // Also do Parcel Media and Parcel Audio
    if (!val)
    {
        if (!LLViewerMedia::isParcelMediaPlaying() && LLViewerMedia::hasParcelMedia())
        {
            LLViewerParcelMedia::getInstance()->play(agent_parcel);
        }

        static LLCachedControl<bool> audio_streaming_music(gSavedSettings, "AudioStreamingMusic", true);
        if (audio_streaming_music &&
            !LLViewerMedia::isParcelAudioPlaying() &&
            gAudiop &&
            LLViewerMedia::hasParcelAudio())
        {
            if (LLAudioEngine::AUDIO_PAUSED == gAudiop->isInternetStreamPlaying())
            {
                // 'false' means unpause
                gAudiop->pauseInternetStream(false);
            }
            else
            {
                LLViewerAudio::getInstance()->startInternetStreamWithAutoFade(LLViewerMedia::getParcelAudioURL());
            }
        }
    }
    else {
        // This actually unloads the impl, as opposed to "stop"ping the media
        LLViewerParcelMedia::getInstance()->stop();
        if (gAudiop)
        {
            LLViewerAudio::getInstance()->stopInternetStreamWithAutoFade();
        }
    }

    // remove play choice for current parcel
    if (agent_parcel && gAgent.getRegion())
    {
        LLViewerParcelAskPlay::getInstance()->resetSetting(gAgent.getRegion()->getRegionID(), agent_parcel->getLocalID());
    }
}

//////////////////////////////////////////////////////////////////////////////////////////
bool LLViewerMedia::isParcelMediaPlaying()
{
    viewer_media_t media = LLViewerParcelMedia::getInstance()->getParcelMedia();
    return (LLViewerMedia::hasParcelMedia() && media && media->hasMedia());
}

/////////////////////////////////////////////////////////////////////////////////////////
bool LLViewerMedia::isParcelAudioPlaying()
{
    return (LLViewerMedia::hasParcelAudio() && gAudiop && LLAudioEngine::AUDIO_PLAYING == gAudiop->isInternetStreamPlaying());
}

/////////////////////////////////////////////////////////////////////////////////////////
// static
void LLViewerMedia::authSubmitCallback(const LLSD& notification, const LLSD& response)
{
    LLViewerMedia::getInstance()->onAuthSubmit(notification, response);
}

void LLViewerMedia::onAuthSubmit(const LLSD& notification, const LLSD& response)
{
    LLViewerMediaImpl *impl = LLViewerMedia::getMediaImplFromTextureID(notification["payload"]["media_id"]);
    if(impl)
    {
        LLPluginClassMedia* media = impl->getMediaPlugin();
        if(media)
        {
            if (response["ok"])
            {
                media->sendAuthResponse(true, response["username"], response["password"]);
            }
            else
            {
                media->sendAuthResponse(false, "", "");
            }
        }
    }
}

/////////////////////////////////////////////////////////////////////////////////////////
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();
        }
    }
}

/////////////////////////////////////////////////////////////////////////////////////////
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();
    }
}

/////////////////////////////////////////////////////////////////////////////////////////
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->cookies_enabled(enabled);
        }
    }
}

/////////////////////////////////////////////////////////////////////////////////////////
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);
        }
    }
}

/////////////////////////////////////////////////////////////////////////////////////////
LLSD LLViewerMedia::getHeaders()
{
    LLSD headers = LLSD::emptyMap();
    headers[HTTP_OUT_HEADER_ACCEPT] = "*/*";
    // *TODO: Should this be 'application/llsd+xml' ?
    // *TODO: Should this even be set at all?   This header is only not overridden in 'GET' methods.
    headers[HTTP_OUT_HEADER_CONTENT_TYPE] = HTTP_CONTENT_XML;
    headers[HTTP_OUT_HEADER_COOKIE] = mOpenIDCookie;
    headers[HTTP_OUT_HEADER_USER_AGENT] = getCurrentUserAgent();

    return headers;
}

 /////////////////////////////////////////////////////////////////////////////////////////
bool LLViewerMedia::parseRawCookie(const std::string raw_cookie, std::string& name, std::string& value, std::string& path, bool& httponly, bool& secure)
{
    std::size_t name_pos = raw_cookie.find_first_of("=");
    if (name_pos != std::string::npos)
    {
        name = raw_cookie.substr(0, name_pos);
        std::size_t value_pos = raw_cookie.find_first_of(";", name_pos);
        if (value_pos != std::string::npos)
        {
            value = raw_cookie.substr(name_pos + 1, value_pos - name_pos - 1);
            path = "/"; // assume root path for now

            httponly = true;    // hard coded for now
            secure = true;

            return true;
        }
    }

    return false;
}

/////////////////////////////////////////////////////////////////////////////////////////
LLCore::HttpHeaders::ptr_t LLViewerMedia::getHttpHeaders()
{
    LLCore::HttpHeaders::ptr_t headers(new LLCore::HttpHeaders);

    headers->append(HTTP_OUT_HEADER_ACCEPT, "*/*");
    headers->append(HTTP_OUT_HEADER_CONTENT_TYPE, HTTP_CONTENT_XML);
    headers->append(HTTP_OUT_HEADER_COOKIE, mOpenIDCookie);
    headers->append(HTTP_OUT_HEADER_USER_AGENT, getCurrentUserAgent());

    return headers;
}


/////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMedia::setOpenIDCookie(const std::string& url)
{
    if(!gNonInteractive && !mOpenIDCookie.empty())
    {
        std::string profileUrl = getProfileURL("");

        LLCoros::instance().launch("LLViewerMedia::getOpenIDCookieCoro",
            boost::bind(&LLViewerMedia::getOpenIDCookieCoro, profileUrl));
    }
}

//static
void LLViewerMedia::getOpenIDCookieCoro(std::string url)
{
    LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
    LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
        httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getOpenIDCookieCoro", httpPolicy));
    LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);
    LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions);
    LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders);

    httpOpts->setFollowRedirects(true);
    httpOpts->setWantHeaders(true);
    httpOpts->setSSLVerifyPeer(false); // viewer's cert bundle doesn't appear to agree with web certs from "https://my.secondlife.com/"

    LLURL hostUrl(url.c_str());
    std::string hostAuth = hostUrl.getAuthority();

    // *TODO: Expand LLURL to split and extract this information better.
    // The structure of a URL is well defined and needing to retrieve parts of it are common.
    // original comment:
    // 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 = getInstance()->mOpenIDURL.mAuthority;
    std::string::size_type hostStart = authority.find('@');
    if (hostStart == std::string::npos)
    {   // no username/password
        hostStart = 0;
    }
    else
    {   // Hostname starts after the @.
            // 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.)
        ++hostStart;
    }
    std::string::size_type hostEnd = authority.rfind(':');
    if ((hostEnd == std::string::npos) || (hostEnd < hostStart))
    {   // no port
        hostEnd = authority.size();
    }

    LLViewerMedia* inst = getInstance();
    if (url.length())
    {
        LLMediaCtrl* media_instance = LLFloaterReg::getInstance("destinations")->getChild<LLMediaCtrl>("destination_guide_contents");
        if (media_instance)
        {
            std::string cookie_host = authority.substr(hostStart, hostEnd - hostStart);
            std::string cookie_name = "";
            std::string cookie_value = "";
            std::string cookie_path = "";
            bool httponly = true;
            bool secure = true;
            if (inst->parseRawCookie(inst->mOpenIDCookie, cookie_name, cookie_value, cookie_path, httponly, secure) &&
                media_instance->getMediaPlugin())
            {
                // MAINT-5711 - inexplicably, the CEF setCookie function will no longer set the cookie if the
                // url and domain are not the same. This used to be my.sl.com and id.sl.com respectively and worked.
                // For now, we use the URL for the OpenID POST request since it will have the same authority
                // as the domain field.
                // (Feels like there must be a less dirty way to construct a URL from component LLURL parts)
                // MAINT-6392 - Rider: Do not change, however, the original URI requested, since it is used further
                // down.
                std::string cefUrl(std::string(inst->mOpenIDURL.mURI) + "://" + std::string(inst->mOpenIDURL.mAuthority));

                media_instance->getMediaPlugin()->setCookie(cefUrl, cookie_name, cookie_value, cookie_host,
                    cookie_path, httponly, secure);

                // Now that we have parsed the raw cookie, we must store it so that each new media instance
                // can also get a copy and faciliate logging into internal SL sites.
                media_instance->getMediaPlugin()->storeOpenIDCookie(cefUrl, cookie_name, cookie_value,
                    cookie_host, cookie_path, httponly, secure);
            }
        }
    }

    // Note: Rider: MAINT-6392 - Some viewer code requires access to the my.sl.com openid cookie for such
    // actions as posting snapshots to the feed.  This is handled through HTTPCore rather than CEF and so
    // we must learn to SHARE the cookies.

    // Do a web profile get so we can store the cookie
    httpHeaders->append(HTTP_OUT_HEADER_ACCEPT, "*/*");
    httpHeaders->append(HTTP_OUT_HEADER_COOKIE, inst->mOpenIDCookie);
    httpHeaders->append(HTTP_OUT_HEADER_USER_AGENT, inst->getCurrentUserAgent());

    LL_DEBUGS("MediaAuth") << "Requesting " << url << LL_ENDL;
    LL_DEBUGS("MediaAuth") << "sOpenIDCookie = [" << inst->mOpenIDCookie << "]" << LL_ENDL;

    LLSD result = httpAdapter->getRawAndSuspend(httpRequest, url, httpOpts, httpHeaders);

    LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
    LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);

    if (!status)
    {
        LL_WARNS("MediaAuth") << "Error getting web profile." << LL_ENDL;
        return;
    }

    LLSD resultHeaders = httpResults[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_HEADERS];
    if (!resultHeaders.has(HTTP_IN_HEADER_SET_COOKIE))
    {
        LL_WARNS("MediaAuth") << "No cookie in response." << LL_ENDL;
        return;
    }

    const std::string& cookie = resultHeaders[HTTP_IN_HEADER_SET_COOKIE].asStringRef();
    LL_DEBUGS("MediaAuth") << "cookie = " << cookie << LL_ENDL;

    // Set cookie for snapshot publishing.
    std::string authCookie = cookie.substr(0, cookie.find(";")); // strip path
    LLWebProfile::setAuthCookie(authCookie);
}

/////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMedia::openIDSetup(const std::string &openidUrl, const std::string &openidToken)
{
    LL_DEBUGS("MediaAuth") << "url = \"" << openidUrl << "\", token = \"" << openidToken << "\"" << LL_ENDL;

    LLCoros::instance().launch("LLViewerMedia::openIDSetupCoro",
        boost::bind(&LLViewerMedia::openIDSetupCoro, openidUrl, openidToken));
}

void LLViewerMedia::openIDSetupCoro(std::string openidUrl, std::string openidToken)
{
    LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
    LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
        httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("openIDSetupCoro", httpPolicy));
    LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);
    LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions);
    LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders);

    httpOpts->setWantHeaders(true);

    // 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.
    getInstance()->mOpenIDURL.init(openidUrl.c_str());

    // We shouldn't ever do this twice, but just in case this code gets repurposed later, clear existing cookies.
    getInstance()->mOpenIDCookie.clear();

    httpHeaders->append(HTTP_OUT_HEADER_ACCEPT, "*/*");
    httpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, "application/x-www-form-urlencoded");

    LLCore::BufferArray::ptr_t rawbody(new LLCore::BufferArray);
    LLCore::BufferArrayStream bas(rawbody.get());

    bas << std::noskipws << openidToken;

    LLSD result = httpAdapter->postRawAndSuspend(httpRequest, openidUrl, rawbody, httpOpts, httpHeaders);

    LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
    LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);

    if (!status)
    {
        LL_WARNS("MediaAuth") << "Error getting Open ID cookie" << LL_ENDL;
        return;
    }

    LLSD resultHeaders = httpResults[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_HEADERS];
    if (!resultHeaders.has(HTTP_IN_HEADER_SET_COOKIE))
    {
        LL_WARNS("MediaAuth") << "No cookie in response." << LL_ENDL;
        return;
    }

    // We don't care about the content of the response, only the Set-Cookie header.
    const std::string& cookie = resultHeaders[HTTP_IN_HEADER_SET_COOKIE].asString();

    // *TODO: What about bad status codes?  Does this destroy previous cookies?
    LLViewerMedia::getInstance()->openIDCookieResponse(openidUrl, cookie);
    LL_DEBUGS("MediaAuth") << "OpenID cookie set." << LL_ENDL;

}

/////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMedia::openIDCookieResponse(const std::string& url, const std::string &cookie)
{
    LL_DEBUGS("MediaAuth") << "Cookie received: \"" << cookie << "\"" << LL_ENDL;

    mOpenIDCookie += cookie;

    setOpenIDCookie(url);
}

/////////////////////////////////////////////////////////////////////////////////////////
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);
        }
    }
}

/////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMedia::proxyWindowClosed(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->proxyWindowClosed(uuid);
        }
    }
}

/////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMedia::createSpareBrowserMediaSource()
{
    // If we don't have a spare browser media source, create one.
    // However, if PluginAttachDebuggerToPlugins is set then don't spawn a spare
    // SLPlugin process in order to not be confused by an unrelated gdb terminal
    // popping up at the moment we start a media plugin.
    if (!mSpareBrowserMediaSource && !gSavedSettings.getBOOL("PluginAttachDebuggerToPlugins"))
    {
        // The null owner will keep the browser plugin from fully initializing
        // (specifically, it keeps LLPluginClassMedia from negotiating a size change,
        // which keeps MediaPluginWebkit::initBrowserWindow from doing anything until we have some necessary data, like the background color)
        mSpareBrowserMediaSource = LLViewerMediaImpl::newSourceFromMediaType(HTTP_CONTENT_TEXT_HTML, NULL, 0, 0, 1.0);
    }
}

/////////////////////////////////////////////////////////////////////////////////////////
LLPluginClassMedia* LLViewerMedia::getSpareBrowserMediaSource()
{
    LLPluginClassMedia* result = mSpareBrowserMediaSource;
    mSpareBrowserMediaSource = NULL;
    return result;
};

bool LLViewerMedia::hasInWorldMedia()
{
    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++)
    {
        LLViewerMediaImpl* pimpl = *iter;
        if (!pimpl->getUsedInUI() && !pimpl->isParcelMedia())
        {
            // Found an in-world media impl
            return true;
        }
    }
    return false;
}

//////////////////////////////////////////////////////////////////////////////////////////
bool LLViewerMedia::hasParcelMedia()
{
    return !LLViewerParcelMedia::getInstance()->getURL().empty();
}

//////////////////////////////////////////////////////////////////////////////////////////
bool LLViewerMedia::hasParcelAudio()
{
    return !LLViewerMedia::getParcelAudioURL().empty();
}

//////////////////////////////////////////////////////////////////////////////////////////
std::string LLViewerMedia::getParcelAudioURL()
{
    return LLViewerParcelMgr::getInstance()->getAgentParcel()->getMusicURL();
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMedia::onTeleportFinished()
{
    // On teleport, clear this setting (i.e. set it to true)
    gSavedSettings.setBOOL("MediaTentativeAutoPlay", true);

    LLViewerMediaImpl::sMimeTypesFailed.clear();
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMedia::setOnlyAudibleMediaTextureID(const LLUUID& texture_id)
{
    sOnlyAudibleTextureID = texture_id;
    sForceUpdate = true;
}

std::vector<std::string> LLViewerMediaImpl::sMimeTypesFailed;
//////////////////////////////////////////////////////////////////////////////////////////
// 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),
    mPreviousVolume(1.0f),
    mIsMuted(false),
    mNeedsMuteCheck(false),
    mPreviousMediaState(MEDIA_NONE),
    mPreviousMediaTime(0.0f),
    mIsDisabled(false),
    mIsParcelMedia(false),
    mProximity(-1),
    mProximityDistance(0.0f),
    mMediaAutoPlay(false),
    mInNearbyMediaList(false),
    mClearCache(false),
    mBackgroundColor(LLColor4::white),
    mNavigateSuspended(false),
    mNavigateSuspendedDeferred(false),
    mIsUpdated(false),
    mTrustedBrowser(false),
    mZoomFactor(1.0),
    mCleanBrowser(false),
    mMimeProbe(),
    mCanceling(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.
    // *TODO: Consider enabling mipmaps (they have been disabled for a long time). Likely has a significant performance impact for tiled/high texture repeat media. Mip generation in a shader may also be an option if necessary.
    LLViewerMediaTexture* media_tex = LLViewerTextureManager::getMediaTexture(mTextureId, USE_MIPMAPS);
    if(media_tex)
    {
        media_tex->setMediaImpl();
    }

    mMainQueue = LL::WorkQueue::getInstance("mainloop");
    mTexUpdateQueue = LL::WorkQueue::getInstance("LLImageGL"); // Share work queue with tex loader.
}

//////////////////////////////////////////////////////////////////////////////////////////
LLViewerMediaImpl::~LLViewerMediaImpl()
{
    destroyMediaSource();

    LLViewerMediaTexture::removeMediaImplFromTexture(mTextureId) ;

    setTextureID();
    remove_media_impl(this);
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMediaImpl::emitEvent(LLPluginClassMedia* plugin, LLViewerMediaObserver::EMediaEvent event)
{
    // 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);
}

//////////////////////////////////////////////////////////////////////////////////////////
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();

    {
        LLMutexLock lock(&mLock); // Delay tear-down while bg thread is updating
        if(mMediaSource)
        {
            mMediaSource->setDeleteOK(true) ;
            mMediaSource = NULL; // shared pointer
        }
    }
}

//////////////////////////////////////////////////////////////////////////////////////////
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, F64 zoom_factor, const std::string target, bool clean_browser)
{
    if (gNonInteractive)
    {
        return NULL;
    }

    std::string plugin_basename = LLMIMETypes::implType(media_type);
    LLPluginClassMedia* media_source = NULL;

    // HACK: we always try to keep a spare running webkit plugin around to improve launch times.
    // If a spare was already created before PluginAttachDebuggerToPlugins was set, don't use it.
    // Do not use a spare if launching with full viewer control (e.g. Twitter and few others)
    if ((plugin_basename == "media_plugin_cef") &&
        !gSavedSettings.getBOOL("PluginAttachDebuggerToPlugins") && !clean_browser)
    {
        media_source = LLViewerMedia::getInstance()->getSpareBrowserMediaSource();
        if(media_source)
        {
            media_source->setOwner(owner);
            media_source->setTarget(target);
            media_source->setSize(default_width, default_height);
            media_source->setZoomFactor(zoom_factor);

            return media_source;
        }
    }
    if(plugin_basename.empty())
    {
        LL_WARNS_ONCE("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_cache = gDirUtilp->getCacheDir(false);
        user_data_path_cache += gDirUtilp->getDirDelimiter();

        std::string user_data_path_cef_log = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "cef_log.txt");

        // See if the plugin executable exists
        llstat s;
        if(LLFile::stat(launcher_name, &s))
        {
            LL_WARNS_ONCE("Media") << "Couldn't find launcher at " << launcher_name << LL_ENDL;
        }
        else if(LLFile::stat(plugin_name, &s))
        {
#if !LL_LINUX
            LL_WARNS_ONCE("Media") << "Couldn't find plugin at " << plugin_name << LL_ENDL;
#endif
        }
        else
        {
            media_source = new LLPluginClassMedia(owner);
            media_source->setSize(default_width, default_height);
            media_source->setUserDataPath(user_data_path_cache, gDirUtilp->getUserName(), user_data_path_cef_log);
            media_source->setLanguageCode(LLUI::getLanguage());
            media_source->setZoomFactor(zoom_factor);

            // collect 'cookies enabled' setting from prefs and send to embedded browser
            bool cookies_enabled = gSavedSettings.getBOOL( "CookiesEnabled" );
            media_source->cookies_enabled( cookies_enabled || clean_browser);

            // collect 'javascript enabled' setting from prefs and send to embedded browser
            bool javascript_enabled = gSavedSettings.getBOOL("BrowserJavascriptEnabled");
            media_source->setJavascriptEnabled(javascript_enabled || clean_browser);

            // collect 'web security disabled' (see Chrome --web-security-disabled) setting from prefs and send to embedded browser
            bool web_security_disabled = gSavedSettings.getBOOL("BrowserWebSecurityDisabled");
            media_source->setWebSecurityDisabled(web_security_disabled || clean_browser);

            // collect setting indicates if local file access from file URLs is allowed from prefs and send to embedded browser
            bool file_access_from_file_urls = gSavedSettings.getBOOL("BrowserFileAccessFromFileUrls");
            media_source->setFileAccessFromFileUrlsEnabled(file_access_from_file_urls || clean_browser);

            // As of SL-15559 PDF files do not load in CEF v91 we enable plugins
            // but explicitly disable Flash (PDF support in CEF is now treated as a plugin)
            media_source->setPluginsEnabled(true);

            bool media_plugin_debugging_enabled = gSavedSettings.getBOOL("MediaPluginDebugging");
            media_source->enableMediaPluginDebugging( media_plugin_debugging_enabled  || clean_browser);

            // need to set agent string here before instance created
            media_source->setBrowserUserAgent(LLViewerMedia::getInstance()->getCurrentUserAgent());

            // configure and pass proxy setup based on debug settings that are
            // configured by UI in prefs -> setup
            media_source->proxy_setup(gSavedSettings.getBOOL("BrowserProxyEnabled"), gSavedSettings.getString("BrowserProxyAddress"), gSavedSettings.getS32("BrowserProxyPort"));

            media_source->setTarget(target);

            const std::string plugin_dir = gDirUtilp->getLLPluginDir();
            if (media_source->init(launcher_name, plugin_dir, plugin_name, gSavedSettings.getBOOL("PluginAttachDebuggerToPlugins")))
            {
                return media_source;
            }
            else
            {
                LL_WARNS("Media") << "Failed to init plugin.  Destroying." << LL_ENDL;
                delete media_source;
            }
        }
    }
#if !LL_LINUX
    LL_WARNS_ONCE("Plugin") << "plugin initialization failed for mime type: " << media_type << LL_ENDL;
#endif

    if(gAgent.isInitialized())
    {
        if (std::find(sMimeTypesFailed.begin(), sMimeTypesFailed.end(), media_type) == sMimeTypesFailed.end())
        {
            LLSD args;
            args["MIME_TYPE"] = media_type;
            LLNotificationsUtil::add("NoPlugin", args);
            sMimeTypesFailed.push_back(media_type);
        }
    }
    return NULL;
}

//////////////////////////////////////////////////////////////////////////////////////////
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();
        mZoomFactor = mMediaSource->getZoomFactor();
    }

    // Always delete the old media impl first.
    destroyMediaSource();

    // and unconditionally set the mime type
    mMimeType = media_type;

    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;

    // Save the MIME type that really caused the plugin to load
    mCurrentMimeType = mMimeType;

    LLPluginClassMedia* media_source = newSourceFromMediaType(mMimeType, this, mMediaWidth, mMediaHeight, mZoomFactor, mTarget, mCleanBrowser);

    if (media_source)
    {
        media_source->injectOpenIDCookie();
        media_source->setDisableTimeout(gSavedSettings.getBOOL("DebugPluginDisableTimeout"));
        media_source->setLoop(mMediaLoop);
        media_source->setAutoScale(mMediaAutoScale);
        media_source->setBrowserUserAgent(LLViewerMedia::getInstance()->getCurrentUserAgent());
        media_source->focus(mHasFocus);
        media_source->setBackgroundColor(mBackgroundColor);

        if(gSavedSettings.getBOOL("BrowserIgnoreSSLCertErrors"))
        {
            media_source->ignore_ssl_cert_errors(true);
        }

        // the correct way to deal with certs it to load ours from ca-bundle.crt and append them to the ones
        // Qt/WebKit loads from your system location.
        std::string ca_path = gDirUtilp->getCAFile();
        media_source->addCertificateFilePath( ca_path );

        if(mClearCache)
        {
            mClearCache = false;
            media_source->clear_cache();
        }

        mMediaSource.reset(media_source);
        mMediaSource->setDeleteOK(false) ;
        updateVolume();

        return true;
    }

    // Make sure the timer doesn't try re-initing this plugin repeatedly until something else changes.
    mMediaSourceFailed = true;

    return false;
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMediaImpl::loadURI()
{
    if(mMediaSource)
    {
        // trim whitespace from front and back of URL - fixes EXT-5363
        LLStringUtil::trim( mMediaURL );

        // URI often comes unescaped
        std::string uri = LLURI::escapePathAndData(mMediaURL);
        {
            // Do not log the query parts
            LLURI u(uri);
            std::string sanitized_uri = (u.query().empty() ? uri : u.scheme() + "://" + u.authority() + u.path());
            LL_INFOS() << "Asking media source to load URI: " << sanitized_uri << LL_ENDL;
        }

        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::executeJavaScript(const std::string& code)
{
    if (mMediaSource)
    {
        mMediaSource->executeJavaScript(code);
    }
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMediaImpl::setSize(int width, int height)
{
    mMediaWidth = width;
    mMediaHeight = height;
    if(mMediaSource)
    {
        mMediaSource->setSize(width, height);
    }
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMediaImpl::showNotification(LLNotificationPtr notify)
{
    mNotification = notify;
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMediaImpl::hideNotification()
{
    mNotification.reset();
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMediaImpl::play()
{
    // If the media source isn't there, try to initialize it and load an URL.
    if(mMediaSource == NULL)
    {
        if(!initializeMedia(mMimeType))
        {
            // 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();
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMediaImpl::stop()
{
    if(mMediaSource)
    {
        mMediaSource->stop();
        // destroyMediaSource();
    }
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMediaImpl::pause()
{
    if(mMediaSource)
    {
        mMediaSource->pause();
    }
    else
    {
        mPreviousMediaState = MEDIA_PAUSED;
    }
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMediaImpl::start()
{
    if(mMediaSource)
    {
        mMediaSource->start();
    }
    else
    {
        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;
    }
}

//////////////////////////////////////////////////////////////////////////////////////////
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);
        }
    }
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMediaImpl::skipForward(F32 step_scale)
{
    if(mMediaSource)
    {
        if(mMediaSource->pluginSupportsMediaTime())
        {
            F64 forward_step = mMediaSource->getCurrentTime() + (mMediaSource->getDuration()*step_scale);
            if(forward_step > mMediaSource->getDuration())
            {
                forward_step = mMediaSource->getDuration();
            }
            mMediaSource->seek(forward_step);
        }
    }
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMediaImpl::setVolume(F32 volume)
{
    mRequestedVolume = volume;
    updateVolume();
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMediaImpl::setMute(bool mute)
{
    if (mute)
    {
        mPreviousVolume = mRequestedVolume;
        setVolume(0.0);
    }
    else
    {
        setVolume(mPreviousVolume);
    }
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMediaImpl::updateVolume()
{
    LL_RECORD_BLOCK_TIME(FTM_MEDIA_UPDATE_VOLUME);
    if(mMediaSource)
    {
        // always scale the volume by the global media volume
        F32 volume = mRequestedVolume * LLViewerMedia::getInstance()->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);
            }
        }

        if (sOnlyAudibleTextureID == LLUUID::null || sOnlyAudibleTextureID == mTextureId)
        {
            mMediaSource->setVolume(volume);
        }
        else
        {
            mMediaSource->setVolume(0.0f);
        }
    }
}

//////////////////////////////////////////////////////////////////////////////////////////
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)
        {
            // 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;
}

std::string LLViewerMediaImpl::getCurrentMediaURL()
{
    if(!mCurrentMediaURL.empty())
    {
        return mCurrentMediaURL;
    }

    return mMediaURL;
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMediaImpl::clearCache()
{
    if(mMediaSource)
    {
        mMediaSource->clear_cache();
    }
    else
    {
        mClearCache = true;
    }
}


//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMediaImpl::setPageZoomFactor( double factor )
{
    if(mMediaSource && factor != mZoomFactor)
    {
        mZoomFactor = factor;
        mMediaSource->set_page_zoom_factor( factor );
    }
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMediaImpl::mouseDown(S32 x, S32 y, MASK mask, S32 button)
{
    scaleMouse(&x, &y);
    mLastMouseX = x;
    mLastMouseY = y;
//  LL_INFOS() << "mouse down (" << x << ", " << y << ")" << LL_ENDL;
    if (mMediaSource)
    {
        mMediaSource->mouseEvent(LLPluginClassMedia::MOUSE_EVENT_DOWN, button, x, y, mask);
    }
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMediaImpl::mouseUp(S32 x, S32 y, MASK mask, S32 button)
{
    scaleMouse(&x, &y);
    mLastMouseX = x;
    mLastMouseY = y;
//  LL_INFOS() << "mouse up (" << x << ", " << y << ")" << LL_ENDL;
    if (mMediaSource)
    {
        mMediaSource->mouseEvent(LLPluginClassMedia::MOUSE_EVENT_UP, button, x, y, mask);
    }
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMediaImpl::mouseMove(S32 x, S32 y, MASK mask)
{
    scaleMouse(&x, &y);
    mLastMouseX = x;
    mLastMouseY = y;
//  LL_INFOS() << "mouse move (" << x << ", " << y << ")" << LL_ENDL;
    if (mMediaSource)
    {
        mMediaSource->mouseEvent(LLPluginClassMedia::MOUSE_EVENT_MOVE, 0, x, y, mask);
    }
}

//////////////////////////////////////////////////////////////////////////////////////////
//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 = ll_round(texture_x * mMediaSource->getTextureWidth());
    *y = ll_round((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);
    }
}

void LLViewerMediaImpl::mouseDoubleClick(const LLVector2& texture_coords, MASK mask)
{
    if (mMediaSource)
    {
        S32 x, y;
        scaleTextureCoords(texture_coords, &x, &y);

        mouseDoubleClick(x, y, mask);
    }
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMediaImpl::mouseDoubleClick(S32 x, S32 y, MASK mask, S32 button)
{
    scaleMouse(&x, &y);
    mLastMouseX = x;
    mLastMouseY = y;
    if (mMediaSource)
    {
        mMediaSource->mouseEvent(LLPluginClassMedia::MOUSE_EVENT_DOUBLE_CLICK, button, x, y, mask);
    }
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMediaImpl::scrollWheel(const LLVector2& texture_coords, S32 scroll_x, S32 scroll_y, MASK mask)
{
    if (mMediaSource)
    {
        S32 x, y;
        scaleTextureCoords(texture_coords, &x, &y);

        scrollWheel(x, y, scroll_x, scroll_y, mask);
    }
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMediaImpl::scrollWheel(S32 x, S32 y, S32 scroll_x, S32 scroll_y, MASK mask)
{
    scaleMouse(&x, &y);
    mLastMouseX = x;
    mLastMouseY = y;
    if (mMediaSource)
    {
        mMediaSource->scrollEvent(x, y, scroll_x, scroll_y, mask);
    }
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMediaImpl::onMouseCaptureLost()
{
    if (mMediaSource)
    {
        mMediaSource->mouseEvent(LLPluginClassMedia::MOUSE_EVENT_UP, 0, mLastMouseX, mLastMouseY, 0);
    }
}

//////////////////////////////////////////////////////////////////////////////////////////
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 );
    }

    return TRUE;
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMediaImpl::updateJavascriptObject()
{
    static LLFrameTimer timer ;

    if ( mMediaSource )
    {
        // flag to expose this information to internal browser or not.
        bool enable = gSavedSettings.getBOOL("BrowserEnableJSObject");

        if(!enable)
        {
            return ; //no need to go further.
        }

        if(timer.getElapsedTimeF32() < 1.0f)
        {
            return ; //do not update more than once per second.
        }
        timer.reset() ;

        mMediaSource->jsEnableObject( enable );

        // these values are only menaingful after login so don't set them before
        bool logged_in = LLLoginInstance::getInstance()->authSuccess();
        if ( logged_in )
        {
            // current location within a region
            LLVector3 agent_pos = gAgent.getPositionAgent();
            double x = agent_pos.mV[ VX ];
            double y = agent_pos.mV[ VY ];
            double z = agent_pos.mV[ VZ ];
            mMediaSource->jsAgentLocationEvent( x, y, z );

            // current location within the grid
            LLVector3d agent_pos_global = gAgent.getLastPositionGlobal();
            double global_x = agent_pos_global.mdV[ VX ];
            double global_y = agent_pos_global.mdV[ VY ];
            double global_z = agent_pos_global.mdV[ VZ ];
            mMediaSource->jsAgentGlobalLocationEvent( global_x, global_y, global_z );

            // current agent orientation
            double rotation = atan2( gAgent.getAtAxis().mV[VX], gAgent.getAtAxis().mV[VY] );
            double angle = rotation * RAD_TO_DEG;
            if ( angle < 0.0f ) angle = 360.0f + angle; // TODO: has to be a better way to get orientation!
            mMediaSource->jsAgentOrientationEvent( angle );

            // current region agent is in
            std::string region_name("");
            LLViewerRegion* region = gAgent.getRegion();
            if ( region )
            {
                region_name = region->getName();
            };
            mMediaSource->jsAgentRegionEvent( region_name );
        }

        // language code the viewer is set to
        mMediaSource->jsAgentLanguageEvent( LLUI::getLanguage() );

        // maturity setting the agent has selected
        if ( gAgent.prefersAdult() )
            mMediaSource->jsAgentMaturityEvent( "GMA" );    // Adult means see adult, mature and general content
        else
        if ( gAgent.prefersMature() )
            mMediaSource->jsAgentMaturityEvent( "GM" ); // Mature means see mature and general content
        else
        if ( gAgent.prefersPG() )
            mMediaSource->jsAgentMaturityEvent( "G" );  // PG means only see General content
    }
}

//////////////////////////////////////////////////////////////////////////////////////////
const std::string& LLViewerMediaImpl::getName() const
{
    if (mMediaSource)
    {
        return mMediaSource->getMediaName();
    }

    return LLStringUtil::null;
};

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMediaImpl::navigateBack()
{
    if (mMediaSource)
    {
        mMediaSource->browse_back();
    }
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMediaImpl::navigateForward()
{
    if (mMediaSource)
    {
        mMediaSource->browse_forward();
    }
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMediaImpl::navigateReload()
{
    navigateTo(getCurrentMediaURL(), "", true, false);
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMediaImpl::navigateHome()
{
    bool rediscover_mimetype = mHomeMimeType.empty();
    navigateTo(mHomeURL, mHomeMimeType, rediscover_mimetype, false);
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMediaImpl::unload()
{
    // Unload the media impl and clear its state.
    destroyMediaSource();
    resetPreviousMediaState();
    mMediaURL.clear();
    mMimeType.clear();
    mCurrentMediaURL.clear();
    mCurrentMimeType.clear();
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMediaImpl::navigateTo(const std::string& url, const std::string& mime_type,  bool rediscover_type, bool server_request, bool clean_browser)
{
    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;
    mCleanBrowser = clean_browser;

    // 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.
        {
            // Do not log the query parts
            LLURI u(url);
            std::string sanitized_url = (u.query().empty() ? url : u.scheme() + "://" + u.authority() + u.path());
            LL_INFOS() << "NOT LOADING media id= " << mTextureId << " url=" << sanitized_url << ", mime_type=" << mime_type << LL_ENDL;
        }

        // This impl should not be loaded at this time.
        LL_DEBUGS("PluginPriority") << this << "Not loading (PRIORITY_UNLOADED)" << LL_ENDL;

        return;
    }

    navigateInternal();
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMediaImpl::navigateInternal()
{
    // Helpful to have media urls in log file. Shouldn't be spammy.
    {
        // Do not log the query parts
        LLURI u(mMediaURL);
        std::string sanitized_url = (u.query().empty() ? mMediaURL : u.scheme() + "://" + u.authority() + u.path());
        LL_INFOS() << "media id= " << mTextureId << " url=" << sanitized_url << ", mime_type=" << mMimeType << LL_ENDL;
    }

    if(mNavigateSuspended)
    {
        LL_WARNS() << "Deferring navigate." << LL_ENDL;
        mNavigateSuspendedDeferred = true;
        return;
    }


    if (!mMimeProbe.expired())
    {
        LL_WARNS() << "MIME type probe already in progress -- bailing out." << LL_ENDL;
        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)
        {
            LLCoros::instance().launch("LLViewerMediaImpl::mimeDiscoveryCoro",
                boost::bind(&LLViewerMediaImpl::mimeDiscoveryCoro, this, mMediaURL));
        }
        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(HTTP_CONTENT_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;
    }
}

void LLViewerMediaImpl::mimeDiscoveryCoro(std::string url)
{
    LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
    LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
        httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("mimeDiscoveryCoro", httpPolicy));
    LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);
    LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions);
    LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders);

    // Increment our refcount so that we do not go away while the coroutine is active.
    this->ref();

    mMimeProbe = httpAdapter;

    httpOpts->setFollowRedirects(true);
    httpOpts->setHeadersOnly(true);

    httpHeaders->append(HTTP_OUT_HEADER_ACCEPT, "*/*");
    httpHeaders->append(HTTP_OUT_HEADER_COOKIE, "");

    LLSD result = httpAdapter->getRawAndSuspend(httpRequest, url, httpOpts, httpHeaders);

    mMimeProbe.reset();

    LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
    LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);

    if (!status)
    {
        LL_WARNS() << "Error retrieving media headers." << LL_ENDL;
    }

    if (this->getNumRefs() > 1)
    {   // if there is only a single ref count outstanding it will be the one we took out above...
        // we can skip the rest of this routine

        LLSD resultHeaders = httpResults[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_HEADERS];

        const std::string& mediaType = resultHeaders[HTTP_IN_HEADER_CONTENT_TYPE].asStringRef();

        std::string::size_type idx1 = mediaType.find_first_of(";");
        std::string mimeType = mediaType.substr(0, idx1);

        // We now no longer need to 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.
        // The probe was successful.
        if (mimeType.empty())
        {
            // Some sites don't return any content-type header at all.
            // Treat an empty mime type as text/html.
            mimeType = HTTP_CONTENT_TEXT_HTML;
        }

        LL_DEBUGS() << "Media type \"" << mediaType << "\", mime type is \"" << mimeType << "\"" << LL_ENDL;

        // the call to initializeMedia may disconnect the responder, which will clear mMediaImpl.
        // Make a local copy so we can call loadURI() afterwards.

        if (!mimeType.empty())
        {
            if (initializeMedia(mimeType))
            {
                loadURI();
            }
        }

    }
    else
    {
        LL_WARNS() << "LLViewerMediaImpl to be released." << LL_ENDL;
    }

    this->unref();
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMediaImpl::navigateStop()
{
    if(mMediaSource)
    {
        mMediaSource->browse_stop();
    }
}

//////////////////////////////////////////////////////////////////////////////////////////
bool LLViewerMediaImpl::handleKeyHere(KEY key, MASK mask)
{
    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 && key != KEY_LEFT && key != KEY_RIGHT && key != KEY_HOME && key != KEY_END)
        {
            result = true;
        }

        if (!result)
        {
            LLSD native_key_data = gViewerWindow->getWindow()->getNativeKeyData();
            result = mMediaSource->keyEvent(LLPluginClassMedia::KEY_EVENT_DOWN, key, mask, native_key_data);
        }
    }

    return result;
}

//////////////////////////////////////////////////////////////////////////////////////////
bool LLViewerMediaImpl::handleKeyUpHere(KEY key, MASK mask)
{
    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 && key != KEY_LEFT && key != KEY_RIGHT && key != KEY_HOME && key != KEY_END)
        {
            result = true;
        }

        if (!result)
        {
            LLSD native_key_data = gViewerWindow->getWindow()->getNativeKeyData();
            result = mMediaSource->keyEvent(LLPluginClassMedia::KEY_EVENT_UP, key, mask, native_key_data);
        }
    }

    return result;
}

//////////////////////////////////////////////////////////////////////////////////////////
bool LLViewerMediaImpl::handleUnicodeCharHere(llwchar uni_char)
{
    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;
}

//////////////////////////////////////////////////////////////////////////////////////////
bool LLViewerMediaImpl::canNavigateForward()
{
    BOOL result = FALSE;
    if (mMediaSource)
    {
        result = mMediaSource->getHistoryForwardAvailable();
    }
    return result;
}

//////////////////////////////////////////////////////////////////////////////////////////
bool LLViewerMediaImpl::canNavigateBack()
{
    BOOL result = FALSE;
    if (mMediaSource)
    {
        result = mMediaSource->getHistoryBackAvailable();
    }
    return result;
}

//////////////////////////////////////////////////////////////////////////////////////////
static LLTrace::BlockTimerStatHandle FTM_MEDIA_DO_UPDATE("Do Update");
static LLTrace::BlockTimerStatHandle FTM_MEDIA_GET_DATA("Get Data");
static LLTrace::BlockTimerStatHandle FTM_MEDIA_SET_SUBIMAGE("Set Subimage");


void LLViewerMediaImpl::update()
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_MEDIA; //LL_RECORD_BLOCK_TIME(FTM_MEDIA_DO_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 (!mMimeProbe.expired())
        {
            // 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();

        // TODO: this is updated every frame - is this bad?
        // Removing this as part of the post viewer64 media update
        // Removed as not implemented in CEF embedded browser
        // See MAINT-8194 for a more fuller description
        // updateJavascriptObject();
    }


    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* media_tex;
    U8* data;
    S32 data_width;
    S32 data_height;
    S32 x_pos;
    S32 y_pos;
    S32 width;
    S32 height;

    if (preMediaTexUpdate(media_tex, data, data_width, data_height, x_pos, y_pos, width, height))
    {
        // Push update to worker thread
        auto main_queue = LLImageGLThread::sEnabledMedia ? mMainQueue.lock() : nullptr;
        if (main_queue)
        {
            mTextureUpdatePending = true;
            ref();  // protect texture from deletion while active on bg queue
            media_tex->ref();
            main_queue->postTo(
                mTexUpdateQueue, // Worker thread queue
                [=]() // work done on update worker thread
                {
#if LL_IMAGEGL_THREAD_CHECK
                    media_tex->getGLTexture()->mActiveThread = LLThread::currentID();
#endif
                    doMediaTexUpdate(media_tex, data, data_width, data_height, x_pos, y_pos, width, height, true);
                },
                [=]() // callback to main thread
                {
#if LL_IMAGEGL_THREAD_CHECK
                    media_tex->getGLTexture()->mActiveThread = LLThread::currentID();
#endif
                    mTextureUpdatePending = false;
                    media_tex->unref();
                    unref();
                });
        }
        else
        {
            doMediaTexUpdate(media_tex, data, data_width, data_height, x_pos, y_pos, width, height, false); // otherwise, update on main thread
        }
    }
}

bool LLViewerMediaImpl::preMediaTexUpdate(LLViewerMediaTexture*& media_tex, U8*& data, S32& data_width, S32& data_height, S32& x_pos, S32& y_pos, S32& width, S32& height)
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_MEDIA;

    bool retval = false;

    if (!mTextureUpdatePending)
    {
        media_tex = updateMediaImage();

        if (media_tex && mMediaSource)
        {
            LLRect dirty_rect;
            S32 media_width = mMediaSource->getTextureWidth();
            S32 media_height = mMediaSource->getTextureHeight();
            //S32 media_depth = mMediaSource->getTextureDepth();

            // Since we're updating this texture, we know it's playing.  Tell the texture to do its replacement magic so it gets rendered.
            media_tex->setPlaying(TRUE);

            if (mMediaSource->getDirty(&dirty_rect))
            {
                // Constrain the dirty rect to be inside the texture
                x_pos = llmax(dirty_rect.mLeft, 0);
                y_pos = llmax(dirty_rect.mBottom, 0);
                width = llmin(dirty_rect.mRight, media_width) - x_pos;
                height = llmin(dirty_rect.mTop, media_height) - y_pos;

                if (width > 0 && height > 0)
                {
                    data = mMediaSource->getBitsData();
                    data_width = mMediaSource->getWidth();
                    data_height = mMediaSource->getHeight();

                    if (data != NULL)
                    {
                        // data is ready to be copied to GL
                        retval = true;
                    }
                }

                mMediaSource->resetDirty();
            }
        }
    }

    return retval;
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMediaImpl::doMediaTexUpdate(LLViewerMediaTexture* media_tex, U8* data, S32 data_width, S32 data_height, S32 x_pos, S32 y_pos, S32 width, S32 height, bool sync)
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_MEDIA;
    LLMutexLock lock(&mLock); // don't allow media source tear-down during update

    // wrap "data" in an LLImageRaw but do NOT make a copy
    LLPointer<LLImageRaw> raw = new LLImageRaw(data, media_tex->getWidth(), media_tex->getHeight(), media_tex->getComponents(), true);

    // *NOTE: Recreating the GL texture each media update may seem wasteful
    // (note the texture creation in preMediaTexUpdate), however, it apparently
    // prevents GL calls from blocking, due to poor bookkeeping of state of
    // updated textures by the OpenGL implementation. (Windows 10/Nvidia)
    // -Cosmic,2023-04-04
    // Allocate GL texture based on LLImageRaw but do NOT copy to GL
    LLGLuint tex_name = 0;
    media_tex->createGLTexture(0, raw, 0, TRUE, LLGLTexture::OTHER, true, &tex_name);

    // copy just the subimage covered by the image raw to GL
    media_tex->setSubImage(data, data_width, data_height, x_pos, y_pos, width, height, tex_name);

    if (sync)
    {
        media_tex->getGLTexture()->syncToMainThread(tex_name);
    }
    else
    {
        media_tex->getGLTexture()->syncTexName(tex_name);
    }

    // release the data pointer before freeing raw so LLImageRaw destructor doesn't
    // free memory at data pointer
    raw->releaseData();
}

//////////////////////////////////////////////////////////////////////////////////////////
void LLViewerMediaImpl::updateImagesMediaStreams()
{
}

//////////////////////////////////////////////////////////////////////////////////////////
LLViewerMediaTexture* LLViewerMediaImpl::updateMediaImage()
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_MEDIA;
    llassert(!gCubeSnapshot);
    if (!mMediaSource)
    {
        return nullptr; // not ready for updating
    }

    //llassert(!mTextureId.isNull());
    // *TODO: Consider enabling mipmaps (they have been disabled for a long time). Likely has a significant performance impact for tiled/high texture repeat media. Mip generation in a shader may also be an option if necessary.
    LLViewerMediaTexture* media_tex = LLViewerTextureManager::getMediaTexture( mTextureId, USE_MIPMAPS );

    if ( mNeedsNewTexture
        || (media_tex->getWidth() != mMediaSource->getTextureWidth())
        || (media_tex->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
        media_tex->destroyGLTexture();

        // 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);

        // ask media source for correct GL image format constants
        media_tex->setExplicitFormat(mMediaSource->getTextureFormatInternal(),
            mMediaSource->getTextureFormatPrimary(),
            mMediaSource->getTextureFormatType(),
            mMediaSource->getTextureFormatSwapBytes());

        int discard_level = 0;
        media_tex->createGLTexture(discard_level, raw);

        // MEDIAOPT: set this dynamically on play/stop
        // FIXME
//      media_tex->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 media_tex;
}


//////////////////////////////////////////////////////////////////////////////////////////
LLUUID LLViewerMediaImpl::getMediaTextureID() const
{
    return mTextureId;
}

//////////////////////////////////////////////////////////////////////////////////////////
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)
    {
        return true;
    }

    // If this media's class is not supposed to be shown, unload
    if (!shouldShowBasedOnClass() || isObscured())
    {
        return true;
    }

    return false;
}

//////////////////////////////////////////////////////////////////////////////////////////
//
bool LLViewerMediaImpl::isPlayable() const
{
    if(isForcedUnloaded())
    {
        // All of the forced-unloaded criteria also imply not playable.
        return false;
    }

    if(hasMedia())
    {
        // 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;
}

//////////////////////////////////////////////////////////////////////////////////////////
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();
            std::string nav_type = plugin->getClickNavType();
            LLURLDispatcher::dispatch(url, nav_type, 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();
            mLastSetCursor = getCursorFromString(cursor);
        }
        break;

        case LLViewerMediaObserver::MEDIA_EVENT_FILE_DOWNLOAD:
        {
            LL_DEBUGS("Media") << "Media event - file download requested - filename is " << plugin->getFileDownloadFilename() << LL_ENDL;
        }
        break;

        case LLViewerMediaObserver::MEDIA_EVENT_NAVIGATE_BEGIN:
        {
            LL_DEBUGS("Media") << "MEDIA_EVENT_NAVIGATE_BEGIN, uri is: " << plugin->getNavigateURI() << LL_ENDL;
            hideNotification();

            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
            {
                bool internal_nav = false;
                if (url != mCurrentMediaURL)
                {
                    // Check if it is internal navigation
                    // Note: Not sure if we should detect internal navigations as 'address change',
                    // but they are not redirects and do not cause NAVIGATE_BEGIN (also see SL-1005)
                    size_t pos = url.find("#");
                    if (pos != std::string::npos)
                    {
                        // assume that new link always have '#', so this is either
                        // transfer from 'link#1' to 'link#2' or from link to 'link#2'
                        // filter out cases like 'redirect?link'
                        std::string base_url = url.substr(0, pos);
                        pos = mCurrentMediaURL.find(base_url);
                        if (pos == 0)
                        {
                            // base link hasn't changed
                            internal_nav = true;
                        }
                    }
                }

                if (internal_nav)
                {
                    // Internal navigation by '#'
                    mCurrentMediaURL = url;
                    setNavState(MEDIANAVSTATE_FIRST_LOCATION_CHANGED);
                }
                else
                {
                    // Don't track redirects.
                    setNavState(MEDIANAVSTATE_NONE);
                }
            }
        }
        break;

        case LLViewerMediaObserver::MEDIA_EVENT_PICK_FILE_REQUEST:
        {
            LL_DEBUGS("Media") << "Media event - file pick requested." <<  LL_ENDL;

            init_threaded_picker_load_dialog(plugin, LLFilePicker::FFLOAD_ALL, plugin->getIsMultipleFilePick());
        }
        break;

        case LLViewerMediaObserver::MEDIA_EVENT_AUTH_REQUEST:
        {
            LLNotification::Params auth_request_params;
            auth_request_params.name = "AuthRequest";

            // pass in host name and realm for site (may be zero length but will always exist)
            LLSD args;
            LLURL raw_url( plugin->getAuthURL().c_str() );
            args["HOST_NAME"] = raw_url.getAuthority();
            args["REALM"] = plugin->getAuthRealm();
            auth_request_params.substitutions = args;

            auth_request_params.payload = LLSD().with("media_id", mTextureId);
            auth_request_params.functor.function = boost::bind(&LLViewerMedia::authSubmitCallback, _1, _2);
            LLNotifications::instance().add(auth_request_params);
        };
        break;

        case LLViewerMediaObserver::MEDIA_EVENT_CLOSE_REQUEST:
        {
            std::string uuid = plugin->getClickUUID();

            LL_INFOS() << "MEDIA_EVENT_CLOSE_REQUEST for uuid " << uuid << LL_ENDL;

            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;
                LLFloaterWebContent::closeRequest(uuid);
            }
        }
        break;

        case LLViewerMediaObserver::MEDIA_EVENT_GEOMETRY_CHANGE:
        {
            std::string uuid = plugin->getClickUUID();

            LL_INFOS() << "MEDIA_EVENT_GEOMETRY_CHANGE for uuid " << uuid << LL_ENDL;

            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;
                LLFloaterWebContent::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::cut()
{
    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 ;
}

static LLTrace::BlockTimerStatHandle FTM_MEDIA_CALCULATE_INTEREST("Calculate Interest");

void LLViewerMediaImpl::calculateInterest()
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_MEDIA; //LL_RECORD_BLOCK_TIME(FTM_MEDIA_CALCULATE_INTEREST);
    LLViewerMediaTexture* texture = LLViewerTextureManager::findMediaTexture( mTextureId );

    llassert(!gCubeSnapshot);

    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.

            static LLUICachedControl<S32> mEarLocation("MediaSoundsEarLocation", 0);
            LLVector3d ear_position;
            switch(mEarLocation)
            {
            case 0:
            default:
                ear_position = gAgentCamera.getCameraPositionGlobal();
                break;

            case 1:
                ear_position = agent_global;
                break;
            }
            LLVector3d camera_delta = ear_position - 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" << LL_ENDL; break;
        case MEDIANAVSTATE_BEGUN: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_BEGUN" << LL_ENDL; break;
        case MEDIANAVSTATE_FIRST_LOCATION_CHANGED: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_FIRST_LOCATION_CHANGED" << LL_ENDL; break;
        case MEDIANAVSTATE_FIRST_LOCATION_CHANGED_SPURIOUS: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_FIRST_LOCATION_CHANGED_SPURIOUS" << LL_ENDL; break;
        case MEDIANAVSTATE_COMPLETE_BEFORE_LOCATION_CHANGED: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_COMPLETE_BEFORE_LOCATION_CHANGED" << LL_ENDL; break;
        case MEDIANAVSTATE_COMPLETE_BEFORE_LOCATION_CHANGED_SPURIOUS: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_COMPLETE_BEFORE_LOCATION_CHANGED_SPURIOUS" << LL_ENDL; break;
        case MEDIANAVSTATE_SERVER_SENT: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_SERVER_SENT" << LL_ENDL; break;
        case MEDIANAVSTATE_SERVER_BEGUN: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_SERVER_BEGUN" << LL_ENDL; break;
        case MEDIANAVSTATE_SERVER_FIRST_LOCATION_CHANGED: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_SERVER_FIRST_LOCATION_CHANGED" << LL_ENDL; break;
        case MEDIANAVSTATE_SERVER_COMPLETE_BEFORE_LOCATION_CHANGED: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_SERVER_COMPLETE_BEFORE_LOCATION_CHANGED" << LL_ENDL; 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()
{
    LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t probeAdapter = mMimeProbe.lock();

    if (probeAdapter)
        probeAdapter->cancelSuspendedOperation();

}

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;
    }
}

//////////////////////////////////////////////////////////////////////////////////////////
//
bool LLViewerMediaImpl::isAutoPlayable() const
{
    return (mMediaAutoPlay &&
            gSavedSettings.getS32("ParcelMediaAutoPlayEnable") != 0 &&
            gSavedSettings.getBOOL("MediaTentativeAutoPlay"));
}

//////////////////////////////////////////////////////////////////////////////////////////
//
bool LLViewerMediaImpl::shouldShowBasedOnClass() const
{
    // 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();

    //  LL_INFOS() << " 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)) << LL_ENDL;

    // 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)
    {
        static LLCachedControl<bool> show_media_on_others(gSavedSettings, LLViewerMedia::SHOW_MEDIA_ON_OTHERS_SETTING, false);
        return show_media_on_others;
    }
    if (inside_parcel)
    {
        static LLCachedControl<bool> show_media_within_parcel(gSavedSettings, LLViewerMedia::SHOW_MEDIA_WITHIN_PARCEL_SETTING, true);

        return show_media_within_parcel;
    }
    else
    {
        static LLCachedControl<bool> show_media_outside_parcel(gSavedSettings, LLViewerMedia::SHOW_MEDIA_OUTSIDE_PARCEL_SETTING, true);

        return show_media_outside_parcel;
    }
}

//////////////////////////////////////////////////////////////////////////////////////////
//
bool LLViewerMediaImpl::isObscured() const
{
    if (getUsedInUI() || isParcelMedia() || isAttachedToHUD()) return false;

    LLParcel* agent_parcel = LLViewerParcelMgr::getInstance()->getAgentParcel();
    if (!agent_parcel)
    {
        return false;
    }

    if (agent_parcel->getObscureMOAP() && !isInAgentParcel())
    {
        return true;
    }

    return false;
}

bool LLViewerMediaImpl::isAttachedToHUD() const
{
    std::list< LLVOVolume* >::const_iterator iter = mObjectList.begin();
    std::list< LLVOVolume* >::const_iterator end = mObjectList.end();
    for ( ; iter != end; iter++)
    {
        if ((*iter)->isHUDAttachment())
        {
            return true;
        }
    }
    return false;
}

//////////////////////////////////////////////////////////////////////////////////////////
//
bool LLViewerMediaImpl::isAttachedToAnotherAvatar() const
{
    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
bool LLViewerMediaImpl::isObjectAttachedToAnotherAvatar(LLVOVolume *obj)
{
    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;
}

//////////////////////////////////////////////////////////////////////////////////////////
//
bool LLViewerMediaImpl::isInAgentParcel() const
{
    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;
}

LLNotificationPtr LLViewerMediaImpl::getCurrentNotification() const
{
    return mNotification;
}

//////////////////////////////////////////////////////////////////////////////////////////
//
// static
bool LLViewerMediaImpl::isObjectInAgentParcel(LLVOVolume *obj)
{
    return (LLViewerParcelMgr::getInstance()->inAgentParcel(obj->getPositionGlobal()));
}