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