diff --git a/indra/llaudio/llstreamingaudio.h b/indra/llaudio/llstreamingaudio.h index 009327c7c..0e81cc530 100644 --- a/indra/llaudio/llstreamingaudio.h +++ b/indra/llaudio/llstreamingaudio.h @@ -53,7 +53,11 @@ class LLStreamingAudioInterface virtual void setGain(F32 vol) = 0; virtual F32 getGain() = 0; virtual std::string getURL() = 0; - virtual const LLSD *getMetaData() = 0; //return NULL if not supported. + + virtual bool supportsMetaData() = 0; + virtual const LLSD *getMetaData() = 0; + virtual bool supportsWaveData() = 0; + virtual bool getWaveData(float* arr, S32 count, S32 stride = 1) = 0; }; #endif // LL_STREAMINGAUDIO_H diff --git a/indra/llaudio/llstreamingaudio_fmod.h b/indra/llaudio/llstreamingaudio_fmod.h index 5c4597fae..c8a940eed 100644 --- a/indra/llaudio/llstreamingaudio_fmod.h +++ b/indra/llaudio/llstreamingaudio_fmod.h @@ -54,8 +54,11 @@ class LLStreamingAudio_FMOD : public LLStreamingAudioInterface /*virtual*/ void setGain(F32 vol); /*virtual*/ F32 getGain(); /*virtual*/ std::string getURL(); - /*virtual*/ const LLSD *getMetaData(){return mMetaData;} //return NULL if not supported. + /*virtual*/ bool supportsMetaData(){return true;} + /*virtual*/ const LLSD *getMetaData(){return mMetaData;} //return NULL if not playing. + /*virtual*/ bool supportsWaveData(){return false;} + /*virtual*/ bool getWaveData(float* arr, S32 count, S32 stride = 1){return false}; private: LLAudioStreamManagerFMOD *mCurrentInternetStreamp; int mFMODInternetStreamChannel; diff --git a/indra/llaudio/llstreamingaudio_fmodex.cpp b/indra/llaudio/llstreamingaudio_fmodex.cpp index ce46e0162..741af65d1 100644 --- a/indra/llaudio/llstreamingaudio_fmodex.cpp +++ b/indra/llaudio/llstreamingaudio_fmodex.cpp @@ -334,6 +334,27 @@ void LLStreamingAudio_FMODEX::setGain(F32 vol) } } +/*virtual*/ bool LLStreamingAudio_FMODEX::getWaveData(float* arr, S32 count, S32 stride/*=1*/) +{ + if(!mFMODInternetStreamChannelp || !mCurrentInternetStreamp) + return false; + + static std::vector local_array(count); //Have to have an extra buffer to mix channels. Bleh. + if(count > (S32)local_array.size()) //Expand the array if needed. Try to minimize allocation calls, so don't ever shrink. + local_array.resize(count); + + if( mFMODInternetStreamChannelp->getWaveData(&local_array[0],count,0) == FMOD_OK && + mFMODInternetStreamChannelp->getWaveData(&arr[0],count,1) == FMOD_OK ) + { + for(S32 i = count;i>=0;i-=stride) + { + arr[i] += local_array[i]; + arr[i] *= .5f; + } + return true; + } + return false; +} /////////////////////////////////////////////////////// // manager of possibly-multiple internet audio streams diff --git a/indra/llaudio/llstreamingaudio_fmodex.h b/indra/llaudio/llstreamingaudio_fmodex.h index 0bb88fc8a..064b266e6 100644 --- a/indra/llaudio/llstreamingaudio_fmodex.h +++ b/indra/llaudio/llstreamingaudio_fmodex.h @@ -61,8 +61,11 @@ class LLStreamingAudio_FMODEX : public LLStreamingAudioInterface /*virtual*/ void setGain(F32 vol); /*virtual*/ F32 getGain(); /*virtual*/ std::string getURL(); - /*virtual*/ const LLSD *getMetaData(){return mMetaData;} //return NULL if not supported. + /*virtual*/ bool supportsMetaData(){return true;} + /*virtual*/ const LLSD *getMetaData(){return mMetaData;} //return NULL if not playing. + /*virtual*/ bool supportsWaveData(){return true;} + /*virtual*/ bool getWaveData(float* arr, S32 count, S32 stride = 1); private: FMOD::System *mSystem; diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index bd1e5a28f..39b74df92 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -537,6 +537,7 @@ set(viewer_SOURCE_FILES rlvfloaterbehaviour.cpp rlvviewer2.cpp shcommandhandler.cpp + shfloatermediaticker.cpp ) # This gets renamed in the packaging step @@ -1019,6 +1020,7 @@ set(viewer_HEADER_FILES rlvfloaterbehaviour.h rlvviewer2.h shcommandhandler.h + shfloatermediaticker.h ) source_group("CMake Rules" FILES ViewerInstall.cmake) diff --git a/indra/newview/app_settings/settings_sh.xml b/indra/newview/app_settings/settings_sh.xml index d1c5f640b..db1e8490f 100644 --- a/indra/newview/app_settings/settings_sh.xml +++ b/indra/newview/app_settings/settings_sh.xml @@ -1,6 +1,35 @@ + + SHShowMediaTicker + + Comment + Enable media ticker tool for supported audio libraries + Persist + 1 + Type + Boolean + Value + 0 + + SHMediaTickerRect + + Comment + Rectangle for media ticker + Persist + 1 + Type + Rect + Value + + 200 + 82 + 456 + 50 + + + SHEnableFMODExProfiler Comment diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index a23c3ddd1..191b8c918 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -214,6 +214,7 @@ #include "ascentdaycyclemanager.h" #include "llfloaterblacklist.h" #include "scriptcounter.h" +#include "shfloatermediaticker.h" // #include "llavatarnamecache.h" @@ -2053,6 +2054,10 @@ bool idle_startup() { LLFloaterAvatarList::createInstance(false); } + if (gSavedSettings.getBOOL("SHShowMediaTicker")) + { + SHFloaterMediaTicker::showInstance(); + } // if (gSavedSettings.getBOOL("ShowCameraControls")) { @@ -2073,6 +2078,8 @@ bool idle_startup() LLFloaterBeacons::showInstance(); } + + if (!gNoRender) { //Set up cloud rendertypes. Passed argument is unused. diff --git a/indra/newview/llviewergesture.cpp b/indra/newview/llviewergesture.cpp index 2c0a7739a..a8129875a 100644 --- a/indra/newview/llviewergesture.cpp +++ b/indra/newview/llviewergesture.cpp @@ -51,6 +51,7 @@ #include "chatbar_as_cmdline.h" #if SHY_MOD //Command handler +#include "llvoavatarself.h" #include "shcommandhandler.h" #endif //shy_mod @@ -109,7 +110,6 @@ BOOL LLViewerGesture::trigger(const std::string &trigger_string) } } - // private void LLViewerGesture::doTrigger( BOOL send_chat ) { diff --git a/indra/newview/llviewermedia_streamingaudio.h b/indra/newview/llviewermedia_streamingaudio.h index e5093f974..cba667861 100644 --- a/indra/newview/llviewermedia_streamingaudio.h +++ b/indra/newview/llviewermedia_streamingaudio.h @@ -55,7 +55,11 @@ class LLStreamingAudio_MediaPlugins : public LLStreamingAudioInterface /*virtual*/ void setGain(F32 vol); /*virtual*/ F32 getGain(); /*virtual*/ std::string getURL(); - /*virtual*/ LLSD *getMetaData(){return NULL;} //return NULL if not supported. + + /*virtual*/ bool supportsMetaData(){return false;} + /*virtual*/ LLSD *getMetaData(){return NULL;} //return NULL if not playing. + /*virtual*/ virtual bool supportsWaveData(){return false;} + /*virtual*/ virtual bool getWaveData(float* arr, S32 count, S32 stride = 1){return false;} private: LLPluginClassMedia* initializeMedia(const std::string& media_type); diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index 9b3186ba2..ffc19fda5 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -247,6 +247,7 @@ #include "llfloatermessagelog.h" #include "llfloatervfs.h" #include "llfloatervfsexplorer.h" +#include "shfloatermediaticker.h" // #include "scriptcounter.h" @@ -825,6 +826,8 @@ void init_menus() &handle_sounds_explorer, NULL)); menu->append(new LLMenuItemCallGL( "Asset Blacklist", &handle_blacklist, NULL)); + menu->append(new LLMenuItemCheckGL( "Streaming Audio Display", + &handle_ticker_toggle, &handle_ticker_enabled, &handle_ticker_check, NULL )); diff --git a/indra/newview/shfloatermediaticker.cpp b/indra/newview/shfloatermediaticker.cpp new file mode 100644 index 000000000..92f2df399 --- /dev/null +++ b/indra/newview/shfloatermediaticker.cpp @@ -0,0 +1,262 @@ + +#include "llviewerprecompiledheaders.h" + +#include "shfloatermediaticker.h" + +// Library includes +#include "llaudioengine.h" +#include "lliconctrl.h" +#include "llstreamingaudio.h" +#include "lluictrlfactory.h" + +// Viewer includes +#include "llviewercontrol.h" + + +SHFloaterMediaTicker::SHFloaterMediaTicker() : LLFloater()/*, LLSingleton()*/, + mPlayState(STATE_PAUSED), + mArtistScrollChars(0), + mTitleScrollChars(0), + mCurScrollChar(0), + mTickerBackground(NULL), + mArtistText(NULL), + mTitleText(NULL), + mVisualizer(NULL) +{ + setIsChrome(TRUE); + LLUICtrlFactory::getInstance()->buildFloater(this, "sh_floater_media_ticker.xml"); +} + +/*virtual*/ BOOL SHFloaterMediaTicker::postBuild() +{ + mTickerBackground = getChild("ticker_background"); + mArtistText = getChild("artist_text",true,false); + mTitleText = getChild("title_text",true,false); + mVisualizer = getChild("visualizer_box",true,false); + mszLoading = getString("loading"); + mszPaused = getString("paused"); + + if(mArtistText) mArtistText->setText(mszPaused); + if(mTitleText) mTitleText->setText(mszPaused); + + if(!gAudiop->getStreamingAudioImpl()->supportsWaveData()) //Can't visualize. Extend textboxes. + { + if(mArtistText) + { + LLRect text_rect = mArtistText->getRect(); + text_rect.mRight = llmax(mTickerBackground->getRect().mRight-2,text_rect.mRight); + mArtistText->setRect(text_rect); + } + if(mTitleText) + { + LLRect text_rect = mTitleText->getRect(); + text_rect.mRight = llmax(mTickerBackground->getRect().mRight-2,text_rect.mRight); + mArtistText->setRect(text_rect); + } + } + + return LLFloater::postBuild(); +} + +/*virtual*/ void SHFloaterMediaTicker::draw() +{ + updateTickerText(); + LLFloater::draw(); + drawOscilloscope(); +} + +/*virtual*/ void SHFloaterMediaTicker::onOpen() +{ + LLFloater::onOpen(); + + gSavedSettings.setBOOL("SHShowMediaTicker", TRUE); +} +/*virtual*/ void SHFloaterMediaTicker::onClose(bool app_quitting) +{ + LLFloater::onClose(app_quitting); + + if (!app_quitting) + { + gSavedSettings.setBOOL("SHShowMediaTicker", FALSE); + delete this; + } +} + +void SHFloaterMediaTicker::updateTickerText() //called via draw. +{ + bool stream_paused = gAudiop->getStreamingAudioImpl()->isPlaying() != 1; //will return 1 if playing. + + bool dirty = setPaused(stream_paused); + if(!stream_paused) + { + const LLSD* metadata = gAudiop->getStreamingAudioImpl()->getMetaData(); + LLSD artist = metadata ? metadata->get("ARTIST") : LLSD(); + LLSD title = metadata ? metadata->get("TITLE") : LLSD(); + + dirty |= setArtist(artist.isDefined() ? artist.asString() : mszLoading); + dirty |= setTitle(title.isDefined() ? title.asString() : mszLoading); + + if(dirty) + resetTicker(); + else iterateTickerOffset(); + } +} + +void SHFloaterMediaTicker::drawOscilloscope() //called via draw. +{ + if(!mVisualizer || !gAudiop->getStreamingAudioImpl()->supportsWaveData()) + return; + + static const S32 NUM_LINE_STRIPS = 64; //How many lines to draw. 64 is more than enough. + static const S32 WAVE_DATA_STEP_SIZE = 4; //Increase to provide more history at expense of cpu/memory. + + static const S32 NUM_WAVE_DATA_VALUES = NUM_LINE_STRIPS*WAVE_DATA_STEP_SIZE; //Actual buffer size. Don't toy with this. Change above vars to tweak. + static F32 buf[NUM_WAVE_DATA_VALUES]; + + if(!gAudiop->getStreamingAudioImpl()->getWaveData(&buf[0],NUM_WAVE_DATA_VALUES,WAVE_DATA_STEP_SIZE)) + return; + + LLRect root_rect = mVisualizer->getRect(); + + F32 height = root_rect.getHeight(); + F32 height_scale = height / 2.f; //WaveData ranges from 1 to -1, so height_scale = height / 2 + F32 width_scale = root_rect.getWidth() / (F32)NUM_WAVE_DATA_VALUES; + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + gGL.color4f(0.f,0.f,0.f,.75f); + gGL.pushMatrix(); + gGL.translatef((F32)root_rect.mLeft, (F32)root_rect.mBottom + height*.5f, 0.f); + gGL.begin( LLRender::LINE_STRIP ); + if((mPlayState != STATE_PAUSED)) + { + for(S32 i = NUM_WAVE_DATA_VALUES; i>=0;i-=WAVE_DATA_STEP_SIZE) + gGL.vertex2f((F32)i * width_scale, buf[i]*height_scale); + } + else + { + gGL.vertex2f(0.f, 0.f); + gGL.vertex2f(root_rect.getWidth(), 0.f); + } + gGL.end(); + gGL.popMatrix(); + gGL.flush(); +} + +bool SHFloaterMediaTicker::setPaused(bool pause) +{ + if(pause == (mPlayState == STATE_PAUSED)) + return false; + mPlayState = pause ? STATE_PAUSED : STATE_PLAYING; + if(pause) + { + if(mArtistText) mArtistText->setText(mszPaused); + if(mTitleText) mTitleText->setText(mszPaused); + } + return true; +} + +void SHFloaterMediaTicker::resetTicker() +{ + mScrollTimer.reset(); + mCurScrollChar=0; + if(mArtistText) mArtistText->setText(LLStringExplicit(mszArtist.substr(0,mszArtist.length()-mArtistScrollChars))); + if(mTitleText) mTitleText->setText(LLStringExplicit(mszTitle.substr(0,mszTitle.length()-mTitleScrollChars))); +} + +bool SHFloaterMediaTicker::setArtist(const std::string &artist) +{ + if(!mArtistText || mszArtist == artist) + return false; + mszArtist = artist; + mArtistText->setText(mszArtist); + mArtistScrollChars = countExtraChars(mArtistText,mszArtist); + return true; +} + +bool SHFloaterMediaTicker::setTitle(const std::string &title) +{ + if(!mTitleText || mszTitle == title) + return false; + mszTitle=title; + mTitleText->setText(mszTitle); + mTitleScrollChars = countExtraChars(mTitleText,mszTitle); + return true; +} + +S32 SHFloaterMediaTicker::countExtraChars(LLTextBox *texbox, const std::string &text) +{ + S32 text_width = texbox->getTextPixelWidth(); + S32 box_width = texbox->getRect().getWidth(); + if(text_width > box_width) + { + const LLFontGL* font = texbox->getFont(); + for(S32 count = 1;count<(S32)text.length();count++) + { + //This isn't very efficient... + const std::string substr = text.substr(0,text.length()-count); + if(font->getWidth(substr) <= box_width) + return count; + } + } + return 0; +} + +void SHFloaterMediaTicker::iterateTickerOffset() +{ + if( (mPlayState != STATE_PAUSED) && + (mArtistScrollChars || mTitleScrollChars) && + ((!mCurScrollChar && mScrollTimer.getElapsedTimeF32() >= 5.f) || + ( mCurScrollChar && mScrollTimer.getElapsedTimeF32() >= .5f))) + { + if(++mCurScrollChar > llmax(mArtistScrollChars, mTitleScrollChars)) + { + if(mScrollTimer.getElapsedTimeF32() >= 2.f) //pause for a bit when it reaches beyond last character. + resetTicker(); + } + else + { + mScrollTimer.reset(); + if(mArtistText && mCurScrollChar <= mArtistScrollChars) + { + mArtistText->setText(LLStringExplicit(mszArtist.substr(mCurScrollChar,mszArtist.length()-mArtistScrollChars+mCurScrollChar))); + } + if(mTitleText && mCurScrollChar <= mTitleScrollChars) + { + mTitleText->setText(LLStringExplicit(mszTitle.substr(mCurScrollChar,mszTitle.length()-mTitleScrollChars+mCurScrollChar))); + } + } + } +} + +/*static*/ +void SHFloaterMediaTicker::showInstance() +{ + if(!handle_ticker_enabled(NULL)) + return; + if(!SHFloaterMediaTicker::instanceExists()) + { + SHFloaterMediaTicker::getInstance(); + } +} + +BOOL handle_ticker_enabled(void *) +{ + return gAudiop && gAudiop->getStreamingAudioImpl() && gAudiop->getStreamingAudioImpl()->supportsMetaData(); +} +BOOL handle_ticker_check(void *) +{ + return SHFloaterMediaTicker::instanceExists(); +} +void handle_ticker_toggle(void *) +{ + if(!handle_ticker_enabled(NULL)) + return; + if(!SHFloaterMediaTicker::instanceExists()) + { + SHFloaterMediaTicker::getInstance(); + } + else + { + SHFloaterMediaTicker::getInstance()->close(); + } +} \ No newline at end of file diff --git a/indra/newview/shfloatermediaticker.h b/indra/newview/shfloatermediaticker.h new file mode 100644 index 000000000..cfc0475e7 --- /dev/null +++ b/indra/newview/shfloatermediaticker.h @@ -0,0 +1,55 @@ +#include "llfloater.h" + +class LLIconCtrl; + +class SHFloaterMediaTicker : public LLFloater, public LLSingleton +{ + friend class LLSingleton; +public: + SHFloaterMediaTicker(); //ctor + + virtual ~SHFloaterMediaTicker() {} + /*virtual*/ BOOL postBuild(); + /*virtual*/ void draw(); + /*virtual*/ void onOpen(); + /*virtual*/ void onClose(bool app_quitting); + + static void showInstance(); //use to create. +private: + void updateTickerText(); //called via draw. + void drawOscilloscope(); //called via draw. + bool setPaused(bool pause); //returns true on state change. + void resetTicker(); //Resets tickers to their innitial values (no offset). + bool setArtist(const std::string &artist); //returns true on change + bool setTitle(const std::string &title); //returns true on change + S32 countExtraChars(LLTextBox *texbox, const std::string &text); //calculates how many characters are truncated by bounds. + void iterateTickerOffset(); //Logic that actually shuffles the text to the left. + + enum ePlayState + { + STATE_PAUSED, + STATE_PLAYING + }; + + ePlayState mPlayState; + std::string mszLoading; + std::string mszPaused; + std::string mszArtist; + std::string mszTitle; + LLTimer mScrollTimer; + S32 mArtistScrollChars; + S32 mTitleScrollChars; + S32 mCurScrollChar; + + //UI elements + LLIconCtrl* mTickerBackground; + LLTextBox* mArtistText; + LLTextBox* mTitleText; + LLUICtrl* mVisualizer; +}; + +//Menu callbacks. +BOOL handle_ticker_enabled(void *); +BOOL handle_ticker_check(void *); +void handle_ticker_toggle(void *); + diff --git a/indra/newview/skins/default/textures/ticker_background_small.tga b/indra/newview/skins/default/textures/ticker_background_small.tga new file mode 100644 index 000000000..ce3bb3fcd Binary files /dev/null and b/indra/newview/skins/default/textures/ticker_background_small.tga differ diff --git a/indra/newview/skins/default/xui/en-us/sh_floater_media_ticker.xml b/indra/newview/skins/default/xui/en-us/sh_floater_media_ticker.xml new file mode 100644 index 000000000..f95f2d09f --- /dev/null +++ b/indra/newview/skins/default/xui/en-us/sh_floater_media_ticker.xml @@ -0,0 +1,39 @@ + + + + + Artist: + + + + + Title: + + + + + + + + (not playing) + + + (loading...) + +