QTMovie.cpp revision 2fc2651226baac27029e38c9d6ef883fa32084db
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 "QTMovie.h"
28
29#include "QTMovieTask.h"
30#include "QTMovieWinTimer.h"
31#include <FixMath.h>
32#include <GXMath.h>
33#include <Movies.h>
34#include <QTML.h>
35#include <QuickTimeComponents.h>
36#include <WebKitSystemInterface/WebKitSystemInterface.h>
37#include <wtf/Assertions.h>
38#include <wtf/MathExtras.h>
39#include <wtf/Noncopyable.h>
40#include <wtf/Vector.h>
41
42using namespace std;
43
44static const long minimumQuickTimeVersion = 0x07300000; // 7.3
45
46static const long closedCaptionTrackType = 'clcp';
47static const long subTitleTrackType = 'sbtl';
48static const long mpeg4ObjectDescriptionTrackType = 'odsm';
49static const long mpeg4SceneDescriptionTrackType = 'sdsm';
50static const long closedCaptionDisplayPropertyID = 'disp';
51
52// Resizing GWorlds is slow, give them a minimum size so size of small
53// videos can be animated smoothly
54static const int cGWorldMinWidth = 640;
55static const int cGWorldMinHeight = 360;
56
57static const float cNonContinuousTimeChange = 0.2f;
58
59union UppParam {
60    long longValue;
61    void* ptr;
62};
63
64static CFMutableArrayRef gSupportedTypes = 0;
65static SInt32 quickTimeVersion = 0;
66
67class QTMoviePrivate : public QTMovieTaskClient {
68    WTF_MAKE_NONCOPYABLE(QTMoviePrivate);
69public:
70    QTMoviePrivate();
71    ~QTMoviePrivate();
72    void task();
73    void startTask();
74    void endTask();
75
76    void createMovieController();
77    void cacheMovieScale();
78
79    QTMovie* m_movieWin;
80    Movie m_movie;
81    MovieController m_movieController;
82    bool m_tasking;
83    bool m_disabled;
84    Vector<QTMovieClient*> m_clients;
85    long m_loadState;
86    bool m_ended;
87    bool m_seeking;
88    float m_lastMediaTime;
89    double m_lastLoadStateCheckTime;
90    int m_width;
91    int m_height;
92    bool m_visible;
93    long m_loadError;
94    float m_widthScaleFactor;
95    float m_heightScaleFactor;
96    CFURLRef m_currentURL;
97    float m_timeToRestore;
98    float m_rateToRestore;
99#if !ASSERT_DISABLED
100    bool m_scaleCached;
101#endif
102};
103
104QTMoviePrivate::QTMoviePrivate()
105    : m_movieWin(0)
106    , m_movie(0)
107    , m_movieController(0)
108    , m_tasking(false)
109    , m_loadState(0)
110    , m_ended(false)
111    , m_seeking(false)
112    , m_lastMediaTime(0)
113    , m_lastLoadStateCheckTime(0)
114    , m_width(0)
115    , m_height(0)
116    , m_visible(false)
117    , m_loadError(0)
118    , m_widthScaleFactor(1)
119    , m_heightScaleFactor(1)
120    , m_currentURL(0)
121    , m_timeToRestore(-1.0f)
122    , m_rateToRestore(-1.0f)
123    , m_disabled(false)
124#if !ASSERT_DISABLED
125    , m_scaleCached(false)
126#endif
127{
128}
129
130QTMoviePrivate::~QTMoviePrivate()
131{
132    endTask();
133    if (m_movieController)
134        DisposeMovieController(m_movieController);
135    if (m_movie)
136        DisposeMovie(m_movie);
137    if (m_currentURL)
138        CFRelease(m_currentURL);
139}
140
141void QTMoviePrivate::startTask()
142{
143    if (!m_tasking) {
144        QTMovieTask::sharedTask()->addTaskClient(this);
145        m_tasking = true;
146    }
147    QTMovieTask::sharedTask()->updateTaskTimer();
148}
149
150void QTMoviePrivate::endTask()
151{
152    if (m_tasking) {
153        QTMovieTask::sharedTask()->removeTaskClient(this);
154        m_tasking = false;
155    }
156    QTMovieTask::sharedTask()->updateTaskTimer();
157}
158
159void QTMoviePrivate::task()
160{
161    ASSERT(m_tasking);
162
163    if (!m_loadError) {
164        if (m_movieController)
165            MCIdle(m_movieController);
166        else
167            MoviesTask(m_movie, 0);
168    }
169
170    // GetMovieLoadState documentation says that you should not call it more often than every quarter of a second.
171    if (systemTime() >= m_lastLoadStateCheckTime + 0.25 || m_loadError) {
172        // If load fails QT's load state is QTMovieLoadStateComplete.
173        // This is different from QTKit API and seems strange.
174        long loadState = m_loadError ? QTMovieLoadStateError : GetMovieLoadState(m_movie);
175        if (loadState != m_loadState) {
176            // we only need to erase the movie gworld when the load state changes to loaded while it
177            //  is visible as the gworld is destroyed/created when visibility changes
178            bool shouldRestorePlaybackState = false;
179            bool movieNewlyPlayable = loadState >= QTMovieLoadStateLoaded && m_loadState < QTMovieLoadStateLoaded;
180            m_loadState = loadState;
181            if (movieNewlyPlayable) {
182                cacheMovieScale();
183                shouldRestorePlaybackState = true;
184            }
185
186            if (!m_movieController && m_loadState >= QTMovieLoadStateLoaded)
187                createMovieController();
188
189            for (size_t i = 0; i < m_clients.size(); ++i)
190                m_clients[i]->movieLoadStateChanged(m_movieWin);
191
192            if (shouldRestorePlaybackState && m_timeToRestore != -1.0f) {
193                m_movieWin->setCurrentTime(m_timeToRestore);
194                m_timeToRestore = -1.0f;
195                m_movieWin->setRate(m_rateToRestore);
196                m_rateToRestore = -1.0f;
197            }
198
199            if (m_disabled) {
200                endTask();
201                return;
202            }
203        }
204        m_lastLoadStateCheckTime = systemTime();
205    }
206
207    bool ended = !!IsMovieDone(m_movie);
208    if (ended != m_ended) {
209        m_ended = ended;
210        if (ended) {
211            for (size_t i = 0; i < m_clients.size(); ++i)
212               m_clients[i]->movieEnded(m_movieWin);
213        }
214    }
215
216    float time = m_movieWin->currentTime();
217    if (time < m_lastMediaTime || time >= m_lastMediaTime + cNonContinuousTimeChange || m_seeking) {
218        m_seeking = false;
219        for (size_t i = 0; i < m_clients.size(); ++i)
220            m_clients[i]->movieTimeChanged(m_movieWin);
221    }
222    m_lastMediaTime = time;
223
224    if (m_loadError)
225        endTask();
226    else
227        QTMovieTask::sharedTask()->updateTaskTimer();
228}
229
230void QTMoviePrivate::createMovieController()
231{
232    Rect bounds;
233    long flags;
234
235    if (!m_movie)
236        return;
237
238    if (m_movieController)
239        DisposeMovieController(m_movieController);
240
241    GetMovieBox(m_movie, &bounds);
242    flags = mcTopLeftMovie | mcNotVisible;
243    m_movieController = NewMovieController(m_movie, &bounds, flags);
244    if (!m_movieController)
245        return;
246
247    // Disable automatic looping.
248    MCDoAction(m_movieController, mcActionSetLooping, 0);
249}
250
251void QTMoviePrivate::cacheMovieScale()
252{
253    Rect naturalRect;
254    Rect initialRect;
255
256    GetMovieNaturalBoundsRect(m_movie, &naturalRect);
257    GetMovieBox(m_movie, &initialRect);
258
259    float naturalWidth = naturalRect.right - naturalRect.left;
260    float naturalHeight = naturalRect.bottom - naturalRect.top;
261
262    if (naturalWidth)
263        m_widthScaleFactor = (initialRect.right - initialRect.left) / naturalWidth;
264    if (naturalHeight)
265        m_heightScaleFactor = (initialRect.bottom - initialRect.top) / naturalHeight;
266#if !ASSERT_DISABLED
267    m_scaleCached = true;
268#endif
269}
270
271QTMovie::QTMovie(QTMovieClient* client)
272    : m_private(new QTMoviePrivate())
273{
274    m_private->m_movieWin = this;
275    if (client)
276        m_private->m_clients.append(client);
277    initializeQuickTime();
278}
279
280QTMovie::~QTMovie()
281{
282    delete m_private;
283}
284
285void QTMovie::disableComponent(uint32_t cd[5])
286{
287    ComponentDescription nullDesc = {'null', 'base', kAppleManufacturer, 0, 0};
288    Component nullComp = FindNextComponent(0, &nullDesc);
289    Component disabledComp = 0;
290
291    while (disabledComp = FindNextComponent(disabledComp, (ComponentDescription*)&cd[0]))
292        CaptureComponent(disabledComp, nullComp);
293}
294
295void QTMovie::addClient(QTMovieClient* client)
296{
297    if (client)
298        m_private->m_clients.append(client);
299}
300
301void QTMovie::removeClient(QTMovieClient* client)
302{
303    size_t indexOfClient = m_private->m_clients.find(client);
304    if (indexOfClient != notFound)
305        m_private->m_clients.remove(indexOfClient);
306}
307
308void QTMovie::play()
309{
310    m_private->m_timeToRestore = -1.0f;
311
312    if (m_private->m_movieController)
313        MCDoAction(m_private->m_movieController, mcActionPrerollAndPlay, (void *)GetMoviePreferredRate(m_private->m_movie));
314    else
315        StartMovie(m_private->m_movie);
316    m_private->startTask();
317}
318
319void QTMovie::pause()
320{
321    m_private->m_timeToRestore = -1.0f;
322
323    if (m_private->m_movieController)
324        MCDoAction(m_private->m_movieController, mcActionPlay, 0);
325    else
326        StopMovie(m_private->m_movie);
327    QTMovieTask::sharedTask()->updateTaskTimer();
328}
329
330float QTMovie::rate() const
331{
332    if (!m_private->m_movie)
333        return 0;
334    return FixedToFloat(GetMovieRate(m_private->m_movie));
335}
336
337void QTMovie::setRate(float rate)
338{
339    if (!m_private->m_movie)
340        return;
341    m_private->m_timeToRestore = -1.0f;
342
343    if (m_private->m_movieController)
344        MCDoAction(m_private->m_movieController, mcActionPrerollAndPlay, (void *)FloatToFixed(rate));
345    else
346        SetMovieRate(m_private->m_movie, FloatToFixed(rate));
347    QTMovieTask::sharedTask()->updateTaskTimer();
348}
349
350float QTMovie::duration() const
351{
352    if (!m_private->m_movie)
353        return 0;
354    TimeValue val = GetMovieDuration(m_private->m_movie);
355    TimeScale scale = GetMovieTimeScale(m_private->m_movie);
356    return static_cast<float>(val) / scale;
357}
358
359float QTMovie::currentTime() const
360{
361    if (!m_private->m_movie)
362        return 0;
363    TimeValue val = GetMovieTime(m_private->m_movie, 0);
364    TimeScale scale = GetMovieTimeScale(m_private->m_movie);
365    return static_cast<float>(val) / scale;
366}
367
368void QTMovie::setCurrentTime(float time) const
369{
370    if (!m_private->m_movie)
371        return;
372
373    m_private->m_timeToRestore = -1.0f;
374
375    m_private->m_seeking = true;
376    TimeScale scale = GetMovieTimeScale(m_private->m_movie);
377    if (m_private->m_movieController) {
378        QTRestartAtTimeRecord restart = { lroundf(time * scale) , 0 };
379        MCDoAction(m_private->m_movieController, mcActionRestartAtTime, (void *)&restart);
380    } else
381        SetMovieTimeValue(m_private->m_movie, TimeValue(lroundf(time * scale)));
382    QTMovieTask::sharedTask()->updateTaskTimer();
383}
384
385void QTMovie::setVolume(float volume)
386{
387    if (!m_private->m_movie)
388        return;
389    SetMovieVolume(m_private->m_movie, static_cast<short>(volume * 256));
390}
391
392void QTMovie::setPreservesPitch(bool preservesPitch)
393{
394    if (!m_private->m_movie || !m_private->m_currentURL)
395        return;
396
397    OSErr error;
398    bool prop = false;
399
400    error = QTGetMovieProperty(m_private->m_movie, kQTPropertyClass_Audio, kQTAudioPropertyID_RateChangesPreservePitch,
401                               sizeof(kQTAudioPropertyID_RateChangesPreservePitch), static_cast<QTPropertyValuePtr>(&prop), 0);
402
403    if (error || prop == preservesPitch)
404        return;
405
406    m_private->m_timeToRestore = currentTime();
407    m_private->m_rateToRestore = rate();
408    load(m_private->m_currentURL, preservesPitch);
409}
410
411unsigned QTMovie::dataSize() const
412{
413    if (!m_private->m_movie)
414        return 0;
415    return GetMovieDataSize(m_private->m_movie, 0, GetMovieDuration(m_private->m_movie));
416}
417
418float QTMovie::maxTimeLoaded() const
419{
420    if (!m_private->m_movie)
421        return 0;
422    TimeValue val;
423    GetMaxLoadedTimeInMovie(m_private->m_movie, &val);
424    TimeScale scale = GetMovieTimeScale(m_private->m_movie);
425    return static_cast<float>(val) / scale;
426}
427
428long QTMovie::loadState() const
429{
430    return m_private->m_loadState;
431}
432
433void QTMovie::getNaturalSize(int& width, int& height)
434{
435    Rect rect = { 0, };
436
437    if (m_private->m_movie)
438        GetMovieNaturalBoundsRect(m_private->m_movie, &rect);
439    width = (rect.right - rect.left) * m_private->m_widthScaleFactor;
440    height = (rect.bottom - rect.top) * m_private->m_heightScaleFactor;
441}
442
443void QTMovie::load(const UChar* url, int len, bool preservesPitch)
444{
445    CFStringRef urlStringRef = CFStringCreateWithCharacters(kCFAllocatorDefault, reinterpret_cast<const UniChar*>(url), len);
446    CFURLRef cfURL = CFURLCreateWithString(kCFAllocatorDefault, urlStringRef, 0);
447
448    load(cfURL, preservesPitch);
449
450    CFRelease(cfURL);
451    CFRelease(urlStringRef);
452}
453
454void QTMovie::load(CFURLRef url, bool preservesPitch)
455{
456    if (!url)
457        return;
458
459    if (m_private->m_movie) {
460        m_private->endTask();
461        if (m_private->m_movieController)
462            DisposeMovieController(m_private->m_movieController);
463        m_private->m_movieController = 0;
464        DisposeMovie(m_private->m_movie);
465        m_private->m_movie = 0;
466        m_private->m_loadState = 0;
467    }
468
469    // Define a property array for NewMovieFromProperties. 8 should be enough for our needs.
470    QTNewMoviePropertyElement movieProps[8];
471    ItemCount moviePropCount = 0;
472
473    bool boolTrue = true;
474
475    // Disable streaming support for now.
476    CFStringRef scheme = CFURLCopyScheme(url);
477    bool isRTSP = CFStringHasPrefix(scheme, CFSTR("rtsp:"));
478    CFRelease(scheme);
479
480    if (isRTSP) {
481        m_private->m_loadError = noMovieFound;
482        goto end;
483    }
484
485    if (m_private->m_currentURL) {
486        if (m_private->m_currentURL != url) {
487            CFRelease(m_private->m_currentURL);
488            m_private->m_currentURL = url;
489            CFRetain(url);
490        }
491    } else {
492        m_private->m_currentURL = url;
493        CFRetain(url);
494    }
495
496    // Add the movie data location to the property array
497    movieProps[moviePropCount].propClass = kQTPropertyClass_DataLocation;
498    movieProps[moviePropCount].propID = kQTDataLocationPropertyID_CFURL;
499    movieProps[moviePropCount].propValueSize = sizeof(m_private->m_currentURL);
500    movieProps[moviePropCount].propValueAddress = &(m_private->m_currentURL);
501    movieProps[moviePropCount].propStatus = 0;
502    moviePropCount++;
503
504    movieProps[moviePropCount].propClass = kQTPropertyClass_MovieInstantiation;
505    movieProps[moviePropCount].propID = kQTMovieInstantiationPropertyID_DontAskUnresolvedDataRefs;
506    movieProps[moviePropCount].propValueSize = sizeof(boolTrue);
507    movieProps[moviePropCount].propValueAddress = &boolTrue;
508    movieProps[moviePropCount].propStatus = 0;
509    moviePropCount++;
510
511    movieProps[moviePropCount].propClass = kQTPropertyClass_MovieInstantiation;
512    movieProps[moviePropCount].propID = kQTMovieInstantiationPropertyID_AsyncOK;
513    movieProps[moviePropCount].propValueSize = sizeof(boolTrue);
514    movieProps[moviePropCount].propValueAddress = &boolTrue;
515    movieProps[moviePropCount].propStatus = 0;
516    moviePropCount++;
517
518    movieProps[moviePropCount].propClass = kQTPropertyClass_NewMovieProperty;
519    movieProps[moviePropCount].propID = kQTNewMoviePropertyID_Active;
520    movieProps[moviePropCount].propValueSize = sizeof(boolTrue);
521    movieProps[moviePropCount].propValueAddress = &boolTrue;
522    movieProps[moviePropCount].propStatus = 0;
523    moviePropCount++;
524
525    movieProps[moviePropCount].propClass = kQTPropertyClass_NewMovieProperty;
526    movieProps[moviePropCount].propID = kQTNewMoviePropertyID_DontInteractWithUser;
527    movieProps[moviePropCount].propValueSize = sizeof(boolTrue);
528    movieProps[moviePropCount].propValueAddress = &boolTrue;
529    movieProps[moviePropCount].propStatus = 0;
530    moviePropCount++;
531
532    movieProps[moviePropCount].propClass = kQTPropertyClass_MovieInstantiation;
533    movieProps[moviePropCount].propID = '!url';
534    movieProps[moviePropCount].propValueSize = sizeof(boolTrue);
535    movieProps[moviePropCount].propValueAddress = &boolTrue;
536    movieProps[moviePropCount].propStatus = 0;
537    moviePropCount++;
538
539    movieProps[moviePropCount].propClass = kQTPropertyClass_MovieInstantiation;
540    movieProps[moviePropCount].propID = 'site';
541    movieProps[moviePropCount].propValueSize = sizeof(boolTrue);
542    movieProps[moviePropCount].propValueAddress = &boolTrue;
543    movieProps[moviePropCount].propStatus = 0;
544    moviePropCount++;
545
546    movieProps[moviePropCount].propClass = kQTPropertyClass_Audio;
547    movieProps[moviePropCount].propID = kQTAudioPropertyID_RateChangesPreservePitch;
548    movieProps[moviePropCount].propValueSize = sizeof(preservesPitch);
549    movieProps[moviePropCount].propValueAddress = &preservesPitch;
550    movieProps[moviePropCount].propStatus = 0;
551    moviePropCount++;
552
553    ASSERT(moviePropCount <= WTF_ARRAY_LENGTH(movieProps));
554    m_private->m_loadError = NewMovieFromProperties(moviePropCount, movieProps, 0, 0, &m_private->m_movie);
555
556end:
557    m_private->startTask();
558    // get the load fail callback quickly
559    if (m_private->m_loadError)
560        QTMovieTask::sharedTask()->updateTaskTimer(0);
561    else {
562        OSType mode = kQTApertureMode_CleanAperture;
563
564        // Set the aperture mode property on a movie to signal that we want aspect ratio
565        // and clean aperture dimensions. Don't worry about errors, we can't do anything if
566        // the installed version of QT doesn't support it and it isn't serious enough to
567        // warrant failing.
568        QTSetMovieProperty(m_private->m_movie, kQTPropertyClass_Visual, kQTVisualPropertyID_ApertureMode, sizeof(mode), &mode);
569    }
570}
571
572void QTMovie::disableUnsupportedTracks(unsigned& enabledTrackCount, unsigned& totalTrackCount)
573{
574    if (!m_private->m_movie) {
575        totalTrackCount = 0;
576        enabledTrackCount = 0;
577        return;
578    }
579
580    static HashSet<OSType>* allowedTrackTypes = 0;
581    if (!allowedTrackTypes) {
582        allowedTrackTypes = new HashSet<OSType>;
583        allowedTrackTypes->add(VideoMediaType);
584        allowedTrackTypes->add(SoundMediaType);
585        allowedTrackTypes->add(TextMediaType);
586        allowedTrackTypes->add(BaseMediaType);
587        allowedTrackTypes->add(closedCaptionTrackType);
588        allowedTrackTypes->add(subTitleTrackType);
589        allowedTrackTypes->add(mpeg4ObjectDescriptionTrackType);
590        allowedTrackTypes->add(mpeg4SceneDescriptionTrackType);
591        allowedTrackTypes->add(TimeCodeMediaType);
592        allowedTrackTypes->add(TimeCode64MediaType);
593    }
594
595    long trackCount = GetMovieTrackCount(m_private->m_movie);
596    enabledTrackCount = trackCount;
597    totalTrackCount = trackCount;
598
599    // Track indexes are 1-based. yuck. These things must descend from old-
600    // school mac resources or something.
601    for (long trackIndex = 1; trackIndex <= trackCount; trackIndex++) {
602        // Grab the track at the current index. If there isn't one there, then
603        // we can move onto the next one.
604        Track currentTrack = GetMovieIndTrack(m_private->m_movie, trackIndex);
605        if (!currentTrack)
606            continue;
607
608        // Check to see if the track is disabled already, we should move along.
609        // We don't need to re-disable it.
610        if (!GetTrackEnabled(currentTrack))
611            continue;
612
613        // Grab the track's media. We're going to check to see if we need to
614        // disable the tracks. They could be unsupported.
615        Media trackMedia = GetTrackMedia(currentTrack);
616        if (!trackMedia)
617            continue;
618
619        // Grab the media type for this track. Make sure that we don't
620        // get an error in doing so. If we do, then something really funky is
621        // wrong.
622        OSType mediaType;
623        GetMediaHandlerDescription(trackMedia, &mediaType, nil, nil);
624        OSErr mediaErr = GetMoviesError();
625        if (mediaErr != noErr)
626            continue;
627
628        if (!allowedTrackTypes->contains(mediaType)) {
629
630            // Different mpeg variants import as different track types so check for the "mpeg
631            // characteristic" instead of hard coding the (current) list of mpeg media types.
632            if (GetMovieIndTrackType(m_private->m_movie, 1, 'mpeg', movieTrackCharacteristic | movieTrackEnabledOnly))
633                continue;
634
635            SetTrackEnabled(currentTrack, false);
636            --enabledTrackCount;
637        }
638
639        // Grab the track reference count for chapters. This will tell us if it
640        // has chapter tracks in it. If there aren't any references, then we
641        // can move on the next track.
642        long referenceCount = GetTrackReferenceCount(currentTrack, kTrackReferenceChapterList);
643        if (referenceCount <= 0)
644            continue;
645
646        long referenceIndex = 0;
647        while (1) {
648            // If we get nothing here, we've overstepped our bounds and can stop
649            // looking. Chapter indices here are 1-based as well - hence, the
650            // pre-increment.
651            referenceIndex++;
652            Track chapterTrack = GetTrackReference(currentTrack, kTrackReferenceChapterList, referenceIndex);
653            if (!chapterTrack)
654                break;
655
656            // Try to grab the media for the track.
657            Media chapterMedia = GetTrackMedia(chapterTrack);
658            if (!chapterMedia)
659                continue;
660
661            // Grab the media type for this track. Make sure that we don't
662            // get an error in doing so. If we do, then something really
663            // funky is wrong.
664            OSType mediaType;
665            GetMediaHandlerDescription(chapterMedia, &mediaType, nil, nil);
666            OSErr mediaErr = GetMoviesError();
667            if (mediaErr != noErr)
668                continue;
669
670            // Check to see if the track is a video track. We don't care about
671            // other non-video tracks.
672            if (mediaType != VideoMediaType)
673                continue;
674
675            // Check to see if the track is already disabled. If it is, we
676            // should move along.
677            if (!GetTrackEnabled(chapterTrack))
678                continue;
679
680            // Disabled the evil, evil track.
681            SetTrackEnabled(chapterTrack, false);
682            --enabledTrackCount;
683        }
684    }
685}
686
687bool QTMovie::isDisabled() const
688{
689    return m_private->m_disabled;
690}
691
692void QTMovie::setDisabled(bool b)
693{
694    m_private->m_disabled = b;
695}
696
697
698bool QTMovie::hasVideo() const
699{
700    if (!m_private->m_movie)
701        return false;
702    return (GetMovieIndTrackType(m_private->m_movie, 1, VisualMediaCharacteristic, movieTrackCharacteristic | movieTrackEnabledOnly));
703}
704
705bool QTMovie::hasAudio() const
706{
707    if (!m_private->m_movie)
708        return false;
709    return (GetMovieIndTrackType(m_private->m_movie, 1, AudioMediaCharacteristic, movieTrackCharacteristic | movieTrackEnabledOnly));
710}
711
712QTTrackArray QTMovie::videoTracks() const
713{
714    QTTrackArray tracks;
715    long trackIndex = 1;
716
717    while (Track theTrack = GetMovieIndTrackType(m_private->m_movie, trackIndex++, VisualMediaCharacteristic, movieTrackCharacteristic | movieTrackEnabledOnly))
718        tracks.append(QTTrack::create(theTrack));
719
720    return tracks;
721}
722
723bool QTMovie::hasClosedCaptions() const
724{
725    if (!m_private->m_movie)
726        return false;
727    return GetMovieIndTrackType(m_private->m_movie, 1, closedCaptionTrackType, movieTrackMediaType);
728}
729
730void QTMovie::setClosedCaptionsVisible(bool visible)
731{
732    if (!m_private->m_movie)
733        return;
734
735    Track ccTrack = GetMovieIndTrackType(m_private->m_movie, 1, closedCaptionTrackType, movieTrackMediaType);
736    if (!ccTrack)
737        return;
738
739    Boolean doDisplay = visible;
740    QTSetTrackProperty(ccTrack, closedCaptionTrackType, closedCaptionDisplayPropertyID, sizeof(doDisplay), &doDisplay);
741}
742
743long QTMovie::timeScale() const
744{
745    if (!m_private->m_movie)
746        return 0;
747
748    return GetMovieTimeScale(m_private->m_movie);
749}
750
751static void getMIMETypeCallBack(const char* type);
752
753static void initializeSupportedTypes()
754{
755    if (gSupportedTypes)
756        return;
757
758    gSupportedTypes = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
759    if (quickTimeVersion < minimumQuickTimeVersion) {
760        LOG_ERROR("QuickTime version %x detected, at least %x required. Returning empty list of supported media MIME types.", quickTimeVersion, minimumQuickTimeVersion);
761        return;
762    }
763
764    // QuickTime doesn't have an importer for video/quicktime. Add it manually.
765    CFArrayAppendValue(gSupportedTypes, CFSTR("video/quicktime"));
766
767    wkGetQuickTimeMIMETypeList(getMIMETypeCallBack);
768}
769
770static void getMIMETypeCallBack(const char* type)
771{
772    ASSERT(type);
773    CFStringRef cfType = CFStringCreateWithCString(kCFAllocatorDefault, type, kCFStringEncodingMacRoman);
774    if (!cfType)
775        return;
776
777    // Filter out all non-audio or -video MIME Types, and only add each type once:
778    if (CFStringHasPrefix(cfType, CFSTR("audio/")) || CFStringHasPrefix(cfType, CFSTR("video/"))) {
779        CFRange range = CFRangeMake(0, CFArrayGetCount(gSupportedTypes));
780        if (!CFArrayContainsValue(gSupportedTypes, range, cfType))
781            CFArrayAppendValue(gSupportedTypes, cfType);
782    }
783
784    CFRelease(cfType);
785}
786
787unsigned QTMovie::countSupportedTypes()
788{
789    initializeSupportedTypes();
790    return static_cast<unsigned>(CFArrayGetCount(gSupportedTypes));
791}
792
793void QTMovie::getSupportedType(unsigned index, const UChar*& str, unsigned& len)
794{
795    initializeSupportedTypes();
796    ASSERT(index < CFArrayGetCount(gSupportedTypes));
797
798    // Allocate sufficient buffer to hold any MIME type
799    static UniChar* staticBuffer = 0;
800    if (!staticBuffer)
801        staticBuffer = new UniChar[32];
802
803    CFStringRef cfstr = (CFStringRef)CFArrayGetValueAtIndex(gSupportedTypes, index);
804    len = CFStringGetLength(cfstr);
805    CFRange range = { 0, len };
806    CFStringGetCharacters(cfstr, range, staticBuffer);
807    str = reinterpret_cast<const UChar*>(staticBuffer);
808
809}
810
811CGAffineTransform QTMovie::getTransform() const
812{
813    ASSERT(m_private->m_movie);
814    MatrixRecord m = {0};
815    GetMovieMatrix(m_private->m_movie, &m);
816
817    ASSERT(!m.matrix[0][2]);
818    ASSERT(!m.matrix[1][2]);
819    CGAffineTransform transform = CGAffineTransformMake(
820        Fix2X(m.matrix[0][0]),
821        Fix2X(m.matrix[0][1]),
822        Fix2X(m.matrix[1][0]),
823        Fix2X(m.matrix[1][1]),
824        Fix2X(m.matrix[2][0]),
825        Fix2X(m.matrix[2][1]));
826    return transform;
827}
828
829void QTMovie::setTransform(CGAffineTransform t)
830{
831    ASSERT(m_private->m_movie);
832    MatrixRecord m = {{
833        {X2Fix(t.a), X2Fix(t.b), 0},
834        {X2Fix(t.c), X2Fix(t.d), 0},
835        {X2Fix(t.tx), X2Fix(t.ty), fract1},
836    }};
837
838    SetMovieMatrix(m_private->m_movie, &m);
839    m_private->cacheMovieScale();
840}
841
842void QTMovie::resetTransform()
843{
844    ASSERT(m_private->m_movie);
845    SetMovieMatrix(m_private->m_movie, 0);
846    m_private->cacheMovieScale();
847}
848
849
850bool QTMovie::initializeQuickTime()
851{
852    static bool initialized = false;
853    static bool initializationSucceeded = false;
854    if (!initialized) {
855        initialized = true;
856        // Initialize and check QuickTime version
857        OSErr result = InitializeQTML(kInitializeQTMLEnableDoubleBufferedSurface);
858        if (result == noErr)
859            result = Gestalt(gestaltQuickTime, &quickTimeVersion);
860        if (result != noErr) {
861            LOG_ERROR("No QuickTime available. Disabling <video> and <audio> support.");
862            return false;
863        }
864        if (quickTimeVersion < minimumQuickTimeVersion) {
865            LOG_ERROR("QuickTime version %x detected, at least %x required. Disabling <video> and <audio> support.", quickTimeVersion, minimumQuickTimeVersion);
866            return false;
867        }
868        EnterMovies();
869        initializationSucceeded = true;
870    }
871    return initializationSucceeded;
872}
873
874Movie QTMovie::getMovieHandle() const
875{
876    return m_private->m_movie;
877}
878
879BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
880{
881    switch (fdwReason) {
882    case DLL_PROCESS_ATTACH:
883        return TRUE;
884    case DLL_PROCESS_DETACH:
885    case DLL_THREAD_ATTACH:
886    case DLL_THREAD_DETACH:
887        return FALSE;
888    }
889    ASSERT_NOT_REACHED();
890    return FALSE;
891}
892