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 26 #include "config.h" 27 28 #if ENABLE(VIDEO) 29 #include "MediaPlayerPrivateQuickTimeVisualContext.h" 30 31 #include "ApplicationCacheHost.h" 32 #include "ApplicationCacheResource.h" 33 #include "Cookie.h" 34 #include "CookieJar.h" 35 #include "DocumentLoader.h" 36 #include "Frame.h" 37 #include "FrameView.h" 38 #include "GraphicsContext.h" 39 #include "KURL.h" 40 #include "MediaPlayerPrivateTaskTimer.h" 41 #include "QTCFDictionary.h" 42 #include "QTDecompressionSession.h" 43 #include "QTMovie.h" 44 #include "QTMovieTask.h" 45 #include "QTMovieVisualContext.h" 46 #include "ScrollView.h" 47 #include "Settings.h" 48 #include "SoftLinking.h" 49 #include "TimeRanges.h" 50 #include "Timer.h" 51 #include <AssertMacros.h> 52 #include <CoreGraphics/CGAffineTransform.h> 53 #include <CoreGraphics/CGContext.h> 54 #include <QuartzCore/CATransform3D.h> 55 #include <Wininet.h> 56 #include <wtf/CurrentTime.h> 57 #include <wtf/HashSet.h> 58 #include <wtf/MainThread.h> 59 #include <wtf/MathExtras.h> 60 #include <wtf/StdLibExtras.h> 61 #include <wtf/text/StringBuilder.h> 62 #include <wtf/text/StringHash.h> 63 64 #if USE(ACCELERATED_COMPOSITING) 65 #include "PlatformCALayer.h" 66 #include "WKCAImageQueue.h" 67 #endif 68 69 using namespace std; 70 71 namespace WebCore { 72 73 static CGImageRef CreateCGImageFromPixelBuffer(QTPixelBuffer buffer); 74 static bool requiredDllsAvailable(); 75 76 SOFT_LINK_LIBRARY(Wininet) 77 SOFT_LINK(Wininet, InternetSetCookieExW, DWORD, WINAPI, (LPCWSTR lpszUrl, LPCWSTR lpszCookieName, LPCWSTR lpszCookieData, DWORD dwFlags, DWORD_PTR dwReserved), (lpszUrl, lpszCookieName, lpszCookieData, dwFlags, dwReserved)) 78 79 // Interface declaration for MediaPlayerPrivateQuickTimeVisualContext's QTMovieClient aggregate 80 class MediaPlayerPrivateQuickTimeVisualContext::MovieClient : public QTMovieClient { 81 public: 82 MovieClient(MediaPlayerPrivateQuickTimeVisualContext* parent) : m_parent(parent) {} 83 virtual ~MovieClient() { m_parent = 0; } 84 virtual void movieEnded(QTMovie*); 85 virtual void movieLoadStateChanged(QTMovie*); 86 virtual void movieTimeChanged(QTMovie*); 87 private: 88 MediaPlayerPrivateQuickTimeVisualContext* m_parent; 89 }; 90 91 #if USE(ACCELERATED_COMPOSITING) 92 class MediaPlayerPrivateQuickTimeVisualContext::LayerClient : public PlatformCALayerClient { 93 public: 94 LayerClient(MediaPlayerPrivateQuickTimeVisualContext* parent) : m_parent(parent) {} 95 virtual ~LayerClient() { m_parent = 0; } 96 97 private: 98 virtual void platformCALayerLayoutSublayersOfLayer(PlatformCALayer*); 99 virtual bool platformCALayerRespondsToLayoutChanges() const { return true; } 100 101 virtual void platformCALayerAnimationStarted(CFTimeInterval beginTime) { } 102 virtual GraphicsLayer::CompositingCoordinatesOrientation platformCALayerContentsOrientation() const { return GraphicsLayer::CompositingCoordinatesBottomUp; } 103 virtual void platformCALayerPaintContents(GraphicsContext&, const IntRect& inClip) { } 104 virtual bool platformCALayerShowDebugBorders() const { return false; } 105 virtual bool platformCALayerShowRepaintCounter() const { return false; } 106 virtual int platformCALayerIncrementRepaintCount() { return 0; } 107 108 virtual bool platformCALayerContentsOpaque() const { return false; } 109 virtual bool platformCALayerDrawsContent() const { return false; } 110 virtual void platformCALayerLayerDidDisplay(PlatformLayer*) { } 111 112 MediaPlayerPrivateQuickTimeVisualContext* m_parent; 113 }; 114 115 void MediaPlayerPrivateQuickTimeVisualContext::LayerClient::platformCALayerLayoutSublayersOfLayer(PlatformCALayer* layer) 116 { 117 ASSERT(m_parent); 118 ASSERT(m_parent->m_transformLayer == layer); 119 120 FloatSize parentSize = layer->bounds().size(); 121 FloatSize naturalSize = m_parent->naturalSize(); 122 123 // Calculate the ratio of these two sizes and use that ratio to scale the qtVideoLayer: 124 FloatSize ratio(parentSize.width() / naturalSize.width(), parentSize.height() / naturalSize.height()); 125 126 int videoWidth = 0; 127 int videoHeight = 0; 128 m_parent->m_movie->getNaturalSize(videoWidth, videoHeight); 129 FloatRect videoBounds(0, 0, videoWidth * ratio.width(), videoHeight * ratio.height()); 130 FloatPoint3D videoAnchor = m_parent->m_qtVideoLayer->anchorPoint(); 131 132 // Calculate the new position based on the parent's size: 133 FloatPoint position(parentSize.width() * 0.5 - videoBounds.width() * (0.5 - videoAnchor.x()), 134 parentSize.height() * 0.5 - videoBounds.height() * (0.5 - videoAnchor.y())); 135 136 m_parent->m_qtVideoLayer->setBounds(videoBounds); 137 m_parent->m_qtVideoLayer->setPosition(position); 138 } 139 #endif 140 141 class MediaPlayerPrivateQuickTimeVisualContext::VisualContextClient : public QTMovieVisualContextClient { 142 public: 143 VisualContextClient(MediaPlayerPrivateQuickTimeVisualContext* parent) : m_parent(parent) {} 144 virtual ~VisualContextClient() { m_parent = 0; } 145 void imageAvailableForTime(const QTCVTimeStamp*); 146 static void retrieveCurrentImageProc(void*); 147 private: 148 MediaPlayerPrivateQuickTimeVisualContext* m_parent; 149 }; 150 151 MediaPlayerPrivateInterface* MediaPlayerPrivateQuickTimeVisualContext::create(MediaPlayer* player) 152 { 153 return new MediaPlayerPrivateQuickTimeVisualContext(player); 154 } 155 156 void MediaPlayerPrivateQuickTimeVisualContext::registerMediaEngine(MediaEngineRegistrar registrar) 157 { 158 if (isAvailable()) 159 registrar(create, getSupportedTypes, supportsType, 0, 0, 0); 160 } 161 162 MediaPlayerPrivateQuickTimeVisualContext::MediaPlayerPrivateQuickTimeVisualContext(MediaPlayer* player) 163 : m_player(player) 164 , m_seekTo(-1) 165 , m_seekTimer(this, &MediaPlayerPrivateQuickTimeVisualContext::seekTimerFired) 166 , m_visualContextTimer(this, &MediaPlayerPrivateQuickTimeVisualContext::visualContextTimerFired) 167 , m_networkState(MediaPlayer::Empty) 168 , m_readyState(MediaPlayer::HaveNothing) 169 , m_enabledTrackCount(0) 170 , m_totalTrackCount(0) 171 , m_hasUnsupportedTracks(false) 172 , m_startedPlaying(false) 173 , m_isStreaming(false) 174 , m_visible(false) 175 , m_newFrameAvailable(false) 176 , m_movieClient(new MediaPlayerPrivateQuickTimeVisualContext::MovieClient(this)) 177 #if USE(ACCELERATED_COMPOSITING) 178 , m_layerClient(new MediaPlayerPrivateQuickTimeVisualContext::LayerClient(this)) 179 , m_movieTransform(CGAffineTransformIdentity) 180 #endif 181 , m_visualContextClient(new MediaPlayerPrivateQuickTimeVisualContext::VisualContextClient(this)) 182 , m_delayingLoad(false) 183 , m_privateBrowsing(false) 184 , m_preload(MediaPlayer::Auto) 185 { 186 } 187 188 MediaPlayerPrivateQuickTimeVisualContext::~MediaPlayerPrivateQuickTimeVisualContext() 189 { 190 tearDownVideoRendering(); 191 cancelCallOnMainThread(&VisualContextClient::retrieveCurrentImageProc, this); 192 } 193 194 bool MediaPlayerPrivateQuickTimeVisualContext::supportsFullscreen() const 195 { 196 #if USE(ACCELERATED_COMPOSITING) 197 Document* document = m_player->mediaPlayerClient()->mediaPlayerOwningDocument(); 198 if (document && document->settings()) 199 return document->settings()->acceleratedCompositingEnabled(); 200 #endif 201 return false; 202 } 203 204 PlatformMedia MediaPlayerPrivateQuickTimeVisualContext::platformMedia() const 205 { 206 PlatformMedia p; 207 p.type = PlatformMedia::QTMovieVisualContextType; 208 p.media.qtMovieVisualContext = m_visualContext.get(); 209 return p; 210 } 211 #if USE(ACCELERATED_COMPOSITING) 212 213 PlatformLayer* MediaPlayerPrivateQuickTimeVisualContext::platformLayer() const 214 { 215 return m_transformLayer ? m_transformLayer->platformLayer() : 0; 216 } 217 #endif 218 219 String MediaPlayerPrivateQuickTimeVisualContext::rfc2616DateStringFromTime(CFAbsoluteTime time) 220 { 221 static const char* const dayStrings[] = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" }; 222 static const char* const monthStrings[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; 223 static const CFStringRef dateFormatString = CFSTR("%s, %02d %s %04d %02d:%02d:%02d GMT"); 224 static CFTimeZoneRef gmtTimeZone; 225 if (!gmtTimeZone) 226 gmtTimeZone = CFTimeZoneCopyDefault(); 227 228 CFGregorianDate dateValue = CFAbsoluteTimeGetGregorianDate(time, gmtTimeZone); 229 if (!CFGregorianDateIsValid(dateValue, kCFGregorianAllUnits)) 230 return String(); 231 232 time = CFGregorianDateGetAbsoluteTime(dateValue, gmtTimeZone); 233 SInt32 day = CFAbsoluteTimeGetDayOfWeek(time, 0); 234 235 RetainPtr<CFStringRef> dateCFString(AdoptCF, CFStringCreateWithFormat(0, 0, dateFormatString, dayStrings[day - 1], dateValue.day, 236 monthStrings[dateValue.month - 1], dateValue.year, dateValue.hour, dateValue.minute, (int)dateValue.second)); 237 return dateCFString.get(); 238 } 239 240 static void addCookieParam(StringBuilder& cookieBuilder, const String& name, const String& value) 241 { 242 if (name.isEmpty()) 243 return; 244 245 // If this isn't the first parameter added, terminate the previous one. 246 if (cookieBuilder.length()) 247 cookieBuilder.append("; "); 248 249 // Add parameter name, and value if there is one. 250 cookieBuilder.append(name); 251 if (!value.isEmpty()) { 252 cookieBuilder.append('='); 253 cookieBuilder.append(value); 254 } 255 } 256 257 void MediaPlayerPrivateQuickTimeVisualContext::setUpCookiesForQuickTime(const String& url) 258 { 259 // WebCore loaded the page with the movie URL with CFNetwork but QuickTime will 260 // use WinINet to download the movie, so we need to copy any cookies needed to 261 // download the movie into WinInet before asking QuickTime to open it. 262 Document* document = m_player->mediaPlayerClient()->mediaPlayerOwningDocument(); 263 Frame* frame = document ? document->frame() : 0; 264 if (!frame || !frame->page() || !frame->page()->cookieEnabled()) 265 return; 266 267 KURL movieURL = KURL(KURL(), url); 268 Vector<Cookie> documentCookies; 269 if (!getRawCookies(frame->document(), movieURL, documentCookies)) 270 return; 271 272 for (size_t ndx = 0; ndx < documentCookies.size(); ndx++) { 273 const Cookie& cookie = documentCookies[ndx]; 274 275 if (cookie.name.isEmpty()) 276 continue; 277 278 // Build up the cookie string with as much information as we can get so WinINet 279 // knows what to do with it. 280 StringBuilder cookieBuilder; 281 addCookieParam(cookieBuilder, cookie.name, cookie.value); 282 addCookieParam(cookieBuilder, "path", cookie.path); 283 if (cookie.expires) 284 addCookieParam(cookieBuilder, "expires", rfc2616DateStringFromTime(cookie.expires)); 285 if (cookie.httpOnly) 286 addCookieParam(cookieBuilder, "httpOnly", String()); 287 cookieBuilder.append(';'); 288 289 String cookieURL; 290 if (!cookie.domain.isEmpty()) { 291 StringBuilder urlBuilder; 292 293 urlBuilder.append(movieURL.protocol()); 294 urlBuilder.append("://"); 295 if (cookie.domain[0] == '.') 296 urlBuilder.append(cookie.domain.substring(1)); 297 else 298 urlBuilder.append(cookie.domain); 299 if (cookie.path.length() > 1) 300 urlBuilder.append(cookie.path); 301 302 cookieURL = urlBuilder.toString(); 303 } else 304 cookieURL = movieURL; 305 306 InternetSetCookieExW(cookieURL.charactersWithNullTermination(), 0, cookieBuilder.toString().charactersWithNullTermination(), 0, 0); 307 } 308 } 309 310 static void disableComponentsOnce() 311 { 312 static bool sComponentsDisabled = false; 313 if (sComponentsDisabled) 314 return; 315 sComponentsDisabled = true; 316 317 uint32_t componentsToDisable[][5] = { 318 {'eat ', 'TEXT', 'text', 0, 0}, 319 {'eat ', 'TXT ', 'text', 0, 0}, 320 {'eat ', 'utxt', 'text', 0, 0}, 321 {'eat ', 'TEXT', 'tx3g', 0, 0}, 322 }; 323 324 for (size_t i = 0; i < WTF_ARRAY_LENGTH(componentsToDisable); ++i) 325 QTMovie::disableComponent(componentsToDisable[i]); 326 } 327 328 void MediaPlayerPrivateQuickTimeVisualContext::resumeLoad() 329 { 330 m_delayingLoad = false; 331 332 if (!m_movieURL.isEmpty()) 333 loadInternal(m_movieURL); 334 } 335 336 void MediaPlayerPrivateQuickTimeVisualContext::load(const String& url) 337 { 338 m_movieURL = url; 339 340 if (m_preload == MediaPlayer::None) { 341 m_delayingLoad = true; 342 return; 343 } 344 345 loadInternal(url); 346 } 347 348 void MediaPlayerPrivateQuickTimeVisualContext::loadInternal(const String& url) 349 { 350 if (!QTMovie::initializeQuickTime()) { 351 // FIXME: is this the right error to return? 352 m_networkState = MediaPlayer::DecodeError; 353 m_player->networkStateChanged(); 354 return; 355 } 356 357 disableComponentsOnce(); 358 359 // Initialize the task timer. 360 MediaPlayerPrivateTaskTimer::initialize(); 361 362 if (m_networkState != MediaPlayer::Loading) { 363 m_networkState = MediaPlayer::Loading; 364 m_player->networkStateChanged(); 365 } 366 if (m_readyState != MediaPlayer::HaveNothing) { 367 m_readyState = MediaPlayer::HaveNothing; 368 m_player->readyStateChanged(); 369 } 370 cancelSeek(); 371 372 setUpCookiesForQuickTime(url); 373 374 m_movie = adoptRef(new QTMovie(m_movieClient.get())); 375 376 #if ENABLE(OFFLINE_WEB_APPLICATIONS) 377 Frame* frame = m_player->frameView() ? m_player->frameView()->frame() : 0; 378 ApplicationCacheHost* cacheHost = frame ? frame->loader()->documentLoader()->applicationCacheHost() : 0; 379 ApplicationCacheResource* resource = 0; 380 if (cacheHost && cacheHost->shouldLoadResourceFromApplicationCache(ResourceRequest(url), resource) && resource && !resource->path().isEmpty()) 381 m_movie->load(resource->path().characters(), resource->path().length(), m_player->preservesPitch()); 382 else 383 #endif 384 m_movie->load(url.characters(), url.length(), m_player->preservesPitch()); 385 m_movie->setVolume(m_player->volume()); 386 } 387 388 void MediaPlayerPrivateQuickTimeVisualContext::prepareToPlay() 389 { 390 if (!m_movie || m_delayingLoad) 391 resumeLoad(); 392 } 393 394 void MediaPlayerPrivateQuickTimeVisualContext::play() 395 { 396 if (!m_movie) 397 return; 398 m_startedPlaying = true; 399 400 m_movie->play(); 401 m_visualContextTimer.startRepeating(1.0 / 30); 402 } 403 404 void MediaPlayerPrivateQuickTimeVisualContext::pause() 405 { 406 if (!m_movie) 407 return; 408 m_startedPlaying = false; 409 410 m_movie->pause(); 411 m_visualContextTimer.stop(); 412 } 413 414 float MediaPlayerPrivateQuickTimeVisualContext::duration() const 415 { 416 if (!m_movie) 417 return 0; 418 return m_movie->duration(); 419 } 420 421 float MediaPlayerPrivateQuickTimeVisualContext::currentTime() const 422 { 423 if (!m_movie) 424 return 0; 425 return m_movie->currentTime(); 426 } 427 428 void MediaPlayerPrivateQuickTimeVisualContext::seek(float time) 429 { 430 cancelSeek(); 431 432 if (!m_movie) 433 return; 434 435 if (time > duration()) 436 time = duration(); 437 438 m_seekTo = time; 439 if (maxTimeLoaded() >= m_seekTo) 440 doSeek(); 441 else 442 m_seekTimer.start(0, 0.5f); 443 } 444 445 void MediaPlayerPrivateQuickTimeVisualContext::doSeek() 446 { 447 float oldRate = m_movie->rate(); 448 if (oldRate) 449 m_movie->setRate(0); 450 m_movie->setCurrentTime(m_seekTo); 451 float timeAfterSeek = currentTime(); 452 // restore playback only if not at end, othewise QTMovie will loop 453 if (oldRate && timeAfterSeek < duration()) 454 m_movie->setRate(oldRate); 455 cancelSeek(); 456 } 457 458 void MediaPlayerPrivateQuickTimeVisualContext::cancelSeek() 459 { 460 m_seekTo = -1; 461 m_seekTimer.stop(); 462 } 463 464 void MediaPlayerPrivateQuickTimeVisualContext::seekTimerFired(Timer<MediaPlayerPrivateQuickTimeVisualContext>*) 465 { 466 if (!m_movie || !seeking() || currentTime() == m_seekTo) { 467 cancelSeek(); 468 updateStates(); 469 m_player->timeChanged(); 470 return; 471 } 472 473 if (maxTimeLoaded() >= m_seekTo) 474 doSeek(); 475 else { 476 MediaPlayer::NetworkState state = networkState(); 477 if (state == MediaPlayer::Empty || state == MediaPlayer::Loaded) { 478 cancelSeek(); 479 updateStates(); 480 m_player->timeChanged(); 481 } 482 } 483 } 484 485 bool MediaPlayerPrivateQuickTimeVisualContext::paused() const 486 { 487 if (!m_movie) 488 return true; 489 return (!m_movie->rate()); 490 } 491 492 bool MediaPlayerPrivateQuickTimeVisualContext::seeking() const 493 { 494 if (!m_movie) 495 return false; 496 return m_seekTo >= 0; 497 } 498 499 IntSize MediaPlayerPrivateQuickTimeVisualContext::naturalSize() const 500 { 501 if (!m_movie) 502 return IntSize(); 503 int width; 504 int height; 505 m_movie->getNaturalSize(width, height); 506 #if USE(ACCELERATED_COMPOSITING) 507 CGSize originalSize = {width, height}; 508 CGSize transformedSize = CGSizeApplyAffineTransform(originalSize, m_movieTransform); 509 return IntSize(abs(transformedSize.width), abs(transformedSize.height)); 510 #else 511 return IntSize(width, height); 512 #endif 513 } 514 515 bool MediaPlayerPrivateQuickTimeVisualContext::hasVideo() const 516 { 517 if (!m_movie) 518 return false; 519 return m_movie->hasVideo(); 520 } 521 522 bool MediaPlayerPrivateQuickTimeVisualContext::hasAudio() const 523 { 524 if (!m_movie) 525 return false; 526 return m_movie->hasAudio(); 527 } 528 529 void MediaPlayerPrivateQuickTimeVisualContext::setVolume(float volume) 530 { 531 if (!m_movie) 532 return; 533 m_movie->setVolume(volume); 534 } 535 536 void MediaPlayerPrivateQuickTimeVisualContext::setRate(float rate) 537 { 538 if (!m_movie) 539 return; 540 541 // Do not call setRate(...) unless we have started playing; otherwise 542 // QuickTime's VisualContext can get wedged waiting for a rate change 543 // call which will never come. 544 if (m_startedPlaying) 545 m_movie->setRate(rate); 546 } 547 548 void MediaPlayerPrivateQuickTimeVisualContext::setPreservesPitch(bool preservesPitch) 549 { 550 if (!m_movie) 551 return; 552 m_movie->setPreservesPitch(preservesPitch); 553 } 554 555 bool MediaPlayerPrivateQuickTimeVisualContext::hasClosedCaptions() const 556 { 557 if (!m_movie) 558 return false; 559 return m_movie->hasClosedCaptions(); 560 } 561 562 void MediaPlayerPrivateQuickTimeVisualContext::setClosedCaptionsVisible(bool visible) 563 { 564 if (!m_movie) 565 return; 566 m_movie->setClosedCaptionsVisible(visible); 567 } 568 569 PassRefPtr<TimeRanges> MediaPlayerPrivateQuickTimeVisualContext::buffered() const 570 { 571 RefPtr<TimeRanges> timeRanges = TimeRanges::create(); 572 float loaded = maxTimeLoaded(); 573 // rtsp streams are not buffered 574 if (!m_isStreaming && loaded > 0) 575 timeRanges->add(0, loaded); 576 return timeRanges.release(); 577 } 578 579 float MediaPlayerPrivateQuickTimeVisualContext::maxTimeSeekable() const 580 { 581 // infinite duration means live stream 582 return !isfinite(duration()) ? 0 : maxTimeLoaded(); 583 } 584 585 float MediaPlayerPrivateQuickTimeVisualContext::maxTimeLoaded() const 586 { 587 if (!m_movie) 588 return 0; 589 return m_movie->maxTimeLoaded(); 590 } 591 592 unsigned MediaPlayerPrivateQuickTimeVisualContext::bytesLoaded() const 593 { 594 if (!m_movie) 595 return 0; 596 float dur = duration(); 597 float maxTime = maxTimeLoaded(); 598 if (!dur) 599 return 0; 600 return totalBytes() * maxTime / dur; 601 } 602 603 unsigned MediaPlayerPrivateQuickTimeVisualContext::totalBytes() const 604 { 605 if (!m_movie) 606 return 0; 607 return m_movie->dataSize(); 608 } 609 610 void MediaPlayerPrivateQuickTimeVisualContext::cancelLoad() 611 { 612 if (m_networkState < MediaPlayer::Loading || m_networkState == MediaPlayer::Loaded) 613 return; 614 615 tearDownVideoRendering(); 616 617 // Cancel the load by destroying the movie. 618 m_movie.clear(); 619 620 updateStates(); 621 } 622 623 void MediaPlayerPrivateQuickTimeVisualContext::updateStates() 624 { 625 MediaPlayer::NetworkState oldNetworkState = m_networkState; 626 MediaPlayer::ReadyState oldReadyState = m_readyState; 627 628 long loadState = m_movie ? m_movie->loadState() : QTMovieLoadStateError; 629 630 if (loadState >= QTMovieLoadStateLoaded && m_readyState < MediaPlayer::HaveMetadata) { 631 m_movie->disableUnsupportedTracks(m_enabledTrackCount, m_totalTrackCount); 632 if (m_player->inMediaDocument()) { 633 if (!m_enabledTrackCount || m_enabledTrackCount != m_totalTrackCount) { 634 // This is a type of media that we do not handle directly with a <video> 635 // element, eg. QuickTime VR, a movie with a sprite track, etc. Tell the 636 // MediaPlayerClient that we won't support it. 637 sawUnsupportedTracks(); 638 return; 639 } 640 } else if (!m_enabledTrackCount) 641 loadState = QTMovieLoadStateError; 642 } 643 644 // "Loaded" is reserved for fully buffered movies, never the case when streaming 645 if (loadState >= QTMovieLoadStateComplete && !m_isStreaming) { 646 m_networkState = MediaPlayer::Loaded; 647 m_readyState = MediaPlayer::HaveEnoughData; 648 } else if (loadState >= QTMovieLoadStatePlaythroughOK) { 649 m_readyState = MediaPlayer::HaveEnoughData; 650 } else if (loadState >= QTMovieLoadStatePlayable) { 651 // FIXME: This might not work correctly in streaming case, <rdar://problem/5693967> 652 m_readyState = currentTime() < maxTimeLoaded() ? MediaPlayer::HaveFutureData : MediaPlayer::HaveCurrentData; 653 } else if (loadState >= QTMovieLoadStateLoaded) { 654 m_readyState = MediaPlayer::HaveMetadata; 655 } else if (loadState > QTMovieLoadStateError) { 656 m_networkState = MediaPlayer::Loading; 657 m_readyState = MediaPlayer::HaveNothing; 658 } else { 659 if (m_player->inMediaDocument()) { 660 // Something went wrong in the loading of media within a standalone file. 661 // This can occur with chained ref movies that eventually resolve to a 662 // file we don't support. 663 sawUnsupportedTracks(); 664 return; 665 } 666 667 float loaded = maxTimeLoaded(); 668 if (!loaded) 669 m_readyState = MediaPlayer::HaveNothing; 670 671 if (!m_enabledTrackCount) 672 m_networkState = MediaPlayer::FormatError; 673 else { 674 // FIXME: We should differentiate between load/network errors and decode errors <rdar://problem/5605692> 675 if (loaded > 0) 676 m_networkState = MediaPlayer::DecodeError; 677 else 678 m_readyState = MediaPlayer::HaveNothing; 679 } 680 } 681 682 if (isReadyForRendering() && !hasSetUpVideoRendering()) 683 setUpVideoRendering(); 684 685 if (seeking()) 686 m_readyState = MediaPlayer::HaveNothing; 687 688 if (m_networkState != oldNetworkState) 689 m_player->networkStateChanged(); 690 if (m_readyState != oldReadyState) 691 m_player->readyStateChanged(); 692 } 693 694 bool MediaPlayerPrivateQuickTimeVisualContext::isReadyForRendering() const 695 { 696 return m_readyState >= MediaPlayer::HaveMetadata && m_player->visible(); 697 } 698 699 void MediaPlayerPrivateQuickTimeVisualContext::sawUnsupportedTracks() 700 { 701 m_movie->setDisabled(true); 702 m_hasUnsupportedTracks = true; 703 m_player->mediaPlayerClient()->mediaPlayerSawUnsupportedTracks(m_player); 704 } 705 706 void MediaPlayerPrivateQuickTimeVisualContext::didEnd() 707 { 708 if (m_hasUnsupportedTracks) 709 return; 710 711 m_startedPlaying = false; 712 713 updateStates(); 714 m_player->timeChanged(); 715 } 716 717 void MediaPlayerPrivateQuickTimeVisualContext::setSize(const IntSize& size) 718 { 719 if (m_hasUnsupportedTracks || !m_movie || m_size == size) 720 return; 721 m_size = size; 722 } 723 724 void MediaPlayerPrivateQuickTimeVisualContext::setVisible(bool visible) 725 { 726 if (m_hasUnsupportedTracks || !m_movie || m_visible == visible) 727 return; 728 729 m_visible = visible; 730 if (m_visible) { 731 if (isReadyForRendering()) 732 setUpVideoRendering(); 733 } else 734 tearDownVideoRendering(); 735 } 736 737 void MediaPlayerPrivateQuickTimeVisualContext::paint(GraphicsContext* p, const IntRect& r) 738 { 739 MediaRenderingMode currentMode = currentRenderingMode(); 740 741 if (currentMode == MediaRenderingNone) 742 return; 743 744 if (currentMode == MediaRenderingSoftwareRenderer && !m_visualContext) 745 return; 746 747 QTPixelBuffer buffer = m_visualContext->imageForTime(0); 748 if (buffer.pixelBufferRef()) { 749 #if USE(ACCELERATED_COMPOSITING) 750 if (m_qtVideoLayer) { 751 // We are probably being asked to render the video into a canvas, but 752 // there's a good chance the QTPixelBuffer is not ARGB and thus can't be 753 // drawn using CG. If so, fire up an ICMDecompressionSession and convert 754 // the current frame into something which can be rendered by CG. 755 if (!buffer.pixelFormatIs32ARGB() && !buffer.pixelFormatIs32BGRA()) { 756 // The decompression session will only decompress a specific pixelFormat 757 // at a specific width and height; if these differ, the session must be 758 // recreated with the new parameters. 759 if (!m_decompressionSession || !m_decompressionSession->canDecompress(buffer)) 760 m_decompressionSession = QTDecompressionSession::create(buffer.pixelFormatType(), buffer.width(), buffer.height()); 761 buffer = m_decompressionSession->decompress(buffer); 762 } 763 } 764 #endif 765 CGImageRef image = CreateCGImageFromPixelBuffer(buffer); 766 767 CGContextRef context = p->platformContext(); 768 CGContextSaveGState(context); 769 CGContextTranslateCTM(context, r.x(), r.y()); 770 CGContextTranslateCTM(context, 0, r.height()); 771 CGContextScaleCTM(context, 1, -1); 772 CGContextDrawImage(context, CGRectMake(0, 0, r.width(), r.height()), image); 773 CGContextRestoreGState(context); 774 775 CGImageRelease(image); 776 } 777 paintCompleted(*p, r); 778 } 779 780 void MediaPlayerPrivateQuickTimeVisualContext::paintCompleted(GraphicsContext& context, const IntRect& rect) 781 { 782 m_newFrameAvailable = false; 783 } 784 785 void MediaPlayerPrivateQuickTimeVisualContext::VisualContextClient::retrieveCurrentImageProc(void* refcon) 786 { 787 static_cast<MediaPlayerPrivateQuickTimeVisualContext*>(refcon)->retrieveCurrentImage(); 788 } 789 790 void MediaPlayerPrivateQuickTimeVisualContext::VisualContextClient::imageAvailableForTime(const QTCVTimeStamp* timeStamp) 791 { 792 // This call may come in on another thread, so marshall to the main thread first: 793 callOnMainThread(&retrieveCurrentImageProc, m_parent); 794 795 // callOnMainThread must be paired with cancelCallOnMainThread in the destructor, 796 // in case this object is deleted before the main thread request is handled. 797 } 798 799 void MediaPlayerPrivateQuickTimeVisualContext::visualContextTimerFired(Timer<MediaPlayerPrivateQuickTimeVisualContext>*) 800 { 801 if (m_visualContext && m_visualContext->isImageAvailableForTime(0)) 802 retrieveCurrentImage(); 803 } 804 805 static CFDictionaryRef QTCFDictionaryCreateWithDataCallback(CFAllocatorRef allocator, const UInt8* bytes, CFIndex length) 806 { 807 RetainPtr<CFDataRef> data(AdoptCF, CFDataCreateWithBytesNoCopy(allocator, bytes, length, kCFAllocatorNull)); 808 if (!data) 809 return 0; 810 811 return reinterpret_cast<CFDictionaryRef>(CFPropertyListCreateFromXMLData(allocator, data.get(), kCFPropertyListImmutable, 0)); 812 } 813 814 static CGImageRef CreateCGImageFromPixelBuffer(QTPixelBuffer buffer) 815 { 816 #if USE(ACCELERATED_COMPOSITING) 817 CGDataProviderRef provider = 0; 818 CGColorSpaceRef colorSpace = 0; 819 CGImageRef image = 0; 820 821 size_t bitsPerComponent = 0; 822 size_t bitsPerPixel = 0; 823 CGImageAlphaInfo alphaInfo = kCGImageAlphaNone; 824 825 if (buffer.pixelFormatIs32BGRA()) { 826 bitsPerComponent = 8; 827 bitsPerPixel = 32; 828 alphaInfo = (CGImageAlphaInfo)(kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little); 829 } else if (buffer.pixelFormatIs32ARGB()) { 830 bitsPerComponent = 8; 831 bitsPerPixel = 32; 832 alphaInfo = (CGImageAlphaInfo)(kCGImageAlphaNoneSkipLast | kCGBitmapByteOrder32Big); 833 } else { 834 // All other pixel formats are currently unsupported: 835 ASSERT_NOT_REACHED(); 836 } 837 838 CGDataProviderDirectAccessCallbacks callbacks = { 839 &QTPixelBuffer::dataProviderGetBytePointerCallback, 840 &QTPixelBuffer::dataProviderReleaseBytePointerCallback, 841 &QTPixelBuffer::dataProviderGetBytesAtPositionCallback, 842 &QTPixelBuffer::dataProviderReleaseInfoCallback, 843 }; 844 845 // Colorspace should be device, so that Quartz does not have to do an extra render. 846 colorSpace = CGColorSpaceCreateDeviceRGB(); 847 require(colorSpace, Bail); 848 849 provider = CGDataProviderCreateDirectAccess(buffer.pixelBufferRef(), buffer.dataSize(), &callbacks); 850 require(provider, Bail); 851 852 // CGDataProvider does not retain the buffer, but it will release it later, so do an extra retain here: 853 QTPixelBuffer::retainCallback(buffer.pixelBufferRef()); 854 855 image = CGImageCreate(buffer.width(), buffer.height(), bitsPerComponent, bitsPerPixel, buffer.bytesPerRow(), colorSpace, alphaInfo, provider, 0, false, kCGRenderingIntentDefault); 856 857 Bail: 858 // Once the image is created we can release our reference to the provider and the colorspace, they are retained by the image 859 if (provider) 860 CGDataProviderRelease(provider); 861 if (colorSpace) 862 CGColorSpaceRelease(colorSpace); 863 864 return image; 865 #else 866 return 0; 867 #endif 868 } 869 870 871 void MediaPlayerPrivateQuickTimeVisualContext::retrieveCurrentImage() 872 { 873 if (!m_visualContext) 874 return; 875 876 #if USE(ACCELERATED_COMPOSITING) 877 if (m_qtVideoLayer) { 878 879 QTPixelBuffer buffer = m_visualContext->imageForTime(0); 880 if (!buffer.pixelBufferRef()) 881 return; 882 883 PlatformCALayer* layer = m_qtVideoLayer.get(); 884 885 if (!buffer.lockBaseAddress()) { 886 if (requiredDllsAvailable()) { 887 if (!m_imageQueue) { 888 m_imageQueue = new WKCAImageQueue(buffer.width(), buffer.height(), 30); 889 m_imageQueue->setFlags(WKCAImageQueue::Fill, WKCAImageQueue::Fill); 890 layer->setContents(m_imageQueue->get()); 891 } 892 893 // Debug QuickTime links against a non-Debug version of CoreFoundation, so the 894 // CFDictionary attached to the CVPixelBuffer cannot be directly passed on into the 895 // CAImageQueue without being converted to a non-Debug CFDictionary. Additionally, 896 // old versions of QuickTime used a non-AAS CoreFoundation, so the types are not 897 // interchangable even in the release case. 898 RetainPtr<CFDictionaryRef> attachments(AdoptCF, QTCFDictionaryCreateCopyWithDataCallback(kCFAllocatorDefault, buffer.attachments(), &QTCFDictionaryCreateWithDataCallback)); 899 CFTimeInterval imageTime = QTMovieVisualContext::currentHostTime(); 900 901 m_imageQueue->collect(); 902 903 uint64_t imageId = m_imageQueue->registerPixelBuffer(buffer.baseAddress(), buffer.dataSize(), buffer.bytesPerRow(), buffer.width(), buffer.height(), buffer.pixelFormatType(), attachments.get(), 0); 904 905 if (m_imageQueue->insertImage(imageTime, WKCAImageQueue::Buffer, imageId, WKCAImageQueue::Opaque | WKCAImageQueue::Flush, &QTPixelBuffer::imageQueueReleaseCallback, buffer.pixelBufferRef())) { 906 // Retain the buffer one extra time so it doesn't dissappear before CAImageQueue decides to release it: 907 QTPixelBuffer::retainCallback(buffer.pixelBufferRef()); 908 } 909 910 } else { 911 CGImageRef image = CreateCGImageFromPixelBuffer(buffer); 912 layer->setContents(image); 913 CGImageRelease(image); 914 } 915 916 buffer.unlockBaseAddress(); 917 layer->setNeedsCommit(); 918 } 919 } else 920 #endif 921 m_player->repaint(); 922 923 m_visualContext->task(); 924 } 925 926 static HashSet<String> mimeTypeCache() 927 { 928 DEFINE_STATIC_LOCAL(HashSet<String>, typeCache, ()); 929 static bool typeListInitialized = false; 930 931 if (!typeListInitialized) { 932 unsigned count = QTMovie::countSupportedTypes(); 933 for (unsigned n = 0; n < count; n++) { 934 const UChar* character; 935 unsigned len; 936 QTMovie::getSupportedType(n, character, len); 937 if (len) 938 typeCache.add(String(character, len)); 939 } 940 941 typeListInitialized = true; 942 } 943 944 return typeCache; 945 } 946 947 static CFStringRef createVersionStringFromModuleName(LPCWSTR moduleName) 948 { 949 HMODULE module = GetModuleHandleW(moduleName); 950 if (!module) 951 return 0; 952 953 wchar_t filePath[MAX_PATH] = {0}; 954 if (!GetModuleFileNameW(module, filePath, MAX_PATH)) 955 return 0; 956 957 DWORD versionInfoSize = GetFileVersionInfoSizeW(filePath, 0); 958 if (!versionInfoSize) 959 return 0; 960 961 CFStringRef versionString = 0; 962 void* versionInfo = calloc(versionInfoSize, sizeof(char)); 963 if (GetFileVersionInfo(filePath, 0, versionInfoSize, versionInfo)) { 964 VS_FIXEDFILEINFO* fileInfo = 0; 965 UINT fileInfoLength = 0; 966 967 if (VerQueryValueW(versionInfo, L"\\", reinterpret_cast<LPVOID*>(&fileInfo), &fileInfoLength)) { 968 versionString = CFStringCreateWithFormat(kCFAllocatorDefault, 0, CFSTR("%d.%d.%d.%d"), 969 HIWORD(fileInfo->dwFileVersionMS), LOWORD(fileInfo->dwFileVersionMS), 970 HIWORD(fileInfo->dwFileVersionLS), LOWORD(fileInfo->dwFileVersionLS)); 971 } 972 } 973 free(versionInfo); 974 975 return versionString; 976 } 977 978 static bool requiredDllsAvailable() 979 { 980 static bool s_prerequisitesChecked = false; 981 static bool s_prerequisitesSatisfied; 982 static const CFStringRef kMinQuartzCoreVersion = CFSTR("1.0.42.0"); 983 static const CFStringRef kMinCoreVideoVersion = CFSTR("1.0.1.0"); 984 985 if (s_prerequisitesChecked) 986 return s_prerequisitesSatisfied; 987 s_prerequisitesChecked = true; 988 s_prerequisitesSatisfied = false; 989 990 CFStringRef quartzCoreString = createVersionStringFromModuleName(L"QuartzCore"); 991 if (!quartzCoreString) 992 quartzCoreString = createVersionStringFromModuleName(L"QuartzCore_debug"); 993 994 CFStringRef coreVideoString = createVersionStringFromModuleName(L"CoreVideo"); 995 if (!coreVideoString) 996 coreVideoString = createVersionStringFromModuleName(L"CoreVideo_debug"); 997 998 s_prerequisitesSatisfied = (quartzCoreString && coreVideoString 999 && CFStringCompare(quartzCoreString, kMinQuartzCoreVersion, kCFCompareNumerically) != kCFCompareLessThan 1000 && CFStringCompare(coreVideoString, kMinCoreVideoVersion, kCFCompareNumerically) != kCFCompareLessThan); 1001 1002 if (quartzCoreString) 1003 CFRelease(quartzCoreString); 1004 if (coreVideoString) 1005 CFRelease(coreVideoString); 1006 1007 return s_prerequisitesSatisfied; 1008 } 1009 1010 void MediaPlayerPrivateQuickTimeVisualContext::getSupportedTypes(HashSet<String>& types) 1011 { 1012 types = mimeTypeCache(); 1013 } 1014 1015 bool MediaPlayerPrivateQuickTimeVisualContext::isAvailable() 1016 { 1017 return QTMovie::initializeQuickTime(); 1018 } 1019 1020 MediaPlayer::SupportsType MediaPlayerPrivateQuickTimeVisualContext::supportsType(const String& type, const String& codecs) 1021 { 1022 // only return "IsSupported" if there is no codecs parameter for now as there is no way to ask QT if it supports an 1023 // extended MIME type 1024 return mimeTypeCache().contains(type) ? (codecs.isEmpty() ? MediaPlayer::MayBeSupported : MediaPlayer::IsSupported) : MediaPlayer::IsNotSupported; 1025 } 1026 1027 void MediaPlayerPrivateQuickTimeVisualContext::MovieClient::movieEnded(QTMovie* movie) 1028 { 1029 if (m_parent->m_hasUnsupportedTracks) 1030 return; 1031 1032 m_parent->m_visualContextTimer.stop(); 1033 1034 ASSERT(m_parent->m_movie.get() == movie); 1035 m_parent->didEnd(); 1036 } 1037 1038 void MediaPlayerPrivateQuickTimeVisualContext::MovieClient::movieLoadStateChanged(QTMovie* movie) 1039 { 1040 if (m_parent->m_hasUnsupportedTracks) 1041 return; 1042 1043 ASSERT(m_parent->m_movie.get() == movie); 1044 m_parent->updateStates(); 1045 } 1046 1047 void MediaPlayerPrivateQuickTimeVisualContext::MovieClient::movieTimeChanged(QTMovie* movie) 1048 { 1049 if (m_parent->m_hasUnsupportedTracks) 1050 return; 1051 1052 ASSERT(m_parent->m_movie.get() == movie); 1053 m_parent->updateStates(); 1054 m_parent->m_player->timeChanged(); 1055 } 1056 1057 bool MediaPlayerPrivateQuickTimeVisualContext::hasSingleSecurityOrigin() const 1058 { 1059 // We tell quicktime to disallow resources that come from different origins 1060 // so we all media is single origin. 1061 return true; 1062 } 1063 1064 void MediaPlayerPrivateQuickTimeVisualContext::setPreload(MediaPlayer::Preload preload) 1065 { 1066 m_preload = preload; 1067 if (m_delayingLoad && m_preload != MediaPlayer::None) 1068 resumeLoad(); 1069 } 1070 1071 float MediaPlayerPrivateQuickTimeVisualContext::mediaTimeForTimeValue(float timeValue) const 1072 { 1073 long timeScale; 1074 if (m_readyState < MediaPlayer::HaveMetadata || !(timeScale = m_movie->timeScale())) 1075 return timeValue; 1076 1077 long mediaTimeValue = lroundf(timeValue * timeScale); 1078 return static_cast<float>(mediaTimeValue) / timeScale; 1079 } 1080 1081 MediaPlayerPrivateQuickTimeVisualContext::MediaRenderingMode MediaPlayerPrivateQuickTimeVisualContext::currentRenderingMode() const 1082 { 1083 if (!m_movie) 1084 return MediaRenderingNone; 1085 1086 #if USE(ACCELERATED_COMPOSITING) 1087 if (m_qtVideoLayer) 1088 return MediaRenderingMovieLayer; 1089 #endif 1090 1091 return m_visualContext ? MediaRenderingSoftwareRenderer : MediaRenderingNone; 1092 } 1093 1094 MediaPlayerPrivateQuickTimeVisualContext::MediaRenderingMode MediaPlayerPrivateQuickTimeVisualContext::preferredRenderingMode() const 1095 { 1096 if (!m_player->frameView() || !m_movie) 1097 return MediaRenderingNone; 1098 1099 #if USE(ACCELERATED_COMPOSITING) 1100 if (supportsAcceleratedRendering() && m_player->mediaPlayerClient()->mediaPlayerRenderingCanBeAccelerated(m_player)) 1101 return MediaRenderingMovieLayer; 1102 #endif 1103 1104 return MediaRenderingSoftwareRenderer; 1105 } 1106 1107 void MediaPlayerPrivateQuickTimeVisualContext::setUpVideoRendering() 1108 { 1109 MediaRenderingMode currentMode = currentRenderingMode(); 1110 MediaRenderingMode preferredMode = preferredRenderingMode(); 1111 1112 #if !USE(ACCELERATED_COMPOSITING) 1113 ASSERT(preferredMode != MediaRenderingMovieLayer); 1114 #endif 1115 1116 if (currentMode == preferredMode && currentMode != MediaRenderingNone) 1117 return; 1118 1119 if (currentMode != MediaRenderingNone) 1120 tearDownVideoRendering(); 1121 1122 if (preferredMode == MediaRenderingMovieLayer) 1123 createLayerForMovie(); 1124 1125 #if USE(ACCELERATED_COMPOSITING) 1126 if (currentMode == MediaRenderingMovieLayer || preferredMode == MediaRenderingMovieLayer) 1127 m_player->mediaPlayerClient()->mediaPlayerRenderingModeChanged(m_player); 1128 #endif 1129 1130 QTPixelBuffer::Type contextType = requiredDllsAvailable() && preferredMode == MediaRenderingMovieLayer ? QTPixelBuffer::ConfigureForCAImageQueue : QTPixelBuffer::ConfigureForCGImage; 1131 m_visualContext = QTMovieVisualContext::create(m_visualContextClient.get(), contextType); 1132 m_visualContext->setMovie(m_movie.get()); 1133 } 1134 1135 void MediaPlayerPrivateQuickTimeVisualContext::tearDownVideoRendering() 1136 { 1137 #if USE(ACCELERATED_COMPOSITING) 1138 if (m_qtVideoLayer) 1139 destroyLayerForMovie(); 1140 #endif 1141 1142 m_visualContext = 0; 1143 } 1144 1145 bool MediaPlayerPrivateQuickTimeVisualContext::hasSetUpVideoRendering() const 1146 { 1147 #if USE(ACCELERATED_COMPOSITING) 1148 return m_qtVideoLayer || (currentRenderingMode() != MediaRenderingMovieLayer && m_visualContext); 1149 #else 1150 return true; 1151 #endif 1152 } 1153 1154 void MediaPlayerPrivateQuickTimeVisualContext::retrieveAndResetMovieTransform() 1155 { 1156 #if USE(ACCELERATED_COMPOSITING) 1157 // First things first, reset the total movie transform so that 1158 // we can bail out early: 1159 m_movieTransform = CGAffineTransformIdentity; 1160 1161 if (!m_movie || !m_movie->hasVideo()) 1162 return; 1163 1164 // This trick will only work on movies with a single video track, 1165 // so bail out early if the video contains more than one (or zero) 1166 // video tracks. 1167 QTTrackArray videoTracks = m_movie->videoTracks(); 1168 if (videoTracks.size() != 1) 1169 return; 1170 1171 QTTrack* track = videoTracks[0].get(); 1172 ASSERT(track); 1173 1174 CGAffineTransform movieTransform = m_movie->getTransform(); 1175 if (!CGAffineTransformEqualToTransform(movieTransform, CGAffineTransformIdentity)) 1176 m_movie->resetTransform(); 1177 1178 CGAffineTransform trackTransform = track->getTransform(); 1179 if (!CGAffineTransformEqualToTransform(trackTransform, CGAffineTransformIdentity)) 1180 track->resetTransform(); 1181 1182 // Multiply the two transforms together, taking care to 1183 // do so in the correct order, track * movie = final: 1184 m_movieTransform = CGAffineTransformConcat(trackTransform, movieTransform); 1185 #endif 1186 } 1187 1188 void MediaPlayerPrivateQuickTimeVisualContext::createLayerForMovie() 1189 { 1190 #if USE(ACCELERATED_COMPOSITING) 1191 ASSERT(supportsAcceleratedRendering()); 1192 1193 if (!m_movie || m_qtVideoLayer) 1194 return; 1195 1196 // Create a PlatformCALayer which will transform the contents of the video layer 1197 // which is in m_qtVideoLayer. 1198 m_transformLayer = PlatformCALayer::create(PlatformCALayer::LayerTypeLayer, m_layerClient.get()); 1199 if (!m_transformLayer) 1200 return; 1201 1202 // Mark the layer as anchored in the top left. 1203 m_transformLayer->setAnchorPoint(FloatPoint3D()); 1204 1205 m_qtVideoLayer = PlatformCALayer::create(PlatformCALayer::LayerTypeLayer, 0); 1206 if (!m_qtVideoLayer) 1207 return; 1208 1209 if (CGAffineTransformEqualToTransform(m_movieTransform, CGAffineTransformIdentity)) 1210 retrieveAndResetMovieTransform(); 1211 CGAffineTransform t = m_movieTransform; 1212 1213 // Remove the translation portion of the transform, since we will always rotate about 1214 // the layer's center point. In our limited use-case (a single video track), this is 1215 // safe: 1216 t.tx = t.ty = 0; 1217 m_qtVideoLayer->setTransform(CATransform3DMakeAffineTransform(t)); 1218 1219 #ifndef NDEBUG 1220 m_qtVideoLayer->setName("Video layer"); 1221 #endif 1222 m_transformLayer->appendSublayer(m_qtVideoLayer.get()); 1223 m_transformLayer->setNeedsLayout(); 1224 // The layer will get hooked up via RenderLayerBacking::updateGraphicsLayerConfiguration(). 1225 #endif 1226 1227 // Fill the newly created layer with image data, so we're not looking at 1228 // an empty layer until the next time a new image is available, which could 1229 // be a long time if we're paused. 1230 if (m_visualContext) 1231 retrieveCurrentImage(); 1232 } 1233 1234 void MediaPlayerPrivateQuickTimeVisualContext::destroyLayerForMovie() 1235 { 1236 #if USE(ACCELERATED_COMPOSITING) 1237 if (m_qtVideoLayer) { 1238 m_qtVideoLayer->removeFromSuperlayer(); 1239 m_qtVideoLayer = 0; 1240 } 1241 1242 if (m_transformLayer) 1243 m_transformLayer = 0; 1244 1245 if (m_imageQueue) 1246 m_imageQueue = 0; 1247 #endif 1248 } 1249 1250 #if USE(ACCELERATED_COMPOSITING) 1251 bool MediaPlayerPrivateQuickTimeVisualContext::supportsAcceleratedRendering() const 1252 { 1253 return isReadyForRendering(); 1254 } 1255 1256 void MediaPlayerPrivateQuickTimeVisualContext::acceleratedRenderingStateChanged() 1257 { 1258 // Set up or change the rendering path if necessary. 1259 setUpVideoRendering(); 1260 } 1261 1262 void MediaPlayerPrivateQuickTimeVisualContext::setPrivateBrowsingMode(bool privateBrowsing) 1263 { 1264 m_privateBrowsing = privateBrowsing; 1265 if (m_movie) 1266 m_movie->setPrivateBrowsingMode(m_privateBrowsing); 1267 } 1268 1269 #endif 1270 1271 1272 } 1273 1274 #endif 1275