b5095508ac38a15685efa9ef589a6e261794158b
[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, QTMediaTypeAttribute, NSString *)
59 SOFT_LINK_POINTER(QTKit, QTMediaTypeBase, NSString *)
60 SOFT_LINK_POINTER(QTKit, QTMediaTypeSound, NSString *)
61 SOFT_LINK_POINTER(QTKit, QTMediaTypeText, NSString *)
62 SOFT_LINK_POINTER(QTKit, QTMediaTypeVideo, NSString *)
63 SOFT_LINK_POINTER(QTKit, QTMovieDataSizeAttribute, NSString *)
64 SOFT_LINK_POINTER(QTKit, QTMovieDidEndNotification, NSString *)
65 SOFT_LINK_POINTER(QTKit, QTMovieHasVideoAttribute, NSString *)
66 SOFT_LINK_POINTER(QTKit, QTMovieIsActiveAttribute, NSString *)
67 SOFT_LINK_POINTER(QTKit, QTMovieLoadStateAttribute, NSString *)
68 SOFT_LINK_POINTER(QTKit, QTMovieLoadStateDidChangeNotification, NSString *)
69 SOFT_LINK_POINTER(QTKit, QTMovieNaturalSizeAttribute, NSString *)
70 SOFT_LINK_POINTER(QTKit, QTMoviePreventExternalURLLinksAttribute, NSString *)
71 SOFT_LINK_POINTER(QTKit, QTMovieRateDidChangeNotification, NSString *)
72 SOFT_LINK_POINTER(QTKit, QTMovieSizeDidChangeNotification, NSString *)
73 SOFT_LINK_POINTER(QTKit, QTMovieTimeDidChangeNotification, NSString *)
74 SOFT_LINK_POINTER(QTKit, QTMovieTimeScaleAttribute, NSString *)
75 SOFT_LINK_POINTER(QTKit, QTMovieURLAttribute, NSString *)
76 SOFT_LINK_POINTER(QTKit, QTMovieVolumeDidChangeNotification, NSString *)
77 SOFT_LINK_POINTER(QTKit, QTSecurityPolicyNoCrossSiteAttribute, NSString *)
78
79 #define QTMovie getQTMovieClass()
80 #define QTMovieView getQTMovieViewClass()
81
82 #define QTMediaTypeAttribute getQTMediaTypeAttribute()
83 #define QTMediaTypeBase getQTMediaTypeBase()
84 #define QTMediaTypeSound getQTMediaTypeSound()
85 #define QTMediaTypeText getQTMediaTypeText()
86 #define QTMediaTypeVideo getQTMediaTypeVideo()
87 #define QTMovieDataSizeAttribute getQTMovieDataSizeAttribute()
88 #define QTMovieDidEndNotification getQTMovieDidEndNotification()
89 #define QTMovieHasVideoAttribute getQTMovieHasVideoAttribute()
90 #define QTMovieIsActiveAttribute getQTMovieIsActiveAttribute()
91 #define QTMovieLoadStateAttribute getQTMovieLoadStateAttribute()
92 #define QTMovieLoadStateDidChangeNotification getQTMovieLoadStateDidChangeNotification()
93 #define QTMovieNaturalSizeAttribute getQTMovieNaturalSizeAttribute()
94 #define QTMoviePreventExternalURLLinksAttribute getQTMoviePreventExternalURLLinksAttribute()
95 #define QTMovieRateDidChangeNotification getQTMovieRateDidChangeNotification()
96 #define QTMovieSizeDidChangeNotification getQTMovieSizeDidChangeNotification()
97 #define QTMovieTimeDidChangeNotification getQTMovieTimeDidChangeNotification()
98 #define QTMovieTimeScaleAttribute getQTMovieTimeScaleAttribute()
99 #define QTMovieURLAttribute getQTMovieURLAttribute()
100 #define QTMovieVolumeDidChangeNotification getQTMovieVolumeDidChangeNotification()
101 #define QTSecurityPolicyNoCrossSiteAttribute getQTSecurityPolicyNoCrossSiteAttribute()
102
103 // Older versions of the QTKit header don't have these constants.
104 #if !defined QTKIT_VERSION_MAX_ALLOWED || QTKIT_VERSION_MAX_ALLOWED <= QTKIT_VERSION_7_0
105 enum {
106     QTMovieLoadStateError = -1L,
107     QTMovieLoadStateLoaded  = 2000L,
108     QTMovieLoadStatePlayable = 10000L,
109     QTMovieLoadStatePlaythroughOK = 20000L,
110     QTMovieLoadStateComplete = 100000L
111 };
112 #endif
113
114 using namespace WebCore;
115 using namespace std;
116
117 @interface WebCoreMovieObserver : NSObject
118 {
119     MediaPlayerPrivate* m_callback;
120     BOOL m_delayCallbacks;
121 }
122 -(id)initWithCallback:(MediaPlayerPrivate*)callback;
123 -(void)disconnect;
124 -(void)repaint;
125 -(void)setDelayCallbacks:(BOOL)shouldDelay;
126 -(void)loadStateChanged:(NSNotification *)notification;
127 -(void)rateChanged:(NSNotification *)notification;
128 -(void)sizeChanged:(NSNotification *)notification;
129 -(void)timeChanged:(NSNotification *)notification;
130 -(void)didEnd:(NSNotification *)notification;
131 @end
132
133 namespace WebCore {
134
135 static const float endPointTimerInterval = 0.020f;
136 static const long minimumQuickTimeVersion = 0x07300000; // 7.3
137     
138 MediaPlayerPrivate::MediaPlayerPrivate(MediaPlayer* player)
139     : m_player(player)
140     , m_objcObserver(AdoptNS, [[WebCoreMovieObserver alloc] initWithCallback:this])
141     , m_seekTo(-1)
142     , m_endTime(numeric_limits<float>::infinity())
143     , m_seekTimer(this, &MediaPlayerPrivate::seekTimerFired)
144     , m_endPointTimer(this, &MediaPlayerPrivate::endPointTimerFired)
145     , m_networkState(MediaPlayer::Empty)
146     , m_readyState(MediaPlayer::DataUnavailable)
147     , m_startedPlaying(false)
148     , m_isStreaming(false)
149 {
150 }
151
152 MediaPlayerPrivate::~MediaPlayerPrivate()
153 {
154     detachQTMovieView();
155
156     [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()];
157     [m_objcObserver.get() disconnect];
158 }
159
160 void MediaPlayerPrivate::createQTMovie(const String& url)
161 {
162     [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()];
163     
164     m_qtMovie = 0;
165     
166     // Disable streaming support for now. 
167     if (url.startsWith("rtsp:"))
168         return;
169     
170     NSDictionary* movieAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
171                                      KURL(url.deprecatedString()).getNSURL(), QTMovieURLAttribute,
172                                      [NSNumber numberWithBool:YES], QTMoviePreventExternalURLLinksAttribute,
173                                      [NSNumber numberWithBool:YES], QTSecurityPolicyNoCrossSiteAttribute,
174                                      nil];
175     
176     NSError* error = nil;
177     m_qtMovie.adoptNS([[QTMovie alloc] initWithAttributes:movieAttributes error:&error]);
178     
179     // FIXME: Find a proper way to detect streaming content.
180     m_isStreaming = url.startsWith("rtsp:");
181     
182     if (!m_qtMovie)
183         return;
184     
185     [m_qtMovie.get() setVolume:m_player->volume()];
186     
187     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
188                                              selector:@selector(loadStateChanged:) 
189                                                  name:QTMovieLoadStateDidChangeNotification 
190                                                object:m_qtMovie.get()];
191     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
192                                              selector:@selector(rateChanged:) 
193                                                  name:QTMovieRateDidChangeNotification 
194                                                object:m_qtMovie.get()];
195     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
196                                              selector:@selector(sizeChanged:) 
197                                                  name:QTMovieSizeDidChangeNotification 
198                                                object:m_qtMovie.get()];
199     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
200                                              selector:@selector(timeChanged:) 
201                                                  name:QTMovieTimeDidChangeNotification 
202                                                object:m_qtMovie.get()];
203     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
204                                              selector:@selector(didEnd:) 
205                                                  name:QTMovieDidEndNotification 
206                                                object:m_qtMovie.get()];
207 }
208
209 static void mainThreadSetNeedsDisplay(id self, SEL _cmd)
210 {
211     id movieView = [self superview];
212     ASSERT(!movieView || [movieView isKindOfClass:[QTMovieView class]]);
213     if (!movieView || ![movieView isKindOfClass:[QTMovieView class]])
214         return;
215
216     WebCoreMovieObserver* delegate = [movieView delegate];
217     ASSERT(!delegate || [delegate isKindOfClass:[WebCoreMovieObserver class]]);
218     if (!delegate || ![delegate isKindOfClass:[WebCoreMovieObserver class]])
219         return;
220
221     [delegate repaint];
222 }
223
224 void MediaPlayerPrivate::createQTMovieView()
225 {
226     detachQTMovieView();
227
228     if (!m_player->m_parentWidget || !m_qtMovie)
229         return;
230
231     static bool addedCustomMethods = false;
232     if (!addedCustomMethods) {
233         Class QTMovieContentViewClass = NSClassFromString(@"QTMovieContentView");
234         ASSERT(QTMovieContentViewClass);
235
236         Method mainThreadSetNeedsDisplayMethod = class_getInstanceMethod(QTMovieContentViewClass, @selector(_mainThreadSetNeedsDisplay));
237         ASSERT(mainThreadSetNeedsDisplayMethod);
238
239         method_setImplementation(mainThreadSetNeedsDisplayMethod, reinterpret_cast<IMP>(mainThreadSetNeedsDisplay));
240         addedCustomMethods = true;
241     }
242
243     m_qtMovieView.adoptNS([[QTMovieView alloc] init]);
244     setRect(m_player->rect());
245     NSView* parentView = static_cast<ScrollView*>(m_player->m_parentWidget)->getDocumentView();
246     [parentView addSubview:m_qtMovieView.get()];
247 #ifdef BUILDING_ON_TIGER
248     // setDelegate: isn't a public call in Tiger, so use performSelector to keep the compiler happy
249     [m_qtMovieView.get() performSelector:@selector(setDelegate:) withObject:m_objcObserver.get()];    
250 #else
251     [m_qtMovieView.get() setDelegate:m_objcObserver.get()];
252 #endif
253     [m_qtMovieView.get() setMovie:m_qtMovie.get()];
254     [m_qtMovieView.get() setControllerVisible:NO];
255     [m_qtMovieView.get() setPreservesAspectRatio:NO];
256     // the area not covered by video should be transparent
257     [m_qtMovieView.get() setFillColor:[NSColor clearColor]];
258     wkQTMovieViewSetDrawSynchronously(m_qtMovieView.get(), YES);
259 }
260
261 void MediaPlayerPrivate::detachQTMovieView()
262 {
263     if (m_qtMovieView) {
264 #ifdef BUILDING_ON_TIGER
265         // setDelegate: isn't a public call in Tiger, so use performSelector to keep the compiler happy
266         [m_qtMovieView.get() performSelector:@selector(setDelegate:) withObject:nil];    
267 #else
268         [m_qtMovieView.get() setDelegate:nil];
269 #endif
270         [m_qtMovieView.get() removeFromSuperview];
271         m_qtMovieView = nil;
272     }
273 }
274
275 QTTime MediaPlayerPrivate::createQTTime(float time) const
276 {
277     if (!m_qtMovie)
278         return QTMakeTime(0, 600);
279     long timeScale = [[m_qtMovie.get() attributeForKey:QTMovieTimeScaleAttribute] longValue];
280     return QTMakeTime(time * timeScale, timeScale);
281 }
282
283 void MediaPlayerPrivate::load(const String& url)
284 {
285     if (m_networkState != MediaPlayer::Loading) {
286         m_networkState = MediaPlayer::Loading;
287         m_player->networkStateChanged();
288     }
289     if (m_readyState != MediaPlayer::DataUnavailable) {
290         m_readyState = MediaPlayer::DataUnavailable;
291         m_player->readyStateChanged();
292     }
293     cancelSeek();
294     m_endPointTimer.stop();
295     
296     [m_objcObserver.get() setDelayCallbacks:YES];
297
298     createQTMovie(url);
299     if (m_player->visible())
300         createQTMovieView();
301
302     [m_objcObserver.get() loadStateChanged:nil];
303     [m_objcObserver.get() setDelayCallbacks:NO];
304 }
305
306 void MediaPlayerPrivate::play()
307 {
308     if (!m_qtMovie)
309         return;
310     m_startedPlaying = true;
311     [m_objcObserver.get() setDelayCallbacks:YES];
312     [m_qtMovie.get() setRate:m_player->rate()];
313     [m_objcObserver.get() setDelayCallbacks:NO];
314     startEndPointTimerIfNeeded();
315 }
316
317 void MediaPlayerPrivate::pause()
318 {
319     if (!m_qtMovie)
320         return;
321     m_startedPlaying = false;
322     [m_objcObserver.get() setDelayCallbacks:YES];
323     [m_qtMovie.get() stop];
324     [m_objcObserver.get() setDelayCallbacks:NO];
325     m_endPointTimer.stop();
326 }
327
328 float MediaPlayerPrivate::duration() const
329 {
330     if (!m_qtMovie)
331         return 0;
332     QTTime time = [m_qtMovie.get() duration];
333     if (time.flags == kQTTimeIsIndefinite)
334         return numeric_limits<float>::infinity();
335     return static_cast<float>(time.timeValue) / time.timeScale;
336 }
337
338 float MediaPlayerPrivate::currentTime() const
339 {
340     if (!m_qtMovie)
341         return 0;
342     QTTime time = [m_qtMovie.get() currentTime];
343     return min(static_cast<float>(time.timeValue) / time.timeScale, m_endTime);
344 }
345
346 void MediaPlayerPrivate::seek(float time)
347 {
348     cancelSeek();
349     
350     if (!m_qtMovie)
351         return;
352     
353     if (time > duration())
354         time = duration();
355     
356     m_seekTo = time;
357     if (maxTimeLoaded() >= m_seekTo)
358         doSeek();
359     else 
360         m_seekTimer.start(0, 0.5f);
361 }
362
363 void MediaPlayerPrivate::doSeek() 
364 {
365     QTTime qttime = createQTTime(m_seekTo);
366     // setCurrentTime generates several event callbacks, update afterwards
367     [m_objcObserver.get() setDelayCallbacks:YES];
368     float oldRate = [m_qtMovie.get() rate];
369     [m_qtMovie.get() setRate:0];
370     [m_qtMovie.get() setCurrentTime:qttime];
371     float timeAfterSeek = currentTime();
372     // restore playback only if not at end, othewise QTMovie will loop
373     if (timeAfterSeek < duration() && timeAfterSeek < m_endTime)
374         [m_qtMovie.get() setRate:oldRate];
375     cancelSeek();
376     [m_objcObserver.get() setDelayCallbacks:NO];
377 }
378
379 void MediaPlayerPrivate::cancelSeek()
380 {
381     m_seekTo = -1;
382     m_seekTimer.stop();
383 }
384
385 void MediaPlayerPrivate::seekTimerFired(Timer<MediaPlayerPrivate>*)
386 {        
387     if (!m_qtMovie || !seeking() || currentTime() == m_seekTo) {
388         cancelSeek();
389         updateStates();
390         m_player->timeChanged(); 
391         return;
392     } 
393     
394     if (maxTimeLoaded() >= m_seekTo)
395         doSeek();
396     else {
397         MediaPlayer::NetworkState state = networkState();
398         if (state == MediaPlayer::Empty || state == MediaPlayer::Loaded) {
399             cancelSeek();
400             updateStates();
401             m_player->timeChanged();
402         }
403     }
404 }
405
406 void MediaPlayerPrivate::setEndTime(float time)
407 {
408     m_endTime = time;
409     startEndPointTimerIfNeeded();
410 }
411
412 void MediaPlayerPrivate::startEndPointTimerIfNeeded()
413 {
414     if (m_endTime < duration() && m_startedPlaying && !m_endPointTimer.isActive())
415         m_endPointTimer.startRepeating(endPointTimerInterval);
416 }
417
418 void MediaPlayerPrivate::endPointTimerFired(Timer<MediaPlayerPrivate>*)
419 {
420     float time = currentTime();
421     
422     // just do end for now
423     if (time >= m_endTime) {
424         pause();
425         didEnd();
426     }
427 }
428
429 bool MediaPlayerPrivate::paused() const
430 {
431     if (!m_qtMovie)
432         return true;
433     return [m_qtMovie.get() rate] == 0;
434 }
435
436 bool MediaPlayerPrivate::seeking() const
437 {
438     if (!m_qtMovie)
439         return false;
440     return m_seekTo >= 0;
441 }
442
443 IntSize MediaPlayerPrivate::naturalSize() const
444 {
445     if (!m_qtMovie)
446         return IntSize();
447     return IntSize([[m_qtMovie.get() attributeForKey:QTMovieNaturalSizeAttribute] sizeValue]);
448 }
449
450 bool MediaPlayerPrivate::hasVideo() const
451 {
452     if (!m_qtMovie)
453         return false;
454     return [[m_qtMovie.get() attributeForKey:QTMovieHasVideoAttribute] boolValue];
455 }
456
457 void MediaPlayerPrivate::setVolume(float volume)
458 {
459     if (!m_qtMovie)
460         return;
461     [m_qtMovie.get() setVolume:volume];  
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] : static_cast<long>(QTMovieLoadStateError);
537     
538     if (loadState >= QTMovieLoadStateLoaded && m_networkState < MediaPlayer::LoadedMetaData) {
539         unsigned enabledTrackCount;
540         disableUnsupportedTracks(enabledTrackCount);
541         // FIXME: We should differentiate between load errors and decode errors <rdar://problem/5605692>
542         if (!enabledTrackCount)
543             loadState = QTMovieLoadStateError;
544     }
545     
546     // "Loaded" is reserved for fully buffered movies, never the case when streaming
547     if (loadState >= QTMovieLoadStateComplete && !m_isStreaming) {
548         if (m_networkState < MediaPlayer::Loaded)
549             m_networkState = MediaPlayer::Loaded;
550         m_readyState = MediaPlayer::CanPlayThrough;
551     } else if (loadState >= QTMovieLoadStatePlaythroughOK) {
552         if (m_networkState < MediaPlayer::LoadedFirstFrame && !seeking())
553             m_networkState = MediaPlayer::LoadedFirstFrame;
554         m_readyState = ([m_qtMovie.get() rate] == 0 && m_startedPlaying) ? MediaPlayer::DataUnavailable : MediaPlayer::CanPlayThrough;
555     } else if (loadState >= QTMovieLoadStatePlayable) {
556         if (m_networkState < MediaPlayer::LoadedFirstFrame && !seeking())
557             m_networkState = MediaPlayer::LoadedFirstFrame;
558         m_readyState = ([m_qtMovie.get() rate] == 0 && m_startedPlaying) ? MediaPlayer::DataUnavailable : MediaPlayer::CanPlay;
559     } else if (loadState >= QTMovieLoadStateLoaded) {
560         if (m_networkState < MediaPlayer::LoadedMetaData)
561             m_networkState = MediaPlayer::LoadedMetaData;
562         m_readyState = MediaPlayer::DataUnavailable;
563     } else if (loadState > QTMovieLoadStateError) {
564         if (m_networkState < MediaPlayer::Loading)
565             m_networkState = MediaPlayer::Loading;
566         m_readyState = MediaPlayer::DataUnavailable;        
567     } else {
568         m_networkState = MediaPlayer::LoadFailed;
569         m_readyState = MediaPlayer::DataUnavailable; 
570     }
571
572     if (seeking())
573         m_readyState = MediaPlayer::DataUnavailable;
574     
575     if (m_networkState != oldNetworkState)
576         m_player->networkStateChanged();
577     if (m_readyState != oldReadyState)
578         m_player->readyStateChanged();
579 }
580
581 void MediaPlayerPrivate::loadStateChanged()
582 {
583     updateStates();
584 }
585
586 void MediaPlayerPrivate::rateChanged()
587 {
588     updateStates();
589 }
590
591 void MediaPlayerPrivate::sizeChanged()
592 {
593 }
594
595 void MediaPlayerPrivate::timeChanged()
596 {
597     updateStates();
598     m_player->timeChanged();
599 }
600
601 void MediaPlayerPrivate::didEnd()
602 {
603     m_endPointTimer.stop();
604     m_startedPlaying = false;
605     updateStates();
606     m_player->timeChanged();
607 }
608
609 void MediaPlayerPrivate::setRect(const IntRect& r) 
610
611     if (!m_qtMovieView) 
612         return;
613     // We don't really need the QTMovieView in any specific location so let's just get it out of the way
614     // where it won't intercept events or try to bring up the context menu.
615     IntRect farAwayButCorrectSize(r);
616     farAwayButCorrectSize.move(-1000000, -1000000);
617     [m_qtMovieView.get() setFrame:farAwayButCorrectSize];
618 }
619
620 void MediaPlayerPrivate::setVisible(bool b)
621 {
622     if (b)
623         createQTMovieView();
624     else
625         detachQTMovieView();
626 }
627
628 void MediaPlayerPrivate::repaint()
629 {
630     m_player->repaint();
631 }
632
633 void MediaPlayerPrivate::paint(GraphicsContext* context, const IntRect& r)
634 {
635     if (context->paintingDisabled())
636         return;
637     NSView *view = m_qtMovieView.get();
638     if (view == nil)
639         return;
640     [m_objcObserver.get() setDelayCallbacks:YES];
641     BEGIN_BLOCK_OBJC_EXCEPTIONS;
642     context->save();
643     context->translate(r.x(), r.y() + r.height());
644     context->scale(FloatSize(1.0f, -1.0f));
645     IntRect paintRect(IntPoint(0, 0), IntSize(r.width(), r.height()));
646     NSGraphicsContext* newContext = [NSGraphicsContext graphicsContextWithGraphicsPort:context->platformContext() flipped:NO];
647     [view displayRectIgnoringOpacity:paintRect inContext:newContext];
648     context->restore();
649     END_BLOCK_OBJC_EXCEPTIONS;
650     [m_objcObserver.get() setDelayCallbacks:NO];
651 }
652
653 void MediaPlayerPrivate::getSupportedTypes(HashSet<String>& types)
654 {
655     NSArray* fileTypes = [QTMovie movieFileTypes:QTIncludeCommonTypes];
656     int count = [fileTypes count];
657     for (int n = 0; n < count; n++) {
658         CFStringRef ext = reinterpret_cast<CFStringRef>([fileTypes objectAtIndex:n]);
659         RetainPtr<CFStringRef> uti(AdoptCF, UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, ext, NULL));
660         if (!uti)
661             continue;
662         RetainPtr<CFStringRef> mime(AdoptCF, UTTypeCopyPreferredTagWithClass(uti.get(), kUTTagClassMIMEType));
663         if (!mime)
664             continue;
665         types.add(mime.get());
666     }
667
668     
669 bool MediaPlayerPrivate::isAvailable()
670 {
671     SInt32 version;
672     OSErr result;
673     // This Carbon API is available in 64 bit too
674     result = Gestalt(gestaltQuickTime, &version);
675     if (result != noErr) {
676         LOG_ERROR("No QuickTime available. Disabling <video> and <audio> support.");
677         return false;
678     }
679     if (version < minimumQuickTimeVersion) {
680         LOG_ERROR("QuickTime version %x detected, at least %x required. Disabling <video> and <audio> support.", version, minimumQuickTimeVersion);
681         return false;
682     }
683     return true;
684 }
685     
686 void MediaPlayerPrivate::disableUnsupportedTracks(unsigned& enabledTrackCount)
687 {
688     if (!m_qtMovie) {
689         enabledTrackCount = 0;
690         return;
691     }
692     
693     static HashSet<String>* allowedTrackTypes = 0;
694     if (!allowedTrackTypes) {
695         allowedTrackTypes = new HashSet<String>;
696         allowedTrackTypes->add(QTMediaTypeVideo);
697         allowedTrackTypes->add(QTMediaTypeSound);
698         allowedTrackTypes->add(QTMediaTypeText);
699         allowedTrackTypes->add(QTMediaTypeBase);
700         allowedTrackTypes->add("clcp");
701         allowedTrackTypes->add("sbtl");
702     }
703     
704     NSArray *tracks = [m_qtMovie.get() tracks];
705     
706     unsigned trackCount = [tracks count];
707     enabledTrackCount = trackCount;
708     for (unsigned trackIndex = 0; trackIndex < trackCount; trackIndex++) {
709         // Grab the track at the current index. If there isn't one there, then
710         // we can move onto the next one.
711         QTTrack *track = [tracks objectAtIndex:trackIndex];
712         if (!track)
713             continue;
714         
715         // Check to see if the track is disabled already, we should move along.
716         // We don't need to re-disable it.
717         if (![track isEnabled])
718             continue;
719         
720         // Grab the track's media. We're going to check to see if we need to
721         // disable the tracks. They could be unsupported. <rdar://problem/4983892>
722         QTMedia *trackMedia = [track media];
723         if (!trackMedia)
724             continue;
725         
726         // Grab the media type for this track.
727         NSString *mediaType = [trackMedia attributeForKey:QTMediaTypeAttribute];
728         if (!mediaType)
729             continue;
730         
731         // Test whether the media type is in our white list.
732         if (!allowedTrackTypes->contains(mediaType)) {
733             // If this track type is not allowed, then we need to disable it.
734             [track setEnabled:NO];
735             --enabledTrackCount;
736         }
737         
738         // Disable chapter tracks. These are most likely to lead to trouble, as
739         // they will be composited under the video tracks, forcing QT to do extra
740         // work.
741         QTTrack *chapterTrack = [track performSelector:@selector(chapterlist)];
742         if (!chapterTrack)
743             continue;
744         
745         // Try to grab the media for the track.
746         QTMedia *chapterMedia = [chapterTrack media];
747         if (!chapterMedia)
748             continue;
749         
750         // Grab the media type for this track.
751         id chapterMediaType = [chapterMedia attributeForKey:QTMediaTypeAttribute];
752         if (!chapterMediaType)
753             continue;
754         
755         // Check to see if the track is a video track. We don't care about
756         // other non-video tracks.
757         if (![chapterMediaType isEqual:QTMediaTypeVideo])
758             continue;
759         
760         // Check to see if the track is already disabled. If it is, we
761         // should move along.
762         if (![chapterTrack isEnabled])
763             continue;
764         
765         // Disable the evil, evil track.
766         [chapterTrack setEnabled:NO];
767         --enabledTrackCount;
768     }
769 }
770
771 }
772
773 @implementation WebCoreMovieObserver
774
775 - (id)initWithCallback:(MediaPlayerPrivate *)callback
776 {
777     m_callback = callback;
778     return [super init];
779 }
780
781 - (void)disconnect
782 {
783     [NSObject cancelPreviousPerformRequestsWithTarget:self];
784     m_callback = 0;
785 }
786
787 -(void)repaint
788 {
789     if (m_delayCallbacks)
790         [self performSelector:_cmd withObject:nil afterDelay:0.];
791     else if (m_callback)
792         m_callback->repaint();
793 }
794
795 - (void)loadStateChanged:(NSNotification *)notification
796 {
797     if (m_delayCallbacks)
798         [self performSelector:_cmd withObject:nil afterDelay:0];
799     else
800         m_callback->loadStateChanged();
801 }
802
803 - (void)rateChanged:(NSNotification *)notification
804 {
805     if (m_delayCallbacks)
806         [self performSelector:_cmd withObject:nil afterDelay:0];
807     else
808         m_callback->rateChanged();
809 }
810
811 - (void)sizeChanged:(NSNotification *)notification
812 {
813     if (m_delayCallbacks)
814         [self performSelector:_cmd withObject:nil afterDelay:0];
815     else
816         m_callback->sizeChanged();
817 }
818
819 - (void)timeChanged:(NSNotification *)notification
820 {
821     if (m_delayCallbacks)
822         [self performSelector:_cmd withObject:nil afterDelay:0];
823     else
824         m_callback->timeChanged();
825 }
826
827 - (void)didEnd:(NSNotification *)notification
828 {
829     if (m_delayCallbacks)
830         [self performSelector:_cmd withObject:nil afterDelay:0];
831     else
832         m_callback->didEnd();
833 }
834
835 - (void)setDelayCallbacks:(BOOL)shouldDelay
836 {
837     m_delayCallbacks = shouldDelay;
838 }
839
840 @end
841
842 #endif