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.
29 #import "MoviePrivateQTKit.h"
31 #import "BlockExceptions.h"
32 #import "DeprecatedString.h"
33 #import "GraphicsContext.h"
37 #import "MIMETypeRegistry.h"
39 #import <QTKit/QTKit.h>
40 #import "ScrollView.h"
41 #import "WebCoreSystemInterface.h"
43 #import "wtf/RetainPtr.h"
45 @interface WebCoreMovieObserver : NSObject
47 WebCore::MoviePrivate* _callback;
50 -(id)initWithCallback:(WebCore::MoviePrivate *)c;
52 -(void)setDelayCallbacks:(BOOL)b;
53 -(void)loadStateChanged:(NSNotification *)notification;
54 -(void)rateChanged:(NSNotification *)notification;
55 -(void)sizeChanged:(NSNotification *)notification;
56 -(void)timeChanged:(NSNotification *)notification;
57 -(void)volumeChanged:(NSNotification *)notification;
58 -(void)didEnd:(NSNotification *)notification;
63 MoviePrivate::MoviePrivate(Movie* movie)
67 , m_objcObserver(AdoptNS, [[WebCoreMovieObserver alloc] initWithCallback:this])
69 , m_endTime(std::numeric_limits<float>::infinity())
70 , m_seekTimer(this, &MoviePrivate::seekTimerFired)
71 , m_cuePointTimer(this, &MoviePrivate::cuePointTimerFired)
72 , m_previousTimeCueTimerFired(0)
73 , m_networkState(Movie::Empty)
74 , m_readyState(Movie::DataUnavailable)
75 , m_startedPlaying(false)
76 , m_isStreaming(false)
81 MoviePrivate::~MoviePrivate()
84 [m_qtMovieView.get() removeFromSuperview];
85 [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()];
86 [m_objcObserver.get() disconnect];
89 void MoviePrivate::createQTMovie(String url)
91 [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()];
96 m_qtMovie = [[[QTMovie alloc] initWithURL:KURL(url.deprecatedString()).getNSURL() error:&error] autorelease];
98 // FIXME: find a proper way to do this
99 m_isStreaming = url.startsWith("rtsp:");
104 [m_qtMovie.get() setVolume: m_movie->volume()];
105 [m_qtMovie.get() setMuted: m_movie->muted()];
107 [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
108 selector:@selector(loadStateChanged:)
109 name:QTMovieLoadStateDidChangeNotification
110 object:m_qtMovie.get()];
111 [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
112 selector:@selector(rateChanged:)
113 name:QTMovieRateDidChangeNotification
114 object:m_qtMovie.get()];
115 [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
116 selector:@selector(sizeChanged:)
117 name:QTMovieSizeDidChangeNotification
118 object:m_qtMovie.get()];
119 [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
120 selector:@selector(timeChanged:)
121 name:QTMovieTimeDidChangeNotification
122 object:m_qtMovie.get()];
123 [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
124 selector:@selector(volumeChanged:)
125 name:QTMovieVolumeDidChangeNotification
126 object:m_qtMovie.get()];
127 [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
128 selector:@selector(didEnd:)
129 name:QTMovieDidEndNotification
130 object:m_qtMovie.get()];
133 void MoviePrivate::createQTMovieView()
136 [m_qtMovieView.get() removeFromSuperview];
139 if (!m_movie->m_parentWidget || !m_qtMovie)
141 m_qtMovieView = [[[QTMovieView alloc] initWithFrame:m_movie->rect()] autorelease];
142 NSView* parentView = static_cast<ScrollView*>(m_movie->m_parentWidget)->getDocumentView();
143 [parentView addSubview:m_qtMovieView.get()];
144 [m_qtMovieView.get() setMovie:m_qtMovie.get()];
145 [m_qtMovieView.get() setControllerVisible:NO];
146 [m_qtMovieView.get() setPreservesAspectRatio:YES];
147 wkQTMovieViewSetDrawSynchronously(m_qtMovieView.get(), YES);
150 QTTime MoviePrivate::createQTTime(float time)
153 return QTMakeTime(0, 600);
154 int timeScale = [[m_qtMovie.get() attributeForKey:QTMovieTimeScaleAttribute] intValue];
155 return QTMakeTime((long long)(time * timeScale), timeScale);
158 void MoviePrivate::load(String url)
160 if (m_networkState != Movie::Loading) {
161 m_networkState = Movie::Loading;
162 m_movie->networkStateChanged();
164 if (m_readyState != Movie::DataUnavailable) {
165 m_readyState = Movie::DataUnavailable;
166 m_movie->readyStateChanged();
169 m_cuePointTimer.stop();
171 [m_objcObserver.get() setDelayCallbacks:YES];
174 if (m_movie->visible())
177 [m_objcObserver.get() loadStateChanged:nil];
178 [m_objcObserver.get() setDelayCallbacks:NO];
181 void MoviePrivate::play()
185 m_startedPlaying = true;
186 [m_objcObserver.get() setDelayCallbacks:YES];
187 [m_qtMovie.get() setRate: m_movie->rate()];
188 [m_objcObserver.get() setDelayCallbacks:NO];
189 startCuePointTimerIfNeeded();
192 void MoviePrivate::pause()
196 m_startedPlaying = false;
197 [m_objcObserver.get() setDelayCallbacks:YES];
198 [m_qtMovie.get() stop];
199 [m_objcObserver.get() setDelayCallbacks:NO];
200 m_cuePointTimer.stop();
203 float MoviePrivate::duration() const
207 QTTime time = [m_qtMovie.get() duration];
208 if (time.flags == kQTTimeIsIndefinite)
209 return std::numeric_limits<float>::infinity();
210 return (float)time.timeValue / time.timeScale;
213 float MoviePrivate::currentTime() const
217 QTTime time = [m_qtMovie.get() currentTime];
218 float current = (float)time.timeValue / time.timeScale;
219 current = std::min(current, m_endTime);
223 void MoviePrivate::seek(float time)
230 if (time > duration())
234 if (maxTimeLoaded() >= m_seekTo)
237 m_seekTimer.start(0, 0.5f);
240 void MoviePrivate::doSeek()
242 QTTime qttime = createQTTime(m_seekTo);
243 // setCurrentTime generates several event callbacks, update afterwards
244 [m_objcObserver.get() setDelayCallbacks:YES];
245 float oldRate = [m_qtMovie.get() rate];
246 [m_qtMovie.get() setRate:0];
247 [m_qtMovie.get() setCurrentTime: qttime];
248 float timeAfterSeek = currentTime();
249 // restore playback only if not at end, othewise QTMovie will loop
250 if (timeAfterSeek < duration() && timeAfterSeek < m_endTime)
251 [m_qtMovie.get() setRate:oldRate];
253 [m_objcObserver.get() setDelayCallbacks:NO];
256 void MoviePrivate::cancelSeek()
262 void MoviePrivate::seekTimerFired(Timer<MoviePrivate>*)
264 if (!m_qtMovie || !seeking() || currentTime() == m_seekTo) {
267 m_movie->timeChanged();
271 if (maxTimeLoaded() >= m_seekTo)
274 Movie::NetworkState state = networkState();
275 if (state == Movie::Empty || state == Movie::Loaded) {
278 m_movie->timeChanged();
283 void MoviePrivate::setEndTime(float time)
286 startCuePointTimerIfNeeded();
289 void MoviePrivate::addCuePoint(float time)
291 // FIXME: simulate with timer for now
292 startCuePointTimerIfNeeded();
295 void MoviePrivate::removeCuePoint(float time)
299 void MoviePrivate::clearCuePoints()
303 void MoviePrivate::startCuePointTimerIfNeeded()
305 if ((m_endTime < duration() || !m_movie->m_cuePoints.isEmpty())
306 && m_startedPlaying && !m_cuePointTimer.isActive()) {
307 m_previousTimeCueTimerFired = currentTime();
308 m_cuePointTimer.startRepeating(0.020f);
312 void MoviePrivate::cuePointTimerFired(Timer<MoviePrivate>*)
314 float time = currentTime();
315 float previousTime = m_previousTimeCueTimerFired;
316 m_previousTimeCueTimerFired = time;
318 // just do end for now
319 if (time >= m_endTime) {
323 HashSet<float>::const_iterator end = m_movie->m_cuePoints.end();
324 for (HashSet<float>::const_iterator it = m_movie->m_cuePoints.begin(); it != end; ++it) {
326 if (previousTime < cueTime && cueTime <= time)
327 m_movie->cuePointReached(cueTime);
331 bool MoviePrivate::paused() const
335 return [m_qtMovie.get() rate] == 0.0f;
338 bool MoviePrivate::seeking() const
342 return m_seekTo >= 0;
345 IntSize MoviePrivate::naturalSize()
349 NSSize val = [[m_qtMovie.get() attributeForKey:QTMovieNaturalSizeAttribute] sizeValue];
353 bool MoviePrivate::hasVideo()
357 BOOL val = [[m_qtMovie.get() attributeForKey: QTMovieHasVideoAttribute] boolValue];
361 void MoviePrivate::setVolume(float volume)
365 [m_qtMovie.get() setVolume:volume];
368 void MoviePrivate::setMuted(bool b)
372 [m_qtMovie.get() setMuted:b];
375 void MoviePrivate::setRate(float rate)
380 [m_qtMovie.get() setRate:rate];
383 int MoviePrivate::dataRate() const
387 return wkQTMovieDataRate(m_qtMovie.get());
391 Movie::NetworkState MoviePrivate::networkState()
393 return m_networkState;
396 Movie::ReadyState MoviePrivate::readyState()
401 float MoviePrivate::maxTimeBuffered()
403 // rtsp streams are not buffered
404 return m_isStreaming ? 0 : maxTimeLoaded();
407 float MoviePrivate::maxTimeSeekable()
409 // infinite duration means live stream
410 return isinf(duration()) ? 0 : maxTimeLoaded();
413 float MoviePrivate::maxTimeLoaded()
417 return wkQTMovieMaxTimeLoaded(m_qtMovie.get());
420 unsigned MoviePrivate::bytesLoaded()
424 float dur = duration();
425 float maxTime = maxTimeLoaded();
428 return totalBytes() * maxTime / dur;
431 bool MoviePrivate::totalBytesKnown()
433 return totalBytes() > 0;
436 unsigned MoviePrivate::totalBytes()
440 return [[m_qtMovie.get() attributeForKey: QTMovieDataSizeAttribute] intValue];
443 void MoviePrivate::cancelLoad()
445 // FIXME better way to do this?
446 if (m_networkState < Movie::Loading || m_networkState == Movie::Loaded)
450 [m_qtMovieView.get() removeFromSuperview];
458 void MoviePrivate::updateStates()
460 Movie::NetworkState oldNetworkState = m_networkState;
461 Movie::ReadyState oldReadyState = m_readyState;
463 long loadState = m_qtMovie ? [[m_qtMovie.get() attributeForKey:QTMovieLoadStateAttribute] longValue] : -1;
464 // "Loaded" is reserved for fully buffered movies, never the case when rtsp streaming
465 if (loadState >= 100000 && !m_isStreaming) {
466 // 100000 is kMovieLoadStateComplete
467 if (m_networkState < Movie::Loaded)
468 m_networkState = Movie::Loaded;
469 m_readyState = Movie::CanPlayThrough;
470 } else if (loadState >= 20000) {
471 // 20000 is kMovieLoadStatePlaythroughOK
472 if (m_networkState < Movie::LoadedFirstFrame && !seeking())
473 m_networkState = Movie::LoadedFirstFrame;
474 m_readyState = ([m_qtMovie.get() rate] == 0.0f && m_startedPlaying) ? Movie::DataUnavailable : Movie::CanPlayThrough;
475 } else if (loadState >= 10000) {
476 // 10000 is kMovieLoadStatePlayable
477 if (m_networkState < Movie::LoadedFirstFrame && !seeking())
478 m_networkState = Movie::LoadedFirstFrame;
479 m_readyState = ([m_qtMovie.get() rate] == 0.0f && m_startedPlaying) ? Movie::DataUnavailable : Movie::CanPlay;
480 } else if (loadState >= 2000) {
481 // 10000 is kMovieLoadStateLoaded
482 if (m_networkState < Movie::LoadedMetaData)
483 m_networkState = Movie::LoadedMetaData;
484 m_readyState = Movie::DataUnavailable;
485 } else if (loadState >= 0) {
486 if (m_networkState < Movie::Loading)
487 m_networkState = Movie::Loading;
488 m_readyState = Movie::DataUnavailable;
490 m_networkState = Movie::LoadFailed;
491 m_readyState = Movie::DataUnavailable;
495 m_readyState = Movie::DataUnavailable;
497 if (m_networkState != oldNetworkState)
498 m_movie->networkStateChanged();
499 if (m_readyState != oldReadyState)
500 m_movie->readyStateChanged();
503 void MoviePrivate::loadStateChanged()
508 void MoviePrivate::rateChanged()
513 void MoviePrivate::sizeChanged()
517 void MoviePrivate::timeChanged()
519 m_previousTimeCueTimerFired = -1;
521 m_movie->timeChanged();
524 void MoviePrivate::volumeChanged()
526 m_movie->volumeChanged();
529 void MoviePrivate::didEnd()
531 m_cuePointTimer.stop();
532 m_startedPlaying = false;
534 m_movie->timeChanged();
537 void MoviePrivate::setRect(const IntRect& r)
540 [m_qtMovieView.get() setFrame: r];
543 void MoviePrivate::setVisible(bool b)
547 else if (m_qtMovieView) {
548 [m_qtMovieView.get() removeFromSuperview];
553 void MoviePrivate::paint(GraphicsContext* p, const IntRect& r)
555 if (p->paintingDisabled())
557 NSView *view = m_qtMovieView.get();
560 [m_objcObserver.get() setDelayCallbacks:YES];
561 BEGIN_BLOCK_OBJC_EXCEPTIONS;
562 [view displayRectIgnoringOpacity:[view convertRect:r fromView:[view superview]]];
563 END_BLOCK_OBJC_EXCEPTIONS;
564 [m_objcObserver.get() setDelayCallbacks:NO];
567 void MoviePrivate::getSupportedTypes(HashSet<String>& types)
569 NSArray* fileTypes = [QTMovie movieFileTypes:(QTMovieFileTypeOptions)0];
570 int count = [fileTypes count];
571 for (int n = 0; n < count; n++) {
572 NSString* ext = (NSString*)[fileTypes objectAtIndex:n];
573 CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (CFStringRef)ext, NULL);
576 CFStringRef mime = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType);
580 types.add(String((NSString*)mime));
587 @implementation WebCoreMovieObserver
588 -(id)initWithCallback:(WebCore::MoviePrivate *)c
595 [NSObject cancelPreviousPerformRequestsWithTarget:self];
598 -(void)loadStateChanged:(NSNotification *)notification
601 [self performSelector:@selector(loadStateChanged:) withObject:nil afterDelay:0];
603 _callback->loadStateChanged();
605 -(void)rateChanged:(NSNotification *)notification
608 [self performSelector:@selector(rateChanged:) withObject:nil afterDelay:0];
610 _callback->rateChanged();
612 -(void)sizeChanged:(NSNotification *)notification
615 [self performSelector:@selector(sizeChanged:) withObject:nil afterDelay:0];
617 _callback->sizeChanged();
619 -(void)timeChanged:(NSNotification *)notification
622 [self performSelector:@selector(timeChanged:) withObject:nil afterDelay:0];
624 _callback->timeChanged();
626 -(void)volumeChanged:(NSNotification *)notification
629 [self performSelector:@selector(volumeChanged:) withObject:nil afterDelay:0];
631 _callback->volumeChanged();
633 -(void)didEnd:(NSNotification *)notification
636 [self performSelector:@selector(didEnd:) withObject:nil afterDelay:0];
640 -(void)setDelayCallbacks:(BOOL)b