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