• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
61static 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
69SOFT_LINK_FRAMEWORK(QTKit)
70
71SOFT_LINK(QTKit, QTMakeTime, QTTime, (long long timeValue, long timeScale), (timeValue, timeScale))
72
73SOFT_LINK_CLASS(QTKit, QTMovie)
74SOFT_LINK_CLASS(QTKit, QTMovieView)
75SOFT_LINK_CLASS(QTKit, QTMovieLayer)
76
77SOFT_LINK_POINTER(QTKit, QTTrackMediaTypeAttribute, NSString *)
78SOFT_LINK_POINTER(QTKit, QTMediaTypeAttribute, NSString *)
79SOFT_LINK_POINTER(QTKit, QTMediaTypeBase, NSString *)
80SOFT_LINK_POINTER(QTKit, QTMediaTypeMPEG, NSString *)
81SOFT_LINK_POINTER(QTKit, QTMediaTypeSound, NSString *)
82SOFT_LINK_POINTER(QTKit, QTMediaTypeText, NSString *)
83SOFT_LINK_POINTER(QTKit, QTMediaTypeVideo, NSString *)
84SOFT_LINK_POINTER(QTKit, QTMovieAskUnresolvedDataRefsAttribute, NSString *)
85SOFT_LINK_POINTER(QTKit, QTMovieDataSizeAttribute, NSString *)
86SOFT_LINK_POINTER(QTKit, QTMovieDidEndNotification, NSString *)
87SOFT_LINK_POINTER(QTKit, QTMovieHasVideoAttribute, NSString *)
88SOFT_LINK_POINTER(QTKit, QTMovieHasAudioAttribute, NSString *)
89SOFT_LINK_POINTER(QTKit, QTMovieIsActiveAttribute, NSString *)
90SOFT_LINK_POINTER(QTKit, QTMovieLoadStateAttribute, NSString *)
91SOFT_LINK_POINTER(QTKit, QTMovieLoadStateDidChangeNotification, NSString *)
92SOFT_LINK_POINTER(QTKit, QTMovieNaturalSizeAttribute, NSString *)
93SOFT_LINK_POINTER(QTKit, QTMovieCurrentSizeAttribute, NSString *)
94SOFT_LINK_POINTER(QTKit, QTMoviePreventExternalURLLinksAttribute, NSString *)
95SOFT_LINK_POINTER(QTKit, QTMovieRateChangesPreservePitchAttribute, NSString *)
96SOFT_LINK_POINTER(QTKit, QTMovieRateDidChangeNotification, NSString *)
97SOFT_LINK_POINTER(QTKit, QTMovieSizeDidChangeNotification, NSString *)
98SOFT_LINK_POINTER(QTKit, QTMovieTimeDidChangeNotification, NSString *)
99SOFT_LINK_POINTER(QTKit, QTMovieTimeScaleAttribute, NSString *)
100SOFT_LINK_POINTER(QTKit, QTMovieURLAttribute, NSString *)
101SOFT_LINK_POINTER(QTKit, QTMovieVolumeDidChangeNotification, NSString *)
102SOFT_LINK_POINTER(QTKit, QTSecurityPolicyNoCrossSiteAttribute, NSString *)
103SOFT_LINK_POINTER(QTKit, QTVideoRendererWebKitOnlyNewImageAvailableNotification, NSString *)
104#ifndef BUILDING_ON_TIGER
105SOFT_LINK_POINTER(QTKit, QTMovieApertureModeClean, NSString *)
106SOFT_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
147enum {
148    QTMovieLoadStateError = -1L,
149    QTMovieLoadStateLoaded  = 2000L,
150    QTMovieLoadStatePlayable = 10000L,
151    QTMovieLoadStatePlaythroughOK = 20000L,
152    QTMovieLoadStateComplete = 100000L
153};
154#endif
155
156using namespace WebCore;
157using 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
182namespace WebCore {
183
184#ifdef BUILDING_ON_TIGER
185static const long minimumQuickTimeVersion = 0x07300000; // 7.3
186#endif
187
188
189MediaPlayerPrivateInterface* MediaPlayerPrivate::create(MediaPlayer* player)
190{
191    return new MediaPlayerPrivate(player);
192}
193
194void MediaPlayerPrivate::registerMediaEngine(MediaEngineRegistrar registrar)
195{
196    if (isAvailable())
197        registrar(create, getSupportedTypes, supportsType);
198}
199
200MediaPlayerPrivate::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
227MediaPlayerPrivate::~MediaPlayerPrivate()
228{
229    tearDownVideoRendering();
230
231    [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()];
232    [m_objcObserver.get() disconnect];
233}
234
235void 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
255void 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
314static 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
329static Class QTVideoRendererClass()
330{
331     static Class QTVideoRendererWebKitOnlyClass = NSClassFromString(@"QTVideoRendererWebKitOnly");
332     return QTVideoRendererWebKitOnlyClass;
333}
334
335void 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
380void 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
395void 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
415void 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
431void 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
458void 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
470MediaPlayerPrivate::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
484MediaPlayerPrivate::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
500void 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
527void MediaPlayerPrivate::tearDownVideoRendering()
528{
529    if (m_qtMovieView)
530        detachQTMovieView();
531    if (m_qtVideoRenderer)
532        destroyQTVideoRenderer();
533    if (m_qtVideoLayer)
534        destroyQTMovieLayer();
535}
536
537bool MediaPlayerPrivate::hasSetUpVideoRendering() const
538{
539    return m_qtMovieView
540        || m_qtVideoLayer
541        || m_qtVideoRenderer;
542}
543
544QTTime 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
552void 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
573PlatformMedia MediaPlayerPrivate::platformMedia() const
574{
575    PlatformMedia plaftformMedia = { m_qtMovie.get() };
576    return plaftformMedia;
577}
578
579void 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
592void 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
605float 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
619float 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
627void 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
648void 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
668void MediaPlayerPrivate::cancelSeek()
669{
670    m_seekTo = -1;
671    m_seekTimer.stop();
672}
673
674void 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
695bool MediaPlayerPrivate::paused() const
696{
697    if (!metaDataAvailable())
698        return true;
699    return [m_qtMovie.get() rate] == 0;
700}
701
702bool MediaPlayerPrivate::seeking() const
703{
704    if (!metaDataAvailable())
705        return false;
706    return m_seekTo >= 0;
707}
708
709IntSize 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
725bool MediaPlayerPrivate::hasVideo() const
726{
727    if (!metaDataAvailable())
728        return false;
729    return [[m_qtMovie.get() attributeForKey:QTMovieHasVideoAttribute] boolValue];
730}
731
732bool MediaPlayerPrivate::hasAudio() const
733{
734    if (!m_qtMovie)
735        return false;
736    return [[m_qtMovie.get() attributeForKey:QTMovieHasAudioAttribute] boolValue];
737}
738
739bool 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
749void MediaPlayerPrivate::setVolume(float volume)
750{
751    if (m_qtMovie)
752        [m_qtMovie.get() setVolume:volume];
753}
754
755bool MediaPlayerPrivate::hasClosedCaptions() const
756{
757    if (!metaDataAvailable())
758        return false;
759    return wkQTMovieHasClosedCaptions(m_qtMovie.get());
760}
761
762void 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
776void MediaPlayerPrivate::setRate(float rate)
777{
778    if (m_qtMovie)
779        [m_qtMovie.get() setRate:rate];
780}
781
782void 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
800PassRefPtr<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
809float 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
821float MediaPlayerPrivate::maxTimeLoaded() const
822{
823    if (!metaDataAvailable())
824        return 0;
825    return wkQTMovieMaxTimeLoaded(m_qtMovie.get());
826}
827
828unsigned MediaPlayerPrivate::bytesLoaded() const
829{
830    float dur = duration();
831    if (!dur)
832        return 0;
833    return totalBytes() * maxTimeLoaded() / dur;
834}
835
836unsigned MediaPlayerPrivate::totalBytes() const
837{
838    if (!metaDataAvailable())
839        return 0;
840    return [[m_qtMovie.get() attributeForKey:QTMovieDataSizeAttribute] intValue];
841}
842
843void 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
855void 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
880bool MediaPlayerPrivate::isReadyForRendering() const
881{
882    return m_readyState >= MediaPlayer::HaveMetadata && m_player->visible();
883}
884
885void 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
999void MediaPlayerPrivate::loadStateChanged()
1000{
1001    if (!m_hasUnsupportedTracks)
1002        updateStates();
1003}
1004
1005void MediaPlayerPrivate::rateChanged()
1006{
1007    if (m_hasUnsupportedTracks)
1008        return;
1009
1010    updateStates();
1011    m_player->rateChanged();
1012}
1013
1014void MediaPlayerPrivate::sizeChanged()
1015{
1016    if (!m_hasUnsupportedTracks)
1017        m_player->sizeChanged();
1018}
1019
1020void 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
1036void 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
1058void 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
1069void 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
1080bool 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
1094void 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
1112void 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
1126void 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
1207static 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
1235static 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
1249static 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
1263void 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
1270MediaPlayer::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
1282bool 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
1303void 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
1392void MediaPlayerPrivate::sawUnsupportedTracks()
1393{
1394    m_hasUnsupportedTracks = true;
1395    m_player->mediaPlayerClient()->mediaPlayerSawUnsupportedTracks(m_player);
1396}
1397
1398#if USE(ACCELERATED_COMPOSITING)
1399bool 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
1405void 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
1418bool 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
1425MediaPlayer::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