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