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