media_plugin_quicktime.cpp
上传用户:king477883
上传日期:2021-03-01
资源大小:9553k
文件大小:30k
源码类别:

游戏引擎

开发平台:

C++ Builder

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