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