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