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