/* * Copyright (C) 2007, 2008, 2009, 2010 Apple, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "QTMovieGWorld.h" #include "QTMovieTask.h" #include #include #include #include #include #include #include #include using namespace std; static const long minimumQuickTimeVersion = 0x07300000; // 7.3 static LPCWSTR fullscreenQTMovieGWorldPointerProp = L"fullscreenQTMovieGWorldPointer"; // Resizing GWorlds is slow, give them a minimum size so size of small // videos can be animated smoothly static const int cGWorldMinWidth = 640; static const int cGWorldMinHeight = 360; static const float cNonContinuousTimeChange = 0.2f; union UppParam { long longValue; void* ptr; }; static MovieDrawingCompleteUPP gMovieDrawingCompleteUPP = 0; static HashSet* gTaskList; static Vector* gSupportedTypes = 0; static SInt32 quickTimeVersion = 0; class QTMovieGWorldPrivate : public QTMovieClient { public: QTMovieGWorldPrivate(QTMovieGWorld* movieWin); virtual ~QTMovieGWorldPrivate(); void registerDrawingCallback(); void unregisterDrawingCallback(); void drawingComplete(); void updateGWorld(); void createGWorld(); void deleteGWorld(); void clearGWorld(); void updateMovieSize(); void setSize(int, int); virtual void movieEnded(QTMovie*); virtual void movieLoadStateChanged(QTMovie*); virtual void movieTimeChanged(QTMovie*); QTMovieGWorld* m_movieWin; RefPtr m_qtMovie; Movie m_movie; QTMovieGWorldClient* m_client; long m_loadState; int m_width; int m_height; bool m_visible; GWorldPtr m_gWorld; int m_gWorldWidth; int m_gWorldHeight; GWorldPtr m_savedGWorld; float m_widthScaleFactor; float m_heightScaleFactor; #if !ASSERT_DISABLED bool m_scaleCached; #endif WindowPtr m_fullscreenWindow; GWorldPtr m_fullscreenOrigGWorld; Rect m_fullscreenRect; QTMovieGWorldFullscreenClient* m_fullscreenClient; char* m_fullscreenRestoreState; bool m_disabled; }; QTMovieGWorldPrivate::QTMovieGWorldPrivate(QTMovieGWorld* movieWin) : m_movieWin(movieWin) , m_movie(0) , m_client(0) , m_loadState(0) , m_width(0) , m_height(0) , m_visible(false) , m_gWorld(0) , m_gWorldWidth(0) , m_gWorldHeight(0) , m_savedGWorld(0) , m_widthScaleFactor(1) , m_heightScaleFactor(1) #if !ASSERT_DISABLED , m_scaleCached(false) #endif , m_fullscreenWindow(0) , m_fullscreenOrigGWorld(0) , m_fullscreenClient(0) , m_fullscreenRestoreState(0) , m_disabled(false) , m_qtMovie(0) { Rect rect = { 0, 0, 0, 0 }; m_fullscreenRect = rect; } QTMovieGWorldPrivate::~QTMovieGWorldPrivate() { ASSERT(!m_fullscreenWindow); if (m_gWorld) deleteGWorld(); } pascal OSErr movieDrawingCompleteProc(Movie movie, long data) { UppParam param; param.longValue = data; QTMovieGWorldPrivate* mp = static_cast(param.ptr); if (mp) mp->drawingComplete(); return 0; } void QTMovieGWorldPrivate::registerDrawingCallback() { if (!gMovieDrawingCompleteUPP) gMovieDrawingCompleteUPP = NewMovieDrawingCompleteUPP(movieDrawingCompleteProc); UppParam param; param.ptr = this; SetMovieDrawingCompleteProc(m_movie, movieDrawingCallWhenChanged, gMovieDrawingCompleteUPP, param.longValue); } void QTMovieGWorldPrivate::unregisterDrawingCallback() { SetMovieDrawingCompleteProc(m_movie, movieDrawingCallWhenChanged, 0, 0); } void QTMovieGWorldPrivate::drawingComplete() { if (!m_gWorld || m_movieWin->m_private->m_disabled || m_loadState < QTMovieLoadStateLoaded) return; m_client->movieNewImageAvailable(m_movieWin); } void QTMovieGWorldPrivate::updateGWorld() { bool shouldBeVisible = m_visible; if (!m_height || !m_width) shouldBeVisible = false; if (shouldBeVisible && !m_gWorld) createGWorld(); else if (!shouldBeVisible && m_gWorld) deleteGWorld(); else if (m_gWorld && (m_width > m_gWorldWidth || m_height > m_gWorldHeight)) { // need a bigger, better gWorld deleteGWorld(); createGWorld(); } } void QTMovieGWorldPrivate::createGWorld() { ASSERT(!m_gWorld); if (!m_movie || m_loadState < QTMovieLoadStateLoaded) return; m_gWorldWidth = max(cGWorldMinWidth, m_width); m_gWorldHeight = max(cGWorldMinHeight, m_height); Rect bounds; bounds.top = 0; bounds.left = 0; bounds.right = m_gWorldWidth; bounds.bottom = m_gWorldHeight; OSErr err = QTNewGWorld(&m_gWorld, k32BGRAPixelFormat, &bounds, 0, 0, 0); if (err) return; GetMovieGWorld(m_movie, &m_savedGWorld, 0); SetMovieGWorld(m_movie, m_gWorld, 0); bounds.right = m_width; bounds.bottom = m_height; SetMovieBox(m_movie, &bounds); } void QTMovieGWorldPrivate::clearGWorld() { if (!m_movie || !m_gWorld) return; GrafPtr savePort; GetPort(&savePort); MacSetPort((GrafPtr)m_gWorld); Rect bounds; bounds.top = 0; bounds.left = 0; bounds.right = m_gWorldWidth; bounds.bottom = m_gWorldHeight; EraseRect(&bounds); MacSetPort(savePort); } void QTMovieGWorldPrivate::setSize(int width, int height) { if (m_width == width && m_height == height) return; m_width = width; m_height = height; // Do not change movie box before reaching load state loaded as we grab // the initial size when task() sees that state for the first time, and // we need the initial size to be able to scale movie properly. if (!m_movie || m_loadState < QTMovieLoadStateLoaded) return; #if !ASSERT_DISABLED ASSERT(m_scaleCached); #endif updateMovieSize(); } void QTMovieGWorldPrivate::updateMovieSize() { if (!m_movie || m_loadState < QTMovieLoadStateLoaded) return; Rect bounds; bounds.top = 0; bounds.left = 0; bounds.right = m_width; bounds.bottom = m_height; SetMovieBox(m_movie, &bounds); updateGWorld(); } void QTMovieGWorldPrivate::deleteGWorld() { ASSERT(m_gWorld); if (m_movie) SetMovieGWorld(m_movie, m_savedGWorld, 0); m_savedGWorld = 0; DisposeGWorld(m_gWorld); m_gWorld = 0; m_gWorldWidth = 0; m_gWorldHeight = 0; } void QTMovieGWorldPrivate::movieEnded(QTMovie*) { // Do nothing } void QTMovieGWorldPrivate::movieLoadStateChanged(QTMovie* movie) { long loadState = GetMovieLoadState(movie->getMovieHandle()); if (loadState != m_loadState) { // we only need to erase the movie gworld when the load state changes to loaded while it // is visible as the gworld is destroyed/created when visibility changes bool movieNewlyPlayable = loadState >= QTMovieLoadStateLoaded && m_loadState < QTMovieLoadStateLoaded; m_loadState = loadState; if (movieNewlyPlayable) { updateMovieSize(); if (m_visible) clearGWorld(); } } } void QTMovieGWorldPrivate::movieTimeChanged(QTMovie*) { // Do nothing } QTMovieGWorld::QTMovieGWorld(QTMovieGWorldClient* client) : m_private(new QTMovieGWorldPrivate(this)) { m_private->m_client = client; } QTMovieGWorld::~QTMovieGWorld() { delete m_private; } void QTMovieGWorld::setSize(int width, int height) { m_private->setSize(width, height); QTMovieTask::sharedTask()->updateTaskTimer(); } void QTMovieGWorld::setVisible(bool b) { m_private->m_visible = b; m_private->updateGWorld(); } void QTMovieGWorld::getCurrentFrameInfo(void*& buffer, unsigned& bitsPerPixel, unsigned& rowBytes, unsigned& width, unsigned& height) { if (!m_private->m_gWorld) { buffer = 0; bitsPerPixel = 0; rowBytes = 0; width = 0; height = 0; return; } PixMapHandle offscreenPixMap = GetGWorldPixMap(m_private->m_gWorld); buffer = (*offscreenPixMap)->baseAddr; bitsPerPixel = (*offscreenPixMap)->pixelSize; rowBytes = (*offscreenPixMap)->rowBytes & 0x3FFF; width = m_private->m_width; height = m_private->m_height; } void QTMovieGWorld::paint(HDC hdc, int x, int y) { if (!m_private->m_gWorld) return; HDC hdcSrc = static_cast(GetPortHDC(reinterpret_cast(m_private->m_gWorld))); if (!hdcSrc) return; // FIXME: If we could determine the movie has no alpha, we could use BitBlt for those cases, which might be faster. BLENDFUNCTION blendFunction; blendFunction.BlendOp = AC_SRC_OVER; blendFunction.BlendFlags = 0; blendFunction.SourceConstantAlpha = 255; blendFunction.AlphaFormat = AC_SRC_ALPHA; AlphaBlend(hdc, x, y, m_private->m_width, m_private->m_height, hdcSrc, 0, 0, m_private->m_width, m_private->m_height, blendFunction); } void QTMovieGWorld::setDisabled(bool b) { m_private->m_disabled = b; } bool QTMovieGWorld::isDisabled() const { return m_private->m_disabled; } LRESULT QTMovieGWorld::fullscreenWndProc(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam) { QTMovieGWorld* movie = static_cast(GetPropW(wnd, fullscreenQTMovieGWorldPointerProp)); if (message == WM_DESTROY) RemovePropW(wnd, fullscreenQTMovieGWorldPointerProp); if (!movie) return DefWindowProc(wnd, message, wParam, lParam); return movie->m_private->m_fullscreenClient->fullscreenClientWndProc(wnd, message, wParam, lParam); } HWND QTMovieGWorld::enterFullscreen(QTMovieGWorldFullscreenClient* client) { m_private->m_fullscreenClient = client; BeginFullScreen(&m_private->m_fullscreenRestoreState, 0, 0, 0, &m_private->m_fullscreenWindow, 0, fullScreenAllowEvents); QTMLSetWindowWndProc(m_private->m_fullscreenWindow, fullscreenWndProc); CreatePortAssociation(GetPortNativeWindow(m_private->m_fullscreenWindow), 0, 0); GetMovieBox(m_private->m_movie, &m_private->m_fullscreenRect); GetMovieGWorld(m_private->m_movie, &m_private->m_fullscreenOrigGWorld, 0); SetMovieGWorld(m_private->m_movie, reinterpret_cast(m_private->m_fullscreenWindow), GetGWorldDevice(reinterpret_cast(m_private->m_fullscreenWindow))); // Set the size of the box to preserve aspect ratio Rect rect = m_private->m_fullscreenWindow->portRect; float movieRatio = static_cast(m_private->m_width) / m_private->m_height; int windowWidth = rect.right - rect.left; int windowHeight = rect.bottom - rect.top; float windowRatio = static_cast(windowWidth) / windowHeight; int actualWidth = (windowRatio > movieRatio) ? (windowHeight * movieRatio) : windowWidth; int actualHeight = (windowRatio < movieRatio) ? (windowWidth / movieRatio) : windowHeight; int offsetX = (windowWidth - actualWidth) / 2; int offsetY = (windowHeight - actualHeight) / 2; rect.left = offsetX; rect.right = offsetX + actualWidth; rect.top = offsetY; rect.bottom = offsetY + actualHeight; SetMovieBox(m_private->m_movie, &rect); ShowHideTaskBar(true); // Set the 'this' pointer on the HWND HWND wnd = static_cast(GetPortNativeWindow(m_private->m_fullscreenWindow)); SetPropW(wnd, fullscreenQTMovieGWorldPointerProp, static_cast(this)); return wnd; } void QTMovieGWorld::exitFullscreen() { if (!m_private->m_fullscreenWindow) return; HWND wnd = static_cast(GetPortNativeWindow(m_private->m_fullscreenWindow)); DestroyPortAssociation(reinterpret_cast(m_private->m_fullscreenWindow)); SetMovieGWorld(m_private->m_movie, m_private->m_fullscreenOrigGWorld, 0); EndFullScreen(m_private->m_fullscreenRestoreState, 0L); SetMovieBox(m_private->m_movie, &m_private->m_fullscreenRect); m_private->m_fullscreenWindow = 0; } void QTMovieGWorld::setMovie(PassRefPtr movie) { if (m_private->m_qtMovie) { m_private->unregisterDrawingCallback(); m_private->m_qtMovie->removeClient(m_private); m_private->m_qtMovie = 0; m_private->m_movie = 0; } m_private->m_qtMovie = movie; if (m_private->m_qtMovie) { m_private->m_qtMovie->addClient(m_private); m_private->m_movie = m_private->m_qtMovie->getMovieHandle(); m_private->registerDrawingCallback(); } } QTMovie* QTMovieGWorld::movie() const { return m_private->m_qtMovie.get(); }