Home | History | Annotate | Download | only in win
      1 /*
      2  * Copyright (C) 2007, 2008, 2009, 2010 Apple, Inc.  All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  * 1. Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2. Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
     17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 #include "config.h"
     26 
     27 #include "QTMovieGWorld.h"
     28 
     29 #include "QTMovieTask.h"
     30 #include <GXMath.h>
     31 #include <Movies.h>
     32 #include <QTML.h>
     33 #include <QuickTimeComponents.h>
     34 #include <wtf/Assertions.h>
     35 #include <wtf/HashSet.h>
     36 #include <wtf/Noncopyable.h>
     37 #include <wtf/Vector.h>
     38 
     39 using namespace std;
     40 
     41 static const long minimumQuickTimeVersion = 0x07300000; // 7.3
     42 
     43 static LPCWSTR fullscreenQTMovieGWorldPointerProp = L"fullscreenQTMovieGWorldPointer";
     44 
     45 // Resizing GWorlds is slow, give them a minimum size so size of small
     46 // videos can be animated smoothly
     47 static const int cGWorldMinWidth = 640;
     48 static const int cGWorldMinHeight = 360;
     49 
     50 static const float cNonContinuousTimeChange = 0.2f;
     51 
     52 union UppParam {
     53     long longValue;
     54     void* ptr;
     55 };
     56 
     57 static MovieDrawingCompleteUPP gMovieDrawingCompleteUPP = 0;
     58 static HashSet<QTMovieGWorldPrivate*>* gTaskList;
     59 static Vector<CFStringRef>* gSupportedTypes = 0;
     60 static SInt32 quickTimeVersion = 0;
     61 
     62 class QTMovieGWorldPrivate : public QTMovieClient {
     63 public:
     64     QTMovieGWorldPrivate(QTMovieGWorld* movieWin);
     65     virtual ~QTMovieGWorldPrivate();
     66 
     67     void registerDrawingCallback();
     68     void unregisterDrawingCallback();
     69     void drawingComplete();
     70     void updateGWorld();
     71     void createGWorld();
     72     void deleteGWorld();
     73     void clearGWorld();
     74     void updateMovieSize();
     75 
     76     void setSize(int, int);
     77 
     78     virtual void movieEnded(QTMovie*);
     79     virtual void movieLoadStateChanged(QTMovie*);
     80     virtual void movieTimeChanged(QTMovie*);
     81 
     82     QTMovieGWorld* m_movieWin;
     83     RefPtr<QTMovie> m_qtMovie;
     84     Movie m_movie;
     85     QTMovieGWorldClient* m_client;
     86     long m_loadState;
     87     int m_width;
     88     int m_height;
     89     bool m_visible;
     90     GWorldPtr m_gWorld;
     91     int m_gWorldWidth;
     92     int m_gWorldHeight;
     93     GWorldPtr m_savedGWorld;
     94     float m_widthScaleFactor;
     95     float m_heightScaleFactor;
     96 #if !ASSERT_DISABLED
     97     bool m_scaleCached;
     98 #endif
     99     WindowPtr m_fullscreenWindow;
    100     GWorldPtr m_fullscreenOrigGWorld;
    101     Rect m_fullscreenRect;
    102     QTMovieGWorldFullscreenClient* m_fullscreenClient;
    103     char* m_fullscreenRestoreState;
    104     bool m_disabled;
    105 };
    106 
    107 QTMovieGWorldPrivate::QTMovieGWorldPrivate(QTMovieGWorld* movieWin)
    108     : m_movieWin(movieWin)
    109     , m_movie(0)
    110     , m_client(0)
    111     , m_loadState(0)
    112     , m_width(0)
    113     , m_height(0)
    114     , m_visible(false)
    115     , m_gWorld(0)
    116     , m_gWorldWidth(0)
    117     , m_gWorldHeight(0)
    118     , m_savedGWorld(0)
    119     , m_widthScaleFactor(1)
    120     , m_heightScaleFactor(1)
    121 #if !ASSERT_DISABLED
    122     , m_scaleCached(false)
    123 #endif
    124     , m_fullscreenWindow(0)
    125     , m_fullscreenOrigGWorld(0)
    126     , m_fullscreenClient(0)
    127     , m_fullscreenRestoreState(0)
    128     , m_disabled(false)
    129     , m_qtMovie(0)
    130 {
    131     Rect rect = { 0, 0, 0, 0 };
    132     m_fullscreenRect = rect;
    133 }
    134 
    135 QTMovieGWorldPrivate::~QTMovieGWorldPrivate()
    136 {
    137     ASSERT(!m_fullscreenWindow);
    138 
    139     if (m_gWorld)
    140         deleteGWorld();
    141 }
    142 
    143 pascal OSErr movieDrawingCompleteProc(Movie movie, long data)
    144 {
    145     UppParam param;
    146     param.longValue = data;
    147     QTMovieGWorldPrivate* mp = static_cast<QTMovieGWorldPrivate*>(param.ptr);
    148     if (mp)
    149         mp->drawingComplete();
    150     return 0;
    151 }
    152 
    153 void QTMovieGWorldPrivate::registerDrawingCallback()
    154 {
    155     if (!gMovieDrawingCompleteUPP)
    156         gMovieDrawingCompleteUPP = NewMovieDrawingCompleteUPP(movieDrawingCompleteProc);
    157 
    158     UppParam param;
    159     param.ptr = this;
    160     SetMovieDrawingCompleteProc(m_movie, movieDrawingCallWhenChanged, gMovieDrawingCompleteUPP, param.longValue);
    161 }
    162 
    163 void QTMovieGWorldPrivate::unregisterDrawingCallback()
    164 {
    165     SetMovieDrawingCompleteProc(m_movie, movieDrawingCallWhenChanged, 0, 0);
    166 }
    167 
    168 void QTMovieGWorldPrivate::drawingComplete()
    169 {
    170     if (!m_gWorld || m_movieWin->m_private->m_disabled || m_loadState < QTMovieLoadStateLoaded)
    171         return;
    172     m_client->movieNewImageAvailable(m_movieWin);
    173 }
    174 
    175 void QTMovieGWorldPrivate::updateGWorld()
    176 {
    177     bool shouldBeVisible = m_visible;
    178     if (!m_height || !m_width)
    179         shouldBeVisible = false;
    180 
    181     if (shouldBeVisible && !m_gWorld)
    182         createGWorld();
    183     else if (!shouldBeVisible && m_gWorld)
    184         deleteGWorld();
    185     else if (m_gWorld && (m_width > m_gWorldWidth || m_height > m_gWorldHeight)) {
    186         // need a bigger, better gWorld
    187         deleteGWorld();
    188         createGWorld();
    189     }
    190 }
    191 
    192 void QTMovieGWorldPrivate::createGWorld()
    193 {
    194     ASSERT(!m_gWorld);
    195     if (!m_movie || m_loadState < QTMovieLoadStateLoaded)
    196         return;
    197 
    198     m_gWorldWidth = max(cGWorldMinWidth, m_width);
    199     m_gWorldHeight = max(cGWorldMinHeight, m_height);
    200     Rect bounds;
    201     bounds.top = 0;
    202     bounds.left = 0;
    203     bounds.right = m_gWorldWidth;
    204     bounds.bottom = m_gWorldHeight;
    205     OSErr err = QTNewGWorld(&m_gWorld, k32BGRAPixelFormat, &bounds, 0, 0, 0);
    206     if (err)
    207         return;
    208     GetMovieGWorld(m_movie, &m_savedGWorld, 0);
    209     SetMovieGWorld(m_movie, m_gWorld, 0);
    210     bounds.right = m_width;
    211     bounds.bottom = m_height;
    212     SetMovieBox(m_movie, &bounds);
    213 }
    214 
    215 void QTMovieGWorldPrivate::clearGWorld()
    216 {
    217     if (!m_movie || !m_gWorld)
    218         return;
    219 
    220     GrafPtr savePort;
    221     GetPort(&savePort);
    222     MacSetPort((GrafPtr)m_gWorld);
    223 
    224     Rect bounds;
    225     bounds.top = 0;
    226     bounds.left = 0;
    227     bounds.right = m_gWorldWidth;
    228     bounds.bottom = m_gWorldHeight;
    229     EraseRect(&bounds);
    230 
    231     MacSetPort(savePort);
    232 }
    233 
    234 void QTMovieGWorldPrivate::setSize(int width, int height)
    235 {
    236     if (m_width == width && m_height == height)
    237         return;
    238     m_width = width;
    239     m_height = height;
    240 
    241     // Do not change movie box before reaching load state loaded as we grab
    242     // the initial size when task() sees that state for the first time, and
    243     // we need the initial size to be able to scale movie properly.
    244     if (!m_movie || m_loadState < QTMovieLoadStateLoaded)
    245         return;
    246 
    247 #if !ASSERT_DISABLED
    248     ASSERT(m_scaleCached);
    249 #endif
    250 
    251     updateMovieSize();
    252 }
    253 
    254 void QTMovieGWorldPrivate::updateMovieSize()
    255 {
    256     if (!m_movie || m_loadState < QTMovieLoadStateLoaded)
    257         return;
    258 
    259     Rect bounds;
    260     bounds.top = 0;
    261     bounds.left = 0;
    262     bounds.right = m_width;
    263     bounds.bottom = m_height;
    264     SetMovieBox(m_movie, &bounds);
    265     updateGWorld();
    266 }
    267 
    268 
    269 void QTMovieGWorldPrivate::deleteGWorld()
    270 {
    271     ASSERT(m_gWorld);
    272     if (m_movie)
    273         SetMovieGWorld(m_movie, m_savedGWorld, 0);
    274     m_savedGWorld = 0;
    275     DisposeGWorld(m_gWorld);
    276     m_gWorld = 0;
    277     m_gWorldWidth = 0;
    278     m_gWorldHeight = 0;
    279 }
    280 
    281 void QTMovieGWorldPrivate::movieEnded(QTMovie*)
    282 {
    283     // Do nothing
    284 }
    285 
    286 void QTMovieGWorldPrivate::movieLoadStateChanged(QTMovie* movie)
    287 {
    288     long loadState = GetMovieLoadState(movie->getMovieHandle());
    289     if (loadState != m_loadState) {
    290 
    291         // we only need to erase the movie gworld when the load state changes to loaded while it
    292         //  is visible as the gworld is destroyed/created when visibility changes
    293         bool movieNewlyPlayable = loadState >= QTMovieLoadStateLoaded && m_loadState < QTMovieLoadStateLoaded;
    294         m_loadState = loadState;
    295 
    296         if (movieNewlyPlayable) {
    297             updateMovieSize();
    298             if (m_visible)
    299                 clearGWorld();
    300         }
    301     }
    302 }
    303 
    304 void QTMovieGWorldPrivate::movieTimeChanged(QTMovie*)
    305 {
    306     // Do nothing
    307 }
    308 
    309 QTMovieGWorld::QTMovieGWorld(QTMovieGWorldClient* client)
    310     : m_private(new QTMovieGWorldPrivate(this))
    311 {
    312     m_private->m_client = client;
    313 }
    314 
    315 QTMovieGWorld::~QTMovieGWorld()
    316 {
    317     delete m_private;
    318 }
    319 
    320 void QTMovieGWorld::setSize(int width, int height)
    321 {
    322     m_private->setSize(width, height);
    323     QTMovieTask::sharedTask()->updateTaskTimer();
    324 }
    325 
    326 void QTMovieGWorld::setVisible(bool b)
    327 {
    328     m_private->m_visible = b;
    329     m_private->updateGWorld();
    330 }
    331 
    332 void QTMovieGWorld::getCurrentFrameInfo(void*& buffer, unsigned& bitsPerPixel, unsigned& rowBytes, unsigned& width, unsigned& height)
    333 {
    334     if (!m_private->m_gWorld) {
    335         buffer = 0;
    336         bitsPerPixel = 0;
    337         rowBytes = 0;
    338         width = 0;
    339         height = 0;
    340         return;
    341     }
    342     PixMapHandle offscreenPixMap = GetGWorldPixMap(m_private->m_gWorld);
    343     buffer = (*offscreenPixMap)->baseAddr;
    344     bitsPerPixel = (*offscreenPixMap)->pixelSize;
    345     rowBytes = (*offscreenPixMap)->rowBytes & 0x3FFF;
    346     width = m_private->m_width;
    347     height = m_private->m_height;
    348 }
    349 
    350 void QTMovieGWorld::paint(HDC hdc, int x, int y)
    351 {
    352     if (!m_private->m_gWorld)
    353         return;
    354 
    355     HDC hdcSrc = static_cast<HDC>(GetPortHDC(reinterpret_cast<GrafPtr>(m_private->m_gWorld)));
    356     if (!hdcSrc)
    357         return;
    358 
    359     // FIXME: If we could determine the movie has no alpha, we could use BitBlt for those cases, which might be faster.
    360     BLENDFUNCTION blendFunction;
    361     blendFunction.BlendOp = AC_SRC_OVER;
    362     blendFunction.BlendFlags = 0;
    363     blendFunction.SourceConstantAlpha = 255;
    364     blendFunction.AlphaFormat = AC_SRC_ALPHA;
    365     AlphaBlend(hdc, x, y, m_private->m_width, m_private->m_height, hdcSrc,
    366          0, 0, m_private->m_width, m_private->m_height, blendFunction);
    367 }
    368 
    369 void QTMovieGWorld::setDisabled(bool b)
    370 {
    371     m_private->m_disabled = b;
    372 }
    373 
    374 bool QTMovieGWorld::isDisabled() const
    375 {
    376     return m_private->m_disabled;
    377 }
    378 
    379 LRESULT QTMovieGWorld::fullscreenWndProc(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam)
    380 {
    381     QTMovieGWorld* movie = static_cast<QTMovieGWorld*>(GetPropW(wnd, fullscreenQTMovieGWorldPointerProp));
    382 
    383     if (message == WM_DESTROY)
    384         RemovePropW(wnd, fullscreenQTMovieGWorldPointerProp);
    385 
    386     if (!movie)
    387         return DefWindowProc(wnd, message, wParam, lParam);
    388 
    389     return movie->m_private->m_fullscreenClient->fullscreenClientWndProc(wnd, message, wParam, lParam);
    390 }
    391 
    392 HWND QTMovieGWorld::enterFullscreen(QTMovieGWorldFullscreenClient* client)
    393 {
    394     m_private->m_fullscreenClient = client;
    395 
    396     BeginFullScreen(&m_private->m_fullscreenRestoreState, 0, 0, 0, &m_private->m_fullscreenWindow, 0, fullScreenAllowEvents);
    397     QTMLSetWindowWndProc(m_private->m_fullscreenWindow, fullscreenWndProc);
    398     CreatePortAssociation(GetPortNativeWindow(m_private->m_fullscreenWindow), 0, 0);
    399 
    400     GetMovieBox(m_private->m_movie, &m_private->m_fullscreenRect);
    401     GetMovieGWorld(m_private->m_movie, &m_private->m_fullscreenOrigGWorld, 0);
    402     SetMovieGWorld(m_private->m_movie, reinterpret_cast<CGrafPtr>(m_private->m_fullscreenWindow), GetGWorldDevice(reinterpret_cast<CGrafPtr>(m_private->m_fullscreenWindow)));
    403 
    404     // Set the size of the box to preserve aspect ratio
    405     Rect rect = m_private->m_fullscreenWindow->portRect;
    406 
    407     float movieRatio = static_cast<float>(m_private->m_width) / m_private->m_height;
    408     int windowWidth =  rect.right - rect.left;
    409     int windowHeight = rect.bottom - rect.top;
    410     float windowRatio = static_cast<float>(windowWidth) / windowHeight;
    411     int actualWidth = (windowRatio > movieRatio) ? (windowHeight * movieRatio) : windowWidth;
    412     int actualHeight = (windowRatio < movieRatio) ? (windowWidth / movieRatio) : windowHeight;
    413     int offsetX = (windowWidth - actualWidth) / 2;
    414     int offsetY = (windowHeight - actualHeight) / 2;
    415 
    416     rect.left = offsetX;
    417     rect.right = offsetX + actualWidth;
    418     rect.top = offsetY;
    419     rect.bottom = offsetY + actualHeight;
    420 
    421     SetMovieBox(m_private->m_movie, &rect);
    422     ShowHideTaskBar(true);
    423 
    424     // Set the 'this' pointer on the HWND
    425     HWND wnd = static_cast<HWND>(GetPortNativeWindow(m_private->m_fullscreenWindow));
    426     SetPropW(wnd, fullscreenQTMovieGWorldPointerProp, static_cast<HANDLE>(this));
    427 
    428     return wnd;
    429 }
    430 
    431 void QTMovieGWorld::exitFullscreen()
    432 {
    433     if (!m_private->m_fullscreenWindow)
    434         return;
    435 
    436     HWND wnd = static_cast<HWND>(GetPortNativeWindow(m_private->m_fullscreenWindow));
    437     DestroyPortAssociation(reinterpret_cast<CGrafPtr>(m_private->m_fullscreenWindow));
    438     SetMovieGWorld(m_private->m_movie, m_private->m_fullscreenOrigGWorld, 0);
    439     EndFullScreen(m_private->m_fullscreenRestoreState, 0L);
    440     SetMovieBox(m_private->m_movie, &m_private->m_fullscreenRect);
    441     m_private->m_fullscreenWindow = 0;
    442 }
    443 
    444 void QTMovieGWorld::setMovie(PassRefPtr<QTMovie> movie)
    445 {
    446     if (m_private->m_qtMovie) {
    447         m_private->unregisterDrawingCallback();
    448         m_private->m_qtMovie->removeClient(m_private);
    449         m_private->m_qtMovie = 0;
    450         m_private->m_movie = 0;
    451     }
    452 
    453     m_private->m_qtMovie = movie;
    454 
    455     if (m_private->m_qtMovie) {
    456         m_private->m_qtMovie->addClient(m_private);
    457         m_private->m_movie = m_private->m_qtMovie->getMovieHandle();
    458         m_private->registerDrawingCallback();
    459     }
    460 }
    461 
    462 QTMovie* QTMovieGWorld::movie() const
    463 {
    464     return m_private->m_qtMovie.get();
    465 }
    466