bb23eadb9136df459ace09239d3ba26fdf3b968a
[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, <rdar://problem/5693967>
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 = MediaPlayer::CanPlayThrough;
555     } else if (loadState >= QTMovieLoadStatePlayable) {
556         if (m_networkState < MediaPlayer::LoadedFirstFrame && !seeking())
557             m_networkState = MediaPlayer::LoadedFirstFrame;
558         // FIXME: This might not work correctly in streaming case, <rdar://problem/5693967>
559         m_readyState = currentTime() < maxTimeLoaded() ? MediaPlayer::CanPlay : MediaPlayer::DataUnavailable;
560     } else if (loadState >= QTMovieLoadStateLoaded) {
561         if (m_networkState < MediaPlayer::LoadedMetaData)
562             m_networkState = MediaPlayer::LoadedMetaData;
563         m_readyState = MediaPlayer::DataUnavailable;
564     } else if (loadState > QTMovieLoadStateError) {
565         if (m_networkState < MediaPlayer::Loading)
566             m_networkState = MediaPlayer::Loading;
567         m_readyState = MediaPlayer::DataUnavailable;        
568     } else {
569         m_networkState = MediaPlayer::LoadFailed;
570         m_readyState = MediaPlayer::DataUnavailable; 
571     }
572
573     if (seeking())
574         m_readyState = MediaPlayer::DataUnavailable;
575     
576     if (m_networkState != oldNetworkState)
577         m_player->networkStateChanged();
578     if (m_readyState != oldReadyState)
579         m_player->readyStateChanged();
580 }
581
582 void MediaPlayerPrivate::loadStateChanged()
583 {
584     updateStates();
585 }
586
587 void MediaPlayerPrivate::rateChanged()
588 {
589     updateStates();
590 }
591
592 void MediaPlayerPrivate::sizeChanged()
593 {
594 }
595
596 void MediaPlayerPrivate::timeChanged()
597 {
598     updateStates();
599     m_player->timeChanged();
600 }
601
602 void MediaPlayerPrivate::didEnd()
603 {
604     m_endPointTimer.stop();
605     m_startedPlaying = false;
606     updateStates();
607     m_player->timeChanged();
608 }
609
610 void MediaPlayerPrivate::setRect(const IntRect& r) 
611
612     if (!m_qtMovieView) 
613         return;
614     // We don't really need the QTMovieView in any specific location so let's just get it out of the way
615     // where it won't intercept events or try to bring up the context menu.
616     IntRect farAwayButCorrectSize(r);
617     farAwayButCorrectSize.move(-1000000, -1000000);
618     [m_qtMovieView.get() setFrame:farAwayButCorrectSize];
619 }
620
621 void MediaPlayerPrivate::setVisible(bool b)
622 {
623     if (b)
624         createQTMovieView();
625     else
626         detachQTMovieView();
627 }
628
629 void MediaPlayerPrivate::repaint()
630 {
631     m_player->repaint();
632 }
633
634 void MediaPlayerPrivate::paint(GraphicsContext* context, const IntRect& r)
635 {
636     if (context->paintingDisabled())
637         return;
638     NSView *view = m_qtMovieView.get();
639     if (view == nil)
640         return;
641     [m_objcObserver.get() setDelayCallbacks:YES];
642     BEGIN_BLOCK_OBJC_EXCEPTIONS;
643     context->save();
644     context->translate(r.x(), r.y() + r.height());
645     context->scale(FloatSize(1.0f, -1.0f));
646     IntRect paintRect(IntPoint(0, 0), IntSize(r.width(), r.height()));
647     NSGraphicsContext* newContext = [NSGraphicsContext graphicsContextWithGraphicsPort:context->platformContext() flipped:NO];
648     [view displayRectIgnoringOpacity:paintRect inContext:newContext];
649     context->restore();
650     END_BLOCK_OBJC_EXCEPTIONS;
651     [m_objcObserver.get() setDelayCallbacks:NO];
652 }
653
654 void MediaPlayerPrivate::getSupportedTypes(HashSet<String>& types)
655 {
656     NSArray* fileTypes = [QTMovie movieFileTypes:QTIncludeCommonTypes];
657     int count = [fileTypes count];
658     for (int n = 0; n < count; n++) {
659         CFStringRef ext = reinterpret_cast<CFStringRef>([fileTypes objectAtIndex:n]);
660         RetainPtr<CFStringRef> uti(AdoptCF, UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, ext, NULL));
661         if (!uti)
662             continue;
663         RetainPtr<CFStringRef> mime(AdoptCF, UTTypeCopyPreferredTagWithClass(uti.get(), kUTTagClassMIMEType));
664         if (!mime)
665             continue;
666         types.add(mime.get());
667     }
668
669     
670 bool MediaPlayerPrivate::isAvailable()
671 {
672     SInt32 version;
673     OSErr result;
674     // This Carbon API is available in 64 bit too
675     result = Gestalt(gestaltQuickTime, &version);
676     if (result != noErr) {
677         LOG_ERROR("No QuickTime available. Disabling <video> and <audio> support.");
678         return false;
679     }
680     if (version < minimumQuickTimeVersion) {
681         LOG_ERROR("QuickTime version %x detected, at least %x required. Disabling <video> and <audio> support.", version, minimumQuickTimeVersion);
682         return false;
683     }
684     return true;
685 }
686     
687 void MediaPlayerPrivate::disableUnsupportedTracks(unsigned& enabledTrackCount)
688 {
689     if (!m_qtMovie) {
690         enabledTrackCount = 0;
691         return;
692     }
693     
694     static HashSet<String>* allowedTrackTypes = 0;
695     if (!allowedTrackTypes) {
696         allowedTrackTypes = new HashSet<String>;
697         allowedTrackTypes->add(QTMediaTypeVideo);
698         allowedTrackTypes->add(QTMediaTypeSound);
699         allowedTrackTypes->add(QTMediaTypeText);
700         allowedTrackTypes->add(QTMediaTypeBase);
701         allowedTrackTypes->add("clcp");
702         allowedTrackTypes->add("sbtl");
703     }
704     
705     NSArray *tracks = [m_qtMovie.get() tracks];
706     
707     unsigned trackCount = [tracks count];
708     enabledTrackCount = trackCount;
709     for (unsigned trackIndex = 0; trackIndex < trackCount; trackIndex++) {
710         // Grab the track at the current index. If there isn't one there, then
711         // we can move onto the next one.
712         QTTrack *track = [tracks objectAtIndex:trackIndex];
713         if (!track)
714             continue;
715         
716         // Check to see if the track is disabled already, we should move along.
717         // We don't need to re-disable it.
718         if (![track isEnabled])
719             continue;
720         
721         // Grab the track's media. We're going to check to see if we need to
722         // disable the tracks. They could be unsupported. <rdar://problem/4983892>
723         QTMedia *trackMedia = [track media];
724         if (!trackMedia)
725             continue;
726         
727         // Grab the media type for this track.
728         NSString *mediaType = [trackMedia attributeForKey:QTMediaTypeAttribute];
729         if (!mediaType)
730             continue;
731         
732         // Test whether the media type is in our white list.
733         if (!allowedTrackTypes->contains(mediaType)) {
734             // If this track type is not allowed, then we need to disable it.
735             [track setEnabled:NO];
736             --enabledTrackCount;
737         }
738         
739         // Disable chapter tracks. These are most likely to lead to trouble, as
740         // they will be composited under the video tracks, forcing QT to do extra
741         // work.
742         QTTrack *chapterTrack = [track performSelector:@selector(chapterlist)];
743         if (!chapterTrack)
744             continue;
745         
746         // Try to grab the media for the track.
747         QTMedia *chapterMedia = [chapterTrack media];
748         if (!chapterMedia)
749             continue;
750         
751         // Grab the media type for this track.
752         id chapterMediaType = [chapterMedia attributeForKey:QTMediaTypeAttribute];
753         if (!chapterMediaType)
754             continue;
755         
756         // Check to see if the track is a video track. We don't care about
757         // other non-video tracks.
758         if (![chapterMediaType isEqual:QTMediaTypeVideo])
759             continue;
760         
761         // Check to see if the track is already disabled. If it is, we
762         // should move along.
763         if (![chapterTrack isEnabled])
764             continue;
765         
766         // Disable the evil, evil track.
767         [chapterTrack setEnabled:NO];
768         --enabledTrackCount;
769     }
770 }
771
772 }
773
774 @implementation WebCoreMovieObserver
775
776 - (id)initWithCallback:(MediaPlayerPrivate *)callback
777 {
778     m_callback = callback;
779     return [super init];
780 }
781
782 - (void)disconnect
783 {
784     [NSObject cancelPreviousPerformRequestsWithTarget:self];
785     m_callback = 0;
786 }
787
788 -(void)repaint
789 {
790     if (m_delayCallbacks)
791         [self performSelector:_cmd withObject:nil afterDelay:0.];
792     else if (m_callback)
793         m_callback->repaint();
794 }
795
796 - (void)loadStateChanged:(NSNotification *)notification
797 {
798     if (m_delayCallbacks)
799         [self performSelector:_cmd withObject:nil afterDelay:0];
800     else
801         m_callback->loadStateChanged();
802 }
803
804 - (void)rateChanged:(NSNotification *)notification
805 {
806     if (m_delayCallbacks)
807         [self performSelector:_cmd withObject:nil afterDelay:0];
808     else
809         m_callback->rateChanged();
810 }
811
812 - (void)sizeChanged:(NSNotification *)notification
813 {
814     if (m_delayCallbacks)
815         [self performSelector:_cmd withObject:nil afterDelay:0];
816     else
817         m_callback->sizeChanged();
818 }
819
820 - (void)timeChanged:(NSNotification *)notification
821 {
822     if (m_delayCallbacks)
823         [self performSelector:_cmd withObject:nil afterDelay:0];
824     else
825         m_callback->timeChanged();
826 }
827
828 - (void)didEnd:(NSNotification *)notification
829 {
830     if (m_delayCallbacks)
831         [self performSelector:_cmd withObject:nil afterDelay:0];
832     else
833         m_callback->didEnd();
834 }
835
836 - (void)setDelayCallbacks:(BOOL)shouldDelay
837 {
838     m_delayCallbacks = shouldDelay;
839 }
840
841 @end
842
843 #endif