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 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 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 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 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 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 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 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 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 282 QTMovie::~QTMovie() 283 { 284 delete m_private; 285 } 286 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 297 void QTMovie::addClient(QTMovieClient* client) 298 { 299 if (client) 300 m_private->m_clients.append(client); 301 } 302 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 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 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 332 float QTMovie::rate() const 333 { 334 if (!m_private->m_movie) 335 return 0; 336 return FixedToFloat(GetMovieRate(m_private->m_movie)); 337 } 338 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 352 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 361 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 370 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 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 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 413 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 420 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 430 long QTMovie::loadState() const 431 { 432 return m_private->m_loadState; 433 } 434 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 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 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 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 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 708 bool QTMovie::isDisabled() const 709 { 710 return m_private->m_disabled; 711 } 712 713 void QTMovie::setDisabled(bool b) 714 { 715 m_private->m_disabled = b; 716 } 717 718 719 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 726 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 733 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 744 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 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 764 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 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 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 808 unsigned QTMovie::countSupportedTypes() 809 { 810 initializeSupportedTypes(); 811 return static_cast<unsigned>(CFArrayGetCount(gSupportedTypes)); 812 } 813 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 832 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 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 863 void QTMovie::resetTransform() 864 { 865 ASSERT(m_private->m_movie); 866 SetMovieMatrix(m_private->m_movie, 0); 867 m_private->cacheMovieScale(); 868 } 869 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 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 903 Movie QTMovie::getMovieHandle() const 904 { 905 return m_private->m_movie; 906 } 907 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