1 /* 2 * Copyright (C) 2007, 2008, 2009, 2010 Apple, Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 #include "config.h" 26 27 #include "QTMovieWin.h" 28 29 // Put Movies.h first so build failures here point clearly to QuickTime 30 #include <Movies.h> 31 32 #include "QTMovieWinTimer.h" 33 #include <GXMath.h> 34 #include <QTML.h> 35 #include <QuickTimeComponents.h> 36 #include <wtf/Assertions.h> 37 #include <wtf/HashSet.h> 38 #include <wtf/Noncopyable.h> 39 #include <wtf/Vector.h> 40 41 using namespace std; 42 43 static const long minimumQuickTimeVersion = 0x07300000; // 7.3 44 45 static const long closedCaptionTrackType = 'clcp'; 46 static const long subTitleTrackType = 'sbtl'; 47 static const long mpeg4ObjectDescriptionTrackType = 'odsm'; 48 static const long mpeg4SceneDescriptionTrackType = 'sdsm'; 49 static const long closedCaptionDisplayPropertyID = 'disp'; 50 static LPCTSTR fullscreenQTMovieWinPointerProp = TEXT("fullscreenQTMovieWinPointer"); 51 52 // Resizing GWorlds is slow, give them a minimum size so size of small 53 // videos can be animated smoothly 54 static const int cGWorldMinWidth = 640; 55 static const int cGWorldMinHeight = 360; 56 57 static const float cNonContinuousTimeChange = 0.2f; 58 59 union UppParam { 60 long longValue; 61 void* ptr; 62 }; 63 64 static MovieDrawingCompleteUPP gMovieDrawingCompleteUPP = 0; 65 static HashSet<QTMovieWinPrivate*>* gTaskList; 66 static Vector<CFStringRef>* gSupportedTypes = 0; 67 static SInt32 quickTimeVersion = 0; 68 69 static QTMovieWin::SetTaskTimerDelayFunc gSetTaskTimerDelay = 0; 70 static QTMovieWin::StopTaskTimerFunc gStopTaskTimer = 0; 71 72 static void updateTaskTimer(int maxInterval = 1000) 73 { 74 if (!gTaskList->size()) { 75 gStopTaskTimer(); 76 return; 77 } 78 79 long intervalInMS; 80 QTGetTimeUntilNextTask(&intervalInMS, 1000); 81 if (intervalInMS > maxInterval) 82 intervalInMS = maxInterval; 83 gSetTaskTimerDelay(static_cast<float>(intervalInMS) / 1000); 84 } 85 86 class QTMovieWinPrivate : public Noncopyable { 87 public: 88 QTMovieWinPrivate(); 89 ~QTMovieWinPrivate(); 90 void task(); 91 void startTask(); 92 void endTask(); 93 94 void createMovieController(); 95 void registerDrawingCallback(); 96 void drawingComplete(); 97 void updateGWorld(); 98 void createGWorld(); 99 void deleteGWorld(); 100 void clearGWorld(); 101 void cacheMovieScale(); 102 void updateMovieSize(); 103 104 void setSize(int, int); 105 106 QTMovieWin* m_movieWin; 107 Movie m_movie; 108 MovieController m_movieController; 109 bool m_tasking; 110 QTMovieWinClient* m_client; 111 long m_loadState; 112 bool m_ended; 113 bool m_seeking; 114 float m_lastMediaTime; 115 double m_lastLoadStateCheckTime; 116 int m_width; 117 int m_height; 118 bool m_visible; 119 GWorldPtr m_gWorld; 120 int m_gWorldWidth; 121 int m_gWorldHeight; 122 GWorldPtr m_savedGWorld; 123 long m_loadError; 124 float m_widthScaleFactor; 125 float m_heightScaleFactor; 126 CFURLRef m_currentURL; 127 float m_timeToRestore; 128 float m_rateToRestore; 129 #if !ASSERT_DISABLED 130 bool m_scaleCached; 131 #endif 132 WindowPtr m_fullscreenWindow; 133 GWorldPtr m_fullscreenOrigGWorld; 134 Rect m_fullscreenRect; 135 QTMovieWinFullscreenClient* m_fullscreenClient; 136 char* m_fullscreenRestoreState; 137 }; 138 139 QTMovieWinPrivate::QTMovieWinPrivate() 140 : m_movieWin(0) 141 , m_movie(0) 142 , m_movieController(0) 143 , m_tasking(false) 144 , m_client(0) 145 , m_loadState(0) 146 , m_ended(false) 147 , m_seeking(false) 148 , m_lastMediaTime(0) 149 , m_lastLoadStateCheckTime(0) 150 , m_width(0) 151 , m_height(0) 152 , m_visible(false) 153 , m_gWorld(0) 154 , m_gWorldWidth(0) 155 , m_gWorldHeight(0) 156 , m_savedGWorld(0) 157 , m_loadError(0) 158 , m_widthScaleFactor(1) 159 , m_heightScaleFactor(1) 160 , m_currentURL(0) 161 , m_timeToRestore(-1.0f) 162 , m_rateToRestore(-1.0f) 163 #if !ASSERT_DISABLED 164 , m_scaleCached(false) 165 #endif 166 , m_fullscreenWindow(0) 167 , m_fullscreenOrigGWorld(0) 168 , m_fullscreenClient(0) 169 , m_fullscreenRestoreState(0) 170 { 171 Rect rect = { 0, 0, 0, 0 }; 172 m_fullscreenRect = rect; 173 } 174 175 QTMovieWinPrivate::~QTMovieWinPrivate() 176 { 177 ASSERT(!m_fullscreenWindow); 178 179 endTask(); 180 if (m_gWorld) 181 deleteGWorld(); 182 if (m_movieController) 183 DisposeMovieController(m_movieController); 184 if (m_movie) 185 DisposeMovie(m_movie); 186 if (m_currentURL) 187 CFRelease(m_currentURL); 188 } 189 190 void QTMovieWin::taskTimerFired() 191 { 192 // The hash content might change during task() 193 Vector<QTMovieWinPrivate*> tasks; 194 copyToVector(*gTaskList, tasks); 195 size_t count = tasks.size(); 196 for (unsigned n = 0; n < count; ++n) 197 tasks[n]->task(); 198 199 updateTaskTimer(); 200 } 201 202 void QTMovieWinPrivate::startTask() 203 { 204 if (m_tasking) 205 return; 206 if (!gTaskList) 207 gTaskList = new HashSet<QTMovieWinPrivate*>; 208 gTaskList->add(this); 209 m_tasking = true; 210 updateTaskTimer(); 211 } 212 213 void QTMovieWinPrivate::endTask() 214 { 215 if (!m_tasking) 216 return; 217 gTaskList->remove(this); 218 m_tasking = false; 219 updateTaskTimer(); 220 } 221 222 void QTMovieWinPrivate::cacheMovieScale() 223 { 224 Rect naturalRect; 225 Rect initialRect; 226 227 GetMovieNaturalBoundsRect(m_movie, &naturalRect); 228 GetMovieBox(m_movie, &initialRect); 229 230 float naturalWidth = naturalRect.right - naturalRect.left; 231 float naturalHeight = naturalRect.bottom - naturalRect.top; 232 233 if (naturalWidth) 234 m_widthScaleFactor = (initialRect.right - initialRect.left) / naturalWidth; 235 if (naturalHeight) 236 m_heightScaleFactor = (initialRect.bottom - initialRect.top) / naturalHeight; 237 #if !ASSERT_DISABLED 238 m_scaleCached = true;; 239 #endif 240 } 241 242 void QTMovieWinPrivate::task() 243 { 244 ASSERT(m_tasking); 245 246 if (!m_loadError) { 247 if (m_movieController) 248 MCIdle(m_movieController); 249 else 250 MoviesTask(m_movie, 0); 251 } 252 253 // GetMovieLoadState documentation says that you should not call it more often than every quarter of a second. 254 if (systemTime() >= m_lastLoadStateCheckTime + 0.25 || m_loadError) { 255 // If load fails QT's load state is QTMovieLoadStateComplete. 256 // This is different from QTKit API and seems strange. 257 long loadState = m_loadError ? QTMovieLoadStateError : GetMovieLoadState(m_movie); 258 if (loadState != m_loadState) { 259 260 // we only need to erase the movie gworld when the load state changes to loaded while it 261 // is visible as the gworld is destroyed/created when visibility changes 262 bool shouldRestorePlaybackState = false; 263 bool movieNewlyPlayable = loadState >= QTMovieLoadStateLoaded && m_loadState < QTMovieLoadStateLoaded; 264 m_loadState = loadState; 265 if (movieNewlyPlayable) { 266 cacheMovieScale(); 267 updateMovieSize(); 268 if (m_visible) 269 clearGWorld(); 270 shouldRestorePlaybackState = true; 271 } 272 273 if (!m_movieController && m_loadState >= QTMovieLoadStateLoaded) 274 createMovieController(); 275 m_client->movieLoadStateChanged(m_movieWin); 276 277 if (shouldRestorePlaybackState && m_timeToRestore != -1.0f) { 278 m_movieWin->setCurrentTime(m_timeToRestore); 279 m_timeToRestore = -1.0f; 280 m_movieWin->setRate(m_rateToRestore); 281 m_rateToRestore = -1.0f; 282 } 283 284 if (m_movieWin->m_disabled) { 285 endTask(); 286 return; 287 } 288 } 289 m_lastLoadStateCheckTime = systemTime(); 290 } 291 292 bool ended = !!IsMovieDone(m_movie); 293 if (ended != m_ended) { 294 m_ended = ended; 295 if (m_client && ended) 296 m_client->movieEnded(m_movieWin); 297 } 298 299 float time = m_movieWin->currentTime(); 300 if (time < m_lastMediaTime || time >= m_lastMediaTime + cNonContinuousTimeChange || m_seeking) { 301 m_seeking = false; 302 if (m_client) 303 m_client->movieTimeChanged(m_movieWin); 304 } 305 m_lastMediaTime = time; 306 307 if (m_loadError) 308 endTask(); 309 } 310 311 void QTMovieWinPrivate::createMovieController() 312 { 313 Rect bounds; 314 long flags; 315 316 if (!m_movie) 317 return; 318 319 if (m_movieController) 320 DisposeMovieController(m_movieController); 321 322 GetMovieBox(m_movie, &bounds); 323 flags = mcTopLeftMovie | mcNotVisible; 324 m_movieController = NewMovieController(m_movie, &bounds, flags); 325 if (!m_movieController) 326 return; 327 328 MCSetControllerPort(m_movieController, m_gWorld); 329 MCSetControllerAttached(m_movieController, false); 330 } 331 332 void QTMovieWinPrivate::registerDrawingCallback() 333 { 334 UppParam param; 335 param.ptr = this; 336 SetMovieDrawingCompleteProc(m_movie, movieDrawingCallWhenChanged, gMovieDrawingCompleteUPP, param.longValue); 337 } 338 339 void QTMovieWinPrivate::drawingComplete() 340 { 341 if (!m_gWorld || m_movieWin->m_disabled || m_loadState < QTMovieLoadStateLoaded) 342 return; 343 m_client->movieNewImageAvailable(m_movieWin); 344 } 345 346 void QTMovieWinPrivate::updateGWorld() 347 { 348 bool shouldBeVisible = m_visible; 349 if (!m_height || !m_width) 350 shouldBeVisible = false; 351 352 if (shouldBeVisible && !m_gWorld) 353 createGWorld(); 354 else if (!shouldBeVisible && m_gWorld) 355 deleteGWorld(); 356 else if (m_gWorld && (m_width > m_gWorldWidth || m_height > m_gWorldHeight)) { 357 // need a bigger, better gWorld 358 deleteGWorld(); 359 createGWorld(); 360 } 361 } 362 363 void QTMovieWinPrivate::createGWorld() 364 { 365 ASSERT(!m_gWorld); 366 if (!m_movie || m_loadState < QTMovieLoadStateLoaded) 367 return; 368 369 m_gWorldWidth = max(cGWorldMinWidth, m_width); 370 m_gWorldHeight = max(cGWorldMinHeight, m_height); 371 Rect bounds; 372 bounds.top = 0; 373 bounds.left = 0; 374 bounds.right = m_gWorldWidth; 375 bounds.bottom = m_gWorldHeight; 376 OSErr err = QTNewGWorld(&m_gWorld, k32BGRAPixelFormat, &bounds, 0, 0, 0); 377 if (err) 378 return; 379 GetMovieGWorld(m_movie, &m_savedGWorld, 0); 380 if (m_movieController) 381 MCSetControllerPort(m_movieController, m_gWorld); 382 SetMovieGWorld(m_movie, m_gWorld, 0); 383 bounds.right = m_width; 384 bounds.bottom = m_height; 385 if (m_movieController) 386 MCSetControllerBoundsRect(m_movieController, &bounds); 387 SetMovieBox(m_movie, &bounds); 388 } 389 390 void QTMovieWinPrivate::clearGWorld() 391 { 392 if (!m_movie||!m_gWorld) 393 return; 394 395 GrafPtr savePort; 396 GetPort(&savePort); 397 MacSetPort((GrafPtr)m_gWorld); 398 399 Rect bounds; 400 bounds.top = 0; 401 bounds.left = 0; 402 bounds.right = m_gWorldWidth; 403 bounds.bottom = m_gWorldHeight; 404 EraseRect(&bounds); 405 406 MacSetPort(savePort); 407 } 408 409 void QTMovieWinPrivate::setSize(int width, int height) 410 { 411 if (m_width == width && m_height == height) 412 return; 413 m_width = width; 414 m_height = height; 415 416 // Do not change movie box before reaching load state loaded as we grab 417 // the initial size when task() sees that state for the first time, and 418 // we need the initial size to be able to scale movie properly. 419 if (!m_movie || m_loadState < QTMovieLoadStateLoaded) 420 return; 421 422 #if !ASSERT_DISABLED 423 ASSERT(m_scaleCached); 424 #endif 425 426 updateMovieSize(); 427 } 428 429 void QTMovieWinPrivate::updateMovieSize() 430 { 431 if (!m_movie || m_loadState < QTMovieLoadStateLoaded) 432 return; 433 434 Rect bounds; 435 bounds.top = 0; 436 bounds.left = 0; 437 bounds.right = m_width; 438 bounds.bottom = m_height; 439 if (m_movieController) 440 MCSetControllerBoundsRect(m_movieController, &bounds); 441 SetMovieBox(m_movie, &bounds); 442 updateGWorld(); 443 } 444 445 446 void QTMovieWinPrivate::deleteGWorld() 447 { 448 ASSERT(m_gWorld); 449 if (m_movieController) 450 MCSetControllerPort(m_movieController, m_savedGWorld); 451 if (m_movie) 452 SetMovieGWorld(m_movie, m_savedGWorld, 0); 453 m_savedGWorld = 0; 454 DisposeGWorld(m_gWorld); 455 m_gWorld = 0; 456 m_gWorldWidth = 0; 457 m_gWorldHeight = 0; 458 } 459 460 461 QTMovieWin::QTMovieWin(QTMovieWinClient* client) 462 : m_private(new QTMovieWinPrivate()) 463 , m_disabled(false) 464 { 465 m_private->m_movieWin = this; 466 m_private->m_client = client; 467 initializeQuickTime(); 468 } 469 470 QTMovieWin::~QTMovieWin() 471 { 472 delete m_private; 473 } 474 475 void QTMovieWin::play() 476 { 477 m_private->m_timeToRestore = -1.0f; 478 479 if (m_private->m_movieController) 480 MCDoAction(m_private->m_movieController, mcActionPrerollAndPlay, (void *)GetMoviePreferredRate(m_private->m_movie)); 481 else 482 StartMovie(m_private->m_movie); 483 m_private->startTask(); 484 } 485 486 void QTMovieWin::pause() 487 { 488 m_private->m_timeToRestore = -1.0f; 489 490 if (m_private->m_movieController) 491 MCDoAction(m_private->m_movieController, mcActionPlay, 0); 492 else 493 StopMovie(m_private->m_movie); 494 updateTaskTimer(); 495 } 496 497 float QTMovieWin::rate() const 498 { 499 if (!m_private->m_movie) 500 return 0; 501 return FixedToFloat(GetMovieRate(m_private->m_movie)); 502 } 503 504 void QTMovieWin::setRate(float rate) 505 { 506 if (!m_private->m_movie) 507 return; 508 m_private->m_timeToRestore = -1.0f; 509 510 if (m_private->m_movieController) 511 MCDoAction(m_private->m_movieController, mcActionPrerollAndPlay, (void *)FloatToFixed(rate)); 512 else 513 SetMovieRate(m_private->m_movie, FloatToFixed(rate)); 514 updateTaskTimer(); 515 } 516 517 float QTMovieWin::duration() const 518 { 519 if (!m_private->m_movie) 520 return 0; 521 TimeValue val = GetMovieDuration(m_private->m_movie); 522 TimeScale scale = GetMovieTimeScale(m_private->m_movie); 523 return static_cast<float>(val) / scale; 524 } 525 526 float QTMovieWin::currentTime() const 527 { 528 if (!m_private->m_movie) 529 return 0; 530 TimeValue val = GetMovieTime(m_private->m_movie, 0); 531 TimeScale scale = GetMovieTimeScale(m_private->m_movie); 532 return static_cast<float>(val) / scale; 533 } 534 535 void QTMovieWin::setCurrentTime(float time) const 536 { 537 if (!m_private->m_movie) 538 return; 539 540 m_private->m_timeToRestore = -1.0f; 541 542 m_private->m_seeking = true; 543 TimeScale scale = GetMovieTimeScale(m_private->m_movie); 544 if (m_private->m_movieController){ 545 QTRestartAtTimeRecord restart = { time * scale , 0 }; 546 MCDoAction(m_private->m_movieController, mcActionRestartAtTime, (void *)&restart); 547 } else 548 SetMovieTimeValue(m_private->m_movie, TimeValue(time * scale)); 549 updateTaskTimer(); 550 } 551 552 void QTMovieWin::setVolume(float volume) 553 { 554 if (!m_private->m_movie) 555 return; 556 SetMovieVolume(m_private->m_movie, static_cast<short>(volume * 256)); 557 } 558 559 void QTMovieWin::setPreservesPitch(bool preservesPitch) 560 { 561 if (!m_private->m_movie || !m_private->m_currentURL) 562 return; 563 564 OSErr error; 565 bool prop = false; 566 567 error = QTGetMovieProperty(m_private->m_movie, kQTPropertyClass_Audio, kQTAudioPropertyID_RateChangesPreservePitch, 568 sizeof(kQTAudioPropertyID_RateChangesPreservePitch), static_cast<QTPropertyValuePtr>(&prop), 0); 569 570 if (error || prop == preservesPitch) 571 return; 572 573 m_private->m_timeToRestore = currentTime(); 574 m_private->m_rateToRestore = rate(); 575 load(m_private->m_currentURL, preservesPitch); 576 } 577 578 unsigned QTMovieWin::dataSize() const 579 { 580 if (!m_private->m_movie) 581 return 0; 582 return GetMovieDataSize(m_private->m_movie, 0, GetMovieDuration(m_private->m_movie)); 583 } 584 585 float QTMovieWin::maxTimeLoaded() const 586 { 587 if (!m_private->m_movie) 588 return 0; 589 TimeValue val; 590 GetMaxLoadedTimeInMovie(m_private->m_movie, &val); 591 TimeScale scale = GetMovieTimeScale(m_private->m_movie); 592 return static_cast<float>(val) / scale; 593 } 594 595 long QTMovieWin::loadState() const 596 { 597 return m_private->m_loadState; 598 } 599 600 void QTMovieWin::getNaturalSize(int& width, int& height) 601 { 602 Rect rect = { 0, }; 603 604 if (m_private->m_movie) 605 GetMovieNaturalBoundsRect(m_private->m_movie, &rect); 606 width = (rect.right - rect.left) * m_private->m_widthScaleFactor; 607 height = (rect.bottom - rect.top) * m_private->m_heightScaleFactor; 608 } 609 610 void QTMovieWin::setSize(int width, int height) 611 { 612 m_private->setSize(width, height); 613 updateTaskTimer(0); 614 } 615 616 void QTMovieWin::setVisible(bool b) 617 { 618 m_private->m_visible = b; 619 m_private->updateGWorld(); 620 } 621 622 void QTMovieWin::getCurrentFrameInfo(void*& buffer, unsigned& bitsPerPixel, unsigned& rowBytes, unsigned& width, unsigned& height) 623 { 624 if (!m_private->m_gWorld) { 625 buffer = 0; 626 bitsPerPixel = 0; 627 rowBytes = 0; 628 width = 0; 629 height = 0; 630 return; 631 } 632 PixMapHandle offscreenPixMap = GetGWorldPixMap(m_private->m_gWorld); 633 buffer = (*offscreenPixMap)->baseAddr; 634 bitsPerPixel = (*offscreenPixMap)->pixelSize; 635 rowBytes = (*offscreenPixMap)->rowBytes & 0x3FFF; 636 width = m_private->m_width; 637 height = m_private->m_height; 638 } 639 640 void QTMovieWin::paint(HDC hdc, int x, int y) 641 { 642 if (!m_private->m_gWorld) 643 return; 644 645 HDC hdcSrc = static_cast<HDC>(GetPortHDC(reinterpret_cast<GrafPtr>(m_private->m_gWorld))); 646 if (!hdcSrc) 647 return; 648 649 // FIXME: If we could determine the movie has no alpha, we could use BitBlt for those cases, which might be faster. 650 BLENDFUNCTION blendFunction; 651 blendFunction.BlendOp = AC_SRC_OVER; 652 blendFunction.BlendFlags = 0; 653 blendFunction.SourceConstantAlpha = 255; 654 blendFunction.AlphaFormat = AC_SRC_ALPHA; 655 AlphaBlend(hdc, x, y, m_private->m_width, m_private->m_height, hdcSrc, 656 0, 0, m_private->m_width, m_private->m_height, blendFunction); 657 } 658 659 void QTMovieWin::load(const UChar* url, int len, bool preservesPitch) 660 { 661 CFStringRef urlStringRef = CFStringCreateWithCharacters(kCFAllocatorDefault, reinterpret_cast<const UniChar*>(url), len); 662 CFURLRef cfURL = CFURLCreateWithString(kCFAllocatorDefault, urlStringRef, 0); 663 664 load(cfURL, preservesPitch); 665 666 CFRelease(cfURL); 667 CFRelease(urlStringRef); 668 } 669 670 void QTMovieWin::load(CFURLRef url, bool preservesPitch) 671 { 672 if (!url) 673 return; 674 675 if (m_private->m_movie) { 676 m_private->endTask(); 677 if (m_private->m_gWorld) 678 m_private->deleteGWorld(); 679 if (m_private->m_movieController) 680 DisposeMovieController(m_private->m_movieController); 681 m_private->m_movieController = 0; 682 DisposeMovie(m_private->m_movie); 683 m_private->m_movie = 0; 684 m_private->m_loadState = 0; 685 } 686 687 // Define a property array for NewMovieFromProperties. 8 should be enough for our needs. 688 QTNewMoviePropertyElement movieProps[8]; 689 ItemCount moviePropCount = 0; 690 691 bool boolTrue = true; 692 693 // Disable streaming support for now. 694 CFStringRef scheme = CFURLCopyScheme(url); 695 bool isRTSP = CFStringHasPrefix(scheme, CFSTR("rtsp:")); 696 CFRelease(scheme); 697 698 if (isRTSP) { 699 m_private->m_loadError = noMovieFound; 700 goto end; 701 } 702 703 if (m_private->m_currentURL) { 704 if (m_private->m_currentURL != url) { 705 CFRelease(m_private->m_currentURL); 706 m_private->m_currentURL = url; 707 CFRetain(url); 708 } 709 } else { 710 m_private->m_currentURL = url; 711 CFRetain(url); 712 } 713 714 // Add the movie data location to the property array 715 movieProps[moviePropCount].propClass = kQTPropertyClass_DataLocation; 716 movieProps[moviePropCount].propID = kQTDataLocationPropertyID_CFURL; 717 movieProps[moviePropCount].propValueSize = sizeof(m_private->m_currentURL); 718 movieProps[moviePropCount].propValueAddress = &(m_private->m_currentURL); 719 movieProps[moviePropCount].propStatus = 0; 720 moviePropCount++; 721 722 movieProps[moviePropCount].propClass = kQTPropertyClass_MovieInstantiation; 723 movieProps[moviePropCount].propID = kQTMovieInstantiationPropertyID_DontAskUnresolvedDataRefs; 724 movieProps[moviePropCount].propValueSize = sizeof(boolTrue); 725 movieProps[moviePropCount].propValueAddress = &boolTrue; 726 movieProps[moviePropCount].propStatus = 0; 727 moviePropCount++; 728 729 movieProps[moviePropCount].propClass = kQTPropertyClass_MovieInstantiation; 730 movieProps[moviePropCount].propID = kQTMovieInstantiationPropertyID_AsyncOK; 731 movieProps[moviePropCount].propValueSize = sizeof(boolTrue); 732 movieProps[moviePropCount].propValueAddress = &boolTrue; 733 movieProps[moviePropCount].propStatus = 0; 734 moviePropCount++; 735 736 movieProps[moviePropCount].propClass = kQTPropertyClass_NewMovieProperty; 737 movieProps[moviePropCount].propID = kQTNewMoviePropertyID_Active; 738 movieProps[moviePropCount].propValueSize = sizeof(boolTrue); 739 movieProps[moviePropCount].propValueAddress = &boolTrue; 740 movieProps[moviePropCount].propStatus = 0; 741 moviePropCount++; 742 743 movieProps[moviePropCount].propClass = kQTPropertyClass_NewMovieProperty; 744 movieProps[moviePropCount].propID = kQTNewMoviePropertyID_DontInteractWithUser; 745 movieProps[moviePropCount].propValueSize = sizeof(boolTrue); 746 movieProps[moviePropCount].propValueAddress = &boolTrue; 747 movieProps[moviePropCount].propStatus = 0; 748 moviePropCount++; 749 750 movieProps[moviePropCount].propClass = kQTPropertyClass_MovieInstantiation; 751 movieProps[moviePropCount].propID = '!url'; 752 movieProps[moviePropCount].propValueSize = sizeof(boolTrue); 753 movieProps[moviePropCount].propValueAddress = &boolTrue; 754 movieProps[moviePropCount].propStatus = 0; 755 moviePropCount++; 756 757 movieProps[moviePropCount].propClass = kQTPropertyClass_MovieInstantiation; 758 movieProps[moviePropCount].propID = 'site'; 759 movieProps[moviePropCount].propValueSize = sizeof(boolTrue); 760 movieProps[moviePropCount].propValueAddress = &boolTrue; 761 movieProps[moviePropCount].propStatus = 0; 762 moviePropCount++; 763 764 movieProps[moviePropCount].propClass = kQTPropertyClass_Audio; 765 movieProps[moviePropCount].propID = kQTAudioPropertyID_RateChangesPreservePitch; 766 movieProps[moviePropCount].propValueSize = sizeof(preservesPitch); 767 movieProps[moviePropCount].propValueAddress = &preservesPitch; 768 movieProps[moviePropCount].propStatus = 0; 769 moviePropCount++; 770 771 ASSERT(moviePropCount <= sizeof(movieProps)/sizeof(movieProps[0])); 772 m_private->m_loadError = NewMovieFromProperties(moviePropCount, movieProps, 0, 0, &m_private->m_movie); 773 774 end: 775 m_private->startTask(); 776 // get the load fail callback quickly 777 if (m_private->m_loadError) 778 updateTaskTimer(0); 779 else { 780 OSType mode = kQTApertureMode_CleanAperture; 781 782 // Set the aperture mode property on a movie to signal that we want aspect ratio 783 // and clean aperture dimensions. Don't worry about errors, we can't do anything if 784 // the installed version of QT doesn't support it and it isn't serious enough to 785 // warrant failing. 786 QTSetMovieProperty(m_private->m_movie, kQTPropertyClass_Visual, kQTVisualPropertyID_ApertureMode, sizeof(mode), &mode); 787 m_private->registerDrawingCallback(); 788 } 789 } 790 791 void QTMovieWin::disableUnsupportedTracks(unsigned& enabledTrackCount, unsigned& totalTrackCount) 792 { 793 if (!m_private->m_movie) { 794 totalTrackCount = 0; 795 enabledTrackCount = 0; 796 return; 797 } 798 799 static HashSet<OSType>* allowedTrackTypes = 0; 800 if (!allowedTrackTypes) { 801 allowedTrackTypes = new HashSet<OSType>; 802 allowedTrackTypes->add(VideoMediaType); 803 allowedTrackTypes->add(SoundMediaType); 804 allowedTrackTypes->add(TextMediaType); 805 allowedTrackTypes->add(BaseMediaType); 806 allowedTrackTypes->add(closedCaptionTrackType); 807 allowedTrackTypes->add(subTitleTrackType); 808 allowedTrackTypes->add(mpeg4ObjectDescriptionTrackType); 809 allowedTrackTypes->add(mpeg4SceneDescriptionTrackType); 810 allowedTrackTypes->add(TimeCodeMediaType); 811 allowedTrackTypes->add(TimeCode64MediaType); 812 } 813 814 long trackCount = GetMovieTrackCount(m_private->m_movie); 815 enabledTrackCount = trackCount; 816 totalTrackCount = trackCount; 817 818 // Track indexes are 1-based. yuck. These things must descend from old- 819 // school mac resources or something. 820 for (long trackIndex = 1; trackIndex <= trackCount; trackIndex++) { 821 // Grab the track at the current index. If there isn't one there, then 822 // we can move onto the next one. 823 Track currentTrack = GetMovieIndTrack(m_private->m_movie, trackIndex); 824 if (!currentTrack) 825 continue; 826 827 // Check to see if the track is disabled already, we should move along. 828 // We don't need to re-disable it. 829 if (!GetTrackEnabled(currentTrack)) 830 continue; 831 832 // Grab the track's media. We're going to check to see if we need to 833 // disable the tracks. They could be unsupported. 834 Media trackMedia = GetTrackMedia(currentTrack); 835 if (!trackMedia) 836 continue; 837 838 // Grab the media type for this track. Make sure that we don't 839 // get an error in doing so. If we do, then something really funky is 840 // wrong. 841 OSType mediaType; 842 GetMediaHandlerDescription(trackMedia, &mediaType, nil, nil); 843 OSErr mediaErr = GetMoviesError(); 844 if (mediaErr != noErr) 845 continue; 846 847 if (!allowedTrackTypes->contains(mediaType)) { 848 849 // Different mpeg variants import as different track types so check for the "mpeg 850 // characteristic" instead of hard coding the (current) list of mpeg media types. 851 if (GetMovieIndTrackType(m_private->m_movie, 1, 'mpeg', movieTrackCharacteristic | movieTrackEnabledOnly)) 852 continue; 853 854 SetTrackEnabled(currentTrack, false); 855 --enabledTrackCount; 856 } 857 858 // Grab the track reference count for chapters. This will tell us if it 859 // has chapter tracks in it. If there aren't any references, then we 860 // can move on the next track. 861 long referenceCount = GetTrackReferenceCount(currentTrack, kTrackReferenceChapterList); 862 if (referenceCount <= 0) 863 continue; 864 865 long referenceIndex = 0; 866 while (1) { 867 // If we get nothing here, we've overstepped our bounds and can stop 868 // looking. Chapter indices here are 1-based as well - hence, the 869 // pre-increment. 870 referenceIndex++; 871 Track chapterTrack = GetTrackReference(currentTrack, kTrackReferenceChapterList, referenceIndex); 872 if (!chapterTrack) 873 break; 874 875 // Try to grab the media for the track. 876 Media chapterMedia = GetTrackMedia(chapterTrack); 877 if (!chapterMedia) 878 continue; 879 880 // Grab the media type for this track. Make sure that we don't 881 // get an error in doing so. If we do, then something really 882 // funky is wrong. 883 OSType mediaType; 884 GetMediaHandlerDescription(chapterMedia, &mediaType, nil, nil); 885 OSErr mediaErr = GetMoviesError(); 886 if (mediaErr != noErr) 887 continue; 888 889 // Check to see if the track is a video track. We don't care about 890 // other non-video tracks. 891 if (mediaType != VideoMediaType) 892 continue; 893 894 // Check to see if the track is already disabled. If it is, we 895 // should move along. 896 if (!GetTrackEnabled(chapterTrack)) 897 continue; 898 899 // Disabled the evil, evil track. 900 SetTrackEnabled(chapterTrack, false); 901 --enabledTrackCount; 902 } 903 } 904 } 905 906 void QTMovieWin::setDisabled(bool b) 907 { 908 m_disabled = b; 909 } 910 911 912 bool QTMovieWin::hasVideo() const 913 { 914 if (!m_private->m_movie) 915 return false; 916 return (GetMovieIndTrackType(m_private->m_movie, 1, VisualMediaCharacteristic, movieTrackCharacteristic | movieTrackEnabledOnly)); 917 } 918 919 bool QTMovieWin::hasAudio() const 920 { 921 if (!m_private->m_movie) 922 return false; 923 return (GetMovieIndTrackType(m_private->m_movie, 1, AudioMediaCharacteristic, movieTrackCharacteristic | movieTrackEnabledOnly)); 924 } 925 926 927 bool QTMovieWin::hasClosedCaptions() const 928 { 929 if (!m_private->m_movie) 930 return false; 931 return GetMovieIndTrackType(m_private->m_movie, 1, closedCaptionTrackType, movieTrackMediaType); 932 } 933 934 void QTMovieWin::setClosedCaptionsVisible(bool visible) 935 { 936 if (!m_private->m_movie) 937 return; 938 939 Track ccTrack = GetMovieIndTrackType(m_private->m_movie, 1, closedCaptionTrackType, movieTrackMediaType); 940 if (!ccTrack) 941 return; 942 943 Boolean doDisplay = visible; 944 QTSetTrackProperty(ccTrack, closedCaptionTrackType, closedCaptionDisplayPropertyID, sizeof(doDisplay), &doDisplay); 945 } 946 947 pascal OSErr movieDrawingCompleteProc(Movie movie, long data) 948 { 949 UppParam param; 950 param.longValue = data; 951 QTMovieWinPrivate* mp = static_cast<QTMovieWinPrivate*>(param.ptr); 952 if (mp) 953 mp->drawingComplete(); 954 return 0; 955 } 956 957 static void initializeSupportedTypes() 958 { 959 if (gSupportedTypes) 960 return; 961 962 gSupportedTypes = new Vector<CFStringRef>; 963 if (quickTimeVersion < minimumQuickTimeVersion) { 964 LOG_ERROR("QuickTime version %x detected, at least %x required. Returning empty list of supported media MIME types.", quickTimeVersion, minimumQuickTimeVersion); 965 return; 966 } 967 968 // QuickTime doesn't have an importer for video/quicktime. Add it manually. 969 gSupportedTypes->append(CFSTR("video/quicktime")); 970 971 for (int index = 0; index < 2; index++) { 972 ComponentDescription findCD; 973 974 // look at all movie importers that can import in place and are installed. 975 findCD.componentType = MovieImportType; 976 findCD.componentSubType = 0; 977 findCD.componentManufacturer = 0; 978 findCD.componentFlagsMask = cmpIsMissing | movieImportSubTypeIsFileExtension | canMovieImportInPlace | dontAutoFileMovieImport; 979 980 // look at those registered by HFS file types the first time through, by file extension the second time 981 findCD.componentFlags = canMovieImportInPlace | (index ? movieImportSubTypeIsFileExtension : 0); 982 983 long componentCount = CountComponents(&findCD); 984 if (!componentCount) 985 continue; 986 987 Component comp = 0; 988 while (comp = FindNextComponent(comp, &findCD)) { 989 // Does this component have a MIME type container? 990 ComponentDescription infoCD; 991 OSErr err = GetComponentInfo(comp, &infoCD, nil /*name*/, nil /*info*/, nil /*icon*/); 992 if (err) 993 continue; 994 if (!(infoCD.componentFlags & hasMovieImportMIMEList)) 995 continue; 996 QTAtomContainer mimeList = 0; 997 err = MovieImportGetMIMETypeList((ComponentInstance)comp, &mimeList); 998 if (err || !mimeList) 999 continue; 1000 1001 // Grab every type from the container. 1002 QTLockContainer(mimeList); 1003 int typeCount = QTCountChildrenOfType(mimeList, kParentAtomIsContainer, kMimeInfoMimeTypeTag); 1004 for (int typeIndex = 1; typeIndex <= typeCount; typeIndex++) { 1005 QTAtom mimeTag = QTFindChildByIndex(mimeList, 0, kMimeInfoMimeTypeTag, typeIndex, 0); 1006 if (!mimeTag) 1007 continue; 1008 char* atomData; 1009 long typeLength; 1010 if (noErr != QTGetAtomDataPtr(mimeList, mimeTag, &typeLength, &atomData)) 1011 continue; 1012 1013 char typeBuffer[256]; 1014 if (typeLength >= sizeof(typeBuffer)) 1015 continue; 1016 memcpy(typeBuffer, atomData, typeLength); 1017 typeBuffer[typeLength] = 0; 1018 1019 // Only add "audio/..." and "video/..." types. 1020 if (strncmp(typeBuffer, "audio/", 6) && strncmp(typeBuffer, "video/", 6)) 1021 continue; 1022 1023 CFStringRef cfMimeType = CFStringCreateWithCString(0, typeBuffer, kCFStringEncodingUTF8); 1024 if (!cfMimeType) 1025 continue; 1026 1027 // Only add each type once. 1028 bool alreadyAdded = false; 1029 for (int addedIndex = 0; addedIndex < gSupportedTypes->size(); addedIndex++) { 1030 CFStringRef type = gSupportedTypes->at(addedIndex); 1031 if (kCFCompareEqualTo == CFStringCompare(cfMimeType, type, kCFCompareCaseInsensitive)) { 1032 alreadyAdded = true; 1033 break; 1034 } 1035 } 1036 if (!alreadyAdded) 1037 gSupportedTypes->append(cfMimeType); 1038 else 1039 CFRelease(cfMimeType); 1040 } 1041 DisposeHandle(mimeList); 1042 } 1043 } 1044 } 1045 1046 unsigned QTMovieWin::countSupportedTypes() 1047 { 1048 initializeSupportedTypes(); 1049 return static_cast<unsigned>(gSupportedTypes->size()); 1050 } 1051 1052 void QTMovieWin::getSupportedType(unsigned index, const UChar*& str, unsigned& len) 1053 { 1054 initializeSupportedTypes(); 1055 ASSERT(index < gSupportedTypes->size()); 1056 1057 // Allocate sufficient buffer to hold any MIME type 1058 static UniChar* staticBuffer = 0; 1059 if (!staticBuffer) 1060 staticBuffer = new UniChar[32]; 1061 1062 CFStringRef cfstr = gSupportedTypes->at(index); 1063 len = CFStringGetLength(cfstr); 1064 CFRange range = { 0, len }; 1065 CFStringGetCharacters(cfstr, range, staticBuffer); 1066 str = reinterpret_cast<const UChar*>(staticBuffer); 1067 1068 } 1069 1070 void QTMovieWin::setTaskTimerFuncs(SetTaskTimerDelayFunc setTaskTimerDelay, StopTaskTimerFunc stopTaskTimer) 1071 { 1072 gSetTaskTimerDelay = setTaskTimerDelay; 1073 gStopTaskTimer = stopTaskTimer; 1074 } 1075 1076 bool QTMovieWin::initializeQuickTime() 1077 { 1078 static bool initialized = false; 1079 static bool initializationSucceeded = false; 1080 if (!initialized) { 1081 initialized = true; 1082 // Initialize and check QuickTime version 1083 OSErr result = InitializeQTML(kInitializeQTMLEnableDoubleBufferedSurface); 1084 if (result == noErr) 1085 result = Gestalt(gestaltQuickTime, &quickTimeVersion); 1086 if (result != noErr) { 1087 LOG_ERROR("No QuickTime available. Disabling <video> and <audio> support."); 1088 return false; 1089 } 1090 if (quickTimeVersion < minimumQuickTimeVersion) { 1091 LOG_ERROR("QuickTime version %x detected, at least %x required. Disabling <video> and <audio> support.", quickTimeVersion, minimumQuickTimeVersion); 1092 return false; 1093 } 1094 EnterMovies(); 1095 gMovieDrawingCompleteUPP = NewMovieDrawingCompleteUPP(movieDrawingCompleteProc); 1096 initializationSucceeded = true; 1097 } 1098 return initializationSucceeded; 1099 } 1100 1101 LRESULT QTMovieWin::fullscreenWndProc(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam) 1102 { 1103 QTMovieWin* movie = static_cast<QTMovieWin*>(GetProp(wnd, fullscreenQTMovieWinPointerProp)); 1104 1105 if (message == WM_DESTROY) 1106 RemoveProp(wnd, fullscreenQTMovieWinPointerProp); 1107 1108 if (!movie) 1109 return DefWindowProc(wnd, message, wParam, lParam); 1110 1111 return movie->m_private->m_fullscreenClient->fullscreenClientWndProc(wnd, message, wParam, lParam); 1112 } 1113 1114 HWND QTMovieWin::enterFullscreen(QTMovieWinFullscreenClient* client) 1115 { 1116 m_private->m_fullscreenClient = client; 1117 1118 BeginFullScreen(&m_private->m_fullscreenRestoreState, 0, 0, 0, &m_private->m_fullscreenWindow, 0, fullScreenAllowEvents); 1119 QTMLSetWindowWndProc(m_private->m_fullscreenWindow, fullscreenWndProc); 1120 CreatePortAssociation(GetPortNativeWindow(m_private->m_fullscreenWindow), 0, 0); 1121 1122 GetMovieBox(m_private->m_movie, &m_private->m_fullscreenRect); 1123 GetMovieGWorld(m_private->m_movie, &m_private->m_fullscreenOrigGWorld, 0); 1124 SetMovieGWorld(m_private->m_movie, reinterpret_cast<CGrafPtr>(m_private->m_fullscreenWindow), GetGWorldDevice(reinterpret_cast<CGrafPtr>(m_private->m_fullscreenWindow))); 1125 1126 // Set the size of the box to preserve aspect ratio 1127 Rect rect = m_private->m_fullscreenWindow->portRect; 1128 1129 float movieRatio = static_cast<float>(m_private->m_width) / m_private->m_height; 1130 int windowWidth = rect.right - rect.left; 1131 int windowHeight = rect.bottom - rect.top; 1132 float windowRatio = static_cast<float>(windowWidth) / windowHeight; 1133 int actualWidth = (windowRatio > movieRatio) ? (windowHeight * movieRatio) : windowWidth; 1134 int actualHeight = (windowRatio < movieRatio) ? (windowWidth / movieRatio) : windowHeight; 1135 int offsetX = (windowWidth - actualWidth) / 2; 1136 int offsetY = (windowHeight - actualHeight) / 2; 1137 1138 rect.left = offsetX; 1139 rect.right = offsetX + actualWidth; 1140 rect.top = offsetY; 1141 rect.bottom = offsetY + actualHeight; 1142 1143 SetMovieBox(m_private->m_movie, &rect); 1144 ShowHideTaskBar(true); 1145 1146 // Set the 'this' pointer on the HWND 1147 HWND wnd = static_cast<HWND>(GetPortNativeWindow(m_private->m_fullscreenWindow)); 1148 SetProp(wnd, fullscreenQTMovieWinPointerProp, static_cast<HANDLE>(this)); 1149 1150 return wnd; 1151 } 1152 1153 void QTMovieWin::exitFullscreen() 1154 { 1155 if (!m_private->m_fullscreenWindow) 1156 return; 1157 1158 HWND wnd = static_cast<HWND>(GetPortNativeWindow(m_private->m_fullscreenWindow)); 1159 DestroyPortAssociation(reinterpret_cast<CGrafPtr>(m_private->m_fullscreenWindow)); 1160 SetMovieGWorld(m_private->m_movie, m_private->m_fullscreenOrigGWorld, 0); 1161 EndFullScreen(m_private->m_fullscreenRestoreState, 0L); 1162 SetMovieBox(m_private->m_movie, &m_private->m_fullscreenRect); 1163 m_private->m_fullscreenWindow = 0; 1164 } 1165 1166 BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) 1167 { 1168 switch (fdwReason) { 1169 case DLL_PROCESS_ATTACH: 1170 return TRUE; 1171 case DLL_PROCESS_DETACH: 1172 case DLL_THREAD_ATTACH: 1173 case DLL_THREAD_DETACH: 1174 return FALSE; 1175 } 1176 ASSERT_NOT_REACHED(); 1177 return FALSE; 1178 } 1179