bc96dbb556aed9bfaa57262c5f85f024b39acd5d
[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 SOFT_LINK_FRAMEWORK(QTKit)
43
44 SOFT_LINK(QTKit, QTMakeTime, QTTime, (long long timeValue, long timeScale), (timeValue, timeScale))
45
46 SOFT_LINK_CLASS(QTKit, QTMovie)
47 SOFT_LINK_CLASS(QTKit, QTMovieView)
48
49 SOFT_LINK_POINTER(QTKit, QTMovieDataSizeAttribute, NSString *)
50 SOFT_LINK_POINTER(QTKit, QTMovieDidEndNotification, NSString *)
51 SOFT_LINK_POINTER(QTKit, QTMovieHasVideoAttribute, NSString *)
52 SOFT_LINK_POINTER(QTKit, QTMovieLoadStateAttribute, NSString *)
53 SOFT_LINK_POINTER(QTKit, QTMovieLoadStateDidChangeNotification, NSString *)
54 SOFT_LINK_POINTER(QTKit, QTMovieNaturalSizeAttribute, NSString *)
55 SOFT_LINK_POINTER(QTKit, QTMovieRateDidChangeNotification, NSString *)
56 SOFT_LINK_POINTER(QTKit, QTMovieSizeDidChangeNotification, NSString *)
57 SOFT_LINK_POINTER(QTKit, QTMovieTimeDidChangeNotification, NSString *)
58 SOFT_LINK_POINTER(QTKit, QTMovieTimeScaleAttribute, NSString *)
59 SOFT_LINK_POINTER(QTKit, QTMovieVolumeDidChangeNotification, NSString *)
60
61 #define QTMovie getQTMovieClass()
62 #define QTMovieView getQTMovieViewClass()
63
64 #define QTMovieDataSizeAttribute getQTMovieDataSizeAttribute()
65 #define QTMovieDidEndNotification getQTMovieDidEndNotification()
66 #define QTMovieHasVideoAttribute getQTMovieHasVideoAttribute()
67 #define QTMovieLoadStateAttribute getQTMovieLoadStateAttribute()
68 #define QTMovieLoadStateDidChangeNotification getQTMovieLoadStateDidChangeNotification()
69 #define QTMovieNaturalSizeAttribute getQTMovieNaturalSizeAttribute()
70 #define QTMovieRateDidChangeNotification getQTMovieRateDidChangeNotification()
71 #define QTMovieSizeDidChangeNotification getQTMovieSizeDidChangeNotification()
72 #define QTMovieTimeDidChangeNotification getQTMovieTimeDidChangeNotification()
73 #define QTMovieTimeScaleAttribute getQTMovieTimeScaleAttribute()
74 #define QTMovieVolumeDidChangeNotification getQTMovieVolumeDidChangeNotification()
75
76 using namespace WebCore;
77 using namespace std;
78
79 @interface WebCoreMovieObserver : NSObject
80 {
81     MediaPlayerPrivate* m_callback;
82     BOOL m_delayCallbacks;
83 }
84 -(id)initWithCallback:(MediaPlayerPrivate*)callback;
85 -(void)disconnect;
86 -(void)setDelayCallbacks:(BOOL)shouldDelay;
87 -(void)loadStateChanged:(NSNotification *)notification;
88 -(void)rateChanged:(NSNotification *)notification;
89 -(void)sizeChanged:(NSNotification *)notification;
90 -(void)timeChanged:(NSNotification *)notification;
91 -(void)volumeChanged:(NSNotification *)notification;
92 -(void)didEnd:(NSNotification *)notification;
93 @end
94
95 namespace WebCore {
96
97 static const float cuePointTimerInterval = 0.020f;
98     
99 MediaPlayerPrivate::MediaPlayerPrivate(MediaPlayer* player)
100     : m_player(player)
101     , m_objcObserver(AdoptNS, [[WebCoreMovieObserver alloc] initWithCallback:this])
102     , m_seekTo(-1)
103     , m_endTime(numeric_limits<float>::infinity())
104     , m_seekTimer(this, &MediaPlayerPrivate::seekTimerFired)
105     , m_cuePointTimer(this, &MediaPlayerPrivate::cuePointTimerFired)
106     , m_previousTimeCueTimerFired(0)
107     , m_networkState(MediaPlayer::Empty)
108     , m_readyState(MediaPlayer::DataUnavailable)
109     , m_startedPlaying(false)
110     , m_isStreaming(false)
111 {
112 }
113
114 MediaPlayerPrivate::~MediaPlayerPrivate()
115 {
116     if (m_qtMovieView)
117         [m_qtMovieView.get() removeFromSuperview];
118     [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()];
119     [m_objcObserver.get() disconnect];
120 }
121
122 void MediaPlayerPrivate::createQTMovie(const String& url)
123 {
124     [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()];
125     
126     NSError* error = nil;
127     m_qtMovie.adoptNS([[QTMovie alloc] initWithURL:KURL(url.deprecatedString()).getNSURL() error:&error]);
128     
129     // FIXME: Find a proper way to detect streaming content.
130     m_isStreaming = url.startsWith("rtsp:");
131     
132     if (!m_qtMovie)
133         return;
134     
135     [m_qtMovie.get() setVolume:m_player->volume()];
136     [m_qtMovie.get() setMuted:m_player->muted()];
137     
138     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
139                                              selector:@selector(loadStateChanged:) 
140                                                  name:QTMovieLoadStateDidChangeNotification 
141                                                object:m_qtMovie.get()];
142     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
143                                              selector:@selector(rateChanged:) 
144                                                  name:QTMovieRateDidChangeNotification 
145                                                object:m_qtMovie.get()];
146     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
147                                              selector:@selector(sizeChanged:) 
148                                                  name:QTMovieSizeDidChangeNotification 
149                                                object:m_qtMovie.get()];
150     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
151                                              selector:@selector(timeChanged:) 
152                                                  name:QTMovieTimeDidChangeNotification 
153                                                object:m_qtMovie.get()];
154     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
155                                              selector:@selector(volumeChanged:) 
156                                                  name:QTMovieVolumeDidChangeNotification 
157                                                object:m_qtMovie.get()];
158     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
159                                              selector:@selector(didEnd:) 
160                                                  name:QTMovieDidEndNotification 
161                                                object:m_qtMovie.get()];
162 }
163
164 void MediaPlayerPrivate::createQTMovieView()
165 {
166     if (m_qtMovieView) {
167         [m_qtMovieView.get() removeFromSuperview];
168         m_qtMovieView = nil;
169     }
170     if (!m_player->m_parentWidget || !m_qtMovie)
171         return;
172     m_qtMovieView.adoptNS([[QTMovieView alloc] initWithFrame:m_player->rect()]);
173     NSView* parentView = static_cast<ScrollView*>(m_player->m_parentWidget)->getDocumentView();
174     [parentView addSubview:m_qtMovieView.get()];
175     [m_qtMovieView.get() setMovie:m_qtMovie.get()];
176     [m_qtMovieView.get() setControllerVisible:NO];
177     [m_qtMovieView.get() setPreservesAspectRatio:YES];
178     // the area not covered by video should be transparent
179     [m_qtMovieView.get() setFillColor:[NSColor clearColor]];
180     wkQTMovieViewSetDrawSynchronously(m_qtMovieView.get(), YES);
181 }
182
183 QTTime MediaPlayerPrivate::createQTTime(float time) const
184 {
185     if (!m_qtMovie)
186         return QTMakeTime(0, 600);
187     long timeScale = [[m_qtMovie.get() attributeForKey:QTMovieTimeScaleAttribute] longValue];
188     return QTMakeTime(time * timeScale, timeScale);
189 }
190
191 void MediaPlayerPrivate::load(const String& url)
192 {
193     if (m_networkState != MediaPlayer::Loading) {
194         m_networkState = MediaPlayer::Loading;
195         m_player->networkStateChanged();
196     }
197     if (m_readyState != MediaPlayer::DataUnavailable) {
198         m_readyState = MediaPlayer::DataUnavailable;
199         m_player->readyStateChanged();
200     }
201     cancelSeek();
202     m_cuePointTimer.stop();
203     
204     [m_objcObserver.get() setDelayCallbacks:YES];
205
206     createQTMovie(url);
207     if (m_player->visible())
208         createQTMovieView();
209
210     [m_objcObserver.get() loadStateChanged:nil];
211     [m_objcObserver.get() setDelayCallbacks:NO];
212 }
213
214 void MediaPlayerPrivate::play()
215 {
216     if (!m_qtMovie)
217         return;
218     m_startedPlaying = true;
219     [m_objcObserver.get() setDelayCallbacks:YES];
220     [m_qtMovie.get() setRate:m_player->rate()];
221     [m_objcObserver.get() setDelayCallbacks:NO];
222     startCuePointTimerIfNeeded();
223 }
224
225 void MediaPlayerPrivate::pause()
226 {
227     if (!m_qtMovie)
228         return;
229     m_startedPlaying = false;
230     [m_objcObserver.get() setDelayCallbacks:YES];
231     [m_qtMovie.get() stop];
232     [m_objcObserver.get() setDelayCallbacks:NO];
233     m_cuePointTimer.stop();
234 }
235
236 float MediaPlayerPrivate::duration() const
237 {
238     if (!m_qtMovie)
239         return 0;
240     QTTime time = [m_qtMovie.get() duration];
241     if (time.flags == kQTTimeIsIndefinite)
242         return numeric_limits<float>::infinity();
243     return static_cast<float>(time.timeValue) / time.timeScale;
244 }
245
246 float MediaPlayerPrivate::currentTime() const
247 {
248     if (!m_qtMovie)
249         return 0;
250     QTTime time = [m_qtMovie.get() currentTime];
251     return min(static_cast<float>(time.timeValue) / time.timeScale, m_endTime);
252 }
253
254 void MediaPlayerPrivate::seek(float time)
255 {
256     cancelSeek();
257     
258     if (!m_qtMovie)
259         return;
260     
261     if (time > duration())
262         time = duration();
263     
264     m_seekTo = time;
265     if (maxTimeLoaded() >= m_seekTo)
266         doSeek();
267     else 
268         m_seekTimer.start(0, 0.5f);
269 }
270
271 void MediaPlayerPrivate::doSeek() 
272 {
273     QTTime qttime = createQTTime(m_seekTo);
274     // setCurrentTime generates several event callbacks, update afterwards
275     [m_objcObserver.get() setDelayCallbacks:YES];
276     float oldRate = [m_qtMovie.get() rate];
277     [m_qtMovie.get() setRate:0];
278     [m_qtMovie.get() setCurrentTime:qttime];
279     float timeAfterSeek = currentTime();
280     // restore playback only if not at end, othewise QTMovie will loop
281     if (timeAfterSeek < duration() && timeAfterSeek < m_endTime)
282         [m_qtMovie.get() setRate:oldRate];
283     cancelSeek();
284     [m_objcObserver.get() setDelayCallbacks:NO];
285 }
286
287 void MediaPlayerPrivate::cancelSeek()
288 {
289     m_seekTo = -1;
290     m_seekTimer.stop();
291 }
292
293 void MediaPlayerPrivate::seekTimerFired(Timer<MediaPlayerPrivate>*)
294 {        
295     if (!m_qtMovie || !seeking() || currentTime() == m_seekTo) {
296         cancelSeek();
297         updateStates();
298         m_player->timeChanged(); 
299         return;
300     } 
301     
302     if (maxTimeLoaded() >= m_seekTo)
303         doSeek();
304     else {
305         MediaPlayer::NetworkState state = networkState();
306         if (state == MediaPlayer::Empty || state == MediaPlayer::Loaded) {
307             cancelSeek();
308             updateStates();
309             m_player->timeChanged();
310         }
311     }
312 }
313
314 void MediaPlayerPrivate::setEndTime(float time)
315 {
316     m_endTime = time;
317     startCuePointTimerIfNeeded();
318 }
319
320 void MediaPlayerPrivate::addCuePoint(float /*time*/)
321 {
322     // FIXME: Eventually we'd like an approach that doesn't involve a timer.
323     startCuePointTimerIfNeeded();
324 }
325
326 void MediaPlayerPrivate::removeCuePoint(float /*time*/)
327 {
328 }
329
330 void MediaPlayerPrivate::clearCuePoints()
331 {
332 }
333
334 void MediaPlayerPrivate::startCuePointTimerIfNeeded()
335 {
336     if ((m_endTime < duration() || !m_player->m_cuePoints.isEmpty())
337         && m_startedPlaying && !m_cuePointTimer.isActive()) {
338         m_previousTimeCueTimerFired = currentTime();
339         m_cuePointTimer.startRepeating(cuePointTimerInterval);
340     }
341 }
342
343 void MediaPlayerPrivate::cuePointTimerFired(Timer<MediaPlayerPrivate>*)
344 {
345     float time = currentTime();
346     float previousTime = m_previousTimeCueTimerFired;
347     m_previousTimeCueTimerFired = time;
348     
349     // just do end for now
350     if (time >= m_endTime) {
351         pause();
352         didEnd();
353     }
354
355     // Make a copy since m_cuePoints could change as we deliver JavaScript calls.
356     Vector<float> cuePoints;
357     copyToVector(m_player->m_cuePoints, cuePoints);
358     size_t numCuePoints = cuePoints.size();
359     for (size_t i = 0; i < numCuePoints; ++i) {
360         float cueTime = cuePoints[i];
361         if (previousTime < cueTime && cueTime <= time)
362             m_player->cuePointReached(cueTime);
363     }
364 }
365
366 bool MediaPlayerPrivate::paused() const
367 {
368     if (!m_qtMovie)
369         return true;
370     return [m_qtMovie.get() rate] == 0;
371 }
372
373 bool MediaPlayerPrivate::seeking() const
374 {
375     if (!m_qtMovie)
376         return false;
377     return m_seekTo >= 0;
378 }
379
380 IntSize MediaPlayerPrivate::naturalSize() const
381 {
382     if (!m_qtMovie)
383         return IntSize();
384     return IntSize([[m_qtMovie.get() attributeForKey:QTMovieNaturalSizeAttribute] sizeValue]);
385 }
386
387 bool MediaPlayerPrivate::hasVideo() const
388 {
389     if (!m_qtMovie)
390         return false;
391     return [[m_qtMovie.get() attributeForKey:QTMovieHasVideoAttribute] boolValue];
392 }
393
394 void MediaPlayerPrivate::setVolume(float volume)
395 {
396     if (!m_qtMovie)
397         return;
398     [m_qtMovie.get() setVolume:volume];  
399 }
400
401 void MediaPlayerPrivate::setMuted(bool b)
402 {
403     if (!m_qtMovie)
404         return;
405     [m_qtMovie.get() setMuted:b];
406 }
407
408 void MediaPlayerPrivate::setRate(float rate)
409 {
410     if (!m_qtMovie)
411         return;
412     if (!paused())
413         [m_qtMovie.get() setRate:rate];
414 }
415
416 int MediaPlayerPrivate::dataRate() const
417 {
418     if (!m_qtMovie)
419         return 0;
420     return wkQTMovieDataRate(m_qtMovie.get()); 
421 }
422
423
424 float MediaPlayerPrivate::maxTimeBuffered() const
425 {
426     // rtsp streams are not buffered
427     return m_isStreaming ? 0 : maxTimeLoaded();
428 }
429
430 float MediaPlayerPrivate::maxTimeSeekable() const
431 {
432     // infinite duration means live stream
433     return isinf(duration()) ? 0 : maxTimeLoaded();
434 }
435
436 float MediaPlayerPrivate::maxTimeLoaded() const
437 {
438     if (!m_qtMovie)
439         return 0;
440     return wkQTMovieMaxTimeLoaded(m_qtMovie.get()); 
441 }
442
443 unsigned MediaPlayerPrivate::bytesLoaded() const
444 {
445     float dur = duration();
446     if (!dur)
447         return 0;
448     return totalBytes() * maxTimeLoaded() / dur;
449 }
450
451 bool MediaPlayerPrivate::totalBytesKnown() const
452 {
453     return totalBytes() > 0;
454 }
455
456 unsigned MediaPlayerPrivate::totalBytes() const
457 {
458     if (!m_qtMovie)
459         return 0;
460     return [[m_qtMovie.get() attributeForKey:QTMovieDataSizeAttribute] intValue];
461 }
462
463 void MediaPlayerPrivate::cancelLoad()
464 {
465     // FIXME: Is there a better way to check for this?
466     if (m_networkState < MediaPlayer::Loading || m_networkState == MediaPlayer::Loaded)
467         return;
468     
469     if (m_qtMovieView) {
470         [m_qtMovieView.get() removeFromSuperview];
471         m_qtMovieView = nil;
472     }
473     m_qtMovie = nil;
474     
475     updateStates();
476 }
477
478 void MediaPlayerPrivate::updateStates()
479 {
480     MediaPlayer::NetworkState oldNetworkState = m_networkState;
481     MediaPlayer::ReadyState oldReadyState = m_readyState;
482     
483     long loadState = m_qtMovie ? [[m_qtMovie.get() attributeForKey:QTMovieLoadStateAttribute] longValue] : -1;
484     // "Loaded" is reserved for fully buffered movies, never the case when streaming
485     if (loadState >= kMovieLoadStateComplete && !m_isStreaming) {
486         if (m_networkState < MediaPlayer::Loaded)
487             m_networkState = MediaPlayer::Loaded;
488         m_readyState = MediaPlayer::CanPlayThrough;
489     } else if (loadState >= kMovieLoadStatePlaythroughOK) {
490         if (m_networkState < MediaPlayer::LoadedFirstFrame && !seeking())
491             m_networkState = MediaPlayer::LoadedFirstFrame;
492         m_readyState = ([m_qtMovie.get() rate] == 0 && m_startedPlaying) ? MediaPlayer::DataUnavailable : MediaPlayer::CanPlayThrough;
493     } else if (loadState >= kMovieLoadStatePlayable) {
494         if (m_networkState < MediaPlayer::LoadedFirstFrame && !seeking())
495             m_networkState = MediaPlayer::LoadedFirstFrame;
496         m_readyState = ([m_qtMovie.get() rate] == 0 && m_startedPlaying) ? MediaPlayer::DataUnavailable : MediaPlayer::CanPlay;
497     } else if (loadState >= kMovieLoadStateLoaded) {
498         if (m_networkState < MediaPlayer::LoadedMetaData)
499             m_networkState = MediaPlayer::LoadedMetaData;
500         m_readyState = MediaPlayer::DataUnavailable;
501     } else if (loadState >= 0) {
502         if (m_networkState < MediaPlayer::Loading)
503             m_networkState = MediaPlayer::Loading;
504         m_readyState = MediaPlayer::DataUnavailable;        
505     } else {
506         m_networkState = MediaPlayer::LoadFailed;
507         m_readyState = MediaPlayer::DataUnavailable; 
508     }
509
510     if (seeking())
511         m_readyState = MediaPlayer::DataUnavailable;
512     
513     if (m_networkState != oldNetworkState)
514         m_player->networkStateChanged();
515     if (m_readyState != oldReadyState)
516         m_player->readyStateChanged();
517 }
518
519 void MediaPlayerPrivate::loadStateChanged()
520 {
521     updateStates();
522 }
523
524 void MediaPlayerPrivate::rateChanged()
525 {
526     updateStates();
527 }
528
529 void MediaPlayerPrivate::sizeChanged()
530 {
531 }
532
533 void MediaPlayerPrivate::timeChanged()
534 {
535     m_previousTimeCueTimerFired = -1;
536     updateStates();
537     m_player->timeChanged();
538 }
539
540 void MediaPlayerPrivate::volumeChanged()
541 {
542     m_player->volumeChanged();
543 }
544
545 void MediaPlayerPrivate::didEnd()
546 {
547     m_cuePointTimer.stop();
548     m_startedPlaying = false;
549     updateStates();
550     m_player->timeChanged();
551 }
552
553 void MediaPlayerPrivate::setRect(const IntRect& r) 
554
555     if (m_qtMovieView)
556         [m_qtMovieView.get() setFrame:r];
557 }
558
559 void MediaPlayerPrivate::setVisible(bool b)
560 {
561     if (b)
562         createQTMovieView();
563     else if (m_qtMovieView) {
564         [m_qtMovieView.get() removeFromSuperview];
565         m_qtMovieView = nil;
566     }
567 }
568
569 void MediaPlayerPrivate::paint(GraphicsContext* context, const IntRect& r)
570 {
571     if (context->paintingDisabled())
572         return;
573     NSView *view = m_qtMovieView.get();
574     if (view == nil)
575         return;
576     [m_objcObserver.get() setDelayCallbacks:YES];
577     BEGIN_BLOCK_OBJC_EXCEPTIONS;
578     [view displayRectIgnoringOpacity:[view convertRect:r fromView:[view superview]]];
579     END_BLOCK_OBJC_EXCEPTIONS;
580     [m_objcObserver.get() setDelayCallbacks:NO];
581 }
582
583 void MediaPlayerPrivate::getSupportedTypes(HashSet<String>& types)
584 {
585     NSArray* fileTypes = [QTMovie movieFileTypes:QTIncludeCommonTypes];
586     int count = [fileTypes count];
587     for (int n = 0; n < count; n++) {
588         CFStringRef ext = reinterpret_cast<CFStringRef>([fileTypes objectAtIndex:n]);
589         RetainPtr<CFStringRef> uti(AdoptCF, UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, ext, NULL));
590         if (!uti)
591             continue;
592         RetainPtr<CFStringRef> mime(AdoptCF, UTTypeCopyPreferredTagWithClass(uti.get(), kUTTagClassMIMEType));
593         if (!mime)
594             continue;
595         types.add(mime.get());
596     }
597
598
599 }
600
601 @implementation WebCoreMovieObserver
602
603 - (id)initWithCallback:(MediaPlayerPrivate *)callback
604 {
605     m_callback = callback;
606     return [super init];
607 }
608
609 - (void)disconnect
610 {
611     [NSObject cancelPreviousPerformRequestsWithTarget:self];
612     m_callback = 0;
613 }
614
615 - (void)loadStateChanged:(NSNotification *)notification
616 {
617     if (m_delayCallbacks)
618         [self performSelector:_cmd withObject:nil afterDelay:0];
619     else
620         m_callback->loadStateChanged();
621 }
622
623 - (void)rateChanged:(NSNotification *)notification
624 {
625     if (m_delayCallbacks)
626         [self performSelector:_cmd withObject:nil afterDelay:0];
627     else
628         m_callback->rateChanged();
629 }
630
631 - (void)sizeChanged:(NSNotification *)notification
632 {
633     if (m_delayCallbacks)
634         [self performSelector:_cmd withObject:nil afterDelay:0];
635     else
636         m_callback->sizeChanged();
637 }
638
639 - (void)timeChanged:(NSNotification *)notification
640 {
641     if (m_delayCallbacks)
642         [self performSelector:_cmd withObject:nil afterDelay:0];
643     else
644         m_callback->timeChanged();
645 }
646
647 - (void)volumeChanged:(NSNotification *)notification
648 {
649     if (m_delayCallbacks)
650         [self performSelector:_cmd withObject:nil afterDelay:0];
651     else
652         m_callback->volumeChanged();
653 }
654
655 - (void)didEnd:(NSNotification *)notification
656 {
657     if (m_delayCallbacks)
658         [self performSelector:_cmd withObject:nil afterDelay:0];
659     else
660         m_callback->didEnd();
661 }
662
663 - (void)setDelayCallbacks:(BOOL)shouldDelay
664 {
665     m_delayCallbacks = shouldDelay;
666 }
667
668 @end
669
670 #endif