/** * @file llviewermedia.cpp * @brief Client interface to the media engine * * $LicenseInfo:firstyear=2007&license=viewergpl$ * * Copyright (c) 2007-2008, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or * online at http://secondlife.com/developers/opensource/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or * online at http://secondlife.com/developers/opensource/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. * $/LicenseInfo$ */ #include "llviewerprecompiledheaders.h" #include "llviewermedia.h" #include "llmimetypes.h" #include "llviewercontrol.h" #include "llviewerimage.h" #include "llviewerwindow.h" #include "llversionviewer.h" #include "llviewerimagelist.h" #include "llevent.h" // LLSimpleListener #include "llmediamanager.h" #include "lluuid.h" #include // for SkinFolder listener #include // Implementation functions not exported into header file class LLViewerMediaImpl : public LLMediaObserver { public: LLViewerMediaImpl() : mMediaSource( NULL ), mMovieImageID(), mMovieImageHasMips(false) { } void destroyMediaSource(); void play(const std::string& media_url, const std::string& mime_type, const LLUUID& placeholder_texture_id, S32 media_width, S32 media_height, U8 media_auto_scale, U8 media_loop); void stop(); void pause(); void start(); void seek(F32 time); void setVolume(F32 volume); LLMediaBase::EStatus getStatus(); /*virtual*/ void onMediaSizeChange(const EventType& event_in); /*virtual*/ void onMediaContentsChange(const EventType& event_in); void updateMovieImage(const LLUUID& image_id, BOOL active); void updateImagesMediaStreams(); LLUUID getMediaTextureID(); // Internally set our desired browser user agent string, including // the Second Life version and skin name. Used because we can // switch skins without restarting the app. static void updateBrowserUserAgent(); // Callback for when the SkinCurrent control is changed to // switch the user agent string to indicate the new skin. static bool handleSkinCurrentChanged(const LLSD& newvalue); public: // a single media url with some data and an impl. LLMediaBase* mMediaSource; LLUUID mMovieImageID; bool mMovieImageHasMips; std::string mMediaURL; std::string mMimeType; private: void initializePlaceholderImage(LLViewerImage *placeholder_image, LLMediaBase *media_source); }; static LLViewerMediaImpl sViewerMediaImpl; ////////////////////////////////////////////////////////////////////////////////////////// void LLViewerMediaImpl::destroyMediaSource() { LLMediaManager* mgr = LLMediaManager::getInstance(); if ( mMediaSource ) { bool was_playing = LLViewerMedia::isMediaPlaying(); mMediaSource->remObserver(this); mgr->destroySource( mMediaSource ); // Restore the texture updateMovieImage(LLUUID::null, was_playing); } mMediaSource = NULL; } void LLViewerMediaImpl::play(const std::string& media_url, const std::string& mime_type, const LLUUID& placeholder_texture_id, S32 media_width, S32 media_height, U8 media_auto_scale, U8 media_loop) { // first stop any previously playing media stop(); // Save this first, as init/load below may fire events mMovieImageID = placeholder_texture_id; // If the mime_type passed in is different than the cached one, and // Auto-discovery is turned OFF, replace the cached mime_type with the new one. if(mime_type != mMimeType && ! gSavedSettings.getBOOL("AutoMimeDiscovery")) { mMimeType = mime_type; } LLURI url(media_url); std::string scheme = url.scheme() != "" ? url.scheme() : "http"; LLMediaManager* mgr = LLMediaManager::getInstance(); mMediaSource = mgr->createSourceFromMimeType(scheme, mMimeType ); if ( !mMediaSource ) { if (mMimeType != "none/none") { llwarns << "media source create failed " << media_url << " type " << mMimeType << llendl; } return; } // Store the URL and Mime Type mMediaURL = media_url; if ((media_width != 0) && (media_height != 0)) { mMediaSource->setRequestedMediaSize(media_width, media_height); } mMediaSource->setLooping(media_loop); mMediaSource->setAutoScaled(media_auto_scale); mMediaSource->addObserver( this ); mMediaSource->navigateTo( media_url ); mMediaSource->addCommand(LLMediaBase::COMMAND_START); } void LLViewerMediaImpl::stop() { destroyMediaSource(); } void LLViewerMediaImpl::pause() { if(mMediaSource) { mMediaSource->addCommand(LLMediaBase::COMMAND_PAUSE); } } void LLViewerMediaImpl::start() { if(mMediaSource) { mMediaSource->addCommand(LLMediaBase::COMMAND_START); } } void LLViewerMediaImpl::seek(F32 time) { if(mMediaSource) { mMediaSource->seek(time); } } void LLViewerMediaImpl::setVolume(F32 volume) { if(mMediaSource) { mMediaSource->setVolume( volume); } } LLMediaBase::EStatus LLViewerMediaImpl::getStatus() { if (mMediaSource) { return mMediaSource->getStatus(); } else { return LLMediaBase::STATUS_UNKNOWN; } } ////////////////////////////////////////////////////////////////////////////////////////// // static void LLViewerMediaImpl::updateMovieImage(const LLUUID& uuid, BOOL active) { // IF the media image hasn't changed, do nothing if (mMovieImageID == uuid) { return; } // If we have changed media uuid, restore the old one if (!mMovieImageID.isNull()) { LLViewerImage* oldImage = LLViewerImage::getImage( mMovieImageID ); if (oldImage) { oldImage->reinit(mMovieImageHasMips); oldImage->mIsMediaTexture = FALSE; } mMovieImageID.setNull(); } // If the movie is playing, set the new media image if (active && !uuid.isNull()) { LLViewerImage* viewerImage = LLViewerImage::getImage( uuid ); if( viewerImage ) { mMovieImageID = uuid; // Can't use mipmaps for movies because they don't update the full image mMovieImageHasMips = viewerImage->getUseMipMaps(); viewerImage->reinit(FALSE); viewerImage->mIsMediaTexture = TRUE; } } } ////////////////////////////////////////////////////////////////////////////////////////// // static void LLViewerMediaImpl::updateImagesMediaStreams() { LLMediaManager::updateClass(); } void LLViewerMediaImpl::initializePlaceholderImage(LLViewerImage *placeholder_image, LLMediaBase *media_source) { int media_width = media_source->getMediaWidth(); int media_height = media_source->getMediaHeight(); //int media_rowspan = media_source->getMediaRowSpan(); // if width & height are invalid, don't bother doing anything if ( media_width < 1 || media_height < 1 ) return; llinfos << "initializing media placeholder" << llendl; llinfos << "movie image id " << mMovieImageID << llendl; int texture_width = LLMediaManager::textureWidthFromMediaWidth( media_width ); int texture_height = LLMediaManager::textureHeightFromMediaHeight( media_height ); int texture_depth = media_source->getMediaDepth(); // MEDIAOPT: check to see if size actually changed before doing work placeholder_image->destroyGLTexture(); // MEDIAOPT: apparently just calling setUseMipMaps(FALSE) doesn't work? placeholder_image->reinit(FALSE); // probably not needed // MEDIAOPT: seems insane that we actually have to make an imageraw then // immediately discard it LLPointer raw = new LLImageRaw(texture_width, texture_height, texture_depth); raw->clear(0x0f, 0x0f, 0x0f, 0xff); int discard_level = 0; // ask media source for correct GL image format constants placeholder_image->setExplicitFormat(media_source->getTextureFormatInternal(), media_source->getTextureFormatPrimary(), media_source->getTextureFormatType()); placeholder_image->createGLTexture(discard_level, raw); // placeholder_image->setExplicitFormat() placeholder_image->setUseMipMaps(FALSE); // MEDIAOPT: set this dynamically on play/stop placeholder_image->mIsMediaTexture = true; } // virtual void LLViewerMediaImpl::onMediaContentsChange(const EventType& event_in) { LLMediaBase* media_source = event_in.getSubject(); LLViewerImage* placeholder_image = gImageList.getImage( mMovieImageID ); if ((placeholder_image) && (placeholder_image->getHasGLTexture())) { if (placeholder_image->getUseMipMaps()) { // bad image! NO MIPMAPS! initializePlaceholderImage(placeholder_image, media_source); } U8* data = media_source->getMediaData(); S32 x_pos = 0; S32 y_pos = 0; S32 width = media_source->getMediaWidth(); S32 height = media_source->getMediaHeight(); S32 data_width = media_source->getMediaDataWidth(); S32 data_height = media_source->getMediaDataHeight(); placeholder_image->setSubImage(data, data_width, data_height, x_pos, y_pos, width, height); } } // virtual void LLViewerMediaImpl::onMediaSizeChange(const EventType& event_in) { LLMediaBase* media_source = event_in.getSubject(); LLViewerImage* placeholder_image = gImageList.getImage( mMovieImageID ); if (placeholder_image) { initializePlaceholderImage(placeholder_image, media_source); } else { llinfos << "no placeholder image" << llendl; } } // Get the image we're using /* // update media stream if required LLMediaEngine* media_engine = LLMediaEngine::getInstance(); if (media_engine) { if ( media_engine->update() ) { LLUUID media_uuid = media_engine->getImageUUID(); updateMovieImage(media_uuid, TRUE); if (!media_uuid.isNull()) { LLViewerImage* viewerImage = getImage( media_uuid ); if( viewerImage ) { LLMediaBase* renderer = media_engine->getMediaRenderer(); if ((renderer->getTextureWidth() != viewerImage->getWidth()) || (renderer->getTextureHeight() != viewerImage->getHeight()) || (renderer->getTextureDepth() != viewerImage->getComponents()) || (viewerImage->getHasGLTexture() == FALSE)) { // destroy existing GL image viewerImage->destroyGLTexture(); // set new size viewerImage->setSize( renderer->getTextureWidth(), renderer->getTextureHeight(), renderer->getTextureDepth() ); LLPointer raw = new LLImageRaw(renderer->getTextureWidth(), renderer->getTextureHeight(), renderer->getTextureDepth()); raw->clear(0x7f,0x7f,0x7f,0xff); viewerImage->createGLTexture(0, raw); } // Set the explicit format the instance wants viewerImage->setExplicitFormat(renderer->getTextureFormatInternal(), renderer->getTextureFormatPrimary(), renderer->getTextureFormatType(), renderer->getTextureFormatSwapBytes()); // This should be redundant, but just in case: viewerImage->setUseMipMaps(FALSE); LLImageRaw* rawImage = media_engine->getImageRaw(); if ( rawImage ) { viewerImage->setSubImage(rawImage, 0, 0, renderer->getMediaWidth(), renderer->getMediaHeight()); } } else { llwarns << "MediaEngine update unable to get viewer image for GL texture" << llendl; } } } else { LLUUID media_uuid = media_engine->getImageUUID(); updateMovieImage(media_uuid, FALSE); } } */ LLUUID LLViewerMediaImpl::getMediaTextureID() { return mMovieImageID; } // static void LLViewerMediaImpl::updateBrowserUserAgent() { // Don't use user-visible string to avoid // punctuation and strange characters. std::string skin_name = gSavedSettings.getString("SkinCurrent"); // Just in case we need to check browser differences in A/B test // builds. std::string channel = gSavedSettings.getString("VersionChannelName"); // append our magic version number string to the browser user agent id // See the HTTP 1.0 and 1.1 specifications for allowed formats: // http://www.ietf.org/rfc/rfc1945.txt section 10.15 // http://www.ietf.org/rfc/rfc2068.txt section 3.8 // This was also helpful: // http://www.mozilla.org/build/revised-user-agent-strings.html std::ostringstream codec; codec << "SecondLife/"; codec << LL_VERSION_MAJOR << "." << LL_VERSION_MINOR << "." << LL_VERSION_PATCH << "." << LL_VERSION_BUILD; codec << " (" << channel << "; " << skin_name << " skin)"; llinfos << codec.str() << llendl; LLMediaManager::setBrowserUserAgent( codec.str() ); } // static bool LLViewerMediaImpl::handleSkinCurrentChanged(const LLSD& /*newvalue*/) { // gSavedSettings is already updated when this function is called. updateBrowserUserAgent(); return true; } ////////////////////////////////////////////////////////////////////////////////////////// // Wrapper class ////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////// // The viewer takes a long time to load the start screen. Part of the problem // is media initialization -- in particular, QuickTime loads many DLLs and // hits the disk heavily. So we initialize only the browser component before // the login screen, then do the rest later when we have a progress bar. JC // static void LLViewerMedia::initBrowser() { LLMediaManagerData* init_data = new LLMediaManagerData; buildMediaManagerData( init_data ); LLMediaManager::initBrowser( init_data ); delete init_data; } ////////////////////////////////////////////////////////////////////////////////////////// // static void LLViewerMedia::initClass() { // *TODO: This looks like a memory leak to me. JC LLMediaManagerData* init_data = new LLMediaManagerData; buildMediaManagerData( init_data ); LLMediaManager::initClass( init_data ); delete init_data; LLMediaManager* mm = LLMediaManager::getInstance(); LLMIMETypes::mime_info_map_t::const_iterator it; for (it = LLMIMETypes::sMap.begin(); it != LLMIMETypes::sMap.end(); ++it) { const std::string& mime_type = it->first; const LLMIMETypes::LLMIMEInfo& info = it->second; mm->addMimeTypeImplNameMap( mime_type, info.mImpl ); } } ////////////////////////////////////////////////////////////////////////////////////////// // static void LLViewerMedia::buildMediaManagerData( LLMediaManagerData* init_data ) { // std::string executable_dir = std::string( arg0 ).substr( 0, std::string( arg0 ).find_last_of("\\/") ); // std::string component_dir = std::string( executable_dir ).substr( 0, std::string( executable_dir ).find_last_of("\\/") ); // component_dir = std::string( component_dir ).substr( 0, std::string( component_dir ).find_last_of("\\/") ); // component_dir = std::string( component_dir ).substr( 0, std::string( component_dir ).find_last_of("\\/") ); // component_dir += "\\newview\\app_settings\\mozilla"; #if LL_DARWIN // For Mac OS, we store both the shared libraries and the runtime files (chrome/, plugins/, etc) in // Second Life.app/Contents/MacOS/. This matches the way Firefox is distributed on the Mac. std::string component_dir(gDirUtilp->getExecutableDir()); #elif LL_WINDOWS std::string component_dir( gDirUtilp->getExpandedFilename( LL_PATH_APP_SETTINGS, "" ) ); component_dir += gDirUtilp->getDirDelimiter(); #ifdef LL_DEBUG component_dir += "mozilla_debug"; #else // LL_DEBUG component_dir += "mozilla"; #endif // LL_DEBUG #elif LL_LINUX std::string component_dir( gDirUtilp->getExpandedFilename( LL_PATH_APP_SETTINGS, "" ) ); component_dir += gDirUtilp->getDirDelimiter(); component_dir += "mozilla-runtime-linux-i686"; #else std::string component_dir( gDirUtilp->getExpandedFilename( LL_PATH_APP_SETTINGS, "" ) ); component_dir += gDirUtilp->getDirDelimiter(); component_dir += "mozilla"; #endif std::string application_dir = gDirUtilp->getExecutableDir(); init_data->setBrowserApplicationDir( application_dir ); std::string profile_dir = gDirUtilp->getExpandedFilename( LL_PATH_MOZILLA_PROFILE, "" ); init_data->setBrowserProfileDir( profile_dir ); init_data->setBrowserComponentDir( component_dir ); std::string profile_name("Second Life"); init_data->setBrowserProfileName( profile_name ); init_data->setBrowserParentWindow( gViewerWindow->getMediaWindow() ); // We use a custom user agent with viewer version and skin name. LLViewerMediaImpl::updateBrowserUserAgent(); // Users can change skins while client is running, so make sure // we pick up on changes. gSavedSettings.getControl("SkinCurrent")->getSignal()->connect( boost::bind( LLViewerMediaImpl::handleSkinCurrentChanged, _1 ) ); } ////////////////////////////////////////////////////////////////////////////////////////// // static void LLViewerMedia::cleanupClass() { LLMediaManager::cleanupClass(); } // static void LLViewerMedia::play(const std::string& media_url, const std::string& mime_type, const LLUUID& placeholder_texture_id, S32 media_width, S32 media_height, U8 media_auto_scale, U8 media_loop) { sViewerMediaImpl.play(media_url, mime_type, placeholder_texture_id, media_width, media_height, media_auto_scale, media_loop); } // static void LLViewerMedia::stop() { sViewerMediaImpl.stop(); } // static void LLViewerMedia::pause() { sViewerMediaImpl.pause(); } // static void LLViewerMedia::start() { sViewerMediaImpl.start(); } // static void LLViewerMedia::seek(F32 time) { sViewerMediaImpl.seek(time); } // static void LLViewerMedia::setVolume(F32 volume) { sViewerMediaImpl.setVolume(volume); } // static LLMediaBase::EStatus LLViewerMedia::getStatus() { return sViewerMediaImpl.getStatus(); } ////////////////////////////////////////////////////////////////////////////////////////// // static LLUUID LLViewerMedia::getMediaTextureID() { return sViewerMediaImpl.getMediaTextureID(); } ////////////////////////////////////////////////////////////////////////////////////////// // static bool LLViewerMedia::getMediaSize(S32 *media_width, S32 *media_height) { // make sure we're valid if ( sViewerMediaImpl.mMediaSource != NULL ) { *media_width = sViewerMediaImpl.mMediaSource->getMediaWidth(); *media_height = sViewerMediaImpl.mMediaSource->getMediaHeight(); return true; } return false; } ////////////////////////////////////////////////////////////////////////////////////////// // static bool LLViewerMedia::getTextureSize(S32 *texture_width, S32 *texture_height) { if ( sViewerMediaImpl.mMediaSource != NULL ) { S32 media_width = sViewerMediaImpl.mMediaSource->getMediaWidth(); S32 media_height = sViewerMediaImpl.mMediaSource->getMediaHeight(); *texture_width = LLMediaManager::textureWidthFromMediaWidth( media_width ); *texture_height = LLMediaManager::textureHeightFromMediaHeight( media_height ); return true; } return false; } ////////////////////////////////////////////////////////////////////////////////////////// // static void LLViewerMedia::updateImagesMediaStreams() { sViewerMediaImpl.updateImagesMediaStreams(); } ////////////////////////////////////////////////////////////////////////////////////////// // static bool LLViewerMedia::isMediaPlaying() { LLMediaBase::EStatus status = sViewerMediaImpl.getStatus(); return (status == LLMediaBase::STATUS_STARTED ); } ////////////////////////////////////////////////////////////////////////////////////////// // static bool LLViewerMedia::isMediaPaused() { LLMediaBase::EStatus status = sViewerMediaImpl.getStatus(); return (status == LLMediaBase::STATUS_PAUSED); } ////////////////////////////////////////////////////////////////////////////////////////// // static bool LLViewerMedia::hasMedia() { return sViewerMediaImpl.mMediaSource != NULL; } ////////////////////////////////////////////////////////////////////////////////////////// //static bool LLViewerMedia::isActiveMediaTexture(const LLUUID& id) { return (id.notNull() && id == getMediaTextureID() && isMediaPlaying()); } ////////////////////////////////////////////////////////////////////////////////////////// // static std::string LLViewerMedia::getMediaURL() { return sViewerMediaImpl.mMediaURL; } ////////////////////////////////////////////////////////////////////////////////////////// // static std::string LLViewerMedia::getMimeType() { return sViewerMediaImpl.mMimeType; } ////////////////////////////////////////////////////////////////////////////////////////// // static void LLViewerMedia::setMimeType(std::string mime_type) { sViewerMediaImpl.mMimeType = mime_type; }