/indra/media_plugins/quicktime/media_plugin_quicktime.cpp

https://bitbucket.org/lindenlab/viewer-beta/ · C++ · 1111 lines · 774 code · 175 blank · 162 comment · 167 complexity · a94a96996975821d2a44860dd88aba34 MD5 · raw file

  1. /**
  2. * @file media_plugin_quicktime.cpp
  3. * @brief QuickTime plugin for LLMedia API plugin system
  4. *
  5. * @cond
  6. * $LicenseInfo:firstyear=2008&license=viewerlgpl$
  7. * Second Life Viewer Source Code
  8. * Copyright (C) 2010, Linden Research, Inc.
  9. *
  10. * This library is free software; you can redistribute it and/or
  11. * modify it under the terms of the GNU Lesser General Public
  12. * License as published by the Free Software Foundation;
  13. * version 2.1 of the License only.
  14. *
  15. * This library is distributed in the hope that it will be useful,
  16. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  18. * Lesser General Public License for more details.
  19. *
  20. * You should have received a copy of the GNU Lesser General Public
  21. * License along with this library; if not, write to the Free Software
  22. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  23. *
  24. * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
  25. * $/LicenseInfo$
  26. * @endcond
  27. */
  28. #include "linden_common.h"
  29. #include "llgl.h"
  30. #include "llplugininstance.h"
  31. #include "llpluginmessage.h"
  32. #include "llpluginmessageclasses.h"
  33. #include "media_plugin_base.h"
  34. #if LL_QUICKTIME_ENABLED
  35. #if defined(LL_DARWIN)
  36. #include <QuickTime/QuickTime.h>
  37. #elif defined(LL_WINDOWS)
  38. #include "MacTypes.h"
  39. #include "QTML.h"
  40. #include "Movies.h"
  41. #include "QDoffscreen.h"
  42. #include "FixMath.h"
  43. #include "QTLoadLibraryUtils.h"
  44. #endif
  45. // TODO: Make sure that the only symbol exported from this library is LLPluginInitEntryPoint
  46. ////////////////////////////////////////////////////////////////////////////////
  47. //
  48. class MediaPluginQuickTime : public MediaPluginBase
  49. {
  50. public:
  51. MediaPluginQuickTime(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data);
  52. ~MediaPluginQuickTime();
  53. /* virtual */ void receiveMessage(const char *message_string);
  54. private:
  55. int mNaturalWidth;
  56. int mNaturalHeight;
  57. Movie mMovieHandle;
  58. GWorldPtr mGWorldHandle;
  59. ComponentInstance mMovieController;
  60. int mCurVolume;
  61. bool mMediaSizeChanging;
  62. bool mIsLooping;
  63. std::string mMovieTitle;
  64. bool mReceivedTitle;
  65. const int mMinWidth;
  66. const int mMaxWidth;
  67. const int mMinHeight;
  68. const int mMaxHeight;
  69. F64 mPlayRate;
  70. std::string mNavigateURL;
  71. enum ECommand {
  72. COMMAND_NONE,
  73. COMMAND_STOP,
  74. COMMAND_PLAY,
  75. COMMAND_FAST_FORWARD,
  76. COMMAND_FAST_REWIND,
  77. COMMAND_PAUSE,
  78. COMMAND_SEEK,
  79. };
  80. ECommand mCommand;
  81. // Override this to add current time and duration to the message
  82. /*virtual*/ void setDirty(int left, int top, int right, int bottom)
  83. {
  84. LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "updated");
  85. message.setValueS32("left", left);
  86. message.setValueS32("top", top);
  87. message.setValueS32("right", right);
  88. message.setValueS32("bottom", bottom);
  89. if(mMovieHandle)
  90. {
  91. message.setValueReal("current_time", getCurrentTime());
  92. message.setValueReal("duration", getDuration());
  93. message.setValueReal("current_rate", Fix2X(GetMovieRate(mMovieHandle)));
  94. }
  95. sendMessage(message);
  96. }
  97. static Rect rectFromSize(int width, int height)
  98. {
  99. Rect result;
  100. result.left = 0;
  101. result.top = 0;
  102. result.right = width;
  103. result.bottom = height;
  104. return result;
  105. }
  106. Fixed getPlayRate(void)
  107. {
  108. Fixed result;
  109. if(mPlayRate == 0.0f)
  110. {
  111. // Default to the movie's preferred rate
  112. result = GetMoviePreferredRate(mMovieHandle);
  113. if(result == 0)
  114. {
  115. // Don't return a 0 play rate, ever.
  116. std::cerr << "Movie's preferred rate is 0, forcing to 1.0." << std::endl;
  117. result = X2Fix(1.0f);
  118. }
  119. }
  120. else
  121. {
  122. result = X2Fix(mPlayRate);
  123. }
  124. return result;
  125. }
  126. void load( const std::string url )
  127. {
  128. if ( url.empty() )
  129. return;
  130. // Stop and unload any existing movie before starting another one.
  131. unload();
  132. setStatus(STATUS_LOADING);
  133. //In case std::string::c_str() makes a copy of the url data,
  134. //make sure there is memory to hold it before allocating memory for handle.
  135. //if fails, NewHandleClear(...) should return NULL.
  136. const char* url_string = url.c_str() ;
  137. Handle handle = NewHandleClear( ( Size )( url.length() + 1 ) );
  138. if ( NULL == handle || noErr != MemError() || NULL == *handle )
  139. {
  140. setStatus(STATUS_ERROR);
  141. return;
  142. }
  143. BlockMove( url_string, *handle, ( Size )( url.length() + 1 ) );
  144. OSErr err = NewMovieFromDataRef( &mMovieHandle, newMovieActive | newMovieDontInteractWithUser | newMovieAsyncOK | newMovieIdleImportOK, nil, handle, URLDataHandlerSubType );
  145. DisposeHandle( handle );
  146. if ( noErr != err )
  147. {
  148. setStatus(STATUS_ERROR);
  149. return;
  150. };
  151. mNavigateURL = url;
  152. LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "navigate_begin");
  153. message.setValue("uri", mNavigateURL);
  154. sendMessage(message);
  155. // do pre-roll actions (typically fired for streaming movies but not always)
  156. PrePrerollMovie( mMovieHandle, 0, getPlayRate(), moviePrePrerollCompleteCallback, ( void * )this );
  157. Rect movie_rect = rectFromSize(mWidth, mHeight);
  158. // make a new movie controller
  159. mMovieController = NewMovieController( mMovieHandle, &movie_rect, mcNotVisible | mcTopLeftMovie );
  160. // movie controller
  161. MCSetActionFilterWithRefCon( mMovieController, mcActionFilterCallBack, ( long )this );
  162. SetMoviePlayHints( mMovieHandle, hintsAllowDynamicResize, hintsAllowDynamicResize );
  163. // function that gets called when a frame is drawn
  164. SetMovieDrawingCompleteProc( mMovieHandle, movieDrawingCallWhenChanged, movieDrawingCompleteCallback, ( long )this );
  165. setStatus(STATUS_LOADED);
  166. sizeChanged();
  167. };
  168. bool unload()
  169. {
  170. // new movie and have to get title again
  171. mReceivedTitle = false;
  172. if ( mMovieHandle )
  173. {
  174. StopMovie( mMovieHandle );
  175. if ( mMovieController )
  176. {
  177. MCMovieChanged( mMovieController, mMovieHandle );
  178. };
  179. };
  180. if ( mMovieController )
  181. {
  182. MCSetActionFilterWithRefCon( mMovieController, NULL, (long)this );
  183. DisposeMovieController( mMovieController );
  184. mMovieController = NULL;
  185. };
  186. if ( mMovieHandle )
  187. {
  188. SetMovieDrawingCompleteProc( mMovieHandle, movieDrawingCallWhenChanged, nil, ( long )this );
  189. DisposeMovie( mMovieHandle );
  190. mMovieHandle = NULL;
  191. };
  192. if ( mGWorldHandle )
  193. {
  194. DisposeGWorld( mGWorldHandle );
  195. mGWorldHandle = NULL;
  196. };
  197. setStatus(STATUS_NONE);
  198. return true;
  199. }
  200. bool navigateTo( const std::string url )
  201. {
  202. unload();
  203. load( url );
  204. return true;
  205. };
  206. bool sizeChanged()
  207. {
  208. if ( ! mMovieHandle )
  209. return false;
  210. // Check to see whether the movie's natural size has updated
  211. {
  212. int width, height;
  213. getMovieNaturalSize(&width, &height);
  214. if((width != 0) && (height != 0) && ((width != mNaturalWidth) || (height != mNaturalHeight)))
  215. {
  216. mNaturalWidth = width;
  217. mNaturalHeight = height;
  218. LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "size_change_request");
  219. message.setValue("name", mTextureSegmentName);
  220. message.setValueS32("width", width);
  221. message.setValueS32("height", height);
  222. sendMessage(message);
  223. //std::cerr << "<--- Sending size change request to application with name: " << mTextureSegmentName << " - size is " << width << " x " << height << std::endl;
  224. }
  225. }
  226. // sanitize destination size
  227. Rect dest_rect = rectFromSize(mWidth, mHeight);
  228. // media depth won't change
  229. int depth_bits = mDepth * 8;
  230. long rowbytes = mDepth * mTextureWidth;
  231. GWorldPtr old_gworld_handle = mGWorldHandle;
  232. if(mPixels != NULL)
  233. {
  234. // We have pixels. Set up a GWorld pointing at the texture.
  235. OSErr result = NewGWorldFromPtr( &mGWorldHandle, depth_bits, &dest_rect, NULL, NULL, 0, (Ptr)mPixels, rowbytes);
  236. if ( noErr != result )
  237. {
  238. // TODO: unrecoverable?? throw exception? return something?
  239. return false;
  240. }
  241. }
  242. else
  243. {
  244. // We don't have pixels. Create a fake GWorld we can point the movie at when it's not safe to render normally.
  245. Rect tempRect = rectFromSize(1, 1);
  246. OSErr result = NewGWorld( &mGWorldHandle, depth_bits, &tempRect, NULL, NULL, 0);
  247. if ( noErr != result )
  248. {
  249. // TODO: unrecoverable?? throw exception? return something?
  250. return false;
  251. }
  252. }
  253. SetMovieGWorld( mMovieHandle, mGWorldHandle, GetGWorldDevice( mGWorldHandle ) );
  254. // If the GWorld was already set up, delete it.
  255. if(old_gworld_handle != NULL)
  256. {
  257. DisposeGWorld( old_gworld_handle );
  258. }
  259. // Set up the movie display matrix
  260. {
  261. // scale movie to fit rect and invert vertically to match opengl image format
  262. MatrixRecord transform;
  263. SetIdentityMatrix( &transform ); // transforms are additive so start from identify matrix
  264. double scaleX = (double) mWidth / mNaturalWidth;
  265. double scaleY = -1.0 * (double) mHeight / mNaturalHeight;
  266. double centerX = mWidth / 2.0;
  267. double centerY = mHeight / 2.0;
  268. ScaleMatrix( &transform, X2Fix( scaleX ), X2Fix( scaleY ), X2Fix( centerX ), X2Fix( centerY ) );
  269. SetMovieMatrix( mMovieHandle, &transform );
  270. }
  271. // update movie controller
  272. if ( mMovieController )
  273. {
  274. MCSetControllerPort( mMovieController, mGWorldHandle );
  275. MCPositionController( mMovieController, &dest_rect, &dest_rect,
  276. mcTopLeftMovie | mcPositionDontInvalidate );
  277. MCMovieChanged( mMovieController, mMovieHandle );
  278. }
  279. // Emit event with size change so the calling app knows about it too
  280. // TODO:
  281. //LLMediaEvent event( this );
  282. //mEventEmitter.update( &LLMediaObserver::onMediaSizeChange, event );
  283. return true;
  284. }
  285. static Boolean mcActionFilterCallBack( MovieController mc, short action, void *params, long ref )
  286. {
  287. Boolean result = false;
  288. MediaPluginQuickTime* self = ( MediaPluginQuickTime* )ref;
  289. switch( action )
  290. {
  291. // handle window resizing
  292. case mcActionControllerSizeChanged:
  293. // Ensure that the movie draws correctly at the new size
  294. self->sizeChanged();
  295. break;
  296. // Block any movie controller actions that open URLs.
  297. case mcActionLinkToURL:
  298. case mcActionGetNextURL:
  299. case mcActionLinkToURLExtended:
  300. // Prevent the movie controller from handling the message
  301. result = true;
  302. break;
  303. default:
  304. break;
  305. };
  306. return result;
  307. };
  308. static OSErr movieDrawingCompleteCallback( Movie call_back_movie, long ref )
  309. {
  310. MediaPluginQuickTime* self = ( MediaPluginQuickTime* )ref;
  311. // IMPORTANT: typically, a consumer who is observing this event will set a flag
  312. // when this event is fired then render later. Be aware that the media stream
  313. // can change during this period - dimensions, depth, format etc.
  314. //LLMediaEvent event( self );
  315. // self->updateQuickTime();
  316. // TODO ^^^
  317. if ( self->mWidth > 0 && self->mHeight > 0 )
  318. self->setDirty( 0, 0, self->mWidth, self->mHeight );
  319. return noErr;
  320. };
  321. static void moviePrePrerollCompleteCallback( Movie movie, OSErr preroll_err, void *ref )
  322. {
  323. MediaPluginQuickTime* self = ( MediaPluginQuickTime* )ref;
  324. // TODO:
  325. //LLMediaEvent event( self );
  326. //self->mEventEmitter.update( &LLMediaObserver::onMediaPreroll, event );
  327. // Send a "navigate complete" event.
  328. LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "navigate_complete");
  329. message.setValue("uri", self->mNavigateURL);
  330. message.setValueS32("result_code", 200);
  331. message.setValue("result_string", "OK");
  332. self->sendMessage(message);
  333. };
  334. void rewind()
  335. {
  336. GoToBeginningOfMovie( mMovieHandle );
  337. MCMovieChanged( mMovieController, mMovieHandle );
  338. };
  339. bool processState()
  340. {
  341. if ( mCommand == COMMAND_PLAY )
  342. {
  343. if ( mStatus == STATUS_LOADED || mStatus == STATUS_PAUSED || mStatus == STATUS_PLAYING || mStatus == STATUS_DONE )
  344. {
  345. long state = GetMovieLoadState( mMovieHandle );
  346. if ( state >= kMovieLoadStatePlaythroughOK )
  347. {
  348. // if the movie is at the end (generally because it reached it naturally)
  349. // and we play is requested, jump back to the start of the movie.
  350. // note: this is different from having loop flag set.
  351. if ( IsMovieDone( mMovieHandle ) )
  352. {
  353. Fixed rate = X2Fix( 0.0 );
  354. MCDoAction( mMovieController, mcActionPlay, (void*)rate );
  355. rewind();
  356. };
  357. MCDoAction( mMovieController, mcActionPrerollAndPlay, (void*)getPlayRate() );
  358. MCDoAction( mMovieController, mcActionSetVolume, (void*)mCurVolume );
  359. setStatus(STATUS_PLAYING);
  360. mCommand = COMMAND_NONE;
  361. };
  362. };
  363. }
  364. else
  365. if ( mCommand == COMMAND_STOP )
  366. {
  367. if ( mStatus == STATUS_PLAYING || mStatus == STATUS_PAUSED || mStatus == STATUS_DONE )
  368. {
  369. if ( GetMovieLoadState( mMovieHandle ) >= kMovieLoadStatePlaythroughOK )
  370. {
  371. Fixed rate = X2Fix( 0.0 );
  372. MCDoAction( mMovieController, mcActionPlay, (void*)rate );
  373. rewind();
  374. setStatus(STATUS_LOADED);
  375. mCommand = COMMAND_NONE;
  376. };
  377. };
  378. }
  379. else
  380. if ( mCommand == COMMAND_PAUSE )
  381. {
  382. if ( mStatus == STATUS_PLAYING )
  383. {
  384. if ( GetMovieLoadState( mMovieHandle ) >= kMovieLoadStatePlaythroughOK )
  385. {
  386. Fixed rate = X2Fix( 0.0 );
  387. MCDoAction( mMovieController, mcActionPlay, (void*)rate );
  388. setStatus(STATUS_PAUSED);
  389. mCommand = COMMAND_NONE;
  390. };
  391. };
  392. };
  393. return true;
  394. };
  395. void play(F64 rate)
  396. {
  397. mPlayRate = rate;
  398. mCommand = COMMAND_PLAY;
  399. };
  400. void stop()
  401. {
  402. mCommand = COMMAND_STOP;
  403. };
  404. void pause()
  405. {
  406. mCommand = COMMAND_PAUSE;
  407. };
  408. void getMovieNaturalSize(int *movie_width, int *movie_height)
  409. {
  410. Rect rect;
  411. GetMovieNaturalBoundsRect( mMovieHandle, &rect );
  412. int width = ( rect.right - rect.left );
  413. int height = ( rect.bottom - rect.top );
  414. // make sure width and height fall in valid range
  415. if ( width < mMinWidth )
  416. width = mMinWidth;
  417. if ( width > mMaxWidth )
  418. width = mMaxWidth;
  419. if ( height < mMinHeight )
  420. height = mMinHeight;
  421. if ( height > mMaxHeight )
  422. height = mMaxHeight;
  423. // return the new rect
  424. *movie_width = width;
  425. *movie_height = height;
  426. }
  427. void updateQuickTime(int milliseconds)
  428. {
  429. if ( ! mMovieHandle )
  430. return;
  431. if ( ! mMovieController )
  432. return;
  433. // this wasn't required in 1.xx viewer but we have to manually
  434. // work the Windows message pump now
  435. #if defined( LL_WINDOWS )
  436. MSG msg;
  437. while ( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) )
  438. {
  439. GetMessage( &msg, NULL, 0, 0 );
  440. TranslateMessage( &msg );
  441. DispatchMessage( &msg );
  442. };
  443. #endif
  444. MCIdle( mMovieController );
  445. if ( ! mGWorldHandle )
  446. return;
  447. if ( mMediaSizeChanging )
  448. return;
  449. // update state machine
  450. processState();
  451. // see if title arrived and if so, update member variable with contents
  452. checkTitle();
  453. // QT call to see if we are at the end - can't do with controller
  454. if ( IsMovieDone( mMovieHandle ) )
  455. {
  456. // special code for looping - need to rewind at the end of the movie
  457. if ( mIsLooping )
  458. {
  459. // go back to start
  460. rewind();
  461. if ( mMovieController )
  462. {
  463. // kick off new play
  464. MCDoAction( mMovieController, mcActionPrerollAndPlay, (void*)getPlayRate() );
  465. // set the volume
  466. MCDoAction( mMovieController, mcActionSetVolume, (void*)mCurVolume );
  467. };
  468. }
  469. else
  470. {
  471. if(mStatus == STATUS_PLAYING)
  472. {
  473. setStatus(STATUS_DONE);
  474. }
  475. }
  476. }
  477. };
  478. int getDataWidth() const
  479. {
  480. if ( mGWorldHandle )
  481. {
  482. int depth = mDepth;
  483. if (depth < 1)
  484. depth = 1;
  485. // ALWAYS use the row bytes from the PixMap if we have a GWorld because
  486. // sometimes it's not the same as mMediaDepth * mMediaWidth !
  487. PixMapHandle pix_map_handle = GetGWorldPixMap( mGWorldHandle );
  488. return QTGetPixMapHandleRowBytes( pix_map_handle ) / depth;
  489. }
  490. else
  491. {
  492. // TODO : return LLMediaImplCommon::getaDataWidth();
  493. return 0;
  494. }
  495. };
  496. void seek( F64 time )
  497. {
  498. if ( mMovieController )
  499. {
  500. TimeRecord when;
  501. when.scale = GetMovieTimeScale( mMovieHandle );
  502. when.base = 0;
  503. // 'time' is in (floating point) seconds. The timebase time will be in 'units', where
  504. // there are 'scale' units per second.
  505. SInt64 raw_time = ( SInt64 )( time * (double)( when.scale ) );
  506. when.value.hi = ( SInt32 )( raw_time >> 32 );
  507. when.value.lo = ( SInt32 )( ( raw_time & 0x00000000FFFFFFFF ) );
  508. MCDoAction( mMovieController, mcActionGoToTime, &when );
  509. };
  510. };
  511. F64 getLoadedDuration()
  512. {
  513. TimeValue duration;
  514. if(GetMaxLoadedTimeInMovie( mMovieHandle, &duration ) != noErr)
  515. {
  516. // If GetMaxLoadedTimeInMovie returns an error, return the full duration of the movie.
  517. duration = GetMovieDuration( mMovieHandle );
  518. }
  519. TimeValue scale = GetMovieTimeScale( mMovieHandle );
  520. return (F64)duration / (F64)scale;
  521. };
  522. F64 getDuration()
  523. {
  524. TimeValue duration = GetMovieDuration( mMovieHandle );
  525. TimeValue scale = GetMovieTimeScale( mMovieHandle );
  526. return (F64)duration / (F64)scale;
  527. };
  528. F64 getCurrentTime()
  529. {
  530. TimeValue curr_time = GetMovieTime( mMovieHandle, 0 );
  531. TimeValue scale = GetMovieTimeScale( mMovieHandle );
  532. return (F64)curr_time / (F64)scale;
  533. };
  534. void setVolume( F64 volume )
  535. {
  536. mCurVolume = (short)(volume * ( double ) 0x100 );
  537. if ( mMovieController )
  538. {
  539. MCDoAction( mMovieController, mcActionSetVolume, (void*)mCurVolume );
  540. };
  541. };
  542. ////////////////////////////////////////////////////////////////////////////////
  543. //
  544. void update(int milliseconds = 0)
  545. {
  546. updateQuickTime(milliseconds);
  547. };
  548. ////////////////////////////////////////////////////////////////////////////////
  549. //
  550. void mouseDown( int x, int y )
  551. {
  552. };
  553. ////////////////////////////////////////////////////////////////////////////////
  554. //
  555. void mouseUp( int x, int y )
  556. {
  557. };
  558. ////////////////////////////////////////////////////////////////////////////////
  559. //
  560. void mouseMove( int x, int y )
  561. {
  562. };
  563. ////////////////////////////////////////////////////////////////////////////////
  564. //
  565. void keyPress( unsigned char key )
  566. {
  567. };
  568. ////////////////////////////////////////////////////////////////////////////////
  569. // Grab movie title into mMovieTitle - should be called repeatedly
  570. // until it returns true since movie title takes a while to become
  571. // available.
  572. const bool getMovieTitle()
  573. {
  574. // grab meta data from movie
  575. QTMetaDataRef media_data_ref;
  576. OSErr result = QTCopyMovieMetaData( mMovieHandle, &media_data_ref );
  577. if ( noErr != result )
  578. return false;
  579. // look up "Display Name" in meta data
  580. OSType meta_data_key = kQTMetaDataCommonKeyDisplayName;
  581. QTMetaDataItem item = kQTMetaDataItemUninitialized;
  582. result = QTMetaDataGetNextItem( media_data_ref, kQTMetaDataStorageFormatWildcard,
  583. 0, kQTMetaDataKeyFormatCommon,
  584. (const UInt8 *)&meta_data_key,
  585. sizeof( meta_data_key ), &item );
  586. if ( noErr != result )
  587. return false;
  588. // find the size of the title
  589. ByteCount size;
  590. result = QTMetaDataGetItemValue( media_data_ref, item, NULL, 0, &size );
  591. if ( noErr != result || size <= 0 /*|| size > 1024 FIXME: arbitrary limit */ )
  592. return false;
  593. // allocate some space and grab it
  594. UInt8* item_data = new UInt8[ size + 1 ];
  595. memset( item_data, 0, ( size + 1 ) * sizeof( UInt8 ) );
  596. result = QTMetaDataGetItemValue( media_data_ref, item, item_data, size, NULL );
  597. if ( noErr != result )
  598. {
  599. delete [] item_data;
  600. return false;
  601. };
  602. // save it
  603. if ( strlen( (char*)item_data ) )
  604. mMovieTitle = std::string( (char* )item_data );
  605. else
  606. mMovieTitle = "";
  607. // clean up
  608. delete [] item_data;
  609. return true;
  610. };
  611. // called regularly to see if title changed
  612. void checkTitle()
  613. {
  614. // we did already receive title so keep checking
  615. if ( ! mReceivedTitle )
  616. {
  617. // grab title from movie meta data
  618. if ( getMovieTitle() )
  619. {
  620. // pass back to host application
  621. LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "name_text");
  622. message.setValue("name", mMovieTitle );
  623. sendMessage( message );
  624. // stop looking once we find a title for this movie.
  625. // TODO: this may to be reset if movie title changes
  626. // during playback but this is okay for now
  627. mReceivedTitle = true;
  628. };
  629. };
  630. };
  631. };
  632. MediaPluginQuickTime::MediaPluginQuickTime(
  633. LLPluginInstance::sendMessageFunction host_send_func,
  634. void *host_user_data ) :
  635. MediaPluginBase(host_send_func, host_user_data),
  636. mMinWidth( 0 ),
  637. mMaxWidth( 2048 ),
  638. mMinHeight( 0 ),
  639. mMaxHeight( 2048 )
  640. {
  641. // std::cerr << "MediaPluginQuickTime constructor" << std::endl;
  642. mNaturalWidth = -1;
  643. mNaturalHeight = -1;
  644. mMovieHandle = 0;
  645. mGWorldHandle = 0;
  646. mMovieController = 0;
  647. mCurVolume = 0x99;
  648. mMediaSizeChanging = false;
  649. mIsLooping = false;
  650. mMovieTitle = std::string();
  651. mReceivedTitle = false;
  652. mCommand = COMMAND_NONE;
  653. mPlayRate = 0.0f;
  654. mStatus = STATUS_NONE;
  655. }
  656. MediaPluginQuickTime::~MediaPluginQuickTime()
  657. {
  658. // std::cerr << "MediaPluginQuickTime destructor" << std::endl;
  659. ExitMovies();
  660. #ifdef LL_WINDOWS
  661. TerminateQTML();
  662. // std::cerr << "QuickTime closing down" << std::endl;
  663. #endif
  664. }
  665. void MediaPluginQuickTime::receiveMessage(const char *message_string)
  666. {
  667. // std::cerr << "MediaPluginQuickTime::receiveMessage: received message: \"" << message_string << "\"" << std::endl;
  668. LLPluginMessage message_in;
  669. if(message_in.parse(message_string) >= 0)
  670. {
  671. std::string message_class = message_in.getClass();
  672. std::string message_name = message_in.getName();
  673. if(message_class == LLPLUGIN_MESSAGE_CLASS_BASE)
  674. {
  675. if(message_name == "init")
  676. {
  677. LLPluginMessage message("base", "init_response");
  678. LLSD versions = LLSD::emptyMap();
  679. versions[LLPLUGIN_MESSAGE_CLASS_BASE] = LLPLUGIN_MESSAGE_CLASS_BASE_VERSION;
  680. versions[LLPLUGIN_MESSAGE_CLASS_MEDIA] = LLPLUGIN_MESSAGE_CLASS_MEDIA_VERSION;
  681. // Normally a plugin would only specify one of these two subclasses, but this is a demo...
  682. versions[LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME] = LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME_VERSION;
  683. message.setValueLLSD("versions", versions);
  684. #ifdef LL_WINDOWS
  685. // QuickTime 7.6.4 has an issue (that was not present in 7.6.2) with initializing QuickTime
  686. // according to this article: http://lists.apple.com/archives/QuickTime-API/2009/Sep/msg00097.html
  687. // The solution presented there appears to work.
  688. QTLoadLibrary("qtcf.dll");
  689. // main initialization for QuickTime - only required on Windows
  690. OSErr result = InitializeQTML( 0L );
  691. if ( result != noErr )
  692. {
  693. //TODO: If no QT on Windows, this fails - respond accordingly.
  694. }
  695. else
  696. {
  697. //std::cerr << "QuickTime initialized" << std::endl;
  698. };
  699. #endif
  700. // required for both Windows and Mac
  701. EnterMovies();
  702. std::string plugin_version = "QuickTime media plugin, QuickTime version ";
  703. long version = 0;
  704. Gestalt( gestaltQuickTimeVersion, &version );
  705. std::ostringstream codec( "" );
  706. codec << std::hex << version << std::dec;
  707. plugin_version += codec.str();
  708. message.setValue("plugin_version", plugin_version);
  709. sendMessage(message);
  710. }
  711. else if(message_name == "idle")
  712. {
  713. // no response is necessary here.
  714. F64 time = message_in.getValueReal("time");
  715. // Convert time to milliseconds for update()
  716. update((int)(time * 1000.0f));
  717. }
  718. else if(message_name == "cleanup")
  719. {
  720. // TODO: clean up here
  721. }
  722. else if(message_name == "shm_added")
  723. {
  724. SharedSegmentInfo info;
  725. info.mAddress = message_in.getValuePointer("address");
  726. info.mSize = (size_t)message_in.getValueS32("size");
  727. std::string name = message_in.getValue("name");
  728. // std::cerr << "MediaPluginQuickTime::receiveMessage: shared memory added, name: " << name
  729. // << ", size: " << info.mSize
  730. // << ", address: " << info.mAddress
  731. // << std::endl;
  732. mSharedSegments.insert(SharedSegmentMap::value_type(name, info));
  733. }
  734. else if(message_name == "shm_remove")
  735. {
  736. std::string name = message_in.getValue("name");
  737. // std::cerr << "MediaPluginQuickTime::receiveMessage: shared memory remove, name = " << name << std::endl;
  738. SharedSegmentMap::iterator iter = mSharedSegments.find(name);
  739. if(iter != mSharedSegments.end())
  740. {
  741. if(mPixels == iter->second.mAddress)
  742. {
  743. // This is the currently active pixel buffer. Make sure we stop drawing to it.
  744. mPixels = NULL;
  745. mTextureSegmentName.clear();
  746. // Make sure the movie GWorld is no longer pointed at the shared segment.
  747. sizeChanged();
  748. }
  749. mSharedSegments.erase(iter);
  750. }
  751. else
  752. {
  753. // std::cerr << "MediaPluginQuickTime::receiveMessage: unknown shared memory region!" << std::endl;
  754. }
  755. // Send the response so it can be cleaned up.
  756. LLPluginMessage message("base", "shm_remove_response");
  757. message.setValue("name", name);
  758. sendMessage(message);
  759. }
  760. else
  761. {
  762. // std::cerr << "MediaPluginQuickTime::receiveMessage: unknown base message: " << message_name << std::endl;
  763. }
  764. }
  765. else if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA)
  766. {
  767. if(message_name == "init")
  768. {
  769. // This is the media init message -- all necessary data for initialization should have been received.
  770. // Plugin gets to decide the texture parameters to use.
  771. LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "texture_params");
  772. #if defined(LL_WINDOWS)
  773. // Values for Windows
  774. mDepth = 3;
  775. message.setValueU32("format", GL_RGB);
  776. message.setValueU32("type", GL_UNSIGNED_BYTE);
  777. // We really want to pad the texture width to a multiple of 32 bytes, but since we're using 3-byte pixels, it doesn't come out even.
  778. // Padding to a multiple of 3*32 guarantees it'll divide out properly.
  779. message.setValueU32("padding", 32 * 3);
  780. #else
  781. // Values for Mac
  782. mDepth = 4;
  783. message.setValueU32("format", GL_BGRA_EXT);
  784. #ifdef __BIG_ENDIAN__
  785. message.setValueU32("type", GL_UNSIGNED_INT_8_8_8_8_REV );
  786. #else
  787. message.setValueU32("type", GL_UNSIGNED_INT_8_8_8_8);
  788. #endif
  789. // Pad texture width to a multiple of 32 bytes, to line up with cache lines.
  790. message.setValueU32("padding", 32);
  791. #endif
  792. message.setValueS32("depth", mDepth);
  793. message.setValueU32("internalformat", GL_RGB);
  794. message.setValueBoolean("coords_opengl", true); // true == use OpenGL-style coordinates, false == (0,0) is upper left.
  795. message.setValueBoolean("allow_downsample", true);
  796. sendMessage(message);
  797. }
  798. else if(message_name == "size_change")
  799. {
  800. std::string name = message_in.getValue("name");
  801. S32 width = message_in.getValueS32("width");
  802. S32 height = message_in.getValueS32("height");
  803. S32 texture_width = message_in.getValueS32("texture_width");
  804. S32 texture_height = message_in.getValueS32("texture_height");
  805. //std::cerr << "---->Got size change instruction from application with name: " << name << " - size is " << width << " x " << height << std::endl;
  806. LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "size_change_response");
  807. message.setValue("name", name);
  808. message.setValueS32("width", width);
  809. message.setValueS32("height", height);
  810. message.setValueS32("texture_width", texture_width);
  811. message.setValueS32("texture_height", texture_height);
  812. sendMessage(message);
  813. if(!name.empty())
  814. {
  815. // Find the shared memory region with this name
  816. SharedSegmentMap::iterator iter = mSharedSegments.find(name);
  817. if(iter != mSharedSegments.end())
  818. {
  819. // std::cerr << "%%% Got size change, new size is " << width << " by " << height << std::endl;
  820. // std::cerr << "%%%% texture size is " << texture_width << " by " << texture_height << std::endl;
  821. mPixels = (unsigned char*)iter->second.mAddress;
  822. mTextureSegmentName = name;
  823. mWidth = width;
  824. mHeight = height;
  825. mTextureWidth = texture_width;
  826. mTextureHeight = texture_height;
  827. mMediaSizeChanging = false;
  828. sizeChanged();
  829. update();
  830. };
  831. };
  832. }
  833. else if(message_name == "load_uri")
  834. {
  835. std::string uri = message_in.getValue("uri");
  836. load( uri );
  837. sendStatus();
  838. }
  839. else if(message_name == "mouse_event")
  840. {
  841. std::string event = message_in.getValue("event");
  842. S32 x = message_in.getValueS32("x");
  843. S32 y = message_in.getValueS32("y");
  844. if(event == "down")
  845. {
  846. mouseDown(x, y);
  847. }
  848. else if(event == "up")
  849. {
  850. mouseUp(x, y);
  851. }
  852. else if(event == "move")
  853. {
  854. mouseMove(x, y);
  855. };
  856. };
  857. }
  858. else if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME)
  859. {
  860. if(message_name == "stop")
  861. {
  862. stop();
  863. }
  864. else if(message_name == "start")
  865. {
  866. F64 rate = 0.0;
  867. if(message_in.hasValue("rate"))
  868. {
  869. rate = message_in.getValueReal("rate");
  870. }
  871. play(rate);
  872. }
  873. else if(message_name == "pause")
  874. {
  875. pause();
  876. }
  877. else if(message_name == "seek")
  878. {
  879. F64 time = message_in.getValueReal("time");
  880. seek(time);
  881. }
  882. else if(message_name == "set_loop")
  883. {
  884. bool loop = message_in.getValueBoolean("loop");
  885. mIsLooping = loop;
  886. }
  887. else if(message_name == "set_volume")
  888. {
  889. F64 volume = message_in.getValueReal("volume");
  890. setVolume(volume);
  891. }
  892. }
  893. else
  894. {
  895. // std::cerr << "MediaPluginQuickTime::receiveMessage: unknown message class: " << message_class << std::endl;
  896. };
  897. };
  898. }
  899. int init_media_plugin(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data, LLPluginInstance::sendMessageFunction *plugin_send_func, void **plugin_user_data)
  900. {
  901. MediaPluginQuickTime *self = new MediaPluginQuickTime(host_send_func, host_user_data);
  902. *plugin_send_func = MediaPluginQuickTime::staticReceiveMessage;
  903. *plugin_user_data = (void*)self;
  904. return 0;
  905. }
  906. #else // LL_QUICKTIME_ENABLED
  907. // Stubbed-out class with constructor/destructor (necessary or windows linker
  908. // will just think its dead code and optimize it all out)
  909. class MediaPluginQuickTime : public MediaPluginBase
  910. {
  911. public:
  912. MediaPluginQuickTime(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data);
  913. ~MediaPluginQuickTime();
  914. /* virtual */ void receiveMessage(const char *message_string);
  915. };
  916. MediaPluginQuickTime::MediaPluginQuickTime(
  917. LLPluginInstance::sendMessageFunction host_send_func,
  918. void *host_user_data ) :
  919. MediaPluginBase(host_send_func, host_user_data)
  920. {
  921. // no-op
  922. }
  923. MediaPluginQuickTime::~MediaPluginQuickTime()
  924. {
  925. // no-op
  926. }
  927. void MediaPluginQuickTime::receiveMessage(const char *message_string)
  928. {
  929. // no-op
  930. }
  931. // We're building without quicktime enabled. Just refuse to initialize.
  932. int init_media_plugin(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data, LLPluginInstance::sendMessageFunction *plugin_send_func, void **plugin_user_data)
  933. {
  934. return -1;
  935. }
  936. #endif // LL_QUICKTIME_ENABLED