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