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