1/////////////////////////////////////////////////////////////////////////////
2// Name:        src/cocoa/mediactrl.mm
3// Purpose:     Built-in Media Backends for Cocoa
4// Author:      Ryan Norton <wxprojects@comcast.net>
5// Modified by:
6// Created:     02/03/05
7// Copyright:   (c) 2004-2005 Ryan Norton, (c) 2005 David Elliot
8// Licence:     wxWindows licence
9/////////////////////////////////////////////////////////////////////////////
10
11//===========================================================================
12//  DECLARATIONS
13//===========================================================================
14
15//---------------------------------------------------------------------------
16// Pre-compiled header stuff
17//---------------------------------------------------------------------------
18
19// For compilers that support precompilation, includes "wx.h".
20#include "wx/wxprec.h"
21
22#ifdef __BORLANDC__
23#pragma hdrstop
24#endif
25
26//---------------------------------------------------------------------------
27// Compilation guard
28//---------------------------------------------------------------------------
29#if wxUSE_MEDIACTRL
30
31#include "wx/mediactrl.h"
32
33#ifndef WX_PRECOMP
34    #include "wx/timer.h"
35#endif
36
37//===========================================================================
38//  BACKEND DECLARATIONS
39//===========================================================================
40
41//---------------------------------------------------------------------------
42//
43//  wxQTMediaBackend
44//
45//---------------------------------------------------------------------------
46
47//---------------------------------------------------------------------------
48//  QT Includes
49//---------------------------------------------------------------------------
50#include <QuickTime/QuickTime.h>
51
52#include "wx/cocoa/autorelease.h"
53#include "wx/cocoa/string.h"
54
55#import <AppKit/NSMovie.h>
56#import <AppKit/NSMovieView.h>
57
58
59class WXDLLIMPEXP_MEDIA wxQTMediaBackend : public wxMediaBackend
60{
61public:
62
63    wxQTMediaBackend();
64    ~wxQTMediaBackend();
65
66    virtual bool CreateControl(wxControl* ctrl, wxWindow* parent,
67                                     wxWindowID id,
68                                     const wxPoint& pos,
69                                     const wxSize& size,
70                                     long style,
71                                     const wxValidator& validator,
72                                     const wxString& name);
73
74    virtual bool Play();
75    virtual bool Pause();
76    virtual bool Stop();
77
78    virtual bool Load(const wxString& fileName);
79    virtual bool Load(const wxURI& location);
80
81    virtual wxMediaState GetState();
82
83    virtual bool SetPosition(wxLongLong where);
84    virtual wxLongLong GetPosition();
85    virtual wxLongLong GetDuration();
86
87    virtual void Move(int x, int y, int w, int h);
88    wxSize GetVideoSize() const;
89
90    virtual double GetPlaybackRate();
91    virtual bool SetPlaybackRate(double dRate);
92
93    void Cleanup();
94    void FinishLoad();
95
96    wxSize m_bestSize;              //Original movie size
97    Movie m_movie;                  //QT Movie handle/instance
98    NSMovieView* m_movieview;       //NSMovieView instance
99    wxControl* m_ctrl;              //Parent control
100    bool m_bVideo;                  //Whether or not we have video
101    class _wxQTTimer* m_timer;      //Timer for streaming the movie
102
103    DECLARE_DYNAMIC_CLASS(wxQTMediaBackend);
104};
105
106
107//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
108//
109// wxQTMediaBackend
110//
111//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
112
113IMPLEMENT_DYNAMIC_CLASS(wxQTMediaBackend, wxMediaBackend);
114
115//Time between timer calls
116#define MOVIE_DELAY 100
117
118// --------------------------------------------------------------------------
119//          wxQTTimer - Handle Asyncronous Playing
120// --------------------------------------------------------------------------
121class _wxQTTimer : public wxTimer
122{
123public:
124    _wxQTTimer(Movie movie, wxQTMediaBackend* parent) :
125        m_movie(movie), m_bPaused(false), m_parent(parent)
126    {
127    }
128
129    ~_wxQTTimer()
130    {
131    }
132
133    bool GetPaused() {return m_bPaused;}
134    void SetPaused(bool bPaused) {m_bPaused = bPaused;}
135
136    //-----------------------------------------------------------------------
137    // _wxQTTimer::Notify
138    //
139    // 1) Checks to see if the movie is done, and if not continues
140    //    streaming the movie
141    // 2) Sends the wxEVT_MEDIA_STOP event if we have reached the end of
142    //    the movie.
143    //-----------------------------------------------------------------------
144    void Notify()
145    {
146        if (!m_bPaused)
147        {
148            if(!IsMovieDone(m_movie))
149                MoviesTask(m_movie, MOVIE_DELAY);
150            else
151            {
152                wxMediaEvent theEvent(wxEVT_MEDIA_STOP,
153                                      m_parent->m_ctrl->GetId());
154                m_parent->m_ctrl->ProcessEvent(theEvent);
155
156                if(theEvent.IsAllowed())
157                {
158                    Stop();
159                    m_parent->Stop();
160                    wxASSERT(::GetMoviesError() == noErr);
161
162                    //send the event to our child
163                    wxMediaEvent theEvent(wxEVT_MEDIA_FINISHED,
164                                          m_parent->m_ctrl->GetId());
165                    m_parent->m_ctrl->ProcessEvent(theEvent);
166                }
167            }
168        }
169    }
170
171protected:
172    Movie m_movie;                  //Our movie instance
173    bool m_bPaused;                 //Whether we are paused or not
174    wxQTMediaBackend* m_parent;     //Backend pointer
175};
176
177//---------------------------------------------------------------------------
178// wxQTMediaBackend Constructor
179//
180// Sets m_timer to NULL signifying we havn't loaded anything yet
181//---------------------------------------------------------------------------
182wxQTMediaBackend::wxQTMediaBackend() : m_timer(NULL)
183{
184}
185
186//---------------------------------------------------------------------------
187// wxQTMediaBackend Destructor
188//
189// 1) Cleans up the QuickTime movie instance
190// 2) Decrements the QuickTime reference counter - if this reaches
191//    0, QuickTime shuts down
192// 3) Decrements the QuickTime Windows Media Layer reference counter -
193//    if this reaches 0, QuickTime shuts down the Windows Media Layer
194//---------------------------------------------------------------------------
195wxQTMediaBackend::~wxQTMediaBackend()
196{
197    if(m_timer)
198        Cleanup();
199
200    //Note that ExitMovies() is not necessary...
201    ExitMovies();
202}
203
204//---------------------------------------------------------------------------
205// wxQTMediaBackend::CreateControl
206//
207// 1) Intializes QuickTime
208// 2) Creates the control window
209//---------------------------------------------------------------------------
210bool wxQTMediaBackend::CreateControl(wxControl* inctrl, wxWindow* parent,
211                                     wxWindowID wid,
212                                     const wxPoint& pos,
213                                     const wxSize& size,
214                                     long style,
215                                     const wxValidator& validator,
216                                     const wxString& name)
217{
218    EnterMovies();
219
220    wxMediaCtrl* ctrl = (wxMediaCtrl*) inctrl;
221
222    //Create the control base
223    wxASSERT(ctrl->CreateBase(parent,wid,pos,size,style, validator, name));
224
225    //Create the NSMovieView
226    ctrl->SetNSView(NULL);
227    NSMovieView* theView = [[NSMovieView alloc] initWithFrame: ctrl->MakeDefaultNSRect(size)];
228    ctrl->SetNSView(theView);
229    [theView release];
230
231    if (parent)
232    {
233        parent->AddChild(ctrl);
234        parent->CocoaAddChild(ctrl);
235        ctrl->SetInitialFrameRect(pos,size);
236    }
237
238    [theView showController:false adjustingSize:true];
239    m_movieview = theView;
240    m_ctrl = ctrl;
241    return true;
242}
243
244//---------------------------------------------------------------------------
245// wxQTMediaBackend::Load (file version)
246//
247// Calls the URI version
248//---------------------------------------------------------------------------
249bool wxQTMediaBackend::Load(const wxString& fileName)
250{
251    return Load(
252                wxURI(
253                    wxString( wxT("file://") ) + fileName
254                     )
255               );
256}
257
258//---------------------------------------------------------------------------
259// wxQTMediaBackend::Load (URL Version)
260//
261// 1) Build an escaped URI from location
262// ...
263//---------------------------------------------------------------------------
264bool wxQTMediaBackend::Load(const wxURI& location)
265{
266    if(m_timer)
267        Cleanup();
268
269    wxString theURI = location.BuildURI();
270
271    [m_movieview setMovie:[[NSMovie alloc] initWithURL: [NSURL URLWithString: wxNSStringWithWxString(theURI)]
272                               byReference: YES ] ];
273
274    m_movie = (Movie) [[m_movieview movie] QTMovie];
275
276    //preroll movie for streaming
277    //TODO:Async this using threads?
278    TimeValue timeNow;
279    Fixed playRate;
280    timeNow = GetMovieTime(m_movie, NULL);
281    playRate = GetMoviePreferredRate(m_movie);
282    PrePrerollMovie(m_movie, timeNow, playRate, NULL, NULL);
283    PrerollMovie(m_movie, timeNow, playRate);
284    SetMovieRate(m_movie, playRate);
285
286    FinishLoad();
287
288    return ::GetMoviesError() == noErr;
289}
290
291//---------------------------------------------------------------------------
292// wxQTMediaBackend::FinishLoad
293//
294// 1) Create the movie timer
295// 2) Get real size of movie for GetBestSize/sizers
296// 3) See if there is video in the movie, and if so then either
297//    SetMovieGWorld if < 10.2 or use Native CreateMovieControl
298// 4) Set the movie time scale to something usable so that seeking
299//    etc.  will work correctly
300// 5) Refresh parent window
301//---------------------------------------------------------------------------
302void wxQTMediaBackend::FinishLoad()
303{
304    m_timer = new _wxQTTimer(m_movie, (wxQTMediaBackend*) this);
305    wxASSERT(m_timer);
306
307    //get the real size of the movie
308    Rect outRect;
309    ::GetMovieNaturalBoundsRect (m_movie, &outRect);
310    wxASSERT(::GetMoviesError() == noErr);
311
312    m_bestSize.x = outRect.right - outRect.left;
313    m_bestSize.y = outRect.bottom - outRect.top;
314
315    //we want millisecond precision
316    ::SetMovieTimeScale(m_movie, 1000);
317    wxASSERT(::GetMoviesError() == noErr);
318
319    //
320    //Here, if the parent of the control has a sizer - we
321    //tell it to recalculate the size of this control since
322    //the user opened a separate media file
323    //
324    m_ctrl->InvalidateBestSize();
325    m_ctrl->GetParent()->Layout();
326    m_ctrl->GetParent()->Refresh();
327    m_ctrl->GetParent()->Update();
328}
329
330//---------------------------------------------------------------------------
331// wxQTMediaBackend::Play
332//
333// 1) Start the QT movie
334// 2) Start the movie loading timer
335//---------------------------------------------------------------------------
336bool wxQTMediaBackend::Play()
337{
338    ::StartMovie(m_movie);
339    m_timer->SetPaused(false);
340    m_timer->Start(MOVIE_DELAY, wxTIMER_CONTINUOUS);
341    return ::GetMoviesError() == noErr;
342}
343
344//---------------------------------------------------------------------------
345// wxQTMediaBackend::Pause
346//
347// 1) Stop the movie
348// 2) Stop the movie timer
349//---------------------------------------------------------------------------
350bool wxQTMediaBackend::Pause()
351{
352    ::StopMovie(m_movie);
353    m_timer->SetPaused(true);
354    m_timer->Stop();
355    return ::GetMoviesError() == noErr;
356}
357
358//---------------------------------------------------------------------------
359// wxQTMediaBackend::Stop
360//
361// 1) Stop the movie
362// 2) Stop the movie timer
363// 3) Seek to the beginning of the movie
364//---------------------------------------------------------------------------
365bool wxQTMediaBackend::Stop()
366{
367    m_timer->SetPaused(false);
368    m_timer->Stop();
369
370    ::StopMovie(m_movie);
371    if(::GetMoviesError() != noErr)
372        return false;
373
374    ::GoToBeginningOfMovie(m_movie);
375    return ::GetMoviesError() == noErr;
376}
377
378//---------------------------------------------------------------------------
379// wxQTMediaBackend::GetPlaybackRate
380//
381// 1) Get the movie playback rate from ::GetMovieRate
382//---------------------------------------------------------------------------
383double wxQTMediaBackend::GetPlaybackRate()
384{
385    return ( ((double)::GetMovieRate(m_movie)) / 0x10000);
386}
387
388//---------------------------------------------------------------------------
389// wxQTMediaBackend::SetPlaybackRate
390//
391// 1) Convert dRate to Fixed and Set the movie rate through SetMovieRate
392//---------------------------------------------------------------------------
393bool wxQTMediaBackend::SetPlaybackRate(double dRate)
394{
395    ::SetMovieRate(m_movie, (Fixed) (dRate * 0x10000));
396    return ::GetMoviesError() == noErr;
397}
398
399//---------------------------------------------------------------------------
400// wxQTMediaBackend::SetPosition
401//
402// 1) Create a time record struct (TimeRecord) with appropriate values
403// 2) Pass struct to SetMovieTime
404//---------------------------------------------------------------------------
405bool wxQTMediaBackend::SetPosition(wxLongLong where)
406{
407    TimeRecord theTimeRecord;
408    memset(&theTimeRecord, 0, sizeof(TimeRecord));
409    theTimeRecord.value.lo = where.GetValue();
410    theTimeRecord.scale = ::GetMovieTimeScale(m_movie);
411    theTimeRecord.base = ::GetMovieTimeBase(m_movie);
412    ::SetMovieTime(m_movie, &theTimeRecord);
413
414    if (::GetMoviesError() != noErr)
415        return false;
416
417    return true;
418}
419
420//---------------------------------------------------------------------------
421// wxQTMediaBackend::GetPosition
422//
423// Calls GetMovieTime
424//---------------------------------------------------------------------------
425wxLongLong wxQTMediaBackend::GetPosition()
426{
427    return ::GetMovieTime(m_movie, NULL);
428}
429
430//---------------------------------------------------------------------------
431// wxQTMediaBackend::GetDuration
432//
433// Calls GetMovieDuration
434//---------------------------------------------------------------------------
435wxLongLong wxQTMediaBackend::GetDuration()
436{
437    return ::GetMovieDuration(m_movie);
438}
439
440//---------------------------------------------------------------------------
441// wxQTMediaBackend::GetState
442//
443// Determines the current state - the timer keeps track of whether or not
444// we are paused or stopped (if the timer is running we are playing)
445//---------------------------------------------------------------------------
446wxMediaState wxQTMediaBackend::GetState()
447{
448    if ( !m_timer || (m_timer->IsRunning() == false &&
449                      m_timer->GetPaused() == false) )
450        return wxMEDIASTATE_STOPPED;
451
452    if( m_timer->IsRunning() == true )
453        return wxMEDIASTATE_PLAYING;
454    else
455        return wxMEDIASTATE_PAUSED;
456}
457
458//---------------------------------------------------------------------------
459// wxQTMediaBackend::Cleanup
460//
461// Diposes of the movie timer, Control if native, and stops and disposes
462// of the QT movie
463//---------------------------------------------------------------------------
464void wxQTMediaBackend::Cleanup()
465{
466    delete m_timer;
467    m_timer = NULL;
468
469    [[m_movieview movie] release];
470    [m_movieview setMovie:NULL];
471}
472
473//---------------------------------------------------------------------------
474// wxQTMediaBackend::GetVideoSize
475//
476// Returns the actual size of the QT movie
477//---------------------------------------------------------------------------
478wxSize wxQTMediaBackend::GetVideoSize() const
479{
480    return m_bestSize;
481}
482
483//---------------------------------------------------------------------------
484// wxQTMediaBackend::Move
485//
486// Nothin... cocoa takes care of this for us
487//---------------------------------------------------------------------------
488void wxQTMediaBackend::Move(int x, int y, int w, int h)
489{
490}
491
492
493//in source file that contains stuff you don't directly use
494#include "wx/html/forcelnk.h"
495FORCE_LINK_ME(basewxmediabackends);
496
497#endif //wxUSE_MEDIACTRL
498