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