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