1///////////////////////////////////////////////////////////////////////////// 2// Name: src/mac/carbon/mediactrl.cpp 3// Purpose: Built-in Media Backends for Mac 4// Author: Ryan Norton <wxprojects@comcast.net> 5// Modified by: 6// Created: 11/07/04 7// RCS-ID: $Id: mediactrl.cpp 46051 2007-05-15 20:12:31Z SC $ 8// Copyright: (c) 2004-2006 Ryan Norton 9// Licence: wxWindows licence 10///////////////////////////////////////////////////////////////////////////// 11 12//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 13// OK, a casual overseer of this file may wonder why we don't use 14// either CreateMovieControl or HIMovieView... 15// 16// CreateMovieControl 17// 1) Need to dispose and create each time a new movie is loaded 18// 2) Not that many real advantages 19// 3) Progressively buggier in higher OSX versions 20// (see main.c of QTCarbonShell sample for details) 21// HIMovieView 22// 1) Crashes on destruction in ALL cases on quite a few systems! 23// (With the only real "alternative" is to simply not 24// dispose of it and let it leak...) 25// 2) Massive refreshing bugs with its movie controller between 26// movies 27// 28// At one point we had a complete implementation for CreateMovieControl 29// and on my (RN) local copy I had one for HIMovieView - but they 30// were simply deemed to be too buggy/unuseful. HIMovieView could 31// have been useful as well because it uses OpenGL contexts instead 32// of GWorlds. Perhaps someday when someone comes out with some 33// ingenious workarounds :). 34//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 35 36// For compilers that support precompilation, includes "wx.h". 37#include "wx/wxprec.h" 38 39#if wxUSE_MEDIACTRL 40 41#include "wx/mediactrl.h" 42 43#ifndef WX_PRECOMP 44 #include "wx/log.h" 45 #include "wx/timer.h" 46#endif 47 48// uma is for wxMacFSSpec 49#include "wx/mac/uma.h" 50 51// standard QT stuff 52#ifndef __DARWIN__ 53#include <Movies.h> 54#include <Gestalt.h> 55#include <QuickTimeComponents.h> 56#else 57#include <QuickTime/QuickTimeComponents.h> 58#endif 59 60#if !defined(__DARWIN__) || !defined(__LP64__) 61#define USE_QUICKTIME 1 62#else 63#define USE_QUICKTIME 0 64#endif 65 66#if USE_QUICKTIME 67 68//--------------------------------------------------------------------------- 69// Height and Width of movie controller in the movie control (apple samples) 70//--------------------------------------------------------------------------- 71#define wxMCWIDTH 320 72#define wxMCHEIGHT 16 73 74//=========================================================================== 75// BACKEND DECLARATIONS 76//=========================================================================== 77 78//--------------------------------------------------------------------------- 79// wxQTMediaBackend 80//--------------------------------------------------------------------------- 81 82class WXDLLIMPEXP_MEDIA wxQTMediaBackend : public wxMediaBackendCommonBase 83{ 84public: 85 wxQTMediaBackend(); 86 virtual ~wxQTMediaBackend(); 87 88 virtual bool CreateControl(wxControl* ctrl, wxWindow* parent, 89 wxWindowID id, 90 const wxPoint& pos, 91 const wxSize& size, 92 long style, 93 const wxValidator& validator, 94 const wxString& name); 95 96 virtual bool Load(const wxString& fileName); 97 virtual bool Load(const wxURI& location); 98 99 virtual bool Play(); 100 virtual bool Pause(); 101 virtual bool Stop(); 102 103 virtual wxMediaState GetState(); 104 105 virtual bool SetPosition(wxLongLong where); 106 virtual wxLongLong GetPosition(); 107 virtual wxLongLong GetDuration(); 108 109 virtual void Move(int x, int y, int w, int h); 110 wxSize GetVideoSize() const; 111 112 virtual double GetPlaybackRate(); 113 virtual bool SetPlaybackRate(double dRate); 114 115 virtual double GetVolume(); 116 virtual bool SetVolume(double); 117 118 void Cleanup(); 119 void FinishLoad(); 120 121 virtual bool ShowPlayerControls(wxMediaCtrlPlayerControls flags); 122 123 virtual wxLongLong GetDownloadProgress(); 124 virtual wxLongLong GetDownloadTotal(); 125 126 virtual void MacVisibilityChanged(); 127 128 // 129 // ------ Implementation from now on -------- 130 // 131 bool DoPause(); 132 bool DoStop(); 133 134 void DoLoadBestSize(); 135 void DoSetControllerVisible(wxMediaCtrlPlayerControls flags); 136 137 wxLongLong GetDataSizeFromStart(TimeValue end); 138 139 Boolean IsQuickTime4Installed(); 140 void DoNewMovieController(); 141 142 static pascal void PPRMProc( 143 Movie theMovie, OSErr theErr, void* theRefCon); 144 145 //TODO: Last param actually long - does this work on 64bit machines? 146 static pascal Boolean MCFilterProc(MovieController theController, 147 short action, void *params, long refCon); 148 149 static pascal OSStatus WindowEventHandler( 150 EventHandlerCallRef inHandlerCallRef, 151 EventRef inEvent, void *inUserData ); 152 153 wxSize m_bestSize; // Original movie size 154 Movie m_movie; // Movie instance 155 bool m_bPlaying; // Whether media is playing or not 156 class wxTimer* m_timer; // Timer for streaming the movie 157 MovieController m_mc; // MovieController instance 158 wxMediaCtrlPlayerControls m_interfaceflags; // Saved interface flags 159 160 // Event handlers and UPPs/Callbacks 161 EventHandlerRef m_windowEventHandler; 162 EventHandlerUPP m_windowUPP; 163 164 MoviePrePrerollCompleteUPP m_preprerollupp; 165 MCActionFilterWithRefConUPP m_mcactionupp; 166 167 GWorldPtr m_movieWorld; //Offscreen movie GWorld 168 169 friend class wxQTMediaEvtHandler; 170 171 DECLARE_DYNAMIC_CLASS(wxQTMediaBackend) 172}; 173 174// helper to hijack background erasing for the QT window 175class WXDLLIMPEXP_MEDIA wxQTMediaEvtHandler : public wxEvtHandler 176{ 177public: 178 wxQTMediaEvtHandler(wxQTMediaBackend *qtb) 179 { 180 m_qtb = qtb; 181 182 qtb->m_ctrl->Connect( 183 qtb->m_ctrl->GetId(), wxEVT_ERASE_BACKGROUND, 184 wxEraseEventHandler(wxQTMediaEvtHandler::OnEraseBackground), 185 NULL, this); 186 } 187 188 void OnEraseBackground(wxEraseEvent& event); 189 190private: 191 wxQTMediaBackend *m_qtb; 192 193 DECLARE_NO_COPY_CLASS(wxQTMediaEvtHandler) 194}; 195 196//=========================================================================== 197// IMPLEMENTATION 198//=========================================================================== 199 200 201//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 202// 203// wxQTMediaBackend 204// 205//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 206 207IMPLEMENT_DYNAMIC_CLASS(wxQTMediaBackend, wxMediaBackend) 208 209//Time between timer calls - this is the Apple recommondation to the TCL 210//team I believe 211#define MOVIE_DELAY 20 212 213//--------------------------------------------------------------------------- 214// wxQTMediaLoadTimer 215// 216// QT, esp. QT for Windows is very picky about how you go about 217// async loading. If you were to go through a Windows message loop 218// or a MoviesTask or both and then check the movie load state 219// it would still return 1000 (loading)... even (pre)prerolling doesn't 220// help. However, making a load timer like this works 221//--------------------------------------------------------------------------- 222class wxQTMediaLoadTimer : public wxTimer 223{ 224public: 225 wxQTMediaLoadTimer(wxQTMediaBackend* parent) : 226 m_parent(parent) {} 227 228 void Notify() 229 { 230 ::MCIdle(m_parent->m_mc); 231 232 // kMovieLoadStatePlayable is not enough on MAC: 233 // it plays, but IsMovieDone might return true (!) 234 // sure we need to wait until kMovieLoadStatePlaythroughOK 235 if (::GetMovieLoadState(m_parent->m_movie) >= 20000) 236 { 237 m_parent->FinishLoad(); 238 delete this; 239 } 240 } 241 242protected: 243 wxQTMediaBackend *m_parent; // Backend pointer 244}; 245 246// -------------------------------------------------------------------------- 247// wxQTMediaPlayTimer - Handle Asyncronous Playing 248// 249// 1) Checks to see if the movie is done, and if not continues 250// streaming the movie 251// 2) Sends the wxEVT_MEDIA_STOP event if we have reached the end of 252// the movie. 253// -------------------------------------------------------------------------- 254class wxQTMediaPlayTimer : public wxTimer 255{ 256public: 257 wxQTMediaPlayTimer(wxQTMediaBackend* parent) : 258 m_parent(parent) {} 259 260 void Notify() 261 { 262 // 263 // OK, a little explaining - basically originally 264 // we only called MoviesTask if the movie was actually 265 // playing (not paused or stopped)... this was before 266 // we realized MoviesTask actually handles repainting 267 // of the current frame - so if you were to resize 268 // or something it would previously not redraw that 269 // portion of the movie. 270 // 271 // So now we call MoviesTask always so that it repaints 272 // correctly. 273 // 274 ::MCIdle(m_parent->m_mc); 275 276 // 277 // Handle the stop event - if the movie has reached 278 // the end, notify our handler 279 // 280 if (::IsMovieDone(m_parent->m_movie)) 281 { 282 if ( m_parent->SendStopEvent() ) 283 { 284 m_parent->Stop(); 285 wxASSERT(::GetMoviesError() == noErr); 286 287 m_parent->QueueFinishEvent(); 288 } 289 } 290 } 291 292protected: 293 wxQTMediaBackend* m_parent; // Backend pointer 294}; 295 296 297//--------------------------------------------------------------------------- 298// wxQTMediaBackend Constructor 299// 300// Sets m_timer to NULL signifying we havn't loaded anything yet 301//--------------------------------------------------------------------------- 302wxQTMediaBackend::wxQTMediaBackend() 303 : m_movie(NULL), m_bPlaying(false), m_timer(NULL) 304 , m_mc(NULL), m_interfaceflags(wxMEDIACTRLPLAYERCONTROLS_NONE) 305 , m_preprerollupp(NULL), m_movieWorld(NULL) 306{ 307} 308 309//--------------------------------------------------------------------------- 310// wxQTMediaBackend Destructor 311// 312// 1) Cleans up the QuickTime movie instance 313// 2) Decrements the QuickTime reference counter - if this reaches 314// 0, QuickTime shuts down 315// 3) Decrements the QuickTime Windows Media Layer reference counter - 316// if this reaches 0, QuickTime shuts down the Windows Media Layer 317//--------------------------------------------------------------------------- 318wxQTMediaBackend::~wxQTMediaBackend() 319{ 320 if (m_movie) 321 Cleanup(); 322 323 // Cleanup for moviecontroller 324 if (m_mc) 325 { 326 // destroy wxQTMediaEvtHandler we pushed on it 327 m_ctrl->PopEventHandler(true); 328 RemoveEventHandler(m_windowEventHandler); 329 DisposeEventHandlerUPP(m_windowUPP); 330 331 // Dispose of the movie controller 332 ::DisposeMovieController(m_mc); 333 m_mc = NULL; 334 335 // Dispose of offscreen GWorld 336 ::DisposeGWorld(m_movieWorld); 337 } 338 339 // Note that ExitMovies() is not necessary... 340 ExitMovies(); 341} 342 343//--------------------------------------------------------------------------- 344// wxQTMediaBackend::CreateControl 345// 346// 1) Intializes QuickTime 347// 2) Creates the control window 348//--------------------------------------------------------------------------- 349bool wxQTMediaBackend::CreateControl( 350 wxControl* ctrl, 351 wxWindow* parent, 352 wxWindowID id, 353 const wxPoint& pos, 354 const wxSize& size, 355 long style, 356 const wxValidator& validator, 357 const wxString& name) 358{ 359 if (!IsQuickTime4Installed()) 360 return false; 361 362 EnterMovies(); 363 364 wxMediaCtrl* mediactrl = (wxMediaCtrl*)ctrl; 365 366 // 367 // Create window 368 // By default wxWindow(s) is created with a border - 369 // so we need to get rid of those 370 // 371 // Since we don't have a child window like most other 372 // backends, we don't need wxCLIP_CHILDREN 373 // 374 if ( !mediactrl->wxControl::Create( 375 parent, id, pos, size, 376 wxWindow::MacRemoveBordersFromStyle(style), 377 validator, name)) 378 { 379 return false; 380 } 381 382#if wxUSE_VALIDATORS 383 mediactrl->SetValidator(validator); 384#endif 385 386 m_ctrl = mediactrl; 387 return true; 388} 389 390//--------------------------------------------------------------------------- 391// wxQTMediaBackend::IsQuickTime4Installed 392// 393// Determines whether version 4 of QT is installed 394// (Pretty much for Classic only) 395//--------------------------------------------------------------------------- 396Boolean wxQTMediaBackend::IsQuickTime4Installed() 397{ 398 OSErr error; 399 long result; 400 401 error = Gestalt(gestaltQuickTime, &result); 402 return (error == noErr) && (((result >> 16) & 0xffff) >= 0x0400); 403} 404 405//--------------------------------------------------------------------------- 406// wxQTMediaBackend::Load (file version) 407// 408// 1) Get an FSSpec from the Windows path name 409// 2) Open the movie 410// 3) Obtain the movie instance from the movie resource 411// 4) Close the movie resource 412// 5) Finish loading 413//--------------------------------------------------------------------------- 414bool wxQTMediaBackend::Load(const wxString& fileName) 415{ 416 if (m_movie) 417 Cleanup(); 418 419 ::ClearMoviesStickyError(); // clear previous errors so 420 // GetMoviesStickyError is useful 421 422 OSErr err = noErr; 423 short movieResFile; 424 FSSpec sfFile; 425 426 wxMacFilename2FSSpec( fileName, &sfFile ); 427 if (OpenMovieFile( &sfFile, &movieResFile, fsRdPerm ) != noErr) 428 return false; 429 430 short movieResID = 0; 431 Str255 movieName; 432 433 err = NewMovieFromFile( 434 &m_movie, 435 movieResFile, 436 &movieResID, 437 movieName, 438 newMovieActive, 439 NULL); // wasChanged 440 441 // Do not use ::GetMoviesStickyError() here because it returns -2009 442 // a.k.a. invalid track on valid mpegs 443 if (err == noErr && ::GetMoviesError() == noErr) 444 { 445 ::CloseMovieFile(movieResFile); 446 447 // Create movie controller/control 448 DoNewMovieController(); 449 450 FinishLoad(); 451 return true; 452 } 453 454 return false; 455} 456 457//--------------------------------------------------------------------------- 458// wxQTMediaBackend::Load (URL Version) 459// 460// 1) Build an escaped URI from location 461// 2) Create a handle to store the URI string 462// 3) Put the URI string inside the handle 463// 4) Make a QuickTime URL data ref from the handle with the URI in it 464// 5) Clean up the URI string handle 465// 6) Do some prerolling 466// 7) Finish Loading 467//--------------------------------------------------------------------------- 468bool wxQTMediaBackend::Load(const wxURI& location) 469{ 470 if (m_movie) 471 Cleanup(); 472 473 ::ClearMoviesStickyError(); // clear previous errors so 474 // GetMoviesStickyError is useful 475 476 wxString theURI = location.BuildURI(); 477 OSErr err; 478 479 size_t len; 480 const char* theURIString; 481 482#if wxUSE_UNICODE 483 wxCharBuffer buf = wxConvLocal.cWC2MB(theURI, theURI.length(), &len); 484 theURIString = buf; 485#else 486 theURIString = theURI; 487 len = theURI.length(); 488#endif 489 490 Handle theHandle = ::NewHandleClear(len + 1); 491 wxASSERT(theHandle); 492 493 ::BlockMoveData(theURIString, *theHandle, len + 1); 494 495 // create the movie from the handle that refers to the URI 496 err = ::NewMovieFromDataRef( 497 &m_movie, 498 newMovieActive | newMovieAsyncOK /* | newMovieIdleImportOK*/, 499 NULL, theHandle, 500 URLDataHandlerSubType); 501 502 ::DisposeHandle(theHandle); 503 504 if (err == noErr && ::GetMoviesStickyError() == noErr) 505 { 506 // Movie controller resets prerolling, so we must create first 507 DoNewMovieController(); 508 509 long timeNow; 510 Fixed playRate; 511 512 timeNow = ::GetMovieTime(m_movie, NULL); 513 wxASSERT(::GetMoviesError() == noErr); 514 515 playRate = ::GetMoviePreferredRate(m_movie); 516 wxASSERT(::GetMoviesError() == noErr); 517 518 // 519 // Note that the callback here is optional, 520 // but without it PrePrerollMovie can be buggy 521 // (see Apple ml). Also, some may wonder 522 // why we need this at all - this is because 523 // Apple docs say QuickTime streamed movies 524 // require it if you don't use a Movie Controller, 525 // which we don't by default. 526 // 527 m_preprerollupp = wxQTMediaBackend::PPRMProc; 528 ::PrePrerollMovie( m_movie, timeNow, playRate, 529 m_preprerollupp, (void*)this); 530 531 return true; 532 } 533 534 return false; 535} 536 537//--------------------------------------------------------------------------- 538// wxQTMediaBackend::DoNewMovieController 539// 540// Attaches movie to moviecontroller or creates moviecontroller 541// if not created yet 542//--------------------------------------------------------------------------- 543void wxQTMediaBackend::DoNewMovieController() 544{ 545 if (!m_mc) 546 { 547 // Get top level window ref for some mac functions 548 WindowRef wrTLW = (WindowRef) m_ctrl->MacGetTopLevelWindowRef(); 549 550 // MovieController not set up yet, so we need to create a new one. 551 // You have to pass a valid movie to NewMovieController, evidently 552 ::SetMovieGWorld(m_movie, 553 (CGrafPtr) GetWindowPort(wrTLW), 554 NULL); 555 wxASSERT(::GetMoviesError() == noErr); 556 557 Rect bounds = wxMacGetBoundsForControl( 558 m_ctrl, 559 m_ctrl->GetPosition(), 560 m_ctrl->GetSize()); 561 562 m_mc = ::NewMovieController( 563 m_movie, &bounds, 564 mcTopLeftMovie | mcNotVisible /* | mcWithFrame */ ); 565 wxASSERT(::GetMoviesError() == noErr); 566 567 ::MCDoAction(m_mc, 32, (void*)true); // mcActionSetKeysEnabled 568 wxASSERT(::GetMoviesError() == noErr); 569 570 // Setup a callback so we can tell when the user presses 571 // play on the player controls 572 m_mcactionupp = wxQTMediaBackend::MCFilterProc; 573 ::MCSetActionFilterWithRefCon( m_mc, m_mcactionupp, (long)this ); 574 wxASSERT(::GetMoviesError() == noErr); 575 576 // Part of a suggestion from Greg Hazel to repaint movie when idle 577 m_ctrl->PushEventHandler(new wxQTMediaEvtHandler(this)); 578 579 // Create offscreen GWorld for where to "show" when window is hidden 580 Rect worldRect; 581 worldRect.left = worldRect.top = 0; 582 worldRect.right = worldRect.bottom = 1; 583 ::NewGWorld(&m_movieWorld, 0, &worldRect, NULL, NULL, 0); 584 585 // Catch window messages: 586 // if we do not do this and if the user clicks the play 587 // button on the controller, for instance, nothing will happen... 588 EventTypeSpec theWindowEventTypes[] = 589 { 590 { kEventClassMouse, kEventMouseDown }, 591 { kEventClassMouse, kEventMouseUp }, 592 { kEventClassMouse, kEventMouseDragged }, 593 { kEventClassKeyboard, kEventRawKeyDown }, 594 { kEventClassKeyboard, kEventRawKeyRepeat }, 595 { kEventClassKeyboard, kEventRawKeyUp }, 596 { kEventClassWindow, kEventWindowUpdate }, 597 { kEventClassWindow, kEventWindowActivated }, 598 { kEventClassWindow, kEventWindowDeactivated } 599 }; 600 m_windowUPP = 601 NewEventHandlerUPP( wxQTMediaBackend::WindowEventHandler ); 602 InstallWindowEventHandler( 603 wrTLW, 604 m_windowUPP, 605 GetEventTypeCount( theWindowEventTypes ), theWindowEventTypes, 606 this, 607 &m_windowEventHandler ); 608 } 609 else 610 { 611 // MovieController already created: 612 // Just change the movie in it and we're good to go 613 Point thePoint; 614 thePoint.h = thePoint.v = 0; 615 ::MCSetMovie(m_mc, m_movie, 616 (WindowRef)m_ctrl->MacGetTopLevelWindowRef(), 617 thePoint); 618 wxASSERT(::GetMoviesError() == noErr); 619 } 620} 621 622//--------------------------------------------------------------------------- 623// wxQTMediaBackend::FinishLoad 624// 625// Performs operations after a movie ready to play/loaded. 626//--------------------------------------------------------------------------- 627void wxQTMediaBackend::FinishLoad() 628{ 629 // get the real size of the movie 630 DoLoadBestSize(); 631 632 // show the player controls if the user wants to 633 if (m_interfaceflags) 634 DoSetControllerVisible(m_interfaceflags); 635 636 // we want millisecond precision 637 ::SetMovieTimeScale(m_movie, 1000); 638 wxASSERT(::GetMoviesError() == noErr); 639 640 // start movie progress timer 641 m_timer = new wxQTMediaPlayTimer(this); 642 wxASSERT(m_timer); 643 m_timer->Start(MOVIE_DELAY, wxTIMER_CONTINUOUS); 644 645 // send loaded event and refresh size 646 NotifyMovieLoaded(); 647} 648 649//--------------------------------------------------------------------------- 650// wxQTMediaBackend::DoLoadBestSize 651// 652// Sets the best size of the control from the real size of the movie 653//--------------------------------------------------------------------------- 654void wxQTMediaBackend::DoLoadBestSize() 655{ 656 // get the real size of the movie 657 Rect outRect; 658 ::GetMovieNaturalBoundsRect(m_movie, &outRect); 659 wxASSERT(::GetMoviesError() == noErr); 660 661 // determine best size 662 m_bestSize.x = outRect.right - outRect.left; 663 m_bestSize.y = outRect.bottom - outRect.top; 664} 665 666//--------------------------------------------------------------------------- 667// wxQTMediaBackend::Play 668// 669// Start the QT movie 670// (Apple recommends mcActionPrerollAndPlay but that's QT 4.1+) 671//--------------------------------------------------------------------------- 672bool wxQTMediaBackend::Play() 673{ 674 Fixed fixRate = (Fixed) (wxQTMediaBackend::GetPlaybackRate() * 0x10000); 675 if (!fixRate) 676 fixRate = ::GetMoviePreferredRate(m_movie); 677 678 wxASSERT(fixRate != 0); 679 680 if (!m_bPlaying) 681 ::MCDoAction( m_mc, 8 /* mcActionPlay */, (void*) fixRate); 682 683 bool result = (::GetMoviesError() == noErr); 684 if (result) 685 { 686 m_bPlaying = true; 687 QueuePlayEvent(); 688 } 689 690 return result; 691} 692 693//--------------------------------------------------------------------------- 694// wxQTMediaBackend::Pause 695// 696// Stop the movie 697//--------------------------------------------------------------------------- 698bool wxQTMediaBackend::DoPause() 699{ 700 // Stop the movie A.K.A. ::StopMovie(m_movie); 701 if (m_bPlaying) 702 { 703 ::MCDoAction( m_mc, 8 /*mcActionPlay*/, (void *) 0); 704 m_bPlaying = false; 705 return ::GetMoviesError() == noErr; 706 } 707 708 // already paused 709 return true; 710} 711 712bool wxQTMediaBackend::Pause() 713{ 714 bool bSuccess = DoPause(); 715 if (bSuccess) 716 this->QueuePauseEvent(); 717 718 return bSuccess; 719} 720 721//--------------------------------------------------------------------------- 722// wxQTMediaBackend::Stop 723// 724// 1) Stop the movie 725// 2) Seek to the beginning of the movie 726//--------------------------------------------------------------------------- 727bool wxQTMediaBackend::DoStop() 728{ 729 if (!wxQTMediaBackend::DoPause()) 730 return false; 731 732 ::GoToBeginningOfMovie(m_movie); 733 return ::GetMoviesError() == noErr; 734} 735 736bool wxQTMediaBackend::Stop() 737{ 738 bool bSuccess = DoStop(); 739 if (bSuccess) 740 QueueStopEvent(); 741 742 return bSuccess; 743} 744 745//--------------------------------------------------------------------------- 746// wxQTMediaBackend::GetPlaybackRate 747// 748// 1) Get the movie playback rate from ::GetMovieRate 749//--------------------------------------------------------------------------- 750double wxQTMediaBackend::GetPlaybackRate() 751{ 752 return ( ((double)::GetMovieRate(m_movie)) / 0x10000); 753} 754 755//--------------------------------------------------------------------------- 756// wxQTMediaBackend::SetPlaybackRate 757// 758// 1) Convert dRate to Fixed and Set the movie rate through SetMovieRate 759//--------------------------------------------------------------------------- 760bool wxQTMediaBackend::SetPlaybackRate(double dRate) 761{ 762 ::SetMovieRate(m_movie, (Fixed) (dRate * 0x10000)); 763 return ::GetMoviesError() == noErr; 764} 765 766//--------------------------------------------------------------------------- 767// wxQTMediaBackend::SetPosition 768// 769// 1) Create a time record struct (TimeRecord) with appropriate values 770// 2) Pass struct to SetMovieTime 771//--------------------------------------------------------------------------- 772bool wxQTMediaBackend::SetPosition(wxLongLong where) 773{ 774 TimeRecord theTimeRecord; 775 memset(&theTimeRecord, 0, sizeof(TimeRecord)); 776 theTimeRecord.value.lo = where.GetValue(); 777 theTimeRecord.scale = ::GetMovieTimeScale(m_movie); 778 theTimeRecord.base = ::GetMovieTimeBase(m_movie); 779 ::SetMovieTime(m_movie, &theTimeRecord); 780 781 if (::GetMoviesError() != noErr) 782 return false; 783 784 return true; 785} 786 787//--------------------------------------------------------------------------- 788// wxQTMediaBackend::GetPosition 789// 790// Calls GetMovieTime 791//--------------------------------------------------------------------------- 792wxLongLong wxQTMediaBackend::GetPosition() 793{ 794 return ::GetMovieTime(m_movie, NULL); 795} 796 797//--------------------------------------------------------------------------- 798// wxQTMediaBackend::GetVolume 799// 800// Gets the volume through GetMovieVolume - which returns a 16 bit short - 801// 802// +--------+--------+ 803// + (1) + (2) + 804// +--------+--------+ 805// 806// (1) first 8 bits are value before decimal 807// (2) second 8 bits are value after decimal 808// 809// Volume ranges from -1.0 (gain but no sound), 0 (no sound and no gain) to 810// 1 (full gain and sound) 811//--------------------------------------------------------------------------- 812double wxQTMediaBackend::GetVolume() 813{ 814 short sVolume = ::GetMovieVolume(m_movie); 815 816 if (sVolume & (128 << 8)) //negative - no sound 817 return 0.0; 818 819 return sVolume / 256.0; 820} 821 822//--------------------------------------------------------------------------- 823// wxQTMediaBackend::SetVolume 824// 825// Sets the volume through SetMovieVolume - which takes a 16 bit short - 826// 827// +--------+--------+ 828// + (1) + (2) + 829// +--------+--------+ 830// 831// (1) first 8 bits are value before decimal 832// (2) second 8 bits are value after decimal 833// 834// Volume ranges from -1.0 (gain but no sound), 0 (no sound and no gain) to 835// 1 (full gain and sound) 836//--------------------------------------------------------------------------- 837bool wxQTMediaBackend::SetVolume(double dVolume) 838{ 839 ::SetMovieVolume(m_movie, (short) (dVolume * 256)); 840 return true; 841} 842 843//--------------------------------------------------------------------------- 844// wxQTMediaBackend::GetDuration 845// 846// Calls GetMovieDuration 847//--------------------------------------------------------------------------- 848wxLongLong wxQTMediaBackend::GetDuration() 849{ 850 return ::GetMovieDuration(m_movie); 851} 852 853//--------------------------------------------------------------------------- 854// wxQTMediaBackend::GetState 855// 856// Determines the current state - the timer keeps track of whether or not 857// we are paused or stopped (if the timer is running we are playing) 858//--------------------------------------------------------------------------- 859wxMediaState wxQTMediaBackend::GetState() 860{ 861 // Could use 862 // GetMovieActive/IsMovieDone/SetMovieActive 863 // combo if implemented that way 864 if (m_bPlaying) 865 return wxMEDIASTATE_PLAYING; 866 else if (!m_movie || wxQTMediaBackend::GetPosition() == 0) 867 return wxMEDIASTATE_STOPPED; 868 else 869 return wxMEDIASTATE_PAUSED; 870} 871 872//--------------------------------------------------------------------------- 873// wxQTMediaBackend::Cleanup 874// 875// Diposes of the movie timer, Control if native, and stops and disposes 876// of the QT movie 877//--------------------------------------------------------------------------- 878void wxQTMediaBackend::Cleanup() 879{ 880 m_bPlaying = false; 881 if (m_timer) 882 { 883 delete m_timer; 884 m_timer = NULL; 885 } 886 887 // Stop the movie: 888 // Apple samples with CreateMovieControl typically 889 // install a event handler and do this on the dispose 890 // event, but we do it here for simplicity 891 // (It might keep playing for several seconds after 892 // control destruction if not) 893 wxQTMediaBackend::Pause(); 894 895 // Dispose of control or remove movie from MovieController 896 Point thePoint; 897 thePoint.h = thePoint.v = 0; 898 ::MCSetVisible(m_mc, false); 899 ::MCSetMovie(m_mc, NULL, NULL, thePoint); 900 901 ::DisposeMovie(m_movie); 902 m_movie = NULL; 903} 904 905//--------------------------------------------------------------------------- 906// wxQTMediaBackend::GetVideoSize 907// 908// Returns the actual size of the QT movie 909//--------------------------------------------------------------------------- 910wxSize wxQTMediaBackend::GetVideoSize() const 911{ 912 return m_bestSize; 913} 914 915//--------------------------------------------------------------------------- 916// wxQTMediaBackend::Move 917// 918// Move the movie controller or movie control 919// (we need to actually move the movie control manually...) 920// Top 10 things to do with quicktime in March 93's issue 921// of DEVELOP - very useful 922// http:// www.mactech.com/articles/develop/issue_13/031-033_QuickTime_column.html 923// OLD NOTE: Calling MCSetControllerBoundsRect without detaching 924// supposively resulted in a crash back then. Current code even 925// with CFM classic runs fine. If there is ever a problem, 926// take out the if 0 lines below 927//--------------------------------------------------------------------------- 928void wxQTMediaBackend::Move(int x, int y, int w, int h) 929{ 930 if (m_timer) 931 { 932 m_ctrl->GetParent()->MacWindowToRootWindow(&x, &y); 933 Rect theRect = {y, x, y + h, x + w}; 934 935#if 0 // see note above 936 ::MCSetControllerAttached(m_mc, false); 937 wxASSERT(::GetMoviesError() == noErr); 938#endif 939 940 ::MCSetControllerBoundsRect(m_mc, &theRect); 941 wxASSERT(::GetMoviesError() == noErr); 942 943#if 0 // see note above 944 if (m_interfaceflags) 945 { 946 ::MCSetVisible(m_mc, true); 947 wxASSERT(::GetMoviesError() == noErr); 948 } 949#endif 950 } 951} 952 953//--------------------------------------------------------------------------- 954// wxQTMediaBackend::DoSetControllerVisible 955// 956// Utility function that takes care of showing the moviecontroller 957// and showing/hiding the particular controls on it 958//--------------------------------------------------------------------------- 959void wxQTMediaBackend::DoSetControllerVisible( 960 wxMediaCtrlPlayerControls flags) 961{ 962 ::MCSetVisible(m_mc, true); 963 964 // Take care of subcontrols 965 if (::GetMoviesError() == noErr) 966 { 967 long mcFlags = 0; 968 ::MCDoAction(m_mc, 39/*mcActionGetFlags*/, (void*)&mcFlags); 969 970 if (::GetMoviesError() == noErr) 971 { 972 mcFlags |= ( //(1<<0)/*mcFlagSuppressMovieFrame*/ | 973 (1 << 3)/*mcFlagsUseWindowPalette*/ 974 | ((flags & wxMEDIACTRLPLAYERCONTROLS_STEP) 975 ? 0 : (1 << 1)/*mcFlagSuppressStepButtons*/) 976 | ((flags & wxMEDIACTRLPLAYERCONTROLS_VOLUME) 977 ? 0 : (1 << 2)/*mcFlagSuppressSpeakerButton*/) 978 //if we take care of repainting ourselves 979 // | (1 << 4) /*mcFlagDontInvalidate*/ 980 ); 981 982 ::MCDoAction(m_mc, 38/*mcActionSetFlags*/, (void*)mcFlags); 983 } 984 } 985 986 // Adjust height and width of best size for movie controller 987 // if the user wants it shown 988 m_bestSize.x = m_bestSize.x > wxMCWIDTH ? m_bestSize.x : wxMCWIDTH; 989 m_bestSize.y += wxMCHEIGHT; 990} 991 992//--------------------------------------------------------------------------- 993// wxQTMediaBackend::ShowPlayerControls 994// 995// Shows/Hides subcontrols on the media control 996//--------------------------------------------------------------------------- 997bool wxQTMediaBackend::ShowPlayerControls(wxMediaCtrlPlayerControls flags) 998{ 999 if (!m_mc) 1000 return false; // no movie controller... 1001 1002 bool bSizeChanged = false; 1003 1004 // if the controller is visible and we want to hide it do so 1005 if (m_interfaceflags && !flags) 1006 { 1007 bSizeChanged = true; 1008 DoLoadBestSize(); 1009 ::MCSetVisible(m_mc, false); 1010 } 1011 else if (!m_interfaceflags && flags) // show controller if hidden 1012 { 1013 bSizeChanged = true; 1014 DoSetControllerVisible(flags); 1015 } 1016 1017 // readjust parent sizers 1018 if (bSizeChanged) 1019 { 1020 NotifyMovieSizeChanged(); 1021 1022 // remember state in case of loading new media 1023 m_interfaceflags = flags; 1024 } 1025 1026 return ::GetMoviesError() == noErr; 1027} 1028 1029//--------------------------------------------------------------------------- 1030// wxQTMediaBackend::GetDataSizeFromStart 1031// 1032// Calls either GetMovieDataSize or GetMovieDataSize64 with a value 1033// of 0 for the starting value 1034//--------------------------------------------------------------------------- 1035wxLongLong wxQTMediaBackend::GetDataSizeFromStart(TimeValue end) 1036{ 1037#if 0 // old pre-qt4 way 1038 return ::GetMovieDataSize(m_movie, 0, end) 1039#else // qt4 way 1040 wide llDataSize; 1041 ::GetMovieDataSize64(m_movie, 0, end, &llDataSize); 1042 return wxLongLong(llDataSize.hi, llDataSize.lo); 1043#endif 1044} 1045 1046//--------------------------------------------------------------------------- 1047// wxQTMediaBackend::GetDownloadProgress 1048//--------------------------------------------------------------------------- 1049wxLongLong wxQTMediaBackend::GetDownloadProgress() 1050{ 1051#if 0 // hackish and slow 1052 Handle hMovie = NewHandle(0); 1053 PutMovieIntoHandle(m_movie, hMovie); 1054 long lSize = GetHandleSize(hMovie); 1055 DisposeHandle(hMovie); 1056 1057 return lSize; 1058#else 1059 TimeValue tv; 1060 if (::GetMaxLoadedTimeInMovie(m_movie, &tv) != noErr) 1061 { 1062 wxLogDebug(wxT("GetMaxLoadedTimeInMovie failed")); 1063 return 0; 1064 } 1065 1066 return wxQTMediaBackend::GetDataSizeFromStart(tv); 1067#endif 1068} 1069 1070//--------------------------------------------------------------------------- 1071// wxQTMediaBackend::GetDownloadTotal 1072//--------------------------------------------------------------------------- 1073wxLongLong wxQTMediaBackend::GetDownloadTotal() 1074{ 1075 return wxQTMediaBackend::GetDataSizeFromStart( 1076 ::GetMovieDuration(m_movie) 1077 ); 1078} 1079 1080//--------------------------------------------------------------------------- 1081// wxQTMediaBackend::MacVisibilityChanged 1082// 1083// The main problem here is that Windows quicktime, for example, 1084// renders more directly to a HWND. Mac quicktime does not do this 1085// and instead renders to the port of the WindowRef/WindowPtr on top 1086// of everything else/all other windows. 1087// 1088// So, for example, if you were to have a CreateTabsControl/wxNotebook 1089// and change pages, even if you called HIViewSetVisible/SetControlVisibility 1090// directly the movie will still continue playing on top of everything else 1091// if you went to a different tab. 1092// 1093// Note that another issue, and why we call MCSetControllerPort instead 1094// of SetMovieGWorld directly, is that in addition to rendering on 1095// top of everything else the last created controller steals mouse and 1096// other input from everything else in the window, including other 1097// controllers. Setting the port of it releases this behaviour. 1098//--------------------------------------------------------------------------- 1099void wxQTMediaBackend::MacVisibilityChanged() 1100{ 1101 if(!m_mc || !m_ctrl->m_bLoaded) 1102 return; //not initialized yet 1103 1104 if(m_ctrl->MacIsReallyShown()) 1105 { 1106 //The window is being shown again, so set the GWorld of the 1107 //controller back to the port of the parent WindowRef 1108 WindowRef wrTLW = 1109 (WindowRef) m_ctrl->MacGetTopLevelWindowRef(); 1110 1111 ::MCSetControllerPort(m_mc, (CGrafPtr) GetWindowPort(wrTLW)); 1112 wxASSERT(::GetMoviesError() == noErr); 1113 } 1114 else 1115 { 1116 //We are being hidden - set the GWorld of the controller 1117 //to the offscreen GWorld 1118 ::MCSetControllerPort(m_mc, m_movieWorld); 1119 wxASSERT(::GetMoviesError() == noErr); 1120 } 1121} 1122 1123//--------------------------------------------------------------------------- 1124// wxQTMediaBackend::OnEraseBackground 1125// 1126// Suggestion from Greg Hazel to repaint the movie when idle 1127// (on pause also) 1128//--------------------------------------------------------------------------- 1129void wxQTMediaEvtHandler::OnEraseBackground(wxEraseEvent& evt) 1130{ 1131 // Work around Nasty OSX drawing bug: 1132 // http://lists.apple.com/archives/QuickTime-API/2002/Feb/msg00311.html 1133 WindowRef wrTLW = (WindowRef) m_qtb->m_ctrl->MacGetTopLevelWindowRef(); 1134 1135 RgnHandle region = ::MCGetControllerBoundsRgn(m_qtb->m_mc); 1136 ::MCInvalidate(m_qtb->m_mc, wrTLW, region); 1137 ::MCIdle(m_qtb->m_mc); 1138} 1139 1140//--------------------------------------------------------------------------- 1141// wxQTMediaBackend::PPRMProc (static) 1142// 1143// Called when done PrePrerolling the movie. 1144// Note that in 99% of the cases this does nothing... 1145// Anyway we set up the loading timer here to tell us when the movie is done 1146//--------------------------------------------------------------------------- 1147pascal void wxQTMediaBackend::PPRMProc( 1148 Movie theMovie, 1149 OSErr WXUNUSED_UNLESS_DEBUG(theErr), 1150 void* theRefCon) 1151{ 1152 wxASSERT( theMovie ); 1153 wxASSERT( theRefCon ); 1154 wxASSERT( theErr == noErr ); 1155 1156 wxQTMediaBackend* pBE = (wxQTMediaBackend*) theRefCon; 1157 1158 long lTime = ::GetMovieTime(theMovie,NULL); 1159 Fixed rate = ::GetMoviePreferredRate(theMovie); 1160 ::PrerollMovie(theMovie,lTime,rate); 1161 pBE->m_timer = new wxQTMediaLoadTimer(pBE); 1162 pBE->m_timer->Start(MOVIE_DELAY); 1163} 1164 1165//--------------------------------------------------------------------------- 1166// wxQTMediaBackend::MCFilterProc (static) 1167// 1168// Callback for when the movie controller recieves a message 1169//--------------------------------------------------------------------------- 1170pascal Boolean wxQTMediaBackend::MCFilterProc( 1171 MovieController WXUNUSED(theController), 1172 short action, 1173 void * WXUNUSED(params), 1174 long refCon) 1175{ 1176 wxQTMediaBackend* pThis = (wxQTMediaBackend*)refCon; 1177 1178 switch (action) 1179 { 1180 case 1: 1181 // don't process idle events 1182 break; 1183 1184 case 8: 1185 // play button triggered - MC will set movie to opposite state 1186 // of current - playing ? paused : playing 1187 pThis->m_bPlaying = !(pThis->m_bPlaying); 1188 break; 1189 1190 default: 1191 break; 1192 } 1193 1194 return 0; 1195} 1196 1197//--------------------------------------------------------------------------- 1198// wxQTMediaBackend::WindowEventHandler [static] 1199// 1200// Event callback for the top level window of our control that passes 1201// messages to our moviecontroller so it can receive mouse clicks etc. 1202//--------------------------------------------------------------------------- 1203pascal OSStatus wxQTMediaBackend::WindowEventHandler( 1204 EventHandlerCallRef inHandlerCallRef, 1205 EventRef inEvent, 1206 void *inUserData) 1207{ 1208 wxQTMediaBackend* be = (wxQTMediaBackend*) inUserData; 1209 1210 // Only process keyboard messages on this window if it actually 1211 // has focus, otherwise it will steal keystrokes from other windows! 1212 // As well as when it is not loaded properly as it 1213 // will crash in MCIsPlayerEvent 1214 if((GetEventClass(inEvent) == kEventClassKeyboard && 1215 wxWindow::FindFocus() != be->m_ctrl) 1216 || !be->m_ctrl->m_bLoaded) 1217 return eventNotHandledErr; 1218 1219 // Pass the event onto the movie controller 1220 EventRecord theEvent; 1221 ConvertEventRefToEventRecord( inEvent, &theEvent ); 1222 OSStatus err; 1223 1224 // TODO: Apple says MCIsPlayerEvent is depreciated and 1225 // MCClick, MCKey, MCIdle etc. should be used 1226 // (RN: Of course that's what they say about 1227 // CreateMovieControl and HIMovieView as well, LOL!) 1228 err = ::MCIsPlayerEvent( be->m_mc, &theEvent ); 1229 1230 // Pass on to other event handlers if not handled- i.e. wx 1231 if (err != noErr) 1232 return noErr; 1233 else 1234 return eventNotHandledErr; 1235} 1236 1237#endif 1238 1239// in source file that contains stuff you don't directly use 1240#include "wx/html/forcelnk.h" 1241FORCE_LINK_ME(basewxmediabackends) 1242 1243#endif // wxUSE_MEDIACTRL 1244