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