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 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 bool m_privateBrowsing; 100#if !ASSERT_DISABLED 101 bool m_scaleCached; 102#endif 103}; 104 105QTMoviePrivate::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 132QTMoviePrivate::~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 143void QTMoviePrivate::startTask() 144{ 145 if (!m_tasking) { 146 QTMovieTask::sharedTask()->addTaskClient(this); 147 m_tasking = true; 148 } 149 QTMovieTask::sharedTask()->updateTaskTimer(); 150} 151 152void QTMoviePrivate::endTask() 153{ 154 if (m_tasking) { 155 QTMovieTask::sharedTask()->removeTaskClient(this); 156 m_tasking = false; 157 } 158 QTMovieTask::sharedTask()->updateTaskTimer(); 159} 160 161void 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 232void 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 253void 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 273QTMovie::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 282QTMovie::~QTMovie() 283{ 284 delete m_private; 285} 286 287void 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 297void QTMovie::addClient(QTMovieClient* client) 298{ 299 if (client) 300 m_private->m_clients.append(client); 301} 302 303void 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 310void 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 321void 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 332float QTMovie::rate() const 333{ 334 if (!m_private->m_movie) 335 return 0; 336 return FixedToFloat(GetMovieRate(m_private->m_movie)); 337} 338 339void 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 352float 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 361float 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 370void 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 387void 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 394void 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 413unsigned 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 420float 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 430long QTMovie::loadState() const 431{ 432 return m_private->m_loadState; 433} 434 435void 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 445void 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 if (cfURL) 453 CFRelease(cfURL); 454 if (urlStringRef) 455 CFRelease(urlStringRef); 456} 457 458void QTMovie::load(const UChar* url, int len, bool preservesPitch) 459{ 460 CFStringRef urlStringRef = CFStringCreateWithCharacters(kCFAllocatorDefault, reinterpret_cast<const UniChar*>(url), len); 461 CFURLRef cfURL = CFURLCreateWithString(kCFAllocatorDefault, urlStringRef, 0); 462 463 load(cfURL, preservesPitch); 464 465 if (cfURL) 466 CFRelease(cfURL); 467 if (urlStringRef) 468 CFRelease(urlStringRef); 469} 470 471void QTMovie::load(CFURLRef url, bool preservesPitch) 472{ 473 if (!url) 474 return; 475 476 if (m_private->m_movie) { 477 m_private->endTask(); 478 if (m_private->m_movieController) 479 DisposeMovieController(m_private->m_movieController); 480 m_private->m_movieController = 0; 481 DisposeMovie(m_private->m_movie); 482 m_private->m_movie = 0; 483 m_private->m_loadState = 0; 484 } 485 486 // Define a property array for NewMovieFromProperties. 487 QTNewMoviePropertyElement movieProps[9]; 488 ItemCount moviePropCount = 0; 489 490 bool boolTrue = true; 491 492 // Disable streaming support for now. 493 CFStringRef scheme = CFURLCopyScheme(url); 494 bool isRTSP = CFStringHasPrefix(scheme, CFSTR("rtsp:")); 495 CFRelease(scheme); 496 497 if (isRTSP) { 498 m_private->m_loadError = noMovieFound; 499 goto end; 500 } 501 502 if (m_private->m_currentURL) { 503 if (m_private->m_currentURL != url) { 504 CFRelease(m_private->m_currentURL); 505 m_private->m_currentURL = url; 506 CFRetain(url); 507 } 508 } else { 509 m_private->m_currentURL = url; 510 CFRetain(url); 511 } 512 513 // Add the movie data location to the property array 514 movieProps[moviePropCount].propClass = kQTPropertyClass_DataLocation; 515 movieProps[moviePropCount].propID = kQTDataLocationPropertyID_CFURL; 516 movieProps[moviePropCount].propValueSize = sizeof(m_private->m_currentURL); 517 movieProps[moviePropCount].propValueAddress = &(m_private->m_currentURL); 518 movieProps[moviePropCount].propStatus = 0; 519 moviePropCount++; 520 521 movieProps[moviePropCount].propClass = kQTPropertyClass_MovieInstantiation; 522 movieProps[moviePropCount].propID = kQTMovieInstantiationPropertyID_DontAskUnresolvedDataRefs; 523 movieProps[moviePropCount].propValueSize = sizeof(boolTrue); 524 movieProps[moviePropCount].propValueAddress = &boolTrue; 525 movieProps[moviePropCount].propStatus = 0; 526 moviePropCount++; 527 528 movieProps[moviePropCount].propClass = kQTPropertyClass_MovieInstantiation; 529 movieProps[moviePropCount].propID = kQTMovieInstantiationPropertyID_AsyncOK; 530 movieProps[moviePropCount].propValueSize = sizeof(boolTrue); 531 movieProps[moviePropCount].propValueAddress = &boolTrue; 532 movieProps[moviePropCount].propStatus = 0; 533 moviePropCount++; 534 535 movieProps[moviePropCount].propClass = kQTPropertyClass_NewMovieProperty; 536 movieProps[moviePropCount].propID = kQTNewMoviePropertyID_Active; 537 movieProps[moviePropCount].propValueSize = sizeof(boolTrue); 538 movieProps[moviePropCount].propValueAddress = &boolTrue; 539 movieProps[moviePropCount].propStatus = 0; 540 moviePropCount++; 541 542 movieProps[moviePropCount].propClass = kQTPropertyClass_NewMovieProperty; 543 movieProps[moviePropCount].propID = kQTNewMoviePropertyID_DontInteractWithUser; 544 movieProps[moviePropCount].propValueSize = sizeof(boolTrue); 545 movieProps[moviePropCount].propValueAddress = &boolTrue; 546 movieProps[moviePropCount].propStatus = 0; 547 moviePropCount++; 548 549 movieProps[moviePropCount].propClass = kQTPropertyClass_MovieInstantiation; 550 movieProps[moviePropCount].propID = '!url'; 551 movieProps[moviePropCount].propValueSize = sizeof(boolTrue); 552 movieProps[moviePropCount].propValueAddress = &boolTrue; 553 movieProps[moviePropCount].propStatus = 0; 554 moviePropCount++; 555 556 movieProps[moviePropCount].propClass = kQTPropertyClass_MovieInstantiation; 557 movieProps[moviePropCount].propID = 'site'; 558 movieProps[moviePropCount].propValueSize = sizeof(boolTrue); 559 movieProps[moviePropCount].propValueAddress = &boolTrue; 560 movieProps[moviePropCount].propStatus = 0; 561 moviePropCount++; 562 563 movieProps[moviePropCount].propClass = kQTPropertyClass_Audio; 564 movieProps[moviePropCount].propID = kQTAudioPropertyID_RateChangesPreservePitch; 565 movieProps[moviePropCount].propValueSize = sizeof(preservesPitch); 566 movieProps[moviePropCount].propValueAddress = &preservesPitch; 567 movieProps[moviePropCount].propStatus = 0; 568 moviePropCount++; 569 570 bool allowCaching = !m_private->m_privateBrowsing; 571 movieProps[moviePropCount].propClass = kQTPropertyClass_MovieInstantiation; 572 movieProps[moviePropCount].propID = 'pers'; 573 movieProps[moviePropCount].propValueSize = sizeof(allowCaching); 574 movieProps[moviePropCount].propValueAddress = &allowCaching; 575 movieProps[moviePropCount].propStatus = 0; 576 moviePropCount++; 577 578 ASSERT(moviePropCount <= WTF_ARRAY_LENGTH(movieProps)); 579 m_private->m_loadError = NewMovieFromProperties(moviePropCount, movieProps, 0, 0, &m_private->m_movie); 580 581end: 582 m_private->startTask(); 583 // get the load fail callback quickly 584 if (m_private->m_loadError) 585 QTMovieTask::sharedTask()->updateTaskTimer(0); 586 else { 587 OSType mode = kQTApertureMode_CleanAperture; 588 589 // Set the aperture mode property on a movie to signal that we want aspect ratio 590 // and clean aperture dimensions. Don't worry about errors, we can't do anything if 591 // the installed version of QT doesn't support it and it isn't serious enough to 592 // warrant failing. 593 QTSetMovieProperty(m_private->m_movie, kQTPropertyClass_Visual, kQTVisualPropertyID_ApertureMode, sizeof(mode), &mode); 594 } 595} 596 597void QTMovie::disableUnsupportedTracks(unsigned& enabledTrackCount, unsigned& totalTrackCount) 598{ 599 if (!m_private->m_movie) { 600 totalTrackCount = 0; 601 enabledTrackCount = 0; 602 return; 603 } 604 605 static HashSet<OSType>* allowedTrackTypes = 0; 606 if (!allowedTrackTypes) { 607 allowedTrackTypes = new HashSet<OSType>; 608 allowedTrackTypes->add(VideoMediaType); 609 allowedTrackTypes->add(SoundMediaType); 610 allowedTrackTypes->add(TextMediaType); 611 allowedTrackTypes->add(BaseMediaType); 612 allowedTrackTypes->add(closedCaptionTrackType); 613 allowedTrackTypes->add(subTitleTrackType); 614 allowedTrackTypes->add(mpeg4ObjectDescriptionTrackType); 615 allowedTrackTypes->add(mpeg4SceneDescriptionTrackType); 616 allowedTrackTypes->add(TimeCodeMediaType); 617 allowedTrackTypes->add(TimeCode64MediaType); 618 } 619 620 long trackCount = GetMovieTrackCount(m_private->m_movie); 621 enabledTrackCount = trackCount; 622 totalTrackCount = trackCount; 623 624 // Track indexes are 1-based. yuck. These things must descend from old- 625 // school mac resources or something. 626 for (long trackIndex = 1; trackIndex <= trackCount; trackIndex++) { 627 // Grab the track at the current index. If there isn't one there, then 628 // we can move onto the next one. 629 Track currentTrack = GetMovieIndTrack(m_private->m_movie, trackIndex); 630 if (!currentTrack) 631 continue; 632 633 // Check to see if the track is disabled already, we should move along. 634 // We don't need to re-disable it. 635 if (!GetTrackEnabled(currentTrack)) 636 continue; 637 638 // Grab the track's media. We're going to check to see if we need to 639 // disable the tracks. They could be unsupported. 640 Media trackMedia = GetTrackMedia(currentTrack); 641 if (!trackMedia) 642 continue; 643 644 // Grab the media type for this track. Make sure that we don't 645 // get an error in doing so. If we do, then something really funky is 646 // wrong. 647 OSType mediaType; 648 GetMediaHandlerDescription(trackMedia, &mediaType, nil, nil); 649 OSErr mediaErr = GetMoviesError(); 650 if (mediaErr != noErr) 651 continue; 652 653 if (!allowedTrackTypes->contains(mediaType)) { 654 655 // Different mpeg variants import as different track types so check for the "mpeg 656 // characteristic" instead of hard coding the (current) list of mpeg media types. 657 if (GetMovieIndTrackType(m_private->m_movie, 1, 'mpeg', movieTrackCharacteristic | movieTrackEnabledOnly)) 658 continue; 659 660 SetTrackEnabled(currentTrack, false); 661 --enabledTrackCount; 662 } 663 664 // Grab the track reference count for chapters. This will tell us if it 665 // has chapter tracks in it. If there aren't any references, then we 666 // can move on the next track. 667 long referenceCount = GetTrackReferenceCount(currentTrack, kTrackReferenceChapterList); 668 if (referenceCount <= 0) 669 continue; 670 671 long referenceIndex = 0; 672 while (1) { 673 // If we get nothing here, we've overstepped our bounds and can stop 674 // looking. Chapter indices here are 1-based as well - hence, the 675 // pre-increment. 676 referenceIndex++; 677 Track chapterTrack = GetTrackReference(currentTrack, kTrackReferenceChapterList, referenceIndex); 678 if (!chapterTrack) 679 break; 680 681 // Try to grab the media for the track. 682 Media chapterMedia = GetTrackMedia(chapterTrack); 683 if (!chapterMedia) 684 continue; 685 686 // Grab the media type for this track. Make sure that we don't 687 // get an error in doing so. If we do, then something really 688 // funky is wrong. 689 OSType mediaType; 690 GetMediaHandlerDescription(chapterMedia, &mediaType, nil, nil); 691 OSErr mediaErr = GetMoviesError(); 692 if (mediaErr != noErr) 693 continue; 694 695 // Check to see if the track is a video track. We don't care about 696 // other non-video tracks. 697 if (mediaType != VideoMediaType) 698 continue; 699 700 // Check to see if the track is already disabled. If it is, we 701 // should move along. 702 if (!GetTrackEnabled(chapterTrack)) 703 continue; 704 705 // Disabled the evil, evil track. 706 SetTrackEnabled(chapterTrack, false); 707 --enabledTrackCount; 708 } 709 } 710} 711 712bool QTMovie::isDisabled() const 713{ 714 return m_private->m_disabled; 715} 716 717void QTMovie::setDisabled(bool b) 718{ 719 m_private->m_disabled = b; 720} 721 722 723bool QTMovie::hasVideo() const 724{ 725 if (!m_private->m_movie) 726 return false; 727 return (GetMovieIndTrackType(m_private->m_movie, 1, VisualMediaCharacteristic, movieTrackCharacteristic | movieTrackEnabledOnly)); 728} 729 730bool QTMovie::hasAudio() const 731{ 732 if (!m_private->m_movie) 733 return false; 734 return (GetMovieIndTrackType(m_private->m_movie, 1, AudioMediaCharacteristic, movieTrackCharacteristic | movieTrackEnabledOnly)); 735} 736 737QTTrackArray QTMovie::videoTracks() const 738{ 739 QTTrackArray tracks; 740 long trackIndex = 1; 741 742 while (Track theTrack = GetMovieIndTrackType(m_private->m_movie, trackIndex++, VisualMediaCharacteristic, movieTrackCharacteristic | movieTrackEnabledOnly)) 743 tracks.append(QTTrack::create(theTrack)); 744 745 return tracks; 746} 747 748bool QTMovie::hasClosedCaptions() const 749{ 750 if (!m_private->m_movie) 751 return false; 752 return GetMovieIndTrackType(m_private->m_movie, 1, closedCaptionTrackType, movieTrackMediaType); 753} 754 755void QTMovie::setClosedCaptionsVisible(bool visible) 756{ 757 if (!m_private->m_movie) 758 return; 759 760 Track ccTrack = GetMovieIndTrackType(m_private->m_movie, 1, closedCaptionTrackType, movieTrackMediaType); 761 if (!ccTrack) 762 return; 763 764 Boolean doDisplay = visible; 765 QTSetTrackProperty(ccTrack, closedCaptionTrackType, closedCaptionDisplayPropertyID, sizeof(doDisplay), &doDisplay); 766} 767 768long QTMovie::timeScale() const 769{ 770 if (!m_private->m_movie) 771 return 0; 772 773 return GetMovieTimeScale(m_private->m_movie); 774} 775 776static void getMIMETypeCallBack(const char* type); 777 778static void initializeSupportedTypes() 779{ 780 if (gSupportedTypes) 781 return; 782 783 gSupportedTypes = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); 784 if (quickTimeVersion < minimumQuickTimeVersion) { 785 LOG_ERROR("QuickTime version %x detected, at least %x required. Returning empty list of supported media MIME types.", quickTimeVersion, minimumQuickTimeVersion); 786 return; 787 } 788 789 // QuickTime doesn't have an importer for video/quicktime. Add it manually. 790 CFArrayAppendValue(gSupportedTypes, CFSTR("video/quicktime")); 791 792 wkGetQuickTimeMIMETypeList(getMIMETypeCallBack); 793} 794 795static void getMIMETypeCallBack(const char* type) 796{ 797 ASSERT(type); 798 CFStringRef cfType = CFStringCreateWithCString(kCFAllocatorDefault, type, kCFStringEncodingMacRoman); 799 if (!cfType) 800 return; 801 802 // Filter out all non-audio or -video MIME Types, and only add each type once: 803 if (CFStringHasPrefix(cfType, CFSTR("audio/")) || CFStringHasPrefix(cfType, CFSTR("video/"))) { 804 CFRange range = CFRangeMake(0, CFArrayGetCount(gSupportedTypes)); 805 if (!CFArrayContainsValue(gSupportedTypes, range, cfType)) 806 CFArrayAppendValue(gSupportedTypes, cfType); 807 } 808 809 CFRelease(cfType); 810} 811 812unsigned QTMovie::countSupportedTypes() 813{ 814 initializeSupportedTypes(); 815 return static_cast<unsigned>(CFArrayGetCount(gSupportedTypes)); 816} 817 818void QTMovie::getSupportedType(unsigned index, const UChar*& str, unsigned& len) 819{ 820 initializeSupportedTypes(); 821 ASSERT(index < CFArrayGetCount(gSupportedTypes)); 822 823 // Allocate sufficient buffer to hold any MIME type 824 static UniChar* staticBuffer = 0; 825 if (!staticBuffer) 826 staticBuffer = new UniChar[32]; 827 828 CFStringRef cfstr = (CFStringRef)CFArrayGetValueAtIndex(gSupportedTypes, index); 829 len = CFStringGetLength(cfstr); 830 CFRange range = { 0, len }; 831 CFStringGetCharacters(cfstr, range, staticBuffer); 832 str = reinterpret_cast<const UChar*>(staticBuffer); 833 834} 835 836CGAffineTransform QTMovie::getTransform() const 837{ 838 ASSERT(m_private->m_movie); 839 MatrixRecord m = {0}; 840 GetMovieMatrix(m_private->m_movie, &m); 841 842 ASSERT(!m.matrix[0][2]); 843 ASSERT(!m.matrix[1][2]); 844 CGAffineTransform transform = CGAffineTransformMake( 845 Fix2X(m.matrix[0][0]), 846 Fix2X(m.matrix[0][1]), 847 Fix2X(m.matrix[1][0]), 848 Fix2X(m.matrix[1][1]), 849 Fix2X(m.matrix[2][0]), 850 Fix2X(m.matrix[2][1])); 851 return transform; 852} 853 854void QTMovie::setTransform(CGAffineTransform t) 855{ 856 ASSERT(m_private->m_movie); 857 MatrixRecord m = {{ 858 {X2Fix(t.a), X2Fix(t.b), 0}, 859 {X2Fix(t.c), X2Fix(t.d), 0}, 860 {X2Fix(t.tx), X2Fix(t.ty), fract1}, 861 }}; 862 863 SetMovieMatrix(m_private->m_movie, &m); 864 m_private->cacheMovieScale(); 865} 866 867void QTMovie::resetTransform() 868{ 869 ASSERT(m_private->m_movie); 870 SetMovieMatrix(m_private->m_movie, 0); 871 m_private->cacheMovieScale(); 872} 873 874void QTMovie::setPrivateBrowsingMode(bool privateBrowsing) 875{ 876 m_private->m_privateBrowsing = privateBrowsing; 877 if (m_private->m_movie) { 878 bool allowCaching = !m_private->m_privateBrowsing; 879 QTSetMovieProperty(m_private->m_movie, 'cach', 'pers', sizeof(allowCaching), &allowCaching); 880 } 881} 882 883bool QTMovie::initializeQuickTime() 884{ 885 static bool initialized = false; 886 static bool initializationSucceeded = false; 887 if (!initialized) { 888 initialized = true; 889 // Initialize and check QuickTime version 890 OSErr result = InitializeQTML(kInitializeQTMLEnableDoubleBufferedSurface); 891 if (result == noErr) 892 result = Gestalt(gestaltQuickTime, &quickTimeVersion); 893 if (result != noErr) { 894 LOG_ERROR("No QuickTime available. Disabling <video> and <audio> support."); 895 return false; 896 } 897 if (quickTimeVersion < minimumQuickTimeVersion) { 898 LOG_ERROR("QuickTime version %x detected, at least %x required. Disabling <video> and <audio> support.", quickTimeVersion, minimumQuickTimeVersion); 899 return false; 900 } 901 EnterMovies(); 902 initializationSucceeded = true; 903 } 904 return initializationSucceeded; 905} 906 907Movie QTMovie::getMovieHandle() const 908{ 909 return m_private->m_movie; 910} 911 912BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) 913{ 914 switch (fdwReason) { 915 case DLL_PROCESS_ATTACH: 916 return TRUE; 917 case DLL_PROCESS_DETACH: 918 case DLL_THREAD_ATTACH: 919 case DLL_THREAD_DETACH: 920 return FALSE; 921 } 922 ASSERT_NOT_REACHED(); 923 return FALSE; 924} 925