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