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