| /* |
| * 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 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 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 "GraphicsContext.h" |
| #import "URL.h" |
| #import "Logging.h" |
| #import "MIMETypeRegistry.h" |
| #import "MediaTimeQTKit.h" |
| #import "PlatformLayer.h" |
| #import "PlatformTimeRanges.h" |
| #import "SecurityOrigin.h" |
| #import "SoftLinking.h" |
| #import "WebCoreSystemInterface.h" |
| #import <QTKit/QTKit.h> |
| #import <objc/runtime.h> |
| |
| 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, 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, QTMovieLoadStateErrorAttribute, 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 *) |
| |
| @interface QTMovie(WebKitExtras) |
| - (QTTime)maxTimeLoaded; |
| - (NSArray *)availableRanges; |
| - (NSArray *)loadedRanges; |
| @end |
| |
| #define QTMovie getQTMovieClass() |
| #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 QTMovieLoadStateErrorAttribute getQTMovieLoadStateErrorAttribute() |
| #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 |
| |
| using namespace WebCore; |
| |
| @interface WebCoreMovieObserver : NSObject |
| { |
| MediaPlayerPrivateQTKit* m_callback; |
| BOOL m_delayCallbacks; |
| } |
| -(id)initWithCallback:(MediaPlayerPrivateQTKit*)callback; |
| -(void)disconnect; |
| -(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<MediaPlayerPrivateInterface> MediaPlayerPrivateQTKit::create(MediaPlayer* player) |
| { |
| return adoptPtr(new MediaPlayerPrivateQTKit(player)); |
| } |
| |
| void MediaPlayerPrivateQTKit::registerMediaEngine(MediaEngineRegistrar registrar) |
| { |
| if (isAvailable()) |
| registrar(create, getSupportedTypes, supportsType, getSitesInMediaCache, clearMediaCache, clearMediaCacheForSite, 0); |
| } |
| |
| MediaPlayerPrivateQTKit::MediaPlayerPrivateQTKit(MediaPlayer* player) |
| : m_player(player) |
| , m_objcObserver(adoptNS([[WebCoreMovieObserver alloc] initWithCallback:this])) |
| , m_seekTo(MediaTime::invalidTime()) |
| , 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(MediaTime::invalidTime()) |
| , m_cachedDuration(MediaTime::invalidTime()) |
| , m_timeToRestore(MediaTime::invalidTime()) |
| , 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) |
| { |
| } |
| |
| 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) |
| { |
| URL 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 (<rdar://problem/8378237>) |
| // 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, <rdar://problem/5693967> |
| 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()]; |
| } |
| |
| static Class QTVideoRendererClass() |
| { |
| static Class QTVideoRendererWebKitOnlyClass = NSClassFromString(@"QTVideoRendererWebKitOnly"); |
| return QTVideoRendererWebKitOnlyClass; |
| } |
| |
| 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<WebKitVideoRenderingDetails>)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<WebKitVideoRenderingDetails>)m_qtVideoRenderer.get() setMovie:nil]; |
| |
| m_qtVideoRenderer = nil; |
| } |
| |
| void MediaPlayerPrivateQTKit::createQTMovieLayer() |
| { |
| LOG(Media, "MediaPlayerPrivateQTKit::createQTMovieLayer(%p)", this); |
| 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::updateConfiguration(). |
| } |
| } |
| |
| void MediaPlayerPrivateQTKit::destroyQTMovieLayer() |
| { |
| LOG(Media, "MediaPlayerPrivateQTKit::destroyQTMovieLayer(%p)", this); |
| if (!m_qtVideoLayer) |
| return; |
| |
| // disassociate our movie from our instance of QTMovieLayer |
| [m_qtVideoLayer.get() setMovie:nil]; |
| m_qtVideoLayer = nil; |
| } |
| |
| MediaPlayerPrivateQTKit::MediaRenderingMode MediaPlayerPrivateQTKit::currentRenderingMode() const |
| { |
| if (m_qtVideoLayer) |
| return MediaRenderingMovieLayer; |
| |
| if (m_qtVideoRenderer) |
| return MediaRenderingSoftwareRenderer; |
| |
| return MediaRenderingNone; |
| } |
| |
| MediaPlayerPrivateQTKit::MediaRenderingMode MediaPlayerPrivateQTKit::preferredRenderingMode() const |
| { |
| if (!m_qtMovie) |
| return MediaRenderingNone; |
| |
| if (supportsAcceleratedRendering() && m_player->client().mediaPlayerRenderingCanBeAccelerated(m_player)) |
| return MediaRenderingMovieLayer; |
| |
| if (!QTVideoRendererClass()) |
| return MediaRenderingNone; |
| |
| 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 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->client().mediaPlayerRenderingModeChanged(m_player); |
| } |
| |
| void MediaPlayerPrivateQTKit::tearDownVideoRendering() |
| { |
| LOG(Media, "MediaPlayerPrivateQTKit::tearDownVideoRendering(%p)", this); |
| if (m_qtVideoRenderer) |
| destroyQTVideoRenderer(); |
| if (m_qtVideoLayer) |
| destroyQTMovieLayer(); |
| } |
| |
| bool MediaPlayerPrivateQTKit::hasSetUpVideoRendering() const |
| { |
| return m_qtVideoLayer |
| || m_qtVideoRenderer; |
| } |
| |
| 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]; |
| } |
| |
| #if ENABLE(MEDIA_SOURCE) |
| void MediaPlayerPrivateQTKit::load(const String&, MediaSourcePrivateClient*) |
| { |
| m_networkState = MediaPlayer::FormatError; |
| m_player->networkStateChanged(); |
| } |
| #endif |
| |
| |
| 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; |
| } |
| |
| PlatformLayer* MediaPlayerPrivateQTKit::platformLayer() const |
| { |
| return m_qtVideoLayer.get(); |
| } |
| |
| void MediaPlayerPrivateQTKit::play() |
| { |
| LOG(Media, "MediaPlayerPrivateQTKit::play(%p)", this); |
| if (!metaDataAvailable()) |
| return; |
| m_startedPlaying = true; |
| [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; |
| [m_objcObserver.get() setDelayCallbacks:YES]; |
| [m_qtMovie.get() stop]; |
| [m_objcObserver.get() setDelayCallbacks:NO]; |
| } |
| |
| MediaTime MediaPlayerPrivateQTKit::durationMediaTime() const |
| { |
| if (!metaDataAvailable()) |
| return MediaTime::zeroTime(); |
| |
| if (m_cachedDuration.isValid()) |
| return m_cachedDuration; |
| |
| QTTime time = [m_qtMovie.get() duration]; |
| if (time.flags == kQTTimeIsIndefinite) |
| return MediaTime::positiveInfiniteTime(); |
| return toMediaTime(time); |
| } |
| |
| MediaTime MediaPlayerPrivateQTKit::currentMediaTime() const |
| { |
| if (!metaDataAvailable()) |
| return MediaTime::zeroTime(); |
| QTTime time = [m_qtMovie.get() currentTime]; |
| return toMediaTime(time); |
| } |
| |
| void MediaPlayerPrivateQTKit::seek(const MediaTime& inTime) |
| { |
| MediaTime time = inTime; |
| LOG(Media, "MediaPlayerPrivateQTKit::seek(%p) - time %s", this, toString(time).utf8().data()); |
| // 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 > durationMediaTime()) |
| time = durationMediaTime(); |
| |
| m_seekTo = time; |
| if (maxMediaTimeSeekable() >= m_seekTo) |
| doSeek(); |
| else |
| m_seekTimer.start(0, 0.5f); |
| } |
| |
| void MediaPlayerPrivateQTKit::doSeek() |
| { |
| QTTime qttime = toQTTime(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 |
| MediaTime timeAfterSeek = currentMediaTime(); |
| if (oldRate && timeAfterSeek < durationMediaTime()) |
| [m_qtMovie.get() setRate:oldRate]; |
| |
| cancelSeek(); |
| [m_objcObserver.get() setDelayCallbacks:NO]; |
| } |
| |
| void MediaPlayerPrivateQTKit::cancelSeek() |
| { |
| LOG(Media, "MediaPlayerPrivateQTKit::cancelSeek(%p)", this); |
| m_seekTo = MediaTime::invalidTime(); |
| m_seekTimer.stop(); |
| } |
| |
| void MediaPlayerPrivateQTKit::seekTimerFired() |
| { |
| if (!metaDataAvailable() || !seeking() || currentMediaTime() == m_seekTo) { |
| cancelSeek(); |
| updateStates(); |
| m_player->timeChanged(); |
| return; |
| } |
| |
| if (maxMediaTimeSeekable() >= 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 >= MediaTime::zeroTime(); |
| } |
| |
| 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 (<rdar://problem/9078563>) by returning the last valid |
| // cached natural size: |
| naturalSize = m_cachedNaturalSize; |
| } else { |
| // Unfortunately, due to another QTKit bug (<rdar://problem/9082071>) 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 (closedCaptionsVisible && m_qtVideoLayer) { |
| // Captions will be rendered upside down unless we flag the movie as flipped (again). See <rdar://7408440>. |
| [m_qtVideoLayer.get() setGeometryFlipped:YES]; |
| } |
| } |
| } |
| |
| 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<NSDictionary> movieAttributes = adoptNS([[m_qtMovie.get() movieAttributes] mutableCopy]); |
| ASSERT(movieAttributes); |
| [movieAttributes.get() setValue:[NSNumber numberWithBool:preservesPitch] forKey:QTMovieRateChangesPreservePitchAttribute]; |
| m_timeToRestore = currentMediaTime(); |
| |
| createQTMovie([movieAttributes.get() valueForKey:QTMovieURLAttribute], movieAttributes.get()); |
| } |
| |
| std::unique_ptr<PlatformTimeRanges> MediaPlayerPrivateQTKit::buffered() const |
| { |
| auto timeRanges = PlatformTimeRanges::create(); |
| MediaTime loaded = maxMediaTimeLoaded(); |
| if (loaded > MediaTime::zeroTime()) |
| timeRanges->add(MediaTime::zeroTime(), loaded); |
| return timeRanges; |
| } |
| |
| static MediaTime maxValueForTimeRanges(NSArray *ranges) |
| { |
| if (!ranges) |
| return MediaTime::zeroTime(); |
| |
| MediaTime max; |
| for (NSValue *value in ranges) { |
| QTTimeRange range = [value QTTimeRangeValue]; |
| if (!range.time.timeScale || !range.duration.timeScale) |
| continue; |
| |
| MediaTime time = toMediaTime(range.time); |
| MediaTime duration = toMediaTime(range.duration); |
| if (time.isValid() && duration.isValid()) |
| max = std::max(max, time + duration); |
| } |
| |
| return max; |
| } |
| |
| MediaTime MediaPlayerPrivateQTKit::maxMediaTimeSeekable() const |
| { |
| if (!metaDataAvailable()) |
| return MediaTime::zeroTime(); |
| |
| // infinite duration means live stream |
| if (durationMediaTime().isPositiveInfinite()) |
| return MediaTime::zeroTime(); |
| |
| NSArray* seekableRanges = [m_qtMovie availableRanges]; |
| |
| return maxValueForTimeRanges(seekableRanges); |
| } |
| |
| MediaTime MediaPlayerPrivateQTKit::maxMediaTimeLoaded() const |
| { |
| if (!metaDataAvailable()) |
| return MediaTime::zeroTime(); |
| if ([m_qtMovie respondsToSelector:@selector(loadedRanges)]) |
| return maxValueForTimeRanges([m_qtMovie loadedRanges]); |
| return toMediaTime([m_qtMovie maxTimeLoaded]); |
| } |
| |
| bool MediaPlayerPrivateQTKit::didLoadingProgress() const |
| { |
| if (!duration() || !totalBytes()) |
| return false; |
| MediaTime currentMaxTimeLoaded = maxMediaTimeLoaded(); |
| bool didLoadingProgress = currentMaxTimeLoaded != m_maxTimeLoadedAtLastDidLoadingProgress; |
| m_maxTimeLoadedAtLastDidLoadingProgress = currentMaxTimeLoaded; |
| return didLoadingProgress; |
| } |
| |
| unsigned long long MediaPlayerPrivateQTKit::totalBytes() const |
| { |
| if (!metaDataAvailable()) |
| return 0; |
| return [[m_qtMovie.get() attributeForKey:QTMovieDataSizeAttribute] longLongValue]; |
| } |
| |
| 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->client().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<int>(m_networkState), static_cast<int>(m_readyState)); |
| |
| |
| long loadState = m_qtMovie ? [[m_qtMovie.get() attributeForKey:QTMovieLoadStateAttribute] longValue] : static_cast<long>(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 <video> |
| // element, eg. a rtsp track or QuickTime VR. Tell the MediaPlayerClient |
| // that we noticed. |
| sawUnsupportedTracks(); |
| return; |
| } |
| } else if (!m_enabledTrackCount) |
| loadState = QTMovieLoadStateError; |
| |
| if (loadState != QTMovieLoadStateError) { |
| wkQTMovieSelectPreferredAlternates(m_qtMovie.get()); |
| cacheMovieScale(); |
| MediaPlayer::MovieLoadType movieType = movieLoadType(); |
| m_isStreaming = movieType == MediaPlayer::StoredStream || movieType == MediaPlayer::LiveStream; |
| } |
| } |
| |
| // If this movie is reloading and we mean to restore the current time/rate, this might be the right time to do it. |
| if (loadState >= QTMovieLoadStateLoaded && oldNetworkState < MediaPlayer::Loaded && m_timeToRestore.isValid()) { |
| QTTime qttime = toQTTime(m_timeToRestore); |
| m_timeToRestore = MediaTime::invalidTime(); |
| |
| // Disable event callbacks from setCurrentTime for restoring time in a recreated video |
| [m_objcObserver.get() setDelayCallbacks:YES]; |
| [m_qtMovie.get() setCurrentTime:qttime]; |
| [m_qtMovie.get() setRate:m_player->rate()]; |
| [m_objcObserver.get() setDelayCallbacks:NO]; |
| } |
| |
| BOOL completelyLoaded = !m_isStreaming && (loadState >= QTMovieLoadStateComplete); |
| |
| // Note: QT indicates that we are fully loaded with QTMovieLoadStateComplete. |
| // However newer versions of QT do not, so we check maxTimeLoaded against duration. |
| if (!completelyLoaded && !m_isStreaming && metaDataAvailable()) |
| completelyLoaded = maxMediaTimeLoaded() == durationMediaTime(); |
| |
| if (completelyLoaded) { |
| // "Loaded" is reserved for fully buffered movies, never the case when streaming |
| m_networkState = MediaPlayer::Loaded; |
| m_readyState = MediaPlayer::HaveEnoughData; |
| } else if (loadState >= QTMovieLoadStatePlaythroughOK) { |
| m_readyState = MediaPlayer::HaveEnoughData; |
| m_networkState = MediaPlayer::Loading; |
| } else if (loadState >= QTMovieLoadStatePlayable) { |
| // FIXME: This might not work correctly in streaming case, <rdar://problem/5693967> |
| m_readyState = currentMediaTime() < maxMediaTimeLoaded() ? MediaPlayer::HaveFutureData : MediaPlayer::HaveCurrentData; |
| m_networkState = MediaPlayer::Loading; |
| } else if (loadState >= QTMovieLoadStateLoaded) { |
| m_readyState = MediaPlayer::HaveMetadata; |
| m_networkState = MediaPlayer::Loading; |
| } else if (loadState > QTMovieLoadStateError) { |
| m_readyState = MediaPlayer::HaveNothing; |
| m_networkState = MediaPlayer::Loading; |
| } else { |
| // Loading or decoding failed. |
| |
| if (m_player->inMediaDocument()) { |
| // Something went wrong in the loading of media within a standalone file. |
| // This can occur with chained refmovies pointing to streamed media. |
| sawUnsupportedTracks(); |
| return; |
| } |
| |
| MediaTime loaded = maxMediaTimeLoaded(); |
| if (!loaded) |
| m_readyState = MediaPlayer::HaveNothing; |
| |
| if (!m_enabledTrackCount) |
| m_networkState = MediaPlayer::FormatError; |
| else { |
| // FIXME: We should differentiate between load/network errors and decode errors <rdar://problem/5605692> |
| if (loaded > MediaTime::zeroTime()) |
| m_networkState = MediaPlayer::DecodeError; |
| else |
| m_readyState = MediaPlayer::HaveNothing; |
| } |
| } |
| |
| if (isReadyForVideoSetup() && !hasSetUpVideoRendering()) |
| setUpVideoRendering(); |
| |
| if (seeking()) |
| m_readyState = m_readyState >= MediaPlayer::HaveMetadata ? MediaPlayer::HaveMetadata : MediaPlayer::HaveNothing; |
| |
| // Streaming movies don't use the network when paused. |
| if (m_isStreaming && m_readyState >= MediaPlayer::HaveMetadata && m_networkState >= MediaPlayer::Loading && [m_qtMovie.get() rate] == 0) |
| m_networkState = MediaPlayer::Idle; |
| |
| if (m_networkState != oldNetworkState) |
| m_player->networkStateChanged(); |
| |
| if (m_readyState != oldReadyState) |
| m_player->readyStateChanged(); |
| |
| if (loadState >= QTMovieLoadStateLoaded) { |
| MediaTime dur = durationMediaTime(); |
| if (dur != m_reportedDuration) { |
| if (m_reportedDuration.isValid()) |
| m_player->durationChanged(); |
| m_reportedDuration = dur; |
| } |
| } |
| |
| LOG(Media, "MediaPlayerPrivateQTKit::updateStates(%p) - exiting with networkState = %i, readyState = %i", this, static_cast<int>(m_networkState), static_cast<int>(m_readyState)); |
| } |
| |
| long MediaPlayerPrivateQTKit::platformErrorCode() const |
| { |
| if (!m_qtMovie) |
| return 0; |
| |
| NSError* error = (NSError*)[m_qtMovie attributeForKey:QTMovieLoadStateErrorAttribute]; |
| if (!error || ![error isKindOfClass:[NSError class]]) |
| return 0; |
| |
| return [error code]; |
| } |
| |
| void MediaPlayerPrivateQTKit::loadStateChanged() |
| { |
| LOG(Media, "MediaPlayerPrivateQTKit::loadStateChanged(%p) - loadState = %li", this, [[m_qtMovie.get() attributeForKey:QTMovieLoadStateAttribute] longValue]); |
| |
| if (!m_hasUnsupportedTracks) |
| updateStates(); |
| } |
| |
| void MediaPlayerPrivateQTKit::loadedRangesChanged() |
| { |
| LOG(Media, "MediaPlayerPrivateQTKit::loadedRangesChanged(%p) - loadState = %li", this, [[m_qtMovie.get() attributeForKey:QTMovieLoadStateAttribute] longValue]); |
| |
| if (!m_hasUnsupportedTracks) |
| updateStates(); |
| } |
| |
| void MediaPlayerPrivateQTKit::rateChanged() |
| { |
| LOG(Media, "MediaPlayerPrivateQTKit::rateChanged(%p) - rate = %li", this, [m_qtMovie.get() rate]); |
| if (m_hasUnsupportedTracks) |
| return; |
| |
| updateStates(); |
| m_player->rateChanged(); |
| } |
| |
| void MediaPlayerPrivateQTKit::sizeChanged() |
| { |
| LOG(Media, "MediaPlayerPrivateQTKit::sizeChanged(%p)", this); |
| if (!m_hasUnsupportedTracks) |
| m_player->sizeChanged(); |
| } |
| |
| void MediaPlayerPrivateQTKit::timeChanged() |
| { |
| LOG(Media, "MediaPlayerPrivateQTKit::timeChanged(%p)", this); |
| if (m_hasUnsupportedTracks) |
| return; |
| |
| // It may not be possible to seek to a specific time in a streamed movie. When seeking in a |
| // stream QuickTime sets the movie time to closest time possible and posts a timechanged |
| // notification. Update m_seekTo so we can detect when the seek completes. |
| if (!m_seekTo.isValid()) |
| m_seekTo = currentMediaTime(); |
| |
| m_timeToRestore = MediaTime::invalidTime(); |
| updateStates(); |
| m_player->timeChanged(); |
| } |
| |
| void MediaPlayerPrivateQTKit::didEnd() |
| { |
| LOG(Media, "MediaPlayerPrivateQTKit::didEnd(%p)", this); |
| if (m_hasUnsupportedTracks) |
| return; |
| |
| m_startedPlaying = false; |
| |
| // Hang onto the current time and use it as duration from now on since QuickTime is telling us we |
| // are at the end. Do this because QuickTime sometimes reports one time for duration and stops |
| // playback at another time, which causes problems in HTMLMediaElement. QTKit's 'ended' event |
| // fires when playing in reverse so don't update duration when at time zero! |
| MediaTime now = currentMediaTime(); |
| if (now > MediaTime::zeroTime()) |
| m_cachedDuration = now; |
| |
| updateStates(); |
| m_player->timeChanged(); |
| } |
| |
| void MediaPlayerPrivateQTKit::layerHostChanged(PlatformLayer* rootLayer) |
| { |
| UNUSED_PARAM(rootLayer); |
| } |
| |
| void MediaPlayerPrivateQTKit::setSize(const IntSize&) |
| { |
| } |
| |
| void MediaPlayerPrivateQTKit::setVisible(bool b) |
| { |
| if (m_visible != b) { |
| m_visible = b; |
| if (b) |
| setUpVideoRendering(); |
| else |
| tearDownVideoRendering(); |
| } |
| } |
| |
| bool MediaPlayerPrivateQTKit::hasAvailableVideoFrame() const |
| { |
| // When using a QTMovieLayer return true as soon as the movie reaches QTMovieLoadStatePlayable |
| // because although we don't *know* when the first frame has decoded, by the time we get and |
| // process the notification a frame should have propagated the VisualContext and been set on |
| // the layer. |
| if (currentRenderingMode() == MediaRenderingMovieLayer) |
| return m_readyState >= MediaPlayer::HaveCurrentData; |
| |
| // When using the software renderer QuickTime signals that a frame is available so we might as well |
| // wait until we know that a frame has been drawn. |
| return m_videoFrameHasDrawn; |
| } |
| |
| void MediaPlayerPrivateQTKit::repaint() |
| { |
| if (m_hasUnsupportedTracks) |
| return; |
| |
| m_videoFrameHasDrawn = true; |
| m_player->repaint(); |
| } |
| |
| void MediaPlayerPrivateQTKit::paintCurrentFrameInContext(GraphicsContext* context, const IntRect& r) |
| { |
| id qtVideoRenderer = m_qtVideoRenderer.get(); |
| if (!qtVideoRenderer && currentRenderingMode() == MediaRenderingMovieLayer) { |
| // We're being told to render into a context, but we already have the |
| // MovieLayer going. This probably means we've been called from <canvas>. |
| // Set up a QTVideoRenderer to use, but one that doesn't register for |
| // update callbacks. That way, it won't bother us asking to repaint. |
| createQTVideoRenderer(QTVideoRendererModeDefault); |
| qtVideoRenderer = m_qtVideoRenderer.get(); |
| } |
| paint(context, r); |
| } |
| |
| void MediaPlayerPrivateQTKit::paint(GraphicsContext* context, const IntRect& r) |
| { |
| if (context->paintingDisabled() || m_hasUnsupportedTracks) |
| return; |
| id qtVideoRenderer = m_qtVideoRenderer.get(); |
| if (!qtVideoRenderer) |
| return; |
| |
| [m_objcObserver.get() setDelayCallbacks:YES]; |
| BEGIN_BLOCK_OBJC_EXCEPTIONS; |
| NSGraphicsContext* newContext; |
| FloatSize scaleFactor(1.0f, -1.0f); |
| IntRect paintRect(IntPoint(0, 0), IntSize(r.width(), r.height())); |
| |
| GraphicsContextStateSaver stateSaver(*context); |
| context->translate(r.x(), r.y() + r.height()); |
| context->scale(scaleFactor); |
| context->setImageInterpolationQuality(InterpolationLow); |
| |
| newContext = [NSGraphicsContext graphicsContextWithGraphicsPort:context->platformContext() flipped:NO]; |
| |
| [NSGraphicsContext saveGraphicsState]; |
| [NSGraphicsContext setCurrentContext:newContext]; |
| [(id<WebKitVideoRenderingDetails>)qtVideoRenderer drawInRect:paintRect]; |
| [NSGraphicsContext restoreGraphicsState]; |
| |
| END_BLOCK_OBJC_EXCEPTIONS; |
| [m_objcObserver.get() setDelayCallbacks:NO]; |
| } |
| |
| static bool shouldRejectMIMEType(const String& type) |
| { |
| // QTKit will return non-video MIME types which it claims to support, but which we |
| // do not support in the <video> element. Disclaim all non video/ or audio/ types. |
| return !type.startsWith("video/") && !type.startsWith("audio/"); |
| } |
| |
| static void addFileTypesToCache(NSArray * fileTypes, HashSet<String> &cache) |
| { |
| int count = [fileTypes count]; |
| for (int n = 0; n < count; n++) { |
| CFStringRef ext = reinterpret_cast<CFStringRef>([fileTypes objectAtIndex:n]); |
| RetainPtr<CFStringRef> uti = adoptCF(UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, ext, NULL)); |
| if (!uti) |
| continue; |
| RetainPtr<CFStringRef> mime = adoptCF(UTTypeCopyPreferredTagWithClass(uti.get(), kUTTagClassMIMEType)); |
| if (shouldRejectMIMEType(mime.get())) |
| continue; |
| if (mime) |
| cache.add(mime.get()); |
| |
| // -movieFileTypes: returns both file extensions and OSTypes. The later are surrounded by single |
| // quotes, eg. 'MooV', so don't bother looking at those. |
| if (CFStringGetCharacterAtIndex(ext, 0) != '\'') { |
| // UTI is missing many media related MIME types supported by QTKit (see rdar://6434168), and not all |
| // web servers use the MIME type UTI returns for an extension (see rdar://7875393), so even if UTI |
| // has a type for this extension add any types in hard coded table in the MIME type regsitry. |
| Vector<String> typesForExtension = MIMETypeRegistry::getMediaMIMETypesForExtension(ext); |
| unsigned count = typesForExtension.size(); |
| for (unsigned ndx = 0; ndx < count; ++ndx) { |
| String type = typesForExtension[ndx].lower(); |
| |
| if (shouldRejectMIMEType(type)) |
| continue; |
| |
| if (!cache.contains(type)) |
| cache.add(type); |
| } |
| } |
| } |
| } |
| |
| static HashSet<String> mimeCommonTypesCache() |
| { |
| DEPRECATED_DEFINE_STATIC_LOCAL(HashSet<String>, cache, ()); |
| static bool typeListInitialized = false; |
| |
| if (!typeListInitialized) { |
| typeListInitialized = true; |
| NSArray* fileTypes = [QTMovie movieFileTypes:QTIncludeCommonTypes]; |
| addFileTypesToCache(fileTypes, cache); |
| } |
| |
| return cache; |
| } |
| |
| static HashSet<String> mimeModernTypesCache() |
| { |
| DEPRECATED_DEFINE_STATIC_LOCAL(HashSet<String>, cache, ()); |
| static bool typeListInitialized = false; |
| |
| if (!typeListInitialized) { |
| typeListInitialized = true; |
| NSArray* fileTypes = [QTMovie movieFileTypes:(QTMovieFileTypeOptions)wkQTIncludeOnlyModernMediaFileTypes()]; |
| addFileTypesToCache(fileTypes, cache); |
| } |
| |
| return cache; |
| } |
| |
| static void concatenateHashSets(HashSet<String>& destination, const HashSet<String>& source) |
| { |
| HashSet<String>::const_iterator it = source.begin(); |
| HashSet<String>::const_iterator end = source.end(); |
| for (; it != end; ++it) |
| destination.add(*it); |
| } |
| |
| void MediaPlayerPrivateQTKit::getSupportedTypes(HashSet<String>& supportedTypes) |
| { |
| concatenateHashSets(supportedTypes, mimeModernTypesCache()); |
| |
| // Note: this method starts QTKitServer if it isn't already running when in 64-bit because it has to return the list |
| // of every MIME type supported by QTKit. |
| concatenateHashSets(supportedTypes, mimeCommonTypesCache()); |
| } |
| |
| MediaPlayer::SupportsType MediaPlayerPrivateQTKit::supportsType(const MediaEngineSupportParameters& parameters) |
| { |
| #if ENABLE(ENCRYPTED_MEDIA) |
| // QTKit does not support any encrytped media, so return IsNotSupported if the keySystem is non-NULL: |
| if (!parameters.keySystem.isNull() && !parameters.keySystem.isEmpty()) |
| return MediaPlayer::IsNotSupported; |
| #endif |
| |
| #if ENABLE(MEDIA_SOURCE) |
| if (parameters.isMediaSource) |
| return MediaPlayer::IsNotSupported; |
| #endif |
| |
| // Only return "IsSupported" if there is no codecs parameter for now as there is no way to ask QT if it supports an |
| // extended MIME type yet. |
| |
| // Due to <rdar://problem/10777059>, avoid calling the mime types cache functions if at |
| // all possible: |
| if (shouldRejectMIMEType(parameters.type)) |
| return MediaPlayer::IsNotSupported; |
| |
| // We check the "modern" type cache first, as it doesn't require QTKitServer to start. |
| if (mimeModernTypesCache().contains(parameters.type) || mimeCommonTypesCache().contains(parameters.type)) |
| return parameters.codecs.isEmpty() ? MediaPlayer::MayBeSupported : MediaPlayer::IsSupported; |
| |
| return MediaPlayer::IsNotSupported; |
| } |
| |
| bool MediaPlayerPrivateQTKit::isAvailable() |
| { |
| // 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. |
| return QTKitLibrary(); |
| } |
| |
| void MediaPlayerPrivateQTKit::getSitesInMediaCache(Vector<String>& sites) |
| { |
| NSArray *mediaSites = wkQTGetSitesInMediaDownloadCache(); |
| for (NSString *site in mediaSites) |
| sites.append(site); |
| } |
| |
| void MediaPlayerPrivateQTKit::clearMediaCache() |
| { |
| LOG(Media, "MediaPlayerPrivateQTKit::clearMediaCache()"); |
| wkQTClearMediaDownloadCache(); |
| } |
| |
| void MediaPlayerPrivateQTKit::clearMediaCacheForSite(const String& site) |
| { |
| LOG(Media, "MediaPlayerPrivateQTKit::clearMediaCacheForSite()"); |
| wkQTClearMediaDownloadCacheForSite(site); |
| } |
| |
| void MediaPlayerPrivateQTKit::disableUnsupportedTracks() |
| { |
| LOG(Media, "MediaPlayerPrivateQTKit::disableUnsupportedTracks(%p)", this); |
| |
| if (!m_qtMovie) { |
| m_enabledTrackCount = 0; |
| m_totalTrackCount = 0; |
| return; |
| } |
| |
| static HashSet<String>* allowedTrackTypes = 0; |
| if (!allowedTrackTypes) { |
| allowedTrackTypes = new HashSet<String>; |
| allowedTrackTypes->add(QTMediaTypeVideo); |
| allowedTrackTypes->add(QTMediaTypeSound); |
| allowedTrackTypes->add(QTMediaTypeText); |
| allowedTrackTypes->add(QTMediaTypeBase); |
| allowedTrackTypes->add(QTMediaTypeMPEG); |
| allowedTrackTypes->add("clcp"); // Closed caption |
| allowedTrackTypes->add("sbtl"); // Subtitle |
| allowedTrackTypes->add("odsm"); // MPEG-4 object descriptor stream |
| allowedTrackTypes->add("sdsm"); // MPEG-4 scene description stream |
| allowedTrackTypes->add("tmcd"); // timecode |
| allowedTrackTypes->add("tc64"); // timcode-64 |
| allowedTrackTypes->add("tmet"); // timed metadata |
| } |
| |
| NSArray *tracks = [m_qtMovie.get() tracks]; |
| |
| m_totalTrackCount = [tracks count]; |
| m_enabledTrackCount = m_totalTrackCount; |
| for (unsigned trackIndex = 0; trackIndex < m_totalTrackCount; trackIndex++) { |
| // Grab the track at the current index. If there isn't one there, then |
| // we can move onto the next one. |
| QTTrack *track = [tracks objectAtIndex:trackIndex]; |
| if (!track) |
| continue; |
| |
| // Check to see if the track is disabled already, we should move along. |
| // We don't need to re-disable it. |
| if (![track isEnabled]) { |
| --m_enabledTrackCount; |
| continue; |
| } |
| |
| // Get the track's media type. |
| NSString *mediaType = [track attributeForKey:QTTrackMediaTypeAttribute]; |
| if (!mediaType) |
| continue; |
| |
| // Test whether the media type is in our white list. |
| if (!allowedTrackTypes->contains(mediaType)) { |
| // If this track type is not allowed, then we need to disable it. |
| [track setEnabled:NO]; |
| --m_enabledTrackCount; |
| m_hasUnsupportedTracks = true; |
| } |
| |
| // Disable chapter tracks. These are most likely to lead to trouble, as |
| // they will be composited under the video tracks, forcing QT to do extra |
| // work. |
| QTTrack *chapterTrack = [track performSelector:@selector(chapterlist)]; |
| if (!chapterTrack) |
| continue; |
| |
| // Try to grab the media for the track. |
| QTMedia *chapterMedia = [chapterTrack media]; |
| if (!chapterMedia) |
| continue; |
| |
| // Grab the media type for this track. |
| id chapterMediaType = [chapterMedia attributeForKey:QTMediaTypeAttribute]; |
| if (!chapterMediaType) |
| continue; |
| |
| // Check to see if the track is a video track. We don't care about |
| // other non-video tracks. |
| if (![chapterMediaType isEqual:QTMediaTypeVideo]) |
| continue; |
| |
| // Check to see if the track is already disabled. If it is, we |
| // should move along. |
| if (![chapterTrack isEnabled]) |
| continue; |
| |
| // Disable the evil, evil track. |
| [chapterTrack setEnabled:NO]; |
| --m_enabledTrackCount; |
| m_hasUnsupportedTracks = true; |
| } |
| } |
| |
| void MediaPlayerPrivateQTKit::sawUnsupportedTracks() |
| { |
| m_hasUnsupportedTracks = true; |
| m_player->client().mediaPlayerSawUnsupportedTracks(m_player); |
| } |
| |
| bool MediaPlayerPrivateQTKit::supportsAcceleratedRendering() const |
| { |
| return isReadyForVideoSetup() && getQTMovieLayerClass() != Nil; |
| } |
| |
| void MediaPlayerPrivateQTKit::acceleratedRenderingStateChanged() |
| { |
| // Set up or change the rendering path if necessary. |
| setUpVideoRendering(); |
| } |
| |
| bool MediaPlayerPrivateQTKit::hasSingleSecurityOrigin() const |
| { |
| if (!m_qtMovie) |
| return false; |
| |
| RefPtr<SecurityOrigin> resolvedOrigin = SecurityOrigin::create(URL(wkQTMovieResolvedURL(m_qtMovie.get()))); |
| RefPtr<SecurityOrigin> requestedOrigin = SecurityOrigin::createFromString(m_movieURL); |
| return resolvedOrigin->isSameSchemeHostPort(requestedOrigin.get()); |
| } |
| |
| MediaPlayer::MovieLoadType MediaPlayerPrivateQTKit::movieLoadType() const |
| { |
| if (!m_qtMovie) |
| return MediaPlayer::Unknown; |
| |
| MediaPlayer::MovieLoadType movieType = (MediaPlayer::MovieLoadType)wkQTMovieGetType(m_qtMovie.get()); |
| |
| // Can't include WebKitSystemInterface from WebCore so we can't get the enum returned |
| // by wkQTMovieGetType, but at least verify that the value is in the valid range. |
| ASSERT(movieType >= MediaPlayer::Unknown && movieType <= MediaPlayer::LiveStream); |
| |
| return movieType; |
| } |
| |
| void MediaPlayerPrivateQTKit::setPreload(MediaPlayer::Preload preload) |
| { |
| m_preload = preload; |
| if (m_preload == MediaPlayer::None) |
| return; |
| |
| if (!m_qtMovie) |
| resumeLoad(); |
| else if (m_preload == MediaPlayer::Auto) |
| [m_qtMovie.get() setAttribute:[NSNumber numberWithBool:NO] forKey:@"QTMovieLimitReadAheadAttribute"]; |
| } |
| |
| void MediaPlayerPrivateQTKit::setPrivateBrowsingMode(bool privateBrowsing) |
| { |
| m_privateBrowsing = privateBrowsing; |
| if (!m_qtMovie) |
| return; |
| [m_qtMovie.get() setAttribute:[NSNumber numberWithBool:!privateBrowsing] forKey:@"QTMovieAllowPersistentCacheAttribute"]; |
| } |
| |
| bool MediaPlayerPrivateQTKit::canSaveMediaData() const |
| { |
| URL url; |
| |
| if (durationMediaTime().isPositiveInfinite()) |
| return false; |
| |
| if (m_qtMovie) |
| url = URL(wkQTMovieResolvedURL(m_qtMovie.get())); |
| else |
| url = URL(ParsedURLString, m_movieURL); |
| |
| if (url.isLocalFile()) |
| return true; |
| |
| if (url.protocolIsInHTTPFamily()) |
| return true; |
| |
| return false; |
| } |
| |
| } // namespace WebCore |
| |
| @implementation WebCoreMovieObserver |
| |
| - (id)initWithCallback:(MediaPlayerPrivateQTKit*)callback |
| { |
| m_callback = callback; |
| return [super init]; |
| } |
| |
| - (void)disconnect |
| { |
| [NSObject cancelPreviousPerformRequestsWithTarget:self]; |
| m_callback = 0; |
| } |
| |
| -(void)repaint |
| { |
| if (m_delayCallbacks) |
| [self performSelector:_cmd withObject:nil afterDelay:0.]; |
| else if (m_callback) |
| m_callback->repaint(); |
| } |
| |
| - (void)loadStateChanged:(NSNotification *)unusedNotification |
| { |
| UNUSED_PARAM(unusedNotification); |
| if (m_delayCallbacks) |
| [self performSelector:_cmd withObject:nil afterDelay:0]; |
| else |
| m_callback->loadStateChanged(); |
| } |
| |
| - (void)loadedRangesChanged:(NSNotification *)unusedNotification |
| { |
| UNUSED_PARAM(unusedNotification); |
| if (m_delayCallbacks) |
| [self performSelector:_cmd withObject:nil afterDelay:0]; |
| else |
| m_callback->loadedRangesChanged(); |
| } |
| |
| - (void)rateChanged:(NSNotification *)unusedNotification |
| { |
| UNUSED_PARAM(unusedNotification); |
| if (m_delayCallbacks) |
| [self performSelector:_cmd withObject:nil afterDelay:0]; |
| else |
| m_callback->rateChanged(); |
| } |
| |
| - (void)sizeChanged:(NSNotification *)unusedNotification |
| { |
| UNUSED_PARAM(unusedNotification); |
| if (m_delayCallbacks) |
| [self performSelector:_cmd withObject:nil afterDelay:0]; |
| else |
| m_callback->sizeChanged(); |
| } |
| |
| - (void)timeChanged:(NSNotification *)unusedNotification |
| { |
| UNUSED_PARAM(unusedNotification); |
| if (m_delayCallbacks) |
| [self performSelector:_cmd withObject:nil afterDelay:0]; |
| else |
| m_callback->timeChanged(); |
| } |
| |
| - (void)didEnd:(NSNotification *)unusedNotification |
| { |
| UNUSED_PARAM(unusedNotification); |
| if (m_delayCallbacks) |
| [self performSelector:_cmd withObject:nil afterDelay:0]; |
| else |
| m_callback->didEnd(); |
| } |
| |
| - (void)newImageAvailable:(NSNotification *)unusedNotification |
| { |
| UNUSED_PARAM(unusedNotification); |
| [self repaint]; |
| } |
| |
| - (void)layerHostChanged:(NSNotification *)notification |
| { |
| CALayer* rootLayer = static_cast<CALayer*>([notification object]); |
| m_callback->layerHostChanged(rootLayer); |
| } |
| |
| - (void)setDelayCallbacks:(BOOL)shouldDelay |
| { |
| m_delayCallbacks = shouldDelay; |
| } |
| |
| @end |
| |
| #endif |