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