/* * Copyright (C) 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import "config.h" #if ENABLE(VIDEO) #import "MediaPlayerPrivateQTKit.h" #import "BlockExceptions.h" #import "DocumentLoader.h" #import "Frame.h" #import "FrameView.h" #import "HostWindow.h" #import "GraphicsContext.h" #import "KURL.h" #import "Logging.h" #import "MIMETypeRegistry.h" #import "SecurityOrigin.h" #import "SoftLinking.h" #import "TimeRanges.h" #import "WebCoreSystemInterface.h" #import #import #if USE(ACCELERATED_COMPOSITING) #include "PlatformLayer.h" #endif #if DRAW_FRAME_RATE #import "Font.h" #import "Document.h" #import "RenderObject.h" #import "RenderStyle.h" #endif SOFT_LINK_FRAMEWORK(QTKit) SOFT_LINK(QTKit, QTMakeTime, QTTime, (long long timeValue, long timeScale), (timeValue, timeScale)) SOFT_LINK_CLASS(QTKit, QTMovie) SOFT_LINK_CLASS(QTKit, QTMovieView) SOFT_LINK_CLASS(QTKit, QTMovieLayer) SOFT_LINK_POINTER(QTKit, QTTrackMediaTypeAttribute, NSString *) SOFT_LINK_POINTER(QTKit, QTMediaTypeAttribute, NSString *) SOFT_LINK_POINTER(QTKit, QTMediaTypeBase, NSString *) SOFT_LINK_POINTER(QTKit, QTMediaTypeMPEG, NSString *) SOFT_LINK_POINTER(QTKit, QTMediaTypeSound, NSString *) SOFT_LINK_POINTER(QTKit, QTMediaTypeText, NSString *) SOFT_LINK_POINTER(QTKit, QTMediaTypeVideo, NSString *) SOFT_LINK_POINTER(QTKit, QTMovieAskUnresolvedDataRefsAttribute, NSString *) SOFT_LINK_POINTER(QTKit, QTMovieLoopsAttribute, NSString *) SOFT_LINK_POINTER(QTKit, QTMovieDataAttribute, NSString *) SOFT_LINK_POINTER(QTKit, QTMovieDataSizeAttribute, NSString *) SOFT_LINK_POINTER(QTKit, QTMovieDidEndNotification, NSString *) SOFT_LINK_POINTER(QTKit, QTMovieHasVideoAttribute, NSString *) SOFT_LINK_POINTER(QTKit, QTMovieHasAudioAttribute, NSString *) SOFT_LINK_POINTER(QTKit, QTMovieIsActiveAttribute, NSString *) SOFT_LINK_POINTER(QTKit, QTMovieLoadStateAttribute, NSString *) SOFT_LINK_POINTER(QTKit, QTMovieLoadStateDidChangeNotification, NSString *) SOFT_LINK_POINTER(QTKit, QTMovieNaturalSizeAttribute, NSString *) SOFT_LINK_POINTER(QTKit, QTMovieCurrentSizeAttribute, NSString *) SOFT_LINK_POINTER(QTKit, QTMoviePreventExternalURLLinksAttribute, NSString *) SOFT_LINK_POINTER(QTKit, QTMovieRateChangesPreservePitchAttribute, NSString *) SOFT_LINK_POINTER(QTKit, QTMovieRateDidChangeNotification, NSString *) SOFT_LINK_POINTER(QTKit, QTMovieSizeDidChangeNotification, NSString *) SOFT_LINK_POINTER(QTKit, QTMovieTimeDidChangeNotification, NSString *) SOFT_LINK_POINTER(QTKit, QTMovieTimeScaleAttribute, NSString *) SOFT_LINK_POINTER(QTKit, QTMovieURLAttribute, NSString *) SOFT_LINK_POINTER(QTKit, QTMovieVolumeDidChangeNotification, NSString *) SOFT_LINK_POINTER(QTKit, QTSecurityPolicyNoCrossSiteAttribute, NSString *) SOFT_LINK_POINTER(QTKit, QTVideoRendererWebKitOnlyNewImageAvailableNotification, NSString *) SOFT_LINK_POINTER(QTKit, QTMovieApertureModeClean, NSString *) SOFT_LINK_POINTER(QTKit, QTMovieApertureModeAttribute, NSString *) SOFT_LINK_POINTER_OPTIONAL(QTKit, QTSecurityPolicyNoLocalToRemoteSiteAttribute, NSString *) SOFT_LINK_POINTER_OPTIONAL(QTKit, QTSecurityPolicyNoRemoteToLocalSiteAttribute, NSString *) #define QTMovie getQTMovieClass() #define QTMovieView getQTMovieViewClass() #define QTMovieLayer getQTMovieLayerClass() #define QTTrackMediaTypeAttribute getQTTrackMediaTypeAttribute() #define QTMediaTypeAttribute getQTMediaTypeAttribute() #define QTMediaTypeBase getQTMediaTypeBase() #define QTMediaTypeMPEG getQTMediaTypeMPEG() #define QTMediaTypeSound getQTMediaTypeSound() #define QTMediaTypeText getQTMediaTypeText() #define QTMediaTypeVideo getQTMediaTypeVideo() #define QTMovieAskUnresolvedDataRefsAttribute getQTMovieAskUnresolvedDataRefsAttribute() #define QTMovieLoopsAttribute getQTMovieLoopsAttribute() #define QTMovieDataAttribute getQTMovieDataAttribute() #define QTMovieDataSizeAttribute getQTMovieDataSizeAttribute() #define QTMovieDidEndNotification getQTMovieDidEndNotification() #define QTMovieHasVideoAttribute getQTMovieHasVideoAttribute() #define QTMovieHasAudioAttribute getQTMovieHasAudioAttribute() #define QTMovieIsActiveAttribute getQTMovieIsActiveAttribute() #define QTMovieLoadStateAttribute getQTMovieLoadStateAttribute() #define QTMovieLoadStateDidChangeNotification getQTMovieLoadStateDidChangeNotification() #define QTMovieNaturalSizeAttribute getQTMovieNaturalSizeAttribute() #define QTMovieCurrentSizeAttribute getQTMovieCurrentSizeAttribute() #define QTMoviePreventExternalURLLinksAttribute getQTMoviePreventExternalURLLinksAttribute() #define QTMovieRateChangesPreservePitchAttribute getQTMovieRateChangesPreservePitchAttribute() #define QTMovieRateDidChangeNotification getQTMovieRateDidChangeNotification() #define QTMovieSizeDidChangeNotification getQTMovieSizeDidChangeNotification() #define QTMovieTimeDidChangeNotification getQTMovieTimeDidChangeNotification() #define QTMovieTimeScaleAttribute getQTMovieTimeScaleAttribute() #define QTMovieURLAttribute getQTMovieURLAttribute() #define QTMovieVolumeDidChangeNotification getQTMovieVolumeDidChangeNotification() #define QTSecurityPolicyNoCrossSiteAttribute getQTSecurityPolicyNoCrossSiteAttribute() #define QTSecurityPolicyNoLocalToRemoteSiteAttribute getQTSecurityPolicyNoLocalToRemoteSiteAttribute() #define QTSecurityPolicyNoRemoteToLocalSiteAttribute getQTSecurityPolicyNoRemoteToLocalSiteAttribute() #define QTVideoRendererWebKitOnlyNewImageAvailableNotification getQTVideoRendererWebKitOnlyNewImageAvailableNotification() #define QTMovieApertureModeClean getQTMovieApertureModeClean() #define QTMovieApertureModeAttribute getQTMovieApertureModeAttribute() // Older versions of the QTKit header don't have these constants. #if !defined QTKIT_VERSION_MAX_ALLOWED || QTKIT_VERSION_MAX_ALLOWED <= QTKIT_VERSION_7_0 enum { QTMovieLoadStateError = -1L, QTMovieLoadStateLoaded = 2000L, QTMovieLoadStatePlayable = 10000L, QTMovieLoadStatePlaythroughOK = 20000L, QTMovieLoadStateComplete = 100000L }; #endif @interface FakeQTMovieView : NSObject - (WebCoreMovieObserver *)delegate; @end using namespace WebCore; using namespace std; @interface WebCoreMovieObserver : NSObject { MediaPlayerPrivateQTKit* m_callback; NSView* m_view; BOOL m_delayCallbacks; } -(id)initWithCallback:(MediaPlayerPrivateQTKit*)callback; -(void)disconnect; -(void)setView:(NSView*)view; -(void)repaint; -(void)setDelayCallbacks:(BOOL)shouldDelay; -(void)loadStateChanged:(NSNotification *)notification; -(void)rateChanged:(NSNotification *)notification; -(void)sizeChanged:(NSNotification *)notification; -(void)timeChanged:(NSNotification *)notification; -(void)didEnd:(NSNotification *)notification; -(void)layerHostChanged:(NSNotification *)notification; @end @protocol WebKitVideoRenderingDetails -(void)setMovie:(id)movie; -(void)drawInRect:(NSRect)rect; @end namespace WebCore { PassOwnPtr MediaPlayerPrivateQTKit::create(MediaPlayer* player) { return adoptPtr(new MediaPlayerPrivateQTKit(player)); } void MediaPlayerPrivateQTKit::registerMediaEngine(MediaEngineRegistrar registrar) { if (isAvailable()) #if ENABLE(ENCRYPTED_MEDIA) || ENABLE(ENCRYPTED_MEDIA_V2) registrar(create, getSupportedTypes, extendedSupportsType, getSitesInMediaCache, clearMediaCache, clearMediaCacheForSite); #else registrar(create, getSupportedTypes, supportsType, getSitesInMediaCache, clearMediaCache, clearMediaCacheForSite); #endif } MediaPlayerPrivateQTKit::MediaPlayerPrivateQTKit(MediaPlayer* player) : m_player(player) , m_objcObserver(adoptNS([[WebCoreMovieObserver alloc] initWithCallback:this])) , m_seekTo(-1) , m_seekTimer(this, &MediaPlayerPrivateQTKit::seekTimerFired) , m_networkState(MediaPlayer::Empty) , m_readyState(MediaPlayer::HaveNothing) , m_rect() , m_scaleFactor(1, 1) , m_enabledTrackCount(0) , m_totalTrackCount(0) , m_reportedDuration(-1) , m_cachedDuration(-1) , m_timeToRestore(-1) , m_preload(MediaPlayer::Auto) , m_startedPlaying(false) , m_isStreaming(false) , m_visible(false) , m_hasUnsupportedTracks(false) , m_videoFrameHasDrawn(false) , m_isAllowedToRender(false) , m_privateBrowsing(false) , m_maxTimeLoadedAtLastDidLoadingProgress(0) #if DRAW_FRAME_RATE , m_frameCountWhilePlaying(0) , m_timeStartedPlaying(0) , m_timeStoppedPlaying(0) #endif { } MediaPlayerPrivateQTKit::~MediaPlayerPrivateQTKit() { LOG(Media, "MediaPlayerPrivateQTKit::~MediaPlayerPrivateQTKit(%p)", this); tearDownVideoRendering(); [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()]; [m_objcObserver.get() disconnect]; } NSMutableDictionary *MediaPlayerPrivateQTKit::commonMovieAttributes() { NSMutableDictionary *movieAttributes = [NSMutableDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool:m_player->preservesPitch()], QTMovieRateChangesPreservePitchAttribute, [NSNumber numberWithBool:YES], QTMoviePreventExternalURLLinksAttribute, [NSNumber numberWithBool:NO], QTMovieAskUnresolvedDataRefsAttribute, [NSNumber numberWithBool:NO], QTMovieLoopsAttribute, [NSNumber numberWithBool:!m_privateBrowsing], @"QTMovieAllowPersistentCacheAttribute", QTMovieApertureModeClean, QTMovieApertureModeAttribute, nil]; // Check to see if QTSecurityPolicyNoRemoteToLocalSiteAttribute is defined, which was added in QTKit 7.6.3. // If not, just set NoCrossSite = YES, which does the same thing as NoRemoteToLocal = YES and // NoLocalToRemote = YES in QTKit < 7.6.3. if (QTSecurityPolicyNoRemoteToLocalSiteAttribute) { [movieAttributes setValue:[NSNumber numberWithBool:NO] forKey:QTSecurityPolicyNoCrossSiteAttribute]; [movieAttributes setValue:[NSNumber numberWithBool:YES] forKey:QTSecurityPolicyNoRemoteToLocalSiteAttribute]; [movieAttributes setValue:[NSNumber numberWithBool:YES] forKey:QTSecurityPolicyNoLocalToRemoteSiteAttribute]; } else [movieAttributes setValue:[NSNumber numberWithBool:YES] forKey:QTSecurityPolicyNoCrossSiteAttribute]; if (m_preload < MediaPlayer::Auto) [movieAttributes setValue:[NSNumber numberWithBool:YES] forKey:@"QTMovieLimitReadAheadAttribute"]; return movieAttributes; } void MediaPlayerPrivateQTKit::createQTMovie(const String& url) { KURL kURL(ParsedURLString, url); NSURL *cocoaURL = kURL; NSMutableDictionary *movieAttributes = commonMovieAttributes(); [movieAttributes setValue:cocoaURL forKey:QTMovieURLAttribute]; CFDictionaryRef proxySettings = CFNetworkCopySystemProxySettings(); CFArrayRef proxiesForURL = CFNetworkCopyProxiesForURL((CFURLRef)cocoaURL, proxySettings); BOOL willUseProxy = YES; if (!proxiesForURL || !CFArrayGetCount(proxiesForURL)) willUseProxy = NO; if (CFArrayGetCount(proxiesForURL) == 1) { CFDictionaryRef proxy = (CFDictionaryRef)CFArrayGetValueAtIndex(proxiesForURL, 0); ASSERT(CFGetTypeID(proxy) == CFDictionaryGetTypeID()); CFStringRef proxyType = (CFStringRef)CFDictionaryGetValue(proxy, kCFProxyTypeKey); ASSERT(CFGetTypeID(proxyType) == CFStringGetTypeID()); if (CFStringCompare(proxyType, kCFProxyTypeNone, 0) == kCFCompareEqualTo) willUseProxy = NO; } if (!willUseProxy && !kURL.protocolIsData()) { // Only pass the QTMovieOpenForPlaybackAttribute flag if there are no proxy servers, due // to rdar://problem/7531776, or if not loading a data:// url due to rdar://problem/8103801. [movieAttributes setObject:[NSNumber numberWithBool:YES] forKey:@"QTMovieOpenForPlaybackAttribute"]; } if (proxiesForURL) CFRelease(proxiesForURL); if (proxySettings) CFRelease(proxySettings); createQTMovie(cocoaURL, movieAttributes); } static void disableComponentsOnce() { static bool sComponentsDisabled = false; if (sComponentsDisabled) return; sComponentsDisabled = true; // eat/PDF and grip/PDF components must be disabled twice since they are registered twice // with different flags. However, there is currently a bug in 64-bit QTKit () // which causes subsequent disable component requests of exactly the same type to be ignored if // QTKitServer has not yet started. As a result, we must pass in exactly the flags we want to // disable per component. As a failsafe, if in the future these flags change, we will disable the // PDF components for a third time with a wildcard flags field: uint32_t componentsToDisable[11][5] = { {'eat ', 'TEXT', 'text', 0, 0}, {'eat ', 'TXT ', 'text', 0, 0}, {'eat ', 'utxt', 'text', 0, 0}, {'eat ', 'TEXT', 'tx3g', 0, 0}, {'eat ', 'PDF ', 'vide', 0x44802, 0}, {'eat ', 'PDF ', 'vide', 0x45802, 0}, {'eat ', 'PDF ', 'vide', 0, 0}, {'grip', 'PDF ', 'appl', 0x844a00, 0}, {'grip', 'PDF ', 'appl', 0x845a00, 0}, {'grip', 'PDF ', 'appl', 0, 0}, {'imdc', 'pdf ', 'appl', 0, 0}, }; for (size_t i = 0; i < WTF_ARRAY_LENGTH(componentsToDisable); ++i) wkQTMovieDisableComponent(componentsToDisable[i]); } void MediaPlayerPrivateQTKit::createQTMovie(NSURL *url, NSDictionary *movieAttributes) { LOG(Media, "MediaPlayerPrivateQTKit::createQTMovie(%p) ", this); disableComponentsOnce(); [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()]; bool recreating = false; if (m_qtMovie) { recreating = true; destroyQTVideoRenderer(); m_qtMovie = 0; } // Disable rtsp streams for now, if (protocolIs([url scheme], "rtsp")) return; NSError *error = nil; m_qtMovie = adoptNS([[QTMovie alloc] initWithAttributes:movieAttributes error:&error]); if (!m_qtMovie) return; [m_qtMovie.get() setVolume:m_player->volume()]; if (recreating && hasVideo()) createQTVideoRenderer(QTVideoRendererModeListensForNewImages); [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get() selector:@selector(loadStateChanged:) name:QTMovieLoadStateDidChangeNotification object:m_qtMovie.get()]; // In updateState(), we track when maxTimeLoaded() == duration(). // In newer version of QuickTime, a notification is emitted when maxTimeLoaded changes. // In older version of QuickTime, QTMovieLoadStateDidChangeNotification be fired. if (NSString *maxTimeLoadedChangeNotification = wkQTMovieMaxTimeLoadedChangeNotification()) { [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get() selector:@selector(loadedRangesChanged:) name:maxTimeLoadedChangeNotification object:m_qtMovie.get()]; } [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get() selector:@selector(rateChanged:) name:QTMovieRateDidChangeNotification object:m_qtMovie.get()]; [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get() selector:@selector(sizeChanged:) name:QTMovieSizeDidChangeNotification object:m_qtMovie.get()]; [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get() selector:@selector(timeChanged:) name:QTMovieTimeDidChangeNotification object:m_qtMovie.get()]; [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get() selector:@selector(didEnd:) name:QTMovieDidEndNotification object:m_qtMovie.get()]; #if __MAC_OS_X_VERSION_MIN_REQUIRED == 1060 [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get() selector:@selector(layerHostChanged:) name:@"WebKitLayerHostChanged" object:nil]; #endif } static void mainThreadSetNeedsDisplay(id self, SEL) { id view = [self superview]; ASSERT(!view || [view isKindOfClass:[QTMovieView class]]); if (!view || ![view isKindOfClass:[QTMovieView class]]) return; FakeQTMovieView *movieView = static_cast(view); WebCoreMovieObserver* delegate = [movieView delegate]; ASSERT(!delegate || [delegate isKindOfClass:[WebCoreMovieObserver class]]); if (!delegate || ![delegate isKindOfClass:[WebCoreMovieObserver class]]) return; [delegate repaint]; } static Class QTVideoRendererClass() { static Class QTVideoRendererWebKitOnlyClass = NSClassFromString(@"QTVideoRendererWebKitOnly"); return QTVideoRendererWebKitOnlyClass; } void MediaPlayerPrivateQTKit::createQTMovieView() { LOG(Media, "MediaPlayerPrivateQTKit::createQTMovieView(%p)", this); detachQTMovieView(); static bool addedCustomMethods = false; if (!m_player->inMediaDocument() && !addedCustomMethods) { Class QTMovieContentViewClass = NSClassFromString(@"QTMovieContentView"); ASSERT(QTMovieContentViewClass); Method mainThreadSetNeedsDisplayMethod = class_getInstanceMethod(QTMovieContentViewClass, @selector(_mainThreadSetNeedsDisplay)); ASSERT(mainThreadSetNeedsDisplayMethod); method_setImplementation(mainThreadSetNeedsDisplayMethod, reinterpret_cast(mainThreadSetNeedsDisplay)); addedCustomMethods = true; } // delay callbacks as we *will* get notifications during setup [m_objcObserver.get() setDelayCallbacks:YES]; m_qtMovieView = adoptNS([[QTMovieView alloc] init]); setSize(m_player->size()); NSView* parentView = 0; #if PLATFORM(MAC) parentView = m_player->frameView()->documentView(); [parentView addSubview:m_qtMovieView.get()]; #endif [m_qtMovieView.get() setDelegate:m_objcObserver.get()]; [m_objcObserver.get() setView:m_qtMovieView.get()]; [m_qtMovieView.get() setMovie:m_qtMovie.get()]; [m_qtMovieView.get() setControllerVisible:NO]; [m_qtMovieView.get() setPreservesAspectRatio:NO]; // the area not covered by video should be transparent [m_qtMovieView.get() setFillColor:[NSColor clearColor]]; // If we're in a media document, allow QTMovieView to render in its default mode; // otherwise tell it to draw synchronously. // Note that we expect mainThreadSetNeedsDisplay to be invoked only when synchronous drawing is requested. if (!m_player->inMediaDocument()) wkQTMovieViewSetDrawSynchronously(m_qtMovieView.get(), YES); [m_objcObserver.get() setDelayCallbacks:NO]; } void MediaPlayerPrivateQTKit::detachQTMovieView() { LOG(Media, "MediaPlayerPrivateQTKit::detachQTMovieView(%p)", this); if (m_qtMovieView) { [m_objcObserver.get() setView:nil]; [m_qtMovieView.get() setDelegate:nil]; [m_qtMovieView.get() removeFromSuperview]; m_qtMovieView = nil; } } void MediaPlayerPrivateQTKit::createQTVideoRenderer(QTVideoRendererMode rendererMode) { LOG(Media, "MediaPlayerPrivateQTKit::createQTVideoRenderer(%p)", this); destroyQTVideoRenderer(); m_qtVideoRenderer = adoptNS([[QTVideoRendererClass() alloc] init]); if (!m_qtVideoRenderer) return; // associate our movie with our instance of QTVideoRendererWebKitOnly [(id)m_qtVideoRenderer.get() setMovie:m_qtMovie.get()]; if (rendererMode == QTVideoRendererModeListensForNewImages) { // listen to QTVideoRendererWebKitOnly's QTVideoRendererWebKitOnlyNewImageDidBecomeAvailableNotification [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get() selector:@selector(newImageAvailable:) name:QTVideoRendererWebKitOnlyNewImageAvailableNotification object:m_qtVideoRenderer.get()]; } } void MediaPlayerPrivateQTKit::destroyQTVideoRenderer() { LOG(Media, "MediaPlayerPrivateQTKit::destroyQTVideoRenderer(%p)", this); if (!m_qtVideoRenderer) return; // stop observing the renderer's notifications before we toss it [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get() name:QTVideoRendererWebKitOnlyNewImageAvailableNotification object:m_qtVideoRenderer.get()]; // disassociate our movie from our instance of QTVideoRendererWebKitOnly [(id)m_qtVideoRenderer.get() setMovie:nil]; m_qtVideoRenderer = nil; } void MediaPlayerPrivateQTKit::createQTMovieLayer() { LOG(Media, "MediaPlayerPrivateQTKit::createQTMovieLayer(%p)", this); #if USE(ACCELERATED_COMPOSITING) if (!m_qtMovie) return; ASSERT(supportsAcceleratedRendering()); if (!m_qtVideoLayer) { m_qtVideoLayer = adoptNS([[QTMovieLayer alloc] init]); if (!m_qtVideoLayer) return; [m_qtVideoLayer.get() setMovie:m_qtMovie.get()]; #ifndef NDEBUG [(CALayer *)m_qtVideoLayer.get() setName:@"Video layer"]; #endif // The layer will get hooked up via RenderLayerBacking::updateGraphicsLayerConfiguration(). } #endif } void MediaPlayerPrivateQTKit::destroyQTMovieLayer() { LOG(Media, "MediaPlayerPrivateQTKit::destroyQTMovieLayer(%p)", this); #if USE(ACCELERATED_COMPOSITING) if (!m_qtVideoLayer) return; // disassociate our movie from our instance of QTMovieLayer [m_qtVideoLayer.get() setMovie:nil]; m_qtVideoLayer = nil; #endif } MediaPlayerPrivateQTKit::MediaRenderingMode MediaPlayerPrivateQTKit::currentRenderingMode() const { if (m_qtMovieView) return MediaRenderingMovieView; if (m_qtVideoLayer) return MediaRenderingMovieLayer; if (m_qtVideoRenderer) return MediaRenderingSoftwareRenderer; return MediaRenderingNone; } MediaPlayerPrivateQTKit::MediaRenderingMode MediaPlayerPrivateQTKit::preferredRenderingMode() const { if (!m_player->frameView() || !m_qtMovie) return MediaRenderingNone; #if USE(ACCELERATED_COMPOSITING) if (supportsAcceleratedRendering() && m_player->mediaPlayerClient()->mediaPlayerRenderingCanBeAccelerated(m_player)) return MediaRenderingMovieLayer; #endif if (!QTVideoRendererClass()) return MediaRenderingMovieView; return MediaRenderingSoftwareRenderer; } void MediaPlayerPrivateQTKit::setUpVideoRendering() { LOG(Media, "MediaPlayerPrivateQTKit::setUpVideoRendering(%p)", this); if (!isReadyForVideoSetup()) return; MediaRenderingMode currentMode = currentRenderingMode(); MediaRenderingMode preferredMode = preferredRenderingMode(); if (currentMode == preferredMode && currentMode != MediaRenderingNone) return; if (currentMode != MediaRenderingNone) tearDownVideoRendering(); switch (preferredMode) { case MediaRenderingMovieView: createQTMovieView(); break; case MediaRenderingNone: case MediaRenderingSoftwareRenderer: createQTVideoRenderer(QTVideoRendererModeListensForNewImages); break; case MediaRenderingMovieLayer: createQTMovieLayer(); break; } // If using a movie layer, inform the client so the compositing tree is updated. if (currentMode == MediaRenderingMovieLayer || preferredMode == MediaRenderingMovieLayer) m_player->mediaPlayerClient()->mediaPlayerRenderingModeChanged(m_player); } void MediaPlayerPrivateQTKit::tearDownVideoRendering() { LOG(Media, "MediaPlayerPrivateQTKit::tearDownVideoRendering(%p)", this); if (m_qtMovieView) detachQTMovieView(); if (m_qtVideoRenderer) destroyQTVideoRenderer(); if (m_qtVideoLayer) destroyQTMovieLayer(); } bool MediaPlayerPrivateQTKit::hasSetUpVideoRendering() const { return m_qtMovieView || m_qtVideoLayer || m_qtVideoRenderer; } QTTime MediaPlayerPrivateQTKit::createQTTime(float time) const { if (!metaDataAvailable()) return QTMakeTime(0, 600); long timeScale = [[m_qtMovie.get() attributeForKey:QTMovieTimeScaleAttribute] longValue]; return QTMakeTime(lroundf(time * timeScale), timeScale); } void MediaPlayerPrivateQTKit::resumeLoad() { if (!m_movieURL.isNull()) loadInternal(m_movieURL); } void MediaPlayerPrivateQTKit::load(const String& url) { LOG(Media, "MediaPlayerPrivateQTKit::load(%p)", this); m_movieURL = url; // If the element is not supposed to load any data return immediately. if (m_preload == MediaPlayer::None) return; loadInternal(url); } void MediaPlayerPrivateQTKit::loadInternal(const String& url) { if (m_networkState != MediaPlayer::Loading) { m_networkState = MediaPlayer::Loading; m_player->networkStateChanged(); } if (m_readyState != MediaPlayer::HaveNothing) { m_readyState = MediaPlayer::HaveNothing; m_player->readyStateChanged(); } cancelSeek(); m_videoFrameHasDrawn = false; [m_objcObserver.get() setDelayCallbacks:YES]; createQTMovie(url); [m_objcObserver.get() loadStateChanged:nil]; [m_objcObserver.get() setDelayCallbacks:NO]; } void MediaPlayerPrivateQTKit::prepareToPlay() { LOG(Media, "MediaPlayerPrivateQTKit::prepareToPlay(%p)", this); setPreload(MediaPlayer::Auto); } PlatformMedia MediaPlayerPrivateQTKit::platformMedia() const { PlatformMedia pm; pm.type = PlatformMedia::QTMovieType; pm.media.qtMovie = m_qtMovie.get(); return pm; } #if USE(ACCELERATED_COMPOSITING) PlatformLayer* MediaPlayerPrivateQTKit::platformLayer() const { return m_qtVideoLayer.get(); } #endif void MediaPlayerPrivateQTKit::play() { LOG(Media, "MediaPlayerPrivateQTKit::play(%p)", this); if (!metaDataAvailable()) return; m_startedPlaying = true; #if DRAW_FRAME_RATE m_frameCountWhilePlaying = 0; #endif [m_objcObserver.get() setDelayCallbacks:YES]; [m_qtMovie.get() setRate:m_player->rate()]; [m_objcObserver.get() setDelayCallbacks:NO]; } void MediaPlayerPrivateQTKit::pause() { LOG(Media, "MediaPlayerPrivateQTKit::pause(%p)", this); if (!metaDataAvailable()) return; m_startedPlaying = false; #if DRAW_FRAME_RATE m_timeStoppedPlaying = [NSDate timeIntervalSinceReferenceDate]; #endif [m_objcObserver.get() setDelayCallbacks:YES]; [m_qtMovie.get() stop]; [m_objcObserver.get() setDelayCallbacks:NO]; } float MediaPlayerPrivateQTKit::duration() const { if (!metaDataAvailable()) return 0; if (m_cachedDuration != MediaPlayer::invalidTime()) return m_cachedDuration; QTTime time = [m_qtMovie.get() duration]; if (time.flags == kQTTimeIsIndefinite) return numeric_limits::infinity(); return static_cast(time.timeValue) / time.timeScale; } float MediaPlayerPrivateQTKit::currentTime() const { if (!metaDataAvailable()) return 0; QTTime time = [m_qtMovie.get() currentTime]; return static_cast(time.timeValue) / time.timeScale; } void MediaPlayerPrivateQTKit::seek(float time) { LOG(Media, "MediaPlayerPrivateQTKit::seek(%p) - time %f", this, time); // Nothing to do if we are already in the middle of a seek to the same time. if (time == m_seekTo) return; cancelSeek(); if (!metaDataAvailable()) return; if (time > duration()) time = duration(); m_seekTo = time; if (maxTimeSeekable() >= m_seekTo) doSeek(); else m_seekTimer.start(0, 0.5f); } void MediaPlayerPrivateQTKit::doSeek() { QTTime qttime = createQTTime(m_seekTo); // setCurrentTime generates several event callbacks, update afterwards [m_objcObserver.get() setDelayCallbacks:YES]; float oldRate = [m_qtMovie.get() rate]; if (oldRate) [m_qtMovie.get() setRate:0]; [m_qtMovie.get() setCurrentTime:qttime]; // restore playback only if not at end, otherwise QTMovie will loop float timeAfterSeek = currentTime(); if (oldRate && timeAfterSeek < duration()) [m_qtMovie.get() setRate:oldRate]; cancelSeek(); [m_objcObserver.get() setDelayCallbacks:NO]; } void MediaPlayerPrivateQTKit::cancelSeek() { LOG(Media, "MediaPlayerPrivateQTKit::cancelSeek(%p)", this); m_seekTo = -1; m_seekTimer.stop(); } void MediaPlayerPrivateQTKit::seekTimerFired(Timer*) { if (!metaDataAvailable()|| !seeking() || currentTime() == m_seekTo) { cancelSeek(); updateStates(); m_player->timeChanged(); return; } if (maxTimeSeekable() >= m_seekTo) doSeek(); else { MediaPlayer::NetworkState state = networkState(); if (state == MediaPlayer::Empty || state == MediaPlayer::Loaded) { cancelSeek(); updateStates(); m_player->timeChanged(); } } } bool MediaPlayerPrivateQTKit::paused() const { if (!metaDataAvailable()) return true; return [m_qtMovie.get() rate] == 0; } bool MediaPlayerPrivateQTKit::seeking() const { if (!metaDataAvailable()) return false; return m_seekTo >= 0; } IntSize MediaPlayerPrivateQTKit::naturalSize() const { if (!metaDataAvailable()) return IntSize(); // In spite of the name of this method, return QTMovieNaturalSizeAttribute transformed by the // initial movie scale because the spec says intrinsic size is: // // ... the dimensions of the resource in CSS pixels after taking into account the resource's // dimensions, aspect ratio, clean aperture, resolution, and so forth, as defined for the // format used by the resource FloatSize naturalSize([[m_qtMovie.get() attributeForKey:QTMovieNaturalSizeAttribute] sizeValue]); if (naturalSize.isEmpty() && m_isStreaming) { // HTTP Live Streams will occasionally return {0,0} natural sizes while scrubbing. // Work around this problem () by returning the last valid // cached natural size: naturalSize = m_cachedNaturalSize; } else { // Unfortunately, due to another QTKit bug () we won't get a sizeChanged // event when this happens, so we must cache the last valid naturalSize here: m_cachedNaturalSize = naturalSize; } return IntSize(naturalSize.width() * m_scaleFactor.width(), naturalSize.height() * m_scaleFactor.height()); } bool MediaPlayerPrivateQTKit::hasVideo() const { if (!metaDataAvailable()) return false; return [[m_qtMovie.get() attributeForKey:QTMovieHasVideoAttribute] boolValue]; } bool MediaPlayerPrivateQTKit::hasAudio() const { if (!m_qtMovie) return false; return [[m_qtMovie.get() attributeForKey:QTMovieHasAudioAttribute] boolValue]; } bool MediaPlayerPrivateQTKit::supportsFullscreen() const { return true; } void MediaPlayerPrivateQTKit::setVolume(float volume) { LOG(Media, "MediaPlayerPrivateQTKit::setVolume(%p) - volume %f", this, volume); if (m_qtMovie) [m_qtMovie.get() setVolume:volume]; } bool MediaPlayerPrivateQTKit::hasClosedCaptions() const { if (!metaDataAvailable()) return false; return wkQTMovieHasClosedCaptions(m_qtMovie.get()); } void MediaPlayerPrivateQTKit::setClosedCaptionsVisible(bool closedCaptionsVisible) { if (metaDataAvailable()) { wkQTMovieSetShowClosedCaptions(m_qtMovie.get(), closedCaptionsVisible); #if USE(ACCELERATED_COMPOSITING) if (closedCaptionsVisible && m_qtVideoLayer) { // Captions will be rendered upside down unless we flag the movie as flipped (again). See . [m_qtVideoLayer.get() setGeometryFlipped:YES]; } #endif } } void MediaPlayerPrivateQTKit::setRate(float rate) { LOG(Media, "MediaPlayerPrivateQTKit::setRate(%p) - rate %f", this, rate); if (m_qtMovie) [m_qtMovie.get() setRate:rate]; } void MediaPlayerPrivateQTKit::setPreservesPitch(bool preservesPitch) { LOG(Media, "MediaPlayerPrivateQTKit::setPreservesPitch(%p) - preservesPitch %d", this, (int)preservesPitch); if (!m_qtMovie) return; // QTMovieRateChangesPreservePitchAttribute cannot be changed dynamically after QTMovie creation. // If the passed in value is different than what already exists, we need to recreate the QTMovie for it to take effect. if ([[m_qtMovie.get() attributeForKey:QTMovieRateChangesPreservePitchAttribute] boolValue] == preservesPitch) return; RetainPtr movieAttributes = adoptNS([[m_qtMovie.get() movieAttributes] mutableCopy]); ASSERT(movieAttributes); [movieAttributes.get() setValue:[NSNumber numberWithBool:preservesPitch] forKey:QTMovieRateChangesPreservePitchAttribute]; m_timeToRestore = currentTime(); createQTMovie([movieAttributes.get() valueForKey:QTMovieURLAttribute], movieAttributes.get()); } PassRefPtr MediaPlayerPrivateQTKit::buffered() const { RefPtr timeRanges = TimeRanges::create(); float loaded = maxTimeLoaded(); if (loaded > 0) timeRanges->add(0, loaded); return timeRanges.release(); } float MediaPlayerPrivateQTKit::maxTimeSeekable() const { if (!metaDataAvailable()) return 0; // infinite duration means live stream if (std::isinf(duration())) return 0; return wkQTMovieMaxTimeSeekable(m_qtMovie.get()); } float MediaPlayerPrivateQTKit::maxTimeLoaded() const { if (!metaDataAvailable()) return 0; return wkQTMovieMaxTimeLoaded(m_qtMovie.get()); } bool MediaPlayerPrivateQTKit::didLoadingProgress() const { if (!duration() || !totalBytes()) return false; float currentMaxTimeLoaded = maxTimeLoaded(); bool didLoadingProgress = currentMaxTimeLoaded != m_maxTimeLoadedAtLastDidLoadingProgress; m_maxTimeLoadedAtLastDidLoadingProgress = currentMaxTimeLoaded; return didLoadingProgress; } unsigned MediaPlayerPrivateQTKit::totalBytes() const { if (!metaDataAvailable()) return 0; return [[m_qtMovie.get() attributeForKey:QTMovieDataSizeAttribute] intValue]; } void MediaPlayerPrivateQTKit::cancelLoad() { LOG(Media, "MediaPlayerPrivateQTKit::cancelLoad(%p)", this); // FIXME: Is there a better way to check for this? if (m_networkState < MediaPlayer::Loading || m_networkState == MediaPlayer::Loaded) return; tearDownVideoRendering(); m_qtMovie = nil; updateStates(); } void MediaPlayerPrivateQTKit::cacheMovieScale() { NSSize initialSize = NSZeroSize; NSSize naturalSize = [[m_qtMovie.get() attributeForKey:QTMovieNaturalSizeAttribute] sizeValue]; // QTMovieCurrentSizeAttribute is not allowed with instances of QTMovie that have been // opened with QTMovieOpenForPlaybackAttribute, so ask for the display transform attribute instead. NSAffineTransform *displayTransform = [m_qtMovie.get() attributeForKey:@"QTMoviePreferredTransformAttribute"]; if (displayTransform) initialSize = [displayTransform transformSize:naturalSize]; else { initialSize.width = naturalSize.width; initialSize.height = naturalSize.height; } if (naturalSize.width) m_scaleFactor.setWidth(initialSize.width / naturalSize.width); if (naturalSize.height) m_scaleFactor.setHeight(initialSize.height / naturalSize.height); } bool MediaPlayerPrivateQTKit::isReadyForVideoSetup() const { return m_readyState >= MediaPlayer::HaveMetadata && m_player->visible(); } void MediaPlayerPrivateQTKit::prepareForRendering() { LOG(Media, "MediaPlayerPrivateQTKit::prepareForRendering(%p)", this); if (m_isAllowedToRender) return; m_isAllowedToRender = true; if (!hasSetUpVideoRendering()) setUpVideoRendering(); // If using a movie layer, inform the client so the compositing tree is updated. This is crucial if the movie // has a poster, as it will most likely not have a layer and we will now be rendering frames to the movie layer. if (currentRenderingMode() == MediaRenderingMovieLayer || preferredRenderingMode() == MediaRenderingMovieLayer) m_player->mediaPlayerClient()->mediaPlayerRenderingModeChanged(m_player); } void MediaPlayerPrivateQTKit::updateStates() { MediaPlayer::NetworkState oldNetworkState = m_networkState; MediaPlayer::ReadyState oldReadyState = m_readyState; LOG(Media, "MediaPlayerPrivateQTKit::updateStates(%p) - entering with networkState = %i, readyState = %i", this, static_cast(m_networkState), static_cast(m_readyState)); long loadState = m_qtMovie ? [[m_qtMovie.get() attributeForKey:QTMovieLoadStateAttribute] longValue] : static_cast(QTMovieLoadStateError); if (loadState >= QTMovieLoadStateLoaded && m_readyState < MediaPlayer::HaveMetadata) { disableUnsupportedTracks(); if (m_player->inMediaDocument()) { if (!m_enabledTrackCount || m_hasUnsupportedTracks) { // This has a type of media that we do not handle directly with a