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