PageRenderTime 99ms CodeModel.GetById 10ms app.highlight 80ms RepoModel.GetById 2ms app.codeStats 0ms

/indra/media_plugins/quicktime/media_plugin_quicktime.cpp

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