2 * Copyright (C) 2003-2006 Gabest
3 * http://www.gabest.org
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with GNU Make; see the file COPYING. If not, write to
17 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
18 * http://www.gnu.org/copyleft/gpl.html
24 #include "QuicktimeGraph.h"
25 #include "IQTVideoSurface.h"
27 #include "..\..\DSUtil\DSUtil.h"
33 #pragma warning(disable:4355) // 'this' : used in base member initializer list
37 CQuicktimeGraph::CQuicktimeGraph(HWND hWndParent
, HRESULT
& hr
)
39 , m_wndDestFrame(this)
40 , m_fQtInitialized(false)
44 DWORD dwStyle
= WS_CHILD
|WS_VISIBLE
|WS_CLIPSIBLINGS
|WS_CLIPCHILDREN
;
46 AppSettings
& s
= AfxGetAppSettings();
48 if(s
.iQTVideoRendererType
== VIDRNDT_QT_DX7
)
50 if(SUCCEEDED(CreateAP7(CLSID_QT7AllocatorPresenter
, hWndParent
, &m_pQTAP
)))
51 dwStyle
&= ~WS_VISIBLE
;
53 else if(s
.iQTVideoRendererType
== VIDRNDT_QT_DX9
)
55 if(SUCCEEDED(CreateAP9(CLSID_QT9AllocatorPresenter
, hWndParent
, &m_pQTAP
)))
56 dwStyle
&= ~WS_VISIBLE
;
59 m_fQtInitialized
= false;
60 if(InitializeQTML(0) != 0) {hr
= E_FAIL
; return;}
61 if(EnterMovies() != 0) {TerminateQTML(); hr
= E_FAIL
; return;}
62 m_fQtInitialized
= true;
64 if(!m_wndWindowFrame
.CreateEx(WS_EX_NOPARENTNOTIFY
, NULL
, NULL
, dwStyle
, CRect(0, 0, 0, 0), CWnd::FromHandle(hWndParent
), 0))
70 if(!m_wndDestFrame
.Create(NULL
, NULL
, dwStyle
, CRect(0, 0, 0, 0), &m_wndWindowFrame
, 0))
77 CQuicktimeGraph::~CQuicktimeGraph()
79 m_wndDestFrame
.DestroyWindow();
80 m_wndWindowFrame
.DestroyWindow();
89 STDMETHODIMP
CQuicktimeGraph::NonDelegatingQueryInterface(REFIID riid
, void** ppv
)
91 CheckPointer(ppv
, E_POINTER
);
95 (m_pQTAP
&& (riid
== __uuidof(ISubPicAllocatorPresenter
) || riid
== __uuidof(IQTVideoSurface
))) ? m_pQTAP
->QueryInterface(riid
, ppv
) :
96 __super::NonDelegatingQueryInterface(riid
, ppv
);
100 STDMETHODIMP
CQuicktimeGraph::RenderFile(LPCWSTR lpcwstrFile
, LPCWSTR lpcwstrPlayList
)
102 bool fRet
= m_wndDestFrame
.OpenMovie(CString(lpcwstrFile
));
106 for(int i
= 1, cnt
= GetMovieTrackCount(m_wndDestFrame
.theMovie
); i
<= cnt
; i
++)
108 Track aTrack
= GetMovieIndTrack(m_wndDestFrame
.theMovie
, i
);
109 Media aMedia
= GetTrackMedia(aTrack
);
112 GetMediaHandlerDescription(aMedia
, &aTrackType
, 0, 0);
113 if(aTrackType
== SoundMediaType
)
115 SampleDescriptionHandle aDesc
= (SampleDescriptionHandle
)NewHandle(sizeof(aDesc
));
116 GetMediaSampleDescription(aMedia
, 1, aDesc
);
117 if(GetMoviesError() == noErr
)
119 SoundDescription
& desc
= **(SoundDescriptionHandle
)aDesc
;
120 NotifyEvent(EC_BG_AUDIO_CHANGED
, desc
.numChannels
, 0);
123 DisposeHandle((Handle
)aDesc
);
128 return fRet
? S_OK
: E_FAIL
;
132 STDMETHODIMP
CQuicktimeGraph::Run()
134 m_wndDestFrame
.Run();
137 STDMETHODIMP
CQuicktimeGraph::Pause()
139 m_wndDestFrame
.Pause();
142 STDMETHODIMP
CQuicktimeGraph::Stop()
144 m_wndDestFrame
.Stop();
147 STDMETHODIMP
CQuicktimeGraph::GetState(LONG msTimeout
, OAFilterState
* pfs
)
149 // TODO: this seems to deadlock when opening from the net
150 return pfs
? *pfs
= m_wndDestFrame
.GetState(), S_OK
: E_POINTER
;
154 STDMETHODIMP
CQuicktimeGraph::GetDuration(LONGLONG
* pDuration
)
156 CheckPointer(pDuration
, E_POINTER
);
160 if(!m_wndDestFrame
.theMovie
) return E_UNEXPECTED
;
162 TimeScale ts
= GetMovieTimeScale(m_wndDestFrame
.theMovie
);
163 if(ts
== 0) return E_FAIL
;
165 *pDuration
= 10000i64
*GetMovieDuration(m_wndDestFrame
.theMovie
)/ts
*1000;
169 STDMETHODIMP
CQuicktimeGraph::GetCurrentPosition(LONGLONG
* pCurrent
)
171 CheckPointer(pCurrent
, E_POINTER
);
175 if(!m_wndDestFrame
.theMovie
) return E_UNEXPECTED
;
177 TimeScale ts
= GetMovieTimeScale(m_wndDestFrame
.theMovie
);
178 if(ts
== 0) return E_FAIL
;
181 *pCurrent
= 10000i64
*GetMovieTime(m_wndDestFrame
.theMovie
, &tr
)/ts
*1000;
185 STDMETHODIMP
CQuicktimeGraph::SetPositions(LONGLONG
* pCurrent
, DWORD dwCurrentFlags
, LONGLONG
* pStop
, DWORD dwStopFlags
)
187 CheckPointer(pCurrent
, E_POINTER
);
189 if(!(dwCurrentFlags
&AM_SEEKING_AbsolutePositioning
)) return E_INVALIDARG
;
191 if(!m_wndDestFrame
.theMovie
) return E_UNEXPECTED
;
193 TimeScale ts
= GetMovieTimeScale(m_wndDestFrame
.theMovie
);
194 if(ts
== 0) return E_FAIL
;
196 SetMovieTimeValue(m_wndDestFrame
.theMovie
, (TimeValue
)(*pCurrent
*ts
/1000/10000i64
));
198 if(!m_wndDestFrame
.theMC
)
200 UpdateMovie(m_wndDestFrame
.theMovie
);
201 MoviesTask(m_wndDestFrame
.theMovie
, 0L);
206 STDMETHODIMP
CQuicktimeGraph::SetRate(double dRate
)
208 return m_wndDestFrame
.theMovie
? SetMovieRate(m_wndDestFrame
.theMovie
, (Fixed
)(dRate
* 0x10000)), S_OK
: E_UNEXPECTED
;
210 STDMETHODIMP
CQuicktimeGraph::GetRate(double* pdRate
)
212 CheckPointer(pdRate
, E_POINTER
);
214 return m_wndDestFrame
.theMovie
? *pdRate
= (double)GetMovieRate(m_wndDestFrame
.theMovie
) / 0x10000, S_OK
: E_UNEXPECTED
;
218 STDMETHODIMP
CQuicktimeGraph::SetWindowPosition(long Left
, long Top
, long Width
, long Height
)
220 if(IsWindow(m_wndWindowFrame
.m_hWnd
))
221 m_wndWindowFrame
.MoveWindow(Left
, Top
, Width
, Height
);
227 STDMETHODIMP
CQuicktimeGraph::SetDestinationPosition(long Left
, long Top
, long Width
, long Height
)// {return E_NOTIMPL;}
229 if(!m_pQTAP
&& IsWindow(m_wndDestFrame
.m_hWnd
))
231 m_wndDestFrame
.MoveWindow(Left
, Top
, Width
, Height
);
233 if(m_wndDestFrame
.theMC
)
235 Rect bounds
= {0,0,(short)Height
,(short)Width
};
236 MCPositionController(m_wndDestFrame
.theMC
, &bounds
, NULL
, mcTopLeftMovie
|mcScaleMovieToFit
);
242 STDMETHODIMP
CQuicktimeGraph::GetVideoSize(long* pWidth
, long* pHeight
)
244 if(!pWidth
|| !pHeight
) return E_POINTER
;
246 *pWidth
= m_wndDestFrame
.m_size
.cx
;
247 *pHeight
= m_wndDestFrame
.m_size
.cy
;
253 STDMETHODIMP
CQuicktimeGraph::put_Volume(long lVolume
)
255 if(m_wndDestFrame
.theMovie
)
257 lVolume
= (lVolume
== -10000) ? 0 : (int)pow(10.0, (double)lVolume
/4152.41 + 2.41);
258 SetMovieVolume(m_wndDestFrame
.theMovie
, (short)max(min(lVolume
, 256), 0));
264 STDMETHODIMP
CQuicktimeGraph::get_Volume(long* plVolume
)
266 CheckPointer(plVolume
, E_POINTER
);
268 if(m_wndDestFrame
.theMovie
)
270 long lVolume
= (long)GetMovieVolume(m_wndDestFrame
.theMovie
);
271 *plVolume
= (int)((log10(1.0*lVolume
)-2.41)*4152.41);
272 *plVolume
= max(min(*plVolume
, 0), -10000);
280 STDMETHODIMP
CQuicktimeGraph::Step(DWORD dwFrames
, IUnknown
* pStepObject
)
282 if(pStepObject
) return E_INVALIDARG
;
283 if(dwFrames
== 0) return S_OK
;
284 if(!m_wndDestFrame
.theMovie
) return E_UNEXPECTED
;
286 // w/o m_wndDestFrame.theMC
288 OSType myTypes
[] = {VisualMediaCharacteristic
};
289 TimeValue myCurrTime
= GetMovieTime(m_wndDestFrame
.theMovie
, NULL
);
290 Fixed theRate
= (int)dwFrames
> 0 ? 0x00010000 : 0xffff0000;
292 for(int nSteps
= abs((int)dwFrames
); nSteps
> 0; nSteps
--)
294 TimeValue myNextTime
;
295 GetMovieNextInterestingTime(m_wndDestFrame
.theMovie
, nextTimeStep
, 1, myTypes
, myCurrTime
, theRate
, &myNextTime
, NULL
);
296 if(GetMoviesError() != noErr
) return E_FAIL
;
297 myCurrTime
= myNextTime
;
300 if(myCurrTime
>= 0 && myCurrTime
< GetMovieDuration(m_wndDestFrame
.theMovie
))
302 SetMovieTimeValue(m_wndDestFrame
.theMovie
, myCurrTime
);
303 if(GetMoviesError() != noErr
) return E_FAIL
;
304 // the rest is not needed when we also have m_wndDestFrame.theMC:
305 UpdateMovie(m_wndDestFrame
.theMovie
);
306 if(GetMoviesError() != noErr
) return E_FAIL
;
307 MoviesTask(m_wndDestFrame
.theMovie
, 0L);
310 NotifyEvent(EC_STEP_COMPLETE
);
315 // w/ m_wndDestFrame.theMC
317 short myStep = (short)(long)dwFrames;
318 return noErr == MCDoAction(m_wndDestFrame.theMC, mcActionStep, (Ptr)myStep)
319 ? NotifyEvent(EC_STEP_COMPLETE), S_OK : E_FAIL;
322 STDMETHODIMP
CQuicktimeGraph::CanStep(long bMultiple
, IUnknown
* pStepObject
)
324 return m_wndDestFrame
.theMovie
? S_OK
: S_FALSE
;
326 STDMETHODIMP
CQuicktimeGraph::CancelStep()
332 STDMETHODIMP_(engine_t
) CQuicktimeGraph::GetEngine() {return QuickTime
;}
338 CQuicktimeWindow::CQuicktimeWindow(CQuicktimeGraph
* pGraph
)
344 , m_fs(State_Stopped
)
345 , m_offscreenGWorld(NULL
)
349 void CQuicktimeWindow::ProcessMovieEvent(unsigned int message
, unsigned int wParam
, long lParam
)
351 if(message
>= WM_MOUSEFIRST
&& message
<= WM_MOUSELAST
352 || message
>= WM_KEYFIRST
&& message
<= WM_KEYLAST
)
355 // Convert the Windows event to a QTML event
357 EventRecord macEvent
;
358 LONG thePoints
= GetMessagePos();
360 theMsg
.hwnd
= m_hWnd
;
361 theMsg
.message
= message
;
362 theMsg
.wParam
= wParam
;
363 theMsg
.lParam
= lParam
;
364 theMsg
.time
= GetMessageTime();
365 theMsg
.pt
.x
= LOWORD(thePoints
);
366 theMsg
.pt
.y
= HIWORD(thePoints
);
368 // tranlate a windows event to a mac event
369 WinEventToMacEvent(&theMsg
, &macEvent
);
371 // Pump messages as mac event
372 MCIsPlayerEvent(theMC
, (const EventRecord
*)&macEvent
);
375 LRESULT
CQuicktimeWindow::WindowProc(UINT message
, WPARAM wParam
, LPARAM lParam
)
377 if(message
== WM_ERASEBKGND
)
379 LRESULT theResult
= __super::WindowProc(message
, wParam
, lParam
);
380 ProcessMovieEvent(message
, wParam
, lParam
);
385 ProcessMovieEvent(message
, wParam
, lParam
);
386 return __super::WindowProc(message
, wParam
, lParam
);
390 OSErr
CQuicktimeWindow::MyMovieDrawingCompleteProc(Movie theMovie
, long refCon
)
392 CQuicktimeWindow
* pQW
= (CQuicktimeWindow
*)refCon
;
393 if(!pQW
) return noErr
;
395 CQuicktimeGraph
* pGraph
= pQW
->m_pGraph
;
396 if(!pGraph
) return noErr
;
398 if(CComQIPtr
<IQTVideoSurface
> pQTVS
= (IUnknown
*)(INonDelegatingUnknown
*)pGraph
)
401 pQW
->m_bm
.GetObject(sizeof(bm
), &bm
);
413 bool CQuicktimeWindow::OpenMovie(CString fn
)
417 CComQIPtr
<IQTVideoSurface
> pQTVS
= (IUnknown
*)(INonDelegatingUnknown
*)m_pGraph
;
422 SetGWorld((CGrafPtr
)GetHWNDPort(m_hWnd
), NULL
);
425 if(fn
.Find(_T("://")) > 0)
427 Handle myHandle
= NULL
;
428 Size mySize
= fn
.GetLength()+1;
430 if(!(myHandle
= NewHandleClear(mySize
)))
433 BlockMove((LPSTR
)(LPCSTR
)CStringA(fn
), *myHandle
, mySize
);
435 OSErr err
= NewMovieFromDataRef(&theMovie
, newMovieActive
, NULL
, myHandle
, URLDataHandlerSubType
);
437 DisposeHandle(myHandle
);
439 if(err
!= noErr
) return(false);
443 if(!(fn
.GetLength() > 0 && fn
.GetLength() < 255))
446 CHAR buff
[MAX_PATH
] = {0, 0};
448 WideCharToMultiByte(GetACP(), 0, fn
, -1, buff
+1, MAX_PATH
-1, 0, 0);
452 buff
[0] = strlen(buff
+1);
454 // Make a FSSpec with a pascal string filename
456 FSMakeFSSpec(0, 0L, (BYTE
*)buff
, &sfFile
);
458 // Open the movie file
460 OSErr err
= OpenMovieFile(&sfFile
, &movieResFile
, fsRdPerm
);
463 err
= NewMovieFromFile(&theMovie
, movieResFile
, 0, 0, newMovieActive
, 0);
464 CloseMovieFile(movieResFile
);
466 if(err
!= noErr
) return(false);
470 GetMovieBox(theMovie
, &rect
);
471 MacOffsetRect(&rect
, -rect
.left
, -rect
.top
);
472 SetMovieBox(theMovie
, &rect
);
473 m_size
.SetSize(rect
.right
- rect
.left
, rect
.bottom
- rect
.top
);
476 GetMovieNaturalBoundsRect(theMovie
, &nrect
);
480 theMC
= NewMovieController(theMovie
, &rect
, mcTopLeftMovie
|mcNotVisible
);
482 else if(m_size
.cx
> 0 && m_size
.cy
> 0)
484 SetMovieDrawingCompleteProc(theMovie
,
485 movieDrawingCallWhenChanged
,//|movieDrawingCallAlways,
486 MyMovieDrawingCompleteProc
, (long)this);
488 if(CDC
* pDC
= GetDC())
490 m_dc
.CreateCompatibleDC(pDC
);
495 BITMAPINFOHEADER bmiHeader
;
499 memset(&bmi
, 0, sizeof(bmi
));
501 int bpp
= m_dc
.GetDeviceCaps(BITSPIXEL
);
503 bmi
.bmiHeader
.biSize
= sizeof(BITMAPINFOHEADER
);
504 bmi
.bmiHeader
.biCompression
= BI_BITFIELDS
/*BI_RGB*/;
505 bmi
.bmiHeader
.biWidth
= m_size
.cx
;
506 bmi
.bmiHeader
.biHeight
= -m_size
.cy
;
507 bmi
.bmiHeader
.biPlanes
= 1;
508 bmi
.bmiHeader
.biBitCount
= 32/*bpp*/;
510 bmi
.bmiColors
[0] = /*bpp == 16 ? 0xf800 :*/ 0xff0000;
511 bmi
.bmiColors
[1] = /*bpp == 16 ? 0x07e0 :*/ 0x00ff00;
512 bmi
.bmiColors
[2] = /*bpp == 16 ? 0x001f :*/ 0x0000ff;
515 m_bm
.Attach(CreateDIBSection(m_dc
, (BITMAPINFO
*)&bmi
, DIB_RGB_COLORS
, &bits
, NULL
, 0));
517 QDErr err
= NewGWorldFromHBITMAP(&m_offscreenGWorld
, NULL
, NULL
, 0, m_bm
.m_hObject
, m_dc
.m_hDC
);
519 SetMovieGWorld(theMovie
, m_offscreenGWorld
, GetGWorldDevice(m_offscreenGWorld
));
522 m_bm
.GetObject(sizeof(bm
), &bm
);
527 return(theMovie
!= NULL
);
530 void CQuicktimeWindow::CloseMovie()
532 if(theMC
) DisposeMovieController(theMC
), theMC
= NULL
;
533 if(theMovie
) DisposeMovie(theMovie
), theMovie
= NULL
;
534 m_size
.SetSize(0, 0);
535 m_fs
= State_Stopped
;
537 if(m_offscreenGWorld
) DisposeGWorld(m_offscreenGWorld
), m_offscreenGWorld
= NULL
;
542 void CQuicktimeWindow::Run()
546 StartMovie(theMovie
);
547 if(!m_idEndPoller
) m_idEndPoller
= SetTimer(1, 10, NULL
); // 10ms -> 100fps max
550 m_fs
= State_Running
;
553 void CQuicktimeWindow::Pause()
558 if(m_idEndPoller
) KillTimer(m_idEndPoller
), m_idEndPoller
= 0;
564 void CQuicktimeWindow::Stop()
569 GoToBeginningOfMovie(theMovie
);
570 if(m_idEndPoller
) KillTimer(m_idEndPoller
), m_idEndPoller
= 0;
573 m_fs
= State_Stopped
;
576 FILTER_STATE
CQuicktimeWindow::GetState()
581 BEGIN_MESSAGE_MAP(CQuicktimeWindow
, CPlayerWindow
)
588 int CQuicktimeWindow::OnCreate(LPCREATESTRUCT lpCreateStruct
)
590 if(__super::OnCreate(lpCreateStruct
) == -1)
593 CComQIPtr
<IQTVideoSurface
> pQTVS
= (IUnknown
*)(INonDelegatingUnknown
*)m_pGraph
;
597 // Create GrafPort <-> HWND association
598 CreatePortAssociation(m_hWnd
, NULL
, 0);
604 void CQuicktimeWindow::OnDestroy()
606 CPlayerWindow::OnDestroy();
608 // close any movies before destroying PortAssocation
611 CComQIPtr
<IQTVideoSurface
> pQTVS
= (IUnknown
*)(INonDelegatingUnknown
*)m_pGraph
;
615 // Destroy the view's GrafPort <-> HWND association
617 if(CGrafPtr windowPort
= (CGrafPtr
)GetHWNDPort(m_hWnd
))
618 DestroyPortAssociation(windowPort
);
622 BOOL
CQuicktimeWindow::OnEraseBkgnd(CDC
* pDC
)
624 return m_fs
!= State_Stopped
&& theMovie
? TRUE
: __super::OnEraseBkgnd(pDC
);
627 void CQuicktimeWindow::OnTimer(UINT nIDEvent
)
629 if(nIDEvent
== m_idEndPoller
&& theMovie
)
631 if(IsMovieDone(theMovie
))
634 m_pGraph
->NotifyEvent(EC_COMPLETE
);
636 else if(CComQIPtr
<IQTVideoSurface
> pQTVS
= (IUnknown
*)(INonDelegatingUnknown
*)m_pGraph
)
638 MoviesTask(theMovie
, 0);
640 long duration = 0, scale = 1000;
641 OSErr err = QTGetTimeUntilNextTask(&duration, scale);
643 // err is 0 but still doesn't seem to work... returns duration=0 always
644 TRACE(_T("%d\n"), duration);
645 KillTimer(m_idEndPoller);
646 m_idEndPoller = SetTimer(m_idEndPoller, duration, NULL);
651 __super::OnTimer(nIDEvent
);