/* * Copyright (C) 2007, 2008, 2009, 2010 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" #ifdef BUILDING_ON_TIGER #import "AutodrainedPool.h" #endif #import "BlockExceptions.h" #import "FrameView.h" #import "GraphicsContext.h" #import "KURL.h" #import "MIMETypeRegistry.h" #import "SoftLinking.h" #import "TimeRanges.h" #import "WebCoreSystemInterface.h" #import #import #import #if USE(ACCELERATED_COMPOSITING) #include "GraphicsLayer.h" #endif #if DRAW_FRAME_RATE #import "Font.h" #import "Frame.h" #import "Document.h" #import "RenderObject.h" #import "RenderStyle.h" #endif #ifdef BUILDING_ON_TIGER static IMP method_setImplementation(Method m, IMP imp) { IMP result = m->method_imp; m->method_imp = imp; return result; } #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, 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 *) #ifndef BUILDING_ON_TIGER SOFT_LINK_POINTER(QTKit, QTMovieApertureModeClean, NSString *) SOFT_LINK_POINTER(QTKit, QTMovieApertureModeAttribute, NSString *) #endif #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 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 QTVideoRendererWebKitOnlyNewImageAvailableNotification getQTVideoRendererWebKitOnlyNewImageAvailableNotification() #ifndef BUILDING_ON_TIGER #define QTMovieApertureModeClean getQTMovieApertureModeClean() #define QTMovieApertureModeAttribute getQTMovieApertureModeAttribute() #endif // 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; using namespace std; @interface WebCoreMovieObserver : NSObject { MediaPlayerPrivate* m_callback; NSView* m_view; BOOL m_delayCallbacks; } -(id)initWithCallback:(MediaPlayerPrivate*)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; @end @protocol WebKitVideoRenderingDetails -(void)setMovie:(id)movie; -(void)drawInRect:(NSRect)rect; @end namespace WebCore { #ifdef BUILDING_ON_TIGER static const long minimumQuickTimeVersion = 0x07300000; // 7.3 #endif MediaPlayerPrivateInterface* MediaPlayerPrivate::create(MediaPlayer* player) { return new MediaPlayerPrivate(player); } void MediaPlayerPrivate::registerMediaEngine(MediaEngineRegistrar registrar) { if (isAvailable()) registrar(create, getSupportedTypes, supportsType); } MediaPlayerPrivate::MediaPlayerPrivate(MediaPlayer* player) : m_player(player) , m_objcObserver(AdoptNS, [[WebCoreMovieObserver alloc] initWithCallback:this]) , m_seekTo(-1) , m_seekTimer(this, &MediaPlayerPrivate::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_startedPlaying(false) , m_isStreaming(false) , m_visible(false) , m_hasUnsupportedTracks(false) , m_videoFrameHasDrawn(false) #if DRAW_FRAME_RATE , m_frameCountWhilePlaying(0) , m_timeStartedPlaying(0) , m_timeStoppedPlaying(0) #endif { } MediaPlayerPrivate::~MediaPlayerPrivate() { tearDownVideoRendering(); [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()]; [m_objcObserver.get() disconnect]; } void MediaPlayerPrivate::createQTMovie(const String& url) { NSURL *cocoaURL = KURL(ParsedURLString, url); NSDictionary *movieAttributes = [NSDictionary dictionaryWithObjectsAndKeys: cocoaURL, QTMovieURLAttribute, [NSNumber numberWithBool:m_player->preservesPitch()], QTMovieRateChangesPreservePitchAttribute, [NSNumber numberWithBool:YES], QTMoviePreventExternalURLLinksAttribute, [NSNumber numberWithBool:YES], QTSecurityPolicyNoCrossSiteAttribute, [NSNumber numberWithBool:NO], QTMovieAskUnresolvedDataRefsAttribute, #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) [NSNumber numberWithBool:YES], @"QTMovieOpenForPlaybackAttribute", #endif #ifndef BUILDING_ON_TIGER QTMovieApertureModeClean, QTMovieApertureModeAttribute, #endif nil]; createQTMovie(cocoaURL, movieAttributes); } void MediaPlayerPrivate::createQTMovie(NSURL *url, NSDictionary *movieAttributes) { [[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(loadStateChanged:) 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 void mainThreadSetNeedsDisplay(id self, SEL) { id movieView = [self superview]; ASSERT(!movieView || [movieView isKindOfClass:[QTMovieView class]]); if (!movieView || ![movieView isKindOfClass:[QTMovieView class]]) return; 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 MediaPlayerPrivate::createQTMovieView() { 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 = m_player->frameView()->documentView(); [parentView addSubview:m_qtMovieView.get()]; #ifdef BUILDING_ON_TIGER // setDelegate: isn't a public call in Tiger, so use performSelector to keep the compiler happy [m_qtMovieView.get() performSelector:@selector(setDelegate:) withObject:m_objcObserver.get()]; #else [m_qtMovieView.get() setDelegate:m_objcObserver.get()]; #endif [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 MediaPlayerPrivate::detachQTMovieView() { if (m_qtMovieView) { [m_objcObserver.get() setView:nil]; #ifdef BUILDING_ON_TIGER // setDelegate: isn't a public call in Tiger, so use performSelector to keep the compiler happy [m_qtMovieView.get() performSelector:@selector(setDelegate:) withObject:nil]; #else [m_qtMovieView.get() setDelegate:nil]; #endif [m_qtMovieView.get() removeFromSuperview]; m_qtMovieView = nil; } } void MediaPlayerPrivate::createQTVideoRenderer(QTVideoRendererMode rendererMode) { 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 MediaPlayerPrivate::destroyQTVideoRenderer() { 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 MediaPlayerPrivate::createQTMovieLayer() { #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 // Hang the video layer from the render layer, if we have one yet. If not, we'll do this // later via acceleratedRenderingStateChanged(). GraphicsLayer* videoGraphicsLayer = m_player->mediaPlayerClient()->mediaPlayerGraphicsLayer(m_player); if (videoGraphicsLayer) videoGraphicsLayer->setContentsToMedia(m_qtVideoLayer.get()); } #endif } void MediaPlayerPrivate::destroyQTMovieLayer() { #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 } MediaPlayerPrivate::MediaRenderingMode MediaPlayerPrivate::currentRenderingMode() const { if (m_qtMovieView) return MediaRenderingMovieView; if (m_qtVideoLayer) return MediaRenderingMovieLayer; if (m_qtVideoRenderer) return MediaRenderingSoftwareRenderer; return MediaRenderingNone; } MediaPlayerPrivate::MediaRenderingMode MediaPlayerPrivate::preferredRenderingMode() const { if (!m_player->frameView() || !m_qtMovie) return MediaRenderingNone; if (m_player->inMediaDocument() || !QTVideoRendererClass()) return MediaRenderingMovieView; #if USE(ACCELERATED_COMPOSITING) if (supportsAcceleratedRendering() && m_player->mediaPlayerClient()->mediaPlayerRenderingCanBeAccelerated(m_player)) return MediaRenderingMovieLayer; #endif return MediaRenderingSoftwareRenderer; } void MediaPlayerPrivate::setUpVideoRendering() { if (!isReadyForRendering()) 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; } } void MediaPlayerPrivate::tearDownVideoRendering() { if (m_qtMovieView) detachQTMovieView(); if (m_qtVideoRenderer) destroyQTVideoRenderer(); if (m_qtVideoLayer) destroyQTMovieLayer(); } bool MediaPlayerPrivate::hasSetUpVideoRendering() const { return m_qtMovieView || m_qtVideoLayer || m_qtVideoRenderer; } QTTime MediaPlayerPrivate::createQTTime(float time) const { if (!metaDataAvailable()) return QTMakeTime(0, 600); long timeScale = [[m_qtMovie.get() attributeForKey:QTMovieTimeScaleAttribute] longValue]; return QTMakeTime(time * timeScale, timeScale); } void MediaPlayerPrivate::load(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]; } PlatformMedia MediaPlayerPrivate::platformMedia() const { PlatformMedia plaftformMedia = { m_qtMovie.get() }; return plaftformMedia; } void MediaPlayerPrivate::play() { 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 MediaPlayerPrivate::pause() { 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 MediaPlayerPrivate::duration() const { if (!metaDataAvailable()) return 0; if (m_cachedDuration != -1.0f) 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 MediaPlayerPrivate::currentTime() const { if (!metaDataAvailable()) return 0; QTTime time = [m_qtMovie.get() currentTime]; return static_cast(time.timeValue) / time.timeScale; } void MediaPlayerPrivate::seek(float 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 MediaPlayerPrivate::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 MediaPlayerPrivate::cancelSeek() { m_seekTo = -1; m_seekTimer.stop(); } void MediaPlayerPrivate::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 MediaPlayerPrivate::paused() const { if (!metaDataAvailable()) return true; return [m_qtMovie.get() rate] == 0; } bool MediaPlayerPrivate::seeking() const { if (!metaDataAvailable()) return false; return m_seekTo >= 0; } IntSize MediaPlayerPrivate::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 NSSize naturalSize = [[m_qtMovie.get() attributeForKey:QTMovieNaturalSizeAttribute] sizeValue]; return IntSize(naturalSize.width * m_scaleFactor.width(), naturalSize.height * m_scaleFactor.height()); } bool MediaPlayerPrivate::hasVideo() const { if (!metaDataAvailable()) return false; return [[m_qtMovie.get() attributeForKey:QTMovieHasVideoAttribute] boolValue]; } bool MediaPlayerPrivate::hasAudio() const { if (!m_qtMovie) return false; return [[m_qtMovie.get() attributeForKey:QTMovieHasAudioAttribute] boolValue]; } bool MediaPlayerPrivate::supportsFullscreen() const { #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) return true; #else // See return false; #endif } void MediaPlayerPrivate::setVolume(float volume) { if (m_qtMovie) [m_qtMovie.get() setVolume:volume]; } bool MediaPlayerPrivate::hasClosedCaptions() const { if (!metaDataAvailable()) return false; return wkQTMovieHasClosedCaptions(m_qtMovie.get()); } void MediaPlayerPrivate::setClosedCaptionsVisible(bool closedCaptionsVisible) { if (metaDataAvailable()) { wkQTMovieSetShowClosedCaptions(m_qtMovie.get(), closedCaptionsVisible); #if USE(ACCELERATED_COMPOSITING) && (!defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)) 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 MediaPlayerPrivate::setRate(float rate) { if (m_qtMovie) [m_qtMovie.get() setRate:rate]; } void MediaPlayerPrivate::setPreservesPitch(bool 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; NSDictionary *movieAttributes = [[m_qtMovie.get() movieAttributes] mutableCopy]; ASSERT(movieAttributes); [movieAttributes setValue:[NSNumber numberWithBool:preservesPitch] forKey:QTMovieRateChangesPreservePitchAttribute]; m_timeToRestore = currentTime(); createQTMovie([movieAttributes valueForKey:QTMovieURLAttribute], movieAttributes); } PassRefPtr MediaPlayerPrivate::buffered() const { RefPtr timeRanges = TimeRanges::create(); float loaded = maxTimeLoaded(); if (loaded > 0) timeRanges->add(0, loaded); return timeRanges.release(); } float MediaPlayerPrivate::maxTimeSeekable() const { if (!metaDataAvailable()) return 0; // infinite duration means live stream if (isinf(duration())) return 0; return wkQTMovieMaxTimeSeekable(m_qtMovie.get()); } float MediaPlayerPrivate::maxTimeLoaded() const { if (!metaDataAvailable()) return 0; return wkQTMovieMaxTimeLoaded(m_qtMovie.get()); } unsigned MediaPlayerPrivate::bytesLoaded() const { float dur = duration(); if (!dur) return 0; return totalBytes() * maxTimeLoaded() / dur; } unsigned MediaPlayerPrivate::totalBytes() const { if (!metaDataAvailable()) return 0; return [[m_qtMovie.get() attributeForKey:QTMovieDataSizeAttribute] intValue]; } void MediaPlayerPrivate::cancelLoad() { // 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 MediaPlayerPrivate::cacheMovieScale() { NSSize initialSize = NSZeroSize; NSSize naturalSize = [[m_qtMovie.get() attributeForKey:QTMovieNaturalSizeAttribute] sizeValue]; #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) // 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; } #else initialSize = [[m_qtMovie.get() attributeForKey:QTMovieCurrentSizeAttribute] sizeValue]; #endif if (naturalSize.width) m_scaleFactor.setWidth(initialSize.width / naturalSize.width); if (naturalSize.height) m_scaleFactor.setHeight(initialSize.height / naturalSize.height); } bool MediaPlayerPrivate::isReadyForRendering() const { return m_readyState >= MediaPlayer::HaveMetadata && m_player->visible(); } void MediaPlayerPrivate::updateStates() { MediaPlayer::NetworkState oldNetworkState = m_networkState; MediaPlayer::ReadyState oldReadyState = 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