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