Reviewed and partially fixed by Tim Hatcher.
[WebKit-https.git] / WebCore / platform / graphics / mac / MediaPlayerPrivateQTKit.mm
1 /*
2  * Copyright (C) 2007 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #import "config.h"
27
28 #if ENABLE(VIDEO)
29
30 #import "MediaPlayerPrivateQTKit.h"
31
32 #import "BlockExceptions.h"
33 #import "DeprecatedString.h"
34 #import "GraphicsContext.h"
35 #import "KURL.h"
36 #import "ScrollView.h"
37 #import "SoftLinking.h"
38 #import "WebCoreSystemInterface.h"
39 #import <QTKit/QTKit.h>
40 #import <objc/objc-runtime.h>
41
42 #ifdef BUILDING_ON_TIGER
43 static IMP method_setImplementation(Method m, IMP imp)
44 {
45     IMP result = m->method_imp;
46     m->method_imp = imp;
47     return result;
48 }
49 #endif
50
51 SOFT_LINK_FRAMEWORK(QTKit)
52
53 SOFT_LINK(QTKit, QTMakeTime, QTTime, (long long timeValue, long timeScale), (timeValue, timeScale))
54
55 SOFT_LINK_CLASS(QTKit, QTMovie)
56 SOFT_LINK_CLASS(QTKit, QTMovieView)
57
58 SOFT_LINK_POINTER(QTKit, QTMovieDataSizeAttribute, NSString *)
59 SOFT_LINK_POINTER(QTKit, QTMovieDidEndNotification, NSString *)
60 SOFT_LINK_POINTER(QTKit, QTMovieHasVideoAttribute, NSString *)
61 SOFT_LINK_POINTER(QTKit, QTMovieLoadStateAttribute, NSString *)
62 SOFT_LINK_POINTER(QTKit, QTMovieLoadStateDidChangeNotification, NSString *)
63 SOFT_LINK_POINTER(QTKit, QTMovieNaturalSizeAttribute, NSString *)
64 SOFT_LINK_POINTER(QTKit, QTMovieRateDidChangeNotification, NSString *)
65 SOFT_LINK_POINTER(QTKit, QTMovieSizeDidChangeNotification, NSString *)
66 SOFT_LINK_POINTER(QTKit, QTMovieTimeDidChangeNotification, NSString *)
67 SOFT_LINK_POINTER(QTKit, QTMovieTimeScaleAttribute, NSString *)
68 SOFT_LINK_POINTER(QTKit, QTMovieVolumeDidChangeNotification, NSString *)
69
70 #define QTMovie getQTMovieClass()
71 #define QTMovieView getQTMovieViewClass()
72
73 #define QTMovieDataSizeAttribute getQTMovieDataSizeAttribute()
74 #define QTMovieDidEndNotification getQTMovieDidEndNotification()
75 #define QTMovieHasVideoAttribute getQTMovieHasVideoAttribute()
76 #define QTMovieLoadStateAttribute getQTMovieLoadStateAttribute()
77 #define QTMovieLoadStateDidChangeNotification getQTMovieLoadStateDidChangeNotification()
78 #define QTMovieNaturalSizeAttribute getQTMovieNaturalSizeAttribute()
79 #define QTMovieRateDidChangeNotification getQTMovieRateDidChangeNotification()
80 #define QTMovieSizeDidChangeNotification getQTMovieSizeDidChangeNotification()
81 #define QTMovieTimeDidChangeNotification getQTMovieTimeDidChangeNotification()
82 #define QTMovieTimeScaleAttribute getQTMovieTimeScaleAttribute()
83 #define QTMovieVolumeDidChangeNotification getQTMovieVolumeDidChangeNotification()
84
85 // Older versions of the QTKit header don't have these constants.
86 #if !defined QTKIT_VERSION_MAX_ALLOWED || QTKIT_VERSION_MAX_ALLOWED <= QTKIT_VERSION_7_0
87 enum {
88     QTMovieLoadStateLoaded  = 2000L,
89     QTMovieLoadStatePlayable = 10000L,
90     QTMovieLoadStatePlaythroughOK = 20000L,
91     QTMovieLoadStateComplete = 100000L
92 };
93 #endif
94
95 using namespace WebCore;
96 using namespace std;
97
98 @interface WebCoreMovieObserver : NSObject
99 {
100     MediaPlayerPrivate* m_callback;
101     BOOL m_delayCallbacks;
102 }
103 -(id)initWithCallback:(MediaPlayerPrivate*)callback;
104 -(void)disconnect;
105 -(void)repaint;
106 -(void)setDelayCallbacks:(BOOL)shouldDelay;
107 -(void)loadStateChanged:(NSNotification *)notification;
108 -(void)rateChanged:(NSNotification *)notification;
109 -(void)sizeChanged:(NSNotification *)notification;
110 -(void)timeChanged:(NSNotification *)notification;
111 -(void)volumeChanged:(NSNotification *)notification;
112 -(void)didEnd:(NSNotification *)notification;
113 @end
114
115 namespace WebCore {
116
117 static const float cuePointTimerInterval = 0.020f;
118     
119 MediaPlayerPrivate::MediaPlayerPrivate(MediaPlayer* player)
120     : m_player(player)
121     , m_objcObserver(AdoptNS, [[WebCoreMovieObserver alloc] initWithCallback:this])
122     , m_seekTo(-1)
123     , m_endTime(numeric_limits<float>::infinity())
124     , m_seekTimer(this, &MediaPlayerPrivate::seekTimerFired)
125     , m_cuePointTimer(this, &MediaPlayerPrivate::cuePointTimerFired)
126     , m_previousTimeCueTimerFired(0)
127     , m_networkState(MediaPlayer::Empty)
128     , m_readyState(MediaPlayer::DataUnavailable)
129     , m_startedPlaying(false)
130     , m_isStreaming(false)
131 {
132 }
133
134 MediaPlayerPrivate::~MediaPlayerPrivate()
135 {
136     detachQTMovieView();
137
138     [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()];
139     [m_objcObserver.get() disconnect];
140 }
141
142 void MediaPlayerPrivate::createQTMovie(const String& url)
143 {
144     [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()];
145     
146     NSError* error = nil;
147     m_qtMovie.adoptNS([[QTMovie alloc] initWithURL:KURL(url.deprecatedString()).getNSURL() error:&error]);
148     
149     // FIXME: Find a proper way to detect streaming content.
150     m_isStreaming = url.startsWith("rtsp:");
151     
152     if (!m_qtMovie)
153         return;
154     
155     [m_qtMovie.get() setVolume:m_player->volume()];
156     [m_qtMovie.get() setMuted:m_player->muted()];
157     
158     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
159                                              selector:@selector(loadStateChanged:) 
160                                                  name:QTMovieLoadStateDidChangeNotification 
161                                                object:m_qtMovie.get()];
162     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
163                                              selector:@selector(rateChanged:) 
164                                                  name:QTMovieRateDidChangeNotification 
165                                                object:m_qtMovie.get()];
166     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
167                                              selector:@selector(sizeChanged:) 
168                                                  name:QTMovieSizeDidChangeNotification 
169                                                object:m_qtMovie.get()];
170     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
171                                              selector:@selector(timeChanged:) 
172                                                  name:QTMovieTimeDidChangeNotification 
173                                                object:m_qtMovie.get()];
174     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
175                                              selector:@selector(volumeChanged:) 
176                                                  name:QTMovieVolumeDidChangeNotification 
177                                                object:m_qtMovie.get()];
178     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
179                                              selector:@selector(didEnd:) 
180                                                  name:QTMovieDidEndNotification 
181                                                object:m_qtMovie.get()];
182 }
183
184 static void mainThreadSetNeedsDisplay(id self, SEL _cmd)
185 {
186     id movieView = [self superview];
187     ASSERT(!movieView || [movieView isKindOfClass:[QTMovieView class]]);
188     if (!movieView || ![movieView isKindOfClass:[QTMovieView class]])
189         return;
190
191     WebCoreMovieObserver* delegate = [movieView delegate];
192     ASSERT(!delegate || [delegate isKindOfClass:[WebCoreMovieObserver class]]);
193     if (!delegate || ![delegate isKindOfClass:[WebCoreMovieObserver class]])
194         return;
195
196     [delegate repaint];
197 }
198
199 void MediaPlayerPrivate::createQTMovieView()
200 {
201     detachQTMovieView();
202
203     if (!m_player->m_parentWidget || !m_qtMovie)
204         return;
205
206     static bool addedCustomMethods = false;
207     if (!addedCustomMethods) {
208         Class QTMovieContentViewClass = NSClassFromString(@"QTMovieContentView");
209         ASSERT(QTMovieContentViewClass);
210
211         Method mainThreadSetNeedsDisplayMethod = class_getInstanceMethod(QTMovieContentViewClass, @selector(_mainThreadSetNeedsDisplay));
212         ASSERT(mainThreadSetNeedsDisplayMethod);
213
214         method_setImplementation(mainThreadSetNeedsDisplayMethod, reinterpret_cast<IMP>(mainThreadSetNeedsDisplay));
215         addedCustomMethods = true;
216     }
217
218     m_qtMovieView.adoptNS([[QTMovieView alloc] initWithFrame:m_player->rect()]);
219     NSView* parentView = static_cast<ScrollView*>(m_player->m_parentWidget)->getDocumentView();
220     [parentView addSubview:m_qtMovieView.get()];
221     [m_qtMovieView.get() setDelegate:m_objcObserver.get()];
222     [m_qtMovieView.get() setMovie:m_qtMovie.get()];
223     [m_qtMovieView.get() setControllerVisible:NO];
224     [m_qtMovieView.get() setPreservesAspectRatio:YES];
225     // the area not covered by video should be transparent
226     [m_qtMovieView.get() setFillColor:[NSColor clearColor]];
227     wkQTMovieViewSetDrawSynchronously(m_qtMovieView.get(), YES);
228 }
229
230 void MediaPlayerPrivate::detachQTMovieView()
231 {
232     if (m_qtMovieView) {
233         [m_qtMovieView.get() setDelegate:nil];
234         [m_qtMovieView.get() removeFromSuperview];
235         m_qtMovieView = nil;
236     }
237 }
238
239 QTTime MediaPlayerPrivate::createQTTime(float time) const
240 {
241     if (!m_qtMovie)
242         return QTMakeTime(0, 600);
243     long timeScale = [[m_qtMovie.get() attributeForKey:QTMovieTimeScaleAttribute] longValue];
244     return QTMakeTime(time * timeScale, timeScale);
245 }
246
247 void MediaPlayerPrivate::load(const String& url)
248 {
249     if (m_networkState != MediaPlayer::Loading) {
250         m_networkState = MediaPlayer::Loading;
251         m_player->networkStateChanged();
252     }
253     if (m_readyState != MediaPlayer::DataUnavailable) {
254         m_readyState = MediaPlayer::DataUnavailable;
255         m_player->readyStateChanged();
256     }
257     cancelSeek();
258     m_cuePointTimer.stop();
259     
260     [m_objcObserver.get() setDelayCallbacks:YES];
261
262     createQTMovie(url);
263     if (m_player->visible())
264         createQTMovieView();
265
266     [m_objcObserver.get() loadStateChanged:nil];
267     [m_objcObserver.get() setDelayCallbacks:NO];
268 }
269
270 void MediaPlayerPrivate::play()
271 {
272     if (!m_qtMovie)
273         return;
274     m_startedPlaying = true;
275     [m_objcObserver.get() setDelayCallbacks:YES];
276     [m_qtMovie.get() setRate:m_player->rate()];
277     [m_objcObserver.get() setDelayCallbacks:NO];
278     startCuePointTimerIfNeeded();
279 }
280
281 void MediaPlayerPrivate::pause()
282 {
283     if (!m_qtMovie)
284         return;
285     m_startedPlaying = false;
286     [m_objcObserver.get() setDelayCallbacks:YES];
287     [m_qtMovie.get() stop];
288     [m_objcObserver.get() setDelayCallbacks:NO];
289     m_cuePointTimer.stop();
290 }
291
292 float MediaPlayerPrivate::duration() const
293 {
294     if (!m_qtMovie)
295         return 0;
296     QTTime time = [m_qtMovie.get() duration];
297     if (time.flags == kQTTimeIsIndefinite)
298         return numeric_limits<float>::infinity();
299     return static_cast<float>(time.timeValue) / time.timeScale;
300 }
301
302 float MediaPlayerPrivate::currentTime() const
303 {
304     if (!m_qtMovie)
305         return 0;
306     QTTime time = [m_qtMovie.get() currentTime];
307     return min(static_cast<float>(time.timeValue) / time.timeScale, m_endTime);
308 }
309
310 void MediaPlayerPrivate::seek(float time)
311 {
312     cancelSeek();
313     
314     if (!m_qtMovie)
315         return;
316     
317     if (time > duration())
318         time = duration();
319     
320     m_seekTo = time;
321     if (maxTimeLoaded() >= m_seekTo)
322         doSeek();
323     else 
324         m_seekTimer.start(0, 0.5f);
325 }
326
327 void MediaPlayerPrivate::doSeek() 
328 {
329     QTTime qttime = createQTTime(m_seekTo);
330     // setCurrentTime generates several event callbacks, update afterwards
331     [m_objcObserver.get() setDelayCallbacks:YES];
332     float oldRate = [m_qtMovie.get() rate];
333     [m_qtMovie.get() setRate:0];
334     [m_qtMovie.get() setCurrentTime:qttime];
335     float timeAfterSeek = currentTime();
336     // restore playback only if not at end, othewise QTMovie will loop
337     if (timeAfterSeek < duration() && timeAfterSeek < m_endTime)
338         [m_qtMovie.get() setRate:oldRate];
339     cancelSeek();
340     [m_objcObserver.get() setDelayCallbacks:NO];
341 }
342
343 void MediaPlayerPrivate::cancelSeek()
344 {
345     m_seekTo = -1;
346     m_seekTimer.stop();
347 }
348
349 void MediaPlayerPrivate::seekTimerFired(Timer<MediaPlayerPrivate>*)
350 {        
351     if (!m_qtMovie || !seeking() || currentTime() == m_seekTo) {
352         cancelSeek();
353         updateStates();
354         m_player->timeChanged(); 
355         return;
356     } 
357     
358     if (maxTimeLoaded() >= m_seekTo)
359         doSeek();
360     else {
361         MediaPlayer::NetworkState state = networkState();
362         if (state == MediaPlayer::Empty || state == MediaPlayer::Loaded) {
363             cancelSeek();
364             updateStates();
365             m_player->timeChanged();
366         }
367     }
368 }
369
370 void MediaPlayerPrivate::setEndTime(float time)
371 {
372     m_endTime = time;
373     startCuePointTimerIfNeeded();
374 }
375
376 void MediaPlayerPrivate::addCuePoint(float /*time*/)
377 {
378     // FIXME: Eventually we'd like an approach that doesn't involve a timer.
379     startCuePointTimerIfNeeded();
380 }
381
382 void MediaPlayerPrivate::removeCuePoint(float /*time*/)
383 {
384 }
385
386 void MediaPlayerPrivate::clearCuePoints()
387 {
388 }
389
390 void MediaPlayerPrivate::startCuePointTimerIfNeeded()
391 {
392     if ((m_endTime < duration() || !m_player->m_cuePoints.isEmpty())
393         && m_startedPlaying && !m_cuePointTimer.isActive()) {
394         m_previousTimeCueTimerFired = currentTime();
395         m_cuePointTimer.startRepeating(cuePointTimerInterval);
396     }
397 }
398
399 void MediaPlayerPrivate::cuePointTimerFired(Timer<MediaPlayerPrivate>*)
400 {
401     float time = currentTime();
402     float previousTime = m_previousTimeCueTimerFired;
403     m_previousTimeCueTimerFired = time;
404     
405     // just do end for now
406     if (time >= m_endTime) {
407         pause();
408         didEnd();
409     }
410
411     // Make a copy since m_cuePoints could change as we deliver JavaScript calls.
412     Vector<float> cuePoints;
413     copyToVector(m_player->m_cuePoints, cuePoints);
414     size_t numCuePoints = cuePoints.size();
415     for (size_t i = 0; i < numCuePoints; ++i) {
416         float cueTime = cuePoints[i];
417         if (previousTime < cueTime && cueTime <= time)
418             m_player->cuePointReached(cueTime);
419     }
420 }
421
422 bool MediaPlayerPrivate::paused() const
423 {
424     if (!m_qtMovie)
425         return true;
426     return [m_qtMovie.get() rate] == 0;
427 }
428
429 bool MediaPlayerPrivate::seeking() const
430 {
431     if (!m_qtMovie)
432         return false;
433     return m_seekTo >= 0;
434 }
435
436 IntSize MediaPlayerPrivate::naturalSize() const
437 {
438     if (!m_qtMovie)
439         return IntSize();
440     return IntSize([[m_qtMovie.get() attributeForKey:QTMovieNaturalSizeAttribute] sizeValue]);
441 }
442
443 bool MediaPlayerPrivate::hasVideo() const
444 {
445     if (!m_qtMovie)
446         return false;
447     return [[m_qtMovie.get() attributeForKey:QTMovieHasVideoAttribute] boolValue];
448 }
449
450 void MediaPlayerPrivate::setVolume(float volume)
451 {
452     if (!m_qtMovie)
453         return;
454     [m_qtMovie.get() setVolume:volume];  
455 }
456
457 void MediaPlayerPrivate::setMuted(bool b)
458 {
459     if (!m_qtMovie)
460         return;
461     [m_qtMovie.get() setMuted:b];
462 }
463
464 void MediaPlayerPrivate::setRate(float rate)
465 {
466     if (!m_qtMovie)
467         return;
468     if (!paused())
469         [m_qtMovie.get() setRate:rate];
470 }
471
472 int MediaPlayerPrivate::dataRate() const
473 {
474     if (!m_qtMovie)
475         return 0;
476     return wkQTMovieDataRate(m_qtMovie.get()); 
477 }
478
479
480 float MediaPlayerPrivate::maxTimeBuffered() const
481 {
482     // rtsp streams are not buffered
483     return m_isStreaming ? 0 : maxTimeLoaded();
484 }
485
486 float MediaPlayerPrivate::maxTimeSeekable() const
487 {
488     // infinite duration means live stream
489     return isinf(duration()) ? 0 : maxTimeLoaded();
490 }
491
492 float MediaPlayerPrivate::maxTimeLoaded() const
493 {
494     if (!m_qtMovie)
495         return 0;
496     return wkQTMovieMaxTimeLoaded(m_qtMovie.get()); 
497 }
498
499 unsigned MediaPlayerPrivate::bytesLoaded() const
500 {
501     float dur = duration();
502     if (!dur)
503         return 0;
504     return totalBytes() * maxTimeLoaded() / dur;
505 }
506
507 bool MediaPlayerPrivate::totalBytesKnown() const
508 {
509     return totalBytes() > 0;
510 }
511
512 unsigned MediaPlayerPrivate::totalBytes() const
513 {
514     if (!m_qtMovie)
515         return 0;
516     return [[m_qtMovie.get() attributeForKey:QTMovieDataSizeAttribute] intValue];
517 }
518
519 void MediaPlayerPrivate::cancelLoad()
520 {
521     // FIXME: Is there a better way to check for this?
522     if (m_networkState < MediaPlayer::Loading || m_networkState == MediaPlayer::Loaded)
523         return;
524     
525     detachQTMovieView();
526     m_qtMovie = nil;
527     
528     updateStates();
529 }
530
531 void MediaPlayerPrivate::updateStates()
532 {
533     MediaPlayer::NetworkState oldNetworkState = m_networkState;
534     MediaPlayer::ReadyState oldReadyState = m_readyState;
535     
536     long loadState = m_qtMovie ? [[m_qtMovie.get() attributeForKey:QTMovieLoadStateAttribute] longValue] : -1;
537     // "Loaded" is reserved for fully buffered movies, never the case when streaming
538     if (loadState >= QTMovieLoadStateComplete && !m_isStreaming) {
539         if (m_networkState < MediaPlayer::Loaded)
540             m_networkState = MediaPlayer::Loaded;
541         m_readyState = MediaPlayer::CanPlayThrough;
542     } else if (loadState >= QTMovieLoadStatePlaythroughOK) {
543         if (m_networkState < MediaPlayer::LoadedFirstFrame && !seeking())
544             m_networkState = MediaPlayer::LoadedFirstFrame;
545         m_readyState = ([m_qtMovie.get() rate] == 0 && m_startedPlaying) ? MediaPlayer::DataUnavailable : MediaPlayer::CanPlayThrough;
546     } else if (loadState >= QTMovieLoadStatePlayable) {
547         if (m_networkState < MediaPlayer::LoadedFirstFrame && !seeking())
548             m_networkState = MediaPlayer::LoadedFirstFrame;
549         m_readyState = ([m_qtMovie.get() rate] == 0 && m_startedPlaying) ? MediaPlayer::DataUnavailable : MediaPlayer::CanPlay;
550     } else if (loadState >= QTMovieLoadStateLoaded) {
551         if (m_networkState < MediaPlayer::LoadedMetaData)
552             m_networkState = MediaPlayer::LoadedMetaData;
553         m_readyState = MediaPlayer::DataUnavailable;
554     } else if (loadState >= 0) {
555         if (m_networkState < MediaPlayer::Loading)
556             m_networkState = MediaPlayer::Loading;
557         m_readyState = MediaPlayer::DataUnavailable;        
558     } else {
559         m_networkState = MediaPlayer::LoadFailed;
560         m_readyState = MediaPlayer::DataUnavailable; 
561     }
562
563     if (seeking())
564         m_readyState = MediaPlayer::DataUnavailable;
565     
566     if (m_networkState != oldNetworkState)
567         m_player->networkStateChanged();
568     if (m_readyState != oldReadyState)
569         m_player->readyStateChanged();
570 }
571
572 void MediaPlayerPrivate::loadStateChanged()
573 {
574     updateStates();
575 }
576
577 void MediaPlayerPrivate::rateChanged()
578 {
579     updateStates();
580 }
581
582 void MediaPlayerPrivate::sizeChanged()
583 {
584 }
585
586 void MediaPlayerPrivate::timeChanged()
587 {
588     m_previousTimeCueTimerFired = -1;
589     updateStates();
590     m_player->timeChanged();
591 }
592
593 void MediaPlayerPrivate::volumeChanged()
594 {
595     m_player->volumeChanged();
596 }
597
598 void MediaPlayerPrivate::didEnd()
599 {
600     m_cuePointTimer.stop();
601     m_startedPlaying = false;
602     updateStates();
603     m_player->timeChanged();
604 }
605
606 void MediaPlayerPrivate::setRect(const IntRect& r) 
607
608     if (m_qtMovieView)
609         [m_qtMovieView.get() setFrame:r];
610 }
611
612 void MediaPlayerPrivate::setVisible(bool b)
613 {
614     if (b)
615         createQTMovieView();
616     else
617         detachQTMovieView();
618 }
619
620 void MediaPlayerPrivate::repaint()
621 {
622     m_player->repaint();
623 }
624
625 void MediaPlayerPrivate::paint(GraphicsContext* context, const IntRect& r)
626 {
627     if (context->paintingDisabled())
628         return;
629     NSView *view = m_qtMovieView.get();
630     if (view == nil)
631         return;
632     [m_objcObserver.get() setDelayCallbacks:YES];
633     BEGIN_BLOCK_OBJC_EXCEPTIONS;
634     context->save();
635     context->translate(r.x(), r.y() + r.height());
636     context->scale(FloatSize(1.0f, -1.0f));
637     IntRect paintRect(IntPoint(0, 0), IntSize(r.width(), r.height()));
638     NSGraphicsContext* newContext = [NSGraphicsContext graphicsContextWithGraphicsPort:context->platformContext() flipped:NO];
639     [view displayRectIgnoringOpacity:paintRect inContext:newContext];
640     context->restore();
641     END_BLOCK_OBJC_EXCEPTIONS;
642     [m_objcObserver.get() setDelayCallbacks:NO];
643 }
644
645 void MediaPlayerPrivate::getSupportedTypes(HashSet<String>& types)
646 {
647     NSArray* fileTypes = [QTMovie movieFileTypes:QTIncludeCommonTypes];
648     int count = [fileTypes count];
649     for (int n = 0; n < count; n++) {
650         CFStringRef ext = reinterpret_cast<CFStringRef>([fileTypes objectAtIndex:n]);
651         RetainPtr<CFStringRef> uti(AdoptCF, UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, ext, NULL));
652         if (!uti)
653             continue;
654         RetainPtr<CFStringRef> mime(AdoptCF, UTTypeCopyPreferredTagWithClass(uti.get(), kUTTagClassMIMEType));
655         if (!mime)
656             continue;
657         types.add(mime.get());
658     }
659
660
661 }
662
663 @implementation WebCoreMovieObserver
664
665 - (id)initWithCallback:(MediaPlayerPrivate *)callback
666 {
667     m_callback = callback;
668     return [super init];
669 }
670
671 - (void)disconnect
672 {
673     [NSObject cancelPreviousPerformRequestsWithTarget:self];
674     m_callback = 0;
675 }
676
677 -(void)repaint
678 {
679     if (m_delayCallbacks)
680         [self performSelector:_cmd withObject:nil afterDelay:0.];
681     else if (m_callback)
682         m_callback->repaint();
683 }
684
685 - (void)loadStateChanged:(NSNotification *)notification
686 {
687     if (m_delayCallbacks)
688         [self performSelector:_cmd withObject:nil afterDelay:0];
689     else
690         m_callback->loadStateChanged();
691 }
692
693 - (void)rateChanged:(NSNotification *)notification
694 {
695     if (m_delayCallbacks)
696         [self performSelector:_cmd withObject:nil afterDelay:0];
697     else
698         m_callback->rateChanged();
699 }
700
701 - (void)sizeChanged:(NSNotification *)notification
702 {
703     if (m_delayCallbacks)
704         [self performSelector:_cmd withObject:nil afterDelay:0];
705     else
706         m_callback->sizeChanged();
707 }
708
709 - (void)timeChanged:(NSNotification *)notification
710 {
711     if (m_delayCallbacks)
712         [self performSelector:_cmd withObject:nil afterDelay:0];
713     else
714         m_callback->timeChanged();
715 }
716
717 - (void)volumeChanged:(NSNotification *)notification
718 {
719     if (m_delayCallbacks)
720         [self performSelector:_cmd withObject:nil afterDelay:0];
721     else
722         m_callback->volumeChanged();
723 }
724
725 - (void)didEnd:(NSNotification *)notification
726 {
727     if (m_delayCallbacks)
728         [self performSelector:_cmd withObject:nil afterDelay:0];
729     else
730         m_callback->didEnd();
731 }
732
733 - (void)setDelayCallbacks:(BOOL)shouldDelay
734 {
735     m_delayCallbacks = shouldDelay;
736 }
737
738 @end
739
740 #endif