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 
42 using namespace std;
43 
44 static const long minimumQuickTimeVersion = 0x07300000; // 7.3
45 
46 static const long closedCaptionTrackType = 'clcp';
47 static const long subTitleTrackType = 'sbtl';
48 static const long mpeg4ObjectDescriptionTrackType = 'odsm';
49 static const long mpeg4SceneDescriptionTrackType = 'sdsm';
50 static 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
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 CFMutableArrayRef gSupportedTypes = 0;
65 static SInt32 quickTimeVersion = 0;
66 
67 class QTMoviePrivate : public QTMovieTaskClient {
68     WTF_MAKE_NONCOPYABLE(QTMoviePrivate);
69 public:
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 
QTMoviePrivate()105 QTMoviePrivate::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 
~QTMoviePrivate()132 QTMoviePrivate::~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 
startTask()143 void QTMoviePrivate::startTask()
144 {
145     if (!m_tasking) {
146         QTMovieTask::sharedTask()->addTaskClient(this);
147         m_tasking = true;
148     }
149     QTMovieTask::sharedTask()->updateTaskTimer();
150 }
151 
endTask()152 void QTMoviePrivate::endTask()
153 {
154     if (m_tasking) {
155         QTMovieTask::sharedTask()->removeTaskClient(this);
156         m_tasking = false;
157     }
158     QTMovieTask::sharedTask()->updateTaskTimer();
159 }
160 
task()161 void 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 
createMovieController()232 void 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 
cacheMovieScale()253 void 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 
QTMovie(QTMovieClient * client)273 QTMovie::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 
~QTMovie()282 QTMovie::~QTMovie()
283 {
284     delete m_private;
285 }
286 
disableComponent(uint32_t cd[5])287 void 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 
addClient(QTMovieClient * client)297 void QTMovie::addClient(QTMovieClient* client)
298 {
299     if (client)
300         m_private->m_clients.append(client);
301 }
302 
removeClient(QTMovieClient * client)303 void 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 
play()310 void 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 
pause()321 void 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 
rate() const332 float QTMovie::rate() const
333 {
334     if (!m_private->m_movie)
335         return 0;
336     return FixedToFloat(GetMovieRate(m_private->m_movie));
337 }
338 
setRate(float rate)339 void 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 
duration() const352 float 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 
currentTime() const361 float 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 
setCurrentTime(float time) const370 void 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 
setVolume(float volume)387 void 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 
setPreservesPitch(bool preservesPitch)394 void 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 
dataSize() const413 unsigned 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 
maxTimeLoaded() const420 float 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 
loadState() const430 long QTMovie::loadState() const
431 {
432     return m_private->m_loadState;
433 }
434 
getNaturalSize(int & width,int & height)435 void 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 
loadPath(const UChar * url,int len,bool preservesPitch)445 void 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     CFRelease(cfURL);
453     CFRelease(urlStringRef);
454 }
455 
load(const UChar * url,int len,bool preservesPitch)456 void QTMovie::load(const UChar* url, int len, bool preservesPitch)
457 {
458     CFStringRef urlStringRef = CFStringCreateWithCharacters(kCFAllocatorDefault, reinterpret_cast<const UniChar*>(url), len);
459     CFURLRef cfURL = CFURLCreateWithString(kCFAllocatorDefault, urlStringRef, 0);
460 
461     load(cfURL, preservesPitch);
462 
463     CFRelease(cfURL);
464     CFRelease(urlStringRef);
465 }
466 
load(CFURLRef url,bool preservesPitch)467 void QTMovie::load(CFURLRef url, bool preservesPitch)
468 {
469     if (!url)
470         return;
471 
472     if (m_private->m_movie) {
473         m_private->endTask();
474         if (m_private->m_movieController)
475             DisposeMovieController(m_private->m_movieController);
476         m_private->m_movieController = 0;
477         DisposeMovie(m_private->m_movie);
478         m_private->m_movie = 0;
479         m_private->m_loadState = 0;
480     }
481 
482     // Define a property array for NewMovieFromProperties.
483     QTNewMoviePropertyElement movieProps[9];
484     ItemCount moviePropCount = 0;
485 
486     bool boolTrue = true;
487 
488     // Disable streaming support for now.
489     CFStringRef scheme = CFURLCopyScheme(url);
490     bool isRTSP = CFStringHasPrefix(scheme, CFSTR("rtsp:"));
491     CFRelease(scheme);
492 
493     if (isRTSP) {
494         m_private->m_loadError = noMovieFound;
495         goto end;
496     }
497 
498     if (m_private->m_currentURL) {
499         if (m_private->m_currentURL != url) {
500             CFRelease(m_private->m_currentURL);
501             m_private->m_currentURL = url;
502             CFRetain(url);
503         }
504     } else {
505         m_private->m_currentURL = url;
506         CFRetain(url);
507     }
508 
509     // Add the movie data location to the property array
510     movieProps[moviePropCount].propClass = kQTPropertyClass_DataLocation;
511     movieProps[moviePropCount].propID = kQTDataLocationPropertyID_CFURL;
512     movieProps[moviePropCount].propValueSize = sizeof(m_private->m_currentURL);
513     movieProps[moviePropCount].propValueAddress = &(m_private->m_currentURL);
514     movieProps[moviePropCount].propStatus = 0;
515     moviePropCount++;
516 
517     movieProps[moviePropCount].propClass = kQTPropertyClass_MovieInstantiation;
518     movieProps[moviePropCount].propID = kQTMovieInstantiationPropertyID_DontAskUnresolvedDataRefs;
519     movieProps[moviePropCount].propValueSize = sizeof(boolTrue);
520     movieProps[moviePropCount].propValueAddress = &boolTrue;
521     movieProps[moviePropCount].propStatus = 0;
522     moviePropCount++;
523 
524     movieProps[moviePropCount].propClass = kQTPropertyClass_MovieInstantiation;
525     movieProps[moviePropCount].propID = kQTMovieInstantiationPropertyID_AsyncOK;
526     movieProps[moviePropCount].propValueSize = sizeof(boolTrue);
527     movieProps[moviePropCount].propValueAddress = &boolTrue;
528     movieProps[moviePropCount].propStatus = 0;
529     moviePropCount++;
530 
531     movieProps[moviePropCount].propClass = kQTPropertyClass_NewMovieProperty;
532     movieProps[moviePropCount].propID = kQTNewMoviePropertyID_Active;
533     movieProps[moviePropCount].propValueSize = sizeof(boolTrue);
534     movieProps[moviePropCount].propValueAddress = &boolTrue;
535     movieProps[moviePropCount].propStatus = 0;
536     moviePropCount++;
537 
538     movieProps[moviePropCount].propClass = kQTPropertyClass_NewMovieProperty;
539     movieProps[moviePropCount].propID = kQTNewMoviePropertyID_DontInteractWithUser;
540     movieProps[moviePropCount].propValueSize = sizeof(boolTrue);
541     movieProps[moviePropCount].propValueAddress = &boolTrue;
542     movieProps[moviePropCount].propStatus = 0;
543     moviePropCount++;
544 
545     movieProps[moviePropCount].propClass = kQTPropertyClass_MovieInstantiation;
546     movieProps[moviePropCount].propID = '!url';
547     movieProps[moviePropCount].propValueSize = sizeof(boolTrue);
548     movieProps[moviePropCount].propValueAddress = &boolTrue;
549     movieProps[moviePropCount].propStatus = 0;
550     moviePropCount++;
551 
552     movieProps[moviePropCount].propClass = kQTPropertyClass_MovieInstantiation;
553     movieProps[moviePropCount].propID = 'site';
554     movieProps[moviePropCount].propValueSize = sizeof(boolTrue);
555     movieProps[moviePropCount].propValueAddress = &boolTrue;
556     movieProps[moviePropCount].propStatus = 0;
557     moviePropCount++;
558 
559     movieProps[moviePropCount].propClass = kQTPropertyClass_Audio;
560     movieProps[moviePropCount].propID = kQTAudioPropertyID_RateChangesPreservePitch;
561     movieProps[moviePropCount].propValueSize = sizeof(preservesPitch);
562     movieProps[moviePropCount].propValueAddress = &preservesPitch;
563     movieProps[moviePropCount].propStatus = 0;
564     moviePropCount++;
565 
566     bool allowCaching = !m_private->m_privateBrowsing;
567     movieProps[moviePropCount].propClass = kQTPropertyClass_MovieInstantiation;
568     movieProps[moviePropCount].propID = 'pers';
569     movieProps[moviePropCount].propValueSize = sizeof(allowCaching);
570     movieProps[moviePropCount].propValueAddress = &allowCaching;
571     movieProps[moviePropCount].propStatus = 0;
572     moviePropCount++;
573 
574     ASSERT(moviePropCount <= WTF_ARRAY_LENGTH(movieProps));
575     m_private->m_loadError = NewMovieFromProperties(moviePropCount, movieProps, 0, 0, &m_private->m_movie);
576 
577 end:
578     m_private->startTask();
579     // get the load fail callback quickly
580     if (m_private->m_loadError)
581         QTMovieTask::sharedTask()->updateTaskTimer(0);
582     else {
583         OSType mode = kQTApertureMode_CleanAperture;
584 
585         // Set the aperture mode property on a movie to signal that we want aspect ratio
586         // and clean aperture dimensions. Don't worry about errors, we can't do anything if
587         // the installed version of QT doesn't support it and it isn't serious enough to
588         // warrant failing.
589         QTSetMovieProperty(m_private->m_movie, kQTPropertyClass_Visual, kQTVisualPropertyID_ApertureMode, sizeof(mode), &mode);
590     }
591 }
592 
disableUnsupportedTracks(unsigned & enabledTrackCount,unsigned & totalTrackCount)593 void QTMovie::disableUnsupportedTracks(unsigned& enabledTrackCount, unsigned& totalTrackCount)
594 {
595     if (!m_private->m_movie) {
596         totalTrackCount = 0;
597         enabledTrackCount = 0;
598         return;
599     }
600 
601     static HashSet<OSType>* allowedTrackTypes = 0;
602     if (!allowedTrackTypes) {
603         allowedTrackTypes = new HashSet<OSType>;
604         allowedTrackTypes->add(VideoMediaType);
605         allowedTrackTypes->add(SoundMediaType);
606         allowedTrackTypes->add(TextMediaType);
607         allowedTrackTypes->add(BaseMediaType);
608         allowedTrackTypes->add(closedCaptionTrackType);
609         allowedTrackTypes->add(subTitleTrackType);
610         allowedTrackTypes->add(mpeg4ObjectDescriptionTrackType);
611         allowedTrackTypes->add(mpeg4SceneDescriptionTrackType);
612         allowedTrackTypes->add(TimeCodeMediaType);
613         allowedTrackTypes->add(TimeCode64MediaType);
614     }
615 
616     long trackCount = GetMovieTrackCount(m_private->m_movie);
617     enabledTrackCount = trackCount;
618     totalTrackCount = trackCount;
619 
620     // Track indexes are 1-based. yuck. These things must descend from old-
621     // school mac resources or something.
622     for (long trackIndex = 1; trackIndex <= trackCount; trackIndex++) {
623         // Grab the track at the current index. If there isn't one there, then
624         // we can move onto the next one.
625         Track currentTrack = GetMovieIndTrack(m_private->m_movie, trackIndex);
626         if (!currentTrack)
627             continue;
628 
629         // Check to see if the track is disabled already, we should move along.
630         // We don't need to re-disable it.
631         if (!GetTrackEnabled(currentTrack))
632             continue;
633 
634         // Grab the track's media. We're going to check to see if we need to
635         // disable the tracks. They could be unsupported.
636         Media trackMedia = GetTrackMedia(currentTrack);
637         if (!trackMedia)
638             continue;
639 
640         // Grab the media type for this track. Make sure that we don't
641         // get an error in doing so. If we do, then something really funky is
642         // wrong.
643         OSType mediaType;
644         GetMediaHandlerDescription(trackMedia, &mediaType, nil, nil);
645         OSErr mediaErr = GetMoviesError();
646         if (mediaErr != noErr)
647             continue;
648 
649         if (!allowedTrackTypes->contains(mediaType)) {
650 
651             // Different mpeg variants import as different track types so check for the "mpeg
652             // characteristic" instead of hard coding the (current) list of mpeg media types.
653             if (GetMovieIndTrackType(m_private->m_movie, 1, 'mpeg', movieTrackCharacteristic | movieTrackEnabledOnly))
654                 continue;
655 
656             SetTrackEnabled(currentTrack, false);
657             --enabledTrackCount;
658         }
659 
660         // Grab the track reference count for chapters. This will tell us if it
661         // has chapter tracks in it. If there aren't any references, then we
662         // can move on the next track.
663         long referenceCount = GetTrackReferenceCount(currentTrack, kTrackReferenceChapterList);
664         if (referenceCount <= 0)
665             continue;
666 
667         long referenceIndex = 0;
668         while (1) {
669             // If we get nothing here, we've overstepped our bounds and can stop
670             // looking. Chapter indices here are 1-based as well - hence, the
671             // pre-increment.
672             referenceIndex++;
673             Track chapterTrack = GetTrackReference(currentTrack, kTrackReferenceChapterList, referenceIndex);
674             if (!chapterTrack)
675                 break;
676 
677             // Try to grab the media for the track.
678             Media chapterMedia = GetTrackMedia(chapterTrack);
679             if (!chapterMedia)
680                 continue;
681 
682             // Grab the media type for this track. Make sure that we don't
683             // get an error in doing so. If we do, then something really
684             // funky is wrong.
685             OSType mediaType;
686             GetMediaHandlerDescription(chapterMedia, &mediaType, nil, nil);
687             OSErr mediaErr = GetMoviesError();
688             if (mediaErr != noErr)
689                 continue;
690 
691             // Check to see if the track is a video track. We don't care about
692             // other non-video tracks.
693             if (mediaType != VideoMediaType)
694                 continue;
695 
696             // Check to see if the track is already disabled. If it is, we
697             // should move along.
698             if (!GetTrackEnabled(chapterTrack))
699                 continue;
700 
701             // Disabled the evil, evil track.
702             SetTrackEnabled(chapterTrack, false);
703             --enabledTrackCount;
704         }
705     }
706 }
707 
isDisabled() const708 bool QTMovie::isDisabled() const
709 {
710     return m_private->m_disabled;
711 }
712 
setDisabled(bool b)713 void QTMovie::setDisabled(bool b)
714 {
715     m_private->m_disabled = b;
716 }
717 
718 
hasVideo() const719 bool QTMovie::hasVideo() const
720 {
721     if (!m_private->m_movie)
722         return false;
723     return (GetMovieIndTrackType(m_private->m_movie, 1, VisualMediaCharacteristic, movieTrackCharacteristic | movieTrackEnabledOnly));
724 }
725 
hasAudio() const726 bool QTMovie::hasAudio() const
727 {
728     if (!m_private->m_movie)
729         return false;
730     return (GetMovieIndTrackType(m_private->m_movie, 1, AudioMediaCharacteristic, movieTrackCharacteristic | movieTrackEnabledOnly));
731 }
732 
videoTracks() const733 QTTrackArray QTMovie::videoTracks() const
734 {
735     QTTrackArray tracks;
736     long trackIndex = 1;
737 
738     while (Track theTrack = GetMovieIndTrackType(m_private->m_movie, trackIndex++, VisualMediaCharacteristic, movieTrackCharacteristic | movieTrackEnabledOnly))
739         tracks.append(QTTrack::create(theTrack));
740 
741     return tracks;
742 }
743 
hasClosedCaptions() const744 bool QTMovie::hasClosedCaptions() const
745 {
746     if (!m_private->m_movie)
747         return false;
748     return GetMovieIndTrackType(m_private->m_movie, 1, closedCaptionTrackType, movieTrackMediaType);
749 }
750 
setClosedCaptionsVisible(bool visible)751 void QTMovie::setClosedCaptionsVisible(bool visible)
752 {
753     if (!m_private->m_movie)
754         return;
755 
756     Track ccTrack = GetMovieIndTrackType(m_private->m_movie, 1, closedCaptionTrackType, movieTrackMediaType);
757     if (!ccTrack)
758         return;
759 
760     Boolean doDisplay = visible;
761     QTSetTrackProperty(ccTrack, closedCaptionTrackType, closedCaptionDisplayPropertyID, sizeof(doDisplay), &doDisplay);
762 }
763 
timeScale() const764 long QTMovie::timeScale() const
765 {
766     if (!m_private->m_movie)
767         return 0;
768 
769     return GetMovieTimeScale(m_private->m_movie);
770 }
771 
772 static void getMIMETypeCallBack(const char* type);
773 
initializeSupportedTypes()774 static void initializeSupportedTypes()
775 {
776     if (gSupportedTypes)
777         return;
778 
779     gSupportedTypes = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
780     if (quickTimeVersion < minimumQuickTimeVersion) {
781         LOG_ERROR("QuickTime version %x detected, at least %x required. Returning empty list of supported media MIME types.", quickTimeVersion, minimumQuickTimeVersion);
782         return;
783     }
784 
785     // QuickTime doesn't have an importer for video/quicktime. Add it manually.
786     CFArrayAppendValue(gSupportedTypes, CFSTR("video/quicktime"));
787 
788     wkGetQuickTimeMIMETypeList(getMIMETypeCallBack);
789 }
790 
getMIMETypeCallBack(const char * type)791 static void getMIMETypeCallBack(const char* type)
792 {
793     ASSERT(type);
794     CFStringRef cfType = CFStringCreateWithCString(kCFAllocatorDefault, type, kCFStringEncodingMacRoman);
795     if (!cfType)
796         return;
797 
798     // Filter out all non-audio or -video MIME Types, and only add each type once:
799     if (CFStringHasPrefix(cfType, CFSTR("audio/")) || CFStringHasPrefix(cfType, CFSTR("video/"))) {
800         CFRange range = CFRangeMake(0, CFArrayGetCount(gSupportedTypes));
801         if (!CFArrayContainsValue(gSupportedTypes, range, cfType))
802             CFArrayAppendValue(gSupportedTypes, cfType);
803     }
804 
805     CFRelease(cfType);
806 }
807 
countSupportedTypes()808 unsigned QTMovie::countSupportedTypes()
809 {
810     initializeSupportedTypes();
811     return static_cast<unsigned>(CFArrayGetCount(gSupportedTypes));
812 }
813 
getSupportedType(unsigned index,const UChar * & str,unsigned & len)814 void QTMovie::getSupportedType(unsigned index, const UChar*& str, unsigned& len)
815 {
816     initializeSupportedTypes();
817     ASSERT(index < CFArrayGetCount(gSupportedTypes));
818 
819     // Allocate sufficient buffer to hold any MIME type
820     static UniChar* staticBuffer = 0;
821     if (!staticBuffer)
822         staticBuffer = new UniChar[32];
823 
824     CFStringRef cfstr = (CFStringRef)CFArrayGetValueAtIndex(gSupportedTypes, index);
825     len = CFStringGetLength(cfstr);
826     CFRange range = { 0, len };
827     CFStringGetCharacters(cfstr, range, staticBuffer);
828     str = reinterpret_cast<const UChar*>(staticBuffer);
829 
830 }
831 
getTransform() const832 CGAffineTransform QTMovie::getTransform() const
833 {
834     ASSERT(m_private->m_movie);
835     MatrixRecord m = {0};
836     GetMovieMatrix(m_private->m_movie, &m);
837 
838     ASSERT(!m.matrix[0][2]);
839     ASSERT(!m.matrix[1][2]);
840     CGAffineTransform transform = CGAffineTransformMake(
841         Fix2X(m.matrix[0][0]),
842         Fix2X(m.matrix[0][1]),
843         Fix2X(m.matrix[1][0]),
844         Fix2X(m.matrix[1][1]),
845         Fix2X(m.matrix[2][0]),
846         Fix2X(m.matrix[2][1]));
847     return transform;
848 }
849 
setTransform(CGAffineTransform t)850 void QTMovie::setTransform(CGAffineTransform t)
851 {
852     ASSERT(m_private->m_movie);
853     MatrixRecord m = {{
854         {X2Fix(t.a), X2Fix(t.b), 0},
855         {X2Fix(t.c), X2Fix(t.d), 0},
856         {X2Fix(t.tx), X2Fix(t.ty), fract1},
857     }};
858 
859     SetMovieMatrix(m_private->m_movie, &m);
860     m_private->cacheMovieScale();
861 }
862 
resetTransform()863 void QTMovie::resetTransform()
864 {
865     ASSERT(m_private->m_movie);
866     SetMovieMatrix(m_private->m_movie, 0);
867     m_private->cacheMovieScale();
868 }
869 
setPrivateBrowsingMode(bool privateBrowsing)870 void QTMovie::setPrivateBrowsingMode(bool privateBrowsing)
871 {
872     m_private->m_privateBrowsing = privateBrowsing;
873     if (m_private->m_movie) {
874         bool allowCaching = !m_private->m_privateBrowsing;
875         QTSetMovieProperty(m_private->m_movie, 'cach', 'pers', sizeof(allowCaching), &allowCaching);
876     }
877 }
878 
initializeQuickTime()879 bool QTMovie::initializeQuickTime()
880 {
881     static bool initialized = false;
882     static bool initializationSucceeded = false;
883     if (!initialized) {
884         initialized = true;
885         // Initialize and check QuickTime version
886         OSErr result = InitializeQTML(kInitializeQTMLEnableDoubleBufferedSurface);
887         if (result == noErr)
888             result = Gestalt(gestaltQuickTime, &quickTimeVersion);
889         if (result != noErr) {
890             LOG_ERROR("No QuickTime available. Disabling <video> and <audio> support.");
891             return false;
892         }
893         if (quickTimeVersion < minimumQuickTimeVersion) {
894             LOG_ERROR("QuickTime version %x detected, at least %x required. Disabling <video> and <audio> support.", quickTimeVersion, minimumQuickTimeVersion);
895             return false;
896         }
897         EnterMovies();
898         initializationSucceeded = true;
899     }
900     return initializationSucceeded;
901 }
902 
getMovieHandle() const903 Movie QTMovie::getMovieHandle() const
904 {
905     return m_private->m_movie;
906 }
907 
DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpvReserved)908 BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
909 {
910     switch (fdwReason) {
911     case DLL_PROCESS_ATTACH:
912         return TRUE;
913     case DLL_PROCESS_DETACH:
914     case DLL_THREAD_ATTACH:
915     case DLL_THREAD_DETACH:
916         return FALSE;
917     }
918     ASSERT_NOT_REACHED();
919     return FALSE;
920 }
921