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