2 * Copyright (C) 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
30 #import "MediaPlayerPrivateQTKit.h"
32 #if ENABLE(OFFLINE_WEB_APPLICATIONS)
33 #include "ApplicationCacheHost.h"
34 #include "ApplicationCacheResource.h"
37 #import "BlockExceptions.h"
38 #import "DocumentLoader.h"
41 #import "HostWindow.h"
42 #import "GraphicsContext.h"
44 #import "MIMETypeRegistry.h"
45 #import "SoftLinking.h"
46 #import "TimeRanges.h"
47 #import "WebCoreSystemInterface.h"
48 #import <QTKit/QTKit.h>
49 #import <objc/objc-runtime.h>
50 #import <wtf/UnusedParam.h>
52 #if USE(ACCELERATED_COMPOSITING)
53 #include "GraphicsLayer.h"
59 #import "RenderObject.h"
60 #import "RenderStyle.h"
63 SOFT_LINK_FRAMEWORK(QTKit)
65 SOFT_LINK(QTKit, QTMakeTime, QTTime, (long long timeValue, long timeScale), (timeValue, timeScale))
67 SOFT_LINK_CLASS(QTKit, QTMovie)
68 SOFT_LINK_CLASS(QTKit, QTMovieView)
69 SOFT_LINK_CLASS(QTKit, QTMovieLayer)
71 SOFT_LINK_POINTER(QTKit, QTTrackMediaTypeAttribute, NSString *)
72 SOFT_LINK_POINTER(QTKit, QTMediaTypeAttribute, NSString *)
73 SOFT_LINK_POINTER(QTKit, QTMediaTypeBase, NSString *)
74 SOFT_LINK_POINTER(QTKit, QTMediaTypeMPEG, NSString *)
75 SOFT_LINK_POINTER(QTKit, QTMediaTypeSound, NSString *)
76 SOFT_LINK_POINTER(QTKit, QTMediaTypeText, NSString *)
77 SOFT_LINK_POINTER(QTKit, QTMediaTypeVideo, NSString *)
78 SOFT_LINK_POINTER(QTKit, QTMovieAskUnresolvedDataRefsAttribute, NSString *)
79 SOFT_LINK_POINTER(QTKit, QTMovieLoopsAttribute, NSString *)
80 SOFT_LINK_POINTER(QTKit, QTMovieDataAttribute, NSString *)
81 SOFT_LINK_POINTER(QTKit, QTMovieDataSizeAttribute, NSString *)
82 SOFT_LINK_POINTER(QTKit, QTMovieDidEndNotification, NSString *)
83 SOFT_LINK_POINTER(QTKit, QTMovieHasVideoAttribute, NSString *)
84 SOFT_LINK_POINTER(QTKit, QTMovieHasAudioAttribute, NSString *)
85 SOFT_LINK_POINTER(QTKit, QTMovieIsActiveAttribute, NSString *)
86 SOFT_LINK_POINTER(QTKit, QTMovieLoadStateAttribute, NSString *)
87 SOFT_LINK_POINTER(QTKit, QTMovieLoadStateDidChangeNotification, NSString *)
88 SOFT_LINK_POINTER(QTKit, QTMovieNaturalSizeAttribute, NSString *)
89 SOFT_LINK_POINTER(QTKit, QTMovieCurrentSizeAttribute, NSString *)
90 SOFT_LINK_POINTER(QTKit, QTMoviePreventExternalURLLinksAttribute, NSString *)
91 SOFT_LINK_POINTER(QTKit, QTMovieRateChangesPreservePitchAttribute, NSString *)
92 SOFT_LINK_POINTER(QTKit, QTMovieRateDidChangeNotification, NSString *)
93 SOFT_LINK_POINTER(QTKit, QTMovieSizeDidChangeNotification, NSString *)
94 SOFT_LINK_POINTER(QTKit, QTMovieTimeDidChangeNotification, NSString *)
95 SOFT_LINK_POINTER(QTKit, QTMovieTimeScaleAttribute, NSString *)
96 SOFT_LINK_POINTER(QTKit, QTMovieURLAttribute, NSString *)
97 SOFT_LINK_POINTER(QTKit, QTMovieVolumeDidChangeNotification, NSString *)
98 SOFT_LINK_POINTER(QTKit, QTSecurityPolicyNoCrossSiteAttribute, NSString *)
99 SOFT_LINK_POINTER(QTKit, QTVideoRendererWebKitOnlyNewImageAvailableNotification, NSString *)
100 SOFT_LINK_POINTER(QTKit, QTMovieApertureModeClean, NSString *)
101 SOFT_LINK_POINTER(QTKit, QTMovieApertureModeAttribute, NSString *)
103 #define QTMovie getQTMovieClass()
104 #define QTMovieView getQTMovieViewClass()
105 #define QTMovieLayer getQTMovieLayerClass()
107 #define QTTrackMediaTypeAttribute getQTTrackMediaTypeAttribute()
108 #define QTMediaTypeAttribute getQTMediaTypeAttribute()
109 #define QTMediaTypeBase getQTMediaTypeBase()
110 #define QTMediaTypeMPEG getQTMediaTypeMPEG()
111 #define QTMediaTypeSound getQTMediaTypeSound()
112 #define QTMediaTypeText getQTMediaTypeText()
113 #define QTMediaTypeVideo getQTMediaTypeVideo()
114 #define QTMovieAskUnresolvedDataRefsAttribute getQTMovieAskUnresolvedDataRefsAttribute()
115 #define QTMovieLoopsAttribute getQTMovieLoopsAttribute()
116 #define QTMovieDataAttribute getQTMovieDataAttribute()
117 #define QTMovieDataSizeAttribute getQTMovieDataSizeAttribute()
118 #define QTMovieDidEndNotification getQTMovieDidEndNotification()
119 #define QTMovieHasVideoAttribute getQTMovieHasVideoAttribute()
120 #define QTMovieHasAudioAttribute getQTMovieHasAudioAttribute()
121 #define QTMovieIsActiveAttribute getQTMovieIsActiveAttribute()
122 #define QTMovieLoadStateAttribute getQTMovieLoadStateAttribute()
123 #define QTMovieLoadStateDidChangeNotification getQTMovieLoadStateDidChangeNotification()
124 #define QTMovieNaturalSizeAttribute getQTMovieNaturalSizeAttribute()
125 #define QTMovieCurrentSizeAttribute getQTMovieCurrentSizeAttribute()
126 #define QTMoviePreventExternalURLLinksAttribute getQTMoviePreventExternalURLLinksAttribute()
127 #define QTMovieRateChangesPreservePitchAttribute getQTMovieRateChangesPreservePitchAttribute()
128 #define QTMovieRateDidChangeNotification getQTMovieRateDidChangeNotification()
129 #define QTMovieSizeDidChangeNotification getQTMovieSizeDidChangeNotification()
130 #define QTMovieTimeDidChangeNotification getQTMovieTimeDidChangeNotification()
131 #define QTMovieTimeScaleAttribute getQTMovieTimeScaleAttribute()
132 #define QTMovieURLAttribute getQTMovieURLAttribute()
133 #define QTMovieVolumeDidChangeNotification getQTMovieVolumeDidChangeNotification()
134 #define QTSecurityPolicyNoCrossSiteAttribute getQTSecurityPolicyNoCrossSiteAttribute()
135 #define QTVideoRendererWebKitOnlyNewImageAvailableNotification getQTVideoRendererWebKitOnlyNewImageAvailableNotification()
136 #define QTMovieApertureModeClean getQTMovieApertureModeClean()
137 #define QTMovieApertureModeAttribute getQTMovieApertureModeAttribute()
139 // Older versions of the QTKit header don't have these constants.
140 #if !defined QTKIT_VERSION_MAX_ALLOWED || QTKIT_VERSION_MAX_ALLOWED <= QTKIT_VERSION_7_0
142 QTMovieLoadStateError = -1L,
143 QTMovieLoadStateLoaded = 2000L,
144 QTMovieLoadStatePlayable = 10000L,
145 QTMovieLoadStatePlaythroughOK = 20000L,
146 QTMovieLoadStateComplete = 100000L
150 @interface FakeQTMovieView : NSObject
151 - (WebCoreMovieObserver *)delegate;
154 using namespace WebCore;
157 @interface WebCoreMovieObserver : NSObject
159 MediaPlayerPrivateQTKit* m_callback;
161 BOOL m_delayCallbacks;
163 -(id)initWithCallback:(MediaPlayerPrivateQTKit*)callback;
165 -(void)setView:(NSView*)view;
167 -(void)setDelayCallbacks:(BOOL)shouldDelay;
168 -(void)loadStateChanged:(NSNotification *)notification;
169 -(void)rateChanged:(NSNotification *)notification;
170 -(void)sizeChanged:(NSNotification *)notification;
171 -(void)timeChanged:(NSNotification *)notification;
172 -(void)didEnd:(NSNotification *)notification;
173 -(void)layerHostChanged:(NSNotification *)notification;
176 @protocol WebKitVideoRenderingDetails
177 -(void)setMovie:(id)movie;
178 -(void)drawInRect:(NSRect)rect;
183 PassOwnPtr<MediaPlayerPrivateInterface> MediaPlayerPrivateQTKit::create(MediaPlayer* player)
185 return adoptPtr(new MediaPlayerPrivateQTKit(player));
188 void MediaPlayerPrivateQTKit::registerMediaEngine(MediaEngineRegistrar registrar)
191 registrar(create, getSupportedTypes, supportsType, getSitesInMediaCache, clearMediaCache, clearMediaCacheForSite);
194 MediaPlayerPrivateQTKit::MediaPlayerPrivateQTKit(MediaPlayer* player)
196 , m_objcObserver(AdoptNS, [[WebCoreMovieObserver alloc] initWithCallback:this])
198 , m_seekTimer(this, &MediaPlayerPrivateQTKit::seekTimerFired)
199 , m_networkState(MediaPlayer::Empty)
200 , m_readyState(MediaPlayer::HaveNothing)
202 , m_scaleFactor(1, 1)
203 , m_enabledTrackCount(0)
204 , m_totalTrackCount(0)
205 , m_reportedDuration(-1)
206 , m_cachedDuration(-1)
207 , m_timeToRestore(-1)
208 , m_preload(MediaPlayer::Auto)
209 , m_startedPlaying(false)
210 , m_isStreaming(false)
212 , m_hasUnsupportedTracks(false)
213 , m_videoFrameHasDrawn(false)
214 , m_isAllowedToRender(false)
215 , m_privateBrowsing(false)
217 , m_frameCountWhilePlaying(0)
218 , m_timeStartedPlaying(0)
219 , m_timeStoppedPlaying(0)
224 MediaPlayerPrivateQTKit::~MediaPlayerPrivateQTKit()
226 tearDownVideoRendering();
228 [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()];
229 [m_objcObserver.get() disconnect];
232 NSMutableDictionary *MediaPlayerPrivateQTKit::commonMovieAttributes()
234 NSMutableDictionary *movieAttributes = [NSMutableDictionary dictionaryWithObjectsAndKeys:
235 [NSNumber numberWithBool:m_player->preservesPitch()], QTMovieRateChangesPreservePitchAttribute,
236 [NSNumber numberWithBool:YES], QTMoviePreventExternalURLLinksAttribute,
237 [NSNumber numberWithBool:YES], QTSecurityPolicyNoCrossSiteAttribute,
238 [NSNumber numberWithBool:NO], QTMovieAskUnresolvedDataRefsAttribute,
239 [NSNumber numberWithBool:NO], QTMovieLoopsAttribute,
240 [NSNumber numberWithBool:!m_privateBrowsing], @"QTMovieAllowPersistentCacheAttribute",
241 QTMovieApertureModeClean, QTMovieApertureModeAttribute,
244 if (m_preload < MediaPlayer::Auto)
245 [movieAttributes setValue:[NSNumber numberWithBool:YES] forKey:@"QTMovieLimitReadAheadAttribute"];
247 return movieAttributes;
250 void MediaPlayerPrivateQTKit::createQTMovie(const String& url)
252 NSURL *cocoaURL = KURL(ParsedURLString, url);
253 NSMutableDictionary *movieAttributes = commonMovieAttributes();
254 [movieAttributes setValue:cocoaURL forKey:QTMovieURLAttribute];
256 #if !defined(BUILDING_ON_LEOPARD)
257 CFDictionaryRef proxySettings = CFNetworkCopySystemProxySettings();
258 CFArrayRef proxiesForURL = CFNetworkCopyProxiesForURL((CFURLRef)cocoaURL, proxySettings);
259 BOOL willUseProxy = YES;
261 if (!proxiesForURL || !CFArrayGetCount(proxiesForURL))
264 if (CFArrayGetCount(proxiesForURL) == 1) {
265 CFDictionaryRef proxy = (CFDictionaryRef)CFArrayGetValueAtIndex(proxiesForURL, 0);
266 ASSERT(CFGetTypeID(proxy) == CFDictionaryGetTypeID());
268 CFStringRef proxyType = (CFStringRef)CFDictionaryGetValue(proxy, kCFProxyTypeKey);
269 ASSERT(CFGetTypeID(proxyType) == CFStringGetTypeID());
271 if (CFStringCompare(proxyType, kCFProxyTypeNone, 0) == kCFCompareEqualTo)
276 // Only pass the QTMovieOpenForPlaybackAttribute flag if there are no proxy servers, due
277 // to rdar://problem/7531776.
278 [movieAttributes setObject:[NSNumber numberWithBool:YES] forKey:@"QTMovieOpenForPlaybackAttribute"];
282 CFRelease(proxiesForURL);
284 CFRelease(proxySettings);
287 createQTMovie(cocoaURL, movieAttributes);
290 void MediaPlayerPrivateQTKit::createQTMovie(ApplicationCacheResource* resource)
292 #if ENABLE(OFFLINE_WEB_APPLICATIONS)
295 NSMutableDictionary *movieAttributes = commonMovieAttributes();
296 [movieAttributes setObject:[NSNumber numberWithBool:YES] forKey:@"QTMovieOpenForPlaybackAttribute"];
298 // ApplicationCacheResources can supply either a data pointer, or a path to a locally cached
299 // flat file. We would prefer the path over the data, but QTKit can handle either:
300 String localPath = resource->path();
301 NSURL* cocoaURL = !localPath.isEmpty() ? [NSURL fileURLWithPath:localPath isDirectory:NO] : nil;
303 [movieAttributes setValue:cocoaURL forKey:QTMovieURLAttribute];
305 NSData* movieData = resource->data()->createNSData();
306 [movieAttributes setValue:movieData forKey:QTMovieDataAttribute];
310 createQTMovie(cocoaURL, movieAttributes);
313 ASSERT_NOT_REACHED();
317 static void disableComponentsOnce()
319 static bool sComponentsDisabled = false;
320 if (sComponentsDisabled)
322 sComponentsDisabled = true;
324 // eat/PDF and grip/PDF components must be disabled twice since they are registered twice
325 // with different flags. However, there is currently a bug in 64-bit QTKit (<rdar://problem/8378237>)
326 // which causes subsequent disable component requests of exactly the same type to be ignored if
327 // QTKitServer has not yet started. As a result, we must pass in exactly the flags we want to
328 // disable per component. As a failsafe, if in the future these flags change, we will disable the
329 // PDF components for a third time with a wildcard flags field:
330 uint32_t componentsToDisable[11][5] = {
331 {'eat ', 'TEXT', 'text', 0, 0},
332 {'eat ', 'TXT ', 'text', 0, 0},
333 {'eat ', 'utxt', 'text', 0, 0},
334 {'eat ', 'TEXT', 'tx3g', 0, 0},
335 {'eat ', 'PDF ', 'vide', 0x44802, 0},
336 {'eat ', 'PDF ', 'vide', 0x45802, 0},
337 {'eat ', 'PDF ', 'vide', 0, 0},
338 {'grip', 'PDF ', 'appl', 0x844a00, 0},
339 {'grip', 'PDF ', 'appl', 0x845a00, 0},
340 {'grip', 'PDF ', 'appl', 0, 0},
341 {'imdc', 'pdf ', 'appl', 0, 0},
344 for (size_t i = 0; i < WTF_ARRAY_LENGTH(componentsToDisable); ++i)
345 wkQTMovieDisableComponent(componentsToDisable[i]);
348 void MediaPlayerPrivateQTKit::createQTMovie(NSURL *url, NSDictionary *movieAttributes)
350 disableComponentsOnce();
352 [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()];
354 bool recreating = false;
357 destroyQTVideoRenderer();
361 // Disable rtsp streams for now, <rdar://problem/5693967>
362 if (protocolIs([url scheme], "rtsp"))
365 NSError *error = nil;
366 m_qtMovie.adoptNS([[QTMovie alloc] initWithAttributes:movieAttributes error:&error]);
371 [m_qtMovie.get() setVolume:m_player->volume()];
373 if (recreating && hasVideo())
374 createQTVideoRenderer(QTVideoRendererModeListensForNewImages);
376 [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
377 selector:@selector(loadStateChanged:)
378 name:QTMovieLoadStateDidChangeNotification
379 object:m_qtMovie.get()];
381 // In updateState(), we track when maxTimeLoaded() == duration().
382 // In newer version of QuickTime, a notification is emitted when maxTimeLoaded changes.
383 // In older version of QuickTime, QTMovieLoadStateDidChangeNotification be fired.
384 if (NSString *maxTimeLoadedChangeNotification = wkQTMovieMaxTimeLoadedChangeNotification()) {
385 [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
386 selector:@selector(loadStateChanged:)
387 name:maxTimeLoadedChangeNotification
388 object:m_qtMovie.get()];
391 [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
392 selector:@selector(rateChanged:)
393 name:QTMovieRateDidChangeNotification
394 object:m_qtMovie.get()];
395 [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
396 selector:@selector(sizeChanged:)
397 name:QTMovieSizeDidChangeNotification
398 object:m_qtMovie.get()];
399 [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
400 selector:@selector(timeChanged:)
401 name:QTMovieTimeDidChangeNotification
402 object:m_qtMovie.get()];
403 [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
404 selector:@selector(didEnd:)
405 name:QTMovieDidEndNotification
406 object:m_qtMovie.get()];
407 #if defined(BUILDING_ON_SNOW_LEOPARD)
408 [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
409 selector:@selector(layerHostChanged:)
410 name:@"WebKitLayerHostChanged"
415 static void mainThreadSetNeedsDisplay(id self, SEL)
417 id view = [self superview];
418 ASSERT(!view || [view isKindOfClass:[QTMovieView class]]);
419 if (!view || ![view isKindOfClass:[QTMovieView class]])
422 FakeQTMovieView *movieView = static_cast<FakeQTMovieView *>(view);
423 WebCoreMovieObserver* delegate = [movieView delegate];
424 ASSERT(!delegate || [delegate isKindOfClass:[WebCoreMovieObserver class]]);
425 if (!delegate || ![delegate isKindOfClass:[WebCoreMovieObserver class]])
431 static Class QTVideoRendererClass()
433 static Class QTVideoRendererWebKitOnlyClass = NSClassFromString(@"QTVideoRendererWebKitOnly");
434 return QTVideoRendererWebKitOnlyClass;
437 void MediaPlayerPrivateQTKit::createQTMovieView()
441 static bool addedCustomMethods = false;
442 if (!m_player->inMediaDocument() && !addedCustomMethods) {
443 Class QTMovieContentViewClass = NSClassFromString(@"QTMovieContentView");
444 ASSERT(QTMovieContentViewClass);
446 Method mainThreadSetNeedsDisplayMethod = class_getInstanceMethod(QTMovieContentViewClass, @selector(_mainThreadSetNeedsDisplay));
447 ASSERT(mainThreadSetNeedsDisplayMethod);
449 method_setImplementation(mainThreadSetNeedsDisplayMethod, reinterpret_cast<IMP>(mainThreadSetNeedsDisplay));
450 addedCustomMethods = true;
453 // delay callbacks as we *will* get notifications during setup
454 [m_objcObserver.get() setDelayCallbacks:YES];
456 m_qtMovieView.adoptNS([[QTMovieView alloc] init]);
457 setSize(m_player->size());
458 NSView* parentView = 0;
460 parentView = m_player->frameView()->documentView();
461 [parentView addSubview:m_qtMovieView.get()];
463 [m_qtMovieView.get() setDelegate:m_objcObserver.get()];
464 [m_objcObserver.get() setView:m_qtMovieView.get()];
465 [m_qtMovieView.get() setMovie:m_qtMovie.get()];
466 [m_qtMovieView.get() setControllerVisible:NO];
467 [m_qtMovieView.get() setPreservesAspectRatio:NO];
468 // the area not covered by video should be transparent
469 [m_qtMovieView.get() setFillColor:[NSColor clearColor]];
471 // If we're in a media document, allow QTMovieView to render in its default mode;
472 // otherwise tell it to draw synchronously.
473 // Note that we expect mainThreadSetNeedsDisplay to be invoked only when synchronous drawing is requested.
474 if (!m_player->inMediaDocument())
475 wkQTMovieViewSetDrawSynchronously(m_qtMovieView.get(), YES);
477 [m_objcObserver.get() setDelayCallbacks:NO];
480 void MediaPlayerPrivateQTKit::detachQTMovieView()
483 [m_objcObserver.get() setView:nil];
484 [m_qtMovieView.get() setDelegate:nil];
485 [m_qtMovieView.get() removeFromSuperview];
490 void MediaPlayerPrivateQTKit::createQTVideoRenderer(QTVideoRendererMode rendererMode)
492 destroyQTVideoRenderer();
494 m_qtVideoRenderer.adoptNS([[QTVideoRendererClass() alloc] init]);
495 if (!m_qtVideoRenderer)
498 // associate our movie with our instance of QTVideoRendererWebKitOnly
499 [(id<WebKitVideoRenderingDetails>)m_qtVideoRenderer.get() setMovie:m_qtMovie.get()];
501 if (rendererMode == QTVideoRendererModeListensForNewImages) {
502 // listen to QTVideoRendererWebKitOnly's QTVideoRendererWebKitOnlyNewImageDidBecomeAvailableNotification
503 [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
504 selector:@selector(newImageAvailable:)
505 name:QTVideoRendererWebKitOnlyNewImageAvailableNotification
506 object:m_qtVideoRenderer.get()];
510 void MediaPlayerPrivateQTKit::destroyQTVideoRenderer()
512 if (!m_qtVideoRenderer)
515 // stop observing the renderer's notifications before we toss it
516 [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()
517 name:QTVideoRendererWebKitOnlyNewImageAvailableNotification
518 object:m_qtVideoRenderer.get()];
520 // disassociate our movie from our instance of QTVideoRendererWebKitOnly
521 [(id<WebKitVideoRenderingDetails>)m_qtVideoRenderer.get() setMovie:nil];
523 m_qtVideoRenderer = nil;
526 void MediaPlayerPrivateQTKit::createQTMovieLayer()
528 #if USE(ACCELERATED_COMPOSITING) && !(PLATFORM(QT) && USE(QTKIT))
532 ASSERT(supportsAcceleratedRendering());
534 if (!m_qtVideoLayer) {
535 m_qtVideoLayer.adoptNS([[QTMovieLayer alloc] init]);
539 [m_qtVideoLayer.get() setMovie:m_qtMovie.get()];
541 [(CALayer *)m_qtVideoLayer.get() setName:@"Video layer"];
543 // The layer will get hooked up via RenderLayerBacking::updateGraphicsLayerConfiguration().
548 void MediaPlayerPrivateQTKit::destroyQTMovieLayer()
550 #if USE(ACCELERATED_COMPOSITING)
554 // disassociate our movie from our instance of QTMovieLayer
555 [m_qtVideoLayer.get() setMovie:nil];
556 m_qtVideoLayer = nil;
560 MediaPlayerPrivateQTKit::MediaRenderingMode MediaPlayerPrivateQTKit::currentRenderingMode() const
563 return MediaRenderingMovieView;
566 return MediaRenderingMovieLayer;
568 if (m_qtVideoRenderer)
569 return MediaRenderingSoftwareRenderer;
571 return MediaRenderingNone;
574 MediaPlayerPrivateQTKit::MediaRenderingMode MediaPlayerPrivateQTKit::preferredRenderingMode() const
576 if (!m_player->frameView() || !m_qtMovie)
577 return MediaRenderingNone;
579 #if USE(ACCELERATED_COMPOSITING) && !(PLATFORM(QT) && USE(QTKIT))
580 if (supportsAcceleratedRendering() && m_player->mediaPlayerClient()->mediaPlayerRenderingCanBeAccelerated(m_player))
581 return MediaRenderingMovieLayer;
584 if (!QTVideoRendererClass())
585 return MediaRenderingMovieView;
587 return MediaRenderingSoftwareRenderer;
590 void MediaPlayerPrivateQTKit::setUpVideoRendering()
592 if (!isReadyForVideoSetup())
595 MediaRenderingMode currentMode = currentRenderingMode();
596 MediaRenderingMode preferredMode = preferredRenderingMode();
597 if (currentMode == preferredMode && currentMode != MediaRenderingNone)
600 if (currentMode != MediaRenderingNone)
601 tearDownVideoRendering();
603 switch (preferredMode) {
604 case MediaRenderingMovieView:
607 case MediaRenderingNone:
608 case MediaRenderingSoftwareRenderer:
609 createQTVideoRenderer(QTVideoRendererModeListensForNewImages);
611 case MediaRenderingMovieLayer:
612 createQTMovieLayer();
616 // If using a movie layer, inform the client so the compositing tree is updated.
617 if (currentMode == MediaRenderingMovieLayer || preferredMode == MediaRenderingMovieLayer)
618 m_player->mediaPlayerClient()->mediaPlayerRenderingModeChanged(m_player);
621 void MediaPlayerPrivateQTKit::tearDownVideoRendering()
625 if (m_qtVideoRenderer)
626 destroyQTVideoRenderer();
628 destroyQTMovieLayer();
631 bool MediaPlayerPrivateQTKit::hasSetUpVideoRendering() const
635 || m_qtVideoRenderer;
638 QTTime MediaPlayerPrivateQTKit::createQTTime(float time) const
640 if (!metaDataAvailable())
641 return QTMakeTime(0, 600);
642 long timeScale = [[m_qtMovie.get() attributeForKey:QTMovieTimeScaleAttribute] longValue];
643 return QTMakeTime(lroundf(time * timeScale), timeScale);
646 void MediaPlayerPrivateQTKit::resumeLoad()
648 if (!m_movieURL.isNull())
649 loadInternal(m_movieURL);
652 void MediaPlayerPrivateQTKit::load(const String& url)
656 // If the element is not supposed to load any data return immediately.
657 if (m_preload == MediaPlayer::None)
663 void MediaPlayerPrivateQTKit::loadInternal(const String& url)
665 if (m_networkState != MediaPlayer::Loading) {
666 m_networkState = MediaPlayer::Loading;
667 m_player->networkStateChanged();
669 if (m_readyState != MediaPlayer::HaveNothing) {
670 m_readyState = MediaPlayer::HaveNothing;
671 m_player->readyStateChanged();
674 m_videoFrameHasDrawn = false;
676 [m_objcObserver.get() setDelayCallbacks:YES];
678 #if ENABLE(OFFLINE_WEB_APPLICATIONS)
679 Frame* frame = m_player->frameView() ? m_player->frameView()->frame() : NULL;
680 ApplicationCacheHost* cacheHost = frame ? frame->loader()->documentLoader()->applicationCacheHost() : NULL;
681 ApplicationCacheResource* resource = NULL;
682 if (cacheHost && cacheHost->shouldLoadResourceFromApplicationCache(ResourceRequest(url), resource) && resource)
683 createQTMovie(resource);
688 [m_objcObserver.get() loadStateChanged:nil];
689 [m_objcObserver.get() setDelayCallbacks:NO];
692 void MediaPlayerPrivateQTKit::prepareToPlay()
694 setPreload(MediaPlayer::Auto);
697 PlatformMedia MediaPlayerPrivateQTKit::platformMedia() const
700 pm.type = PlatformMedia::QTMovieType;
701 pm.media.qtMovie = m_qtMovie.get();
705 #if USE(ACCELERATED_COMPOSITING) && !(PLATFORM(QT) && USE(QTKIT))
706 PlatformLayer* MediaPlayerPrivateQTKit::platformLayer() const
708 return m_qtVideoLayer.get();
712 void MediaPlayerPrivateQTKit::play()
714 if (!metaDataAvailable())
716 m_startedPlaying = true;
718 m_frameCountWhilePlaying = 0;
720 [m_objcObserver.get() setDelayCallbacks:YES];
721 [m_qtMovie.get() setRate:m_player->rate()];
722 [m_objcObserver.get() setDelayCallbacks:NO];
725 void MediaPlayerPrivateQTKit::pause()
727 if (!metaDataAvailable())
729 m_startedPlaying = false;
731 m_timeStoppedPlaying = [NSDate timeIntervalSinceReferenceDate];
733 [m_objcObserver.get() setDelayCallbacks:YES];
734 [m_qtMovie.get() stop];
735 [m_objcObserver.get() setDelayCallbacks:NO];
738 float MediaPlayerPrivateQTKit::duration() const
740 if (!metaDataAvailable())
743 if (m_cachedDuration != -1.0f)
744 return m_cachedDuration;
746 QTTime time = [m_qtMovie.get() duration];
747 if (time.flags == kQTTimeIsIndefinite)
748 return numeric_limits<float>::infinity();
749 return static_cast<float>(time.timeValue) / time.timeScale;
752 float MediaPlayerPrivateQTKit::currentTime() const
754 if (!metaDataAvailable())
756 QTTime time = [m_qtMovie.get() currentTime];
757 return static_cast<float>(time.timeValue) / time.timeScale;
760 void MediaPlayerPrivateQTKit::seek(float time)
762 // Nothing to do if we are already in the middle of a seek to the same time.
763 if (time == m_seekTo)
768 if (!metaDataAvailable())
771 if (time > duration())
775 if (maxTimeSeekable() >= m_seekTo)
778 m_seekTimer.start(0, 0.5f);
781 void MediaPlayerPrivateQTKit::doSeek()
783 QTTime qttime = createQTTime(m_seekTo);
784 // setCurrentTime generates several event callbacks, update afterwards
785 [m_objcObserver.get() setDelayCallbacks:YES];
786 float oldRate = [m_qtMovie.get() rate];
789 [m_qtMovie.get() setRate:0];
790 [m_qtMovie.get() setCurrentTime:qttime];
792 // restore playback only if not at end, otherwise QTMovie will loop
793 float timeAfterSeek = currentTime();
794 if (oldRate && timeAfterSeek < duration())
795 [m_qtMovie.get() setRate:oldRate];
798 [m_objcObserver.get() setDelayCallbacks:NO];
801 void MediaPlayerPrivateQTKit::cancelSeek()
807 void MediaPlayerPrivateQTKit::seekTimerFired(Timer<MediaPlayerPrivateQTKit>*)
809 if (!metaDataAvailable()|| !seeking() || currentTime() == m_seekTo) {
812 m_player->timeChanged();
816 if (maxTimeSeekable() >= m_seekTo)
819 MediaPlayer::NetworkState state = networkState();
820 if (state == MediaPlayer::Empty || state == MediaPlayer::Loaded) {
823 m_player->timeChanged();
828 bool MediaPlayerPrivateQTKit::paused() const
830 if (!metaDataAvailable())
832 return [m_qtMovie.get() rate] == 0;
835 bool MediaPlayerPrivateQTKit::seeking() const
837 if (!metaDataAvailable())
839 return m_seekTo >= 0;
842 IntSize MediaPlayerPrivateQTKit::naturalSize() const
844 if (!metaDataAvailable())
847 // In spite of the name of this method, return QTMovieNaturalSizeAttribute transformed by the
848 // initial movie scale because the spec says intrinsic size is:
850 // ... the dimensions of the resource in CSS pixels after taking into account the resource's
851 // dimensions, aspect ratio, clean aperture, resolution, and so forth, as defined for the
852 // format used by the resource
854 FloatSize naturalSize([[m_qtMovie.get() attributeForKey:QTMovieNaturalSizeAttribute] sizeValue]);
855 if (naturalSize.isEmpty() && m_isStreaming) {
856 // HTTP Live Streams will occasionally return {0,0} natural sizes while scrubbing.
857 // Work around this problem (<rdar://problem/9078563>) by returning the last valid
858 // cached natural size:
859 naturalSize = m_cachedNaturalSize;
861 // Unfortunately, due to another QTKit bug (<rdar://problem/9082071>) we won't get a sizeChanged
862 // event when this happens, so we must cache the last valid naturalSize here:
863 m_cachedNaturalSize = naturalSize;
866 return IntSize(naturalSize.width() * m_scaleFactor.width(), naturalSize.height() * m_scaleFactor.height());
869 bool MediaPlayerPrivateQTKit::hasVideo() const
871 if (!metaDataAvailable())
873 return [[m_qtMovie.get() attributeForKey:QTMovieHasVideoAttribute] boolValue];
876 bool MediaPlayerPrivateQTKit::hasAudio() const
880 return [[m_qtMovie.get() attributeForKey:QTMovieHasAudioAttribute] boolValue];
883 bool MediaPlayerPrivateQTKit::supportsFullscreen() const
885 #ifndef BUILDING_ON_LEOPARD
888 // See <rdar://problem/7389945>
893 void MediaPlayerPrivateQTKit::setVolume(float volume)
896 [m_qtMovie.get() setVolume:volume];
899 bool MediaPlayerPrivateQTKit::hasClosedCaptions() const
901 if (!metaDataAvailable())
903 return wkQTMovieHasClosedCaptions(m_qtMovie.get());
906 void MediaPlayerPrivateQTKit::setClosedCaptionsVisible(bool closedCaptionsVisible)
908 if (metaDataAvailable()) {
909 wkQTMovieSetShowClosedCaptions(m_qtMovie.get(), closedCaptionsVisible);
911 #if USE(ACCELERATED_COMPOSITING) && !defined(BUILDING_ON_LEOPARD)
912 if (closedCaptionsVisible && m_qtVideoLayer) {
913 // Captions will be rendered upside down unless we flag the movie as flipped (again). See <rdar://7408440>.
914 [m_qtVideoLayer.get() setGeometryFlipped:YES];
920 void MediaPlayerPrivateQTKit::setRate(float rate)
923 [m_qtMovie.get() setRate:rate];
926 void MediaPlayerPrivateQTKit::setPreservesPitch(bool preservesPitch)
931 // QTMovieRateChangesPreservePitchAttribute cannot be changed dynamically after QTMovie creation.
932 // If the passed in value is different than what already exists, we need to recreate the QTMovie for it to take effect.
933 if ([[m_qtMovie.get() attributeForKey:QTMovieRateChangesPreservePitchAttribute] boolValue] == preservesPitch)
936 RetainPtr<NSDictionary> movieAttributes(AdoptNS, [[m_qtMovie.get() movieAttributes] mutableCopy]);
937 ASSERT(movieAttributes);
938 [movieAttributes.get() setValue:[NSNumber numberWithBool:preservesPitch] forKey:QTMovieRateChangesPreservePitchAttribute];
939 m_timeToRestore = currentTime();
941 createQTMovie([movieAttributes.get() valueForKey:QTMovieURLAttribute], movieAttributes.get());
944 PassRefPtr<TimeRanges> MediaPlayerPrivateQTKit::buffered() const
946 RefPtr<TimeRanges> timeRanges = TimeRanges::create();
947 float loaded = maxTimeLoaded();
949 timeRanges->add(0, loaded);
950 return timeRanges.release();
953 float MediaPlayerPrivateQTKit::maxTimeSeekable() const
955 if (!metaDataAvailable())
958 // infinite duration means live stream
959 if (isinf(duration()))
962 return wkQTMovieMaxTimeSeekable(m_qtMovie.get());
965 float MediaPlayerPrivateQTKit::maxTimeLoaded() const
967 if (!metaDataAvailable())
969 return wkQTMovieMaxTimeLoaded(m_qtMovie.get());
972 unsigned MediaPlayerPrivateQTKit::bytesLoaded() const
974 float dur = duration();
977 return totalBytes() * maxTimeLoaded() / dur;
980 unsigned MediaPlayerPrivateQTKit::totalBytes() const
982 if (!metaDataAvailable())
984 return [[m_qtMovie.get() attributeForKey:QTMovieDataSizeAttribute] intValue];
987 void MediaPlayerPrivateQTKit::cancelLoad()
989 // FIXME: Is there a better way to check for this?
990 if (m_networkState < MediaPlayer::Loading || m_networkState == MediaPlayer::Loaded)
993 tearDownVideoRendering();
999 void MediaPlayerPrivateQTKit::cacheMovieScale()
1001 NSSize initialSize = NSZeroSize;
1002 NSSize naturalSize = [[m_qtMovie.get() attributeForKey:QTMovieNaturalSizeAttribute] sizeValue];
1004 #ifndef BUILDING_ON_LEOPARD
1005 // QTMovieCurrentSizeAttribute is not allowed with instances of QTMovie that have been
1006 // opened with QTMovieOpenForPlaybackAttribute, so ask for the display transform attribute instead.
1007 NSAffineTransform *displayTransform = [m_qtMovie.get() attributeForKey:@"QTMoviePreferredTransformAttribute"];
1008 if (displayTransform)
1009 initialSize = [displayTransform transformSize:naturalSize];
1011 initialSize.width = naturalSize.width;
1012 initialSize.height = naturalSize.height;
1015 initialSize = [[m_qtMovie.get() attributeForKey:QTMovieCurrentSizeAttribute] sizeValue];
1018 if (naturalSize.width)
1019 m_scaleFactor.setWidth(initialSize.width / naturalSize.width);
1020 if (naturalSize.height)
1021 m_scaleFactor.setHeight(initialSize.height / naturalSize.height);
1024 bool MediaPlayerPrivateQTKit::isReadyForVideoSetup() const
1026 return m_readyState >= MediaPlayer::HaveMetadata && m_player->visible();
1029 void MediaPlayerPrivateQTKit::prepareForRendering()
1031 if (m_isAllowedToRender)
1033 m_isAllowedToRender = true;
1035 if (!hasSetUpVideoRendering())
1036 setUpVideoRendering();
1038 // If using a movie layer, inform the client so the compositing tree is updated. This is crucial if the movie
1039 // has a poster, as it will most likely not have a layer and we will now be rendering frames to the movie layer.
1040 if (currentRenderingMode() == MediaRenderingMovieLayer || preferredRenderingMode() == MediaRenderingMovieLayer)
1041 m_player->mediaPlayerClient()->mediaPlayerRenderingModeChanged(m_player);
1044 void MediaPlayerPrivateQTKit::updateStates()
1046 MediaPlayer::NetworkState oldNetworkState = m_networkState;
1047 MediaPlayer::ReadyState oldReadyState = m_readyState;
1049 long loadState = m_qtMovie ? [[m_qtMovie.get() attributeForKey:QTMovieLoadStateAttribute] longValue] : static_cast<long>(QTMovieLoadStateError);
1051 if (loadState >= QTMovieLoadStateLoaded && m_readyState < MediaPlayer::HaveMetadata) {
1052 disableUnsupportedTracks();
1053 if (m_player->inMediaDocument()) {
1054 if (!m_enabledTrackCount || m_hasUnsupportedTracks) {
1055 // This has a type of media that we do not handle directly with a <video>
1056 // element, eg. a rtsp track or QuickTime VR. Tell the MediaPlayerClient
1058 sawUnsupportedTracks();
1061 } else if (!m_enabledTrackCount)
1062 loadState = QTMovieLoadStateError;
1064 if (loadState != QTMovieLoadStateError) {
1065 wkQTMovieSelectPreferredAlternates(m_qtMovie.get());
1067 MediaPlayer::MovieLoadType movieType = movieLoadType();
1068 m_isStreaming = movieType == MediaPlayer::StoredStream || movieType == MediaPlayer::LiveStream;
1072 // If this movie is reloading and we mean to restore the current time/rate, this might be the right time to do it.
1073 if (loadState >= QTMovieLoadStateLoaded && oldNetworkState < MediaPlayer::Loaded && m_timeToRestore != -1.0f) {
1074 QTTime qttime = createQTTime(m_timeToRestore);
1075 m_timeToRestore = -1.0f;
1077 // Disable event callbacks from setCurrentTime for restoring time in a recreated video
1078 [m_objcObserver.get() setDelayCallbacks:YES];
1079 [m_qtMovie.get() setCurrentTime:qttime];
1080 [m_qtMovie.get() setRate:m_player->rate()];
1081 [m_objcObserver.get() setDelayCallbacks:NO];
1084 BOOL completelyLoaded = !m_isStreaming && (loadState >= QTMovieLoadStateComplete);
1086 // Note: QT indicates that we are fully loaded with QTMovieLoadStateComplete.
1087 // However newer versions of QT do not, so we check maxTimeLoaded against duration.
1088 if (!completelyLoaded && !m_isStreaming && metaDataAvailable())
1089 completelyLoaded = maxTimeLoaded() == duration();
1091 if (completelyLoaded) {
1092 // "Loaded" is reserved for fully buffered movies, never the case when streaming
1093 m_networkState = MediaPlayer::Loaded;
1094 m_readyState = MediaPlayer::HaveEnoughData;
1095 } else if (loadState >= QTMovieLoadStatePlaythroughOK) {
1096 m_readyState = MediaPlayer::HaveEnoughData;
1097 m_networkState = MediaPlayer::Loading;
1098 } else if (loadState >= QTMovieLoadStatePlayable) {
1099 // FIXME: This might not work correctly in streaming case, <rdar://problem/5693967>
1100 m_readyState = currentTime() < maxTimeLoaded() ? MediaPlayer::HaveFutureData : MediaPlayer::HaveCurrentData;
1101 m_networkState = MediaPlayer::Loading;
1102 } else if (loadState >= QTMovieLoadStateLoaded) {
1103 m_readyState = MediaPlayer::HaveMetadata;
1104 m_networkState = MediaPlayer::Loading;
1105 } else if (loadState > QTMovieLoadStateError) {
1106 m_readyState = MediaPlayer::HaveNothing;
1107 m_networkState = MediaPlayer::Loading;
1109 // Loading or decoding failed.
1111 if (m_player->inMediaDocument()) {
1112 // Something went wrong in the loading of media within a standalone file.
1113 // This can occur with chained refmovies pointing to streamed media.
1114 sawUnsupportedTracks();
1118 float loaded = maxTimeLoaded();
1120 m_readyState = MediaPlayer::HaveNothing;
1122 if (!m_enabledTrackCount)
1123 m_networkState = MediaPlayer::FormatError;
1125 // FIXME: We should differentiate between load/network errors and decode errors <rdar://problem/5605692>
1127 m_networkState = MediaPlayer::DecodeError;
1129 m_readyState = MediaPlayer::HaveNothing;
1133 if (isReadyForVideoSetup() && !hasSetUpVideoRendering())
1134 setUpVideoRendering();
1137 m_readyState = m_readyState >= MediaPlayer::HaveMetadata ? MediaPlayer::HaveMetadata : MediaPlayer::HaveNothing;
1139 // Streaming movies don't use the network when paused.
1140 if (m_isStreaming && m_readyState >= MediaPlayer::HaveMetadata && m_networkState >= MediaPlayer::Loading && [m_qtMovie.get() rate] == 0)
1141 m_networkState = MediaPlayer::Idle;
1143 if (m_networkState != oldNetworkState)
1144 m_player->networkStateChanged();
1146 if (m_readyState != oldReadyState)
1147 m_player->readyStateChanged();
1149 if (loadState >= QTMovieLoadStateLoaded) {
1150 float dur = duration();
1151 if (dur != m_reportedDuration) {
1152 if (m_reportedDuration != -1.0f)
1153 m_player->durationChanged();
1154 m_reportedDuration = dur;
1159 void MediaPlayerPrivateQTKit::loadStateChanged()
1161 if (!m_hasUnsupportedTracks)
1165 void MediaPlayerPrivateQTKit::rateChanged()
1167 if (m_hasUnsupportedTracks)
1171 m_player->rateChanged();
1174 void MediaPlayerPrivateQTKit::sizeChanged()
1176 if (!m_hasUnsupportedTracks)
1177 m_player->sizeChanged();
1180 void MediaPlayerPrivateQTKit::timeChanged()
1182 if (m_hasUnsupportedTracks)
1185 // It may not be possible to seek to a specific time in a streamed movie. When seeking in a
1186 // stream QuickTime sets the movie time to closest time possible and posts a timechanged
1187 // notification. Update m_seekTo so we can detect when the seek completes.
1189 m_seekTo = currentTime();
1191 m_timeToRestore = -1.0f;
1193 m_player->timeChanged();
1196 void MediaPlayerPrivateQTKit::didEnd()
1198 if (m_hasUnsupportedTracks)
1201 m_startedPlaying = false;
1203 m_timeStoppedPlaying = [NSDate timeIntervalSinceReferenceDate];
1206 // Hang onto the current time and use it as duration from now on since QuickTime is telling us we
1207 // are at the end. Do this because QuickTime sometimes reports one time for duration and stops
1208 // playback at another time, which causes problems in HTMLMediaElement. QTKit's 'ended' event
1209 // fires when playing in reverse so don't update duration when at time zero!
1210 float now = currentTime();
1212 m_cachedDuration = now;
1215 m_player->timeChanged();
1218 #if USE(ACCELERATED_COMPOSITING) && !(PLATFORM(QT) && USE(QTKIT))
1219 #if defined(BUILDING_ON_SNOW_LEOPARD)
1220 static bool layerIsDescendentOf(PlatformLayer* child, PlatformLayer* descendent)
1222 if (!child || !descendent)
1226 if (child == descendent)
1228 } while((child = [child superlayer]));
1234 void MediaPlayerPrivateQTKit::layerHostChanged(PlatformLayer* rootLayer)
1236 #if defined(BUILDING_ON_SNOW_LEOPARD)
1240 if (layerIsDescendentOf(m_qtVideoLayer.get(), rootLayer)) {
1241 // We own a child layer of a layer which has switched contexts.
1242 // Tear down our layer, and set m_visible to false, so that the
1243 // next time setVisible(true) is called, the layer will be re-
1244 // created in the correct context.
1245 tearDownVideoRendering();
1249 UNUSED_PARAM(rootLayer);
1254 void MediaPlayerPrivateQTKit::setSize(const IntSize&)
1256 // Don't resize the view now because [view setFrame] also resizes the movie itself, and because
1257 // the renderer calls this function immediately when we report a size change (QTMovieSizeDidChangeNotification)
1258 // we can get into a feedback loop observing the size change and resetting the size, and this can cause
1259 // QuickTime to miss resetting a movie's size when the media size changes (as happens with an rtsp movie
1260 // once the rtsp server sends the track sizes). Instead we remember the size passed to paint() and resize
1261 // the view when it changes.
1262 // <rdar://problem/6336092> REGRESSION: rtsp movie does not resize correctly
1265 void MediaPlayerPrivateQTKit::setVisible(bool b)
1267 if (m_visible != b) {
1270 setUpVideoRendering();
1272 tearDownVideoRendering();
1276 bool MediaPlayerPrivateQTKit::hasAvailableVideoFrame() const
1278 // When using a QTMovieLayer return true as soon as the movie reaches QTMovieLoadStatePlayable
1279 // because although we don't *know* when the first frame has decoded, by the time we get and
1280 // process the notification a frame should have propagated the VisualContext and been set on
1282 if (currentRenderingMode() == MediaRenderingMovieLayer)
1283 return m_readyState >= MediaPlayer::HaveCurrentData;
1285 // When using the software renderer QuickTime signals that a frame is available so we might as well
1286 // wait until we know that a frame has been drawn.
1287 return m_videoFrameHasDrawn;
1290 void MediaPlayerPrivateQTKit::repaint()
1292 if (m_hasUnsupportedTracks)
1296 if (m_startedPlaying) {
1297 m_frameCountWhilePlaying++;
1298 // to eliminate preroll costs from our calculation,
1299 // our frame rate calculation excludes the first frame drawn after playback starts
1300 if (1==m_frameCountWhilePlaying)
1301 m_timeStartedPlaying = [NSDate timeIntervalSinceReferenceDate];
1304 m_videoFrameHasDrawn = true;
1305 m_player->repaint();
1308 void MediaPlayerPrivateQTKit::paintCurrentFrameInContext(GraphicsContext* context, const IntRect& r)
1310 id qtVideoRenderer = m_qtVideoRenderer.get();
1311 if (!qtVideoRenderer && currentRenderingMode() == MediaRenderingMovieLayer) {
1312 // We're being told to render into a context, but we already have the
1313 // MovieLayer going. This probably means we've been called from <canvas>.
1314 // Set up a QTVideoRenderer to use, but one that doesn't register for
1315 // update callbacks. That way, it won't bother us asking to repaint.
1316 createQTVideoRenderer(QTVideoRendererModeDefault);
1317 qtVideoRenderer = m_qtVideoRenderer.get();
1322 void MediaPlayerPrivateQTKit::paint(GraphicsContext* context, const IntRect& r)
1324 if (context->paintingDisabled() || m_hasUnsupportedTracks)
1326 NSView *view = m_qtMovieView.get();
1327 id qtVideoRenderer = m_qtVideoRenderer.get();
1328 if (!view && !qtVideoRenderer)
1331 [m_objcObserver.get() setDelayCallbacks:YES];
1332 BEGIN_BLOCK_OBJC_EXCEPTIONS;
1333 NSGraphicsContext* newContext;
1334 FloatSize scaleFactor(1.0f, -1.0f);
1335 IntRect paintRect(IntPoint(0, 0), IntSize(r.width(), r.height()));
1337 #if PLATFORM(QT) && USE(QTKIT)
1338 // In Qt, GraphicsContext is a QPainter so every transformations applied on it won't matter because here
1339 // the video is rendered by QuickTime not by Qt.
1340 CGContextRef cgContext = static_cast<CGContextRef>([[NSGraphicsContext currentContext] graphicsPort]);
1341 CGContextSaveGState(cgContext);
1342 CGContextSetInterpolationQuality(cgContext, kCGInterpolationLow);
1343 CGContextTranslateCTM(cgContext, r.x(), r.y() + r.height());
1344 CGContextScaleCTM(cgContext, scaleFactor.width(), scaleFactor.height());
1346 newContext = [NSGraphicsContext currentContext];
1348 GraphicsContextStateSaver stateSaver(*context);
1349 context->translate(r.x(), r.y() + r.height());
1350 context->scale(scaleFactor);
1351 context->setImageInterpolationQuality(InterpolationLow);
1353 newContext = [NSGraphicsContext graphicsContextWithGraphicsPort:context->platformContext() flipped:NO];
1355 // draw the current video frame
1356 if (qtVideoRenderer) {
1357 [NSGraphicsContext saveGraphicsState];
1358 [NSGraphicsContext setCurrentContext:newContext];
1359 [(id<WebKitVideoRenderingDetails>)qtVideoRenderer drawInRect:paintRect];
1360 [NSGraphicsContext restoreGraphicsState];
1364 if (m_player->inMediaDocument()) {
1365 // the QTMovieView needs to be placed in the proper location for document mode
1366 [view setFrame:m_rect];
1369 // We don't really need the QTMovieView in any specific location so let's just get it out of the way
1370 // where it won't intercept events or try to bring up the context menu.
1371 IntRect farAwayButCorrectSize(m_rect);
1372 farAwayButCorrectSize.move(-1000000, -1000000);
1373 [view setFrame:farAwayButCorrectSize];
1377 if (m_player->inMediaDocument()) {
1378 // If we're using a QTMovieView in a media document, the view may get layer-backed. AppKit won't update
1379 // the layer hosting correctly if we call displayRectIgnoringOpacity:inContext:, so use displayRectIgnoringOpacity:
1380 // in this case. See <rdar://problem/6702882>.
1381 [view displayRectIgnoringOpacity:paintRect];
1383 [view displayRectIgnoringOpacity:paintRect inContext:newContext];
1387 // Draw the frame rate only after having played more than 10 frames.
1388 if (m_frameCountWhilePlaying > 10) {
1389 Frame* frame = m_player->frameView() ? m_player->frameView()->frame() : NULL;
1390 Document* document = frame ? frame->document() : NULL;
1391 RenderObject* renderer = document ? document->renderer() : NULL;
1392 RenderStyle* styleToUse = renderer ? renderer->style() : NULL;
1394 double frameRate = (m_frameCountWhilePlaying - 1) / ( m_startedPlaying ? ([NSDate timeIntervalSinceReferenceDate] - m_timeStartedPlaying) :
1395 (m_timeStoppedPlaying - m_timeStartedPlaying) );
1396 String text = String::format("%1.2f", frameRate);
1397 TextRun textRun(text.characters(), text.length());
1398 const Color color(255, 0, 0);
1399 context->scale(FloatSize(1.0f, -1.0f));
1400 context->setStrokeColor(color, styleToUse->colorSpace());
1401 context->setStrokeStyle(SolidStroke);
1402 context->setStrokeThickness(1.0f);
1403 context->setFillColor(color, styleToUse->colorSpace());
1404 context->drawText(styleToUse->font(), textRun, IntPoint(2, -3));
1408 #if PLATFORM(QT) && USE(QTKIT)
1409 CGContextRestoreGState(cgContext);
1411 END_BLOCK_OBJC_EXCEPTIONS;
1412 [m_objcObserver.get() setDelayCallbacks:NO];
1415 static void addFileTypesToCache(NSArray * fileTypes, HashSet<String> &cache)
1417 int count = [fileTypes count];
1418 for (int n = 0; n < count; n++) {
1419 CFStringRef ext = reinterpret_cast<CFStringRef>([fileTypes objectAtIndex:n]);
1420 RetainPtr<CFStringRef> uti(AdoptCF, UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, ext, NULL));
1423 RetainPtr<CFStringRef> mime(AdoptCF, UTTypeCopyPreferredTagWithClass(uti.get(), kUTTagClassMIMEType));
1425 cache.add(mime.get());
1427 // -movieFileTypes: returns both file extensions and OSTypes. The later are surrounded by single
1428 // quotes, eg. 'MooV', so don't bother looking at those.
1429 if (CFStringGetCharacterAtIndex(ext, 0) != '\'') {
1430 // UTI is missing many media related MIME types supported by QTKit (see rdar://6434168), and not all
1431 // web servers use the MIME type UTI returns for an extension (see rdar://7875393), so even if UTI
1432 // has a type for this extension add any types in hard coded table in the MIME type regsitry.
1433 Vector<String> typesForExtension = MIMETypeRegistry::getMediaMIMETypesForExtension(ext);
1434 unsigned count = typesForExtension.size();
1435 for (unsigned ndx = 0; ndx < count; ++ndx) {
1436 if (!cache.contains(typesForExtension[ndx]))
1437 cache.add(typesForExtension[ndx]);
1443 static HashSet<String> mimeCommonTypesCache()
1445 DEFINE_STATIC_LOCAL(HashSet<String>, cache, ());
1446 static bool typeListInitialized = false;
1448 if (!typeListInitialized) {
1449 typeListInitialized = true;
1450 NSArray* fileTypes = [QTMovie movieFileTypes:QTIncludeCommonTypes];
1451 addFileTypesToCache(fileTypes, cache);
1457 static HashSet<String> mimeModernTypesCache()
1459 DEFINE_STATIC_LOCAL(HashSet<String>, cache, ());
1460 static bool typeListInitialized = false;
1462 if (!typeListInitialized) {
1463 typeListInitialized = true;
1464 NSArray* fileTypes = [QTMovie movieFileTypes:(QTMovieFileTypeOptions)wkQTIncludeOnlyModernMediaFileTypes()];
1465 addFileTypesToCache(fileTypes, cache);
1471 void MediaPlayerPrivateQTKit::getSupportedTypes(HashSet<String>& supportedTypes)
1473 supportedTypes = mimeModernTypesCache();
1475 // Note: this method starts QTKitServer if it isn't already running when in 64-bit because it has to return the list
1476 // of every MIME type supported by QTKit.
1477 HashSet<String> commonTypes = mimeCommonTypesCache();
1478 HashSet<String>::const_iterator it = commonTypes.begin();
1479 HashSet<String>::const_iterator end = commonTypes.end();
1480 for (; it != end; ++it)
1481 supportedTypes.add(*it);
1484 MediaPlayer::SupportsType MediaPlayerPrivateQTKit::supportsType(const String& type, const String& codecs)
1486 // Only return "IsSupported" if there is no codecs parameter for now as there is no way to ask QT if it supports an
1487 // extended MIME type yet.
1489 // We check the "modern" type cache first, as it doesn't require QTKitServer to start.
1490 if (mimeModernTypesCache().contains(type) || mimeCommonTypesCache().contains(type))
1491 return codecs.isEmpty() ? MediaPlayer::MayBeSupported : MediaPlayer::IsSupported;
1493 return MediaPlayer::IsNotSupported;
1496 bool MediaPlayerPrivateQTKit::isAvailable()
1498 // 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.
1499 return QTKitLibrary();
1502 void MediaPlayerPrivateQTKit::getSitesInMediaCache(Vector<String>& sites)
1504 NSArray *mediaSites = wkQTGetSitesInMediaDownloadCache();
1505 for (NSString *site in mediaSites)
1509 void MediaPlayerPrivateQTKit::clearMediaCache()
1511 wkQTClearMediaDownloadCache();
1514 void MediaPlayerPrivateQTKit::clearMediaCacheForSite(const String& site)
1516 wkQTClearMediaDownloadCacheForSite(site);
1519 void MediaPlayerPrivateQTKit::disableUnsupportedTracks()
1522 m_enabledTrackCount = 0;
1523 m_totalTrackCount = 0;
1527 static HashSet<String>* allowedTrackTypes = 0;
1528 if (!allowedTrackTypes) {
1529 allowedTrackTypes = new HashSet<String>;
1530 allowedTrackTypes->add(QTMediaTypeVideo);
1531 allowedTrackTypes->add(QTMediaTypeSound);
1532 allowedTrackTypes->add(QTMediaTypeText);
1533 allowedTrackTypes->add(QTMediaTypeBase);
1534 allowedTrackTypes->add(QTMediaTypeMPEG);
1535 allowedTrackTypes->add("clcp"); // Closed caption
1536 allowedTrackTypes->add("sbtl"); // Subtitle
1537 allowedTrackTypes->add("odsm"); // MPEG-4 object descriptor stream
1538 allowedTrackTypes->add("sdsm"); // MPEG-4 scene description stream
1539 allowedTrackTypes->add("tmcd"); // timecode
1540 allowedTrackTypes->add("tc64"); // timcode-64
1541 allowedTrackTypes->add("tmet"); // timed metadata
1544 NSArray *tracks = [m_qtMovie.get() tracks];
1546 m_totalTrackCount = [tracks count];
1547 m_enabledTrackCount = m_totalTrackCount;
1548 for (unsigned trackIndex = 0; trackIndex < m_totalTrackCount; trackIndex++) {
1549 // Grab the track at the current index. If there isn't one there, then
1550 // we can move onto the next one.
1551 QTTrack *track = [tracks objectAtIndex:trackIndex];
1555 // Check to see if the track is disabled already, we should move along.
1556 // We don't need to re-disable it.
1557 if (![track isEnabled]) {
1558 --m_enabledTrackCount;
1562 // Get the track's media type.
1563 NSString *mediaType = [track attributeForKey:QTTrackMediaTypeAttribute];
1567 // Test whether the media type is in our white list.
1568 if (!allowedTrackTypes->contains(mediaType)) {
1569 // If this track type is not allowed, then we need to disable it.
1570 [track setEnabled:NO];
1571 --m_enabledTrackCount;
1572 m_hasUnsupportedTracks = true;
1575 // Disable chapter tracks. These are most likely to lead to trouble, as
1576 // they will be composited under the video tracks, forcing QT to do extra
1578 QTTrack *chapterTrack = [track performSelector:@selector(chapterlist)];
1582 // Try to grab the media for the track.
1583 QTMedia *chapterMedia = [chapterTrack media];
1587 // Grab the media type for this track.
1588 id chapterMediaType = [chapterMedia attributeForKey:QTMediaTypeAttribute];
1589 if (!chapterMediaType)
1592 // Check to see if the track is a video track. We don't care about
1593 // other non-video tracks.
1594 if (![chapterMediaType isEqual:QTMediaTypeVideo])
1597 // Check to see if the track is already disabled. If it is, we
1598 // should move along.
1599 if (![chapterTrack isEnabled])
1602 // Disable the evil, evil track.
1603 [chapterTrack setEnabled:NO];
1604 --m_enabledTrackCount;
1605 m_hasUnsupportedTracks = true;
1609 void MediaPlayerPrivateQTKit::sawUnsupportedTracks()
1611 m_hasUnsupportedTracks = true;
1612 m_player->mediaPlayerClient()->mediaPlayerSawUnsupportedTracks(m_player);
1615 #if USE(ACCELERATED_COMPOSITING) && !(PLATFORM(QT) && USE(QTKIT))
1616 bool MediaPlayerPrivateQTKit::supportsAcceleratedRendering() const
1618 return isReadyForVideoSetup() && getQTMovieLayerClass() != Nil;
1621 void MediaPlayerPrivateQTKit::acceleratedRenderingStateChanged()
1623 // Set up or change the rendering path if necessary.
1624 setUpVideoRendering();
1628 bool MediaPlayerPrivateQTKit::hasSingleSecurityOrigin() const
1630 // We tell quicktime to disallow resources that come from different origins
1631 // so we know all media is single origin.
1635 MediaPlayer::MovieLoadType MediaPlayerPrivateQTKit::movieLoadType() const
1638 return MediaPlayer::Unknown;
1640 MediaPlayer::MovieLoadType movieType = (MediaPlayer::MovieLoadType)wkQTMovieGetType(m_qtMovie.get());
1642 // Can't include WebKitSystemInterface from WebCore so we can't get the enum returned
1643 // by wkQTMovieGetType, but at least verify that the value is in the valid range.
1644 ASSERT(movieType >= MediaPlayer::Unknown && movieType <= MediaPlayer::LiveStream);
1649 void MediaPlayerPrivateQTKit::setPreload(MediaPlayer::Preload preload)
1651 m_preload = preload;
1652 if (m_preload == MediaPlayer::None)
1657 else if (m_preload == MediaPlayer::Auto)
1658 [m_qtMovie.get() setAttribute:[NSNumber numberWithBool:NO] forKey:@"QTMovieLimitReadAheadAttribute"];
1661 float MediaPlayerPrivateQTKit::mediaTimeForTimeValue(float timeValue) const
1663 if (!metaDataAvailable())
1666 QTTime qttime = createQTTime(timeValue);
1667 return static_cast<float>(qttime.timeValue) / qttime.timeScale;
1670 void MediaPlayerPrivateQTKit::setPrivateBrowsingMode(bool privateBrowsing)
1672 m_privateBrowsing = privateBrowsing;
1675 [m_qtMovie.get() setAttribute:[NSNumber numberWithBool:!privateBrowsing] forKey:@"QTMovieAllowPersistentCacheAttribute"];
1678 } // namespace WebCore
1680 @implementation WebCoreMovieObserver
1682 - (id)initWithCallback:(MediaPlayerPrivateQTKit*)callback
1684 m_callback = callback;
1685 return [super init];
1690 [NSObject cancelPreviousPerformRequestsWithTarget:self];
1694 -(NSMenu*)menuForEventDelegate:(NSEvent*)theEvent
1696 // Get the contextual menu from the QTMovieView's superview, the frame view
1697 return [[m_view superview] menuForEvent:theEvent];
1700 -(void)setView:(NSView*)view
1707 if (m_delayCallbacks)
1708 [self performSelector:_cmd withObject:nil afterDelay:0.];
1709 else if (m_callback)
1710 m_callback->repaint();
1713 - (void)loadStateChanged:(NSNotification *)unusedNotification
1715 UNUSED_PARAM(unusedNotification);
1716 if (m_delayCallbacks)
1717 [self performSelector:_cmd withObject:nil afterDelay:0];
1719 m_callback->loadStateChanged();
1722 - (void)rateChanged:(NSNotification *)unusedNotification
1724 UNUSED_PARAM(unusedNotification);
1725 if (m_delayCallbacks)
1726 [self performSelector:_cmd withObject:nil afterDelay:0];
1728 m_callback->rateChanged();
1731 - (void)sizeChanged:(NSNotification *)unusedNotification
1733 UNUSED_PARAM(unusedNotification);
1734 if (m_delayCallbacks)
1735 [self performSelector:_cmd withObject:nil afterDelay:0];
1737 m_callback->sizeChanged();
1740 - (void)timeChanged:(NSNotification *)unusedNotification
1742 UNUSED_PARAM(unusedNotification);
1743 if (m_delayCallbacks)
1744 [self performSelector:_cmd withObject:nil afterDelay:0];
1746 m_callback->timeChanged();
1749 - (void)didEnd:(NSNotification *)unusedNotification
1751 UNUSED_PARAM(unusedNotification);
1752 if (m_delayCallbacks)
1753 [self performSelector:_cmd withObject:nil afterDelay:0];
1755 m_callback->didEnd();
1758 - (void)newImageAvailable:(NSNotification *)unusedNotification
1760 UNUSED_PARAM(unusedNotification);
1764 - (void)layerHostChanged:(NSNotification *)notification
1766 #if USE(ACCELERATED_COMPOSITING) && !(PLATFORM(QT) && USE(QTKIT))
1767 CALayer* rootLayer = static_cast<CALayer*>([notification object]);
1768 m_callback->layerHostChanged(rootLayer);
1770 UNUSED_PARAM(notification);
1774 - (void)setDelayCallbacks:(BOOL)shouldDelay
1776 m_delayCallbacks = shouldDelay;