735ba4e90b505111790f22600ea4ef1bcbf113d2
[WebKit-https.git] / WebCore / platform / graphics / mac / MoviePrivateQTKit.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 #import "MoviePrivateQTKit.h"
30
31 #import "BlockExceptions.h"
32 #import "DeprecatedString.h"
33 #import "GraphicsContext.h"
34 #import "IntRect.h"
35 #import "KURL.h"
36 #import <limits>
37 #import "MIMETypeRegistry.h"
38 #import "Movie.h"
39 #import <QTKit/QTKit.h>
40 #import "ScrollView.h"
41 #import "WebCoreSystemInterface.h"
42 #import "Widget.h"
43 #import "wtf/RetainPtr.h"
44
45 @interface WebCoreMovieObserver : NSObject
46 {
47     WebCore::MoviePrivate* _callback;
48     BOOL _delayCallbacks;
49 }
50 -(id)initWithCallback:(WebCore::MoviePrivate *)c;
51 -(void)disconnect;
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;
59 @end
60
61 namespace WebCore {
62     
63 MoviePrivate::MoviePrivate(Movie* movie)
64     : m_movie(movie)
65     , m_qtMovie(nil)
66     , m_qtMovieView(nil)
67     , m_objcObserver(AdoptNS, [[WebCoreMovieObserver alloc] initWithCallback:this])
68     , m_seekTo(-1)
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_rateBeforeSeek(0)
74     , m_networkState(Movie::Empty)
75     , m_readyState(Movie::DataUnavailable)
76     , m_startedPlaying(false)
77     , m_blockStateUpdate(false)
78     , m_isStreaming(false)
79 {
80 }
81
82
83 MoviePrivate::~MoviePrivate()
84 {
85     if (m_qtMovieView)
86         [m_qtMovieView.get() removeFromSuperview];
87     [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()];
88     [m_objcObserver.get() disconnect];
89 }
90
91 void MoviePrivate::createQTMovie(String url)
92 {
93     [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()];
94     
95     m_qtMovie = nil;
96     
97     NSError* error = nil;
98     m_qtMovie = [[[QTMovie alloc] initWithURL:KURL(url.deprecatedString()).getNSURL() error:&error] autorelease];
99     
100     // FIXME: find a proper way to do this
101     m_isStreaming = url.startsWith("rtsp:");
102     
103     if (!m_qtMovie)
104         return;
105     
106     [m_qtMovie.get() setVolume: m_movie->volume()];
107     [m_qtMovie.get() setMuted: m_movie->muted()];
108     
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()];
133 }
134
135 void MoviePrivate::createQTMovieView()
136 {
137     if (m_qtMovieView) {
138         [m_qtMovieView.get() removeFromSuperview];
139         m_qtMovieView = nil;
140     }
141     if (!m_movie->m_parentWidget || !m_qtMovie)
142         return;
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);
150 }
151
152 QTTime MoviePrivate::createQTTime(float time)
153 {
154     if (!m_qtMovie)
155         return QTMakeTime(0, 600);
156     int timeScale = [[m_qtMovie.get() attributeForKey:QTMovieTimeScaleAttribute] intValue];
157     return QTMakeTime((long long)(time * timeScale), timeScale);
158 }
159
160 void MoviePrivate::load(String url)
161 {
162     if (m_networkState != Movie::Loading) {
163         m_networkState = Movie::Loading;
164         m_movie->networkStateChanged();
165     }
166     if (m_readyState != Movie::DataUnavailable) {
167         m_readyState = Movie::DataUnavailable;
168         m_movie->readyStateChanged();
169     }
170     cancelSeek();
171     m_cuePointTimer.stop();
172     createQTMovie(url);
173     if (m_movie->visible())
174         createQTMovieView();
175     
176     updateStates();
177 }
178
179 void MoviePrivate::play()
180 {
181     cancelSeek();
182     if (!m_qtMovie)
183         return;
184     m_startedPlaying = true;
185     [m_qtMovie.get() setRate: m_movie->rate()];
186     startCuePointTimerIfNeeded();
187 }
188
189 void MoviePrivate::pause()
190 {
191     cancelSeek();
192     if (!m_qtMovie)
193         return;
194     m_startedPlaying = false;
195     [m_qtMovie.get() stop];
196     m_cuePointTimer.stop();
197 }
198
199 float MoviePrivate::duration() const
200 {
201     if (!m_qtMovie)
202         return 0;
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;
207 }
208
209 float MoviePrivate::currentTime() const
210 {
211     if (!m_qtMovie)
212         return 0;
213     if (seeking())
214         return m_seekTo;
215     QTTime time = [m_qtMovie.get() currentTime];
216     float current = (float)time.timeValue / time.timeScale;    
217     current = std::min(current, m_endTime);
218     return current;
219 }
220
221 void MoviePrivate::seek(float time)
222 {
223     cancelSeek();
224     
225     if (!m_qtMovie)
226         return;
227     
228     if (time > duration())
229         time = duration();
230     
231     if (maxTimeLoaded() < time) {
232         m_seekTo = time;
233         m_seekTimer.startRepeating(0.5f);
234         m_rateBeforeSeek = [m_qtMovie.get() rate];
235         [m_qtMovie.get() setRate:0.0f];
236         updateStates();
237     } else {
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;
243         updateStates();
244     }
245 }
246
247 void MoviePrivate::setEndTime(float time)
248 {
249     m_endTime = time;
250     startCuePointTimerIfNeeded();
251 }
252
253 void MoviePrivate::addCuePoint(float time)
254 {
255     // FIXME: simulate with timer for now
256     startCuePointTimerIfNeeded();
257 }
258
259 void MoviePrivate::removeCuePoint(float time)
260 {
261 }
262
263 void MoviePrivate::clearCuePoints()
264 {
265 }
266
267 void MoviePrivate::startCuePointTimerIfNeeded()
268 {
269     
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);
274     }
275 }
276
277 void MoviePrivate::cancelSeek()
278 {
279     if (m_seekTo > -1) {
280         m_seekTo = -1;
281         if (m_qtMovie)
282             [m_qtMovie.get() setRate:m_rateBeforeSeek];
283     }
284     m_rateBeforeSeek = 0.0f;
285     m_seekTimer.stop();
286 }
287
288 void MoviePrivate::seekTimerFired(Timer<MoviePrivate>*)
289 {        
290     if (!m_qtMovie) {
291         cancelSeek();
292         return;
293     }
294     if (!seeking()) {
295         updateStates();
296         return;
297     }
298     
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;
305         cancelSeek();
306         updateStates();
307     }
308
309     Movie::NetworkState state = networkState();
310     if (state == Movie::Empty || state == Movie::Loaded) {
311         cancelSeek();
312         updateStates();
313     }
314 }
315
316 void MoviePrivate::cuePointTimerFired(Timer<MoviePrivate>*)
317 {
318     float time = currentTime();
319     float previousTime = m_previousTimeCueTimerFired;
320     m_previousTimeCueTimerFired = time;
321     
322     // just do end for now
323     if (time >= m_endTime) {
324         pause();
325         didEnd();
326     }
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) {
329         float cueTime = *it;
330         if (previousTime < cueTime && cueTime <= time)
331             m_movie->cuePointReached(cueTime);
332     }
333 }
334
335 bool MoviePrivate::paused() const
336 {
337     if (!m_qtMovie)
338         return true;
339     return [m_qtMovie.get() rate] == 0.0f && (!seeking() || m_rateBeforeSeek == 0.0f);
340 }
341
342 bool MoviePrivate::seeking() const
343 {
344     if (!m_qtMovie)
345         return false;
346     return m_seekTo >= 0;
347 }
348
349 IntSize MoviePrivate::naturalSize()
350 {
351     if (!m_qtMovie)
352         return IntSize();
353     NSSize val = [[m_qtMovie.get() attributeForKey:QTMovieNaturalSizeAttribute] sizeValue];
354     return IntSize(val);
355 }
356
357 bool MoviePrivate::hasVideo()
358 {
359     if (!m_qtMovie)
360         return false;
361     BOOL val = [[m_qtMovie.get() attributeForKey: QTMovieHasVideoAttribute] boolValue];
362     return val;
363 }
364
365 void MoviePrivate::setVolume(float volume)
366 {
367     if (!m_qtMovie)
368         return;
369     [m_qtMovie.get() setVolume:volume];  
370 }
371
372 void MoviePrivate::setMuted(bool b)
373 {
374     if (!m_qtMovie)
375         return;
376     [m_qtMovie.get() setMuted:b];
377 }
378
379 void MoviePrivate::setRate(float rate)
380 {
381     if (!m_qtMovie)
382         return;
383     if (!paused())
384         [m_qtMovie.get() setRate:rate];
385 }
386
387 int MoviePrivate::dataRate() const
388 {
389     if (!m_qtMovie)
390         return 0;
391     return wkQTMovieDataRate(m_qtMovie.get()); 
392 }
393
394
395 Movie::NetworkState MoviePrivate::networkState()
396 {
397     return m_networkState;
398 }
399
400 Movie::ReadyState MoviePrivate::readyState()
401 {
402     return m_readyState;
403 }
404
405 float MoviePrivate::maxTimeBuffered()
406 {
407     // rtsp streams are not buffered
408     return m_isStreaming ? 0 : maxTimeLoaded();
409 }
410
411 float MoviePrivate::maxTimeSeekable()
412 {
413     // infinite duration means live stream
414     return isinf(duration()) ? 0 : maxTimeLoaded();
415 }
416
417 float MoviePrivate::maxTimeLoaded()
418 {
419     if (!m_qtMovie)
420         return 0;
421     return wkQTMovieMaxTimeLoaded(m_qtMovie.get()); 
422 }
423
424 unsigned MoviePrivate::bytesLoaded()
425 {
426     if (!m_qtMovie)
427         return 0;
428     float dur = duration();
429     float maxTime = maxTimeLoaded();
430     if (!dur)
431         return 0;
432     return totalBytes() * maxTime / dur;
433 }
434
435 bool MoviePrivate::totalBytesKnown()
436 {
437     return totalBytes() > 0;
438 }
439
440 unsigned MoviePrivate::totalBytes()
441 {
442     if (!m_qtMovie)
443         return 0;
444     return [[m_qtMovie.get() attributeForKey: QTMovieDataSizeAttribute] intValue];
445 }
446
447 void MoviePrivate::cancelLoad()
448 {
449     // FIXME better way to do this?
450     if (m_networkState < Movie::Loading || m_networkState == Movie::Loaded)
451         return;
452     
453     if (m_qtMovieView) {
454         [m_qtMovieView.get() removeFromSuperview];
455         m_qtMovieView = nil;
456     }
457     m_qtMovie = nil;
458     
459     updateStates();
460 }
461
462 void MoviePrivate::updateStates()
463 {
464     if (m_blockStateUpdate)
465         return;
466     
467     Movie::NetworkState oldNetworkState = m_networkState;
468     Movie::ReadyState oldReadyState = m_readyState;
469     
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;        
496     } else {
497         m_networkState = Movie::LoadFailed;
498         m_readyState = Movie::DataUnavailable; 
499     }
500     
501     if (seeking())
502         m_readyState = Movie::DataUnavailable;
503     
504     if (m_networkState != oldNetworkState)
505         m_movie->networkStateChanged();
506     if (m_readyState != oldReadyState)
507         m_movie->readyStateChanged();
508 }
509
510 void MoviePrivate::loadStateChanged()
511 {
512     updateStates();
513 }
514
515 void MoviePrivate::rateChanged()
516 {
517     updateStates();
518 }
519
520 void MoviePrivate::sizeChanged()
521 {
522 }
523
524 void MoviePrivate::timeChanged()
525 {
526     m_previousTimeCueTimerFired = -1;
527     updateStates();
528 }
529
530 void MoviePrivate::volumeChanged()
531 {
532     m_movie->volumeChanged();
533 }
534
535 void MoviePrivate::didEnd()
536 {
537     m_cuePointTimer.stop();
538     m_startedPlaying = false;
539     m_movie->didEnd();
540 }
541
542 void MoviePrivate::setRect(const IntRect& r) 
543
544     if (m_qtMovieView)
545         [m_qtMovieView.get() setFrame: r];
546 }
547
548 void MoviePrivate::setVisible(bool b)
549 {
550     if (b)
551         createQTMovieView();
552     else if (m_qtMovieView) {
553         [m_qtMovieView.get() removeFromSuperview];
554         m_qtMovieView = nil;
555     }
556 }
557
558 void MoviePrivate::paint(GraphicsContext* p, const IntRect& r)
559 {
560     if (p->paintingDisabled())
561         return;
562     NSView *view = m_qtMovieView.get();
563     if (view == nil)
564         return;
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];
570 }
571
572 void MoviePrivate::getSupportedTypes(HashSet<String>& types)
573 {
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);
579         if (!uti)
580             continue;
581         CFStringRef mime = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType);
582         CFRelease(uti);
583         if (!mime)
584             continue;
585         types.add(String((NSString*)mime));
586         CFRelease(mime);
587     }
588
589
590 }
591
592 @implementation WebCoreMovieObserver
593 -(id)initWithCallback:(WebCore::MoviePrivate *)c
594 {
595     _callback = c;
596     return [super init];
597 }
598 -(void)disconnect
599 {
600     [NSObject cancelPreviousPerformRequestsWithTarget:self];
601     _callback = 0;
602 }
603 -(void)loadStateChanged:(NSNotification *)notification
604 {
605     if (_delayCallbacks)
606         [self performSelector:@selector(loadStateChanged:) withObject:nil afterDelay:0];
607     else
608         _callback->loadStateChanged();
609 }
610 -(void)rateChanged:(NSNotification *)notification
611 {
612     if (_delayCallbacks)
613         [self performSelector:@selector(rateChanged:) withObject:nil afterDelay:0];
614     else
615         _callback->rateChanged();
616 }
617 -(void)sizeChanged:(NSNotification *)notification
618 {
619     if (_delayCallbacks)
620         [self performSelector:@selector(sizeChanged:) withObject:nil afterDelay:0];
621     else
622         _callback->sizeChanged();
623 }
624 -(void)timeChanged:(NSNotification *)notification
625 {
626     if (_delayCallbacks)
627         [self performSelector:@selector(timeChanged:) withObject:nil afterDelay:0];
628     else
629         _callback->timeChanged();
630 }
631 -(void)volumeChanged:(NSNotification *)notification
632 {
633     if (_delayCallbacks)
634         [self performSelector:@selector(volumeChanged:) withObject:nil afterDelay:0];
635     else
636         _callback->volumeChanged();
637 }
638 -(void)didEnd:(NSNotification *)notification
639 {
640     if (_delayCallbacks)
641         [self performSelector:@selector(didEnd:) withObject:nil afterDelay:0];
642     else
643         _callback->didEnd();
644 }
645 -(void)setDelayCallbacks:(BOOL)b
646 {
647     _delayCallbacks = b;
648 }
649 @end
650
651 #endif
652