2 * Copyright (C) 2007 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
30 #import "MediaPlayerPrivateQTKit.h"
32 #import "BlockExceptions.h"
33 #import "DeprecatedString.h"
34 #import "GraphicsContext.h"
36 #import "ScrollView.h"
37 #import "SoftLinking.h"
38 #import "WebCoreSystemInterface.h"
39 #import <QTKit/QTKit.h>
40 #import <objc/objc-runtime.h>
42 SOFT_LINK_FRAMEWORK(QTKit)
44 SOFT_LINK(QTKit, QTMakeTime, QTTime, (long long timeValue, long timeScale), (timeValue, timeScale))
46 SOFT_LINK_CLASS(QTKit, QTMovie)
47 SOFT_LINK_CLASS(QTKit, QTMovieView)
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 *)
61 #define QTMovie getQTMovieClass()
62 #define QTMovieView getQTMovieViewClass()
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()
76 // Older versions of the QTKit header don't have these constants.
77 #if QTKIT_VERSION_MAX_ALLOWED <= QTKIT_VERSION_7_0
79 QTMovieLoadStateLoaded = 2000L,
80 QTMovieLoadStatePlayable = 10000L,
81 QTMovieLoadStatePlaythroughOK = 20000L,
82 QTMovieLoadStateComplete = 100000L
86 using namespace WebCore;
89 @interface WebCoreMovieObserver : NSObject
91 MediaPlayerPrivate* m_callback;
92 BOOL m_delayCallbacks;
94 -(id)initWithCallback:(MediaPlayerPrivate*)callback;
96 -(void)setDelayCallbacks:(BOOL)shouldDelay;
97 -(void)loadStateChanged:(NSNotification *)notification;
98 -(void)rateChanged:(NSNotification *)notification;
99 -(void)sizeChanged:(NSNotification *)notification;
100 -(void)timeChanged:(NSNotification *)notification;
101 -(void)volumeChanged:(NSNotification *)notification;
102 -(void)didEnd:(NSNotification *)notification;
107 static const float cuePointTimerInterval = 0.020f;
109 MediaPlayerPrivate::MediaPlayerPrivate(MediaPlayer* player)
111 , m_objcObserver(AdoptNS, [[WebCoreMovieObserver alloc] initWithCallback:this])
113 , m_endTime(numeric_limits<float>::infinity())
114 , m_seekTimer(this, &MediaPlayerPrivate::seekTimerFired)
115 , m_cuePointTimer(this, &MediaPlayerPrivate::cuePointTimerFired)
116 , m_previousTimeCueTimerFired(0)
117 , m_networkState(MediaPlayer::Empty)
118 , m_readyState(MediaPlayer::DataUnavailable)
119 , m_startedPlaying(false)
120 , m_isStreaming(false)
124 MediaPlayerPrivate::~MediaPlayerPrivate()
127 [m_qtMovieView.get() removeFromSuperview];
128 [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()];
129 [m_objcObserver.get() disconnect];
132 void MediaPlayerPrivate::createQTMovie(const String& url)
134 [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()];
136 NSError* error = nil;
137 m_qtMovie.adoptNS([[QTMovie alloc] initWithURL:KURL(url.deprecatedString()).getNSURL() error:&error]);
139 // FIXME: Find a proper way to detect streaming content.
140 m_isStreaming = url.startsWith("rtsp:");
145 [m_qtMovie.get() setVolume:m_player->volume()];
146 [m_qtMovie.get() setMuted:m_player->muted()];
148 [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
149 selector:@selector(loadStateChanged:)
150 name:QTMovieLoadStateDidChangeNotification
151 object:m_qtMovie.get()];
152 [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
153 selector:@selector(rateChanged:)
154 name:QTMovieRateDidChangeNotification
155 object:m_qtMovie.get()];
156 [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
157 selector:@selector(sizeChanged:)
158 name:QTMovieSizeDidChangeNotification
159 object:m_qtMovie.get()];
160 [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
161 selector:@selector(timeChanged:)
162 name:QTMovieTimeDidChangeNotification
163 object:m_qtMovie.get()];
164 [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
165 selector:@selector(volumeChanged:)
166 name:QTMovieVolumeDidChangeNotification
167 object:m_qtMovie.get()];
168 [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
169 selector:@selector(didEnd:)
170 name:QTMovieDidEndNotification
171 object:m_qtMovie.get()];
174 void MediaPlayerPrivate::createQTMovieView()
177 [m_qtMovieView.get() removeFromSuperview];
180 if (!m_player->m_parentWidget || !m_qtMovie)
182 m_qtMovieView.adoptNS([[QTMovieView alloc] initWithFrame:m_player->rect()]);
183 NSView* parentView = static_cast<ScrollView*>(m_player->m_parentWidget)->getDocumentView();
184 [parentView addSubview:m_qtMovieView.get()];
185 [m_qtMovieView.get() setMovie:m_qtMovie.get()];
186 [m_qtMovieView.get() setControllerVisible:NO];
187 [m_qtMovieView.get() setPreservesAspectRatio:YES];
188 // the area not covered by video should be transparent
189 [m_qtMovieView.get() setFillColor:[NSColor clearColor]];
190 wkQTMovieViewSetDrawSynchronously(m_qtMovieView.get(), YES);
193 QTTime MediaPlayerPrivate::createQTTime(float time) const
196 return QTMakeTime(0, 600);
197 long timeScale = [[m_qtMovie.get() attributeForKey:QTMovieTimeScaleAttribute] longValue];
198 return QTMakeTime(time * timeScale, timeScale);
201 void MediaPlayerPrivate::load(const String& url)
203 if (m_networkState != MediaPlayer::Loading) {
204 m_networkState = MediaPlayer::Loading;
205 m_player->networkStateChanged();
207 if (m_readyState != MediaPlayer::DataUnavailable) {
208 m_readyState = MediaPlayer::DataUnavailable;
209 m_player->readyStateChanged();
212 m_cuePointTimer.stop();
214 [m_objcObserver.get() setDelayCallbacks:YES];
217 if (m_player->visible())
220 [m_objcObserver.get() loadStateChanged:nil];
221 [m_objcObserver.get() setDelayCallbacks:NO];
224 void MediaPlayerPrivate::play()
228 m_startedPlaying = true;
229 [m_objcObserver.get() setDelayCallbacks:YES];
230 [m_qtMovie.get() setRate:m_player->rate()];
231 [m_objcObserver.get() setDelayCallbacks:NO];
232 startCuePointTimerIfNeeded();
235 void MediaPlayerPrivate::pause()
239 m_startedPlaying = false;
240 [m_objcObserver.get() setDelayCallbacks:YES];
241 [m_qtMovie.get() stop];
242 [m_objcObserver.get() setDelayCallbacks:NO];
243 m_cuePointTimer.stop();
246 float MediaPlayerPrivate::duration() const
250 QTTime time = [m_qtMovie.get() duration];
251 if (time.flags == kQTTimeIsIndefinite)
252 return numeric_limits<float>::infinity();
253 return static_cast<float>(time.timeValue) / time.timeScale;
256 float MediaPlayerPrivate::currentTime() const
260 QTTime time = [m_qtMovie.get() currentTime];
261 return min(static_cast<float>(time.timeValue) / time.timeScale, m_endTime);
264 void MediaPlayerPrivate::seek(float time)
271 if (time > duration())
275 if (maxTimeLoaded() >= m_seekTo)
278 m_seekTimer.start(0, 0.5f);
281 void MediaPlayerPrivate::doSeek()
283 QTTime qttime = createQTTime(m_seekTo);
284 // setCurrentTime generates several event callbacks, update afterwards
285 [m_objcObserver.get() setDelayCallbacks:YES];
286 float oldRate = [m_qtMovie.get() rate];
287 [m_qtMovie.get() setRate:0];
288 [m_qtMovie.get() setCurrentTime:qttime];
289 float timeAfterSeek = currentTime();
290 // restore playback only if not at end, othewise QTMovie will loop
291 if (timeAfterSeek < duration() && timeAfterSeek < m_endTime)
292 [m_qtMovie.get() setRate:oldRate];
294 [m_objcObserver.get() setDelayCallbacks:NO];
297 void MediaPlayerPrivate::cancelSeek()
303 void MediaPlayerPrivate::seekTimerFired(Timer<MediaPlayerPrivate>*)
305 if (!m_qtMovie || !seeking() || currentTime() == m_seekTo) {
308 m_player->timeChanged();
312 if (maxTimeLoaded() >= m_seekTo)
315 MediaPlayer::NetworkState state = networkState();
316 if (state == MediaPlayer::Empty || state == MediaPlayer::Loaded) {
319 m_player->timeChanged();
324 void MediaPlayerPrivate::setEndTime(float time)
327 startCuePointTimerIfNeeded();
330 void MediaPlayerPrivate::addCuePoint(float /*time*/)
332 // FIXME: Eventually we'd like an approach that doesn't involve a timer.
333 startCuePointTimerIfNeeded();
336 void MediaPlayerPrivate::removeCuePoint(float /*time*/)
340 void MediaPlayerPrivate::clearCuePoints()
344 void MediaPlayerPrivate::startCuePointTimerIfNeeded()
346 if ((m_endTime < duration() || !m_player->m_cuePoints.isEmpty())
347 && m_startedPlaying && !m_cuePointTimer.isActive()) {
348 m_previousTimeCueTimerFired = currentTime();
349 m_cuePointTimer.startRepeating(cuePointTimerInterval);
353 void MediaPlayerPrivate::cuePointTimerFired(Timer<MediaPlayerPrivate>*)
355 float time = currentTime();
356 float previousTime = m_previousTimeCueTimerFired;
357 m_previousTimeCueTimerFired = time;
359 // just do end for now
360 if (time >= m_endTime) {
365 // Make a copy since m_cuePoints could change as we deliver JavaScript calls.
366 Vector<float> cuePoints;
367 copyToVector(m_player->m_cuePoints, cuePoints);
368 size_t numCuePoints = cuePoints.size();
369 for (size_t i = 0; i < numCuePoints; ++i) {
370 float cueTime = cuePoints[i];
371 if (previousTime < cueTime && cueTime <= time)
372 m_player->cuePointReached(cueTime);
376 bool MediaPlayerPrivate::paused() const
380 return [m_qtMovie.get() rate] == 0;
383 bool MediaPlayerPrivate::seeking() const
387 return m_seekTo >= 0;
390 IntSize MediaPlayerPrivate::naturalSize() const
394 return IntSize([[m_qtMovie.get() attributeForKey:QTMovieNaturalSizeAttribute] sizeValue]);
397 bool MediaPlayerPrivate::hasVideo() const
401 return [[m_qtMovie.get() attributeForKey:QTMovieHasVideoAttribute] boolValue];
404 void MediaPlayerPrivate::setVolume(float volume)
408 [m_qtMovie.get() setVolume:volume];
411 void MediaPlayerPrivate::setMuted(bool b)
415 [m_qtMovie.get() setMuted:b];
418 void MediaPlayerPrivate::setRate(float rate)
423 [m_qtMovie.get() setRate:rate];
426 int MediaPlayerPrivate::dataRate() const
430 return wkQTMovieDataRate(m_qtMovie.get());
434 float MediaPlayerPrivate::maxTimeBuffered() const
436 // rtsp streams are not buffered
437 return m_isStreaming ? 0 : maxTimeLoaded();
440 float MediaPlayerPrivate::maxTimeSeekable() const
442 // infinite duration means live stream
443 return isinf(duration()) ? 0 : maxTimeLoaded();
446 float MediaPlayerPrivate::maxTimeLoaded() const
450 return wkQTMovieMaxTimeLoaded(m_qtMovie.get());
453 unsigned MediaPlayerPrivate::bytesLoaded() const
455 float dur = duration();
458 return totalBytes() * maxTimeLoaded() / dur;
461 bool MediaPlayerPrivate::totalBytesKnown() const
463 return totalBytes() > 0;
466 unsigned MediaPlayerPrivate::totalBytes() const
470 return [[m_qtMovie.get() attributeForKey:QTMovieDataSizeAttribute] intValue];
473 void MediaPlayerPrivate::cancelLoad()
475 // FIXME: Is there a better way to check for this?
476 if (m_networkState < MediaPlayer::Loading || m_networkState == MediaPlayer::Loaded)
480 [m_qtMovieView.get() removeFromSuperview];
488 void MediaPlayerPrivate::updateStates()
490 MediaPlayer::NetworkState oldNetworkState = m_networkState;
491 MediaPlayer::ReadyState oldReadyState = m_readyState;
493 long loadState = m_qtMovie ? [[m_qtMovie.get() attributeForKey:QTMovieLoadStateAttribute] longValue] : -1;
494 // "Loaded" is reserved for fully buffered movies, never the case when streaming
495 if (loadState >= QTMovieLoadStateComplete && !m_isStreaming) {
496 if (m_networkState < MediaPlayer::Loaded)
497 m_networkState = MediaPlayer::Loaded;
498 m_readyState = MediaPlayer::CanPlayThrough;
499 } else if (loadState >= QTMovieLoadStatePlaythroughOK) {
500 if (m_networkState < MediaPlayer::LoadedFirstFrame && !seeking())
501 m_networkState = MediaPlayer::LoadedFirstFrame;
502 m_readyState = ([m_qtMovie.get() rate] == 0 && m_startedPlaying) ? MediaPlayer::DataUnavailable : MediaPlayer::CanPlayThrough;
503 } else if (loadState >= QTMovieLoadStatePlayable) {
504 if (m_networkState < MediaPlayer::LoadedFirstFrame && !seeking())
505 m_networkState = MediaPlayer::LoadedFirstFrame;
506 m_readyState = ([m_qtMovie.get() rate] == 0 && m_startedPlaying) ? MediaPlayer::DataUnavailable : MediaPlayer::CanPlay;
507 } else if (loadState >= QTMovieLoadStateLoaded) {
508 if (m_networkState < MediaPlayer::LoadedMetaData)
509 m_networkState = MediaPlayer::LoadedMetaData;
510 m_readyState = MediaPlayer::DataUnavailable;
511 } else if (loadState >= 0) {
512 if (m_networkState < MediaPlayer::Loading)
513 m_networkState = MediaPlayer::Loading;
514 m_readyState = MediaPlayer::DataUnavailable;
516 m_networkState = MediaPlayer::LoadFailed;
517 m_readyState = MediaPlayer::DataUnavailable;
521 m_readyState = MediaPlayer::DataUnavailable;
523 if (m_networkState != oldNetworkState)
524 m_player->networkStateChanged();
525 if (m_readyState != oldReadyState)
526 m_player->readyStateChanged();
529 void MediaPlayerPrivate::loadStateChanged()
534 void MediaPlayerPrivate::rateChanged()
539 void MediaPlayerPrivate::sizeChanged()
543 void MediaPlayerPrivate::timeChanged()
545 m_previousTimeCueTimerFired = -1;
547 m_player->timeChanged();
550 void MediaPlayerPrivate::volumeChanged()
552 m_player->volumeChanged();
555 void MediaPlayerPrivate::didEnd()
557 m_cuePointTimer.stop();
558 m_startedPlaying = false;
560 m_player->timeChanged();
563 void MediaPlayerPrivate::setRect(const IntRect& r)
566 [m_qtMovieView.get() setFrame:r];
569 void MediaPlayerPrivate::setVisible(bool b)
573 else if (m_qtMovieView) {
574 [m_qtMovieView.get() removeFromSuperview];
579 void MediaPlayerPrivate::paint(GraphicsContext* context, const IntRect& r)
581 if (context->paintingDisabled())
583 NSView *view = m_qtMovieView.get();
586 [m_objcObserver.get() setDelayCallbacks:YES];
587 BEGIN_BLOCK_OBJC_EXCEPTIONS;
588 [view displayRectIgnoringOpacity:[view convertRect:r fromView:[view superview]]];
589 END_BLOCK_OBJC_EXCEPTIONS;
590 [m_objcObserver.get() setDelayCallbacks:NO];
593 void MediaPlayerPrivate::getSupportedTypes(HashSet<String>& types)
595 NSArray* fileTypes = [QTMovie movieFileTypes:QTIncludeCommonTypes];
596 int count = [fileTypes count];
597 for (int n = 0; n < count; n++) {
598 CFStringRef ext = reinterpret_cast<CFStringRef>([fileTypes objectAtIndex:n]);
599 RetainPtr<CFStringRef> uti(AdoptCF, UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, ext, NULL));
602 RetainPtr<CFStringRef> mime(AdoptCF, UTTypeCopyPreferredTagWithClass(uti.get(), kUTTagClassMIMEType));
605 types.add(mime.get());
611 @implementation WebCoreMovieObserver
613 - (id)initWithCallback:(MediaPlayerPrivate *)callback
615 m_callback = callback;
621 [NSObject cancelPreviousPerformRequestsWithTarget:self];
625 - (void)loadStateChanged:(NSNotification *)notification
627 if (m_delayCallbacks)
628 [self performSelector:_cmd withObject:nil afterDelay:0];
630 m_callback->loadStateChanged();
633 - (void)rateChanged:(NSNotification *)notification
635 if (m_delayCallbacks)
636 [self performSelector:_cmd withObject:nil afterDelay:0];
638 m_callback->rateChanged();
641 - (void)sizeChanged:(NSNotification *)notification
643 if (m_delayCallbacks)
644 [self performSelector:_cmd withObject:nil afterDelay:0];
646 m_callback->sizeChanged();
649 - (void)timeChanged:(NSNotification *)notification
651 if (m_delayCallbacks)
652 [self performSelector:_cmd withObject:nil afterDelay:0];
654 m_callback->timeChanged();
657 - (void)volumeChanged:(NSNotification *)notification
659 if (m_delayCallbacks)
660 [self performSelector:_cmd withObject:nil afterDelay:0];
662 m_callback->volumeChanged();
665 - (void)didEnd:(NSNotification *)notification
667 if (m_delayCallbacks)
668 [self performSelector:_cmd withObject:nil afterDelay:0];
670 m_callback->didEnd();
673 - (void)setDelayCallbacks:(BOOL)shouldDelay
675 m_delayCallbacks = shouldDelay;