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 26 #import "config.h" 27 28 #if ENABLE(VIDEO) 29 30 #import "MediaPlayerPrivateQTKit.h" 31 32 #ifdef BUILDING_ON_TIGER 33 #import "AutodrainedPool.h" 34 #endif 35 36 #import "BlockExceptions.h" 37 #import "FrameView.h" 38 #import "GraphicsContext.h" 39 #import "KURL.h" 40 #import "MIMETypeRegistry.h" 41 #import "SoftLinking.h" 42 #import "TimeRanges.h" 43 #import "WebCoreSystemInterface.h" 44 #import <QTKit/QTKit.h> 45 #import <objc/objc-runtime.h> 46 #import <wtf/UnusedParam.h> 47 48 #if USE(ACCELERATED_COMPOSITING) 49 #include "GraphicsLayer.h" 50 #endif 51 52 #if DRAW_FRAME_RATE 53 #import "Font.h" 54 #import "Frame.h" 55 #import "Document.h" 56 #import "RenderObject.h" 57 #import "RenderStyle.h" 58 #endif 59 60 #ifdef BUILDING_ON_TIGER 61 static IMP method_setImplementation(Method m, IMP imp) 62 { 63 IMP result = m->method_imp; 64 m->method_imp = imp; 65 return result; 66 } 67 #endif 68 69 SOFT_LINK_FRAMEWORK(QTKit) 70 71 SOFT_LINK(QTKit, QTMakeTime, QTTime, (long long timeValue, long timeScale), (timeValue, timeScale)) 72 73 SOFT_LINK_CLASS(QTKit, QTMovie) 74 SOFT_LINK_CLASS(QTKit, QTMovieView) 75 SOFT_LINK_CLASS(QTKit, QTMovieLayer) 76 77 SOFT_LINK_POINTER(QTKit, QTTrackMediaTypeAttribute, NSString *) 78 SOFT_LINK_POINTER(QTKit, QTMediaTypeAttribute, NSString *) 79 SOFT_LINK_POINTER(QTKit, QTMediaTypeBase, NSString *) 80 SOFT_LINK_POINTER(QTKit, QTMediaTypeMPEG, NSString *) 81 SOFT_LINK_POINTER(QTKit, QTMediaTypeSound, NSString *) 82 SOFT_LINK_POINTER(QTKit, QTMediaTypeText, NSString *) 83 SOFT_LINK_POINTER(QTKit, QTMediaTypeVideo, NSString *) 84 SOFT_LINK_POINTER(QTKit, QTMovieAskUnresolvedDataRefsAttribute, NSString *) 85 SOFT_LINK_POINTER(QTKit, QTMovieDataSizeAttribute, NSString *) 86 SOFT_LINK_POINTER(QTKit, QTMovieDidEndNotification, NSString *) 87 SOFT_LINK_POINTER(QTKit, QTMovieHasVideoAttribute, NSString *) 88 SOFT_LINK_POINTER(QTKit, QTMovieHasAudioAttribute, NSString *) 89 SOFT_LINK_POINTER(QTKit, QTMovieIsActiveAttribute, NSString *) 90 SOFT_LINK_POINTER(QTKit, QTMovieLoadStateAttribute, NSString *) 91 SOFT_LINK_POINTER(QTKit, QTMovieLoadStateDidChangeNotification, NSString *) 92 SOFT_LINK_POINTER(QTKit, QTMovieNaturalSizeAttribute, NSString *) 93 SOFT_LINK_POINTER(QTKit, QTMovieCurrentSizeAttribute, NSString *) 94 SOFT_LINK_POINTER(QTKit, QTMoviePreventExternalURLLinksAttribute, NSString *) 95 SOFT_LINK_POINTER(QTKit, QTMovieRateChangesPreservePitchAttribute, NSString *) 96 SOFT_LINK_POINTER(QTKit, QTMovieRateDidChangeNotification, NSString *) 97 SOFT_LINK_POINTER(QTKit, QTMovieSizeDidChangeNotification, NSString *) 98 SOFT_LINK_POINTER(QTKit, QTMovieTimeDidChangeNotification, NSString *) 99 SOFT_LINK_POINTER(QTKit, QTMovieTimeScaleAttribute, NSString *) 100 SOFT_LINK_POINTER(QTKit, QTMovieURLAttribute, NSString *) 101 SOFT_LINK_POINTER(QTKit, QTMovieVolumeDidChangeNotification, NSString *) 102 SOFT_LINK_POINTER(QTKit, QTSecurityPolicyNoCrossSiteAttribute, NSString *) 103 SOFT_LINK_POINTER(QTKit, QTVideoRendererWebKitOnlyNewImageAvailableNotification, NSString *) 104 #ifndef BUILDING_ON_TIGER 105 SOFT_LINK_POINTER(QTKit, QTMovieApertureModeClean, NSString *) 106 SOFT_LINK_POINTER(QTKit, QTMovieApertureModeAttribute, NSString *) 107 #endif 108 109 #define QTMovie getQTMovieClass() 110 #define QTMovieView getQTMovieViewClass() 111 #define QTMovieLayer getQTMovieLayerClass() 112 113 #define QTTrackMediaTypeAttribute getQTTrackMediaTypeAttribute() 114 #define QTMediaTypeAttribute getQTMediaTypeAttribute() 115 #define QTMediaTypeBase getQTMediaTypeBase() 116 #define QTMediaTypeMPEG getQTMediaTypeMPEG() 117 #define QTMediaTypeSound getQTMediaTypeSound() 118 #define QTMediaTypeText getQTMediaTypeText() 119 #define QTMediaTypeVideo getQTMediaTypeVideo() 120 #define QTMovieAskUnresolvedDataRefsAttribute getQTMovieAskUnresolvedDataRefsAttribute() 121 #define QTMovieDataSizeAttribute getQTMovieDataSizeAttribute() 122 #define QTMovieDidEndNotification getQTMovieDidEndNotification() 123 #define QTMovieHasVideoAttribute getQTMovieHasVideoAttribute() 124 #define QTMovieHasAudioAttribute getQTMovieHasAudioAttribute() 125 #define QTMovieIsActiveAttribute getQTMovieIsActiveAttribute() 126 #define QTMovieLoadStateAttribute getQTMovieLoadStateAttribute() 127 #define QTMovieLoadStateDidChangeNotification getQTMovieLoadStateDidChangeNotification() 128 #define QTMovieNaturalSizeAttribute getQTMovieNaturalSizeAttribute() 129 #define QTMovieCurrentSizeAttribute getQTMovieCurrentSizeAttribute() 130 #define QTMoviePreventExternalURLLinksAttribute getQTMoviePreventExternalURLLinksAttribute() 131 #define QTMovieRateChangesPreservePitchAttribute getQTMovieRateChangesPreservePitchAttribute() 132 #define QTMovieRateDidChangeNotification getQTMovieRateDidChangeNotification() 133 #define QTMovieSizeDidChangeNotification getQTMovieSizeDidChangeNotification() 134 #define QTMovieTimeDidChangeNotification getQTMovieTimeDidChangeNotification() 135 #define QTMovieTimeScaleAttribute getQTMovieTimeScaleAttribute() 136 #define QTMovieURLAttribute getQTMovieURLAttribute() 137 #define QTMovieVolumeDidChangeNotification getQTMovieVolumeDidChangeNotification() 138 #define QTSecurityPolicyNoCrossSiteAttribute getQTSecurityPolicyNoCrossSiteAttribute() 139 #define QTVideoRendererWebKitOnlyNewImageAvailableNotification getQTVideoRendererWebKitOnlyNewImageAvailableNotification() 140 #ifndef BUILDING_ON_TIGER 141 #define QTMovieApertureModeClean getQTMovieApertureModeClean() 142 #define QTMovieApertureModeAttribute getQTMovieApertureModeAttribute() 143 #endif 144 145 // Older versions of the QTKit header don't have these constants. 146 #if !defined QTKIT_VERSION_MAX_ALLOWED || QTKIT_VERSION_MAX_ALLOWED <= QTKIT_VERSION_7_0 147 enum { 148 QTMovieLoadStateError = -1L, 149 QTMovieLoadStateLoaded = 2000L, 150 QTMovieLoadStatePlayable = 10000L, 151 QTMovieLoadStatePlaythroughOK = 20000L, 152 QTMovieLoadStateComplete = 100000L 153 }; 154 #endif 155 156 using namespace WebCore; 157 using namespace std; 158 159 @interface WebCoreMovieObserver : NSObject 160 { 161 MediaPlayerPrivate* m_callback; 162 NSView* m_view; 163 BOOL m_delayCallbacks; 164 } 165 -(id)initWithCallback:(MediaPlayerPrivate*)callback; 166 -(void)disconnect; 167 -(void)setView:(NSView*)view; 168 -(void)repaint; 169 -(void)setDelayCallbacks:(BOOL)shouldDelay; 170 -(void)loadStateChanged:(NSNotification *)notification; 171 -(void)rateChanged:(NSNotification *)notification; 172 -(void)sizeChanged:(NSNotification *)notification; 173 -(void)timeChanged:(NSNotification *)notification; 174 -(void)didEnd:(NSNotification *)notification; 175 @end 176 177 @protocol WebKitVideoRenderingDetails 178 -(void)setMovie:(id)movie; 179 -(void)drawInRect:(NSRect)rect; 180 @end 181 182 namespace WebCore { 183 184 #ifdef BUILDING_ON_TIGER 185 static const long minimumQuickTimeVersion = 0x07300000; // 7.3 186 #endif 187 188 189 MediaPlayerPrivateInterface* MediaPlayerPrivate::create(MediaPlayer* player) 190 { 191 return new MediaPlayerPrivate(player); 192 } 193 194 void MediaPlayerPrivate::registerMediaEngine(MediaEngineRegistrar registrar) 195 { 196 if (isAvailable()) 197 registrar(create, getSupportedTypes, supportsType); 198 } 199 200 MediaPlayerPrivate::MediaPlayerPrivate(MediaPlayer* player) 201 : m_player(player) 202 , m_objcObserver(AdoptNS, [[WebCoreMovieObserver alloc] initWithCallback:this]) 203 , m_seekTo(-1) 204 , m_seekTimer(this, &MediaPlayerPrivate::seekTimerFired) 205 , m_networkState(MediaPlayer::Empty) 206 , m_readyState(MediaPlayer::HaveNothing) 207 , m_rect() 208 , m_scaleFactor(1, 1) 209 , m_enabledTrackCount(0) 210 , m_totalTrackCount(0) 211 , m_reportedDuration(-1) 212 , m_cachedDuration(-1) 213 , m_timeToRestore(-1) 214 , m_startedPlaying(false) 215 , m_isStreaming(false) 216 , m_visible(false) 217 , m_hasUnsupportedTracks(false) 218 , m_videoFrameHasDrawn(false) 219 #if DRAW_FRAME_RATE 220 , m_frameCountWhilePlaying(0) 221 , m_timeStartedPlaying(0) 222 , m_timeStoppedPlaying(0) 223 #endif 224 { 225 } 226 227 MediaPlayerPrivate::~MediaPlayerPrivate() 228 { 229 tearDownVideoRendering(); 230 231 [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()]; 232 [m_objcObserver.get() disconnect]; 233 } 234 235 void MediaPlayerPrivate::createQTMovie(const String& url) 236 { 237 NSURL *cocoaURL = KURL(ParsedURLString, url); 238 NSDictionary *movieAttributes = [NSDictionary dictionaryWithObjectsAndKeys: 239 cocoaURL, QTMovieURLAttribute, 240 [NSNumber numberWithBool:m_player->preservesPitch()], QTMovieRateChangesPreservePitchAttribute, 241 [NSNumber numberWithBool:YES], QTMoviePreventExternalURLLinksAttribute, 242 [NSNumber numberWithBool:YES], QTSecurityPolicyNoCrossSiteAttribute, 243 [NSNumber numberWithBool:NO], QTMovieAskUnresolvedDataRefsAttribute, 244 #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) 245 [NSNumber numberWithBool:YES], @"QTMovieOpenForPlaybackAttribute", 246 #endif 247 #ifndef BUILDING_ON_TIGER 248 QTMovieApertureModeClean, QTMovieApertureModeAttribute, 249 #endif 250 nil]; 251 252 createQTMovie(cocoaURL, movieAttributes); 253 } 254 255 void MediaPlayerPrivate::createQTMovie(NSURL *url, NSDictionary *movieAttributes) 256 { 257 [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()]; 258 259 bool recreating = false; 260 if (m_qtMovie) { 261 recreating = true; 262 destroyQTVideoRenderer(); 263 m_qtMovie = 0; 264 } 265 266 // Disable rtsp streams for now, <rdar://problem/5693967> 267 if (protocolIs([url scheme], "rtsp")) 268 return; 269 270 NSError *error = nil; 271 m_qtMovie.adoptNS([[QTMovie alloc] initWithAttributes:movieAttributes error:&error]); 272 273 if (!m_qtMovie) 274 return; 275 276 [m_qtMovie.get() setVolume:m_player->volume()]; 277 278 if (recreating && hasVideo()) 279 createQTVideoRenderer(QTVideoRendererModeListensForNewImages); 280 281 [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get() 282 selector:@selector(loadStateChanged:) 283 name:QTMovieLoadStateDidChangeNotification 284 object:m_qtMovie.get()]; 285 286 // In updateState(), we track when maxTimeLoaded() == duration(). 287 // In newer version of QuickTime, a notification is emitted when maxTimeLoaded changes. 288 // In older version of QuickTime, QTMovieLoadStateDidChangeNotification be fired. 289 if (NSString *maxTimeLoadedChangeNotification = wkQTMovieMaxTimeLoadedChangeNotification()) { 290 [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get() 291 selector:@selector(loadStateChanged:) 292 name:maxTimeLoadedChangeNotification 293 object:m_qtMovie.get()]; 294 } 295 296 [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get() 297 selector:@selector(rateChanged:) 298 name:QTMovieRateDidChangeNotification 299 object:m_qtMovie.get()]; 300 [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get() 301 selector:@selector(sizeChanged:) 302 name:QTMovieSizeDidChangeNotification 303 object:m_qtMovie.get()]; 304 [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get() 305 selector:@selector(timeChanged:) 306 name:QTMovieTimeDidChangeNotification 307 object:m_qtMovie.get()]; 308 [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get() 309 selector:@selector(didEnd:) 310 name:QTMovieDidEndNotification 311 object:m_qtMovie.get()]; 312 } 313 314 static void mainThreadSetNeedsDisplay(id self, SEL) 315 { 316 id movieView = [self superview]; 317 ASSERT(!movieView || [movieView isKindOfClass:[QTMovieView class]]); 318 if (!movieView || ![movieView isKindOfClass:[QTMovieView class]]) 319 return; 320 321 WebCoreMovieObserver* delegate = [movieView delegate]; 322 ASSERT(!delegate || [delegate isKindOfClass:[WebCoreMovieObserver class]]); 323 if (!delegate || ![delegate isKindOfClass:[WebCoreMovieObserver class]]) 324 return; 325 326 [delegate repaint]; 327 } 328 329 static Class QTVideoRendererClass() 330 { 331 static Class QTVideoRendererWebKitOnlyClass = NSClassFromString(@"QTVideoRendererWebKitOnly"); 332 return QTVideoRendererWebKitOnlyClass; 333 } 334 335 void MediaPlayerPrivate::createQTMovieView() 336 { 337 detachQTMovieView(); 338 339 static bool addedCustomMethods = false; 340 if (!m_player->inMediaDocument() && !addedCustomMethods) { 341 Class QTMovieContentViewClass = NSClassFromString(@"QTMovieContentView"); 342 ASSERT(QTMovieContentViewClass); 343 344 Method mainThreadSetNeedsDisplayMethod = class_getInstanceMethod(QTMovieContentViewClass, @selector(_mainThreadSetNeedsDisplay)); 345 ASSERT(mainThreadSetNeedsDisplayMethod); 346 347 method_setImplementation(mainThreadSetNeedsDisplayMethod, reinterpret_cast<IMP>(mainThreadSetNeedsDisplay)); 348 addedCustomMethods = true; 349 } 350 351 // delay callbacks as we *will* get notifications during setup 352 [m_objcObserver.get() setDelayCallbacks:YES]; 353 354 m_qtMovieView.adoptNS([[QTMovieView alloc] init]); 355 setSize(m_player->size()); 356 NSView* parentView = m_player->frameView()->documentView(); 357 [parentView addSubview:m_qtMovieView.get()]; 358 #ifdef BUILDING_ON_TIGER 359 // setDelegate: isn't a public call in Tiger, so use performSelector to keep the compiler happy 360 [m_qtMovieView.get() performSelector:@selector(setDelegate:) withObject:m_objcObserver.get()]; 361 #else 362 [m_qtMovieView.get() setDelegate:m_objcObserver.get()]; 363 #endif 364 [m_objcObserver.get() setView:m_qtMovieView.get()]; 365 [m_qtMovieView.get() setMovie:m_qtMovie.get()]; 366 [m_qtMovieView.get() setControllerVisible:NO]; 367 [m_qtMovieView.get() setPreservesAspectRatio:NO]; 368 // the area not covered by video should be transparent 369 [m_qtMovieView.get() setFillColor:[NSColor clearColor]]; 370 371 // If we're in a media document, allow QTMovieView to render in its default mode; 372 // otherwise tell it to draw synchronously. 373 // Note that we expect mainThreadSetNeedsDisplay to be invoked only when synchronous drawing is requested. 374 if (!m_player->inMediaDocument()) 375 wkQTMovieViewSetDrawSynchronously(m_qtMovieView.get(), YES); 376 377 [m_objcObserver.get() setDelayCallbacks:NO]; 378 } 379 380 void MediaPlayerPrivate::detachQTMovieView() 381 { 382 if (m_qtMovieView) { 383 [m_objcObserver.get() setView:nil]; 384 #ifdef BUILDING_ON_TIGER 385 // setDelegate: isn't a public call in Tiger, so use performSelector to keep the compiler happy 386 [m_qtMovieView.get() performSelector:@selector(setDelegate:) withObject:nil]; 387 #else 388 [m_qtMovieView.get() setDelegate:nil]; 389 #endif 390 [m_qtMovieView.get() removeFromSuperview]; 391 m_qtMovieView = nil; 392 } 393 } 394 395 void MediaPlayerPrivate::createQTVideoRenderer(QTVideoRendererMode rendererMode) 396 { 397 destroyQTVideoRenderer(); 398 399 m_qtVideoRenderer.adoptNS([[QTVideoRendererClass() alloc] init]); 400 if (!m_qtVideoRenderer) 401 return; 402 403 // associate our movie with our instance of QTVideoRendererWebKitOnly 404 [(id<WebKitVideoRenderingDetails>)m_qtVideoRenderer.get() setMovie:m_qtMovie.get()]; 405 406 if (rendererMode == QTVideoRendererModeListensForNewImages) { 407 // listen to QTVideoRendererWebKitOnly's QTVideoRendererWebKitOnlyNewImageDidBecomeAvailableNotification 408 [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get() 409 selector:@selector(newImageAvailable:) 410 name:QTVideoRendererWebKitOnlyNewImageAvailableNotification 411 object:m_qtVideoRenderer.get()]; 412 } 413 } 414 415 void MediaPlayerPrivate::destroyQTVideoRenderer() 416 { 417 if (!m_qtVideoRenderer) 418 return; 419 420 // stop observing the renderer's notifications before we toss it 421 [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get() 422 name:QTVideoRendererWebKitOnlyNewImageAvailableNotification 423 object:m_qtVideoRenderer.get()]; 424 425 // disassociate our movie from our instance of QTVideoRendererWebKitOnly 426 [(id<WebKitVideoRenderingDetails>)m_qtVideoRenderer.get() setMovie:nil]; 427 428 m_qtVideoRenderer = nil; 429 } 430 431 void MediaPlayerPrivate::createQTMovieLayer() 432 { 433 #if USE(ACCELERATED_COMPOSITING) 434 if (!m_qtMovie) 435 return; 436 437 ASSERT(supportsAcceleratedRendering()); 438 439 if (!m_qtVideoLayer) { 440 m_qtVideoLayer.adoptNS([[QTMovieLayer alloc] init]); 441 if (!m_qtVideoLayer) 442 return; 443 444 [m_qtVideoLayer.get() setMovie:m_qtMovie.get()]; 445 #ifndef NDEBUG 446 [(CALayer *)m_qtVideoLayer.get() setName:@"Video layer"]; 447 #endif 448 449 // Hang the video layer from the render layer, if we have one yet. If not, we'll do this 450 // later via acceleratedRenderingStateChanged(). 451 GraphicsLayer* videoGraphicsLayer = m_player->mediaPlayerClient()->mediaPlayerGraphicsLayer(m_player); 452 if (videoGraphicsLayer) 453 videoGraphicsLayer->setContentsToMedia(m_qtVideoLayer.get()); 454 } 455 #endif 456 } 457 458 void MediaPlayerPrivate::destroyQTMovieLayer() 459 { 460 #if USE(ACCELERATED_COMPOSITING) 461 if (!m_qtVideoLayer) 462 return; 463 464 // disassociate our movie from our instance of QTMovieLayer 465 [m_qtVideoLayer.get() setMovie:nil]; 466 m_qtVideoLayer = nil; 467 #endif 468 } 469 470 MediaPlayerPrivate::MediaRenderingMode MediaPlayerPrivate::currentRenderingMode() const 471 { 472 if (m_qtMovieView) 473 return MediaRenderingMovieView; 474 475 if (m_qtVideoLayer) 476 return MediaRenderingMovieLayer; 477 478 if (m_qtVideoRenderer) 479 return MediaRenderingSoftwareRenderer; 480 481 return MediaRenderingNone; 482 } 483 484 MediaPlayerPrivate::MediaRenderingMode MediaPlayerPrivate::preferredRenderingMode() const 485 { 486 if (!m_player->frameView() || !m_qtMovie) 487 return MediaRenderingNone; 488 489 if (m_player->inMediaDocument() || !QTVideoRendererClass()) 490 return MediaRenderingMovieView; 491 492 #if USE(ACCELERATED_COMPOSITING) 493 if (supportsAcceleratedRendering() && m_player->mediaPlayerClient()->mediaPlayerRenderingCanBeAccelerated(m_player)) 494 return MediaRenderingMovieLayer; 495 #endif 496 497 return MediaRenderingSoftwareRenderer; 498 } 499 500 void MediaPlayerPrivate::setUpVideoRendering() 501 { 502 if (!isReadyForRendering()) 503 return; 504 505 MediaRenderingMode currentMode = currentRenderingMode(); 506 MediaRenderingMode preferredMode = preferredRenderingMode(); 507 if (currentMode == preferredMode && currentMode != MediaRenderingNone) 508 return; 509 510 if (currentMode != MediaRenderingNone) 511 tearDownVideoRendering(); 512 513 switch (preferredMode) { 514 case MediaRenderingMovieView: 515 createQTMovieView(); 516 break; 517 case MediaRenderingNone: 518 case MediaRenderingSoftwareRenderer: 519 createQTVideoRenderer(QTVideoRendererModeListensForNewImages); 520 break; 521 case MediaRenderingMovieLayer: 522 createQTMovieLayer(); 523 break; 524 } 525 } 526 527 void MediaPlayerPrivate::tearDownVideoRendering() 528 { 529 if (m_qtMovieView) 530 detachQTMovieView(); 531 if (m_qtVideoRenderer) 532 destroyQTVideoRenderer(); 533 if (m_qtVideoLayer) 534 destroyQTMovieLayer(); 535 } 536 537 bool MediaPlayerPrivate::hasSetUpVideoRendering() const 538 { 539 return m_qtMovieView 540 || m_qtVideoLayer 541 || m_qtVideoRenderer; 542 } 543 544 QTTime MediaPlayerPrivate::createQTTime(float time) const 545 { 546 if (!metaDataAvailable()) 547 return QTMakeTime(0, 600); 548 long timeScale = [[m_qtMovie.get() attributeForKey:QTMovieTimeScaleAttribute] longValue]; 549 return QTMakeTime(time * timeScale, timeScale); 550 } 551 552 void MediaPlayerPrivate::load(const String& url) 553 { 554 if (m_networkState != MediaPlayer::Loading) { 555 m_networkState = MediaPlayer::Loading; 556 m_player->networkStateChanged(); 557 } 558 if (m_readyState != MediaPlayer::HaveNothing) { 559 m_readyState = MediaPlayer::HaveNothing; 560 m_player->readyStateChanged(); 561 } 562 cancelSeek(); 563 m_videoFrameHasDrawn = false; 564 565 [m_objcObserver.get() setDelayCallbacks:YES]; 566 567 createQTMovie(url); 568 569 [m_objcObserver.get() loadStateChanged:nil]; 570 [m_objcObserver.get() setDelayCallbacks:NO]; 571 } 572 573 PlatformMedia MediaPlayerPrivate::platformMedia() const 574 { 575 PlatformMedia plaftformMedia = { m_qtMovie.get() }; 576 return plaftformMedia; 577 } 578 579 void MediaPlayerPrivate::play() 580 { 581 if (!metaDataAvailable()) 582 return; 583 m_startedPlaying = true; 584 #if DRAW_FRAME_RATE 585 m_frameCountWhilePlaying = 0; 586 #endif 587 [m_objcObserver.get() setDelayCallbacks:YES]; 588 [m_qtMovie.get() setRate:m_player->rate()]; 589 [m_objcObserver.get() setDelayCallbacks:NO]; 590 } 591 592 void MediaPlayerPrivate::pause() 593 { 594 if (!metaDataAvailable()) 595 return; 596 m_startedPlaying = false; 597 #if DRAW_FRAME_RATE 598 m_timeStoppedPlaying = [NSDate timeIntervalSinceReferenceDate]; 599 #endif 600 [m_objcObserver.get() setDelayCallbacks:YES]; 601 [m_qtMovie.get() stop]; 602 [m_objcObserver.get() setDelayCallbacks:NO]; 603 } 604 605 float MediaPlayerPrivate::duration() const 606 { 607 if (!metaDataAvailable()) 608 return 0; 609 610 if (m_cachedDuration != -1.0f) 611 return m_cachedDuration; 612 613 QTTime time = [m_qtMovie.get() duration]; 614 if (time.flags == kQTTimeIsIndefinite) 615 return numeric_limits<float>::infinity(); 616 return static_cast<float>(time.timeValue) / time.timeScale; 617 } 618 619 float MediaPlayerPrivate::currentTime() const 620 { 621 if (!metaDataAvailable()) 622 return 0; 623 QTTime time = [m_qtMovie.get() currentTime]; 624 return static_cast<float>(time.timeValue) / time.timeScale; 625 } 626 627 void MediaPlayerPrivate::seek(float time) 628 { 629 // Nothing to do if we are already in the middle of a seek to the same time. 630 if (time == m_seekTo) 631 return; 632 633 cancelSeek(); 634 635 if (!metaDataAvailable()) 636 return; 637 638 if (time > duration()) 639 time = duration(); 640 641 m_seekTo = time; 642 if (maxTimeSeekable() >= m_seekTo) 643 doSeek(); 644 else 645 m_seekTimer.start(0, 0.5f); 646 } 647 648 void MediaPlayerPrivate::doSeek() 649 { 650 QTTime qttime = createQTTime(m_seekTo); 651 // setCurrentTime generates several event callbacks, update afterwards 652 [m_objcObserver.get() setDelayCallbacks:YES]; 653 float oldRate = [m_qtMovie.get() rate]; 654 655 if (oldRate) 656 [m_qtMovie.get() setRate:0]; 657 [m_qtMovie.get() setCurrentTime:qttime]; 658 659 // restore playback only if not at end, otherwise QTMovie will loop 660 float timeAfterSeek = currentTime(); 661 if (oldRate && timeAfterSeek < duration()) 662 [m_qtMovie.get() setRate:oldRate]; 663 664 cancelSeek(); 665 [m_objcObserver.get() setDelayCallbacks:NO]; 666 } 667 668 void MediaPlayerPrivate::cancelSeek() 669 { 670 m_seekTo = -1; 671 m_seekTimer.stop(); 672 } 673 674 void MediaPlayerPrivate::seekTimerFired(Timer<MediaPlayerPrivate>*) 675 { 676 if (!metaDataAvailable()|| !seeking() || currentTime() == m_seekTo) { 677 cancelSeek(); 678 updateStates(); 679 m_player->timeChanged(); 680 return; 681 } 682 683 if (maxTimeSeekable() >= m_seekTo) 684 doSeek(); 685 else { 686 MediaPlayer::NetworkState state = networkState(); 687 if (state == MediaPlayer::Empty || state == MediaPlayer::Loaded) { 688 cancelSeek(); 689 updateStates(); 690 m_player->timeChanged(); 691 } 692 } 693 } 694 695 bool MediaPlayerPrivate::paused() const 696 { 697 if (!metaDataAvailable()) 698 return true; 699 return [m_qtMovie.get() rate] == 0; 700 } 701 702 bool MediaPlayerPrivate::seeking() const 703 { 704 if (!metaDataAvailable()) 705 return false; 706 return m_seekTo >= 0; 707 } 708 709 IntSize MediaPlayerPrivate::naturalSize() const 710 { 711 if (!metaDataAvailable()) 712 return IntSize(); 713 714 // In spite of the name of this method, return QTMovieNaturalSizeAttribute transformed by the 715 // initial movie scale because the spec says intrinsic size is: 716 // 717 // ... the dimensions of the resource in CSS pixels after taking into account the resource's 718 // dimensions, aspect ratio, clean aperture, resolution, and so forth, as defined for the 719 // format used by the resource 720 721 NSSize naturalSize = [[m_qtMovie.get() attributeForKey:QTMovieNaturalSizeAttribute] sizeValue]; 722 return IntSize(naturalSize.width * m_scaleFactor.width(), naturalSize.height * m_scaleFactor.height()); 723 } 724 725 bool MediaPlayerPrivate::hasVideo() const 726 { 727 if (!metaDataAvailable()) 728 return false; 729 return [[m_qtMovie.get() attributeForKey:QTMovieHasVideoAttribute] boolValue]; 730 } 731 732 bool MediaPlayerPrivate::hasAudio() const 733 { 734 if (!m_qtMovie) 735 return false; 736 return [[m_qtMovie.get() attributeForKey:QTMovieHasAudioAttribute] boolValue]; 737 } 738 739 bool MediaPlayerPrivate::supportsFullscreen() const 740 { 741 #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) 742 return true; 743 #else 744 // See <rdar://problem/7389945> 745 return false; 746 #endif 747 } 748 749 void MediaPlayerPrivate::setVolume(float volume) 750 { 751 if (m_qtMovie) 752 [m_qtMovie.get() setVolume:volume]; 753 } 754 755 bool MediaPlayerPrivate::hasClosedCaptions() const 756 { 757 if (!metaDataAvailable()) 758 return false; 759 return wkQTMovieHasClosedCaptions(m_qtMovie.get()); 760 } 761 762 void MediaPlayerPrivate::setClosedCaptionsVisible(bool closedCaptionsVisible) 763 { 764 if (metaDataAvailable()) { 765 wkQTMovieSetShowClosedCaptions(m_qtMovie.get(), closedCaptionsVisible); 766 767 #if USE(ACCELERATED_COMPOSITING) && (!defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)) 768 if (closedCaptionsVisible && m_qtVideoLayer) { 769 // Captions will be rendered upside down unless we flag the movie as flipped (again). See <rdar://7408440>. 770 [m_qtVideoLayer.get() setGeometryFlipped:YES]; 771 } 772 #endif 773 } 774 } 775 776 void MediaPlayerPrivate::setRate(float rate) 777 { 778 if (m_qtMovie) 779 [m_qtMovie.get() setRate:rate]; 780 } 781 782 void MediaPlayerPrivate::setPreservesPitch(bool preservesPitch) 783 { 784 if (!m_qtMovie) 785 return; 786 787 // QTMovieRateChangesPreservePitchAttribute cannot be changed dynamically after QTMovie creation. 788 // If the passed in value is different than what already exists, we need to recreate the QTMovie for it to take effect. 789 if ([[m_qtMovie.get() attributeForKey:QTMovieRateChangesPreservePitchAttribute] boolValue] == preservesPitch) 790 return; 791 792 NSDictionary *movieAttributes = [[m_qtMovie.get() movieAttributes] mutableCopy]; 793 ASSERT(movieAttributes); 794 [movieAttributes setValue:[NSNumber numberWithBool:preservesPitch] forKey:QTMovieRateChangesPreservePitchAttribute]; 795 m_timeToRestore = currentTime(); 796 797 createQTMovie([movieAttributes valueForKey:QTMovieURLAttribute], movieAttributes); 798 } 799 800 PassRefPtr<TimeRanges> MediaPlayerPrivate::buffered() const 801 { 802 RefPtr<TimeRanges> timeRanges = TimeRanges::create(); 803 float loaded = maxTimeLoaded(); 804 if (loaded > 0) 805 timeRanges->add(0, loaded); 806 return timeRanges.release(); 807 } 808 809 float MediaPlayerPrivate::maxTimeSeekable() const 810 { 811 if (!metaDataAvailable()) 812 return 0; 813 814 // infinite duration means live stream 815 if (isinf(duration())) 816 return 0; 817 818 return wkQTMovieMaxTimeSeekable(m_qtMovie.get()); 819 } 820 821 float MediaPlayerPrivate::maxTimeLoaded() const 822 { 823 if (!metaDataAvailable()) 824 return 0; 825 return wkQTMovieMaxTimeLoaded(m_qtMovie.get()); 826 } 827 828 unsigned MediaPlayerPrivate::bytesLoaded() const 829 { 830 float dur = duration(); 831 if (!dur) 832 return 0; 833 return totalBytes() * maxTimeLoaded() / dur; 834 } 835 836 unsigned MediaPlayerPrivate::totalBytes() const 837 { 838 if (!metaDataAvailable()) 839 return 0; 840 return [[m_qtMovie.get() attributeForKey:QTMovieDataSizeAttribute] intValue]; 841 } 842 843 void MediaPlayerPrivate::cancelLoad() 844 { 845 // FIXME: Is there a better way to check for this? 846 if (m_networkState < MediaPlayer::Loading || m_networkState == MediaPlayer::Loaded) 847 return; 848 849 tearDownVideoRendering(); 850 m_qtMovie = nil; 851 852 updateStates(); 853 } 854 855 void MediaPlayerPrivate::cacheMovieScale() 856 { 857 NSSize initialSize = NSZeroSize; 858 NSSize naturalSize = [[m_qtMovie.get() attributeForKey:QTMovieNaturalSizeAttribute] sizeValue]; 859 860 #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) 861 // QTMovieCurrentSizeAttribute is not allowed with instances of QTMovie that have been 862 // opened with QTMovieOpenForPlaybackAttribute, so ask for the display transform attribute instead. 863 NSAffineTransform *displayTransform = [m_qtMovie.get() attributeForKey:@"QTMoviePreferredTransformAttribute"]; 864 if (displayTransform) 865 initialSize = [displayTransform transformSize:naturalSize]; 866 else { 867 initialSize.width = naturalSize.width; 868 initialSize.height = naturalSize.height; 869 } 870 #else 871 initialSize = [[m_qtMovie.get() attributeForKey:QTMovieCurrentSizeAttribute] sizeValue]; 872 #endif 873 874 if (naturalSize.width) 875 m_scaleFactor.setWidth(initialSize.width / naturalSize.width); 876 if (naturalSize.height) 877 m_scaleFactor.setHeight(initialSize.height / naturalSize.height); 878 } 879 880 bool MediaPlayerPrivate::isReadyForRendering() const 881 { 882 return m_readyState >= MediaPlayer::HaveMetadata && m_player->visible(); 883 } 884 885 void MediaPlayerPrivate::updateStates() 886 { 887 MediaPlayer::NetworkState oldNetworkState = m_networkState; 888 MediaPlayer::ReadyState oldReadyState = m_readyState; 889 890 long loadState = m_qtMovie ? [[m_qtMovie.get() attributeForKey:QTMovieLoadStateAttribute] longValue] : static_cast<long>(QTMovieLoadStateError); 891 892 if (loadState >= QTMovieLoadStateLoaded && m_readyState < MediaPlayer::HaveMetadata) { 893 disableUnsupportedTracks(); 894 if (m_player->inMediaDocument()) { 895 if (!m_enabledTrackCount || m_hasUnsupportedTracks) { 896 // This has a type of media that we do not handle directly with a <video> 897 // element, eg. a rtsp track or QuickTime VR. Tell the MediaPlayerClient 898 // that we noticed. 899 sawUnsupportedTracks(); 900 return; 901 } 902 } else if (!m_enabledTrackCount) 903 loadState = QTMovieLoadStateError; 904 905 if (loadState != QTMovieLoadStateError) { 906 cacheMovieScale(); 907 MediaPlayer::MovieLoadType movieType = movieLoadType(); 908 m_isStreaming = movieType == MediaPlayer::StoredStream || movieType == MediaPlayer::LiveStream; 909 } 910 } 911 912 // If this movie is reloading and we mean to restore the current time/rate, this might be the right time to do it. 913 if (loadState >= QTMovieLoadStateLoaded && oldNetworkState < MediaPlayer::Loaded && m_timeToRestore != -1.0f) { 914 QTTime qttime = createQTTime(m_timeToRestore); 915 m_timeToRestore = -1.0f; 916 917 // Disable event callbacks from setCurrentTime for restoring time in a recreated video 918 [m_objcObserver.get() setDelayCallbacks:YES]; 919 [m_qtMovie.get() setCurrentTime:qttime]; 920 [m_qtMovie.get() setRate:m_player->rate()]; 921 [m_objcObserver.get() setDelayCallbacks:NO]; 922 } 923 924 BOOL completelyLoaded = !m_isStreaming && (loadState >= QTMovieLoadStateComplete); 925 926 // Note: QT indicates that we are fully loaded with QTMovieLoadStateComplete. 927 // However newer versions of QT do not, so we check maxTimeLoaded against duration. 928 if (!completelyLoaded && !m_isStreaming && metaDataAvailable()) 929 completelyLoaded = maxTimeLoaded() == duration(); 930 931 if (completelyLoaded) { 932 // "Loaded" is reserved for fully buffered movies, never the case when streaming 933 m_networkState = MediaPlayer::Loaded; 934 m_readyState = MediaPlayer::HaveEnoughData; 935 } else if (loadState >= QTMovieLoadStatePlaythroughOK) { 936 m_readyState = MediaPlayer::HaveEnoughData; 937 m_networkState = MediaPlayer::Loading; 938 } else if (loadState >= QTMovieLoadStatePlayable) { 939 // FIXME: This might not work correctly in streaming case, <rdar://problem/5693967> 940 m_readyState = currentTime() < maxTimeLoaded() ? MediaPlayer::HaveFutureData : MediaPlayer::HaveCurrentData; 941 m_networkState = MediaPlayer::Loading; 942 } else if (loadState >= QTMovieLoadStateLoaded) { 943 m_readyState = MediaPlayer::HaveMetadata; 944 m_networkState = MediaPlayer::Loading; 945 } else if (loadState > QTMovieLoadStateError) { 946 m_readyState = MediaPlayer::HaveNothing; 947 m_networkState = MediaPlayer::Loading; 948 } else { 949 // Loading or decoding failed. 950 951 if (m_player->inMediaDocument()) { 952 // Something went wrong in the loading of media within a standalone file. 953 // This can occur with chained refmovies pointing to streamed media. 954 sawUnsupportedTracks(); 955 return; 956 } 957 958 float loaded = maxTimeLoaded(); 959 if (!loaded) 960 m_readyState = MediaPlayer::HaveNothing; 961 962 if (!m_enabledTrackCount) 963 m_networkState = MediaPlayer::FormatError; 964 else { 965 // FIXME: We should differentiate between load/network errors and decode errors <rdar://problem/5605692> 966 if (loaded > 0) 967 m_networkState = MediaPlayer::DecodeError; 968 else 969 m_readyState = MediaPlayer::HaveNothing; 970 } 971 } 972 973 if (!hasSetUpVideoRendering()) 974 setUpVideoRendering(); 975 976 if (seeking()) 977 m_readyState = m_readyState >= MediaPlayer::HaveMetadata ? MediaPlayer::HaveMetadata : MediaPlayer::HaveNothing; 978 979 // Streaming movies don't use the network when paused. 980 if (m_isStreaming && m_readyState >= MediaPlayer::HaveMetadata && m_networkState >= MediaPlayer::Loading && [m_qtMovie.get() rate] == 0) 981 m_networkState = MediaPlayer::Idle; 982 983 if (m_networkState != oldNetworkState) 984 m_player->networkStateChanged(); 985 986 if (m_readyState != oldReadyState) 987 m_player->readyStateChanged(); 988 989 if (loadState >= QTMovieLoadStateLoaded) { 990 float dur = duration(); 991 if (dur != m_reportedDuration) { 992 if (m_reportedDuration != -1.0f) 993 m_player->durationChanged(); 994 m_reportedDuration = dur; 995 } 996 } 997 } 998 999 void MediaPlayerPrivate::loadStateChanged() 1000 { 1001 if (!m_hasUnsupportedTracks) 1002 updateStates(); 1003 } 1004 1005 void MediaPlayerPrivate::rateChanged() 1006 { 1007 if (m_hasUnsupportedTracks) 1008 return; 1009 1010 updateStates(); 1011 m_player->rateChanged(); 1012 } 1013 1014 void MediaPlayerPrivate::sizeChanged() 1015 { 1016 if (!m_hasUnsupportedTracks) 1017 m_player->sizeChanged(); 1018 } 1019 1020 void MediaPlayerPrivate::timeChanged() 1021 { 1022 if (m_hasUnsupportedTracks) 1023 return; 1024 1025 // It may not be possible to seek to a specific time in a streamed movie. When seeking in a 1026 // stream QuickTime sets the movie time to closest time possible and posts a timechanged 1027 // notification. Update m_seekTo so we can detect when the seek completes. 1028 if (m_seekTo != -1) 1029 m_seekTo = currentTime(); 1030 1031 m_timeToRestore = -1.0f; 1032 updateStates(); 1033 m_player->timeChanged(); 1034 } 1035 1036 void MediaPlayerPrivate::didEnd() 1037 { 1038 if (m_hasUnsupportedTracks) 1039 return; 1040 1041 m_startedPlaying = false; 1042 #if DRAW_FRAME_RATE 1043 m_timeStoppedPlaying = [NSDate timeIntervalSinceReferenceDate]; 1044 #endif 1045 1046 // Hang onto the current time and use it as duration from now on since QuickTime is telling us we 1047 // are at the end. Do this because QuickTime sometimes reports one time for duration and stops 1048 // playback at another time, which causes problems in HTMLMediaElement. QTKit's 'ended' event 1049 // fires when playing in reverse so don't update duration when at time zero! 1050 float now = currentTime(); 1051 if (now > 0) 1052 m_cachedDuration = now; 1053 1054 updateStates(); 1055 m_player->timeChanged(); 1056 } 1057 1058 void MediaPlayerPrivate::setSize(const IntSize&) 1059 { 1060 // Don't resize the view now because [view setFrame] also resizes the movie itself, and because 1061 // the renderer calls this function immediately when we report a size change (QTMovieSizeDidChangeNotification) 1062 // we can get into a feedback loop observing the size change and resetting the size, and this can cause 1063 // QuickTime to miss resetting a movie's size when the media size changes (as happens with an rtsp movie 1064 // once the rtsp server sends the track sizes). Instead we remember the size passed to paint() and resize 1065 // the view when it changes. 1066 // <rdar://problem/6336092> REGRESSION: rtsp movie does not resize correctly 1067 } 1068 1069 void MediaPlayerPrivate::setVisible(bool b) 1070 { 1071 if (m_visible != b) { 1072 m_visible = b; 1073 if (b) 1074 setUpVideoRendering(); 1075 else 1076 tearDownVideoRendering(); 1077 } 1078 } 1079 1080 bool MediaPlayerPrivate::hasAvailableVideoFrame() const 1081 { 1082 // When using a QTMovieLayer return true as soon as the movie reaches QTMovieLoadStatePlayable 1083 // because although we don't *know* when the first frame has decoded, by the time we get and 1084 // process the notification a frame should have propagated the VisualContext and been set on 1085 // the layer. 1086 if (currentRenderingMode() == MediaRenderingMovieLayer) 1087 return m_readyState >= MediaPlayer::HaveCurrentData; 1088 1089 // When using the software renderer QuickTime signals that a frame is available so we might as well 1090 // wait until we know that a frame has been drawn. 1091 return m_videoFrameHasDrawn; 1092 } 1093 1094 void MediaPlayerPrivate::repaint() 1095 { 1096 if (m_hasUnsupportedTracks) 1097 return; 1098 1099 #if DRAW_FRAME_RATE 1100 if (m_startedPlaying) { 1101 m_frameCountWhilePlaying++; 1102 // to eliminate preroll costs from our calculation, 1103 // our frame rate calculation excludes the first frame drawn after playback starts 1104 if (1==m_frameCountWhilePlaying) 1105 m_timeStartedPlaying = [NSDate timeIntervalSinceReferenceDate]; 1106 } 1107 #endif 1108 m_videoFrameHasDrawn = true; 1109 m_player->repaint(); 1110 } 1111 1112 void MediaPlayerPrivate::paintCurrentFrameInContext(GraphicsContext* context, const IntRect& r) 1113 { 1114 id qtVideoRenderer = m_qtVideoRenderer.get(); 1115 if (!qtVideoRenderer && currentRenderingMode() == MediaRenderingMovieLayer) { 1116 // We're being told to render into a context, but we already have the 1117 // MovieLayer going. This probably means we've been called from <canvas>. 1118 // Set up a QTVideoRenderer to use, but one that doesn't register for 1119 // update callbacks. That way, it won't bother us asking to repaint. 1120 createQTVideoRenderer(QTVideoRendererModeDefault); 1121 qtVideoRenderer = m_qtVideoRenderer.get(); 1122 } 1123 paint(context, r); 1124 } 1125 1126 void MediaPlayerPrivate::paint(GraphicsContext* context, const IntRect& r) 1127 { 1128 if (context->paintingDisabled() || m_hasUnsupportedTracks) 1129 return; 1130 NSView *view = m_qtMovieView.get(); 1131 id qtVideoRenderer = m_qtVideoRenderer.get(); 1132 if (!view && !qtVideoRenderer) 1133 return; 1134 1135 [m_objcObserver.get() setDelayCallbacks:YES]; 1136 BEGIN_BLOCK_OBJC_EXCEPTIONS; 1137 context->save(); 1138 context->translate(r.x(), r.y() + r.height()); 1139 context->scale(FloatSize(1.0f, -1.0f)); 1140 context->setImageInterpolationQuality(InterpolationLow); 1141 IntRect paintRect(IntPoint(0, 0), IntSize(r.width(), r.height())); 1142 1143 #ifdef BUILDING_ON_TIGER 1144 AutodrainedPool pool; 1145 #endif 1146 NSGraphicsContext* newContext = [NSGraphicsContext graphicsContextWithGraphicsPort:context->platformContext() flipped:NO]; 1147 1148 // draw the current video frame 1149 if (qtVideoRenderer) { 1150 [NSGraphicsContext saveGraphicsState]; 1151 [NSGraphicsContext setCurrentContext:newContext]; 1152 [(id<WebKitVideoRenderingDetails>)qtVideoRenderer drawInRect:paintRect]; 1153 [NSGraphicsContext restoreGraphicsState]; 1154 } else { 1155 if (m_rect != r) { 1156 m_rect = r; 1157 if (m_player->inMediaDocument()) { 1158 // the QTMovieView needs to be placed in the proper location for document mode 1159 [view setFrame:m_rect]; 1160 } 1161 else { 1162 // We don't really need the QTMovieView in any specific location so let's just get it out of the way 1163 // where it won't intercept events or try to bring up the context menu. 1164 IntRect farAwayButCorrectSize(m_rect); 1165 farAwayButCorrectSize.move(-1000000, -1000000); 1166 [view setFrame:farAwayButCorrectSize]; 1167 } 1168 } 1169 1170 if (m_player->inMediaDocument()) { 1171 // If we're using a QTMovieView in a media document, the view may get layer-backed. AppKit won't update 1172 // the layer hosting correctly if we call displayRectIgnoringOpacity:inContext:, so use displayRectIgnoringOpacity: 1173 // in this case. See <rdar://problem/6702882>. 1174 [view displayRectIgnoringOpacity:paintRect]; 1175 } else 1176 [view displayRectIgnoringOpacity:paintRect inContext:newContext]; 1177 } 1178 1179 #if DRAW_FRAME_RATE 1180 // Draw the frame rate only after having played more than 10 frames. 1181 if (m_frameCountWhilePlaying > 10) { 1182 Frame* frame = m_player->frameView() ? m_player->frameView()->frame() : NULL; 1183 Document* document = frame ? frame->document() : NULL; 1184 RenderObject* renderer = document ? document->renderer() : NULL; 1185 RenderStyle* styleToUse = renderer ? renderer->style() : NULL; 1186 if (styleToUse) { 1187 double frameRate = (m_frameCountWhilePlaying - 1) / ( m_startedPlaying ? ([NSDate timeIntervalSinceReferenceDate] - m_timeStartedPlaying) : 1188 (m_timeStoppedPlaying - m_timeStartedPlaying) ); 1189 String text = String::format("%1.2f", frameRate); 1190 TextRun textRun(text.characters(), text.length()); 1191 const Color color(255, 0, 0); 1192 context->scale(FloatSize(1.0f, -1.0f)); 1193 context->setStrokeColor(color, styleToUse->colorSpace()); 1194 context->setStrokeStyle(SolidStroke); 1195 context->setStrokeThickness(1.0f); 1196 context->setFillColor(color, styleToUse->colorSpace()); 1197 context->drawText(styleToUse->font(), textRun, IntPoint(2, -3)); 1198 } 1199 } 1200 #endif 1201 1202 context->restore(); 1203 END_BLOCK_OBJC_EXCEPTIONS; 1204 [m_objcObserver.get() setDelayCallbacks:NO]; 1205 } 1206 1207 static void addFileTypesToCache(NSArray * fileTypes, HashSet<String> &cache) 1208 { 1209 int count = [fileTypes count]; 1210 for (int n = 0; n < count; n++) { 1211 CFStringRef ext = reinterpret_cast<CFStringRef>([fileTypes objectAtIndex:n]); 1212 RetainPtr<CFStringRef> uti(AdoptCF, UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, ext, NULL)); 1213 if (!uti) 1214 continue; 1215 RetainPtr<CFStringRef> mime(AdoptCF, UTTypeCopyPreferredTagWithClass(uti.get(), kUTTagClassMIMEType)); 1216 1217 // UTI types are missing many media related MIME types supported by QTKit, see rdar://6434168, 1218 // and not all third party movie importers register their types, so if we didn't find a type for 1219 // this extension look it up in the hard coded table in the MIME type regsitry. 1220 if (!mime) { 1221 // -movieFileTypes: returns both file extensions and OSTypes. The later are surrounded by single 1222 // quotes, eg. 'MooV', so don't bother looking at those. 1223 if (CFStringGetCharacterAtIndex(ext, 0) != '\'') { 1224 String mediaType = MIMETypeRegistry::getMediaMIMETypeForExtension(String(ext)); 1225 if (!mediaType.isEmpty()) 1226 mime.adoptCF(mediaType.createCFString()); 1227 } 1228 } 1229 if (!mime) 1230 continue; 1231 cache.add(mime.get()); 1232 } 1233 } 1234 1235 static HashSet<String> mimeCommonTypesCache() 1236 { 1237 DEFINE_STATIC_LOCAL(HashSet<String>, cache, ()); 1238 static bool typeListInitialized = false; 1239 1240 if (!typeListInitialized) { 1241 typeListInitialized = true; 1242 NSArray* fileTypes = [QTMovie movieFileTypes:QTIncludeCommonTypes]; 1243 addFileTypesToCache(fileTypes, cache); 1244 } 1245 1246 return cache; 1247 } 1248 1249 static HashSet<String> mimeModernTypesCache() 1250 { 1251 DEFINE_STATIC_LOCAL(HashSet<String>, cache, ()); 1252 static bool typeListInitialized = false; 1253 1254 if (!typeListInitialized) { 1255 typeListInitialized = true; 1256 NSArray* fileTypes = [QTMovie movieFileTypes:(QTMovieFileTypeOptions)wkQTIncludeOnlyModernMediaFileTypes()]; 1257 addFileTypesToCache(fileTypes, cache); 1258 } 1259 1260 return cache; 1261 } 1262 1263 void MediaPlayerPrivate::getSupportedTypes(HashSet<String>& types) 1264 { 1265 // Note: this method starts QTKitServer if it isn't already running when in 64-bit because it has to return the list 1266 // of every MIME type supported by QTKit. 1267 types = mimeCommonTypesCache(); 1268 } 1269 1270 MediaPlayer::SupportsType MediaPlayerPrivate::supportsType(const String& type, const String& codecs) 1271 { 1272 // Only return "IsSupported" if there is no codecs parameter for now as there is no way to ask QT if it supports an 1273 // extended MIME type yet. 1274 1275 // We check the "modern" type cache first, as it doesn't require QTKitServer to start. 1276 if (mimeModernTypesCache().contains(type) || mimeCommonTypesCache().contains(type)) 1277 return codecs.isEmpty() ? MediaPlayer::MayBeSupported : MediaPlayer::IsSupported; 1278 1279 return MediaPlayer::IsNotSupported; 1280 } 1281 1282 bool MediaPlayerPrivate::isAvailable() 1283 { 1284 #ifdef BUILDING_ON_TIGER 1285 SInt32 version; 1286 OSErr result; 1287 result = Gestalt(gestaltQuickTime, &version); 1288 if (result != noErr) { 1289 LOG_ERROR("No QuickTime available. Disabling <video> and <audio> support."); 1290 return false; 1291 } 1292 if (version < minimumQuickTimeVersion) { 1293 LOG_ERROR("QuickTime version %x detected, at least %x required. Disabling <video> and <audio> support.", version, minimumQuickTimeVersion); 1294 return false; 1295 } 1296 return true; 1297 #else 1298 // On 10.5 and higher, QuickTime will always be new enough for <video> and <audio> support, so we just check that the framework can be loaded. 1299 return QTKitLibrary(); 1300 #endif 1301 } 1302 1303 void MediaPlayerPrivate::disableUnsupportedTracks() 1304 { 1305 if (!m_qtMovie) { 1306 m_enabledTrackCount = 0; 1307 m_totalTrackCount = 0; 1308 return; 1309 } 1310 1311 static HashSet<String>* allowedTrackTypes = 0; 1312 if (!allowedTrackTypes) { 1313 allowedTrackTypes = new HashSet<String>; 1314 allowedTrackTypes->add(QTMediaTypeVideo); 1315 allowedTrackTypes->add(QTMediaTypeSound); 1316 allowedTrackTypes->add(QTMediaTypeText); 1317 allowedTrackTypes->add(QTMediaTypeBase); 1318 allowedTrackTypes->add(QTMediaTypeMPEG); 1319 allowedTrackTypes->add("clcp"); // Closed caption 1320 allowedTrackTypes->add("sbtl"); // Subtitle 1321 allowedTrackTypes->add("odsm"); // MPEG-4 object descriptor stream 1322 allowedTrackTypes->add("sdsm"); // MPEG-4 scene description stream 1323 allowedTrackTypes->add("tmcd"); // timecode 1324 allowedTrackTypes->add("tc64"); // timcode-64 1325 } 1326 1327 NSArray *tracks = [m_qtMovie.get() tracks]; 1328 1329 m_totalTrackCount = [tracks count]; 1330 m_enabledTrackCount = m_totalTrackCount; 1331 for (unsigned trackIndex = 0; trackIndex < m_totalTrackCount; trackIndex++) { 1332 // Grab the track at the current index. If there isn't one there, then 1333 // we can move onto the next one. 1334 QTTrack *track = [tracks objectAtIndex:trackIndex]; 1335 if (!track) 1336 continue; 1337 1338 // Check to see if the track is disabled already, we should move along. 1339 // We don't need to re-disable it. 1340 if (![track isEnabled]) { 1341 --m_enabledTrackCount; 1342 continue; 1343 } 1344 1345 // Get the track's media type. 1346 NSString *mediaType = [track attributeForKey:QTTrackMediaTypeAttribute]; 1347 if (!mediaType) 1348 continue; 1349 1350 // Test whether the media type is in our white list. 1351 if (!allowedTrackTypes->contains(mediaType)) { 1352 // If this track type is not allowed, then we need to disable it. 1353 [track setEnabled:NO]; 1354 --m_enabledTrackCount; 1355 m_hasUnsupportedTracks = true; 1356 } 1357 1358 // Disable chapter tracks. These are most likely to lead to trouble, as 1359 // they will be composited under the video tracks, forcing QT to do extra 1360 // work. 1361 QTTrack *chapterTrack = [track performSelector:@selector(chapterlist)]; 1362 if (!chapterTrack) 1363 continue; 1364 1365 // Try to grab the media for the track. 1366 QTMedia *chapterMedia = [chapterTrack media]; 1367 if (!chapterMedia) 1368 continue; 1369 1370 // Grab the media type for this track. 1371 id chapterMediaType = [chapterMedia attributeForKey:QTMediaTypeAttribute]; 1372 if (!chapterMediaType) 1373 continue; 1374 1375 // Check to see if the track is a video track. We don't care about 1376 // other non-video tracks. 1377 if (![chapterMediaType isEqual:QTMediaTypeVideo]) 1378 continue; 1379 1380 // Check to see if the track is already disabled. If it is, we 1381 // should move along. 1382 if (![chapterTrack isEnabled]) 1383 continue; 1384 1385 // Disable the evil, evil track. 1386 [chapterTrack setEnabled:NO]; 1387 --m_enabledTrackCount; 1388 m_hasUnsupportedTracks = true; 1389 } 1390 } 1391 1392 void MediaPlayerPrivate::sawUnsupportedTracks() 1393 { 1394 m_hasUnsupportedTracks = true; 1395 m_player->mediaPlayerClient()->mediaPlayerSawUnsupportedTracks(m_player); 1396 } 1397 1398 #if USE(ACCELERATED_COMPOSITING) 1399 bool MediaPlayerPrivate::supportsAcceleratedRendering() const 1400 { 1401 // When in the media document we render via QTMovieView, which is already accelerated. 1402 return isReadyForRendering() && getQTMovieLayerClass() != Nil && !m_player->inMediaDocument(); 1403 } 1404 1405 void MediaPlayerPrivate::acceleratedRenderingStateChanged() 1406 { 1407 // Set up or change the rendering path if necessary. 1408 setUpVideoRendering(); 1409 1410 if (currentRenderingMode() == MediaRenderingMovieLayer) { 1411 GraphicsLayer* videoGraphicsLayer = m_player->mediaPlayerClient()->mediaPlayerGraphicsLayer(m_player); 1412 if (videoGraphicsLayer) 1413 videoGraphicsLayer->setContentsToMedia(m_qtVideoLayer.get()); 1414 } 1415 } 1416 #endif 1417 1418 bool MediaPlayerPrivate::hasSingleSecurityOrigin() const 1419 { 1420 // We tell quicktime to disallow resources that come from different origins 1421 // so we know all media is single origin. 1422 return true; 1423 } 1424 1425 MediaPlayer::MovieLoadType MediaPlayerPrivate::movieLoadType() const 1426 { 1427 if (!m_qtMovie) 1428 return MediaPlayer::Unknown; 1429 1430 MediaPlayer::MovieLoadType movieType = (MediaPlayer::MovieLoadType)wkQTMovieGetType(m_qtMovie.get()); 1431 1432 // Can't include WebKitSystemInterface from WebCore so we can't get the enum returned 1433 // by wkQTMovieGetType, but at least verify that the value is in the valid range. 1434 ASSERT(movieType >= MediaPlayer::Unknown && movieType <= MediaPlayer::LiveStream); 1435 1436 return movieType; 1437 } 1438 1439 1440 } // namespace WebCore 1441 1442 @implementation WebCoreMovieObserver 1443 1444 - (id)initWithCallback:(MediaPlayerPrivate*)callback 1445 { 1446 m_callback = callback; 1447 return [super init]; 1448 } 1449 1450 - (void)disconnect 1451 { 1452 [NSObject cancelPreviousPerformRequestsWithTarget:self]; 1453 m_callback = 0; 1454 } 1455 1456 -(NSMenu*)menuForEventDelegate:(NSEvent*)theEvent 1457 { 1458 // Get the contextual menu from the QTMovieView's superview, the frame view 1459 return [[m_view superview] menuForEvent:theEvent]; 1460 } 1461 1462 -(void)setView:(NSView*)view 1463 { 1464 m_view = view; 1465 } 1466 1467 -(void)repaint 1468 { 1469 if (m_delayCallbacks) 1470 [self performSelector:_cmd withObject:nil afterDelay:0.]; 1471 else if (m_callback) 1472 m_callback->repaint(); 1473 } 1474 1475 - (void)loadStateChanged:(NSNotification *)unusedNotification 1476 { 1477 UNUSED_PARAM(unusedNotification); 1478 if (m_delayCallbacks) 1479 [self performSelector:_cmd withObject:nil afterDelay:0]; 1480 else 1481 m_callback->loadStateChanged(); 1482 } 1483 1484 - (void)rateChanged:(NSNotification *)unusedNotification 1485 { 1486 UNUSED_PARAM(unusedNotification); 1487 if (m_delayCallbacks) 1488 [self performSelector:_cmd withObject:nil afterDelay:0]; 1489 else 1490 m_callback->rateChanged(); 1491 } 1492 1493 - (void)sizeChanged:(NSNotification *)unusedNotification 1494 { 1495 UNUSED_PARAM(unusedNotification); 1496 if (m_delayCallbacks) 1497 [self performSelector:_cmd withObject:nil afterDelay:0]; 1498 else 1499 m_callback->sizeChanged(); 1500 } 1501 1502 - (void)timeChanged:(NSNotification *)unusedNotification 1503 { 1504 UNUSED_PARAM(unusedNotification); 1505 if (m_delayCallbacks) 1506 [self performSelector:_cmd withObject:nil afterDelay:0]; 1507 else 1508 m_callback->timeChanged(); 1509 } 1510 1511 - (void)didEnd:(NSNotification *)unusedNotification 1512 { 1513 UNUSED_PARAM(unusedNotification); 1514 if (m_delayCallbacks) 1515 [self performSelector:_cmd withObject:nil afterDelay:0]; 1516 else 1517 m_callback->didEnd(); 1518 } 1519 1520 - (void)newImageAvailable:(NSNotification *)unusedNotification 1521 { 1522 UNUSED_PARAM(unusedNotification); 1523 [self repaint]; 1524 } 1525 1526 - (void)setDelayCallbacks:(BOOL)shouldDelay 1527 { 1528 m_delayCallbacks = shouldDelay; 1529 } 1530 1531 @end 1532 1533 #endif 1534