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 68 static 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 76 SOFT_LINK_FRAMEWORK(QTKit) 77 78 SOFT_LINK(QTKit, QTMakeTime, QTTime, (long long timeValue, long timeScale), (timeValue, timeScale)) 79 80 SOFT_LINK_CLASS(QTKit, QTMovie) 81 SOFT_LINK_CLASS(QTKit, QTMovieView) 82 SOFT_LINK_CLASS(QTKit, QTMovieLayer) 83 84 SOFT_LINK_POINTER(QTKit, QTTrackMediaTypeAttribute, NSString *) 85 SOFT_LINK_POINTER(QTKit, QTMediaTypeAttribute, NSString *) 86 SOFT_LINK_POINTER(QTKit, QTMediaTypeBase, NSString *) 87 SOFT_LINK_POINTER(QTKit, QTMediaTypeMPEG, NSString *) 88 SOFT_LINK_POINTER(QTKit, QTMediaTypeSound, NSString *) 89 SOFT_LINK_POINTER(QTKit, QTMediaTypeText, NSString *) 90 SOFT_LINK_POINTER(QTKit, QTMediaTypeVideo, NSString *) 91 SOFT_LINK_POINTER(QTKit, QTMovieAskUnresolvedDataRefsAttribute, NSString *) 92 SOFT_LINK_POINTER(QTKit, QTMovieLoopsAttribute, NSString *) 93 SOFT_LINK_POINTER(QTKit, QTMovieDataAttribute, NSString *) 94 SOFT_LINK_POINTER(QTKit, QTMovieDataSizeAttribute, NSString *) 95 SOFT_LINK_POINTER(QTKit, QTMovieDidEndNotification, NSString *) 96 SOFT_LINK_POINTER(QTKit, QTMovieHasVideoAttribute, NSString *) 97 SOFT_LINK_POINTER(QTKit, QTMovieHasAudioAttribute, NSString *) 98 SOFT_LINK_POINTER(QTKit, QTMovieIsActiveAttribute, NSString *) 99 SOFT_LINK_POINTER(QTKit, QTMovieLoadStateAttribute, NSString *) 100 SOFT_LINK_POINTER(QTKit, QTMovieLoadStateDidChangeNotification, NSString *) 101 SOFT_LINK_POINTER(QTKit, QTMovieNaturalSizeAttribute, NSString *) 102 SOFT_LINK_POINTER(QTKit, QTMovieCurrentSizeAttribute, NSString *) 103 SOFT_LINK_POINTER(QTKit, QTMoviePreventExternalURLLinksAttribute, NSString *) 104 SOFT_LINK_POINTER(QTKit, QTMovieRateChangesPreservePitchAttribute, NSString *) 105 SOFT_LINK_POINTER(QTKit, QTMovieRateDidChangeNotification, NSString *) 106 SOFT_LINK_POINTER(QTKit, QTMovieSizeDidChangeNotification, NSString *) 107 SOFT_LINK_POINTER(QTKit, QTMovieTimeDidChangeNotification, NSString *) 108 SOFT_LINK_POINTER(QTKit, QTMovieTimeScaleAttribute, NSString *) 109 SOFT_LINK_POINTER(QTKit, QTMovieURLAttribute, NSString *) 110 SOFT_LINK_POINTER(QTKit, QTMovieVolumeDidChangeNotification, NSString *) 111 SOFT_LINK_POINTER(QTKit, QTSecurityPolicyNoCrossSiteAttribute, NSString *) 112 SOFT_LINK_POINTER(QTKit, QTVideoRendererWebKitOnlyNewImageAvailableNotification, NSString *) 113 #ifndef BUILDING_ON_TIGER 114 SOFT_LINK_POINTER(QTKit, QTMovieApertureModeClean, NSString *) 115 SOFT_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 158 enum { 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 171 using namespace WebCore; 172 using 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 197 namespace WebCore { 198 199 #ifdef BUILDING_ON_TIGER 200 static const long minimumQuickTimeVersion = 0x07300000; // 7.3 201 #endif 202 203 204 MediaPlayerPrivateInterface* MediaPlayerPrivateQTKit::create(MediaPlayer* player) 205 { 206 return new MediaPlayerPrivateQTKit(player); 207 } 208 209 void MediaPlayerPrivateQTKit::registerMediaEngine(MediaEngineRegistrar registrar) 210 { 211 if (isAvailable()) 212 registrar(create, getSupportedTypes, supportsType, 0, 0, 0); 213 } 214 215 MediaPlayerPrivateQTKit::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 245 MediaPlayerPrivateQTKit::~MediaPlayerPrivateQTKit() 246 { 247 tearDownVideoRendering(); 248 249 [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()]; 250 [m_objcObserver.get() disconnect]; 251 } 252 253 NSMutableDictionary* 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 268 void 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 308 void 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 335 static 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 366 void 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 427 static 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 443 static Class QTVideoRendererClass() 444 { 445 static Class QTVideoRendererWebKitOnlyClass = NSClassFromString(@"QTVideoRendererWebKitOnly"); 446 return QTVideoRendererWebKitOnlyClass; 447 } 448 449 void 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 494 void 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 509 void 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 529 void 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 545 void 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 567 void 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 579 MediaPlayerPrivateQTKit::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 593 MediaPlayerPrivateQTKit::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 609 void 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 640 void MediaPlayerPrivateQTKit::tearDownVideoRendering() 641 { 642 if (m_qtMovieView) 643 detachQTMovieView(); 644 if (m_qtVideoRenderer) 645 destroyQTVideoRenderer(); 646 if (m_qtVideoLayer) 647 destroyQTMovieLayer(); 648 } 649 650 bool MediaPlayerPrivateQTKit::hasSetUpVideoRendering() const 651 { 652 return m_qtMovieView 653 || m_qtVideoLayer 654 || m_qtVideoRenderer; 655 } 656 657 QTTime 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 665 void MediaPlayerPrivateQTKit::resumeLoad() 666 { 667 m_delayingLoad = false; 668 669 if (!m_movieURL.isNull()) 670 loadInternal(m_movieURL); 671 } 672 673 void 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 687 void 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 716 void MediaPlayerPrivateQTKit::prepareToPlay() 717 { 718 if (!m_qtMovie || m_delayingLoad) 719 resumeLoad(); 720 } 721 722 PlatformMedia 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) 731 PlatformLayer* MediaPlayerPrivateQTKit::platformLayer() const 732 { 733 return m_qtVideoLayer.get(); 734 } 735 #endif 736 737 void 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 750 void 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 763 float 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 777 float 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 785 void 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 806 void 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 826 void MediaPlayerPrivateQTKit::cancelSeek() 827 { 828 m_seekTo = -1; 829 m_seekTimer.stop(); 830 } 831 832 void 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 853 bool MediaPlayerPrivateQTKit::paused() const 854 { 855 if (!metaDataAvailable()) 856 return true; 857 return [m_qtMovie.get() rate] == 0; 858 } 859 860 bool MediaPlayerPrivateQTKit::seeking() const 861 { 862 if (!metaDataAvailable()) 863 return false; 864 return m_seekTo >= 0; 865 } 866 867 IntSize 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 894 bool MediaPlayerPrivateQTKit::hasVideo() const 895 { 896 if (!metaDataAvailable()) 897 return false; 898 return [[m_qtMovie.get() attributeForKey:QTMovieHasVideoAttribute] boolValue]; 899 } 900 901 bool MediaPlayerPrivateQTKit::hasAudio() const 902 { 903 if (!m_qtMovie) 904 return false; 905 return [[m_qtMovie.get() attributeForKey:QTMovieHasAudioAttribute] boolValue]; 906 } 907 908 bool 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 918 void MediaPlayerPrivateQTKit::setVolume(float volume) 919 { 920 if (m_qtMovie) 921 [m_qtMovie.get() setVolume:volume]; 922 } 923 924 bool MediaPlayerPrivateQTKit::hasClosedCaptions() const 925 { 926 if (!metaDataAvailable()) 927 return false; 928 return wkQTMovieHasClosedCaptions(m_qtMovie.get()); 929 } 930 931 void 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 945 void MediaPlayerPrivateQTKit::setRate(float rate) 946 { 947 if (m_qtMovie) 948 [m_qtMovie.get() setRate:rate]; 949 } 950 951 void 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 969 PassRefPtr<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 978 float 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 990 float MediaPlayerPrivateQTKit::maxTimeLoaded() const 991 { 992 if (!metaDataAvailable()) 993 return 0; 994 return wkQTMovieMaxTimeLoaded(m_qtMovie.get()); 995 } 996 997 unsigned MediaPlayerPrivateQTKit::bytesLoaded() const 998 { 999 float dur = duration(); 1000 if (!dur) 1001 return 0; 1002 return totalBytes() * maxTimeLoaded() / dur; 1003 } 1004 1005 unsigned MediaPlayerPrivateQTKit::totalBytes() const 1006 { 1007 if (!metaDataAvailable()) 1008 return 0; 1009 return [[m_qtMovie.get() attributeForKey:QTMovieDataSizeAttribute] intValue]; 1010 } 1011 1012 void 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 1024 void 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 1049 bool MediaPlayerPrivateQTKit::isReadyForVideoSetup() const 1050 { 1051 return m_readyState >= MediaPlayer::HaveMetadata && m_player->visible(); 1052 } 1053 1054 void 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 1069 void 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 1184 void MediaPlayerPrivateQTKit::loadStateChanged() 1185 { 1186 if (!m_hasUnsupportedTracks) 1187 updateStates(); 1188 } 1189 1190 void MediaPlayerPrivateQTKit::rateChanged() 1191 { 1192 if (m_hasUnsupportedTracks) 1193 return; 1194 1195 updateStates(); 1196 m_player->rateChanged(); 1197 } 1198 1199 void MediaPlayerPrivateQTKit::sizeChanged() 1200 { 1201 if (!m_hasUnsupportedTracks) 1202 m_player->sizeChanged(); 1203 } 1204 1205 void 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 1221 void 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 1243 void 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 1254 void 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 1265 bool 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 1279 void 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 1297 void 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 1311 void 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 1392 static 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 1420 static 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 1434 static 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 1448 void 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 1461 MediaPlayer::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 1473 bool 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 1494 void 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 1584 void MediaPlayerPrivateQTKit::sawUnsupportedTracks() 1585 { 1586 m_hasUnsupportedTracks = true; 1587 m_player->mediaPlayerClient()->mediaPlayerSawUnsupportedTracks(m_player); 1588 } 1589 1590 #if USE(ACCELERATED_COMPOSITING) 1591 bool MediaPlayerPrivateQTKit::supportsAcceleratedRendering() const 1592 { 1593 return isReadyForVideoSetup() && getQTMovieLayerClass() != Nil; 1594 } 1595 1596 void MediaPlayerPrivateQTKit::acceleratedRenderingStateChanged() 1597 { 1598 // Set up or change the rendering path if necessary. 1599 setUpVideoRendering(); 1600 } 1601 #endif 1602 1603 bool 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 1610 MediaPlayer::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 1624 void MediaPlayerPrivateQTKit::setPreload(MediaPlayer::Preload preload) 1625 { 1626 m_preload = preload; 1627 if (m_delayingLoad && m_preload != MediaPlayer::None) 1628 resumeLoad(); 1629 } 1630 1631 float 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 1640 void 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