WebCore:
[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     wkQTMovieViewSetDrawSynchronously(m_qtMovieView.get(), YES);
148 }
149
150 QTTime MoviePrivate::createQTTime(float time)
151 {
152     if (!m_qtMovie)
153         return QTMakeTime(0, 600);
154     int timeScale = [[m_qtMovie.get() attributeForKey:QTMovieTimeScaleAttribute] intValue];
155     return QTMakeTime((long long)(time * timeScale), timeScale);
156 }
157
158 void MoviePrivate::load(String url)
159 {
160     if (m_networkState != Movie::Loading) {
161         m_networkState = Movie::Loading;
162         m_movie->networkStateChanged();
163     }
164     if (m_readyState != Movie::DataUnavailable) {
165         m_readyState = Movie::DataUnavailable;
166         m_movie->readyStateChanged();
167     }
168     cancelSeek();
169     m_cuePointTimer.stop();
170     
171     [m_objcObserver.get() setDelayCallbacks:YES];
172
173     createQTMovie(url);
174     if (m_movie->visible())
175         createQTMovieView();
176
177     [m_objcObserver.get() loadStateChanged:nil];
178     [m_objcObserver.get() setDelayCallbacks:NO];
179 }
180
181 void MoviePrivate::play()
182 {
183     if (!m_qtMovie)
184         return;
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();
190 }
191
192 void MoviePrivate::pause()
193 {
194     if (!m_qtMovie)
195         return;
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();
201 }
202
203 float MoviePrivate::duration() const
204 {
205     if (!m_qtMovie)
206         return 0;
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;
211 }
212
213 float MoviePrivate::currentTime() const
214 {
215     if (!m_qtMovie)
216         return 0;
217     QTTime time = [m_qtMovie.get() currentTime];
218     float current = (float)time.timeValue / time.timeScale;    
219     current = std::min(current, m_endTime);
220     return current;
221 }
222
223 void MoviePrivate::seek(float time)
224 {
225     cancelSeek();
226     
227     if (!m_qtMovie)
228         return;
229     
230     if (time > duration())
231         time = duration();
232     
233     m_seekTo = time;
234     if (maxTimeLoaded() >= m_seekTo)
235         doSeek();
236     else 
237         m_seekTimer.start(0, 0.5f);
238 }
239     
240 void MoviePrivate::doSeek() 
241 {
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];
252     cancelSeek();
253     [m_objcObserver.get() setDelayCallbacks:NO];
254 }
255
256 void MoviePrivate::cancelSeek()
257 {
258     m_seekTo = -1;
259     m_seekTimer.stop();
260 }
261
262 void MoviePrivate::seekTimerFired(Timer<MoviePrivate>*)
263 {        
264     if (!m_qtMovie || !seeking() || currentTime() == m_seekTo) {
265         cancelSeek();
266         updateStates();
267         m_movie->timeChanged(); 
268         return;
269     } 
270     
271     if (maxTimeLoaded() >= m_seekTo)
272         doSeek();
273     else {
274         Movie::NetworkState state = networkState();
275         if (state == Movie::Empty || state == Movie::Loaded) {
276             cancelSeek();
277             updateStates();
278             m_movie->timeChanged();
279         }
280     }
281 }
282
283 void MoviePrivate::setEndTime(float time)
284 {
285     m_endTime = time;
286     startCuePointTimerIfNeeded();
287 }
288
289 void MoviePrivate::addCuePoint(float time)
290 {
291     // FIXME: simulate with timer for now
292     startCuePointTimerIfNeeded();
293 }
294
295 void MoviePrivate::removeCuePoint(float time)
296 {
297 }
298
299 void MoviePrivate::clearCuePoints()
300 {
301 }
302
303 void MoviePrivate::startCuePointTimerIfNeeded()
304 {
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);
309     }
310 }
311
312 void MoviePrivate::cuePointTimerFired(Timer<MoviePrivate>*)
313 {
314     float time = currentTime();
315     float previousTime = m_previousTimeCueTimerFired;
316     m_previousTimeCueTimerFired = time;
317     
318     // just do end for now
319     if (time >= m_endTime) {
320         pause();
321         didEnd();
322     }
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) {
325         float cueTime = *it;
326         if (previousTime < cueTime && cueTime <= time)
327             m_movie->cuePointReached(cueTime);
328     }
329 }
330
331 bool MoviePrivate::paused() const
332 {
333     if (!m_qtMovie)
334         return true;
335     return [m_qtMovie.get() rate] == 0.0f;
336 }
337
338 bool MoviePrivate::seeking() const
339 {
340     if (!m_qtMovie)
341         return false;
342     return m_seekTo >= 0;
343 }
344
345 IntSize MoviePrivate::naturalSize()
346 {
347     if (!m_qtMovie)
348         return IntSize();
349     NSSize val = [[m_qtMovie.get() attributeForKey:QTMovieNaturalSizeAttribute] sizeValue];
350     return IntSize(val);
351 }
352
353 bool MoviePrivate::hasVideo()
354 {
355     if (!m_qtMovie)
356         return false;
357     BOOL val = [[m_qtMovie.get() attributeForKey: QTMovieHasVideoAttribute] boolValue];
358     return val;
359 }
360
361 void MoviePrivate::setVolume(float volume)
362 {
363     if (!m_qtMovie)
364         return;
365     [m_qtMovie.get() setVolume:volume];  
366 }
367
368 void MoviePrivate::setMuted(bool b)
369 {
370     if (!m_qtMovie)
371         return;
372     [m_qtMovie.get() setMuted:b];
373 }
374
375 void MoviePrivate::setRate(float rate)
376 {
377     if (!m_qtMovie)
378         return;
379     if (!paused())
380         [m_qtMovie.get() setRate:rate];
381 }
382
383 int MoviePrivate::dataRate() const
384 {
385     if (!m_qtMovie)
386         return 0;
387     return wkQTMovieDataRate(m_qtMovie.get()); 
388 }
389
390
391 Movie::NetworkState MoviePrivate::networkState()
392 {
393     return m_networkState;
394 }
395
396 Movie::ReadyState MoviePrivate::readyState()
397 {
398     return m_readyState;
399 }
400
401 float MoviePrivate::maxTimeBuffered()
402 {
403     // rtsp streams are not buffered
404     return m_isStreaming ? 0 : maxTimeLoaded();
405 }
406
407 float MoviePrivate::maxTimeSeekable()
408 {
409     // infinite duration means live stream
410     return isinf(duration()) ? 0 : maxTimeLoaded();
411 }
412
413 float MoviePrivate::maxTimeLoaded()
414 {
415     if (!m_qtMovie)
416         return 0;
417     return wkQTMovieMaxTimeLoaded(m_qtMovie.get()); 
418 }
419
420 unsigned MoviePrivate::bytesLoaded()
421 {
422     if (!m_qtMovie)
423         return 0;
424     float dur = duration();
425     float maxTime = maxTimeLoaded();
426     if (!dur)
427         return 0;
428     return totalBytes() * maxTime / dur;
429 }
430
431 bool MoviePrivate::totalBytesKnown()
432 {
433     return totalBytes() > 0;
434 }
435
436 unsigned MoviePrivate::totalBytes()
437 {
438     if (!m_qtMovie)
439         return 0;
440     return [[m_qtMovie.get() attributeForKey: QTMovieDataSizeAttribute] intValue];
441 }
442
443 void MoviePrivate::cancelLoad()
444 {
445     // FIXME better way to do this?
446     if (m_networkState < Movie::Loading || m_networkState == Movie::Loaded)
447         return;
448     
449     if (m_qtMovieView) {
450         [m_qtMovieView.get() removeFromSuperview];
451         m_qtMovieView = nil;
452     }
453     m_qtMovie = nil;
454     
455     updateStates();
456 }
457
458 void MoviePrivate::updateStates()
459 {
460     Movie::NetworkState oldNetworkState = m_networkState;
461     Movie::ReadyState oldReadyState = m_readyState;
462     
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;        
489     } else {
490         m_networkState = Movie::LoadFailed;
491         m_readyState = Movie::DataUnavailable; 
492     }
493
494     if (seeking())
495         m_readyState = Movie::DataUnavailable;
496     
497     if (m_networkState != oldNetworkState)
498         m_movie->networkStateChanged();
499     if (m_readyState != oldReadyState)
500         m_movie->readyStateChanged();
501 }
502
503 void MoviePrivate::loadStateChanged()
504 {
505     updateStates();
506 }
507
508 void MoviePrivate::rateChanged()
509 {
510     updateStates();
511 }
512
513 void MoviePrivate::sizeChanged()
514 {
515 }
516
517 void MoviePrivate::timeChanged()
518 {
519     m_previousTimeCueTimerFired = -1;
520     updateStates();
521     m_movie->timeChanged();
522 }
523
524 void MoviePrivate::volumeChanged()
525 {
526     m_movie->volumeChanged();
527 }
528
529 void MoviePrivate::didEnd()
530 {
531     m_cuePointTimer.stop();
532     m_startedPlaying = false;
533     updateStates();
534     m_movie->timeChanged();
535 }
536
537 void MoviePrivate::setRect(const IntRect& r) 
538
539     if (m_qtMovieView)
540         [m_qtMovieView.get() setFrame: r];
541 }
542
543 void MoviePrivate::setVisible(bool b)
544 {
545     if (b)
546         createQTMovieView();
547     else if (m_qtMovieView) {
548         [m_qtMovieView.get() removeFromSuperview];
549         m_qtMovieView = nil;
550     }
551 }
552
553 void MoviePrivate::paint(GraphicsContext* p, const IntRect& r)
554 {
555     if (p->paintingDisabled())
556         return;
557     NSView *view = m_qtMovieView.get();
558     if (view == nil)
559         return;
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];
565 }
566
567 void MoviePrivate::getSupportedTypes(HashSet<String>& types)
568 {
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);
574         if (!uti)
575             continue;
576         CFStringRef mime = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType);
577         CFRelease(uti);
578         if (!mime)
579             continue;
580         types.add(String((NSString*)mime));
581         CFRelease(mime);
582     }
583
584
585 }
586
587 @implementation WebCoreMovieObserver
588 -(id)initWithCallback:(WebCore::MoviePrivate *)c
589 {
590     _callback = c;
591     return [super init];
592 }
593 -(void)disconnect
594 {
595     [NSObject cancelPreviousPerformRequestsWithTarget:self];
596     _callback = 0;
597 }
598 -(void)loadStateChanged:(NSNotification *)notification
599 {
600     if (_delayCallbacks)
601         [self performSelector:@selector(loadStateChanged:) withObject:nil afterDelay:0];
602     else
603         _callback->loadStateChanged();
604 }
605 -(void)rateChanged:(NSNotification *)notification
606 {
607     if (_delayCallbacks)
608         [self performSelector:@selector(rateChanged:) withObject:nil afterDelay:0];
609     else
610         _callback->rateChanged();
611 }
612 -(void)sizeChanged:(NSNotification *)notification
613 {
614     if (_delayCallbacks)
615         [self performSelector:@selector(sizeChanged:) withObject:nil afterDelay:0];
616     else
617         _callback->sizeChanged();
618 }
619 -(void)timeChanged:(NSNotification *)notification
620 {
621     if (_delayCallbacks)
622         [self performSelector:@selector(timeChanged:) withObject:nil afterDelay:0];
623     else
624         _callback->timeChanged();
625 }
626 -(void)volumeChanged:(NSNotification *)notification
627 {
628     if (_delayCallbacks)
629         [self performSelector:@selector(volumeChanged:) withObject:nil afterDelay:0];
630     else
631         _callback->volumeChanged();
632 }
633 -(void)didEnd:(NSNotification *)notification
634 {
635     if (_delayCallbacks)
636         [self performSelector:@selector(didEnd:) withObject:nil afterDelay:0];
637     else
638         _callback->didEnd();
639 }
640 -(void)setDelayCallbacks:(BOOL)b
641 {
642     _delayCallbacks = b;
643 }
644 @end
645
646 #endif
647