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 "QTMovieWin.h"
     28 
     29 // Put Movies.h first so build failures here point clearly to QuickTime
     30 #include <Movies.h>
     31 
     32 #include "QTMovieWinTimer.h"
     33 #include <GXMath.h>
     34 #include <QTML.h>
     35 #include <QuickTimeComponents.h>
     36 #include <wtf/Assertions.h>
     37 #include <wtf/HashSet.h>
     38 #include <wtf/Noncopyable.h>
     39 #include <wtf/Vector.h>
     40 
     41 using namespace std;
     42 
     43 static const long minimumQuickTimeVersion = 0x07300000; // 7.3
     44 
     45 static const long closedCaptionTrackType = 'clcp';
     46 static const long subTitleTrackType = 'sbtl';
     47 static const long mpeg4ObjectDescriptionTrackType = 'odsm';
     48 static const long mpeg4SceneDescriptionTrackType = 'sdsm';
     49 static const long closedCaptionDisplayPropertyID = 'disp';
     50 static LPCTSTR fullscreenQTMovieWinPointerProp = TEXT("fullscreenQTMovieWinPointer");
     51 
     52 // Resizing GWorlds is slow, give them a minimum size so size of small
     53 // videos can be animated smoothly
     54 static const int cGWorldMinWidth = 640;
     55 static const int cGWorldMinHeight = 360;
     56 
     57 static const float cNonContinuousTimeChange = 0.2f;
     58 
     59 union UppParam {
     60     long longValue;
     61     void* ptr;
     62 };
     63 
     64 static MovieDrawingCompleteUPP gMovieDrawingCompleteUPP = 0;
     65 static HashSet<QTMovieWinPrivate*>* gTaskList;
     66 static Vector<CFStringRef>* gSupportedTypes = 0;
     67 static SInt32 quickTimeVersion = 0;
     68 
     69 static QTMovieWin::SetTaskTimerDelayFunc gSetTaskTimerDelay = 0;
     70 static QTMovieWin::StopTaskTimerFunc gStopTaskTimer = 0;
     71 
     72 static void updateTaskTimer(int maxInterval = 1000)
     73 {
     74     if (!gTaskList->size()) {
     75         gStopTaskTimer();
     76         return;
     77     }
     78 
     79     long intervalInMS;
     80     QTGetTimeUntilNextTask(&intervalInMS, 1000);
     81     if (intervalInMS > maxInterval)
     82         intervalInMS = maxInterval;
     83     gSetTaskTimerDelay(static_cast<float>(intervalInMS) / 1000);
     84 }
     85 
     86 class QTMovieWinPrivate : public Noncopyable {
     87 public:
     88     QTMovieWinPrivate();
     89     ~QTMovieWinPrivate();
     90     void task();
     91     void startTask();
     92     void endTask();
     93 
     94     void createMovieController();
     95     void registerDrawingCallback();
     96     void drawingComplete();
     97     void updateGWorld();
     98     void createGWorld();
     99     void deleteGWorld();
    100     void clearGWorld();
    101     void cacheMovieScale();
    102     void updateMovieSize();
    103 
    104     void setSize(int, int);
    105 
    106     QTMovieWin* m_movieWin;
    107     Movie m_movie;
    108     MovieController m_movieController;
    109     bool m_tasking;
    110     QTMovieWinClient* m_client;
    111     long m_loadState;
    112     bool m_ended;
    113     bool m_seeking;
    114     float m_lastMediaTime;
    115     double m_lastLoadStateCheckTime;
    116     int m_width;
    117     int m_height;
    118     bool m_visible;
    119     GWorldPtr m_gWorld;
    120     int m_gWorldWidth;
    121     int m_gWorldHeight;
    122     GWorldPtr m_savedGWorld;
    123     long m_loadError;
    124     float m_widthScaleFactor;
    125     float m_heightScaleFactor;
    126     CFURLRef m_currentURL;
    127     float m_timeToRestore;
    128     float m_rateToRestore;
    129 #if !ASSERT_DISABLED
    130     bool m_scaleCached;
    131 #endif
    132     WindowPtr m_fullscreenWindow;
    133     GWorldPtr m_fullscreenOrigGWorld;
    134     Rect m_fullscreenRect;
    135     QTMovieWinFullscreenClient* m_fullscreenClient;
    136     char* m_fullscreenRestoreState;
    137 };
    138 
    139 QTMovieWinPrivate::QTMovieWinPrivate()
    140     : m_movieWin(0)
    141     , m_movie(0)
    142     , m_movieController(0)
    143     , m_tasking(false)
    144     , m_client(0)
    145     , m_loadState(0)
    146     , m_ended(false)
    147     , m_seeking(false)
    148     , m_lastMediaTime(0)
    149     , m_lastLoadStateCheckTime(0)
    150     , m_width(0)
    151     , m_height(0)
    152     , m_visible(false)
    153     , m_gWorld(0)
    154     , m_gWorldWidth(0)
    155     , m_gWorldHeight(0)
    156     , m_savedGWorld(0)
    157     , m_loadError(0)
    158     , m_widthScaleFactor(1)
    159     , m_heightScaleFactor(1)
    160     , m_currentURL(0)
    161     , m_timeToRestore(-1.0f)
    162     , m_rateToRestore(-1.0f)
    163 #if !ASSERT_DISABLED
    164     , m_scaleCached(false)
    165 #endif
    166     , m_fullscreenWindow(0)
    167     , m_fullscreenOrigGWorld(0)
    168     , m_fullscreenClient(0)
    169     , m_fullscreenRestoreState(0)
    170 {
    171     Rect rect = { 0, 0, 0, 0 };
    172     m_fullscreenRect = rect;
    173 }
    174 
    175 QTMovieWinPrivate::~QTMovieWinPrivate()
    176 {
    177     ASSERT(!m_fullscreenWindow);
    178 
    179     endTask();
    180     if (m_gWorld)
    181         deleteGWorld();
    182     if (m_movieController)
    183         DisposeMovieController(m_movieController);
    184     if (m_movie)
    185         DisposeMovie(m_movie);
    186     if (m_currentURL)
    187         CFRelease(m_currentURL);
    188 }
    189 
    190 void QTMovieWin::taskTimerFired()
    191 {
    192     // The hash content might change during task()
    193     Vector<QTMovieWinPrivate*> tasks;
    194     copyToVector(*gTaskList, tasks);
    195     size_t count = tasks.size();
    196     for (unsigned n = 0; n < count; ++n)
    197         tasks[n]->task();
    198 
    199     updateTaskTimer();
    200 }
    201 
    202 void QTMovieWinPrivate::startTask()
    203 {
    204     if (m_tasking)
    205         return;
    206     if (!gTaskList)
    207         gTaskList = new HashSet<QTMovieWinPrivate*>;
    208     gTaskList->add(this);
    209     m_tasking = true;
    210     updateTaskTimer();
    211 }
    212 
    213 void QTMovieWinPrivate::endTask()
    214 {
    215     if (!m_tasking)
    216         return;
    217     gTaskList->remove(this);
    218     m_tasking = false;
    219     updateTaskTimer();
    220 }
    221 
    222 void QTMovieWinPrivate::cacheMovieScale()
    223 {
    224     Rect naturalRect;
    225     Rect initialRect;
    226 
    227     GetMovieNaturalBoundsRect(m_movie, &naturalRect);
    228     GetMovieBox(m_movie, &initialRect);
    229 
    230     float naturalWidth = naturalRect.right - naturalRect.left;
    231     float naturalHeight = naturalRect.bottom - naturalRect.top;
    232 
    233     if (naturalWidth)
    234         m_widthScaleFactor = (initialRect.right - initialRect.left) / naturalWidth;
    235     if (naturalHeight)
    236         m_heightScaleFactor = (initialRect.bottom - initialRect.top) / naturalHeight;
    237 #if !ASSERT_DISABLED
    238     m_scaleCached = true;;
    239 #endif
    240 }
    241 
    242 void QTMovieWinPrivate::task()
    243 {
    244     ASSERT(m_tasking);
    245 
    246     if (!m_loadError) {
    247         if (m_movieController)
    248             MCIdle(m_movieController);
    249         else
    250             MoviesTask(m_movie, 0);
    251     }
    252 
    253     // GetMovieLoadState documentation says that you should not call it more often than every quarter of a second.
    254     if (systemTime() >= m_lastLoadStateCheckTime + 0.25 || m_loadError) {
    255         // If load fails QT's load state is QTMovieLoadStateComplete.
    256         // This is different from QTKit API and seems strange.
    257         long loadState = m_loadError ? QTMovieLoadStateError : GetMovieLoadState(m_movie);
    258         if (loadState != m_loadState) {
    259 
    260             // we only need to erase the movie gworld when the load state changes to loaded while it
    261             //  is visible as the gworld is destroyed/created when visibility changes
    262             bool shouldRestorePlaybackState = false;
    263             bool movieNewlyPlayable = loadState >= QTMovieLoadStateLoaded && m_loadState < QTMovieLoadStateLoaded;
    264             m_loadState = loadState;
    265             if (movieNewlyPlayable) {
    266                 cacheMovieScale();
    267                 updateMovieSize();
    268                 if (m_visible)
    269                     clearGWorld();
    270                 shouldRestorePlaybackState = true;
    271             }
    272 
    273             if (!m_movieController && m_loadState >= QTMovieLoadStateLoaded)
    274                 createMovieController();
    275             m_client->movieLoadStateChanged(m_movieWin);
    276 
    277             if (shouldRestorePlaybackState && m_timeToRestore != -1.0f) {
    278                 m_movieWin->setCurrentTime(m_timeToRestore);
    279                 m_timeToRestore = -1.0f;
    280                 m_movieWin->setRate(m_rateToRestore);
    281                 m_rateToRestore = -1.0f;
    282             }
    283 
    284             if (m_movieWin->m_disabled) {
    285                 endTask();
    286                 return;
    287             }
    288         }
    289         m_lastLoadStateCheckTime = systemTime();
    290     }
    291 
    292     bool ended = !!IsMovieDone(m_movie);
    293     if (ended != m_ended) {
    294         m_ended = ended;
    295         if (m_client && ended)
    296             m_client->movieEnded(m_movieWin);
    297     }
    298 
    299     float time = m_movieWin->currentTime();
    300     if (time < m_lastMediaTime || time >= m_lastMediaTime + cNonContinuousTimeChange || m_seeking) {
    301         m_seeking = false;
    302         if (m_client)
    303             m_client->movieTimeChanged(m_movieWin);
    304     }
    305     m_lastMediaTime = time;
    306 
    307     if (m_loadError)
    308         endTask();
    309 }
    310 
    311 void QTMovieWinPrivate::createMovieController()
    312 {
    313     Rect bounds;
    314     long flags;
    315 
    316     if (!m_movie)
    317         return;
    318 
    319     if (m_movieController)
    320         DisposeMovieController(m_movieController);
    321 
    322     GetMovieBox(m_movie, &bounds);
    323     flags = mcTopLeftMovie | mcNotVisible;
    324     m_movieController = NewMovieController(m_movie, &bounds, flags);
    325     if (!m_movieController)
    326         return;
    327 
    328     MCSetControllerPort(m_movieController, m_gWorld);
    329     MCSetControllerAttached(m_movieController, false);
    330 }
    331 
    332 void QTMovieWinPrivate::registerDrawingCallback()
    333 {
    334     UppParam param;
    335     param.ptr = this;
    336     SetMovieDrawingCompleteProc(m_movie, movieDrawingCallWhenChanged, gMovieDrawingCompleteUPP, param.longValue);
    337 }
    338 
    339 void QTMovieWinPrivate::drawingComplete()
    340 {
    341     if (!m_gWorld || m_movieWin->m_disabled || m_loadState < QTMovieLoadStateLoaded)
    342         return;
    343     m_client->movieNewImageAvailable(m_movieWin);
    344 }
    345 
    346 void QTMovieWinPrivate::updateGWorld()
    347 {
    348     bool shouldBeVisible = m_visible;
    349     if (!m_height || !m_width)
    350         shouldBeVisible = false;
    351 
    352     if (shouldBeVisible && !m_gWorld)
    353         createGWorld();
    354     else if (!shouldBeVisible && m_gWorld)
    355         deleteGWorld();
    356     else if (m_gWorld && (m_width > m_gWorldWidth || m_height > m_gWorldHeight)) {
    357         // need a bigger, better gWorld
    358         deleteGWorld();
    359         createGWorld();
    360     }
    361 }
    362 
    363 void QTMovieWinPrivate::createGWorld()
    364 {
    365     ASSERT(!m_gWorld);
    366     if (!m_movie || m_loadState < QTMovieLoadStateLoaded)
    367         return;
    368 
    369     m_gWorldWidth = max(cGWorldMinWidth, m_width);
    370     m_gWorldHeight = max(cGWorldMinHeight, m_height);
    371     Rect bounds;
    372     bounds.top = 0;
    373     bounds.left = 0;
    374     bounds.right = m_gWorldWidth;
    375     bounds.bottom = m_gWorldHeight;
    376     OSErr err = QTNewGWorld(&m_gWorld, k32BGRAPixelFormat, &bounds, 0, 0, 0);
    377     if (err)
    378         return;
    379     GetMovieGWorld(m_movie, &m_savedGWorld, 0);
    380     if (m_movieController)
    381         MCSetControllerPort(m_movieController, m_gWorld);
    382     SetMovieGWorld(m_movie, m_gWorld, 0);
    383     bounds.right = m_width;
    384     bounds.bottom = m_height;
    385     if (m_movieController)
    386         MCSetControllerBoundsRect(m_movieController, &bounds);
    387     SetMovieBox(m_movie, &bounds);
    388 }
    389 
    390 void QTMovieWinPrivate::clearGWorld()
    391 {
    392     if (!m_movie||!m_gWorld)
    393         return;
    394 
    395     GrafPtr savePort;
    396     GetPort(&savePort);
    397     MacSetPort((GrafPtr)m_gWorld);
    398 
    399     Rect bounds;
    400     bounds.top = 0;
    401     bounds.left = 0;
    402     bounds.right = m_gWorldWidth;
    403     bounds.bottom = m_gWorldHeight;
    404     EraseRect(&bounds);
    405 
    406     MacSetPort(savePort);
    407 }
    408 
    409 void QTMovieWinPrivate::setSize(int width, int height)
    410 {
    411     if (m_width == width && m_height == height)
    412         return;
    413     m_width = width;
    414     m_height = height;
    415 
    416     // Do not change movie box before reaching load state loaded as we grab
    417     // the initial size when task() sees that state for the first time, and
    418     // we need the initial size to be able to scale movie properly.
    419     if (!m_movie || m_loadState < QTMovieLoadStateLoaded)
    420         return;
    421 
    422 #if !ASSERT_DISABLED
    423     ASSERT(m_scaleCached);
    424 #endif
    425 
    426     updateMovieSize();
    427 }
    428 
    429 void QTMovieWinPrivate::updateMovieSize()
    430 {
    431     if (!m_movie || m_loadState < QTMovieLoadStateLoaded)
    432         return;
    433 
    434     Rect bounds;
    435     bounds.top = 0;
    436     bounds.left = 0;
    437     bounds.right = m_width;
    438     bounds.bottom = m_height;
    439     if (m_movieController)
    440         MCSetControllerBoundsRect(m_movieController, &bounds);
    441     SetMovieBox(m_movie, &bounds);
    442     updateGWorld();
    443 }
    444 
    445 
    446 void QTMovieWinPrivate::deleteGWorld()
    447 {
    448     ASSERT(m_gWorld);
    449     if (m_movieController)
    450         MCSetControllerPort(m_movieController, m_savedGWorld);
    451     if (m_movie)
    452         SetMovieGWorld(m_movie, m_savedGWorld, 0);
    453     m_savedGWorld = 0;
    454     DisposeGWorld(m_gWorld);
    455     m_gWorld = 0;
    456     m_gWorldWidth = 0;
    457     m_gWorldHeight = 0;
    458 }
    459 
    460 
    461 QTMovieWin::QTMovieWin(QTMovieWinClient* client)
    462     : m_private(new QTMovieWinPrivate())
    463     , m_disabled(false)
    464 {
    465     m_private->m_movieWin = this;
    466     m_private->m_client = client;
    467     initializeQuickTime();
    468 }
    469 
    470 QTMovieWin::~QTMovieWin()
    471 {
    472     delete m_private;
    473 }
    474 
    475 void QTMovieWin::play()
    476 {
    477     m_private->m_timeToRestore = -1.0f;
    478 
    479     if (m_private->m_movieController)
    480         MCDoAction(m_private->m_movieController, mcActionPrerollAndPlay, (void *)GetMoviePreferredRate(m_private->m_movie));
    481     else
    482         StartMovie(m_private->m_movie);
    483     m_private->startTask();
    484 }
    485 
    486 void QTMovieWin::pause()
    487 {
    488     m_private->m_timeToRestore = -1.0f;
    489 
    490     if (m_private->m_movieController)
    491         MCDoAction(m_private->m_movieController, mcActionPlay, 0);
    492     else
    493         StopMovie(m_private->m_movie);
    494     updateTaskTimer();
    495 }
    496 
    497 float QTMovieWin::rate() const
    498 {
    499     if (!m_private->m_movie)
    500         return 0;
    501     return FixedToFloat(GetMovieRate(m_private->m_movie));
    502 }
    503 
    504 void QTMovieWin::setRate(float rate)
    505 {
    506     if (!m_private->m_movie)
    507         return;
    508     m_private->m_timeToRestore = -1.0f;
    509 
    510     if (m_private->m_movieController)
    511         MCDoAction(m_private->m_movieController, mcActionPrerollAndPlay, (void *)FloatToFixed(rate));
    512     else
    513         SetMovieRate(m_private->m_movie, FloatToFixed(rate));
    514     updateTaskTimer();
    515 }
    516 
    517 float QTMovieWin::duration() const
    518 {
    519     if (!m_private->m_movie)
    520         return 0;
    521     TimeValue val = GetMovieDuration(m_private->m_movie);
    522     TimeScale scale = GetMovieTimeScale(m_private->m_movie);
    523     return static_cast<float>(val) / scale;
    524 }
    525 
    526 float QTMovieWin::currentTime() const
    527 {
    528     if (!m_private->m_movie)
    529         return 0;
    530     TimeValue val = GetMovieTime(m_private->m_movie, 0);
    531     TimeScale scale = GetMovieTimeScale(m_private->m_movie);
    532     return static_cast<float>(val) / scale;
    533 }
    534 
    535 void QTMovieWin::setCurrentTime(float time) const
    536 {
    537     if (!m_private->m_movie)
    538         return;
    539 
    540     m_private->m_timeToRestore = -1.0f;
    541 
    542     m_private->m_seeking = true;
    543     TimeScale scale = GetMovieTimeScale(m_private->m_movie);
    544     if (m_private->m_movieController){
    545         QTRestartAtTimeRecord restart = { time * scale , 0 };
    546         MCDoAction(m_private->m_movieController, mcActionRestartAtTime, (void *)&restart);
    547     } else
    548         SetMovieTimeValue(m_private->m_movie, TimeValue(time * scale));
    549     updateTaskTimer();
    550 }
    551 
    552 void QTMovieWin::setVolume(float volume)
    553 {
    554     if (!m_private->m_movie)
    555         return;
    556     SetMovieVolume(m_private->m_movie, static_cast<short>(volume * 256));
    557 }
    558 
    559 void QTMovieWin::setPreservesPitch(bool preservesPitch)
    560 {
    561     if (!m_private->m_movie || !m_private->m_currentURL)
    562         return;
    563 
    564     OSErr error;
    565     bool prop = false;
    566 
    567     error = QTGetMovieProperty(m_private->m_movie, kQTPropertyClass_Audio, kQTAudioPropertyID_RateChangesPreservePitch,
    568                                sizeof(kQTAudioPropertyID_RateChangesPreservePitch), static_cast<QTPropertyValuePtr>(&prop), 0);
    569 
    570     if (error || prop == preservesPitch)
    571         return;
    572 
    573     m_private->m_timeToRestore = currentTime();
    574     m_private->m_rateToRestore = rate();
    575     load(m_private->m_currentURL, preservesPitch);
    576 }
    577 
    578 unsigned QTMovieWin::dataSize() const
    579 {
    580     if (!m_private->m_movie)
    581         return 0;
    582     return GetMovieDataSize(m_private->m_movie, 0, GetMovieDuration(m_private->m_movie));
    583 }
    584 
    585 float QTMovieWin::maxTimeLoaded() const
    586 {
    587     if (!m_private->m_movie)
    588         return 0;
    589     TimeValue val;
    590     GetMaxLoadedTimeInMovie(m_private->m_movie, &val);
    591     TimeScale scale = GetMovieTimeScale(m_private->m_movie);
    592     return static_cast<float>(val) / scale;
    593 }
    594 
    595 long QTMovieWin::loadState() const
    596 {
    597     return m_private->m_loadState;
    598 }
    599 
    600 void QTMovieWin::getNaturalSize(int& width, int& height)
    601 {
    602     Rect rect = { 0, };
    603 
    604     if (m_private->m_movie)
    605         GetMovieNaturalBoundsRect(m_private->m_movie, &rect);
    606     width = (rect.right - rect.left) * m_private->m_widthScaleFactor;
    607     height = (rect.bottom - rect.top) * m_private->m_heightScaleFactor;
    608 }
    609 
    610 void QTMovieWin::setSize(int width, int height)
    611 {
    612     m_private->setSize(width, height);
    613     updateTaskTimer(0);
    614 }
    615 
    616 void QTMovieWin::setVisible(bool b)
    617 {
    618     m_private->m_visible = b;
    619     m_private->updateGWorld();
    620 }
    621 
    622 void QTMovieWin::getCurrentFrameInfo(void*& buffer, unsigned& bitsPerPixel, unsigned& rowBytes, unsigned& width, unsigned& height)
    623 {
    624     if (!m_private->m_gWorld) {
    625         buffer = 0;
    626         bitsPerPixel = 0;
    627         rowBytes = 0;
    628         width = 0;
    629         height = 0;
    630         return;
    631     }
    632     PixMapHandle offscreenPixMap = GetGWorldPixMap(m_private->m_gWorld);
    633     buffer = (*offscreenPixMap)->baseAddr;
    634     bitsPerPixel = (*offscreenPixMap)->pixelSize;
    635     rowBytes = (*offscreenPixMap)->rowBytes & 0x3FFF;
    636     width = m_private->m_width;
    637     height = m_private->m_height;
    638 }
    639 
    640 void QTMovieWin::paint(HDC hdc, int x, int y)
    641 {
    642     if (!m_private->m_gWorld)
    643         return;
    644 
    645     HDC hdcSrc = static_cast<HDC>(GetPortHDC(reinterpret_cast<GrafPtr>(m_private->m_gWorld)));
    646     if (!hdcSrc)
    647         return;
    648 
    649     // FIXME: If we could determine the movie has no alpha, we could use BitBlt for those cases, which might be faster.
    650     BLENDFUNCTION blendFunction;
    651     blendFunction.BlendOp = AC_SRC_OVER;
    652     blendFunction.BlendFlags = 0;
    653     blendFunction.SourceConstantAlpha = 255;
    654     blendFunction.AlphaFormat = AC_SRC_ALPHA;
    655     AlphaBlend(hdc, x, y, m_private->m_width, m_private->m_height, hdcSrc,
    656          0, 0, m_private->m_width, m_private->m_height, blendFunction);
    657 }
    658 
    659 void QTMovieWin::load(const UChar* url, int len, bool preservesPitch)
    660 {
    661     CFStringRef urlStringRef = CFStringCreateWithCharacters(kCFAllocatorDefault, reinterpret_cast<const UniChar*>(url), len);
    662     CFURLRef cfURL = CFURLCreateWithString(kCFAllocatorDefault, urlStringRef, 0);
    663 
    664     load(cfURL, preservesPitch);
    665 
    666     CFRelease(cfURL);
    667     CFRelease(urlStringRef);
    668 }
    669 
    670 void QTMovieWin::load(CFURLRef url, bool preservesPitch)
    671 {
    672     if (!url)
    673         return;
    674 
    675     if (m_private->m_movie) {
    676         m_private->endTask();
    677         if (m_private->m_gWorld)
    678             m_private->deleteGWorld();
    679         if (m_private->m_movieController)
    680             DisposeMovieController(m_private->m_movieController);
    681         m_private->m_movieController = 0;
    682         DisposeMovie(m_private->m_movie);
    683         m_private->m_movie = 0;
    684         m_private->m_loadState = 0;
    685     }
    686 
    687     // Define a property array for NewMovieFromProperties. 8 should be enough for our needs.
    688     QTNewMoviePropertyElement movieProps[8];
    689     ItemCount moviePropCount = 0;
    690 
    691     bool boolTrue = true;
    692 
    693     // Disable streaming support for now.
    694     CFStringRef scheme = CFURLCopyScheme(url);
    695     bool isRTSP = CFStringHasPrefix(scheme, CFSTR("rtsp:"));
    696     CFRelease(scheme);
    697 
    698     if (isRTSP) {
    699         m_private->m_loadError = noMovieFound;
    700         goto end;
    701     }
    702 
    703     if (m_private->m_currentURL) {
    704         if (m_private->m_currentURL != url) {
    705             CFRelease(m_private->m_currentURL);
    706             m_private->m_currentURL = url;
    707             CFRetain(url);
    708         }
    709     } else {
    710         m_private->m_currentURL = url;
    711         CFRetain(url);
    712     }
    713 
    714     // Add the movie data location to the property array
    715     movieProps[moviePropCount].propClass = kQTPropertyClass_DataLocation;
    716     movieProps[moviePropCount].propID = kQTDataLocationPropertyID_CFURL;
    717     movieProps[moviePropCount].propValueSize = sizeof(m_private->m_currentURL);
    718     movieProps[moviePropCount].propValueAddress = &(m_private->m_currentURL);
    719     movieProps[moviePropCount].propStatus = 0;
    720     moviePropCount++;
    721 
    722     movieProps[moviePropCount].propClass = kQTPropertyClass_MovieInstantiation;
    723     movieProps[moviePropCount].propID = kQTMovieInstantiationPropertyID_DontAskUnresolvedDataRefs;
    724     movieProps[moviePropCount].propValueSize = sizeof(boolTrue);
    725     movieProps[moviePropCount].propValueAddress = &boolTrue;
    726     movieProps[moviePropCount].propStatus = 0;
    727     moviePropCount++;
    728 
    729     movieProps[moviePropCount].propClass = kQTPropertyClass_MovieInstantiation;
    730     movieProps[moviePropCount].propID = kQTMovieInstantiationPropertyID_AsyncOK;
    731     movieProps[moviePropCount].propValueSize = sizeof(boolTrue);
    732     movieProps[moviePropCount].propValueAddress = &boolTrue;
    733     movieProps[moviePropCount].propStatus = 0;
    734     moviePropCount++;
    735 
    736     movieProps[moviePropCount].propClass = kQTPropertyClass_NewMovieProperty;
    737     movieProps[moviePropCount].propID = kQTNewMoviePropertyID_Active;
    738     movieProps[moviePropCount].propValueSize = sizeof(boolTrue);
    739     movieProps[moviePropCount].propValueAddress = &boolTrue;
    740     movieProps[moviePropCount].propStatus = 0;
    741     moviePropCount++;
    742 
    743     movieProps[moviePropCount].propClass = kQTPropertyClass_NewMovieProperty;
    744     movieProps[moviePropCount].propID = kQTNewMoviePropertyID_DontInteractWithUser;
    745     movieProps[moviePropCount].propValueSize = sizeof(boolTrue);
    746     movieProps[moviePropCount].propValueAddress = &boolTrue;
    747     movieProps[moviePropCount].propStatus = 0;
    748     moviePropCount++;
    749 
    750     movieProps[moviePropCount].propClass = kQTPropertyClass_MovieInstantiation;
    751     movieProps[moviePropCount].propID = '!url';
    752     movieProps[moviePropCount].propValueSize = sizeof(boolTrue);
    753     movieProps[moviePropCount].propValueAddress = &boolTrue;
    754     movieProps[moviePropCount].propStatus = 0;
    755     moviePropCount++;
    756 
    757     movieProps[moviePropCount].propClass = kQTPropertyClass_MovieInstantiation;
    758     movieProps[moviePropCount].propID = 'site';
    759     movieProps[moviePropCount].propValueSize = sizeof(boolTrue);
    760     movieProps[moviePropCount].propValueAddress = &boolTrue;
    761     movieProps[moviePropCount].propStatus = 0;
    762     moviePropCount++;
    763 
    764     movieProps[moviePropCount].propClass = kQTPropertyClass_Audio;
    765     movieProps[moviePropCount].propID = kQTAudioPropertyID_RateChangesPreservePitch;
    766     movieProps[moviePropCount].propValueSize = sizeof(preservesPitch);
    767     movieProps[moviePropCount].propValueAddress = &preservesPitch;
    768     movieProps[moviePropCount].propStatus = 0;
    769     moviePropCount++;
    770 
    771     ASSERT(moviePropCount <= sizeof(movieProps)/sizeof(movieProps[0]));
    772     m_private->m_loadError = NewMovieFromProperties(moviePropCount, movieProps, 0, 0, &m_private->m_movie);
    773 
    774 end:
    775     m_private->startTask();
    776     // get the load fail callback quickly
    777     if (m_private->m_loadError)
    778         updateTaskTimer(0);
    779     else {
    780         OSType mode = kQTApertureMode_CleanAperture;
    781 
    782         // Set the aperture mode property on a movie to signal that we want aspect ratio
    783         // and clean aperture dimensions. Don't worry about errors, we can't do anything if
    784         // the installed version of QT doesn't support it and it isn't serious enough to
    785         // warrant failing.
    786         QTSetMovieProperty(m_private->m_movie, kQTPropertyClass_Visual, kQTVisualPropertyID_ApertureMode, sizeof(mode), &mode);
    787         m_private->registerDrawingCallback();
    788     }
    789 }
    790 
    791 void QTMovieWin::disableUnsupportedTracks(unsigned& enabledTrackCount, unsigned& totalTrackCount)
    792 {
    793     if (!m_private->m_movie) {
    794         totalTrackCount = 0;
    795         enabledTrackCount = 0;
    796         return;
    797     }
    798 
    799     static HashSet<OSType>* allowedTrackTypes = 0;
    800     if (!allowedTrackTypes) {
    801         allowedTrackTypes = new HashSet<OSType>;
    802         allowedTrackTypes->add(VideoMediaType);
    803         allowedTrackTypes->add(SoundMediaType);
    804         allowedTrackTypes->add(TextMediaType);
    805         allowedTrackTypes->add(BaseMediaType);
    806         allowedTrackTypes->add(closedCaptionTrackType);
    807         allowedTrackTypes->add(subTitleTrackType);
    808         allowedTrackTypes->add(mpeg4ObjectDescriptionTrackType);
    809         allowedTrackTypes->add(mpeg4SceneDescriptionTrackType);
    810         allowedTrackTypes->add(TimeCodeMediaType);
    811         allowedTrackTypes->add(TimeCode64MediaType);
    812     }
    813 
    814     long trackCount = GetMovieTrackCount(m_private->m_movie);
    815     enabledTrackCount = trackCount;
    816     totalTrackCount = trackCount;
    817 
    818     // Track indexes are 1-based. yuck. These things must descend from old-
    819     // school mac resources or something.
    820     for (long trackIndex = 1; trackIndex <= trackCount; trackIndex++) {
    821         // Grab the track at the current index. If there isn't one there, then
    822         // we can move onto the next one.
    823         Track currentTrack = GetMovieIndTrack(m_private->m_movie, trackIndex);
    824         if (!currentTrack)
    825             continue;
    826 
    827         // Check to see if the track is disabled already, we should move along.
    828         // We don't need to re-disable it.
    829         if (!GetTrackEnabled(currentTrack))
    830             continue;
    831 
    832         // Grab the track's media. We're going to check to see if we need to
    833         // disable the tracks. They could be unsupported.
    834         Media trackMedia = GetTrackMedia(currentTrack);
    835         if (!trackMedia)
    836             continue;
    837 
    838         // Grab the media type for this track. Make sure that we don't
    839         // get an error in doing so. If we do, then something really funky is
    840         // wrong.
    841         OSType mediaType;
    842         GetMediaHandlerDescription(trackMedia, &mediaType, nil, nil);
    843         OSErr mediaErr = GetMoviesError();
    844         if (mediaErr != noErr)
    845             continue;
    846 
    847         if (!allowedTrackTypes->contains(mediaType)) {
    848 
    849             // Different mpeg variants import as different track types so check for the "mpeg
    850             // characteristic" instead of hard coding the (current) list of mpeg media types.
    851             if (GetMovieIndTrackType(m_private->m_movie, 1, 'mpeg', movieTrackCharacteristic | movieTrackEnabledOnly))
    852                 continue;
    853 
    854             SetTrackEnabled(currentTrack, false);
    855             --enabledTrackCount;
    856         }
    857 
    858         // Grab the track reference count for chapters. This will tell us if it
    859         // has chapter tracks in it. If there aren't any references, then we
    860         // can move on the next track.
    861         long referenceCount = GetTrackReferenceCount(currentTrack, kTrackReferenceChapterList);
    862         if (referenceCount <= 0)
    863             continue;
    864 
    865         long referenceIndex = 0;
    866         while (1) {
    867             // If we get nothing here, we've overstepped our bounds and can stop
    868             // looking. Chapter indices here are 1-based as well - hence, the
    869             // pre-increment.
    870             referenceIndex++;
    871             Track chapterTrack = GetTrackReference(currentTrack, kTrackReferenceChapterList, referenceIndex);
    872             if (!chapterTrack)
    873                 break;
    874 
    875             // Try to grab the media for the track.
    876             Media chapterMedia = GetTrackMedia(chapterTrack);
    877             if (!chapterMedia)
    878                 continue;
    879 
    880             // Grab the media type for this track. Make sure that we don't
    881             // get an error in doing so. If we do, then something really
    882             // funky is wrong.
    883             OSType mediaType;
    884             GetMediaHandlerDescription(chapterMedia, &mediaType, nil, nil);
    885             OSErr mediaErr = GetMoviesError();
    886             if (mediaErr != noErr)
    887                 continue;
    888 
    889             // Check to see if the track is a video track. We don't care about
    890             // other non-video tracks.
    891             if (mediaType != VideoMediaType)
    892                 continue;
    893 
    894             // Check to see if the track is already disabled. If it is, we
    895             // should move along.
    896             if (!GetTrackEnabled(chapterTrack))
    897                 continue;
    898 
    899             // Disabled the evil, evil track.
    900             SetTrackEnabled(chapterTrack, false);
    901             --enabledTrackCount;
    902         }
    903     }
    904 }
    905 
    906 void QTMovieWin::setDisabled(bool b)
    907 {
    908     m_disabled = b;
    909 }
    910 
    911 
    912 bool QTMovieWin::hasVideo() const
    913 {
    914     if (!m_private->m_movie)
    915         return false;
    916     return (GetMovieIndTrackType(m_private->m_movie, 1, VisualMediaCharacteristic, movieTrackCharacteristic | movieTrackEnabledOnly));
    917 }
    918 
    919 bool QTMovieWin::hasAudio() const
    920 {
    921     if (!m_private->m_movie)
    922         return false;
    923     return (GetMovieIndTrackType(m_private->m_movie, 1, AudioMediaCharacteristic, movieTrackCharacteristic | movieTrackEnabledOnly));
    924 }
    925 
    926 
    927 bool QTMovieWin::hasClosedCaptions() const
    928 {
    929     if (!m_private->m_movie)
    930         return false;
    931     return GetMovieIndTrackType(m_private->m_movie, 1, closedCaptionTrackType, movieTrackMediaType);
    932 }
    933 
    934 void QTMovieWin::setClosedCaptionsVisible(bool visible)
    935 {
    936     if (!m_private->m_movie)
    937         return;
    938 
    939     Track ccTrack = GetMovieIndTrackType(m_private->m_movie, 1, closedCaptionTrackType, movieTrackMediaType);
    940     if (!ccTrack)
    941         return;
    942 
    943     Boolean doDisplay = visible;
    944     QTSetTrackProperty(ccTrack, closedCaptionTrackType, closedCaptionDisplayPropertyID, sizeof(doDisplay), &doDisplay);
    945 }
    946 
    947 pascal OSErr movieDrawingCompleteProc(Movie movie, long data)
    948 {
    949     UppParam param;
    950     param.longValue = data;
    951     QTMovieWinPrivate* mp = static_cast<QTMovieWinPrivate*>(param.ptr);
    952     if (mp)
    953         mp->drawingComplete();
    954     return 0;
    955 }
    956 
    957 static void initializeSupportedTypes()
    958 {
    959     if (gSupportedTypes)
    960         return;
    961 
    962     gSupportedTypes = new Vector<CFStringRef>;
    963     if (quickTimeVersion < minimumQuickTimeVersion) {
    964         LOG_ERROR("QuickTime version %x detected, at least %x required. Returning empty list of supported media MIME types.", quickTimeVersion, minimumQuickTimeVersion);
    965         return;
    966     }
    967 
    968     // QuickTime doesn't have an importer for video/quicktime. Add it manually.
    969     gSupportedTypes->append(CFSTR("video/quicktime"));
    970 
    971     for (int index = 0; index < 2; index++) {
    972         ComponentDescription findCD;
    973 
    974         // look at all movie importers that can import in place and are installed.
    975         findCD.componentType = MovieImportType;
    976         findCD.componentSubType = 0;
    977         findCD.componentManufacturer = 0;
    978         findCD.componentFlagsMask = cmpIsMissing | movieImportSubTypeIsFileExtension | canMovieImportInPlace | dontAutoFileMovieImport;
    979 
    980         // look at those registered by HFS file types the first time through, by file extension the second time
    981         findCD.componentFlags = canMovieImportInPlace | (index ? movieImportSubTypeIsFileExtension : 0);
    982 
    983         long componentCount = CountComponents(&findCD);
    984         if (!componentCount)
    985             continue;
    986 
    987         Component comp = 0;
    988         while (comp = FindNextComponent(comp, &findCD)) {
    989             // Does this component have a MIME type container?
    990             ComponentDescription infoCD;
    991             OSErr err = GetComponentInfo(comp, &infoCD, nil /*name*/, nil /*info*/, nil /*icon*/);
    992             if (err)
    993                 continue;
    994             if (!(infoCD.componentFlags & hasMovieImportMIMEList))
    995                 continue;
    996             QTAtomContainer mimeList = 0;
    997             err = MovieImportGetMIMETypeList((ComponentInstance)comp, &mimeList);
    998             if (err || !mimeList)
    999                 continue;
   1000 
   1001             // Grab every type from the container.
   1002             QTLockContainer(mimeList);
   1003             int typeCount = QTCountChildrenOfType(mimeList, kParentAtomIsContainer, kMimeInfoMimeTypeTag);
   1004             for (int typeIndex = 1; typeIndex <= typeCount; typeIndex++) {
   1005                 QTAtom mimeTag = QTFindChildByIndex(mimeList, 0, kMimeInfoMimeTypeTag, typeIndex, 0);
   1006                 if (!mimeTag)
   1007                     continue;
   1008                 char* atomData;
   1009                 long typeLength;
   1010                 if (noErr != QTGetAtomDataPtr(mimeList, mimeTag, &typeLength, &atomData))
   1011                     continue;
   1012 
   1013                 char typeBuffer[256];
   1014                 if (typeLength >= sizeof(typeBuffer))
   1015                     continue;
   1016                 memcpy(typeBuffer, atomData, typeLength);
   1017                 typeBuffer[typeLength] = 0;
   1018 
   1019                 // Only add "audio/..." and "video/..." types.
   1020                 if (strncmp(typeBuffer, "audio/", 6) && strncmp(typeBuffer, "video/", 6))
   1021                     continue;
   1022 
   1023                 CFStringRef cfMimeType = CFStringCreateWithCString(0, typeBuffer, kCFStringEncodingUTF8);
   1024                 if (!cfMimeType)
   1025                     continue;
   1026 
   1027                 // Only add each type once.
   1028                 bool alreadyAdded = false;
   1029                 for (int addedIndex = 0; addedIndex < gSupportedTypes->size(); addedIndex++) {
   1030                     CFStringRef type = gSupportedTypes->at(addedIndex);
   1031                     if (kCFCompareEqualTo == CFStringCompare(cfMimeType, type, kCFCompareCaseInsensitive)) {
   1032                         alreadyAdded = true;
   1033                         break;
   1034                     }
   1035                 }
   1036                 if (!alreadyAdded)
   1037                     gSupportedTypes->append(cfMimeType);
   1038                 else
   1039                     CFRelease(cfMimeType);
   1040             }
   1041             DisposeHandle(mimeList);
   1042         }
   1043     }
   1044 }
   1045 
   1046 unsigned QTMovieWin::countSupportedTypes()
   1047 {
   1048     initializeSupportedTypes();
   1049     return static_cast<unsigned>(gSupportedTypes->size());
   1050 }
   1051 
   1052 void QTMovieWin::getSupportedType(unsigned index, const UChar*& str, unsigned& len)
   1053 {
   1054     initializeSupportedTypes();
   1055     ASSERT(index < gSupportedTypes->size());
   1056 
   1057     // Allocate sufficient buffer to hold any MIME type
   1058     static UniChar* staticBuffer = 0;
   1059     if (!staticBuffer)
   1060         staticBuffer = new UniChar[32];
   1061 
   1062     CFStringRef cfstr = gSupportedTypes->at(index);
   1063     len = CFStringGetLength(cfstr);
   1064     CFRange range = { 0, len };
   1065     CFStringGetCharacters(cfstr, range, staticBuffer);
   1066     str = reinterpret_cast<const UChar*>(staticBuffer);
   1067 
   1068 }
   1069 
   1070 void QTMovieWin::setTaskTimerFuncs(SetTaskTimerDelayFunc setTaskTimerDelay, StopTaskTimerFunc stopTaskTimer)
   1071 {
   1072     gSetTaskTimerDelay = setTaskTimerDelay;
   1073     gStopTaskTimer = stopTaskTimer;
   1074 }
   1075 
   1076 bool QTMovieWin::initializeQuickTime()
   1077 {
   1078     static bool initialized = false;
   1079     static bool initializationSucceeded = false;
   1080     if (!initialized) {
   1081         initialized = true;
   1082         // Initialize and check QuickTime version
   1083         OSErr result = InitializeQTML(kInitializeQTMLEnableDoubleBufferedSurface);
   1084         if (result == noErr)
   1085             result = Gestalt(gestaltQuickTime, &quickTimeVersion);
   1086         if (result != noErr) {
   1087             LOG_ERROR("No QuickTime available. Disabling <video> and <audio> support.");
   1088             return false;
   1089         }
   1090         if (quickTimeVersion < minimumQuickTimeVersion) {
   1091             LOG_ERROR("QuickTime version %x detected, at least %x required. Disabling <video> and <audio> support.", quickTimeVersion, minimumQuickTimeVersion);
   1092             return false;
   1093         }
   1094         EnterMovies();
   1095         gMovieDrawingCompleteUPP = NewMovieDrawingCompleteUPP(movieDrawingCompleteProc);
   1096         initializationSucceeded = true;
   1097     }
   1098     return initializationSucceeded;
   1099 }
   1100 
   1101 LRESULT QTMovieWin::fullscreenWndProc(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam)
   1102 {
   1103     QTMovieWin* movie = static_cast<QTMovieWin*>(GetProp(wnd, fullscreenQTMovieWinPointerProp));
   1104 
   1105     if (message == WM_DESTROY)
   1106         RemoveProp(wnd, fullscreenQTMovieWinPointerProp);
   1107 
   1108     if (!movie)
   1109         return DefWindowProc(wnd, message, wParam, lParam);
   1110 
   1111     return movie->m_private->m_fullscreenClient->fullscreenClientWndProc(wnd, message, wParam, lParam);
   1112 }
   1113 
   1114 HWND QTMovieWin::enterFullscreen(QTMovieWinFullscreenClient* client)
   1115 {
   1116     m_private->m_fullscreenClient = client;
   1117 
   1118     BeginFullScreen(&m_private->m_fullscreenRestoreState, 0, 0, 0, &m_private->m_fullscreenWindow, 0, fullScreenAllowEvents);
   1119     QTMLSetWindowWndProc(m_private->m_fullscreenWindow, fullscreenWndProc);
   1120     CreatePortAssociation(GetPortNativeWindow(m_private->m_fullscreenWindow), 0, 0);
   1121 
   1122     GetMovieBox(m_private->m_movie, &m_private->m_fullscreenRect);
   1123     GetMovieGWorld(m_private->m_movie, &m_private->m_fullscreenOrigGWorld, 0);
   1124     SetMovieGWorld(m_private->m_movie, reinterpret_cast<CGrafPtr>(m_private->m_fullscreenWindow), GetGWorldDevice(reinterpret_cast<CGrafPtr>(m_private->m_fullscreenWindow)));
   1125 
   1126     // Set the size of the box to preserve aspect ratio
   1127     Rect rect = m_private->m_fullscreenWindow->portRect;
   1128 
   1129     float movieRatio = static_cast<float>(m_private->m_width) / m_private->m_height;
   1130     int windowWidth =  rect.right - rect.left;
   1131     int windowHeight = rect.bottom - rect.top;
   1132     float windowRatio = static_cast<float>(windowWidth) / windowHeight;
   1133     int actualWidth = (windowRatio > movieRatio) ? (windowHeight * movieRatio) : windowWidth;
   1134     int actualHeight = (windowRatio < movieRatio) ? (windowWidth / movieRatio) : windowHeight;
   1135     int offsetX = (windowWidth - actualWidth) / 2;
   1136     int offsetY = (windowHeight - actualHeight) / 2;
   1137 
   1138     rect.left = offsetX;
   1139     rect.right = offsetX + actualWidth;
   1140     rect.top = offsetY;
   1141     rect.bottom = offsetY + actualHeight;
   1142 
   1143     SetMovieBox(m_private->m_movie, &rect);
   1144     ShowHideTaskBar(true);
   1145 
   1146     // Set the 'this' pointer on the HWND
   1147     HWND wnd = static_cast<HWND>(GetPortNativeWindow(m_private->m_fullscreenWindow));
   1148     SetProp(wnd, fullscreenQTMovieWinPointerProp, static_cast<HANDLE>(this));
   1149 
   1150     return wnd;
   1151 }
   1152 
   1153 void QTMovieWin::exitFullscreen()
   1154 {
   1155     if (!m_private->m_fullscreenWindow)
   1156         return;
   1157 
   1158     HWND wnd = static_cast<HWND>(GetPortNativeWindow(m_private->m_fullscreenWindow));
   1159     DestroyPortAssociation(reinterpret_cast<CGrafPtr>(m_private->m_fullscreenWindow));
   1160     SetMovieGWorld(m_private->m_movie, m_private->m_fullscreenOrigGWorld, 0);
   1161     EndFullScreen(m_private->m_fullscreenRestoreState, 0L);
   1162     SetMovieBox(m_private->m_movie, &m_private->m_fullscreenRect);
   1163     m_private->m_fullscreenWindow = 0;
   1164 }
   1165 
   1166 BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
   1167 {
   1168     switch (fdwReason) {
   1169     case DLL_PROCESS_ATTACH:
   1170         return TRUE;
   1171     case DLL_PROCESS_DETACH:
   1172     case DLL_THREAD_ATTACH:
   1173     case DLL_THREAD_DETACH:
   1174         return FALSE;
   1175     }
   1176     ASSERT_NOT_REACHED();
   1177     return FALSE;
   1178 }
   1179