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