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