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)
74 , m_networkState(Movie::Empty)
75 , m_readyState(Movie::DataUnavailable)
76 , m_startedPlaying(false)
77 , m_blockStateUpdate(false)
78 , m_isStreaming(false)
83 MoviePrivate::~MoviePrivate()
86 [m_qtMovieView.get() removeFromSuperview];
87 [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()];
88 [m_objcObserver.get() disconnect];
91 void MoviePrivate::createQTMovie(String url)
93 [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()];
98 m_qtMovie = [[[QTMovie alloc] initWithURL:KURL(url.deprecatedString()).getNSURL() error:&error] autorelease];
100 // FIXME: find a proper way to do this
101 m_isStreaming = url.startsWith("rtsp:");
106 [m_qtMovie.get() setVolume: m_movie->volume()];
107 [m_qtMovie.get() setMuted: m_movie->muted()];
109 [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
110 selector:@selector(loadStateChanged:)
111 name:QTMovieLoadStateDidChangeNotification
112 object:m_qtMovie.get()];
113 [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
114 selector:@selector(rateChanged:)
115 name:QTMovieRateDidChangeNotification
116 object:m_qtMovie.get()];
117 [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
118 selector:@selector(sizeChanged:)
119 name:QTMovieSizeDidChangeNotification
120 object:m_qtMovie.get()];
121 [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
122 selector:@selector(timeChanged:)
123 name:QTMovieTimeDidChangeNotification
124 object:m_qtMovie.get()];
125 [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
126 selector:@selector(volumeChanged:)
127 name:QTMovieVolumeDidChangeNotification
128 object:m_qtMovie.get()];
129 [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
130 selector:@selector(didEnd:)
131 name:QTMovieDidEndNotification
132 object:m_qtMovie.get()];
135 void MoviePrivate::createQTMovieView()
138 [m_qtMovieView.get() removeFromSuperview];
141 if (!m_movie->m_parentWidget || !m_qtMovie)
143 m_qtMovieView = [[[QTMovieView alloc] initWithFrame:m_movie->rect()] autorelease];
144 NSView* parentView = static_cast<ScrollView*>(m_movie->m_parentWidget)->getDocumentView();
145 [parentView addSubview:m_qtMovieView.get()];
146 [m_qtMovieView.get() setMovie:m_qtMovie.get()];
147 [m_qtMovieView.get() setControllerVisible:NO];
148 [m_qtMovieView.get() setPreservesAspectRatio:YES];
149 wkQTMovieViewSetDrawSynchronously(m_qtMovieView.get(), YES);
152 QTTime MoviePrivate::createQTTime(float time)
155 return QTMakeTime(0, 600);
156 int timeScale = [[m_qtMovie.get() attributeForKey:QTMovieTimeScaleAttribute] intValue];
157 return QTMakeTime((long long)(time * timeScale), timeScale);
160 void MoviePrivate::load(String url)
162 if (m_networkState != Movie::Loading) {
163 m_networkState = Movie::Loading;
164 m_movie->networkStateChanged();
166 if (m_readyState != Movie::DataUnavailable) {
167 m_readyState = Movie::DataUnavailable;
168 m_movie->readyStateChanged();
171 m_cuePointTimer.stop();
173 if (m_movie->visible())
179 void MoviePrivate::play()
184 m_startedPlaying = true;
185 [m_qtMovie.get() setRate: m_movie->rate()];
186 startCuePointTimerIfNeeded();
189 void MoviePrivate::pause()
194 m_startedPlaying = false;
195 [m_qtMovie.get() stop];
196 m_cuePointTimer.stop();
199 float MoviePrivate::duration() const
203 QTTime time = [m_qtMovie.get() duration];
204 if (time.flags == kQTTimeIsIndefinite)
205 return std::numeric_limits<float>::infinity();
206 return (float)time.timeValue / time.timeScale;
209 float MoviePrivate::currentTime() const
215 QTTime time = [m_qtMovie.get() currentTime];
216 float current = (float)time.timeValue / time.timeScale;
217 current = std::min(current, m_endTime);
221 void MoviePrivate::seek(float time)
228 if (time > duration())
231 if (maxTimeLoaded() < time) {
233 m_seekTimer.startRepeating(0.5f);
234 m_rateBeforeSeek = [m_qtMovie.get() rate];
235 [m_qtMovie.get() setRate:0.0f];
238 QTTime qttime = createQTTime(time);
239 // setCurrentTime generates several event callbacks, update afterwards
240 m_blockStateUpdate = true;
241 [m_qtMovie.get() setCurrentTime: qttime];
242 m_blockStateUpdate = false;
247 void MoviePrivate::setEndTime(float time)
250 startCuePointTimerIfNeeded();
253 void MoviePrivate::addCuePoint(float time)
255 // FIXME: simulate with timer for now
256 startCuePointTimerIfNeeded();
259 void MoviePrivate::removeCuePoint(float time)
263 void MoviePrivate::clearCuePoints()
267 void MoviePrivate::startCuePointTimerIfNeeded()
270 if ((m_endTime < duration() || !m_movie->m_cuePoints.isEmpty())
271 && m_startedPlaying && !m_cuePointTimer.isActive()) {
272 m_previousTimeCueTimerFired = currentTime();
273 m_cuePointTimer.startRepeating(0.020f);
277 void MoviePrivate::cancelSeek()
282 [m_qtMovie.get() setRate:m_rateBeforeSeek];
284 m_rateBeforeSeek = 0.0f;
288 void MoviePrivate::seekTimerFired(Timer<MoviePrivate>*)
299 if (maxTimeLoaded() > m_seekTo) {
300 QTTime qttime = createQTTime(m_seekTo);
301 // setCurrentTime generates several event callbacks, update afterwards
302 m_blockStateUpdate = true;
303 [m_qtMovie.get() setCurrentTime: qttime];
304 m_blockStateUpdate = false;
309 Movie::NetworkState state = networkState();
310 if (state == Movie::Empty || state == Movie::Loaded) {
316 void MoviePrivate::cuePointTimerFired(Timer<MoviePrivate>*)
318 float time = currentTime();
319 float previousTime = m_previousTimeCueTimerFired;
320 m_previousTimeCueTimerFired = time;
322 // just do end for now
323 if (time >= m_endTime) {
327 HashSet<float>::const_iterator end = m_movie->m_cuePoints.end();
328 for (HashSet<float>::const_iterator it = m_movie->m_cuePoints.begin(); it != end; ++it) {
330 if (previousTime < cueTime && cueTime <= time)
331 m_movie->cuePointReached(cueTime);
335 bool MoviePrivate::paused() const
339 return [m_qtMovie.get() rate] == 0.0f && (!seeking() || m_rateBeforeSeek == 0.0f);
342 bool MoviePrivate::seeking() const
346 return m_seekTo >= 0;
349 IntSize MoviePrivate::naturalSize()
353 NSSize val = [[m_qtMovie.get() attributeForKey:QTMovieNaturalSizeAttribute] sizeValue];
357 bool MoviePrivate::hasVideo()
361 BOOL val = [[m_qtMovie.get() attributeForKey: QTMovieHasVideoAttribute] boolValue];
365 void MoviePrivate::setVolume(float volume)
369 [m_qtMovie.get() setVolume:volume];
372 void MoviePrivate::setMuted(bool b)
376 [m_qtMovie.get() setMuted:b];
379 void MoviePrivate::setRate(float rate)
384 [m_qtMovie.get() setRate:rate];
387 int MoviePrivate::dataRate() const
391 return wkQTMovieDataRate(m_qtMovie.get());
395 Movie::NetworkState MoviePrivate::networkState()
397 return m_networkState;
400 Movie::ReadyState MoviePrivate::readyState()
405 float MoviePrivate::maxTimeBuffered()
407 // rtsp streams are not buffered
408 return m_isStreaming ? 0 : maxTimeLoaded();
411 float MoviePrivate::maxTimeSeekable()
413 // infinite duration means live stream
414 return isinf(duration()) ? 0 : maxTimeLoaded();
417 float MoviePrivate::maxTimeLoaded()
421 return wkQTMovieMaxTimeLoaded(m_qtMovie.get());
424 unsigned MoviePrivate::bytesLoaded()
428 float dur = duration();
429 float maxTime = maxTimeLoaded();
432 return totalBytes() * maxTime / dur;
435 bool MoviePrivate::totalBytesKnown()
437 return totalBytes() > 0;
440 unsigned MoviePrivate::totalBytes()
444 return [[m_qtMovie.get() attributeForKey: QTMovieDataSizeAttribute] intValue];
447 void MoviePrivate::cancelLoad()
449 // FIXME better way to do this?
450 if (m_networkState < Movie::Loading || m_networkState == Movie::Loaded)
454 [m_qtMovieView.get() removeFromSuperview];
462 void MoviePrivate::updateStates()
464 if (m_blockStateUpdate)
467 Movie::NetworkState oldNetworkState = m_networkState;
468 Movie::ReadyState oldReadyState = m_readyState;
470 long loadState = m_qtMovie ? [[m_qtMovie.get() attributeForKey:QTMovieLoadStateAttribute] longValue] : -1;
471 // "Loaded" is reserved for fully buffered movies, never the case when rtsp streaming
472 if (loadState >= 100000 && !m_isStreaming) {
473 // 100000 is kMovieLoadStateComplete
474 if (m_networkState < Movie::Loaded)
475 m_networkState = Movie::Loaded;
476 m_readyState = Movie::CanPlayThrough;
477 } else if (loadState >= 20000) {
478 // 20000 is kMovieLoadStatePlaythroughOK
479 if (m_networkState < Movie::LoadedFirstFrame && !seeking())
480 m_networkState = Movie::LoadedFirstFrame;
481 m_readyState = ([m_qtMovie.get() rate] == 0.0f && m_startedPlaying) ? Movie::DataUnavailable : Movie::CanPlayThrough;
482 } else if (loadState >= 10000) {
483 // 10000 is kMovieLoadStatePlayable
484 if (m_networkState < Movie::LoadedFirstFrame && !seeking())
485 m_networkState = Movie::LoadedFirstFrame;
486 m_readyState = ([m_qtMovie.get() rate] == 0.0f && m_startedPlaying) ? Movie::DataUnavailable : Movie::CanPlay;
487 } else if (loadState >= 2000) {
488 // 10000 is kMovieLoadStateLoaded
489 if (m_networkState < Movie::LoadedMetaData)
490 m_networkState = Movie::LoadedMetaData;
491 m_readyState = Movie::DataUnavailable;
492 } else if (loadState >= 0) {
493 if (m_networkState < Movie::Loading)
494 m_networkState = Movie::Loading;
495 m_readyState = Movie::DataUnavailable;
497 m_networkState = Movie::LoadFailed;
498 m_readyState = Movie::DataUnavailable;
502 m_readyState = Movie::DataUnavailable;
504 if (m_networkState != oldNetworkState)
505 m_movie->networkStateChanged();
506 if (m_readyState != oldReadyState)
507 m_movie->readyStateChanged();
510 void MoviePrivate::loadStateChanged()
515 void MoviePrivate::rateChanged()
520 void MoviePrivate::sizeChanged()
524 void MoviePrivate::timeChanged()
526 m_previousTimeCueTimerFired = -1;
530 void MoviePrivate::volumeChanged()
532 m_movie->volumeChanged();
535 void MoviePrivate::didEnd()
537 m_cuePointTimer.stop();
538 m_startedPlaying = false;
542 void MoviePrivate::setRect(const IntRect& r)
545 [m_qtMovieView.get() setFrame: r];
548 void MoviePrivate::setVisible(bool b)
552 else if (m_qtMovieView) {
553 [m_qtMovieView.get() removeFromSuperview];
558 void MoviePrivate::paint(GraphicsContext* p, const IntRect& r)
560 if (p->paintingDisabled())
562 NSView *view = m_qtMovieView.get();
565 [m_objcObserver.get() setDelayCallbacks:YES];
566 BEGIN_BLOCK_OBJC_EXCEPTIONS;
567 [view displayRectIgnoringOpacity:[view convertRect:r fromView:[view superview]]];
568 END_BLOCK_OBJC_EXCEPTIONS;
569 [m_objcObserver.get() setDelayCallbacks:NO];
572 void MoviePrivate::getSupportedTypes(HashSet<String>& types)
574 NSArray* fileTypes = [QTMovie movieFileTypes:(QTMovieFileTypeOptions)0];
575 int count = [fileTypes count];
576 for (int n = 0; n < count; n++) {
577 NSString* ext = (NSString*)[fileTypes objectAtIndex:n];
578 CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (CFStringRef)ext, NULL);
581 CFStringRef mime = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType);
585 types.add(String((NSString*)mime));
592 @implementation WebCoreMovieObserver
593 -(id)initWithCallback:(WebCore::MoviePrivate *)c
600 [NSObject cancelPreviousPerformRequestsWithTarget:self];
603 -(void)loadStateChanged:(NSNotification *)notification
606 [self performSelector:@selector(loadStateChanged:) withObject:nil afterDelay:0];
608 _callback->loadStateChanged();
610 -(void)rateChanged:(NSNotification *)notification
613 [self performSelector:@selector(rateChanged:) withObject:nil afterDelay:0];
615 _callback->rateChanged();
617 -(void)sizeChanged:(NSNotification *)notification
620 [self performSelector:@selector(sizeChanged:) withObject:nil afterDelay:0];
622 _callback->sizeChanged();
624 -(void)timeChanged:(NSNotification *)notification
627 [self performSelector:@selector(timeChanged:) withObject:nil afterDelay:0];
629 _callback->timeChanged();
631 -(void)volumeChanged:(NSNotification *)notification
634 [self performSelector:@selector(volumeChanged:) withObject:nil afterDelay:0];
636 _callback->volumeChanged();
638 -(void)didEnd:(NSNotification *)notification
641 [self performSelector:@selector(didEnd:) withObject:nil afterDelay:0];
645 -(void)setDelayCallbacks:(BOOL)b