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 61static 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 69SOFT_LINK_FRAMEWORK(QTKit) 70 71SOFT_LINK(QTKit, QTMakeTime, QTTime, (long long timeValue, long timeScale), (timeValue, timeScale)) 72 73SOFT_LINK_CLASS(QTKit, QTMovie) 74SOFT_LINK_CLASS(QTKit, QTMovieView) 75SOFT_LINK_CLASS(QTKit, QTMovieLayer) 76 77SOFT_LINK_POINTER(QTKit, QTTrackMediaTypeAttribute, NSString *) 78SOFT_LINK_POINTER(QTKit, QTMediaTypeAttribute, NSString *) 79SOFT_LINK_POINTER(QTKit, QTMediaTypeBase, NSString *) 80SOFT_LINK_POINTER(QTKit, QTMediaTypeMPEG, NSString *) 81SOFT_LINK_POINTER(QTKit, QTMediaTypeSound, NSString *) 82SOFT_LINK_POINTER(QTKit, QTMediaTypeText, NSString *) 83SOFT_LINK_POINTER(QTKit, QTMediaTypeVideo, NSString *) 84SOFT_LINK_POINTER(QTKit, QTMovieAskUnresolvedDataRefsAttribute, NSString *) 85SOFT_LINK_POINTER(QTKit, QTMovieDataSizeAttribute, NSString *) 86SOFT_LINK_POINTER(QTKit, QTMovieDidEndNotification, NSString *) 87SOFT_LINK_POINTER(QTKit, QTMovieHasVideoAttribute, NSString *) 88SOFT_LINK_POINTER(QTKit, QTMovieHasAudioAttribute, NSString *) 89SOFT_LINK_POINTER(QTKit, QTMovieIsActiveAttribute, NSString *) 90SOFT_LINK_POINTER(QTKit, QTMovieLoadStateAttribute, NSString *) 91SOFT_LINK_POINTER(QTKit, QTMovieLoadStateDidChangeNotification, NSString *) 92SOFT_LINK_POINTER(QTKit, QTMovieNaturalSizeAttribute, NSString *) 93SOFT_LINK_POINTER(QTKit, QTMovieCurrentSizeAttribute, NSString *) 94SOFT_LINK_POINTER(QTKit, QTMoviePreventExternalURLLinksAttribute, NSString *) 95SOFT_LINK_POINTER(QTKit, QTMovieRateChangesPreservePitchAttribute, NSString *) 96SOFT_LINK_POINTER(QTKit, QTMovieRateDidChangeNotification, NSString *) 97SOFT_LINK_POINTER(QTKit, QTMovieSizeDidChangeNotification, NSString *) 98SOFT_LINK_POINTER(QTKit, QTMovieTimeDidChangeNotification, NSString *) 99SOFT_LINK_POINTER(QTKit, QTMovieTimeScaleAttribute, NSString *) 100SOFT_LINK_POINTER(QTKit, QTMovieURLAttribute, NSString *) 101SOFT_LINK_POINTER(QTKit, QTMovieVolumeDidChangeNotification, NSString *) 102SOFT_LINK_POINTER(QTKit, QTSecurityPolicyNoCrossSiteAttribute, NSString *) 103SOFT_LINK_POINTER(QTKit, QTVideoRendererWebKitOnlyNewImageAvailableNotification, NSString *) 104#ifndef BUILDING_ON_TIGER 105SOFT_LINK_POINTER(QTKit, QTMovieApertureModeClean, NSString *) 106SOFT_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 147enum { 148 QTMovieLoadStateError = -1L, 149 QTMovieLoadStateLoaded = 2000L, 150 QTMovieLoadStatePlayable = 10000L, 151 QTMovieLoadStatePlaythroughOK = 20000L, 152 QTMovieLoadStateComplete = 100000L 153}; 154#endif 155 156using namespace WebCore; 157using 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 182namespace WebCore { 183 184#ifdef BUILDING_ON_TIGER 185static const long minimumQuickTimeVersion = 0x07300000; // 7.3 186#endif 187 188 189MediaPlayerPrivateInterface* MediaPlayerPrivate::create(MediaPlayer* player) 190{ 191 return new MediaPlayerPrivate(player); 192} 193 194void MediaPlayerPrivate::registerMediaEngine(MediaEngineRegistrar registrar) 195{ 196 if (isAvailable()) 197 registrar(create, getSupportedTypes, supportsType); 198} 199 200MediaPlayerPrivate::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 227MediaPlayerPrivate::~MediaPlayerPrivate() 228{ 229 tearDownVideoRendering(); 230 231 [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()]; 232 [m_objcObserver.get() disconnect]; 233} 234 235void 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 255void 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 314static 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 329static Class QTVideoRendererClass() 330{ 331 static Class QTVideoRendererWebKitOnlyClass = NSClassFromString(@"QTVideoRendererWebKitOnly"); 332 return QTVideoRendererWebKitOnlyClass; 333} 334 335void 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 380void 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 395void 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 415void 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 431void 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 458void 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 470MediaPlayerPrivate::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 484MediaPlayerPrivate::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 500void 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 527void MediaPlayerPrivate::tearDownVideoRendering() 528{ 529 if (m_qtMovieView) 530 detachQTMovieView(); 531 if (m_qtVideoRenderer) 532 destroyQTVideoRenderer(); 533 if (m_qtVideoLayer) 534 destroyQTMovieLayer(); 535} 536 537bool MediaPlayerPrivate::hasSetUpVideoRendering() const 538{ 539 return m_qtMovieView 540 || m_qtVideoLayer 541 || m_qtVideoRenderer; 542} 543 544QTTime 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 552void 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 573PlatformMedia MediaPlayerPrivate::platformMedia() const 574{ 575 PlatformMedia plaftformMedia = { m_qtMovie.get() }; 576 return plaftformMedia; 577} 578 579void 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 592void 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 605float 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 619float 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 627void 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 648void 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 668void MediaPlayerPrivate::cancelSeek() 669{ 670 m_seekTo = -1; 671 m_seekTimer.stop(); 672} 673 674void 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 695bool MediaPlayerPrivate::paused() const 696{ 697 if (!metaDataAvailable()) 698 return true; 699 return [m_qtMovie.get() rate] == 0; 700} 701 702bool MediaPlayerPrivate::seeking() const 703{ 704 if (!metaDataAvailable()) 705 return false; 706 return m_seekTo >= 0; 707} 708 709IntSize 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 725bool MediaPlayerPrivate::hasVideo() const 726{ 727 if (!metaDataAvailable()) 728 return false; 729 return [[m_qtMovie.get() attributeForKey:QTMovieHasVideoAttribute] boolValue]; 730} 731 732bool MediaPlayerPrivate::hasAudio() const 733{ 734 if (!m_qtMovie) 735 return false; 736 return [[m_qtMovie.get() attributeForKey:QTMovieHasAudioAttribute] boolValue]; 737} 738 739bool 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 749void MediaPlayerPrivate::setVolume(float volume) 750{ 751 if (m_qtMovie) 752 [m_qtMovie.get() setVolume:volume]; 753} 754 755bool MediaPlayerPrivate::hasClosedCaptions() const 756{ 757 if (!metaDataAvailable()) 758 return false; 759 return wkQTMovieHasClosedCaptions(m_qtMovie.get()); 760} 761 762void 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 776void MediaPlayerPrivate::setRate(float rate) 777{ 778 if (m_qtMovie) 779 [m_qtMovie.get() setRate:rate]; 780} 781 782void 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 800PassRefPtr<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 809float 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 821float MediaPlayerPrivate::maxTimeLoaded() const 822{ 823 if (!metaDataAvailable()) 824 return 0; 825 return wkQTMovieMaxTimeLoaded(m_qtMovie.get()); 826} 827 828unsigned MediaPlayerPrivate::bytesLoaded() const 829{ 830 float dur = duration(); 831 if (!dur) 832 return 0; 833 return totalBytes() * maxTimeLoaded() / dur; 834} 835 836unsigned MediaPlayerPrivate::totalBytes() const 837{ 838 if (!metaDataAvailable()) 839 return 0; 840 return [[m_qtMovie.get() attributeForKey:QTMovieDataSizeAttribute] intValue]; 841} 842 843void 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 855void 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 880bool MediaPlayerPrivate::isReadyForRendering() const 881{ 882 return m_readyState >= MediaPlayer::HaveMetadata && m_player->visible(); 883} 884 885void 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 999void MediaPlayerPrivate::loadStateChanged() 1000{ 1001 if (!m_hasUnsupportedTracks) 1002 updateStates(); 1003} 1004 1005void MediaPlayerPrivate::rateChanged() 1006{ 1007 if (m_hasUnsupportedTracks) 1008 return; 1009 1010 updateStates(); 1011 m_player->rateChanged(); 1012} 1013 1014void MediaPlayerPrivate::sizeChanged() 1015{ 1016 if (!m_hasUnsupportedTracks) 1017 m_player->sizeChanged(); 1018} 1019 1020void 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 1036void 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 1058void 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 1069void 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 1080bool 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 1094void 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 1112void 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 1126void 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 1207static 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 1235static 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 1249static 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 1263void 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 1270MediaPlayer::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 1282bool 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 1303void 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 1392void MediaPlayerPrivate::sawUnsupportedTracks() 1393{ 1394 m_hasUnsupportedTracks = true; 1395 m_player->mediaPlayerClient()->mediaPlayerSawUnsupportedTracks(m_player); 1396} 1397 1398#if USE(ACCELERATED_COMPOSITING) 1399bool 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 1405void 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 1418bool 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 1425MediaPlayer::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