Home | History | Annotate | Download | only in mac
      1 /*
      2  * Copyright (C) 2007, 2008, 2009, 2010 Apple Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  * 1. Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2. Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
     17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #import "config.h"
     27 
     28 #if ENABLE(VIDEO)
     29 
     30 #import "MediaPlayerPrivateQTKit.h"
     31 
     32 #ifdef BUILDING_ON_TIGER
     33 #import "AutodrainedPool.h"
     34 #endif
     35 
     36 #import "BlockExceptions.h"
     37 #import "FrameView.h"
     38 #import "GraphicsContext.h"
     39 #import "KURL.h"
     40 #import "MIMETypeRegistry.h"
     41 #import "SoftLinking.h"
     42 #import "TimeRanges.h"
     43 #import "WebCoreSystemInterface.h"
     44 #import <QTKit/QTKit.h>
     45 #import <objc/objc-runtime.h>
     46 #import <wtf/UnusedParam.h>
     47 
     48 #if USE(ACCELERATED_COMPOSITING)
     49 #include "GraphicsLayer.h"
     50 #endif
     51 
     52 #if DRAW_FRAME_RATE
     53 #import "Font.h"
     54 #import "Frame.h"
     55 #import "Document.h"
     56 #import "RenderObject.h"
     57 #import "RenderStyle.h"
     58 #endif
     59 
     60 #ifdef BUILDING_ON_TIGER
     61 static IMP method_setImplementation(Method m, IMP imp)
     62 {
     63     IMP result = m->method_imp;
     64     m->method_imp = imp;
     65     return result;
     66 }
     67 #endif
     68 
     69 SOFT_LINK_FRAMEWORK(QTKit)
     70 
     71 SOFT_LINK(QTKit, QTMakeTime, QTTime, (long long timeValue, long timeScale), (timeValue, timeScale))
     72 
     73 SOFT_LINK_CLASS(QTKit, QTMovie)
     74 SOFT_LINK_CLASS(QTKit, QTMovieView)
     75 SOFT_LINK_CLASS(QTKit, QTMovieLayer)
     76 
     77 SOFT_LINK_POINTER(QTKit, QTTrackMediaTypeAttribute, NSString *)
     78 SOFT_LINK_POINTER(QTKit, QTMediaTypeAttribute, NSString *)
     79 SOFT_LINK_POINTER(QTKit, QTMediaTypeBase, NSString *)
     80 SOFT_LINK_POINTER(QTKit, QTMediaTypeMPEG, NSString *)
     81 SOFT_LINK_POINTER(QTKit, QTMediaTypeSound, NSString *)
     82 SOFT_LINK_POINTER(QTKit, QTMediaTypeText, NSString *)
     83 SOFT_LINK_POINTER(QTKit, QTMediaTypeVideo, NSString *)
     84 SOFT_LINK_POINTER(QTKit, QTMovieAskUnresolvedDataRefsAttribute, NSString *)
     85 SOFT_LINK_POINTER(QTKit, QTMovieDataSizeAttribute, NSString *)
     86 SOFT_LINK_POINTER(QTKit, QTMovieDidEndNotification, NSString *)
     87 SOFT_LINK_POINTER(QTKit, QTMovieHasVideoAttribute, NSString *)
     88 SOFT_LINK_POINTER(QTKit, QTMovieHasAudioAttribute, NSString *)
     89 SOFT_LINK_POINTER(QTKit, QTMovieIsActiveAttribute, NSString *)
     90 SOFT_LINK_POINTER(QTKit, QTMovieLoadStateAttribute, NSString *)
     91 SOFT_LINK_POINTER(QTKit, QTMovieLoadStateDidChangeNotification, NSString *)
     92 SOFT_LINK_POINTER(QTKit, QTMovieNaturalSizeAttribute, NSString *)
     93 SOFT_LINK_POINTER(QTKit, QTMovieCurrentSizeAttribute, NSString *)
     94 SOFT_LINK_POINTER(QTKit, QTMoviePreventExternalURLLinksAttribute, NSString *)
     95 SOFT_LINK_POINTER(QTKit, QTMovieRateChangesPreservePitchAttribute, NSString *)
     96 SOFT_LINK_POINTER(QTKit, QTMovieRateDidChangeNotification, NSString *)
     97 SOFT_LINK_POINTER(QTKit, QTMovieSizeDidChangeNotification, NSString *)
     98 SOFT_LINK_POINTER(QTKit, QTMovieTimeDidChangeNotification, NSString *)
     99 SOFT_LINK_POINTER(QTKit, QTMovieTimeScaleAttribute, NSString *)
    100 SOFT_LINK_POINTER(QTKit, QTMovieURLAttribute, NSString *)
    101 SOFT_LINK_POINTER(QTKit, QTMovieVolumeDidChangeNotification, NSString *)
    102 SOFT_LINK_POINTER(QTKit, QTSecurityPolicyNoCrossSiteAttribute, NSString *)
    103 SOFT_LINK_POINTER(QTKit, QTVideoRendererWebKitOnlyNewImageAvailableNotification, NSString *)
    104 #ifndef BUILDING_ON_TIGER
    105 SOFT_LINK_POINTER(QTKit, QTMovieApertureModeClean, NSString *)
    106 SOFT_LINK_POINTER(QTKit, QTMovieApertureModeAttribute, NSString *)
    107 #endif
    108 
    109 #define QTMovie getQTMovieClass()
    110 #define QTMovieView getQTMovieViewClass()
    111 #define QTMovieLayer getQTMovieLayerClass()
    112 
    113 #define QTTrackMediaTypeAttribute getQTTrackMediaTypeAttribute()
    114 #define QTMediaTypeAttribute getQTMediaTypeAttribute()
    115 #define QTMediaTypeBase getQTMediaTypeBase()
    116 #define QTMediaTypeMPEG getQTMediaTypeMPEG()
    117 #define QTMediaTypeSound getQTMediaTypeSound()
    118 #define QTMediaTypeText getQTMediaTypeText()
    119 #define QTMediaTypeVideo getQTMediaTypeVideo()
    120 #define QTMovieAskUnresolvedDataRefsAttribute getQTMovieAskUnresolvedDataRefsAttribute()
    121 #define QTMovieDataSizeAttribute getQTMovieDataSizeAttribute()
    122 #define QTMovieDidEndNotification getQTMovieDidEndNotification()
    123 #define QTMovieHasVideoAttribute getQTMovieHasVideoAttribute()
    124 #define QTMovieHasAudioAttribute getQTMovieHasAudioAttribute()
    125 #define QTMovieIsActiveAttribute getQTMovieIsActiveAttribute()
    126 #define QTMovieLoadStateAttribute getQTMovieLoadStateAttribute()
    127 #define QTMovieLoadStateDidChangeNotification getQTMovieLoadStateDidChangeNotification()
    128 #define QTMovieNaturalSizeAttribute getQTMovieNaturalSizeAttribute()
    129 #define QTMovieCurrentSizeAttribute getQTMovieCurrentSizeAttribute()
    130 #define QTMoviePreventExternalURLLinksAttribute getQTMoviePreventExternalURLLinksAttribute()
    131 #define QTMovieRateChangesPreservePitchAttribute getQTMovieRateChangesPreservePitchAttribute()
    132 #define QTMovieRateDidChangeNotification getQTMovieRateDidChangeNotification()
    133 #define QTMovieSizeDidChangeNotification getQTMovieSizeDidChangeNotification()
    134 #define QTMovieTimeDidChangeNotification getQTMovieTimeDidChangeNotification()
    135 #define QTMovieTimeScaleAttribute getQTMovieTimeScaleAttribute()
    136 #define QTMovieURLAttribute getQTMovieURLAttribute()
    137 #define QTMovieVolumeDidChangeNotification getQTMovieVolumeDidChangeNotification()
    138 #define QTSecurityPolicyNoCrossSiteAttribute getQTSecurityPolicyNoCrossSiteAttribute()
    139 #define QTVideoRendererWebKitOnlyNewImageAvailableNotification getQTVideoRendererWebKitOnlyNewImageAvailableNotification()
    140 #ifndef BUILDING_ON_TIGER
    141 #define QTMovieApertureModeClean getQTMovieApertureModeClean()
    142 #define QTMovieApertureModeAttribute getQTMovieApertureModeAttribute()
    143 #endif
    144 
    145 // Older versions of the QTKit header don't have these constants.
    146 #if !defined QTKIT_VERSION_MAX_ALLOWED || QTKIT_VERSION_MAX_ALLOWED <= QTKIT_VERSION_7_0
    147 enum {
    148     QTMovieLoadStateError = -1L,
    149     QTMovieLoadStateLoaded  = 2000L,
    150     QTMovieLoadStatePlayable = 10000L,
    151     QTMovieLoadStatePlaythroughOK = 20000L,
    152     QTMovieLoadStateComplete = 100000L
    153 };
    154 #endif
    155 
    156 using namespace WebCore;
    157 using namespace std;
    158 
    159 @interface WebCoreMovieObserver : NSObject
    160 {
    161     MediaPlayerPrivate* m_callback;
    162     NSView* m_view;
    163     BOOL m_delayCallbacks;
    164 }
    165 -(id)initWithCallback:(MediaPlayerPrivate*)callback;
    166 -(void)disconnect;
    167 -(void)setView:(NSView*)view;
    168 -(void)repaint;
    169 -(void)setDelayCallbacks:(BOOL)shouldDelay;
    170 -(void)loadStateChanged:(NSNotification *)notification;
    171 -(void)rateChanged:(NSNotification *)notification;
    172 -(void)sizeChanged:(NSNotification *)notification;
    173 -(void)timeChanged:(NSNotification *)notification;
    174 -(void)didEnd:(NSNotification *)notification;
    175 @end
    176 
    177 @protocol WebKitVideoRenderingDetails
    178 -(void)setMovie:(id)movie;
    179 -(void)drawInRect:(NSRect)rect;
    180 @end
    181 
    182 namespace WebCore {
    183 
    184 #ifdef BUILDING_ON_TIGER
    185 static const long minimumQuickTimeVersion = 0x07300000; // 7.3
    186 #endif
    187 
    188 
    189 MediaPlayerPrivateInterface* MediaPlayerPrivate::create(MediaPlayer* player)
    190 {
    191     return new MediaPlayerPrivate(player);
    192 }
    193 
    194 void MediaPlayerPrivate::registerMediaEngine(MediaEngineRegistrar registrar)
    195 {
    196     if (isAvailable())
    197         registrar(create, getSupportedTypes, supportsType);
    198 }
    199 
    200 MediaPlayerPrivate::MediaPlayerPrivate(MediaPlayer* player)
    201     : m_player(player)
    202     , m_objcObserver(AdoptNS, [[WebCoreMovieObserver alloc] initWithCallback:this])
    203     , m_seekTo(-1)
    204     , m_seekTimer(this, &MediaPlayerPrivate::seekTimerFired)
    205     , m_networkState(MediaPlayer::Empty)
    206     , m_readyState(MediaPlayer::HaveNothing)
    207     , m_rect()
    208     , m_scaleFactor(1, 1)
    209     , m_enabledTrackCount(0)
    210     , m_totalTrackCount(0)
    211     , m_reportedDuration(-1)
    212     , m_cachedDuration(-1)
    213     , m_timeToRestore(-1)
    214     , m_startedPlaying(false)
    215     , m_isStreaming(false)
    216     , m_visible(false)
    217     , m_hasUnsupportedTracks(false)
    218     , m_videoFrameHasDrawn(false)
    219 #if DRAW_FRAME_RATE
    220     , m_frameCountWhilePlaying(0)
    221     , m_timeStartedPlaying(0)
    222     , m_timeStoppedPlaying(0)
    223 #endif
    224 {
    225 }
    226 
    227 MediaPlayerPrivate::~MediaPlayerPrivate()
    228 {
    229     tearDownVideoRendering();
    230 
    231     [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()];
    232     [m_objcObserver.get() disconnect];
    233 }
    234 
    235 void MediaPlayerPrivate::createQTMovie(const String& url)
    236 {
    237     NSURL *cocoaURL = KURL(ParsedURLString, url);
    238     NSDictionary *movieAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
    239                        cocoaURL, QTMovieURLAttribute,
    240                        [NSNumber numberWithBool:m_player->preservesPitch()], QTMovieRateChangesPreservePitchAttribute,
    241                        [NSNumber numberWithBool:YES], QTMoviePreventExternalURLLinksAttribute,
    242                        [NSNumber numberWithBool:YES], QTSecurityPolicyNoCrossSiteAttribute,
    243                        [NSNumber numberWithBool:NO], QTMovieAskUnresolvedDataRefsAttribute,
    244 #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
    245                        [NSNumber numberWithBool:YES], @"QTMovieOpenForPlaybackAttribute",
    246 #endif
    247 #ifndef BUILDING_ON_TIGER
    248                        QTMovieApertureModeClean, QTMovieApertureModeAttribute,
    249 #endif
    250                        nil];
    251 
    252     createQTMovie(cocoaURL, movieAttributes);
    253 }
    254 
    255 void MediaPlayerPrivate::createQTMovie(NSURL *url, NSDictionary *movieAttributes)
    256 {
    257     [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()];
    258 
    259     bool recreating = false;
    260     if (m_qtMovie) {
    261         recreating = true;
    262         destroyQTVideoRenderer();
    263         m_qtMovie = 0;
    264     }
    265 
    266     // Disable rtsp streams for now, <rdar://problem/5693967>
    267     if (protocolIs([url scheme], "rtsp"))
    268         return;
    269 
    270     NSError *error = nil;
    271     m_qtMovie.adoptNS([[QTMovie alloc] initWithAttributes:movieAttributes error:&error]);
    272 
    273     if (!m_qtMovie)
    274         return;
    275 
    276     [m_qtMovie.get() setVolume:m_player->volume()];
    277 
    278     if (recreating && hasVideo())
    279         createQTVideoRenderer(QTVideoRendererModeListensForNewImages);
    280 
    281     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
    282                                              selector:@selector(loadStateChanged:)
    283                                                  name:QTMovieLoadStateDidChangeNotification
    284                                                object:m_qtMovie.get()];
    285 
    286     // In updateState(), we track when maxTimeLoaded() == duration().
    287     // In newer version of QuickTime, a notification is emitted when maxTimeLoaded changes.
    288     // In older version of QuickTime, QTMovieLoadStateDidChangeNotification be fired.
    289     if (NSString *maxTimeLoadedChangeNotification = wkQTMovieMaxTimeLoadedChangeNotification()) {
    290         [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
    291                                                  selector:@selector(loadStateChanged:)
    292                                                      name:maxTimeLoadedChangeNotification
    293                                                    object:m_qtMovie.get()];
    294     }
    295 
    296     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
    297                                              selector:@selector(rateChanged:)
    298                                                  name:QTMovieRateDidChangeNotification
    299                                                object:m_qtMovie.get()];
    300     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
    301                                              selector:@selector(sizeChanged:)
    302                                                  name:QTMovieSizeDidChangeNotification
    303                                                object:m_qtMovie.get()];
    304     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
    305                                              selector:@selector(timeChanged:)
    306                                                  name:QTMovieTimeDidChangeNotification
    307                                                object:m_qtMovie.get()];
    308     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
    309                                              selector:@selector(didEnd:)
    310                                                  name:QTMovieDidEndNotification
    311                                                object:m_qtMovie.get()];
    312 }
    313 
    314 static void mainThreadSetNeedsDisplay(id self, SEL)
    315 {
    316     id movieView = [self superview];
    317     ASSERT(!movieView || [movieView isKindOfClass:[QTMovieView class]]);
    318     if (!movieView || ![movieView isKindOfClass:[QTMovieView class]])
    319         return;
    320 
    321     WebCoreMovieObserver* delegate = [movieView delegate];
    322     ASSERT(!delegate || [delegate isKindOfClass:[WebCoreMovieObserver class]]);
    323     if (!delegate || ![delegate isKindOfClass:[WebCoreMovieObserver class]])
    324         return;
    325 
    326     [delegate repaint];
    327 }
    328 
    329 static Class QTVideoRendererClass()
    330 {
    331      static Class QTVideoRendererWebKitOnlyClass = NSClassFromString(@"QTVideoRendererWebKitOnly");
    332      return QTVideoRendererWebKitOnlyClass;
    333 }
    334 
    335 void MediaPlayerPrivate::createQTMovieView()
    336 {
    337     detachQTMovieView();
    338 
    339     static bool addedCustomMethods = false;
    340     if (!m_player->inMediaDocument() && !addedCustomMethods) {
    341         Class QTMovieContentViewClass = NSClassFromString(@"QTMovieContentView");
    342         ASSERT(QTMovieContentViewClass);
    343 
    344         Method mainThreadSetNeedsDisplayMethod = class_getInstanceMethod(QTMovieContentViewClass, @selector(_mainThreadSetNeedsDisplay));
    345         ASSERT(mainThreadSetNeedsDisplayMethod);
    346 
    347         method_setImplementation(mainThreadSetNeedsDisplayMethod, reinterpret_cast<IMP>(mainThreadSetNeedsDisplay));
    348         addedCustomMethods = true;
    349     }
    350 
    351     // delay callbacks as we *will* get notifications during setup
    352     [m_objcObserver.get() setDelayCallbacks:YES];
    353 
    354     m_qtMovieView.adoptNS([[QTMovieView alloc] init]);
    355     setSize(m_player->size());
    356     NSView* parentView = m_player->frameView()->documentView();
    357     [parentView addSubview:m_qtMovieView.get()];
    358 #ifdef BUILDING_ON_TIGER
    359     // setDelegate: isn't a public call in Tiger, so use performSelector to keep the compiler happy
    360     [m_qtMovieView.get() performSelector:@selector(setDelegate:) withObject:m_objcObserver.get()];
    361 #else
    362     [m_qtMovieView.get() setDelegate:m_objcObserver.get()];
    363 #endif
    364     [m_objcObserver.get() setView:m_qtMovieView.get()];
    365     [m_qtMovieView.get() setMovie:m_qtMovie.get()];
    366     [m_qtMovieView.get() setControllerVisible:NO];
    367     [m_qtMovieView.get() setPreservesAspectRatio:NO];
    368     // the area not covered by video should be transparent
    369     [m_qtMovieView.get() setFillColor:[NSColor clearColor]];
    370 
    371     // If we're in a media document, allow QTMovieView to render in its default mode;
    372     // otherwise tell it to draw synchronously.
    373     // Note that we expect mainThreadSetNeedsDisplay to be invoked only when synchronous drawing is requested.
    374     if (!m_player->inMediaDocument())
    375         wkQTMovieViewSetDrawSynchronously(m_qtMovieView.get(), YES);
    376 
    377     [m_objcObserver.get() setDelayCallbacks:NO];
    378 }
    379 
    380 void MediaPlayerPrivate::detachQTMovieView()
    381 {
    382     if (m_qtMovieView) {
    383         [m_objcObserver.get() setView:nil];
    384 #ifdef BUILDING_ON_TIGER
    385         // setDelegate: isn't a public call in Tiger, so use performSelector to keep the compiler happy
    386         [m_qtMovieView.get() performSelector:@selector(setDelegate:) withObject:nil];
    387 #else
    388         [m_qtMovieView.get() setDelegate:nil];
    389 #endif
    390         [m_qtMovieView.get() removeFromSuperview];
    391         m_qtMovieView = nil;
    392     }
    393 }
    394 
    395 void MediaPlayerPrivate::createQTVideoRenderer(QTVideoRendererMode rendererMode)
    396 {
    397     destroyQTVideoRenderer();
    398 
    399     m_qtVideoRenderer.adoptNS([[QTVideoRendererClass() alloc] init]);
    400     if (!m_qtVideoRenderer)
    401         return;
    402 
    403     // associate our movie with our instance of QTVideoRendererWebKitOnly
    404     [(id<WebKitVideoRenderingDetails>)m_qtVideoRenderer.get() setMovie:m_qtMovie.get()];
    405 
    406     if (rendererMode == QTVideoRendererModeListensForNewImages) {
    407         // listen to QTVideoRendererWebKitOnly's QTVideoRendererWebKitOnlyNewImageDidBecomeAvailableNotification
    408         [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
    409                                                  selector:@selector(newImageAvailable:)
    410                                                      name:QTVideoRendererWebKitOnlyNewImageAvailableNotification
    411                                                    object:m_qtVideoRenderer.get()];
    412     }
    413 }
    414 
    415 void MediaPlayerPrivate::destroyQTVideoRenderer()
    416 {
    417     if (!m_qtVideoRenderer)
    418         return;
    419 
    420     // stop observing the renderer's notifications before we toss it
    421     [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()
    422                                                     name:QTVideoRendererWebKitOnlyNewImageAvailableNotification
    423                                                   object:m_qtVideoRenderer.get()];
    424 
    425     // disassociate our movie from our instance of QTVideoRendererWebKitOnly
    426     [(id<WebKitVideoRenderingDetails>)m_qtVideoRenderer.get() setMovie:nil];
    427 
    428     m_qtVideoRenderer = nil;
    429 }
    430 
    431 void MediaPlayerPrivate::createQTMovieLayer()
    432 {
    433 #if USE(ACCELERATED_COMPOSITING)
    434     if (!m_qtMovie)
    435         return;
    436 
    437     ASSERT(supportsAcceleratedRendering());
    438 
    439     if (!m_qtVideoLayer) {
    440         m_qtVideoLayer.adoptNS([[QTMovieLayer alloc] init]);
    441         if (!m_qtVideoLayer)
    442             return;
    443 
    444         [m_qtVideoLayer.get() setMovie:m_qtMovie.get()];
    445 #ifndef NDEBUG
    446         [(CALayer *)m_qtVideoLayer.get() setName:@"Video layer"];
    447 #endif
    448 
    449         // Hang the video layer from the render layer, if we have one yet. If not, we'll do this
    450         // later via acceleratedRenderingStateChanged().
    451         GraphicsLayer* videoGraphicsLayer = m_player->mediaPlayerClient()->mediaPlayerGraphicsLayer(m_player);
    452         if (videoGraphicsLayer)
    453             videoGraphicsLayer->setContentsToMedia(m_qtVideoLayer.get());
    454     }
    455 #endif
    456 }
    457 
    458 void MediaPlayerPrivate::destroyQTMovieLayer()
    459 {
    460 #if USE(ACCELERATED_COMPOSITING)
    461     if (!m_qtVideoLayer)
    462         return;
    463 
    464     // disassociate our movie from our instance of QTMovieLayer
    465     [m_qtVideoLayer.get() setMovie:nil];
    466     m_qtVideoLayer = nil;
    467 #endif
    468 }
    469 
    470 MediaPlayerPrivate::MediaRenderingMode MediaPlayerPrivate::currentRenderingMode() const
    471 {
    472     if (m_qtMovieView)
    473         return MediaRenderingMovieView;
    474 
    475     if (m_qtVideoLayer)
    476         return MediaRenderingMovieLayer;
    477 
    478     if (m_qtVideoRenderer)
    479         return MediaRenderingSoftwareRenderer;
    480 
    481     return MediaRenderingNone;
    482 }
    483 
    484 MediaPlayerPrivate::MediaRenderingMode MediaPlayerPrivate::preferredRenderingMode() const
    485 {
    486     if (!m_player->frameView() || !m_qtMovie)
    487         return MediaRenderingNone;
    488 
    489     if (m_player->inMediaDocument() || !QTVideoRendererClass())
    490         return MediaRenderingMovieView;
    491 
    492 #if USE(ACCELERATED_COMPOSITING)
    493     if (supportsAcceleratedRendering() && m_player->mediaPlayerClient()->mediaPlayerRenderingCanBeAccelerated(m_player))
    494         return MediaRenderingMovieLayer;
    495 #endif
    496 
    497     return MediaRenderingSoftwareRenderer;
    498 }
    499 
    500 void MediaPlayerPrivate::setUpVideoRendering()
    501 {
    502     if (!isReadyForRendering())
    503         return;
    504 
    505     MediaRenderingMode currentMode = currentRenderingMode();
    506     MediaRenderingMode preferredMode = preferredRenderingMode();
    507     if (currentMode == preferredMode && currentMode != MediaRenderingNone)
    508         return;
    509 
    510     if (currentMode != MediaRenderingNone)
    511         tearDownVideoRendering();
    512 
    513     switch (preferredMode) {
    514     case MediaRenderingMovieView:
    515         createQTMovieView();
    516         break;
    517     case MediaRenderingNone:
    518     case MediaRenderingSoftwareRenderer:
    519         createQTVideoRenderer(QTVideoRendererModeListensForNewImages);
    520         break;
    521     case MediaRenderingMovieLayer:
    522         createQTMovieLayer();
    523         break;
    524     }
    525 }
    526 
    527 void MediaPlayerPrivate::tearDownVideoRendering()
    528 {
    529     if (m_qtMovieView)
    530         detachQTMovieView();
    531     if (m_qtVideoRenderer)
    532         destroyQTVideoRenderer();
    533     if (m_qtVideoLayer)
    534         destroyQTMovieLayer();
    535 }
    536 
    537 bool MediaPlayerPrivate::hasSetUpVideoRendering() const
    538 {
    539     return m_qtMovieView
    540         || m_qtVideoLayer
    541         || m_qtVideoRenderer;
    542 }
    543 
    544 QTTime MediaPlayerPrivate::createQTTime(float time) const
    545 {
    546     if (!metaDataAvailable())
    547         return QTMakeTime(0, 600);
    548     long timeScale = [[m_qtMovie.get() attributeForKey:QTMovieTimeScaleAttribute] longValue];
    549     return QTMakeTime(time * timeScale, timeScale);
    550 }
    551 
    552 void MediaPlayerPrivate::load(const String& url)
    553 {
    554     if (m_networkState != MediaPlayer::Loading) {
    555         m_networkState = MediaPlayer::Loading;
    556         m_player->networkStateChanged();
    557     }
    558     if (m_readyState != MediaPlayer::HaveNothing) {
    559         m_readyState = MediaPlayer::HaveNothing;
    560         m_player->readyStateChanged();
    561     }
    562     cancelSeek();
    563     m_videoFrameHasDrawn = false;
    564 
    565     [m_objcObserver.get() setDelayCallbacks:YES];
    566 
    567     createQTMovie(url);
    568 
    569     [m_objcObserver.get() loadStateChanged:nil];
    570     [m_objcObserver.get() setDelayCallbacks:NO];
    571 }
    572 
    573 PlatformMedia MediaPlayerPrivate::platformMedia() const
    574 {
    575     PlatformMedia plaftformMedia = { m_qtMovie.get() };
    576     return plaftformMedia;
    577 }
    578 
    579 void MediaPlayerPrivate::play()
    580 {
    581     if (!metaDataAvailable())
    582         return;
    583     m_startedPlaying = true;
    584 #if DRAW_FRAME_RATE
    585     m_frameCountWhilePlaying = 0;
    586 #endif
    587     [m_objcObserver.get() setDelayCallbacks:YES];
    588     [m_qtMovie.get() setRate:m_player->rate()];
    589     [m_objcObserver.get() setDelayCallbacks:NO];
    590 }
    591 
    592 void MediaPlayerPrivate::pause()
    593 {
    594     if (!metaDataAvailable())
    595         return;
    596     m_startedPlaying = false;
    597 #if DRAW_FRAME_RATE
    598     m_timeStoppedPlaying = [NSDate timeIntervalSinceReferenceDate];
    599 #endif
    600     [m_objcObserver.get() setDelayCallbacks:YES];
    601     [m_qtMovie.get() stop];
    602     [m_objcObserver.get() setDelayCallbacks:NO];
    603 }
    604 
    605 float MediaPlayerPrivate::duration() const
    606 {
    607     if (!metaDataAvailable())
    608         return 0;
    609 
    610     if (m_cachedDuration != -1.0f)
    611         return m_cachedDuration;
    612 
    613     QTTime time = [m_qtMovie.get() duration];
    614     if (time.flags == kQTTimeIsIndefinite)
    615         return numeric_limits<float>::infinity();
    616     return static_cast<float>(time.timeValue) / time.timeScale;
    617 }
    618 
    619 float MediaPlayerPrivate::currentTime() const
    620 {
    621     if (!metaDataAvailable())
    622         return 0;
    623     QTTime time = [m_qtMovie.get() currentTime];
    624     return static_cast<float>(time.timeValue) / time.timeScale;
    625 }
    626 
    627 void MediaPlayerPrivate::seek(float time)
    628 {
    629     // Nothing to do if we are already in the middle of a seek to the same time.
    630     if (time == m_seekTo)
    631         return;
    632 
    633     cancelSeek();
    634 
    635     if (!metaDataAvailable())
    636         return;
    637 
    638     if (time > duration())
    639         time = duration();
    640 
    641     m_seekTo = time;
    642     if (maxTimeSeekable() >= m_seekTo)
    643         doSeek();
    644     else
    645         m_seekTimer.start(0, 0.5f);
    646 }
    647 
    648 void MediaPlayerPrivate::doSeek()
    649 {
    650     QTTime qttime = createQTTime(m_seekTo);
    651     // setCurrentTime generates several event callbacks, update afterwards
    652     [m_objcObserver.get() setDelayCallbacks:YES];
    653     float oldRate = [m_qtMovie.get() rate];
    654 
    655     if (oldRate)
    656         [m_qtMovie.get() setRate:0];
    657     [m_qtMovie.get() setCurrentTime:qttime];
    658 
    659     // restore playback only if not at end, otherwise QTMovie will loop
    660     float timeAfterSeek = currentTime();
    661     if (oldRate && timeAfterSeek < duration())
    662         [m_qtMovie.get() setRate:oldRate];
    663 
    664     cancelSeek();
    665     [m_objcObserver.get() setDelayCallbacks:NO];
    666 }
    667 
    668 void MediaPlayerPrivate::cancelSeek()
    669 {
    670     m_seekTo = -1;
    671     m_seekTimer.stop();
    672 }
    673 
    674 void MediaPlayerPrivate::seekTimerFired(Timer<MediaPlayerPrivate>*)
    675 {
    676     if (!metaDataAvailable()|| !seeking() || currentTime() == m_seekTo) {
    677         cancelSeek();
    678         updateStates();
    679         m_player->timeChanged();
    680         return;
    681     }
    682 
    683     if (maxTimeSeekable() >= m_seekTo)
    684         doSeek();
    685     else {
    686         MediaPlayer::NetworkState state = networkState();
    687         if (state == MediaPlayer::Empty || state == MediaPlayer::Loaded) {
    688             cancelSeek();
    689             updateStates();
    690             m_player->timeChanged();
    691         }
    692     }
    693 }
    694 
    695 bool MediaPlayerPrivate::paused() const
    696 {
    697     if (!metaDataAvailable())
    698         return true;
    699     return [m_qtMovie.get() rate] == 0;
    700 }
    701 
    702 bool MediaPlayerPrivate::seeking() const
    703 {
    704     if (!metaDataAvailable())
    705         return false;
    706     return m_seekTo >= 0;
    707 }
    708 
    709 IntSize MediaPlayerPrivate::naturalSize() const
    710 {
    711     if (!metaDataAvailable())
    712         return IntSize();
    713 
    714     // In spite of the name of this method, return QTMovieNaturalSizeAttribute transformed by the
    715     // initial movie scale because the spec says intrinsic size is:
    716     //
    717     //    ... the dimensions of the resource in CSS pixels after taking into account the resource's
    718     //    dimensions, aspect ratio, clean aperture, resolution, and so forth, as defined for the
    719     //    format used by the resource
    720 
    721     NSSize naturalSize = [[m_qtMovie.get() attributeForKey:QTMovieNaturalSizeAttribute] sizeValue];
    722     return IntSize(naturalSize.width * m_scaleFactor.width(), naturalSize.height * m_scaleFactor.height());
    723 }
    724 
    725 bool MediaPlayerPrivate::hasVideo() const
    726 {
    727     if (!metaDataAvailable())
    728         return false;
    729     return [[m_qtMovie.get() attributeForKey:QTMovieHasVideoAttribute] boolValue];
    730 }
    731 
    732 bool MediaPlayerPrivate::hasAudio() const
    733 {
    734     if (!m_qtMovie)
    735         return false;
    736     return [[m_qtMovie.get() attributeForKey:QTMovieHasAudioAttribute] boolValue];
    737 }
    738 
    739 bool MediaPlayerPrivate::supportsFullscreen() const
    740 {
    741 #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
    742     return true;
    743 #else
    744     // See <rdar://problem/7389945>
    745     return false;
    746 #endif
    747 }
    748 
    749 void MediaPlayerPrivate::setVolume(float volume)
    750 {
    751     if (m_qtMovie)
    752         [m_qtMovie.get() setVolume:volume];
    753 }
    754 
    755 bool MediaPlayerPrivate::hasClosedCaptions() const
    756 {
    757     if (!metaDataAvailable())
    758         return false;
    759     return wkQTMovieHasClosedCaptions(m_qtMovie.get());
    760 }
    761 
    762 void MediaPlayerPrivate::setClosedCaptionsVisible(bool closedCaptionsVisible)
    763 {
    764     if (metaDataAvailable()) {
    765         wkQTMovieSetShowClosedCaptions(m_qtMovie.get(), closedCaptionsVisible);
    766 
    767 #if USE(ACCELERATED_COMPOSITING) && (!defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD))
    768     if (closedCaptionsVisible && m_qtVideoLayer) {
    769         // Captions will be rendered upside down unless we flag the movie as flipped (again). See <rdar://7408440>.
    770         [m_qtVideoLayer.get() setGeometryFlipped:YES];
    771     }
    772 #endif
    773     }
    774 }
    775 
    776 void MediaPlayerPrivate::setRate(float rate)
    777 {
    778     if (m_qtMovie)
    779         [m_qtMovie.get() setRate:rate];
    780 }
    781 
    782 void MediaPlayerPrivate::setPreservesPitch(bool preservesPitch)
    783 {
    784     if (!m_qtMovie)
    785         return;
    786 
    787     // QTMovieRateChangesPreservePitchAttribute cannot be changed dynamically after QTMovie creation.
    788     // If the passed in value is different than what already exists, we need to recreate the QTMovie for it to take effect.
    789     if ([[m_qtMovie.get() attributeForKey:QTMovieRateChangesPreservePitchAttribute] boolValue] == preservesPitch)
    790         return;
    791 
    792     NSDictionary *movieAttributes = [[m_qtMovie.get() movieAttributes] mutableCopy];
    793     ASSERT(movieAttributes);
    794     [movieAttributes setValue:[NSNumber numberWithBool:preservesPitch] forKey:QTMovieRateChangesPreservePitchAttribute];
    795     m_timeToRestore = currentTime();
    796 
    797     createQTMovie([movieAttributes valueForKey:QTMovieURLAttribute], movieAttributes);
    798 }
    799 
    800 PassRefPtr<TimeRanges> MediaPlayerPrivate::buffered() const
    801 {
    802     RefPtr<TimeRanges> timeRanges = TimeRanges::create();
    803     float loaded = maxTimeLoaded();
    804     if (loaded > 0)
    805         timeRanges->add(0, loaded);
    806     return timeRanges.release();
    807 }
    808 
    809 float MediaPlayerPrivate::maxTimeSeekable() const
    810 {
    811     if (!metaDataAvailable())
    812         return 0;
    813 
    814     // infinite duration means live stream
    815     if (isinf(duration()))
    816         return 0;
    817 
    818     return wkQTMovieMaxTimeSeekable(m_qtMovie.get());
    819 }
    820 
    821 float MediaPlayerPrivate::maxTimeLoaded() const
    822 {
    823     if (!metaDataAvailable())
    824         return 0;
    825     return wkQTMovieMaxTimeLoaded(m_qtMovie.get());
    826 }
    827 
    828 unsigned MediaPlayerPrivate::bytesLoaded() const
    829 {
    830     float dur = duration();
    831     if (!dur)
    832         return 0;
    833     return totalBytes() * maxTimeLoaded() / dur;
    834 }
    835 
    836 unsigned MediaPlayerPrivate::totalBytes() const
    837 {
    838     if (!metaDataAvailable())
    839         return 0;
    840     return [[m_qtMovie.get() attributeForKey:QTMovieDataSizeAttribute] intValue];
    841 }
    842 
    843 void MediaPlayerPrivate::cancelLoad()
    844 {
    845     // FIXME: Is there a better way to check for this?
    846     if (m_networkState < MediaPlayer::Loading || m_networkState == MediaPlayer::Loaded)
    847         return;
    848 
    849     tearDownVideoRendering();
    850     m_qtMovie = nil;
    851 
    852     updateStates();
    853 }
    854 
    855 void MediaPlayerPrivate::cacheMovieScale()
    856 {
    857     NSSize initialSize = NSZeroSize;
    858     NSSize naturalSize = [[m_qtMovie.get() attributeForKey:QTMovieNaturalSizeAttribute] sizeValue];
    859 
    860 #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
    861     // QTMovieCurrentSizeAttribute is not allowed with instances of QTMovie that have been
    862     // opened with QTMovieOpenForPlaybackAttribute, so ask for the display transform attribute instead.
    863     NSAffineTransform *displayTransform = [m_qtMovie.get() attributeForKey:@"QTMoviePreferredTransformAttribute"];
    864     if (displayTransform)
    865         initialSize = [displayTransform transformSize:naturalSize];
    866     else {
    867         initialSize.width = naturalSize.width;
    868         initialSize.height = naturalSize.height;
    869     }
    870 #else
    871     initialSize = [[m_qtMovie.get() attributeForKey:QTMovieCurrentSizeAttribute] sizeValue];
    872 #endif
    873 
    874     if (naturalSize.width)
    875         m_scaleFactor.setWidth(initialSize.width / naturalSize.width);
    876     if (naturalSize.height)
    877         m_scaleFactor.setHeight(initialSize.height / naturalSize.height);
    878 }
    879 
    880 bool MediaPlayerPrivate::isReadyForRendering() const
    881 {
    882     return m_readyState >= MediaPlayer::HaveMetadata && m_player->visible();
    883 }
    884 
    885 void MediaPlayerPrivate::updateStates()
    886 {
    887     MediaPlayer::NetworkState oldNetworkState = m_networkState;
    888     MediaPlayer::ReadyState oldReadyState = m_readyState;
    889 
    890     long loadState = m_qtMovie ? [[m_qtMovie.get() attributeForKey:QTMovieLoadStateAttribute] longValue] : static_cast<long>(QTMovieLoadStateError);
    891 
    892     if (loadState >= QTMovieLoadStateLoaded && m_readyState < MediaPlayer::HaveMetadata) {
    893         disableUnsupportedTracks();
    894         if (m_player->inMediaDocument()) {
    895             if (!m_enabledTrackCount || m_hasUnsupportedTracks) {
    896                 // This has a type of media that we do not handle directly with a <video>
    897                 // element, eg. a rtsp track or QuickTime VR. Tell the MediaPlayerClient
    898                 // that we noticed.
    899                 sawUnsupportedTracks();
    900                 return;
    901             }
    902         } else if (!m_enabledTrackCount)
    903             loadState = QTMovieLoadStateError;
    904 
    905         if (loadState != QTMovieLoadStateError) {
    906             cacheMovieScale();
    907             MediaPlayer::MovieLoadType movieType = movieLoadType();
    908             m_isStreaming = movieType == MediaPlayer::StoredStream || movieType == MediaPlayer::LiveStream;
    909         }
    910     }
    911 
    912     // If this movie is reloading and we mean to restore the current time/rate, this might be the right time to do it.
    913     if (loadState >= QTMovieLoadStateLoaded && oldNetworkState < MediaPlayer::Loaded && m_timeToRestore != -1.0f) {
    914         QTTime qttime = createQTTime(m_timeToRestore);
    915         m_timeToRestore = -1.0f;
    916 
    917         // Disable event callbacks from setCurrentTime for restoring time in a recreated video
    918         [m_objcObserver.get() setDelayCallbacks:YES];
    919         [m_qtMovie.get() setCurrentTime:qttime];
    920         [m_qtMovie.get() setRate:m_player->rate()];
    921         [m_objcObserver.get() setDelayCallbacks:NO];
    922     }
    923 
    924     BOOL completelyLoaded = !m_isStreaming && (loadState >= QTMovieLoadStateComplete);
    925 
    926     // Note: QT indicates that we are fully loaded with QTMovieLoadStateComplete.
    927     // However newer versions of QT do not, so we check maxTimeLoaded against duration.
    928     if (!completelyLoaded && !m_isStreaming && metaDataAvailable())
    929         completelyLoaded = maxTimeLoaded() == duration();
    930 
    931     if (completelyLoaded) {
    932         // "Loaded" is reserved for fully buffered movies, never the case when streaming
    933         m_networkState = MediaPlayer::Loaded;
    934         m_readyState = MediaPlayer::HaveEnoughData;
    935     } else if (loadState >= QTMovieLoadStatePlaythroughOK) {
    936         m_readyState = MediaPlayer::HaveEnoughData;
    937         m_networkState = MediaPlayer::Loading;
    938     } else if (loadState >= QTMovieLoadStatePlayable) {
    939         // FIXME: This might not work correctly in streaming case, <rdar://problem/5693967>
    940         m_readyState = currentTime() < maxTimeLoaded() ? MediaPlayer::HaveFutureData : MediaPlayer::HaveCurrentData;
    941         m_networkState = MediaPlayer::Loading;
    942     } else if (loadState >= QTMovieLoadStateLoaded) {
    943         m_readyState = MediaPlayer::HaveMetadata;
    944         m_networkState = MediaPlayer::Loading;
    945     } else if (loadState > QTMovieLoadStateError) {
    946         m_readyState = MediaPlayer::HaveNothing;
    947         m_networkState = MediaPlayer::Loading;
    948     } else {
    949         // Loading or decoding failed.
    950 
    951         if (m_player->inMediaDocument()) {
    952             // Something went wrong in the loading of media within a standalone file.
    953             // This can occur with chained refmovies pointing to streamed media.
    954             sawUnsupportedTracks();
    955             return;
    956         }
    957 
    958         float loaded = maxTimeLoaded();
    959         if (!loaded)
    960             m_readyState = MediaPlayer::HaveNothing;
    961 
    962         if (!m_enabledTrackCount)
    963             m_networkState = MediaPlayer::FormatError;
    964         else {
    965             // FIXME: We should differentiate between load/network errors and decode errors <rdar://problem/5605692>
    966             if (loaded > 0)
    967                 m_networkState = MediaPlayer::DecodeError;
    968             else
    969                 m_readyState = MediaPlayer::HaveNothing;
    970         }
    971     }
    972 
    973     if (!hasSetUpVideoRendering())
    974         setUpVideoRendering();
    975 
    976     if (seeking())
    977         m_readyState = m_readyState >= MediaPlayer::HaveMetadata ? MediaPlayer::HaveMetadata : MediaPlayer::HaveNothing;
    978 
    979     // Streaming movies don't use the network when paused.
    980     if (m_isStreaming && m_readyState >= MediaPlayer::HaveMetadata && m_networkState >= MediaPlayer::Loading && [m_qtMovie.get() rate] == 0)
    981         m_networkState = MediaPlayer::Idle;
    982 
    983     if (m_networkState != oldNetworkState)
    984         m_player->networkStateChanged();
    985 
    986     if (m_readyState != oldReadyState)
    987         m_player->readyStateChanged();
    988 
    989     if (loadState >= QTMovieLoadStateLoaded) {
    990         float dur = duration();
    991         if (dur != m_reportedDuration) {
    992             if (m_reportedDuration != -1.0f)
    993                 m_player->durationChanged();
    994             m_reportedDuration = dur;
    995         }
    996     }
    997 }
    998 
    999 void MediaPlayerPrivate::loadStateChanged()
   1000 {
   1001     if (!m_hasUnsupportedTracks)
   1002         updateStates();
   1003 }
   1004 
   1005 void MediaPlayerPrivate::rateChanged()
   1006 {
   1007     if (m_hasUnsupportedTracks)
   1008         return;
   1009 
   1010     updateStates();
   1011     m_player->rateChanged();
   1012 }
   1013 
   1014 void MediaPlayerPrivate::sizeChanged()
   1015 {
   1016     if (!m_hasUnsupportedTracks)
   1017         m_player->sizeChanged();
   1018 }
   1019 
   1020 void MediaPlayerPrivate::timeChanged()
   1021 {
   1022     if (m_hasUnsupportedTracks)
   1023         return;
   1024 
   1025     // It may not be possible to seek to a specific time in a streamed movie. When seeking in a
   1026     // stream QuickTime sets the movie time to closest time possible and posts a timechanged
   1027     // notification. Update m_seekTo so we can detect when the seek completes.
   1028     if (m_seekTo != -1)
   1029         m_seekTo = currentTime();
   1030 
   1031     m_timeToRestore = -1.0f;
   1032     updateStates();
   1033     m_player->timeChanged();
   1034 }
   1035 
   1036 void MediaPlayerPrivate::didEnd()
   1037 {
   1038     if (m_hasUnsupportedTracks)
   1039         return;
   1040 
   1041     m_startedPlaying = false;
   1042 #if DRAW_FRAME_RATE
   1043     m_timeStoppedPlaying = [NSDate timeIntervalSinceReferenceDate];
   1044 #endif
   1045 
   1046     // Hang onto the current time and use it as duration from now on since QuickTime is telling us we
   1047     // are at the end. Do this because QuickTime sometimes reports one time for duration and stops
   1048     // playback at another time, which causes problems in HTMLMediaElement. QTKit's 'ended' event
   1049     // fires when playing in reverse so don't update duration when at time zero!
   1050     float now = currentTime();
   1051     if (now > 0)
   1052         m_cachedDuration = now;
   1053 
   1054     updateStates();
   1055     m_player->timeChanged();
   1056 }
   1057 
   1058 void MediaPlayerPrivate::setSize(const IntSize&)
   1059 {
   1060     // Don't resize the view now because [view setFrame] also resizes the movie itself, and because
   1061     // the renderer calls this function immediately when we report a size change (QTMovieSizeDidChangeNotification)
   1062     // we can get into a feedback loop observing the size change and resetting the size, and this can cause
   1063     // QuickTime to miss resetting a movie's size when the media size changes (as happens with an rtsp movie
   1064     // once the rtsp server sends the track sizes). Instead we remember the size passed to paint() and resize
   1065     // the view when it changes.
   1066     // <rdar://problem/6336092> REGRESSION: rtsp movie does not resize correctly
   1067 }
   1068 
   1069 void MediaPlayerPrivate::setVisible(bool b)
   1070 {
   1071     if (m_visible != b) {
   1072         m_visible = b;
   1073         if (b)
   1074             setUpVideoRendering();
   1075         else
   1076             tearDownVideoRendering();
   1077     }
   1078 }
   1079 
   1080 bool MediaPlayerPrivate::hasAvailableVideoFrame() const
   1081 {
   1082     // When using a QTMovieLayer return true as soon as the movie reaches QTMovieLoadStatePlayable
   1083     // because although we don't *know* when the first frame has decoded, by the time we get and
   1084     // process the notification a frame should have propagated the VisualContext and been set on
   1085     // the layer.
   1086     if (currentRenderingMode() == MediaRenderingMovieLayer)
   1087         return m_readyState >= MediaPlayer::HaveCurrentData;
   1088 
   1089     // When using the software renderer QuickTime signals that a frame is available so we might as well
   1090     // wait until we know that a frame has been drawn.
   1091     return m_videoFrameHasDrawn;
   1092 }
   1093 
   1094 void MediaPlayerPrivate::repaint()
   1095 {
   1096     if (m_hasUnsupportedTracks)
   1097         return;
   1098 
   1099 #if DRAW_FRAME_RATE
   1100     if (m_startedPlaying) {
   1101         m_frameCountWhilePlaying++;
   1102         // to eliminate preroll costs from our calculation,
   1103         // our frame rate calculation excludes the first frame drawn after playback starts
   1104         if (1==m_frameCountWhilePlaying)
   1105             m_timeStartedPlaying = [NSDate timeIntervalSinceReferenceDate];
   1106     }
   1107 #endif
   1108     m_videoFrameHasDrawn = true;
   1109     m_player->repaint();
   1110 }
   1111 
   1112 void MediaPlayerPrivate::paintCurrentFrameInContext(GraphicsContext* context, const IntRect& r)
   1113 {
   1114     id qtVideoRenderer = m_qtVideoRenderer.get();
   1115     if (!qtVideoRenderer && currentRenderingMode() == MediaRenderingMovieLayer) {
   1116         // We're being told to render into a context, but we already have the
   1117         // MovieLayer going. This probably means we've been called from <canvas>.
   1118         // Set up a QTVideoRenderer to use, but one that doesn't register for
   1119         // update callbacks. That way, it won't bother us asking to repaint.
   1120         createQTVideoRenderer(QTVideoRendererModeDefault);
   1121         qtVideoRenderer = m_qtVideoRenderer.get();
   1122     }
   1123     paint(context, r);
   1124 }
   1125 
   1126 void MediaPlayerPrivate::paint(GraphicsContext* context, const IntRect& r)
   1127 {
   1128     if (context->paintingDisabled() || m_hasUnsupportedTracks)
   1129         return;
   1130     NSView *view = m_qtMovieView.get();
   1131     id qtVideoRenderer = m_qtVideoRenderer.get();
   1132     if (!view && !qtVideoRenderer)
   1133         return;
   1134 
   1135     [m_objcObserver.get() setDelayCallbacks:YES];
   1136     BEGIN_BLOCK_OBJC_EXCEPTIONS;
   1137     context->save();
   1138     context->translate(r.x(), r.y() + r.height());
   1139     context->scale(FloatSize(1.0f, -1.0f));
   1140     context->setImageInterpolationQuality(InterpolationLow);
   1141     IntRect paintRect(IntPoint(0, 0), IntSize(r.width(), r.height()));
   1142 
   1143 #ifdef BUILDING_ON_TIGER
   1144     AutodrainedPool pool;
   1145 #endif
   1146     NSGraphicsContext* newContext = [NSGraphicsContext graphicsContextWithGraphicsPort:context->platformContext() flipped:NO];
   1147 
   1148     // draw the current video frame
   1149     if (qtVideoRenderer) {
   1150         [NSGraphicsContext saveGraphicsState];
   1151         [NSGraphicsContext setCurrentContext:newContext];
   1152         [(id<WebKitVideoRenderingDetails>)qtVideoRenderer drawInRect:paintRect];
   1153         [NSGraphicsContext restoreGraphicsState];
   1154     } else {
   1155         if (m_rect != r) {
   1156              m_rect = r;
   1157             if (m_player->inMediaDocument()) {
   1158                 // the QTMovieView needs to be placed in the proper location for document mode
   1159                 [view setFrame:m_rect];
   1160             }
   1161             else {
   1162                 // We don't really need the QTMovieView in any specific location so let's just get it out of the way
   1163                 // where it won't intercept events or try to bring up the context menu.
   1164                 IntRect farAwayButCorrectSize(m_rect);
   1165                 farAwayButCorrectSize.move(-1000000, -1000000);
   1166                 [view setFrame:farAwayButCorrectSize];
   1167             }
   1168         }
   1169 
   1170         if (m_player->inMediaDocument()) {
   1171             // If we're using a QTMovieView in a media document, the view may get layer-backed. AppKit won't update
   1172             // the layer hosting correctly if we call displayRectIgnoringOpacity:inContext:, so use displayRectIgnoringOpacity:
   1173             // in this case. See <rdar://problem/6702882>.
   1174             [view displayRectIgnoringOpacity:paintRect];
   1175         } else
   1176             [view displayRectIgnoringOpacity:paintRect inContext:newContext];
   1177     }
   1178 
   1179 #if DRAW_FRAME_RATE
   1180     // Draw the frame rate only after having played more than 10 frames.
   1181     if (m_frameCountWhilePlaying > 10) {
   1182         Frame* frame = m_player->frameView() ? m_player->frameView()->frame() : NULL;
   1183         Document* document = frame ? frame->document() : NULL;
   1184         RenderObject* renderer = document ? document->renderer() : NULL;
   1185         RenderStyle* styleToUse = renderer ? renderer->style() : NULL;
   1186         if (styleToUse) {
   1187             double frameRate = (m_frameCountWhilePlaying - 1) / ( m_startedPlaying ? ([NSDate timeIntervalSinceReferenceDate] - m_timeStartedPlaying) :
   1188                 (m_timeStoppedPlaying - m_timeStartedPlaying) );
   1189             String text = String::format("%1.2f", frameRate);
   1190             TextRun textRun(text.characters(), text.length());
   1191             const Color color(255, 0, 0);
   1192             context->scale(FloatSize(1.0f, -1.0f));
   1193             context->setStrokeColor(color, styleToUse->colorSpace());
   1194             context->setStrokeStyle(SolidStroke);
   1195             context->setStrokeThickness(1.0f);
   1196             context->setFillColor(color, styleToUse->colorSpace());
   1197             context->drawText(styleToUse->font(), textRun, IntPoint(2, -3));
   1198         }
   1199     }
   1200 #endif
   1201 
   1202     context->restore();
   1203     END_BLOCK_OBJC_EXCEPTIONS;
   1204     [m_objcObserver.get() setDelayCallbacks:NO];
   1205 }
   1206 
   1207 static void addFileTypesToCache(NSArray * fileTypes, HashSet<String> &cache)
   1208 {
   1209     int count = [fileTypes count];
   1210     for (int n = 0; n < count; n++) {
   1211         CFStringRef ext = reinterpret_cast<CFStringRef>([fileTypes objectAtIndex:n]);
   1212         RetainPtr<CFStringRef> uti(AdoptCF, UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, ext, NULL));
   1213         if (!uti)
   1214             continue;
   1215         RetainPtr<CFStringRef> mime(AdoptCF, UTTypeCopyPreferredTagWithClass(uti.get(), kUTTagClassMIMEType));
   1216 
   1217         // UTI types are missing many media related MIME types supported by QTKit, see rdar://6434168,
   1218         // and not all third party movie importers register their types, so if we didn't find a type for
   1219         // this extension look it up in the hard coded table in the MIME type regsitry.
   1220         if (!mime) {
   1221             // -movieFileTypes: returns both file extensions and OSTypes. The later are surrounded by single
   1222             // quotes, eg. 'MooV', so don't bother looking at those.
   1223             if (CFStringGetCharacterAtIndex(ext, 0) != '\'') {
   1224                 String mediaType = MIMETypeRegistry::getMediaMIMETypeForExtension(String(ext));
   1225                 if (!mediaType.isEmpty())
   1226                     mime.adoptCF(mediaType.createCFString());
   1227             }
   1228         }
   1229         if (!mime)
   1230             continue;
   1231         cache.add(mime.get());
   1232     }
   1233 }
   1234 
   1235 static HashSet<String> mimeCommonTypesCache()
   1236 {
   1237     DEFINE_STATIC_LOCAL(HashSet<String>, cache, ());
   1238     static bool typeListInitialized = false;
   1239 
   1240     if (!typeListInitialized) {
   1241         typeListInitialized = true;
   1242         NSArray* fileTypes = [QTMovie movieFileTypes:QTIncludeCommonTypes];
   1243         addFileTypesToCache(fileTypes, cache);
   1244     }
   1245 
   1246     return cache;
   1247 }
   1248 
   1249 static HashSet<String> mimeModernTypesCache()
   1250 {
   1251     DEFINE_STATIC_LOCAL(HashSet<String>, cache, ());
   1252     static bool typeListInitialized = false;
   1253 
   1254     if (!typeListInitialized) {
   1255         typeListInitialized = true;
   1256         NSArray* fileTypes = [QTMovie movieFileTypes:(QTMovieFileTypeOptions)wkQTIncludeOnlyModernMediaFileTypes()];
   1257         addFileTypesToCache(fileTypes, cache);
   1258     }
   1259 
   1260     return cache;
   1261 }
   1262 
   1263 void MediaPlayerPrivate::getSupportedTypes(HashSet<String>& types)
   1264 {
   1265     // Note: this method starts QTKitServer if it isn't already running when in 64-bit because it has to return the list
   1266     // of every MIME type supported by QTKit.
   1267     types = mimeCommonTypesCache();
   1268 }
   1269 
   1270 MediaPlayer::SupportsType MediaPlayerPrivate::supportsType(const String& type, const String& codecs)
   1271 {
   1272     // Only return "IsSupported" if there is no codecs parameter for now as there is no way to ask QT if it supports an
   1273     // extended MIME type yet.
   1274 
   1275     // We check the "modern" type cache first, as it doesn't require QTKitServer to start.
   1276     if (mimeModernTypesCache().contains(type) || mimeCommonTypesCache().contains(type))
   1277         return codecs.isEmpty() ? MediaPlayer::MayBeSupported : MediaPlayer::IsSupported;
   1278 
   1279     return MediaPlayer::IsNotSupported;
   1280 }
   1281 
   1282 bool MediaPlayerPrivate::isAvailable()
   1283 {
   1284 #ifdef BUILDING_ON_TIGER
   1285     SInt32 version;
   1286     OSErr result;
   1287     result = Gestalt(gestaltQuickTime, &version);
   1288     if (result != noErr) {
   1289         LOG_ERROR("No QuickTime available. Disabling <video> and <audio> support.");
   1290         return false;
   1291     }
   1292     if (version < minimumQuickTimeVersion) {
   1293         LOG_ERROR("QuickTime version %x detected, at least %x required. Disabling <video> and <audio> support.", version, minimumQuickTimeVersion);
   1294         return false;
   1295     }
   1296     return true;
   1297 #else
   1298     // 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.
   1299     return QTKitLibrary();
   1300 #endif
   1301 }
   1302 
   1303 void MediaPlayerPrivate::disableUnsupportedTracks()
   1304 {
   1305     if (!m_qtMovie) {
   1306         m_enabledTrackCount = 0;
   1307         m_totalTrackCount = 0;
   1308         return;
   1309     }
   1310 
   1311     static HashSet<String>* allowedTrackTypes = 0;
   1312     if (!allowedTrackTypes) {
   1313         allowedTrackTypes = new HashSet<String>;
   1314         allowedTrackTypes->add(QTMediaTypeVideo);
   1315         allowedTrackTypes->add(QTMediaTypeSound);
   1316         allowedTrackTypes->add(QTMediaTypeText);
   1317         allowedTrackTypes->add(QTMediaTypeBase);
   1318         allowedTrackTypes->add(QTMediaTypeMPEG);
   1319         allowedTrackTypes->add("clcp"); // Closed caption
   1320         allowedTrackTypes->add("sbtl"); // Subtitle
   1321         allowedTrackTypes->add("odsm"); // MPEG-4 object descriptor stream
   1322         allowedTrackTypes->add("sdsm"); // MPEG-4 scene description stream
   1323         allowedTrackTypes->add("tmcd"); // timecode
   1324         allowedTrackTypes->add("tc64"); // timcode-64
   1325     }
   1326 
   1327     NSArray *tracks = [m_qtMovie.get() tracks];
   1328 
   1329     m_totalTrackCount = [tracks count];
   1330     m_enabledTrackCount = m_totalTrackCount;
   1331     for (unsigned trackIndex = 0; trackIndex < m_totalTrackCount; trackIndex++) {
   1332         // Grab the track at the current index. If there isn't one there, then
   1333         // we can move onto the next one.
   1334         QTTrack *track = [tracks objectAtIndex:trackIndex];
   1335         if (!track)
   1336             continue;
   1337 
   1338         // Check to see if the track is disabled already, we should move along.
   1339         // We don't need to re-disable it.
   1340         if (![track isEnabled]) {
   1341             --m_enabledTrackCount;
   1342             continue;
   1343         }
   1344 
   1345         // Get the track's media type.
   1346         NSString *mediaType = [track attributeForKey:QTTrackMediaTypeAttribute];
   1347         if (!mediaType)
   1348             continue;
   1349 
   1350         // Test whether the media type is in our white list.
   1351         if (!allowedTrackTypes->contains(mediaType)) {
   1352             // If this track type is not allowed, then we need to disable it.
   1353             [track setEnabled:NO];
   1354             --m_enabledTrackCount;
   1355             m_hasUnsupportedTracks = true;
   1356         }
   1357 
   1358         // Disable chapter tracks. These are most likely to lead to trouble, as
   1359         // they will be composited under the video tracks, forcing QT to do extra
   1360         // work.
   1361         QTTrack *chapterTrack = [track performSelector:@selector(chapterlist)];
   1362         if (!chapterTrack)
   1363             continue;
   1364 
   1365         // Try to grab the media for the track.
   1366         QTMedia *chapterMedia = [chapterTrack media];
   1367         if (!chapterMedia)
   1368             continue;
   1369 
   1370         // Grab the media type for this track.
   1371         id chapterMediaType = [chapterMedia attributeForKey:QTMediaTypeAttribute];
   1372         if (!chapterMediaType)
   1373             continue;
   1374 
   1375         // Check to see if the track is a video track. We don't care about
   1376         // other non-video tracks.
   1377         if (![chapterMediaType isEqual:QTMediaTypeVideo])
   1378             continue;
   1379 
   1380         // Check to see if the track is already disabled. If it is, we
   1381         // should move along.
   1382         if (![chapterTrack isEnabled])
   1383             continue;
   1384 
   1385         // Disable the evil, evil track.
   1386         [chapterTrack setEnabled:NO];
   1387         --m_enabledTrackCount;
   1388         m_hasUnsupportedTracks = true;
   1389     }
   1390 }
   1391 
   1392 void MediaPlayerPrivate::sawUnsupportedTracks()
   1393 {
   1394     m_hasUnsupportedTracks = true;
   1395     m_player->mediaPlayerClient()->mediaPlayerSawUnsupportedTracks(m_player);
   1396 }
   1397 
   1398 #if USE(ACCELERATED_COMPOSITING)
   1399 bool MediaPlayerPrivate::supportsAcceleratedRendering() const
   1400 {
   1401     // When in the media document we render via QTMovieView, which is already accelerated.
   1402     return isReadyForRendering() && getQTMovieLayerClass() != Nil && !m_player->inMediaDocument();
   1403 }
   1404 
   1405 void MediaPlayerPrivate::acceleratedRenderingStateChanged()
   1406 {
   1407     // Set up or change the rendering path if necessary.
   1408     setUpVideoRendering();
   1409 
   1410     if (currentRenderingMode() == MediaRenderingMovieLayer) {
   1411         GraphicsLayer* videoGraphicsLayer = m_player->mediaPlayerClient()->mediaPlayerGraphicsLayer(m_player);
   1412         if (videoGraphicsLayer)
   1413             videoGraphicsLayer->setContentsToMedia(m_qtVideoLayer.get());
   1414     }
   1415 }
   1416 #endif
   1417 
   1418 bool MediaPlayerPrivate::hasSingleSecurityOrigin() const
   1419 {
   1420     // We tell quicktime to disallow resources that come from different origins
   1421     // so we know all media is single origin.
   1422     return true;
   1423 }
   1424 
   1425 MediaPlayer::MovieLoadType MediaPlayerPrivate::movieLoadType() const
   1426 {
   1427     if (!m_qtMovie)
   1428         return MediaPlayer::Unknown;
   1429 
   1430     MediaPlayer::MovieLoadType movieType = (MediaPlayer::MovieLoadType)wkQTMovieGetType(m_qtMovie.get());
   1431 
   1432     // Can't include WebKitSystemInterface from WebCore so we can't get the enum returned
   1433     // by wkQTMovieGetType, but at least verify that the value is in the valid range.
   1434     ASSERT(movieType >= MediaPlayer::Unknown && movieType <= MediaPlayer::LiveStream);
   1435 
   1436     return movieType;
   1437 }
   1438 
   1439 
   1440 } // namespace WebCore
   1441 
   1442 @implementation WebCoreMovieObserver
   1443 
   1444 - (id)initWithCallback:(MediaPlayerPrivate*)callback
   1445 {
   1446     m_callback = callback;
   1447     return [super init];
   1448 }
   1449 
   1450 - (void)disconnect
   1451 {
   1452     [NSObject cancelPreviousPerformRequestsWithTarget:self];
   1453     m_callback = 0;
   1454 }
   1455 
   1456 -(NSMenu*)menuForEventDelegate:(NSEvent*)theEvent
   1457 {
   1458     // Get the contextual menu from the QTMovieView's superview, the frame view
   1459     return [[m_view superview] menuForEvent:theEvent];
   1460 }
   1461 
   1462 -(void)setView:(NSView*)view
   1463 {
   1464     m_view = view;
   1465 }
   1466 
   1467 -(void)repaint
   1468 {
   1469     if (m_delayCallbacks)
   1470         [self performSelector:_cmd withObject:nil afterDelay:0.];
   1471     else if (m_callback)
   1472         m_callback->repaint();
   1473 }
   1474 
   1475 - (void)loadStateChanged:(NSNotification *)unusedNotification
   1476 {
   1477     UNUSED_PARAM(unusedNotification);
   1478     if (m_delayCallbacks)
   1479         [self performSelector:_cmd withObject:nil afterDelay:0];
   1480     else
   1481         m_callback->loadStateChanged();
   1482 }
   1483 
   1484 - (void)rateChanged:(NSNotification *)unusedNotification
   1485 {
   1486     UNUSED_PARAM(unusedNotification);
   1487     if (m_delayCallbacks)
   1488         [self performSelector:_cmd withObject:nil afterDelay:0];
   1489     else
   1490         m_callback->rateChanged();
   1491 }
   1492 
   1493 - (void)sizeChanged:(NSNotification *)unusedNotification
   1494 {
   1495     UNUSED_PARAM(unusedNotification);
   1496     if (m_delayCallbacks)
   1497         [self performSelector:_cmd withObject:nil afterDelay:0];
   1498     else
   1499         m_callback->sizeChanged();
   1500 }
   1501 
   1502 - (void)timeChanged:(NSNotification *)unusedNotification
   1503 {
   1504     UNUSED_PARAM(unusedNotification);
   1505     if (m_delayCallbacks)
   1506         [self performSelector:_cmd withObject:nil afterDelay:0];
   1507     else
   1508         m_callback->timeChanged();
   1509 }
   1510 
   1511 - (void)didEnd:(NSNotification *)unusedNotification
   1512 {
   1513     UNUSED_PARAM(unusedNotification);
   1514     if (m_delayCallbacks)
   1515         [self performSelector:_cmd withObject:nil afterDelay:0];
   1516     else
   1517         m_callback->didEnd();
   1518 }
   1519 
   1520 - (void)newImageAvailable:(NSNotification *)unusedNotification
   1521 {
   1522     UNUSED_PARAM(unusedNotification);
   1523     [self repaint];
   1524 }
   1525 
   1526 - (void)setDelayCallbacks:(BOOL)shouldDelay
   1527 {
   1528     m_delayCallbacks = shouldDelay;
   1529 }
   1530 
   1531 @end
   1532 
   1533 #endif
   1534