1 /*
2 * Copyright (C) 2007, 2008, 2009, 2010, 2011 Apple, Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25 #include "config.h"
26
27 #include "QTMovie.h"
28
29 #include "QTMovieTask.h"
30 #include "QTMovieWinTimer.h"
31 #include <FixMath.h>
32 #include <GXMath.h>
33 #include <Movies.h>
34 #include <QTML.h>
35 #include <QuickTimeComponents.h>
36 #include <WebKitSystemInterface/WebKitSystemInterface.h>
37 #include <wtf/Assertions.h>
38 #include <wtf/MathExtras.h>
39 #include <wtf/Noncopyable.h>
40 #include <wtf/Vector.h>
41
42 using namespace std;
43
44 static const long minimumQuickTimeVersion = 0x07300000; // 7.3
45
46 static const long closedCaptionTrackType = 'clcp';
47 static const long subTitleTrackType = 'sbtl';
48 static const long mpeg4ObjectDescriptionTrackType = 'odsm';
49 static const long mpeg4SceneDescriptionTrackType = 'sdsm';
50 static const long closedCaptionDisplayPropertyID = 'disp';
51
52 // Resizing GWorlds is slow, give them a minimum size so size of small
53 // videos can be animated smoothly
54 static const int cGWorldMinWidth = 640;
55 static const int cGWorldMinHeight = 360;
56
57 static const float cNonContinuousTimeChange = 0.2f;
58
59 union UppParam {
60 long longValue;
61 void* ptr;
62 };
63
64 static CFMutableArrayRef gSupportedTypes = 0;
65 static SInt32 quickTimeVersion = 0;
66
67 class QTMoviePrivate : public QTMovieTaskClient {
68 WTF_MAKE_NONCOPYABLE(QTMoviePrivate);
69 public:
70 QTMoviePrivate();
71 ~QTMoviePrivate();
72 void task();
73 void startTask();
74 void endTask();
75
76 void createMovieController();
77 void cacheMovieScale();
78
79 QTMovie* m_movieWin;
80 Movie m_movie;
81 MovieController m_movieController;
82 bool m_tasking;
83 bool m_disabled;
84 Vector<QTMovieClient*> m_clients;
85 long m_loadState;
86 bool m_ended;
87 bool m_seeking;
88 float m_lastMediaTime;
89 double m_lastLoadStateCheckTime;
90 int m_width;
91 int m_height;
92 bool m_visible;
93 long m_loadError;
94 float m_widthScaleFactor;
95 float m_heightScaleFactor;
96 CFURLRef m_currentURL;
97 float m_timeToRestore;
98 float m_rateToRestore;
99 bool m_privateBrowsing;
100 #if !ASSERT_DISABLED
101 bool m_scaleCached;
102 #endif
103 };
104
QTMoviePrivate()105 QTMoviePrivate::QTMoviePrivate()
106 : m_movieWin(0)
107 , m_movie(0)
108 , m_movieController(0)
109 , m_tasking(false)
110 , m_loadState(0)
111 , m_ended(false)
112 , m_seeking(false)
113 , m_lastMediaTime(0)
114 , m_lastLoadStateCheckTime(0)
115 , m_width(0)
116 , m_height(0)
117 , m_visible(false)
118 , m_loadError(0)
119 , m_widthScaleFactor(1)
120 , m_heightScaleFactor(1)
121 , m_currentURL(0)
122 , m_timeToRestore(-1.0f)
123 , m_rateToRestore(-1.0f)
124 , m_disabled(false)
125 , m_privateBrowsing(false)
126 #if !ASSERT_DISABLED
127 , m_scaleCached(false)
128 #endif
129 {
130 }
131
~QTMoviePrivate()132 QTMoviePrivate::~QTMoviePrivate()
133 {
134 endTask();
135 if (m_movieController)
136 DisposeMovieController(m_movieController);
137 if (m_movie)
138 DisposeMovie(m_movie);
139 if (m_currentURL)
140 CFRelease(m_currentURL);
141 }
142
startTask()143 void QTMoviePrivate::startTask()
144 {
145 if (!m_tasking) {
146 QTMovieTask::sharedTask()->addTaskClient(this);
147 m_tasking = true;
148 }
149 QTMovieTask::sharedTask()->updateTaskTimer();
150 }
151
endTask()152 void QTMoviePrivate::endTask()
153 {
154 if (m_tasking) {
155 QTMovieTask::sharedTask()->removeTaskClient(this);
156 m_tasking = false;
157 }
158 QTMovieTask::sharedTask()->updateTaskTimer();
159 }
160
task()161 void QTMoviePrivate::task()
162 {
163 ASSERT(m_tasking);
164
165 if (!m_loadError) {
166 if (m_movieController)
167 MCIdle(m_movieController);
168 else
169 MoviesTask(m_movie, 0);
170 }
171
172 // GetMovieLoadState documentation says that you should not call it more often than every quarter of a second.
173 if (systemTime() >= m_lastLoadStateCheckTime + 0.25 || m_loadError) {
174 // If load fails QT's load state is QTMovieLoadStateComplete.
175 // This is different from QTKit API and seems strange.
176 long loadState = m_loadError ? QTMovieLoadStateError : GetMovieLoadState(m_movie);
177 if (loadState != m_loadState) {
178 // we only need to erase the movie gworld when the load state changes to loaded while it
179 // is visible as the gworld is destroyed/created when visibility changes
180 bool shouldRestorePlaybackState = false;
181 bool movieNewlyPlayable = loadState >= QTMovieLoadStateLoaded && m_loadState < QTMovieLoadStateLoaded;
182 m_loadState = loadState;
183 if (movieNewlyPlayable) {
184 cacheMovieScale();
185 shouldRestorePlaybackState = true;
186 }
187
188 if (!m_movieController && m_loadState >= QTMovieLoadStateLoaded)
189 createMovieController();
190
191 for (size_t i = 0; i < m_clients.size(); ++i)
192 m_clients[i]->movieLoadStateChanged(m_movieWin);
193
194 if (shouldRestorePlaybackState && m_timeToRestore != -1.0f) {
195 m_movieWin->setCurrentTime(m_timeToRestore);
196 m_timeToRestore = -1.0f;
197 m_movieWin->setRate(m_rateToRestore);
198 m_rateToRestore = -1.0f;
199 }
200
201 if (m_disabled) {
202 endTask();
203 return;
204 }
205 }
206 m_lastLoadStateCheckTime = systemTime();
207 }
208
209 bool ended = !!IsMovieDone(m_movie);
210 if (ended != m_ended) {
211 m_ended = ended;
212 if (ended) {
213 for (size_t i = 0; i < m_clients.size(); ++i)
214 m_clients[i]->movieEnded(m_movieWin);
215 }
216 }
217
218 float time = m_movieWin->currentTime();
219 if (time < m_lastMediaTime || time >= m_lastMediaTime + cNonContinuousTimeChange || m_seeking) {
220 m_seeking = false;
221 for (size_t i = 0; i < m_clients.size(); ++i)
222 m_clients[i]->movieTimeChanged(m_movieWin);
223 }
224 m_lastMediaTime = time;
225
226 if (m_loadError)
227 endTask();
228 else
229 QTMovieTask::sharedTask()->updateTaskTimer();
230 }
231
createMovieController()232 void QTMoviePrivate::createMovieController()
233 {
234 Rect bounds;
235 long flags;
236
237 if (!m_movie)
238 return;
239
240 if (m_movieController)
241 DisposeMovieController(m_movieController);
242
243 GetMovieBox(m_movie, &bounds);
244 flags = mcTopLeftMovie | mcNotVisible;
245 m_movieController = NewMovieController(m_movie, &bounds, flags);
246 if (!m_movieController)
247 return;
248
249 // Disable automatic looping.
250 MCDoAction(m_movieController, mcActionSetLooping, 0);
251 }
252
cacheMovieScale()253 void QTMoviePrivate::cacheMovieScale()
254 {
255 Rect naturalRect;
256 Rect initialRect;
257
258 GetMovieNaturalBoundsRect(m_movie, &naturalRect);
259 GetMovieBox(m_movie, &initialRect);
260
261 float naturalWidth = naturalRect.right - naturalRect.left;
262 float naturalHeight = naturalRect.bottom - naturalRect.top;
263
264 if (naturalWidth)
265 m_widthScaleFactor = (initialRect.right - initialRect.left) / naturalWidth;
266 if (naturalHeight)
267 m_heightScaleFactor = (initialRect.bottom - initialRect.top) / naturalHeight;
268 #if !ASSERT_DISABLED
269 m_scaleCached = true;
270 #endif
271 }
272
QTMovie(QTMovieClient * client)273 QTMovie::QTMovie(QTMovieClient* client)
274 : m_private(new QTMoviePrivate())
275 {
276 m_private->m_movieWin = this;
277 if (client)
278 m_private->m_clients.append(client);
279 initializeQuickTime();
280 }
281
~QTMovie()282 QTMovie::~QTMovie()
283 {
284 delete m_private;
285 }
286
disableComponent(uint32_t cd[5])287 void QTMovie::disableComponent(uint32_t cd[5])
288 {
289 ComponentDescription nullDesc = {'null', 'base', kAppleManufacturer, 0, 0};
290 Component nullComp = FindNextComponent(0, &nullDesc);
291 Component disabledComp = 0;
292
293 while (disabledComp = FindNextComponent(disabledComp, (ComponentDescription*)&cd[0]))
294 CaptureComponent(disabledComp, nullComp);
295 }
296
addClient(QTMovieClient * client)297 void QTMovie::addClient(QTMovieClient* client)
298 {
299 if (client)
300 m_private->m_clients.append(client);
301 }
302
removeClient(QTMovieClient * client)303 void QTMovie::removeClient(QTMovieClient* client)
304 {
305 size_t indexOfClient = m_private->m_clients.find(client);
306 if (indexOfClient != notFound)
307 m_private->m_clients.remove(indexOfClient);
308 }
309
play()310 void QTMovie::play()
311 {
312 m_private->m_timeToRestore = -1.0f;
313
314 if (m_private->m_movieController)
315 MCDoAction(m_private->m_movieController, mcActionPrerollAndPlay, (void *)GetMoviePreferredRate(m_private->m_movie));
316 else
317 StartMovie(m_private->m_movie);
318 m_private->startTask();
319 }
320
pause()321 void QTMovie::pause()
322 {
323 m_private->m_timeToRestore = -1.0f;
324
325 if (m_private->m_movieController)
326 MCDoAction(m_private->m_movieController, mcActionPlay, 0);
327 else
328 StopMovie(m_private->m_movie);
329 QTMovieTask::sharedTask()->updateTaskTimer();
330 }
331
rate() const332 float QTMovie::rate() const
333 {
334 if (!m_private->m_movie)
335 return 0;
336 return FixedToFloat(GetMovieRate(m_private->m_movie));
337 }
338
setRate(float rate)339 void QTMovie::setRate(float rate)
340 {
341 if (!m_private->m_movie)
342 return;
343 m_private->m_timeToRestore = -1.0f;
344
345 if (m_private->m_movieController)
346 MCDoAction(m_private->m_movieController, mcActionPrerollAndPlay, (void *)FloatToFixed(rate));
347 else
348 SetMovieRate(m_private->m_movie, FloatToFixed(rate));
349 QTMovieTask::sharedTask()->updateTaskTimer();
350 }
351
duration() const352 float QTMovie::duration() const
353 {
354 if (!m_private->m_movie)
355 return 0;
356 TimeValue val = GetMovieDuration(m_private->m_movie);
357 TimeScale scale = GetMovieTimeScale(m_private->m_movie);
358 return static_cast<float>(val) / scale;
359 }
360
currentTime() const361 float QTMovie::currentTime() const
362 {
363 if (!m_private->m_movie)
364 return 0;
365 TimeValue val = GetMovieTime(m_private->m_movie, 0);
366 TimeScale scale = GetMovieTimeScale(m_private->m_movie);
367 return static_cast<float>(val) / scale;
368 }
369
setCurrentTime(float time) const370 void QTMovie::setCurrentTime(float time) const
371 {
372 if (!m_private->m_movie)
373 return;
374
375 m_private->m_timeToRestore = -1.0f;
376
377 m_private->m_seeking = true;
378 TimeScale scale = GetMovieTimeScale(m_private->m_movie);
379 if (m_private->m_movieController) {
380 QTRestartAtTimeRecord restart = { lroundf(time * scale) , 0 };
381 MCDoAction(m_private->m_movieController, mcActionRestartAtTime, (void *)&restart);
382 } else
383 SetMovieTimeValue(m_private->m_movie, TimeValue(lroundf(time * scale)));
384 QTMovieTask::sharedTask()->updateTaskTimer();
385 }
386
setVolume(float volume)387 void QTMovie::setVolume(float volume)
388 {
389 if (!m_private->m_movie)
390 return;
391 SetMovieVolume(m_private->m_movie, static_cast<short>(volume * 256));
392 }
393
setPreservesPitch(bool preservesPitch)394 void QTMovie::setPreservesPitch(bool preservesPitch)
395 {
396 if (!m_private->m_movie || !m_private->m_currentURL)
397 return;
398
399 OSErr error;
400 bool prop = false;
401
402 error = QTGetMovieProperty(m_private->m_movie, kQTPropertyClass_Audio, kQTAudioPropertyID_RateChangesPreservePitch,
403 sizeof(kQTAudioPropertyID_RateChangesPreservePitch), static_cast<QTPropertyValuePtr>(&prop), 0);
404
405 if (error || prop == preservesPitch)
406 return;
407
408 m_private->m_timeToRestore = currentTime();
409 m_private->m_rateToRestore = rate();
410 load(m_private->m_currentURL, preservesPitch);
411 }
412
dataSize() const413 unsigned QTMovie::dataSize() const
414 {
415 if (!m_private->m_movie)
416 return 0;
417 return GetMovieDataSize(m_private->m_movie, 0, GetMovieDuration(m_private->m_movie));
418 }
419
maxTimeLoaded() const420 float QTMovie::maxTimeLoaded() const
421 {
422 if (!m_private->m_movie)
423 return 0;
424 TimeValue val;
425 GetMaxLoadedTimeInMovie(m_private->m_movie, &val);
426 TimeScale scale = GetMovieTimeScale(m_private->m_movie);
427 return static_cast<float>(val) / scale;
428 }
429
loadState() const430 long QTMovie::loadState() const
431 {
432 return m_private->m_loadState;
433 }
434
getNaturalSize(int & width,int & height)435 void QTMovie::getNaturalSize(int& width, int& height)
436 {
437 Rect rect = { 0, };
438
439 if (m_private->m_movie)
440 GetMovieNaturalBoundsRect(m_private->m_movie, &rect);
441 width = (rect.right - rect.left) * m_private->m_widthScaleFactor;
442 height = (rect.bottom - rect.top) * m_private->m_heightScaleFactor;
443 }
444
loadPath(const UChar * url,int len,bool preservesPitch)445 void QTMovie::loadPath(const UChar* url, int len, bool preservesPitch)
446 {
447 CFStringRef urlStringRef = CFStringCreateWithCharacters(kCFAllocatorDefault, reinterpret_cast<const UniChar*>(url), len);
448 CFURLRef cfURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, urlStringRef, kCFURLWindowsPathStyle, false);
449
450 load(cfURL, preservesPitch);
451
452 CFRelease(cfURL);
453 CFRelease(urlStringRef);
454 }
455
load(const UChar * url,int len,bool preservesPitch)456 void QTMovie::load(const UChar* url, int len, bool preservesPitch)
457 {
458 CFStringRef urlStringRef = CFStringCreateWithCharacters(kCFAllocatorDefault, reinterpret_cast<const UniChar*>(url), len);
459 CFURLRef cfURL = CFURLCreateWithString(kCFAllocatorDefault, urlStringRef, 0);
460
461 load(cfURL, preservesPitch);
462
463 CFRelease(cfURL);
464 CFRelease(urlStringRef);
465 }
466
load(CFURLRef url,bool preservesPitch)467 void QTMovie::load(CFURLRef url, bool preservesPitch)
468 {
469 if (!url)
470 return;
471
472 if (m_private->m_movie) {
473 m_private->endTask();
474 if (m_private->m_movieController)
475 DisposeMovieController(m_private->m_movieController);
476 m_private->m_movieController = 0;
477 DisposeMovie(m_private->m_movie);
478 m_private->m_movie = 0;
479 m_private->m_loadState = 0;
480 }
481
482 // Define a property array for NewMovieFromProperties.
483 QTNewMoviePropertyElement movieProps[9];
484 ItemCount moviePropCount = 0;
485
486 bool boolTrue = true;
487
488 // Disable streaming support for now.
489 CFStringRef scheme = CFURLCopyScheme(url);
490 bool isRTSP = CFStringHasPrefix(scheme, CFSTR("rtsp:"));
491 CFRelease(scheme);
492
493 if (isRTSP) {
494 m_private->m_loadError = noMovieFound;
495 goto end;
496 }
497
498 if (m_private->m_currentURL) {
499 if (m_private->m_currentURL != url) {
500 CFRelease(m_private->m_currentURL);
501 m_private->m_currentURL = url;
502 CFRetain(url);
503 }
504 } else {
505 m_private->m_currentURL = url;
506 CFRetain(url);
507 }
508
509 // Add the movie data location to the property array
510 movieProps[moviePropCount].propClass = kQTPropertyClass_DataLocation;
511 movieProps[moviePropCount].propID = kQTDataLocationPropertyID_CFURL;
512 movieProps[moviePropCount].propValueSize = sizeof(m_private->m_currentURL);
513 movieProps[moviePropCount].propValueAddress = &(m_private->m_currentURL);
514 movieProps[moviePropCount].propStatus = 0;
515 moviePropCount++;
516
517 movieProps[moviePropCount].propClass = kQTPropertyClass_MovieInstantiation;
518 movieProps[moviePropCount].propID = kQTMovieInstantiationPropertyID_DontAskUnresolvedDataRefs;
519 movieProps[moviePropCount].propValueSize = sizeof(boolTrue);
520 movieProps[moviePropCount].propValueAddress = &boolTrue;
521 movieProps[moviePropCount].propStatus = 0;
522 moviePropCount++;
523
524 movieProps[moviePropCount].propClass = kQTPropertyClass_MovieInstantiation;
525 movieProps[moviePropCount].propID = kQTMovieInstantiationPropertyID_AsyncOK;
526 movieProps[moviePropCount].propValueSize = sizeof(boolTrue);
527 movieProps[moviePropCount].propValueAddress = &boolTrue;
528 movieProps[moviePropCount].propStatus = 0;
529 moviePropCount++;
530
531 movieProps[moviePropCount].propClass = kQTPropertyClass_NewMovieProperty;
532 movieProps[moviePropCount].propID = kQTNewMoviePropertyID_Active;
533 movieProps[moviePropCount].propValueSize = sizeof(boolTrue);
534 movieProps[moviePropCount].propValueAddress = &boolTrue;
535 movieProps[moviePropCount].propStatus = 0;
536 moviePropCount++;
537
538 movieProps[moviePropCount].propClass = kQTPropertyClass_NewMovieProperty;
539 movieProps[moviePropCount].propID = kQTNewMoviePropertyID_DontInteractWithUser;
540 movieProps[moviePropCount].propValueSize = sizeof(boolTrue);
541 movieProps[moviePropCount].propValueAddress = &boolTrue;
542 movieProps[moviePropCount].propStatus = 0;
543 moviePropCount++;
544
545 movieProps[moviePropCount].propClass = kQTPropertyClass_MovieInstantiation;
546 movieProps[moviePropCount].propID = '!url';
547 movieProps[moviePropCount].propValueSize = sizeof(boolTrue);
548 movieProps[moviePropCount].propValueAddress = &boolTrue;
549 movieProps[moviePropCount].propStatus = 0;
550 moviePropCount++;
551
552 movieProps[moviePropCount].propClass = kQTPropertyClass_MovieInstantiation;
553 movieProps[moviePropCount].propID = 'site';
554 movieProps[moviePropCount].propValueSize = sizeof(boolTrue);
555 movieProps[moviePropCount].propValueAddress = &boolTrue;
556 movieProps[moviePropCount].propStatus = 0;
557 moviePropCount++;
558
559 movieProps[moviePropCount].propClass = kQTPropertyClass_Audio;
560 movieProps[moviePropCount].propID = kQTAudioPropertyID_RateChangesPreservePitch;
561 movieProps[moviePropCount].propValueSize = sizeof(preservesPitch);
562 movieProps[moviePropCount].propValueAddress = &preservesPitch;
563 movieProps[moviePropCount].propStatus = 0;
564 moviePropCount++;
565
566 bool allowCaching = !m_private->m_privateBrowsing;
567 movieProps[moviePropCount].propClass = kQTPropertyClass_MovieInstantiation;
568 movieProps[moviePropCount].propID = 'pers';
569 movieProps[moviePropCount].propValueSize = sizeof(allowCaching);
570 movieProps[moviePropCount].propValueAddress = &allowCaching;
571 movieProps[moviePropCount].propStatus = 0;
572 moviePropCount++;
573
574 ASSERT(moviePropCount <= WTF_ARRAY_LENGTH(movieProps));
575 m_private->m_loadError = NewMovieFromProperties(moviePropCount, movieProps, 0, 0, &m_private->m_movie);
576
577 end:
578 m_private->startTask();
579 // get the load fail callback quickly
580 if (m_private->m_loadError)
581 QTMovieTask::sharedTask()->updateTaskTimer(0);
582 else {
583 OSType mode = kQTApertureMode_CleanAperture;
584
585 // Set the aperture mode property on a movie to signal that we want aspect ratio
586 // and clean aperture dimensions. Don't worry about errors, we can't do anything if
587 // the installed version of QT doesn't support it and it isn't serious enough to
588 // warrant failing.
589 QTSetMovieProperty(m_private->m_movie, kQTPropertyClass_Visual, kQTVisualPropertyID_ApertureMode, sizeof(mode), &mode);
590 }
591 }
592
disableUnsupportedTracks(unsigned & enabledTrackCount,unsigned & totalTrackCount)593 void QTMovie::disableUnsupportedTracks(unsigned& enabledTrackCount, unsigned& totalTrackCount)
594 {
595 if (!m_private->m_movie) {
596 totalTrackCount = 0;
597 enabledTrackCount = 0;
598 return;
599 }
600
601 static HashSet<OSType>* allowedTrackTypes = 0;
602 if (!allowedTrackTypes) {
603 allowedTrackTypes = new HashSet<OSType>;
604 allowedTrackTypes->add(VideoMediaType);
605 allowedTrackTypes->add(SoundMediaType);
606 allowedTrackTypes->add(TextMediaType);
607 allowedTrackTypes->add(BaseMediaType);
608 allowedTrackTypes->add(closedCaptionTrackType);
609 allowedTrackTypes->add(subTitleTrackType);
610 allowedTrackTypes->add(mpeg4ObjectDescriptionTrackType);
611 allowedTrackTypes->add(mpeg4SceneDescriptionTrackType);
612 allowedTrackTypes->add(TimeCodeMediaType);
613 allowedTrackTypes->add(TimeCode64MediaType);
614 }
615
616 long trackCount = GetMovieTrackCount(m_private->m_movie);
617 enabledTrackCount = trackCount;
618 totalTrackCount = trackCount;
619
620 // Track indexes are 1-based. yuck. These things must descend from old-
621 // school mac resources or something.
622 for (long trackIndex = 1; trackIndex <= trackCount; trackIndex++) {
623 // Grab the track at the current index. If there isn't one there, then
624 // we can move onto the next one.
625 Track currentTrack = GetMovieIndTrack(m_private->m_movie, trackIndex);
626 if (!currentTrack)
627 continue;
628
629 // Check to see if the track is disabled already, we should move along.
630 // We don't need to re-disable it.
631 if (!GetTrackEnabled(currentTrack))
632 continue;
633
634 // Grab the track's media. We're going to check to see if we need to
635 // disable the tracks. They could be unsupported.
636 Media trackMedia = GetTrackMedia(currentTrack);
637 if (!trackMedia)
638 continue;
639
640 // Grab the media type for this track. Make sure that we don't
641 // get an error in doing so. If we do, then something really funky is
642 // wrong.
643 OSType mediaType;
644 GetMediaHandlerDescription(trackMedia, &mediaType, nil, nil);
645 OSErr mediaErr = GetMoviesError();
646 if (mediaErr != noErr)
647 continue;
648
649 if (!allowedTrackTypes->contains(mediaType)) {
650
651 // Different mpeg variants import as different track types so check for the "mpeg
652 // characteristic" instead of hard coding the (current) list of mpeg media types.
653 if (GetMovieIndTrackType(m_private->m_movie, 1, 'mpeg', movieTrackCharacteristic | movieTrackEnabledOnly))
654 continue;
655
656 SetTrackEnabled(currentTrack, false);
657 --enabledTrackCount;
658 }
659
660 // Grab the track reference count for chapters. This will tell us if it
661 // has chapter tracks in it. If there aren't any references, then we
662 // can move on the next track.
663 long referenceCount = GetTrackReferenceCount(currentTrack, kTrackReferenceChapterList);
664 if (referenceCount <= 0)
665 continue;
666
667 long referenceIndex = 0;
668 while (1) {
669 // If we get nothing here, we've overstepped our bounds and can stop
670 // looking. Chapter indices here are 1-based as well - hence, the
671 // pre-increment.
672 referenceIndex++;
673 Track chapterTrack = GetTrackReference(currentTrack, kTrackReferenceChapterList, referenceIndex);
674 if (!chapterTrack)
675 break;
676
677 // Try to grab the media for the track.
678 Media chapterMedia = GetTrackMedia(chapterTrack);
679 if (!chapterMedia)
680 continue;
681
682 // Grab the media type for this track. Make sure that we don't
683 // get an error in doing so. If we do, then something really
684 // funky is wrong.
685 OSType mediaType;
686 GetMediaHandlerDescription(chapterMedia, &mediaType, nil, nil);
687 OSErr mediaErr = GetMoviesError();
688 if (mediaErr != noErr)
689 continue;
690
691 // Check to see if the track is a video track. We don't care about
692 // other non-video tracks.
693 if (mediaType != VideoMediaType)
694 continue;
695
696 // Check to see if the track is already disabled. If it is, we
697 // should move along.
698 if (!GetTrackEnabled(chapterTrack))
699 continue;
700
701 // Disabled the evil, evil track.
702 SetTrackEnabled(chapterTrack, false);
703 --enabledTrackCount;
704 }
705 }
706 }
707
isDisabled() const708 bool QTMovie::isDisabled() const
709 {
710 return m_private->m_disabled;
711 }
712
setDisabled(bool b)713 void QTMovie::setDisabled(bool b)
714 {
715 m_private->m_disabled = b;
716 }
717
718
hasVideo() const719 bool QTMovie::hasVideo() const
720 {
721 if (!m_private->m_movie)
722 return false;
723 return (GetMovieIndTrackType(m_private->m_movie, 1, VisualMediaCharacteristic, movieTrackCharacteristic | movieTrackEnabledOnly));
724 }
725
hasAudio() const726 bool QTMovie::hasAudio() const
727 {
728 if (!m_private->m_movie)
729 return false;
730 return (GetMovieIndTrackType(m_private->m_movie, 1, AudioMediaCharacteristic, movieTrackCharacteristic | movieTrackEnabledOnly));
731 }
732
videoTracks() const733 QTTrackArray QTMovie::videoTracks() const
734 {
735 QTTrackArray tracks;
736 long trackIndex = 1;
737
738 while (Track theTrack = GetMovieIndTrackType(m_private->m_movie, trackIndex++, VisualMediaCharacteristic, movieTrackCharacteristic | movieTrackEnabledOnly))
739 tracks.append(QTTrack::create(theTrack));
740
741 return tracks;
742 }
743
hasClosedCaptions() const744 bool QTMovie::hasClosedCaptions() const
745 {
746 if (!m_private->m_movie)
747 return false;
748 return GetMovieIndTrackType(m_private->m_movie, 1, closedCaptionTrackType, movieTrackMediaType);
749 }
750
setClosedCaptionsVisible(bool visible)751 void QTMovie::setClosedCaptionsVisible(bool visible)
752 {
753 if (!m_private->m_movie)
754 return;
755
756 Track ccTrack = GetMovieIndTrackType(m_private->m_movie, 1, closedCaptionTrackType, movieTrackMediaType);
757 if (!ccTrack)
758 return;
759
760 Boolean doDisplay = visible;
761 QTSetTrackProperty(ccTrack, closedCaptionTrackType, closedCaptionDisplayPropertyID, sizeof(doDisplay), &doDisplay);
762 }
763
timeScale() const764 long QTMovie::timeScale() const
765 {
766 if (!m_private->m_movie)
767 return 0;
768
769 return GetMovieTimeScale(m_private->m_movie);
770 }
771
772 static void getMIMETypeCallBack(const char* type);
773
initializeSupportedTypes()774 static void initializeSupportedTypes()
775 {
776 if (gSupportedTypes)
777 return;
778
779 gSupportedTypes = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
780 if (quickTimeVersion < minimumQuickTimeVersion) {
781 LOG_ERROR("QuickTime version %x detected, at least %x required. Returning empty list of supported media MIME types.", quickTimeVersion, minimumQuickTimeVersion);
782 return;
783 }
784
785 // QuickTime doesn't have an importer for video/quicktime. Add it manually.
786 CFArrayAppendValue(gSupportedTypes, CFSTR("video/quicktime"));
787
788 wkGetQuickTimeMIMETypeList(getMIMETypeCallBack);
789 }
790
getMIMETypeCallBack(const char * type)791 static void getMIMETypeCallBack(const char* type)
792 {
793 ASSERT(type);
794 CFStringRef cfType = CFStringCreateWithCString(kCFAllocatorDefault, type, kCFStringEncodingMacRoman);
795 if (!cfType)
796 return;
797
798 // Filter out all non-audio or -video MIME Types, and only add each type once:
799 if (CFStringHasPrefix(cfType, CFSTR("audio/")) || CFStringHasPrefix(cfType, CFSTR("video/"))) {
800 CFRange range = CFRangeMake(0, CFArrayGetCount(gSupportedTypes));
801 if (!CFArrayContainsValue(gSupportedTypes, range, cfType))
802 CFArrayAppendValue(gSupportedTypes, cfType);
803 }
804
805 CFRelease(cfType);
806 }
807
countSupportedTypes()808 unsigned QTMovie::countSupportedTypes()
809 {
810 initializeSupportedTypes();
811 return static_cast<unsigned>(CFArrayGetCount(gSupportedTypes));
812 }
813
getSupportedType(unsigned index,const UChar * & str,unsigned & len)814 void QTMovie::getSupportedType(unsigned index, const UChar*& str, unsigned& len)
815 {
816 initializeSupportedTypes();
817 ASSERT(index < CFArrayGetCount(gSupportedTypes));
818
819 // Allocate sufficient buffer to hold any MIME type
820 static UniChar* staticBuffer = 0;
821 if (!staticBuffer)
822 staticBuffer = new UniChar[32];
823
824 CFStringRef cfstr = (CFStringRef)CFArrayGetValueAtIndex(gSupportedTypes, index);
825 len = CFStringGetLength(cfstr);
826 CFRange range = { 0, len };
827 CFStringGetCharacters(cfstr, range, staticBuffer);
828 str = reinterpret_cast<const UChar*>(staticBuffer);
829
830 }
831
getTransform() const832 CGAffineTransform QTMovie::getTransform() const
833 {
834 ASSERT(m_private->m_movie);
835 MatrixRecord m = {0};
836 GetMovieMatrix(m_private->m_movie, &m);
837
838 ASSERT(!m.matrix[0][2]);
839 ASSERT(!m.matrix[1][2]);
840 CGAffineTransform transform = CGAffineTransformMake(
841 Fix2X(m.matrix[0][0]),
842 Fix2X(m.matrix[0][1]),
843 Fix2X(m.matrix[1][0]),
844 Fix2X(m.matrix[1][1]),
845 Fix2X(m.matrix[2][0]),
846 Fix2X(m.matrix[2][1]));
847 return transform;
848 }
849
setTransform(CGAffineTransform t)850 void QTMovie::setTransform(CGAffineTransform t)
851 {
852 ASSERT(m_private->m_movie);
853 MatrixRecord m = {{
854 {X2Fix(t.a), X2Fix(t.b), 0},
855 {X2Fix(t.c), X2Fix(t.d), 0},
856 {X2Fix(t.tx), X2Fix(t.ty), fract1},
857 }};
858
859 SetMovieMatrix(m_private->m_movie, &m);
860 m_private->cacheMovieScale();
861 }
862
resetTransform()863 void QTMovie::resetTransform()
864 {
865 ASSERT(m_private->m_movie);
866 SetMovieMatrix(m_private->m_movie, 0);
867 m_private->cacheMovieScale();
868 }
869
setPrivateBrowsingMode(bool privateBrowsing)870 void QTMovie::setPrivateBrowsingMode(bool privateBrowsing)
871 {
872 m_private->m_privateBrowsing = privateBrowsing;
873 if (m_private->m_movie) {
874 bool allowCaching = !m_private->m_privateBrowsing;
875 QTSetMovieProperty(m_private->m_movie, 'cach', 'pers', sizeof(allowCaching), &allowCaching);
876 }
877 }
878
initializeQuickTime()879 bool QTMovie::initializeQuickTime()
880 {
881 static bool initialized = false;
882 static bool initializationSucceeded = false;
883 if (!initialized) {
884 initialized = true;
885 // Initialize and check QuickTime version
886 OSErr result = InitializeQTML(kInitializeQTMLEnableDoubleBufferedSurface);
887 if (result == noErr)
888 result = Gestalt(gestaltQuickTime, &quickTimeVersion);
889 if (result != noErr) {
890 LOG_ERROR("No QuickTime available. Disabling <video> and <audio> support.");
891 return false;
892 }
893 if (quickTimeVersion < minimumQuickTimeVersion) {
894 LOG_ERROR("QuickTime version %x detected, at least %x required. Disabling <video> and <audio> support.", quickTimeVersion, minimumQuickTimeVersion);
895 return false;
896 }
897 EnterMovies();
898 initializationSucceeded = true;
899 }
900 return initializationSucceeded;
901 }
902
getMovieHandle() const903 Movie QTMovie::getMovieHandle() const
904 {
905 return m_private->m_movie;
906 }
907
DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpvReserved)908 BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
909 {
910 switch (fdwReason) {
911 case DLL_PROCESS_ATTACH:
912 return TRUE;
913 case DLL_PROCESS_DETACH:
914 case DLL_THREAD_ATTACH:
915 case DLL_THREAD_DETACH:
916 return FALSE;
917 }
918 ASSERT_NOT_REACHED();
919 return FALSE;
920 }
921