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