6fc7a7618bfcf8b85c5aa74d58c640f3be24c6a0
[WebKit-https.git] / WebCore / platform / graphics / mac / MediaPlayerPrivateQTKit.mm
1 /*
2  * Copyright (C) 2007, 2008, 2009 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 #ifdef BUILDING_ON_TIGER
33 #import "AutodrainedPool.h"
34 #endif
35
36 #import "BlockExceptions.h"
37 #import "FrameView.h"
38 #import "GraphicsContext.h"
39 #import "KURL.h"
40 #import "SoftLinking.h"
41 #import "WebCoreSystemInterface.h"
42 #import <QTKit/QTKit.h>
43 #import <objc/objc-runtime.h>
44 #import <wtf/UnusedParam.h>
45
46 #if DRAW_FRAME_RATE
47 #import "Font.h"
48 #import "Frame.h"
49 #import "Document.h"
50 #import "RenderObject.h"
51 #import "RenderStyle.h"
52 #endif
53
54 #ifdef BUILDING_ON_TIGER
55 static IMP method_setImplementation(Method m, IMP imp)
56 {
57     IMP result = m->method_imp;
58     m->method_imp = imp;
59     return result;
60 }
61 #endif
62
63 SOFT_LINK_FRAMEWORK(QTKit)
64
65 SOFT_LINK(QTKit, QTMakeTime, QTTime, (long long timeValue, long timeScale), (timeValue, timeScale))
66
67 SOFT_LINK_CLASS(QTKit, QTMovie)
68 SOFT_LINK_CLASS(QTKit, QTMovieView)
69
70 SOFT_LINK_POINTER(QTKit, QTTrackMediaTypeAttribute, NSString *)
71 SOFT_LINK_POINTER(QTKit, QTMediaTypeAttribute, NSString *)
72 SOFT_LINK_POINTER(QTKit, QTMediaTypeBase, NSString *)
73 SOFT_LINK_POINTER(QTKit, QTMediaTypeMPEG, NSString *)
74 SOFT_LINK_POINTER(QTKit, QTMediaTypeSound, NSString *)
75 SOFT_LINK_POINTER(QTKit, QTMediaTypeText, NSString *)
76 SOFT_LINK_POINTER(QTKit, QTMediaTypeVideo, NSString *)
77 SOFT_LINK_POINTER(QTKit, QTMovieAskUnresolvedDataRefsAttribute, NSString *)
78 SOFT_LINK_POINTER(QTKit, QTMovieDataSizeAttribute, NSString *)
79 SOFT_LINK_POINTER(QTKit, QTMovieDidEndNotification, NSString *)
80 SOFT_LINK_POINTER(QTKit, QTMovieHasVideoAttribute, NSString *)
81 SOFT_LINK_POINTER(QTKit, QTMovieIsActiveAttribute, NSString *)
82 SOFT_LINK_POINTER(QTKit, QTMovieLoadStateAttribute, NSString *)
83 SOFT_LINK_POINTER(QTKit, QTMovieLoadStateDidChangeNotification, NSString *)
84 SOFT_LINK_POINTER(QTKit, QTMovieNaturalSizeAttribute, NSString *)
85 SOFT_LINK_POINTER(QTKit, QTMovieCurrentSizeAttribute, NSString *)
86 SOFT_LINK_POINTER(QTKit, QTMoviePreventExternalURLLinksAttribute, NSString *)
87 SOFT_LINK_POINTER(QTKit, QTMovieRateDidChangeNotification, NSString *)
88 SOFT_LINK_POINTER(QTKit, QTMovieSizeDidChangeNotification, NSString *)
89 SOFT_LINK_POINTER(QTKit, QTMovieTimeDidChangeNotification, NSString *)
90 SOFT_LINK_POINTER(QTKit, QTMovieTimeScaleAttribute, NSString *)
91 SOFT_LINK_POINTER(QTKit, QTMovieURLAttribute, NSString *)
92 SOFT_LINK_POINTER(QTKit, QTMovieVolumeDidChangeNotification, NSString *)
93 SOFT_LINK_POINTER(QTKit, QTSecurityPolicyNoCrossSiteAttribute, NSString *)
94 SOFT_LINK_POINTER(QTKit, QTVideoRendererWebKitOnlyNewImageAvailableNotification, NSString *)
95
96 #define QTMovie getQTMovieClass()
97 #define QTMovieView getQTMovieViewClass()
98
99 #define QTTrackMediaTypeAttribute getQTTrackMediaTypeAttribute()
100 #define QTMediaTypeAttribute getQTMediaTypeAttribute()
101 #define QTMediaTypeBase getQTMediaTypeBase()
102 #define QTMediaTypeMPEG getQTMediaTypeMPEG()
103 #define QTMediaTypeSound getQTMediaTypeSound()
104 #define QTMediaTypeText getQTMediaTypeText()
105 #define QTMediaTypeVideo getQTMediaTypeVideo()
106 #define QTMovieAskUnresolvedDataRefsAttribute getQTMovieAskUnresolvedDataRefsAttribute()
107 #define QTMovieDataSizeAttribute getQTMovieDataSizeAttribute()
108 #define QTMovieDidEndNotification getQTMovieDidEndNotification()
109 #define QTMovieHasVideoAttribute getQTMovieHasVideoAttribute()
110 #define QTMovieIsActiveAttribute getQTMovieIsActiveAttribute()
111 #define QTMovieLoadStateAttribute getQTMovieLoadStateAttribute()
112 #define QTMovieLoadStateDidChangeNotification getQTMovieLoadStateDidChangeNotification()
113 #define QTMovieNaturalSizeAttribute getQTMovieNaturalSizeAttribute()
114 #define QTMovieCurrentSizeAttribute getQTMovieCurrentSizeAttribute()
115 #define QTMoviePreventExternalURLLinksAttribute getQTMoviePreventExternalURLLinksAttribute()
116 #define QTMovieRateDidChangeNotification getQTMovieRateDidChangeNotification()
117 #define QTMovieSizeDidChangeNotification getQTMovieSizeDidChangeNotification()
118 #define QTMovieTimeDidChangeNotification getQTMovieTimeDidChangeNotification()
119 #define QTMovieTimeScaleAttribute getQTMovieTimeScaleAttribute()
120 #define QTMovieURLAttribute getQTMovieURLAttribute()
121 #define QTMovieVolumeDidChangeNotification getQTMovieVolumeDidChangeNotification()
122 #define QTSecurityPolicyNoCrossSiteAttribute getQTSecurityPolicyNoCrossSiteAttribute()
123 #define QTVideoRendererWebKitOnlyNewImageAvailableNotification getQTVideoRendererWebKitOnlyNewImageAvailableNotification()
124
125 // Older versions of the QTKit header don't have these constants.
126 #if !defined QTKIT_VERSION_MAX_ALLOWED || QTKIT_VERSION_MAX_ALLOWED <= QTKIT_VERSION_7_0
127 enum {
128     QTMovieLoadStateError = -1L,
129     QTMovieLoadStateLoaded  = 2000L,
130     QTMovieLoadStatePlayable = 10000L,
131     QTMovieLoadStatePlaythroughOK = 20000L,
132     QTMovieLoadStateComplete = 100000L
133 };
134 #endif
135
136 using namespace WebCore;
137 using namespace std;
138
139 @interface WebCoreMovieObserver : NSObject
140 {
141     MediaPlayerPrivate* m_callback;
142     NSView* m_view;
143     BOOL m_delayCallbacks;
144 }
145 -(id)initWithCallback:(MediaPlayerPrivate*)callback;
146 -(void)disconnect;
147 -(void)setView:(NSView*)view;
148 -(void)repaint;
149 -(void)setDelayCallbacks:(BOOL)shouldDelay;
150 -(void)loadStateChanged:(NSNotification *)notification;
151 -(void)rateChanged:(NSNotification *)notification;
152 -(void)sizeChanged:(NSNotification *)notification;
153 -(void)timeChanged:(NSNotification *)notification;
154 -(void)didEnd:(NSNotification *)notification;
155 @end
156
157 @protocol WebKitVideoRenderingDetails
158 -(void)setMovie:(id)movie;
159 -(void)drawInRect:(NSRect)rect;
160 @end
161
162 namespace WebCore {
163
164 #ifdef BUILDING_ON_TIGER
165 static const long minimumQuickTimeVersion = 0x07300000; // 7.3
166 #endif
167
168
169 MediaPlayerPrivateInterface* MediaPlayerPrivate::create(MediaPlayer* player) 
170
171     return new MediaPlayerPrivate(player);
172 }
173
174 void MediaPlayerPrivate::registerMediaEngine(MediaEngineRegistrar registrar)
175 {
176     if (isAvailable())
177         registrar(create, getSupportedTypes, supportsType);
178 }
179
180 MediaPlayerPrivate::MediaPlayerPrivate(MediaPlayer* player)
181     : m_player(player)
182     , m_objcObserver(AdoptNS, [[WebCoreMovieObserver alloc] initWithCallback:this])
183     , m_seekTo(-1)
184     , m_seekTimer(this, &MediaPlayerPrivate::seekTimerFired)
185     , m_networkState(MediaPlayer::Empty)
186     , m_readyState(MediaPlayer::HaveNothing)
187     , m_startedPlaying(false)
188     , m_isStreaming(false)
189     , m_visible(false)
190     , m_rect()
191     , m_enabledTrackCount(0)
192     , m_totalTrackCount(0)
193     , m_hasUnsupportedTracks(false)
194     , m_duration(-1.0f)
195 #if DRAW_FRAME_RATE
196     , m_frameCountWhilePlaying(0)
197     , m_timeStartedPlaying(0)
198     , m_timeStoppedPlaying(0)
199 #endif
200 {
201 }
202
203 MediaPlayerPrivate::~MediaPlayerPrivate()
204 {
205     tearDownVideoRendering();
206
207     [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()];
208     [m_objcObserver.get() disconnect];
209 }
210
211 void MediaPlayerPrivate::createQTMovie(const String& url)
212 {
213     [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()];
214     
215     if (m_qtMovie) {
216         destroyQTVideoRenderer();
217         m_qtMovie = 0;
218     }
219     
220     // Disable streaming support for now, <rdar://problem/5693967>
221     if (protocolIs(url, "rtsp"))
222         return;
223
224     NSURL *cocoaURL = KURL(url);
225     NSDictionary *movieAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
226                                      cocoaURL, QTMovieURLAttribute,
227                                      [NSNumber numberWithBool:YES], QTMoviePreventExternalURLLinksAttribute,
228                                      [NSNumber numberWithBool:YES], QTSecurityPolicyNoCrossSiteAttribute,
229                                      [NSNumber numberWithBool:NO], QTMovieAskUnresolvedDataRefsAttribute,
230                                      [NSNumber numberWithBool:YES], @"QTMovieOpenForPlaybackAttribute",     // FIXME: Use defined attribute when required version of QT supports this attribute
231                                      nil];
232     
233     NSError* error = nil;
234     m_qtMovie.adoptNS([[QTMovie alloc] initWithAttributes:movieAttributes error:&error]);
235     
236     // FIXME: Find a proper way to detect streaming content.
237     m_isStreaming = protocolIs(url, "rtsp");
238     
239     if (!m_qtMovie)
240         return;
241     
242     [m_qtMovie.get() setVolume:m_player->volume()];
243     
244     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
245                                              selector:@selector(loadStateChanged:) 
246                                                  name:QTMovieLoadStateDidChangeNotification 
247                                                object:m_qtMovie.get()];
248     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
249                                              selector:@selector(rateChanged:) 
250                                                  name:QTMovieRateDidChangeNotification 
251                                                object:m_qtMovie.get()];
252     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
253                                              selector:@selector(sizeChanged:) 
254                                                  name:QTMovieSizeDidChangeNotification 
255                                                object:m_qtMovie.get()];
256     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
257                                              selector:@selector(timeChanged:) 
258                                                  name:QTMovieTimeDidChangeNotification 
259                                                object:m_qtMovie.get()];
260     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
261                                              selector:@selector(didEnd:) 
262                                                  name:QTMovieDidEndNotification 
263                                                object:m_qtMovie.get()];
264 }
265
266 static void mainThreadSetNeedsDisplay(id self, SEL)
267 {
268     id movieView = [self superview];
269     ASSERT(!movieView || [movieView isKindOfClass:[QTMovieView class]]);
270     if (!movieView || ![movieView isKindOfClass:[QTMovieView class]])
271         return;
272
273     WebCoreMovieObserver* delegate = [movieView delegate];
274     ASSERT(!delegate || [delegate isKindOfClass:[WebCoreMovieObserver class]]);
275     if (!delegate || ![delegate isKindOfClass:[WebCoreMovieObserver class]])
276         return;
277
278     [delegate repaint];
279 }
280
281 static Class QTVideoRendererClass()
282 {
283      static Class QTVideoRendererWebKitOnlyClass = NSClassFromString(@"QTVideoRendererWebKitOnly");
284      return QTVideoRendererWebKitOnlyClass;
285 }
286
287 void MediaPlayerPrivate::createQTMovieView()
288 {
289     detachQTMovieView();
290
291     static bool addedCustomMethods = false;
292     if (!m_player->inMediaDocument() && !addedCustomMethods) {
293         Class QTMovieContentViewClass = NSClassFromString(@"QTMovieContentView");
294         ASSERT(QTMovieContentViewClass);
295
296         Method mainThreadSetNeedsDisplayMethod = class_getInstanceMethod(QTMovieContentViewClass, @selector(_mainThreadSetNeedsDisplay));
297         ASSERT(mainThreadSetNeedsDisplayMethod);
298
299         method_setImplementation(mainThreadSetNeedsDisplayMethod, reinterpret_cast<IMP>(mainThreadSetNeedsDisplay));
300         addedCustomMethods = true;
301     }
302
303     // delay callbacks as we *will* get notifications during setup
304     [m_objcObserver.get() setDelayCallbacks:YES];
305
306     m_qtMovieView.adoptNS([[QTMovieView alloc] init]);
307     setSize(m_player->size());
308     NSView* parentView = m_player->frameView()->documentView();
309     [parentView addSubview:m_qtMovieView.get()];
310 #ifdef BUILDING_ON_TIGER
311     // setDelegate: isn't a public call in Tiger, so use performSelector to keep the compiler happy
312     [m_qtMovieView.get() performSelector:@selector(setDelegate:) withObject:m_objcObserver.get()];    
313 #else
314     [m_qtMovieView.get() setDelegate:m_objcObserver.get()];
315 #endif
316     [m_objcObserver.get() setView:m_qtMovieView.get()];
317     [m_qtMovieView.get() setMovie:m_qtMovie.get()];
318     [m_qtMovieView.get() setControllerVisible:NO];
319     [m_qtMovieView.get() setPreservesAspectRatio:NO];
320     // the area not covered by video should be transparent
321     [m_qtMovieView.get() setFillColor:[NSColor clearColor]];
322
323     // If we're in a media document, allow QTMovieView to render in its default mode;
324     // otherwise tell it to draw synchronously.
325     // Note that we expect mainThreadSetNeedsDisplay to be invoked only when synchronous drawing is requested.
326     if (!m_player->inMediaDocument())
327         wkQTMovieViewSetDrawSynchronously(m_qtMovieView.get(), YES);
328
329     [m_objcObserver.get() setDelayCallbacks:NO];
330 }
331
332 void MediaPlayerPrivate::detachQTMovieView()
333 {
334     if (m_qtMovieView) {
335         [m_objcObserver.get() setView:nil];
336 #ifdef BUILDING_ON_TIGER
337         // setDelegate: isn't a public call in Tiger, so use performSelector to keep the compiler happy
338         [m_qtMovieView.get() performSelector:@selector(setDelegate:) withObject:nil];    
339 #else
340         [m_qtMovieView.get() setDelegate:nil];
341 #endif
342         [m_qtMovieView.get() removeFromSuperview];
343         m_qtMovieView = nil;
344     }
345 }
346
347 void MediaPlayerPrivate::createQTVideoRenderer()
348 {
349     destroyQTVideoRenderer();
350
351     m_qtVideoRenderer.adoptNS([[QTVideoRendererClass() alloc] init]);
352     if (!m_qtVideoRenderer)
353         return;
354     
355     // associate our movie with our instance of QTVideoRendererWebKitOnly
356     [(id<WebKitVideoRenderingDetails>)m_qtVideoRenderer.get() setMovie:m_qtMovie.get()];    
357
358     // listen to QTVideoRendererWebKitOnly's QTVideoRendererWebKitOnlyNewImageDidBecomeAvailableNotification
359     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
360                                              selector:@selector(newImageAvailable:)
361                                                  name:QTVideoRendererWebKitOnlyNewImageAvailableNotification
362                                                object:m_qtVideoRenderer.get()];
363 }
364
365 void MediaPlayerPrivate::destroyQTVideoRenderer()
366 {
367     if (!m_qtVideoRenderer)
368         return;
369
370     // stop observing the renderer's notifications before we toss it
371     [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()
372                                                     name:QTVideoRendererWebKitOnlyNewImageAvailableNotification
373                                                   object:m_qtVideoRenderer.get()];
374
375     // disassociate our movie from our instance of QTVideoRendererWebKitOnly
376     [(id<WebKitVideoRenderingDetails>)m_qtVideoRenderer.get() setMovie:nil];    
377
378     m_qtVideoRenderer = nil;
379 }
380
381 void MediaPlayerPrivate::setUpVideoRendering()
382 {
383     if (!m_player->frameView() || !m_qtMovie)
384         return;
385
386     if (m_player->inMediaDocument() || !QTVideoRendererClass() )
387         createQTMovieView();
388     else
389         createQTVideoRenderer();
390 }
391
392 void MediaPlayerPrivate::tearDownVideoRendering()
393 {
394     if (m_qtMovieView)
395         detachQTMovieView();
396     else
397         destroyQTVideoRenderer();
398 }
399
400 QTTime MediaPlayerPrivate::createQTTime(float time) const
401 {
402     if (!metaDataAvailable())
403         return QTMakeTime(0, 600);
404     long timeScale = [[m_qtMovie.get() attributeForKey:QTMovieTimeScaleAttribute] longValue];
405     return QTMakeTime(time * timeScale, timeScale);
406 }
407
408 void MediaPlayerPrivate::load(const String& url)
409 {
410     if (m_networkState != MediaPlayer::Loading) {
411         m_networkState = MediaPlayer::Loading;
412         m_player->networkStateChanged();
413     }
414     if (m_readyState != MediaPlayer::HaveNothing) {
415         m_readyState = MediaPlayer::HaveNothing;
416         m_player->readyStateChanged();
417     }
418     cancelSeek();
419     
420     [m_objcObserver.get() setDelayCallbacks:YES];
421
422     createQTMovie(url);
423
424     [m_objcObserver.get() loadStateChanged:nil];
425     [m_objcObserver.get() setDelayCallbacks:NO];
426 }
427
428 void MediaPlayerPrivate::play()
429 {
430     if (!metaDataAvailable())
431         return;
432     m_startedPlaying = true;
433 #if DRAW_FRAME_RATE
434     m_frameCountWhilePlaying = 0;
435 #endif
436     [m_objcObserver.get() setDelayCallbacks:YES];
437     [m_qtMovie.get() setRate:m_player->rate()];
438     [m_objcObserver.get() setDelayCallbacks:NO];
439 }
440
441 void MediaPlayerPrivate::pause()
442 {
443     if (!metaDataAvailable())
444         return;
445     m_startedPlaying = false;
446 #if DRAW_FRAME_RATE
447     m_timeStoppedPlaying = [NSDate timeIntervalSinceReferenceDate];
448 #endif
449     [m_objcObserver.get() setDelayCallbacks:YES];
450     [m_qtMovie.get() stop];
451     [m_objcObserver.get() setDelayCallbacks:NO];
452 }
453
454 float MediaPlayerPrivate::duration() const
455 {
456     if (!metaDataAvailable())
457         return 0;
458     QTTime time = [m_qtMovie.get() duration];
459     if (time.flags == kQTTimeIsIndefinite)
460         return numeric_limits<float>::infinity();
461     return static_cast<float>(time.timeValue) / time.timeScale;
462 }
463
464 float MediaPlayerPrivate::currentTime() const
465 {
466     if (!metaDataAvailable())
467         return 0;
468     QTTime time = [m_qtMovie.get() currentTime];
469     return static_cast<float>(time.timeValue) / time.timeScale;
470 }
471
472 void MediaPlayerPrivate::seek(float time)
473 {
474     cancelSeek();
475     
476     if (!metaDataAvailable())
477         return;
478     
479     if (time > duration())
480         time = duration();
481
482     m_seekTo = time;
483     if (maxTimeLoaded() >= m_seekTo)
484         doSeek();
485     else 
486         m_seekTimer.start(0, 0.5f);
487 }
488
489 void MediaPlayerPrivate::doSeek() 
490 {
491     QTTime qttime = createQTTime(m_seekTo);
492     // setCurrentTime generates several event callbacks, update afterwards
493     [m_objcObserver.get() setDelayCallbacks:YES];
494     float oldRate = [m_qtMovie.get() rate];
495     [m_qtMovie.get() setRate:0];
496     [m_qtMovie.get() setCurrentTime:qttime];
497     float timeAfterSeek = currentTime();
498     // restore playback only if not at end, othewise QTMovie will loop
499     if (oldRate && timeAfterSeek < duration())
500         [m_qtMovie.get() setRate:oldRate];
501     cancelSeek();
502     [m_objcObserver.get() setDelayCallbacks:NO];
503 }
504
505 void MediaPlayerPrivate::cancelSeek()
506 {
507     m_seekTo = -1;
508     m_seekTimer.stop();
509 }
510
511 void MediaPlayerPrivate::seekTimerFired(Timer<MediaPlayerPrivate>*)
512 {        
513     if (!metaDataAvailable()|| !seeking() || currentTime() == m_seekTo) {
514         cancelSeek();
515         updateStates();
516         m_player->timeChanged(); 
517         return;
518     } 
519     
520     if (maxTimeLoaded() >= m_seekTo)
521         doSeek();
522     else {
523         MediaPlayer::NetworkState state = networkState();
524         if (state == MediaPlayer::Empty || state == MediaPlayer::Loaded) {
525             cancelSeek();
526             updateStates();
527             m_player->timeChanged();
528         }
529     }
530 }
531
532 void MediaPlayerPrivate::setEndTime(float)
533 {
534 }
535
536 bool MediaPlayerPrivate::paused() const
537 {
538     if (!metaDataAvailable())
539         return true;
540     return [m_qtMovie.get() rate] == 0;
541 }
542
543 bool MediaPlayerPrivate::seeking() const
544 {
545     if (!metaDataAvailable())
546         return false;
547     return m_seekTo >= 0;
548 }
549
550 IntSize MediaPlayerPrivate::naturalSize() const
551 {
552     if (!metaDataAvailable())
553         return IntSize();
554
555     // In spite of the name of this method, return QTMovieCurrentSizeAttribute rather than
556     // QTMovieNaturalSizeAttribute because we need to return:
557     //
558     //    ... the dimensions of the resource in CSS pixels after taking into account the resource's 
559     //    dimensions, aspect ratio, clean aperture, resolution, and so forth, as defined for the 
560     //    format used by the resource
561
562     return IntSize([[m_qtMovie.get() attributeForKey:QTMovieCurrentSizeAttribute] sizeValue]);
563 }
564
565 bool MediaPlayerPrivate::hasVideo() const
566 {
567     if (!metaDataAvailable())
568         return false;
569     return [[m_qtMovie.get() attributeForKey:QTMovieHasVideoAttribute] boolValue];
570 }
571
572 void MediaPlayerPrivate::setVolume(float volume)
573 {
574     if (!metaDataAvailable())
575         return;
576     [m_qtMovie.get() setVolume:volume];  
577 }
578
579 void MediaPlayerPrivate::setRate(float rate)
580 {
581     if (!metaDataAvailable())
582         return;
583     if (!paused())
584         [m_qtMovie.get() setRate:rate];
585 }
586
587 int MediaPlayerPrivate::dataRate() const
588 {
589     if (!metaDataAvailable())
590         return 0;
591     return wkQTMovieDataRate(m_qtMovie.get()); 
592 }
593
594
595 float MediaPlayerPrivate::maxTimeBuffered() const
596 {
597     // rtsp streams are not buffered
598     return m_isStreaming ? 0 : maxTimeLoaded();
599 }
600
601 float MediaPlayerPrivate::maxTimeSeekable() const
602 {
603     // infinite duration means live stream
604     return isinf(duration()) ? 0 : maxTimeLoaded();
605 }
606
607 float MediaPlayerPrivate::maxTimeLoaded() const
608 {
609     if (!metaDataAvailable())
610         return 0;
611     return wkQTMovieMaxTimeLoaded(m_qtMovie.get()); 
612 }
613
614 unsigned MediaPlayerPrivate::bytesLoaded() const
615 {
616     float dur = duration();
617     if (!dur)
618         return 0;
619     return totalBytes() * maxTimeLoaded() / dur;
620 }
621
622 bool MediaPlayerPrivate::totalBytesKnown() const
623 {
624     return totalBytes() > 0;
625 }
626
627 unsigned MediaPlayerPrivate::totalBytes() const
628 {
629     if (!metaDataAvailable())
630         return 0;
631     return [[m_qtMovie.get() attributeForKey:QTMovieDataSizeAttribute] intValue];
632 }
633
634 void MediaPlayerPrivate::cancelLoad()
635 {
636     // FIXME: Is there a better way to check for this?
637     if (m_networkState < MediaPlayer::Loading || m_networkState == MediaPlayer::Loaded)
638         return;
639     
640     tearDownVideoRendering();
641     m_qtMovie = nil;
642     
643     updateStates();
644 }
645
646 void MediaPlayerPrivate::updateStates()
647 {
648     MediaPlayer::NetworkState oldNetworkState = m_networkState;
649     MediaPlayer::ReadyState oldReadyState = m_readyState;
650     
651     long loadState = m_qtMovie ? [[m_qtMovie.get() attributeForKey:QTMovieLoadStateAttribute] longValue] : static_cast<long>(QTMovieLoadStateError);
652
653     if (loadState >= QTMovieLoadStateLoaded && m_readyState < MediaPlayer::HaveMetadata) {
654         disableUnsupportedTracks();
655         if (m_player->inMediaDocument()) {
656             if (!m_enabledTrackCount || m_enabledTrackCount != m_totalTrackCount) {
657                 // This is a type of media that we do not handle directly with a <video> 
658                 // element, likely streamed media or QuickTime VR. Tell the MediaPlayerClient
659                 // that we noticed.
660                 sawUnsupportedTracks();
661                 return;
662             }
663         } else if (!m_enabledTrackCount) {
664             loadState = QTMovieLoadStateError;
665         }
666     }
667
668     if (loadState >= QTMovieLoadStateComplete && !m_isStreaming) {
669         // "Loaded" is reserved for fully buffered movies, never the case when streaming
670         m_networkState = MediaPlayer::Loaded;
671         m_readyState = MediaPlayer::HaveEnoughData;
672     } else if (loadState >= QTMovieLoadStatePlaythroughOK) {
673         m_readyState = MediaPlayer::HaveEnoughData;
674         m_networkState = MediaPlayer::Loading;
675     } else if (loadState >= QTMovieLoadStatePlayable) {
676         // FIXME: This might not work correctly in streaming case, <rdar://problem/5693967>
677         m_readyState = currentTime() < maxTimeLoaded() ? MediaPlayer::HaveFutureData : MediaPlayer::HaveCurrentData;
678         m_networkState = MediaPlayer::Loading;
679     } else if (loadState >= QTMovieLoadStateLoaded) {
680         m_readyState = MediaPlayer::HaveMetadata;
681         m_networkState = MediaPlayer::Loading;
682     } else if (loadState > QTMovieLoadStateError) {
683         m_readyState = MediaPlayer::HaveNothing;
684         m_networkState = MediaPlayer::Loading;
685     } else {
686         if (m_player->inMediaDocument()) {
687             // Something went wrong in the loading of media within a standalone file. 
688             // This can occur with chained refmovies pointing to streamed media.
689             sawUnsupportedTracks();
690             return;
691         }
692
693         float loaded = maxTimeLoaded();
694         if (!loaded)
695             m_readyState = MediaPlayer::HaveNothing;
696
697         if (!m_enabledTrackCount)
698             m_networkState = MediaPlayer::FormatError;
699         else {
700             // FIXME: We should differentiate between load/network errors and decode errors <rdar://problem/5605692>
701             if (loaded > 0)
702                 m_networkState = MediaPlayer::DecodeError;
703             else
704                 m_readyState = MediaPlayer::HaveNothing;
705         }
706     }
707
708     if (seeking())
709         m_readyState = MediaPlayer::HaveNothing;
710
711     if (m_networkState != oldNetworkState)
712         m_player->networkStateChanged();
713     if (m_readyState != oldReadyState)
714         m_player->readyStateChanged();
715
716     if (loadState >= QTMovieLoadStateLoaded && (!m_qtMovieView && !m_qtVideoRenderer) && m_player->visible())
717         setUpVideoRendering();
718
719     if (loadState >= QTMovieLoadStateLoaded) {
720         float dur = duration();
721         if (dur != m_duration) {
722             if (m_duration != -1.0f)
723                 m_player->durationChanged();
724             m_duration = dur;
725         }
726     }
727 }
728
729 void MediaPlayerPrivate::loadStateChanged()
730 {
731     updateStates();
732 }
733
734 void MediaPlayerPrivate::rateChanged()
735 {
736     updateStates();
737     if (!m_hasUnsupportedTracks)
738         m_player->rateChanged();
739 }
740
741 void MediaPlayerPrivate::sizeChanged()
742 {
743     if (!m_hasUnsupportedTracks)
744         m_player->sizeChanged();
745 }
746
747 void MediaPlayerPrivate::timeChanged()
748 {
749     updateStates();
750     if (!m_hasUnsupportedTracks)
751         m_player->timeChanged();
752 }
753
754 void MediaPlayerPrivate::didEnd()
755 {
756     m_startedPlaying = false;
757 #if DRAW_FRAME_RATE
758     m_timeStoppedPlaying = [NSDate timeIntervalSinceReferenceDate];
759 #endif
760     updateStates();
761     if (!m_hasUnsupportedTracks)
762         m_player->timeChanged();
763 }
764
765 void MediaPlayerPrivate::setSize(const IntSize&) 
766
767     // Don't resize the view now because [view setFrame] also resizes the movie itself, and because
768     // the renderer calls this function immediately when we report a size change (QTMovieSizeDidChangeNotification)
769     // we can get into a feedback loop observing the size change and resetting the size, and this can cause
770     // QuickTime to miss resetting a movie's size when the media size changes (as happens with an rtsp movie
771     // once the rtsp server sends the track sizes). Instead we remember the size passed to paint() and resize
772     // the view when it changes.
773     // <rdar://problem/6336092> REGRESSION: rtsp movie does not resize correctly
774 }
775
776 void MediaPlayerPrivate::setVisible(bool b)
777 {
778     if (m_visible != b) {
779         m_visible = b;
780         if (b) {
781             if (m_readyState >= MediaPlayer::HaveMetadata)
782                 setUpVideoRendering();
783         } else
784             tearDownVideoRendering();
785     }
786 }
787
788 void MediaPlayerPrivate::repaint()
789 {
790 #if DRAW_FRAME_RATE
791     if (m_startedPlaying) {
792         m_frameCountWhilePlaying++;
793         // to eliminate preroll costs from our calculation,
794         // our frame rate calculation excludes the first frame drawn after playback starts
795         if (1==m_frameCountWhilePlaying)
796             m_timeStartedPlaying = [NSDate timeIntervalSinceReferenceDate];
797     }
798 #endif
799     m_player->repaint();
800 }
801
802 void MediaPlayerPrivate::paint(GraphicsContext* context, const IntRect& r)
803 {
804     if (context->paintingDisabled())
805         return;
806     NSView *view = m_qtMovieView.get();
807     id qtVideoRenderer = m_qtVideoRenderer.get();
808     if (!view && !qtVideoRenderer)
809         return;
810
811     [m_objcObserver.get() setDelayCallbacks:YES];
812     BEGIN_BLOCK_OBJC_EXCEPTIONS;
813     context->save();
814     context->translate(r.x(), r.y() + r.height());
815     context->scale(FloatSize(1.0f, -1.0f));
816     context->setImageInterpolationQuality(InterpolationLow);
817     IntRect paintRect(IntPoint(0, 0), IntSize(r.width(), r.height()));
818     
819 #ifdef BUILDING_ON_TIGER
820     AutodrainedPool pool;
821 #endif
822     NSGraphicsContext* newContext = [NSGraphicsContext graphicsContextWithGraphicsPort:context->platformContext() flipped:NO];
823
824     // draw the current video frame
825     if (qtVideoRenderer) {
826         [NSGraphicsContext saveGraphicsState];
827         [NSGraphicsContext setCurrentContext:newContext];
828         [(id<WebKitVideoRenderingDetails>)qtVideoRenderer drawInRect:paintRect];
829         [NSGraphicsContext restoreGraphicsState];
830     } else {
831         if (m_rect != r) {
832              m_rect = r;
833             if (m_player->inMediaDocument()) {
834                 // the QTMovieView needs to be placed in the proper location for document mode
835                 [view setFrame:m_rect];
836             }
837             else {
838                 // We don't really need the QTMovieView in any specific location so let's just get it out of the way
839                 // where it won't intercept events or try to bring up the context menu.
840                 IntRect farAwayButCorrectSize(m_rect);
841                 farAwayButCorrectSize.move(-1000000, -1000000);
842                 [view setFrame:farAwayButCorrectSize];
843             }
844         }
845
846         [view displayRectIgnoringOpacity:paintRect inContext:newContext];
847     }
848
849 #if DRAW_FRAME_RATE
850     // Draw the frame rate only after having played more than 10 frames.
851     if (m_frameCountWhilePlaying > 10) {
852         Frame* frame = m_player->frameView() ? m_player->frameView()->frame() : NULL;
853         Document* document = frame ? frame->document() : NULL;
854         RenderObject* renderer = document ? document->renderer() : NULL;
855         RenderStyle* styleToUse = renderer ? renderer->style() : NULL;
856         if (styleToUse) {
857             double frameRate = (m_frameCountWhilePlaying - 1) / ( m_startedPlaying ? ([NSDate timeIntervalSinceReferenceDate] - m_timeStartedPlaying) :
858                 (m_timeStoppedPlaying - m_timeStartedPlaying) );
859             String text = String::format("%1.2f", frameRate);
860             TextRun textRun(text.characters(), text.length());
861             const Color color(255, 0, 0);
862             context->scale(FloatSize(1.0f, -1.0f));    
863             context->setFont(styleToUse->font());
864             context->setStrokeColor(color);
865             context->setStrokeStyle(SolidStroke);
866             context->setStrokeThickness(1.0f);
867             context->setFillColor(color);
868             context->drawText(textRun, IntPoint(2, -3));
869         }
870     }
871 #endif
872
873     context->restore();
874     END_BLOCK_OBJC_EXCEPTIONS;
875     [m_objcObserver.get() setDelayCallbacks:NO];
876 }
877
878 static void addFileTypesToCache(NSArray * fileTypes, HashSet<String> &cache)
879 {
880     int count = [fileTypes count];
881     for (int n = 0; n < count; n++) {
882         CFStringRef ext = reinterpret_cast<CFStringRef>([fileTypes objectAtIndex:n]);
883         RetainPtr<CFStringRef> uti(AdoptCF, UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, ext, NULL));
884         if (!uti)
885             continue;
886         RetainPtr<CFStringRef> mime(AdoptCF, UTTypeCopyPreferredTagWithClass(uti.get(), kUTTagClassMIMEType));
887         if (!mime)
888             continue;
889         cache.add(mime.get());
890     }    
891 }
892
893 static HashSet<String> mimeCommonTypesCache()
894 {
895     DEFINE_STATIC_LOCAL(HashSet<String>, cache, ());
896     static bool typeListInitialized = false;
897
898     if (!typeListInitialized) {
899         typeListInitialized = true;
900         NSArray* fileTypes = [QTMovie movieFileTypes:QTIncludeCommonTypes];
901         addFileTypesToCache(fileTypes, cache);
902     }
903     
904     return cache;
905
906
907 static HashSet<String> mimeModernTypesCache()
908 {
909     DEFINE_STATIC_LOCAL(HashSet<String>, cache, ());
910     static bool typeListInitialized = false;
911     
912     if (!typeListInitialized) {
913         typeListInitialized = true;
914         NSArray* fileTypes = [QTMovie movieFileTypes:(QTMovieFileTypeOptions)wkQTIncludeOnlyModernMediaFileTypes()];
915         addFileTypesToCache(fileTypes, cache);
916     }
917     
918     return cache;
919
920
921 void MediaPlayerPrivate::getSupportedTypes(HashSet<String>& types)
922 {
923     // Note: this method starts QTKitServer if it isn't already running when in 64-bit because it has to return the list 
924     // of every MIME type supported by QTKit.
925     types = mimeCommonTypesCache();
926
927
928 MediaPlayer::SupportsType MediaPlayerPrivate::supportsType(const String& type, const String& codecs)
929 {
930     // Only return "IsSupported" if there is no codecs parameter for now as there is no way to ask QT if it supports an
931     // extended MIME type yet.
932
933     // We check the "modern" type cache first, as it doesn't require QTKitServer to start.
934     if (mimeModernTypesCache().contains(type) || mimeCommonTypesCache().contains(type))
935         return (codecs && !codecs.isEmpty() ? MediaPlayer::MayBeSupported : MediaPlayer::IsSupported);
936
937     return MediaPlayer::IsNotSupported;
938 }
939
940 bool MediaPlayerPrivate::isAvailable()
941 {
942 #ifdef BUILDING_ON_TIGER
943     SInt32 version;
944     OSErr result;
945     result = Gestalt(gestaltQuickTime, &version);
946     if (result != noErr) {
947         LOG_ERROR("No QuickTime available. Disabling <video> and <audio> support.");
948         return false;
949     }
950     if (version < minimumQuickTimeVersion) {
951         LOG_ERROR("QuickTime version %x detected, at least %x required. Disabling <video> and <audio> support.", version, minimumQuickTimeVersion);
952         return false;
953     }
954     return true;
955 #else
956     // 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.
957     return QTKitLibrary();
958 #endif
959 }
960     
961 void MediaPlayerPrivate::disableUnsupportedTracks()
962 {
963     if (!m_qtMovie) {
964         m_enabledTrackCount = 0;
965         m_totalTrackCount = 0;
966         return;
967     }
968     
969     static HashSet<String>* allowedTrackTypes = 0;
970     if (!allowedTrackTypes) {
971         allowedTrackTypes = new HashSet<String>;
972         allowedTrackTypes->add(QTMediaTypeVideo);
973         allowedTrackTypes->add(QTMediaTypeSound);
974         allowedTrackTypes->add(QTMediaTypeText);
975         allowedTrackTypes->add(QTMediaTypeBase);
976         allowedTrackTypes->add(QTMediaTypeMPEG);
977         allowedTrackTypes->add("clcp");
978         allowedTrackTypes->add("sbtl");
979     }
980     
981     NSArray *tracks = [m_qtMovie.get() tracks];
982     
983     m_totalTrackCount = [tracks count];
984     m_enabledTrackCount = m_totalTrackCount;
985     for (unsigned trackIndex = 0; trackIndex < m_totalTrackCount; trackIndex++) {
986         // Grab the track at the current index. If there isn't one there, then
987         // we can move onto the next one.
988         QTTrack *track = [tracks objectAtIndex:trackIndex];
989         if (!track)
990             continue;
991         
992         // Check to see if the track is disabled already, we should move along.
993         // We don't need to re-disable it.
994         if (![track isEnabled])
995             continue;
996         
997         // Get the track's media type.
998         NSString *mediaType = [track attributeForKey:QTTrackMediaTypeAttribute];
999         if (!mediaType)
1000             continue;
1001
1002         // Test whether the media type is in our white list.
1003         if (!allowedTrackTypes->contains(mediaType)) {
1004             // If this track type is not allowed, then we need to disable it.
1005             [track setEnabled:NO];
1006             --m_enabledTrackCount;
1007         }
1008
1009         // Disable chapter tracks. These are most likely to lead to trouble, as
1010         // they will be composited under the video tracks, forcing QT to do extra
1011         // work.
1012         QTTrack *chapterTrack = [track performSelector:@selector(chapterlist)];
1013         if (!chapterTrack)
1014             continue;
1015         
1016         // Try to grab the media for the track.
1017         QTMedia *chapterMedia = [chapterTrack media];
1018         if (!chapterMedia)
1019             continue;
1020         
1021         // Grab the media type for this track.
1022         id chapterMediaType = [chapterMedia attributeForKey:QTMediaTypeAttribute];
1023         if (!chapterMediaType)
1024             continue;
1025         
1026         // Check to see if the track is a video track. We don't care about
1027         // other non-video tracks.
1028         if (![chapterMediaType isEqual:QTMediaTypeVideo])
1029             continue;
1030         
1031         // Check to see if the track is already disabled. If it is, we
1032         // should move along.
1033         if (![chapterTrack isEnabled])
1034             continue;
1035         
1036         // Disable the evil, evil track.
1037         [chapterTrack setEnabled:NO];
1038         --m_enabledTrackCount;
1039     }
1040 }
1041
1042 void MediaPlayerPrivate::sawUnsupportedTracks()
1043 {
1044     m_player->mediaPlayerClient()->mediaPlayerSawUnsupportedTracks(m_player);
1045     m_hasUnsupportedTracks = true;
1046 }
1047
1048 }
1049
1050 @implementation WebCoreMovieObserver
1051
1052 - (id)initWithCallback:(MediaPlayerPrivate*)callback
1053 {
1054     m_callback = callback;
1055     return [super init];
1056 }
1057
1058 - (void)disconnect
1059 {
1060     [NSObject cancelPreviousPerformRequestsWithTarget:self];
1061     m_callback = 0;
1062 }
1063
1064 -(NSMenu*)menuForEventDelegate:(NSEvent*)theEvent
1065 {
1066     // Get the contextual menu from the QTMovieView's superview, the frame view
1067     return [[m_view superview] menuForEvent:theEvent];
1068 }
1069
1070 -(void)setView:(NSView*)view
1071 {
1072     m_view = view;
1073 }
1074
1075 -(void)repaint
1076 {
1077     if (m_delayCallbacks)
1078         [self performSelector:_cmd withObject:nil afterDelay:0.];
1079     else if (m_callback)
1080         m_callback->repaint();
1081 }
1082
1083 - (void)loadStateChanged:(NSNotification *)unusedNotification
1084 {
1085     UNUSED_PARAM(unusedNotification);
1086     if (m_delayCallbacks)
1087         [self performSelector:_cmd withObject:nil afterDelay:0];
1088     else
1089         m_callback->loadStateChanged();
1090 }
1091
1092 - (void)rateChanged:(NSNotification *)unusedNotification
1093 {
1094     UNUSED_PARAM(unusedNotification);
1095     if (m_delayCallbacks)
1096         [self performSelector:_cmd withObject:nil afterDelay:0];
1097     else
1098         m_callback->rateChanged();
1099 }
1100
1101 - (void)sizeChanged:(NSNotification *)unusedNotification
1102 {
1103     UNUSED_PARAM(unusedNotification);
1104     if (m_delayCallbacks)
1105         [self performSelector:_cmd withObject:nil afterDelay:0];
1106     else
1107         m_callback->sizeChanged();
1108 }
1109
1110 - (void)timeChanged:(NSNotification *)unusedNotification
1111 {
1112     UNUSED_PARAM(unusedNotification);
1113     if (m_delayCallbacks)
1114         [self performSelector:_cmd withObject:nil afterDelay:0];
1115     else
1116         m_callback->timeChanged();
1117 }
1118
1119 - (void)didEnd:(NSNotification *)unusedNotification
1120 {
1121     UNUSED_PARAM(unusedNotification);
1122     if (m_delayCallbacks)
1123         [self performSelector:_cmd withObject:nil afterDelay:0];
1124     else
1125         m_callback->didEnd();
1126 }
1127
1128 - (void)newImageAvailable:(NSNotification *)unusedNotification
1129 {
1130     UNUSED_PARAM(unusedNotification);
1131     [self repaint];
1132 }
1133
1134 - (void)setDelayCallbacks:(BOOL)shouldDelay
1135 {
1136     m_delayCallbacks = shouldDelay;
1137 }
1138
1139 @end
1140
1141 #endif