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