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