2009-04-18 Pierre d'Herbemont <pdherbemont@apple.com>
[WebKit-https.git] / WebCore / platform / graphics / mac / MediaPlayerPrivateQTKit.mm
1 /*
2  * Copyright (C) 2007, 2008, 2009 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
30 #import "MediaPlayerPrivateQTKit.h"
31
32 #ifdef BUILDING_ON_TIGER
33 #import "AutodrainedPool.h"
34 #endif
35
36 #import "BlockExceptions.h"
37 #import "FrameView.h"
38 #import "GraphicsContext.h"
39 #import "KURL.h"
40 #import "SoftLinking.h"
41 #import "WebCoreSystemInterface.h"
42 #import <QTKit/QTKit.h>
43 #import <objc/objc-runtime.h>
44 #import <wtf/UnusedParam.h>
45
46 #if DRAW_FRAME_RATE
47 #import "Font.h"
48 #import "Frame.h"
49 #import "Document.h"
50 #import "RenderObject.h"
51 #import "RenderStyle.h"
52 #endif
53
54 #ifdef BUILDING_ON_TIGER
55 static IMP method_setImplementation(Method m, IMP imp)
56 {
57     IMP result = m->method_imp;
58     m->method_imp = imp;
59     return result;
60 }
61 #endif
62
63 SOFT_LINK_FRAMEWORK(QTKit)
64
65 SOFT_LINK(QTKit, QTMakeTime, QTTime, (long long timeValue, long timeScale), (timeValue, timeScale))
66
67 SOFT_LINK_CLASS(QTKit, QTMovie)
68 SOFT_LINK_CLASS(QTKit, QTMovieView)
69
70 SOFT_LINK_POINTER(QTKit, QTTrackMediaTypeAttribute, NSString *)
71 SOFT_LINK_POINTER(QTKit, QTMediaTypeAttribute, NSString *)
72 SOFT_LINK_POINTER(QTKit, QTMediaTypeBase, NSString *)
73 SOFT_LINK_POINTER(QTKit, QTMediaTypeMPEG, NSString *)
74 SOFT_LINK_POINTER(QTKit, QTMediaTypeSound, NSString *)
75 SOFT_LINK_POINTER(QTKit, QTMediaTypeText, NSString *)
76 SOFT_LINK_POINTER(QTKit, QTMediaTypeVideo, NSString *)
77 SOFT_LINK_POINTER(QTKit, QTMovieAskUnresolvedDataRefsAttribute, NSString *)
78 SOFT_LINK_POINTER(QTKit, QTMovieDataSizeAttribute, NSString *)
79 SOFT_LINK_POINTER(QTKit, QTMovieDidEndNotification, NSString *)
80 SOFT_LINK_POINTER(QTKit, QTMovieHasVideoAttribute, NSString *)
81 SOFT_LINK_POINTER(QTKit, QTMovieIsActiveAttribute, NSString *)
82 SOFT_LINK_POINTER(QTKit, QTMovieLoadStateAttribute, NSString *)
83 SOFT_LINK_POINTER(QTKit, QTMovieLoadStateDidChangeNotification, NSString *)
84 SOFT_LINK_POINTER(QTKit, QTMovieNaturalSizeAttribute, NSString *)
85 SOFT_LINK_POINTER(QTKit, QTMovieCurrentSizeAttribute, NSString *)
86 SOFT_LINK_POINTER(QTKit, QTMoviePreventExternalURLLinksAttribute, NSString *)
87 SOFT_LINK_POINTER(QTKit, QTMovieRateDidChangeNotification, NSString *)
88 SOFT_LINK_POINTER(QTKit, QTMovieSizeDidChangeNotification, NSString *)
89 SOFT_LINK_POINTER(QTKit, QTMovieTimeDidChangeNotification, NSString *)
90 SOFT_LINK_POINTER(QTKit, QTMovieTimeScaleAttribute, NSString *)
91 SOFT_LINK_POINTER(QTKit, QTMovieURLAttribute, NSString *)
92 SOFT_LINK_POINTER(QTKit, QTMovieVolumeDidChangeNotification, NSString *)
93 SOFT_LINK_POINTER(QTKit, QTSecurityPolicyNoCrossSiteAttribute, NSString *)
94 SOFT_LINK_POINTER(QTKit, QTVideoRendererWebKitOnlyNewImageAvailableNotification, NSString *)
95
96 #define QTMovie getQTMovieClass()
97 #define QTMovieView getQTMovieViewClass()
98
99 #define QTTrackMediaTypeAttribute getQTTrackMediaTypeAttribute()
100 #define QTMediaTypeAttribute getQTMediaTypeAttribute()
101 #define QTMediaTypeBase getQTMediaTypeBase()
102 #define QTMediaTypeMPEG getQTMediaTypeMPEG()
103 #define QTMediaTypeSound getQTMediaTypeSound()
104 #define QTMediaTypeText getQTMediaTypeText()
105 #define QTMediaTypeVideo getQTMediaTypeVideo()
106 #define QTMovieAskUnresolvedDataRefsAttribute getQTMovieAskUnresolvedDataRefsAttribute()
107 #define QTMovieDataSizeAttribute getQTMovieDataSizeAttribute()
108 #define QTMovieDidEndNotification getQTMovieDidEndNotification()
109 #define QTMovieHasVideoAttribute getQTMovieHasVideoAttribute()
110 #define QTMovieIsActiveAttribute getQTMovieIsActiveAttribute()
111 #define QTMovieLoadStateAttribute getQTMovieLoadStateAttribute()
112 #define QTMovieLoadStateDidChangeNotification getQTMovieLoadStateDidChangeNotification()
113 #define QTMovieNaturalSizeAttribute getQTMovieNaturalSizeAttribute()
114 #define QTMovieCurrentSizeAttribute getQTMovieCurrentSizeAttribute()
115 #define QTMoviePreventExternalURLLinksAttribute getQTMoviePreventExternalURLLinksAttribute()
116 #define QTMovieRateDidChangeNotification getQTMovieRateDidChangeNotification()
117 #define QTMovieSizeDidChangeNotification getQTMovieSizeDidChangeNotification()
118 #define QTMovieTimeDidChangeNotification getQTMovieTimeDidChangeNotification()
119 #define QTMovieTimeScaleAttribute getQTMovieTimeScaleAttribute()
120 #define QTMovieURLAttribute getQTMovieURLAttribute()
121 #define QTMovieVolumeDidChangeNotification getQTMovieVolumeDidChangeNotification()
122 #define QTSecurityPolicyNoCrossSiteAttribute getQTSecurityPolicyNoCrossSiteAttribute()
123 #define QTVideoRendererWebKitOnlyNewImageAvailableNotification getQTVideoRendererWebKitOnlyNewImageAvailableNotification()
124
125 // Older versions of the QTKit header don't have these constants.
126 #if !defined QTKIT_VERSION_MAX_ALLOWED || QTKIT_VERSION_MAX_ALLOWED <= QTKIT_VERSION_7_0
127 enum {
128     QTMovieLoadStateError = -1L,
129     QTMovieLoadStateLoaded  = 2000L,
130     QTMovieLoadStatePlayable = 10000L,
131     QTMovieLoadStatePlaythroughOK = 20000L,
132     QTMovieLoadStateComplete = 100000L
133 };
134 #endif
135
136 using namespace WebCore;
137 using namespace std;
138
139 @interface WebCoreMovieObserver : NSObject
140 {
141     MediaPlayerPrivate* m_callback;
142     NSView* m_view;
143     BOOL m_delayCallbacks;
144 }
145 -(id)initWithCallback:(MediaPlayerPrivate*)callback;
146 -(void)disconnect;
147 -(void)setView:(NSView*)view;
148 -(void)repaint;
149 -(void)setDelayCallbacks:(BOOL)shouldDelay;
150 -(void)loadStateChanged:(NSNotification *)notification;
151 -(void)rateChanged:(NSNotification *)notification;
152 -(void)sizeChanged:(NSNotification *)notification;
153 -(void)timeChanged:(NSNotification *)notification;
154 -(void)didEnd:(NSNotification *)notification;
155 @end
156
157 @protocol WebKitVideoRenderingDetails
158 -(void)setMovie:(id)movie;
159 -(void)drawInRect:(NSRect)rect;
160 @end
161
162 namespace WebCore {
163
164 #ifdef BUILDING_ON_TIGER
165 static const long minimumQuickTimeVersion = 0x07300000; // 7.3
166 #endif
167
168
169 MediaPlayerPrivateInterface* MediaPlayerPrivate::create(MediaPlayer* player) 
170
171     return new MediaPlayerPrivate(player);
172 }
173
174 void MediaPlayerPrivate::registerMediaEngine(MediaEngineRegistrar registrar)
175 {
176     if (isAvailable())
177         registrar(create, getSupportedTypes, supportsType);
178 }
179
180 MediaPlayerPrivate::MediaPlayerPrivate(MediaPlayer* player)
181     : m_player(player)
182     , m_objcObserver(AdoptNS, [[WebCoreMovieObserver alloc] initWithCallback:this])
183     , m_seekTo(-1)
184     , m_seekTimer(this, &MediaPlayerPrivate::seekTimerFired)
185     , m_networkState(MediaPlayer::Empty)
186     , m_readyState(MediaPlayer::HaveNothing)
187     , m_startedPlaying(false)
188     , m_isStreaming(false)
189     , m_visible(false)
190     , m_rect()
191     , m_enabledTrackCount(0)
192     , m_totalTrackCount(0)
193     , m_hasUnsupportedTracks(false)
194     , m_duration(-1.0f)
195 #if DRAW_FRAME_RATE
196     , m_frameCountWhilePlaying(0)
197     , m_timeStartedPlaying(0)
198     , m_timeStoppedPlaying(0)
199 #endif
200 {
201 }
202
203 MediaPlayerPrivate::~MediaPlayerPrivate()
204 {
205     tearDownVideoRendering();
206
207     [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()];
208     [m_objcObserver.get() disconnect];
209 }
210
211 void MediaPlayerPrivate::createQTMovie(const String& url)
212 {
213     [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()];
214     
215     if (m_qtMovie) {
216         destroyQTVideoRenderer();
217         m_qtMovie = 0;
218     }
219     
220     // Disable streaming support for now, <rdar://problem/5693967>
221     if (protocolIs(url, "rtsp"))
222         return;
223
224     NSURL *cocoaURL = KURL(url);
225     NSDictionary *movieAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
226                                      cocoaURL, QTMovieURLAttribute,
227                                      [NSNumber numberWithBool:YES], QTMoviePreventExternalURLLinksAttribute,
228                                      [NSNumber numberWithBool:YES], QTSecurityPolicyNoCrossSiteAttribute,
229                                      [NSNumber numberWithBool:NO], QTMovieAskUnresolvedDataRefsAttribute,
230                                      [NSNumber numberWithBool:YES], @"QTMovieOpenForPlaybackAttribute",     // FIXME: Use defined attribute when required version of QT supports this attribute
231                                      nil];
232     
233     NSError *error = nil;
234     m_qtMovie.adoptNS([[QTMovie alloc] initWithAttributes:movieAttributes error:&error]);
235     
236     // FIXME: Find a proper way to detect streaming content.
237     m_isStreaming = protocolIs(url, "rtsp");
238     
239     if (!m_qtMovie)
240         return;
241     
242     [m_qtMovie.get() setVolume:m_player->volume()];
243     
244     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
245                                              selector:@selector(loadStateChanged:) 
246                                                  name:QTMovieLoadStateDidChangeNotification 
247                                                object:m_qtMovie.get()];
248
249     // In updateState(), we track when maxTimeLoaded() == duration().
250     // In newer version of QuickTime, a notification is emitted when maxTimeLoaded changes.
251     // In older version of QuickTime, QTMovieLoadStateDidChangeNotification be fired.
252     if (NSString *maxTimeLoadedChangeNotification = wkQTMovieMaxTimeLoadedChangeNotification()) {
253         [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
254                                                  selector:@selector(loadStateChanged:) 
255                                                      name:maxTimeLoadedChangeNotification
256                                                    object:m_qtMovie.get()];        
257     }
258
259     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
260                                              selector:@selector(rateChanged:) 
261                                                  name:QTMovieRateDidChangeNotification 
262                                                object:m_qtMovie.get()];
263     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
264                                              selector:@selector(sizeChanged:) 
265                                                  name:QTMovieSizeDidChangeNotification 
266                                                object:m_qtMovie.get()];
267     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
268                                              selector:@selector(timeChanged:) 
269                                                  name:QTMovieTimeDidChangeNotification 
270                                                object:m_qtMovie.get()];
271     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
272                                              selector:@selector(didEnd:) 
273                                                  name:QTMovieDidEndNotification 
274                                                object:m_qtMovie.get()];
275 }
276
277 static void mainThreadSetNeedsDisplay(id self, SEL)
278 {
279     id movieView = [self superview];
280     ASSERT(!movieView || [movieView isKindOfClass:[QTMovieView class]]);
281     if (!movieView || ![movieView isKindOfClass:[QTMovieView class]])
282         return;
283
284     WebCoreMovieObserver* delegate = [movieView delegate];
285     ASSERT(!delegate || [delegate isKindOfClass:[WebCoreMovieObserver class]]);
286     if (!delegate || ![delegate isKindOfClass:[WebCoreMovieObserver class]])
287         return;
288
289     [delegate repaint];
290 }
291
292 static Class QTVideoRendererClass()
293 {
294      static Class QTVideoRendererWebKitOnlyClass = NSClassFromString(@"QTVideoRendererWebKitOnly");
295      return QTVideoRendererWebKitOnlyClass;
296 }
297
298 void MediaPlayerPrivate::createQTMovieView()
299 {
300     detachQTMovieView();
301
302     static bool addedCustomMethods = false;
303     if (!m_player->inMediaDocument() && !addedCustomMethods) {
304         Class QTMovieContentViewClass = NSClassFromString(@"QTMovieContentView");
305         ASSERT(QTMovieContentViewClass);
306
307         Method mainThreadSetNeedsDisplayMethod = class_getInstanceMethod(QTMovieContentViewClass, @selector(_mainThreadSetNeedsDisplay));
308         ASSERT(mainThreadSetNeedsDisplayMethod);
309
310         method_setImplementation(mainThreadSetNeedsDisplayMethod, reinterpret_cast<IMP>(mainThreadSetNeedsDisplay));
311         addedCustomMethods = true;
312     }
313
314     // delay callbacks as we *will* get notifications during setup
315     [m_objcObserver.get() setDelayCallbacks:YES];
316
317     m_qtMovieView.adoptNS([[QTMovieView alloc] init]);
318     setSize(m_player->size());
319     NSView* parentView = m_player->frameView()->documentView();
320     [parentView addSubview:m_qtMovieView.get()];
321 #ifdef BUILDING_ON_TIGER
322     // setDelegate: isn't a public call in Tiger, so use performSelector to keep the compiler happy
323     [m_qtMovieView.get() performSelector:@selector(setDelegate:) withObject:m_objcObserver.get()];    
324 #else
325     [m_qtMovieView.get() setDelegate:m_objcObserver.get()];
326 #endif
327     [m_objcObserver.get() setView:m_qtMovieView.get()];
328     [m_qtMovieView.get() setMovie:m_qtMovie.get()];
329     [m_qtMovieView.get() setControllerVisible:NO];
330     [m_qtMovieView.get() setPreservesAspectRatio:NO];
331     // the area not covered by video should be transparent
332     [m_qtMovieView.get() setFillColor:[NSColor clearColor]];
333
334     // If we're in a media document, allow QTMovieView to render in its default mode;
335     // otherwise tell it to draw synchronously.
336     // Note that we expect mainThreadSetNeedsDisplay to be invoked only when synchronous drawing is requested.
337     if (!m_player->inMediaDocument())
338         wkQTMovieViewSetDrawSynchronously(m_qtMovieView.get(), YES);
339
340     [m_objcObserver.get() setDelayCallbacks:NO];
341 }
342
343 void MediaPlayerPrivate::detachQTMovieView()
344 {
345     if (m_qtMovieView) {
346         [m_objcObserver.get() setView:nil];
347 #ifdef BUILDING_ON_TIGER
348         // setDelegate: isn't a public call in Tiger, so use performSelector to keep the compiler happy
349         [m_qtMovieView.get() performSelector:@selector(setDelegate:) withObject:nil];    
350 #else
351         [m_qtMovieView.get() setDelegate:nil];
352 #endif
353         [m_qtMovieView.get() removeFromSuperview];
354         m_qtMovieView = nil;
355     }
356 }
357
358 void MediaPlayerPrivate::createQTVideoRenderer()
359 {
360     destroyQTVideoRenderer();
361
362     m_qtVideoRenderer.adoptNS([[QTVideoRendererClass() alloc] init]);
363     if (!m_qtVideoRenderer)
364         return;
365     
366     // associate our movie with our instance of QTVideoRendererWebKitOnly
367     [(id<WebKitVideoRenderingDetails>)m_qtVideoRenderer.get() setMovie:m_qtMovie.get()];    
368
369     // listen to QTVideoRendererWebKitOnly's QTVideoRendererWebKitOnlyNewImageDidBecomeAvailableNotification
370     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
371                                              selector:@selector(newImageAvailable:)
372                                                  name:QTVideoRendererWebKitOnlyNewImageAvailableNotification
373                                                object:m_qtVideoRenderer.get()];
374 }
375
376 void MediaPlayerPrivate::destroyQTVideoRenderer()
377 {
378     if (!m_qtVideoRenderer)
379         return;
380
381     // stop observing the renderer's notifications before we toss it
382     [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()
383                                                     name:QTVideoRendererWebKitOnlyNewImageAvailableNotification
384                                                   object:m_qtVideoRenderer.get()];
385
386     // disassociate our movie from our instance of QTVideoRendererWebKitOnly
387     [(id<WebKitVideoRenderingDetails>)m_qtVideoRenderer.get() setMovie:nil];    
388
389     m_qtVideoRenderer = nil;
390 }
391
392 void MediaPlayerPrivate::setUpVideoRendering()
393 {
394     if (!m_player->frameView() || !m_qtMovie)
395         return;
396
397     if (m_player->inMediaDocument() || !QTVideoRendererClass() )
398         createQTMovieView();
399     else
400         createQTVideoRenderer();
401 }
402
403 void MediaPlayerPrivate::tearDownVideoRendering()
404 {
405     if (m_qtMovieView)
406         detachQTMovieView();
407     else
408         destroyQTVideoRenderer();
409 }
410
411 QTTime MediaPlayerPrivate::createQTTime(float time) const
412 {
413     if (!metaDataAvailable())
414         return QTMakeTime(0, 600);
415     long timeScale = [[m_qtMovie.get() attributeForKey:QTMovieTimeScaleAttribute] longValue];
416     return QTMakeTime(time * timeScale, timeScale);
417 }
418
419 void MediaPlayerPrivate::load(const String& url)
420 {
421     if (m_networkState != MediaPlayer::Loading) {
422         m_networkState = MediaPlayer::Loading;
423         m_player->networkStateChanged();
424     }
425     if (m_readyState != MediaPlayer::HaveNothing) {
426         m_readyState = MediaPlayer::HaveNothing;
427         m_player->readyStateChanged();
428     }
429     cancelSeek();
430     
431     [m_objcObserver.get() setDelayCallbacks:YES];
432
433     createQTMovie(url);
434
435     [m_objcObserver.get() loadStateChanged:nil];
436     [m_objcObserver.get() setDelayCallbacks:NO];
437 }
438
439 void MediaPlayerPrivate::play()
440 {
441     if (!metaDataAvailable())
442         return;
443     m_startedPlaying = true;
444 #if DRAW_FRAME_RATE
445     m_frameCountWhilePlaying = 0;
446 #endif
447     [m_objcObserver.get() setDelayCallbacks:YES];
448     [m_qtMovie.get() setRate:m_player->rate()];
449     [m_objcObserver.get() setDelayCallbacks:NO];
450 }
451
452 void MediaPlayerPrivate::pause()
453 {
454     if (!metaDataAvailable())
455         return;
456     m_startedPlaying = false;
457 #if DRAW_FRAME_RATE
458     m_timeStoppedPlaying = [NSDate timeIntervalSinceReferenceDate];
459 #endif
460     [m_objcObserver.get() setDelayCallbacks:YES];
461     [m_qtMovie.get() stop];
462     [m_objcObserver.get() setDelayCallbacks:NO];
463 }
464
465 float MediaPlayerPrivate::duration() const
466 {
467     if (!metaDataAvailable())
468         return 0;
469     QTTime time = [m_qtMovie.get() duration];
470     if (time.flags == kQTTimeIsIndefinite)
471         return numeric_limits<float>::infinity();
472     return static_cast<float>(time.timeValue) / time.timeScale;
473 }
474
475 float MediaPlayerPrivate::currentTime() const
476 {
477     if (!metaDataAvailable())
478         return 0;
479     QTTime time = [m_qtMovie.get() currentTime];
480     return static_cast<float>(time.timeValue) / time.timeScale;
481 }
482
483 void MediaPlayerPrivate::seek(float time)
484 {
485     cancelSeek();
486     
487     if (!metaDataAvailable())
488         return;
489     
490     if (time > duration())
491         time = duration();
492
493     m_seekTo = time;
494     if (maxTimeLoaded() >= m_seekTo)
495         doSeek();
496     else 
497         m_seekTimer.start(0, 0.5f);
498 }
499
500 void MediaPlayerPrivate::doSeek() 
501 {
502     QTTime qttime = createQTTime(m_seekTo);
503     // setCurrentTime generates several event callbacks, update afterwards
504     [m_objcObserver.get() setDelayCallbacks:YES];
505     float oldRate = [m_qtMovie.get() rate];
506     [m_qtMovie.get() setRate:0];
507     [m_qtMovie.get() setCurrentTime:qttime];
508     float timeAfterSeek = currentTime();
509     // restore playback only if not at end, othewise QTMovie will loop
510     if (oldRate && timeAfterSeek < duration())
511         [m_qtMovie.get() setRate:oldRate];
512     cancelSeek();
513     [m_objcObserver.get() setDelayCallbacks:NO];
514 }
515
516 void MediaPlayerPrivate::cancelSeek()
517 {
518     m_seekTo = -1;
519     m_seekTimer.stop();
520 }
521
522 void MediaPlayerPrivate::seekTimerFired(Timer<MediaPlayerPrivate>*)
523 {        
524     if (!metaDataAvailable()|| !seeking() || currentTime() == m_seekTo) {
525         cancelSeek();
526         updateStates();
527         m_player->timeChanged(); 
528         return;
529     } 
530     
531     if (maxTimeLoaded() >= m_seekTo)
532         doSeek();
533     else {
534         MediaPlayer::NetworkState state = networkState();
535         if (state == MediaPlayer::Empty || state == MediaPlayer::Loaded) {
536             cancelSeek();
537             updateStates();
538             m_player->timeChanged();
539         }
540     }
541 }
542
543 void MediaPlayerPrivate::setEndTime(float)
544 {
545 }
546
547 bool MediaPlayerPrivate::paused() const
548 {
549     if (!metaDataAvailable())
550         return true;
551     return [m_qtMovie.get() rate] == 0;
552 }
553
554 bool MediaPlayerPrivate::seeking() const
555 {
556     if (!metaDataAvailable())
557         return false;
558     return m_seekTo >= 0;
559 }
560
561 IntSize MediaPlayerPrivate::naturalSize() const
562 {
563     if (!metaDataAvailable())
564         return IntSize();
565
566     // In spite of the name of this method, return QTMovieCurrentSizeAttribute rather than
567     // QTMovieNaturalSizeAttribute because we need to return:
568     //
569     //    ... the dimensions of the resource in CSS pixels after taking into account the resource's 
570     //    dimensions, aspect ratio, clean aperture, resolution, and so forth, as defined for the 
571     //    format used by the resource
572
573     return IntSize([[m_qtMovie.get() attributeForKey:QTMovieCurrentSizeAttribute] sizeValue]);
574 }
575
576 bool MediaPlayerPrivate::hasVideo() const
577 {
578     if (!metaDataAvailable())
579         return false;
580     return [[m_qtMovie.get() attributeForKey:QTMovieHasVideoAttribute] boolValue];
581 }
582
583 void MediaPlayerPrivate::setVolume(float volume)
584 {
585     if (!metaDataAvailable())
586         return;
587     [m_qtMovie.get() setVolume:volume];  
588 }
589
590 void MediaPlayerPrivate::setRate(float rate)
591 {
592     if (!metaDataAvailable())
593         return;
594     if (!paused())
595         [m_qtMovie.get() setRate:rate];
596 }
597
598 int MediaPlayerPrivate::dataRate() const
599 {
600     if (!metaDataAvailable())
601         return 0;
602     return wkQTMovieDataRate(m_qtMovie.get()); 
603 }
604
605
606 float MediaPlayerPrivate::maxTimeBuffered() const
607 {
608     // rtsp streams are not buffered
609     return m_isStreaming ? 0 : maxTimeLoaded();
610 }
611
612 float MediaPlayerPrivate::maxTimeSeekable() const
613 {
614     // infinite duration means live stream
615     return isinf(duration()) ? 0 : maxTimeLoaded();
616 }
617
618 float MediaPlayerPrivate::maxTimeLoaded() const
619 {
620     if (!metaDataAvailable())
621         return 0;
622     return wkQTMovieMaxTimeLoaded(m_qtMovie.get()); 
623 }
624
625 unsigned MediaPlayerPrivate::bytesLoaded() const
626 {
627     float dur = duration();
628     if (!dur)
629         return 0;
630     return totalBytes() * maxTimeLoaded() / dur;
631 }
632
633 bool MediaPlayerPrivate::totalBytesKnown() const
634 {
635     return totalBytes() > 0;
636 }
637
638 unsigned MediaPlayerPrivate::totalBytes() const
639 {
640     if (!metaDataAvailable())
641         return 0;
642     return [[m_qtMovie.get() attributeForKey:QTMovieDataSizeAttribute] intValue];
643 }
644
645 void MediaPlayerPrivate::cancelLoad()
646 {
647     // FIXME: Is there a better way to check for this?
648     if (m_networkState < MediaPlayer::Loading || m_networkState == MediaPlayer::Loaded)
649         return;
650     
651     tearDownVideoRendering();
652     m_qtMovie = nil;
653     
654     updateStates();
655 }
656
657 void MediaPlayerPrivate::updateStates()
658 {
659     MediaPlayer::NetworkState oldNetworkState = m_networkState;
660     MediaPlayer::ReadyState oldReadyState = m_readyState;
661     
662     long loadState = m_qtMovie ? [[m_qtMovie.get() attributeForKey:QTMovieLoadStateAttribute] longValue] : static_cast<long>(QTMovieLoadStateError);
663
664     if (loadState >= QTMovieLoadStateLoaded && m_readyState < MediaPlayer::HaveMetadata) {
665         disableUnsupportedTracks();
666         if (m_player->inMediaDocument()) {
667             if (!m_enabledTrackCount || m_enabledTrackCount != m_totalTrackCount) {
668                 // This is a type of media that we do not handle directly with a <video> 
669                 // element, likely streamed media or QuickTime VR. Tell the MediaPlayerClient
670                 // that we noticed.
671                 sawUnsupportedTracks();
672                 return;
673             }
674         } else if (!m_enabledTrackCount) {
675             loadState = QTMovieLoadStateError;
676         }
677     }
678
679     BOOL completelyLoaded = !m_isStreaming && (loadState >= QTMovieLoadStateComplete);
680
681     // Note: QT indicates that we are fully loaded with QTMovieLoadStateComplete.
682     // However newer versions of QT do not, so we check maxTimeLoaded against duration.
683     if (!completelyLoaded && !m_isStreaming && metaDataAvailable())
684         completelyLoaded = maxTimeLoaded() == duration();
685
686     if (completelyLoaded) {
687         // "Loaded" is reserved for fully buffered movies, never the case when streaming
688         m_networkState = MediaPlayer::Loaded;
689         m_readyState = MediaPlayer::HaveEnoughData;
690     } else if (loadState >= QTMovieLoadStatePlaythroughOK) {
691         m_readyState = MediaPlayer::HaveEnoughData;
692         m_networkState = MediaPlayer::Loading;
693     } else if (loadState >= QTMovieLoadStatePlayable) {
694         // FIXME: This might not work correctly in streaming case, <rdar://problem/5693967>
695         m_readyState = currentTime() < maxTimeLoaded() ? MediaPlayer::HaveFutureData : MediaPlayer::HaveCurrentData;
696         m_networkState = MediaPlayer::Loading;
697     } else if (loadState >= QTMovieLoadStateLoaded) {
698         m_readyState = MediaPlayer::HaveMetadata;
699         m_networkState = MediaPlayer::Loading;
700     } else if (loadState > QTMovieLoadStateError) {
701         m_readyState = MediaPlayer::HaveNothing;
702         m_networkState = MediaPlayer::Loading;
703     } else {
704         if (m_player->inMediaDocument()) {
705             // Something went wrong in the loading of media within a standalone file. 
706             // This can occur with chained refmovies pointing to streamed media.
707             sawUnsupportedTracks();
708             return;
709         }
710
711         float loaded = maxTimeLoaded();
712         if (!loaded)
713             m_readyState = MediaPlayer::HaveNothing;
714
715         if (!m_enabledTrackCount)
716             m_networkState = MediaPlayer::FormatError;
717         else {
718             // FIXME: We should differentiate between load/network errors and decode errors <rdar://problem/5605692>
719             if (loaded > 0)
720                 m_networkState = MediaPlayer::DecodeError;
721             else
722                 m_readyState = MediaPlayer::HaveNothing;
723         }
724     }
725
726     if (seeking())
727         m_readyState = MediaPlayer::HaveNothing;
728
729     if (m_networkState != oldNetworkState)
730         m_player->networkStateChanged();
731     if (m_readyState != oldReadyState)
732         m_player->readyStateChanged();
733
734     if (loadState >= QTMovieLoadStateLoaded && (!m_qtMovieView && !m_qtVideoRenderer) && m_player->visible())
735         setUpVideoRendering();
736
737     if (loadState >= QTMovieLoadStateLoaded) {
738         float dur = duration();
739         if (dur != m_duration) {
740             if (m_duration != -1.0f)
741                 m_player->durationChanged();
742             m_duration = dur;
743         }
744     }
745 }
746
747 void MediaPlayerPrivate::loadStateChanged()
748 {
749     updateStates();
750 }
751
752 void MediaPlayerPrivate::rateChanged()
753 {
754     updateStates();
755     if (!m_hasUnsupportedTracks)
756         m_player->rateChanged();
757 }
758
759 void MediaPlayerPrivate::sizeChanged()
760 {
761     if (!m_hasUnsupportedTracks)
762         m_player->sizeChanged();
763 }
764
765 void MediaPlayerPrivate::timeChanged()
766 {
767     updateStates();
768     if (!m_hasUnsupportedTracks)
769         m_player->timeChanged();
770 }
771
772 void MediaPlayerPrivate::didEnd()
773 {
774     m_startedPlaying = false;
775 #if DRAW_FRAME_RATE
776     m_timeStoppedPlaying = [NSDate timeIntervalSinceReferenceDate];
777 #endif
778     updateStates();
779     if (!m_hasUnsupportedTracks)
780         m_player->timeChanged();
781 }
782
783 void MediaPlayerPrivate::setSize(const IntSize&) 
784
785     // Don't resize the view now because [view setFrame] also resizes the movie itself, and because
786     // the renderer calls this function immediately when we report a size change (QTMovieSizeDidChangeNotification)
787     // we can get into a feedback loop observing the size change and resetting the size, and this can cause
788     // QuickTime to miss resetting a movie's size when the media size changes (as happens with an rtsp movie
789     // once the rtsp server sends the track sizes). Instead we remember the size passed to paint() and resize
790     // the view when it changes.
791     // <rdar://problem/6336092> REGRESSION: rtsp movie does not resize correctly
792 }
793
794 void MediaPlayerPrivate::setVisible(bool b)
795 {
796     if (m_visible != b) {
797         m_visible = b;
798         if (b) {
799             if (m_readyState >= MediaPlayer::HaveMetadata)
800                 setUpVideoRendering();
801         } else
802             tearDownVideoRendering();
803     }
804 }
805
806 void MediaPlayerPrivate::repaint()
807 {
808 #if DRAW_FRAME_RATE
809     if (m_startedPlaying) {
810         m_frameCountWhilePlaying++;
811         // to eliminate preroll costs from our calculation,
812         // our frame rate calculation excludes the first frame drawn after playback starts
813         if (1==m_frameCountWhilePlaying)
814             m_timeStartedPlaying = [NSDate timeIntervalSinceReferenceDate];
815     }
816 #endif
817     m_player->repaint();
818 }
819
820 void MediaPlayerPrivate::paint(GraphicsContext* context, const IntRect& r)
821 {
822     if (context->paintingDisabled())
823         return;
824     NSView *view = m_qtMovieView.get();
825     id qtVideoRenderer = m_qtVideoRenderer.get();
826     if (!view && !qtVideoRenderer)
827         return;
828
829     [m_objcObserver.get() setDelayCallbacks:YES];
830     BEGIN_BLOCK_OBJC_EXCEPTIONS;
831     context->save();
832     context->translate(r.x(), r.y() + r.height());
833     context->scale(FloatSize(1.0f, -1.0f));
834     context->setImageInterpolationQuality(InterpolationLow);
835     IntRect paintRect(IntPoint(0, 0), IntSize(r.width(), r.height()));
836     
837 #ifdef BUILDING_ON_TIGER
838     AutodrainedPool pool;
839 #endif
840     NSGraphicsContext* newContext = [NSGraphicsContext graphicsContextWithGraphicsPort:context->platformContext() flipped:NO];
841
842     // draw the current video frame
843     if (qtVideoRenderer) {
844         [NSGraphicsContext saveGraphicsState];
845         [NSGraphicsContext setCurrentContext:newContext];
846         [(id<WebKitVideoRenderingDetails>)qtVideoRenderer drawInRect:paintRect];
847         [NSGraphicsContext restoreGraphicsState];
848     } else {
849         if (m_rect != r) {
850              m_rect = r;
851             if (m_player->inMediaDocument()) {
852                 // the QTMovieView needs to be placed in the proper location for document mode
853                 [view setFrame:m_rect];
854             }
855             else {
856                 // We don't really need the QTMovieView in any specific location so let's just get it out of the way
857                 // where it won't intercept events or try to bring up the context menu.
858                 IntRect farAwayButCorrectSize(m_rect);
859                 farAwayButCorrectSize.move(-1000000, -1000000);
860                 [view setFrame:farAwayButCorrectSize];
861             }
862         }
863
864         [view displayRectIgnoringOpacity:paintRect inContext:newContext];
865     }
866
867 #if DRAW_FRAME_RATE
868     // Draw the frame rate only after having played more than 10 frames.
869     if (m_frameCountWhilePlaying > 10) {
870         Frame* frame = m_player->frameView() ? m_player->frameView()->frame() : NULL;
871         Document* document = frame ? frame->document() : NULL;
872         RenderObject* renderer = document ? document->renderer() : NULL;
873         RenderStyle* styleToUse = renderer ? renderer->style() : NULL;
874         if (styleToUse) {
875             double frameRate = (m_frameCountWhilePlaying - 1) / ( m_startedPlaying ? ([NSDate timeIntervalSinceReferenceDate] - m_timeStartedPlaying) :
876                 (m_timeStoppedPlaying - m_timeStartedPlaying) );
877             String text = String::format("%1.2f", frameRate);
878             TextRun textRun(text.characters(), text.length());
879             const Color color(255, 0, 0);
880             context->scale(FloatSize(1.0f, -1.0f));    
881             context->setFont(styleToUse->font());
882             context->setStrokeColor(color);
883             context->setStrokeStyle(SolidStroke);
884             context->setStrokeThickness(1.0f);
885             context->setFillColor(color);
886             context->drawText(textRun, IntPoint(2, -3));
887         }
888     }
889 #endif
890
891     context->restore();
892     END_BLOCK_OBJC_EXCEPTIONS;
893     [m_objcObserver.get() setDelayCallbacks:NO];
894 }
895
896 static void addFileTypesToCache(NSArray * fileTypes, HashSet<String> &cache)
897 {
898     int count = [fileTypes count];
899     for (int n = 0; n < count; n++) {
900         CFStringRef ext = reinterpret_cast<CFStringRef>([fileTypes objectAtIndex:n]);
901         RetainPtr<CFStringRef> uti(AdoptCF, UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, ext, NULL));
902         if (!uti)
903             continue;
904         RetainPtr<CFStringRef> mime(AdoptCF, UTTypeCopyPreferredTagWithClass(uti.get(), kUTTagClassMIMEType));
905         if (!mime)
906             continue;
907         cache.add(mime.get());
908     }    
909 }
910
911 static HashSet<String> mimeCommonTypesCache()
912 {
913     DEFINE_STATIC_LOCAL(HashSet<String>, cache, ());
914     static bool typeListInitialized = false;
915
916     if (!typeListInitialized) {
917         typeListInitialized = true;
918         NSArray* fileTypes = [QTMovie movieFileTypes:QTIncludeCommonTypes];
919         addFileTypesToCache(fileTypes, cache);
920     }
921     
922     return cache;
923
924
925 static HashSet<String> mimeModernTypesCache()
926 {
927     DEFINE_STATIC_LOCAL(HashSet<String>, cache, ());
928     static bool typeListInitialized = false;
929     
930     if (!typeListInitialized) {
931         typeListInitialized = true;
932         NSArray* fileTypes = [QTMovie movieFileTypes:(QTMovieFileTypeOptions)wkQTIncludeOnlyModernMediaFileTypes()];
933         addFileTypesToCache(fileTypes, cache);
934     }
935     
936     return cache;
937
938
939 void MediaPlayerPrivate::getSupportedTypes(HashSet<String>& types)
940 {
941     // Note: this method starts QTKitServer if it isn't already running when in 64-bit because it has to return the list 
942     // of every MIME type supported by QTKit.
943     types = mimeCommonTypesCache();
944
945
946 MediaPlayer::SupportsType MediaPlayerPrivate::supportsType(const String& type, const String& codecs)
947 {
948     // Only return "IsSupported" if there is no codecs parameter for now as there is no way to ask QT if it supports an
949     // extended MIME type yet.
950
951     // We check the "modern" type cache first, as it doesn't require QTKitServer to start.
952     if (mimeModernTypesCache().contains(type) || mimeCommonTypesCache().contains(type))
953         return (codecs && !codecs.isEmpty() ? MediaPlayer::MayBeSupported : MediaPlayer::IsSupported);
954
955     return MediaPlayer::IsNotSupported;
956 }
957
958 bool MediaPlayerPrivate::isAvailable()
959 {
960 #ifdef BUILDING_ON_TIGER
961     SInt32 version;
962     OSErr result;
963     result = Gestalt(gestaltQuickTime, &version);
964     if (result != noErr) {
965         LOG_ERROR("No QuickTime available. Disabling <video> and <audio> support.");
966         return false;
967     }
968     if (version < minimumQuickTimeVersion) {
969         LOG_ERROR("QuickTime version %x detected, at least %x required. Disabling <video> and <audio> support.", version, minimumQuickTimeVersion);
970         return false;
971     }
972     return true;
973 #else
974     // On 10.5 and higher, QuickTime will always be new enough for <video> and <audio> support, so we just check that the framework can be loaded.
975     return QTKitLibrary();
976 #endif
977 }
978     
979 void MediaPlayerPrivate::disableUnsupportedTracks()
980 {
981     if (!m_qtMovie) {
982         m_enabledTrackCount = 0;
983         m_totalTrackCount = 0;
984         return;
985     }
986     
987     static HashSet<String>* allowedTrackTypes = 0;
988     if (!allowedTrackTypes) {
989         allowedTrackTypes = new HashSet<String>;
990         allowedTrackTypes->add(QTMediaTypeVideo);
991         allowedTrackTypes->add(QTMediaTypeSound);
992         allowedTrackTypes->add(QTMediaTypeText);
993         allowedTrackTypes->add(QTMediaTypeBase);
994         allowedTrackTypes->add(QTMediaTypeMPEG);
995         allowedTrackTypes->add("clcp");
996         allowedTrackTypes->add("sbtl");
997     }
998     
999     NSArray *tracks = [m_qtMovie.get() tracks];
1000     
1001     m_totalTrackCount = [tracks count];
1002     m_enabledTrackCount = m_totalTrackCount;
1003     for (unsigned trackIndex = 0; trackIndex < m_totalTrackCount; trackIndex++) {
1004         // Grab the track at the current index. If there isn't one there, then
1005         // we can move onto the next one.
1006         QTTrack *track = [tracks objectAtIndex:trackIndex];
1007         if (!track)
1008             continue;
1009         
1010         // Check to see if the track is disabled already, we should move along.
1011         // We don't need to re-disable it.
1012         if (![track isEnabled])
1013             continue;
1014         
1015         // Get the track's media type.
1016         NSString *mediaType = [track attributeForKey:QTTrackMediaTypeAttribute];
1017         if (!mediaType)
1018             continue;
1019
1020         // Test whether the media type is in our white list.
1021         if (!allowedTrackTypes->contains(mediaType)) {
1022             // If this track type is not allowed, then we need to disable it.
1023             [track setEnabled:NO];
1024             --m_enabledTrackCount;
1025         }
1026
1027         // Disable chapter tracks. These are most likely to lead to trouble, as
1028         // they will be composited under the video tracks, forcing QT to do extra
1029         // work.
1030         QTTrack *chapterTrack = [track performSelector:@selector(chapterlist)];
1031         if (!chapterTrack)
1032             continue;
1033         
1034         // Try to grab the media for the track.
1035         QTMedia *chapterMedia = [chapterTrack media];
1036         if (!chapterMedia)
1037             continue;
1038         
1039         // Grab the media type for this track.
1040         id chapterMediaType = [chapterMedia attributeForKey:QTMediaTypeAttribute];
1041         if (!chapterMediaType)
1042             continue;
1043         
1044         // Check to see if the track is a video track. We don't care about
1045         // other non-video tracks.
1046         if (![chapterMediaType isEqual:QTMediaTypeVideo])
1047             continue;
1048         
1049         // Check to see if the track is already disabled. If it is, we
1050         // should move along.
1051         if (![chapterTrack isEnabled])
1052             continue;
1053         
1054         // Disable the evil, evil track.
1055         [chapterTrack setEnabled:NO];
1056         --m_enabledTrackCount;
1057     }
1058 }
1059
1060 void MediaPlayerPrivate::sawUnsupportedTracks()
1061 {
1062     m_player->mediaPlayerClient()->mediaPlayerSawUnsupportedTracks(m_player);
1063     m_hasUnsupportedTracks = true;
1064 }
1065
1066 }
1067
1068 @implementation WebCoreMovieObserver
1069
1070 - (id)initWithCallback:(MediaPlayerPrivate*)callback
1071 {
1072     m_callback = callback;
1073     return [super init];
1074 }
1075
1076 - (void)disconnect
1077 {
1078     [NSObject cancelPreviousPerformRequestsWithTarget:self];
1079     m_callback = 0;
1080 }
1081
1082 -(NSMenu*)menuForEventDelegate:(NSEvent*)theEvent
1083 {
1084     // Get the contextual menu from the QTMovieView's superview, the frame view
1085     return [[m_view superview] menuForEvent:theEvent];
1086 }
1087
1088 -(void)setView:(NSView*)view
1089 {
1090     m_view = view;
1091 }
1092
1093 -(void)repaint
1094 {
1095     if (m_delayCallbacks)
1096         [self performSelector:_cmd withObject:nil afterDelay:0.];
1097     else if (m_callback)
1098         m_callback->repaint();
1099 }
1100
1101 - (void)loadStateChanged:(NSNotification *)unusedNotification
1102 {
1103     UNUSED_PARAM(unusedNotification);
1104     if (m_delayCallbacks)
1105         [self performSelector:_cmd withObject:nil afterDelay:0];
1106     else
1107         m_callback->loadStateChanged();
1108 }
1109
1110 - (void)rateChanged:(NSNotification *)unusedNotification
1111 {
1112     UNUSED_PARAM(unusedNotification);
1113     if (m_delayCallbacks)
1114         [self performSelector:_cmd withObject:nil afterDelay:0];
1115     else
1116         m_callback->rateChanged();
1117 }
1118
1119 - (void)sizeChanged:(NSNotification *)unusedNotification
1120 {
1121     UNUSED_PARAM(unusedNotification);
1122     if (m_delayCallbacks)
1123         [self performSelector:_cmd withObject:nil afterDelay:0];
1124     else
1125         m_callback->sizeChanged();
1126 }
1127
1128 - (void)timeChanged:(NSNotification *)unusedNotification
1129 {
1130     UNUSED_PARAM(unusedNotification);
1131     if (m_delayCallbacks)
1132         [self performSelector:_cmd withObject:nil afterDelay:0];
1133     else
1134         m_callback->timeChanged();
1135 }
1136
1137 - (void)didEnd:(NSNotification *)unusedNotification
1138 {
1139     UNUSED_PARAM(unusedNotification);
1140     if (m_delayCallbacks)
1141         [self performSelector:_cmd withObject:nil afterDelay:0];
1142     else
1143         m_callback->didEnd();
1144 }
1145
1146 - (void)newImageAvailable:(NSNotification *)unusedNotification
1147 {
1148     UNUSED_PARAM(unusedNotification);
1149     [self repaint];
1150 }
1151
1152 - (void)setDelayCallbacks:(BOOL)shouldDelay
1153 {
1154     m_delayCallbacks = shouldDelay;
1155 }
1156
1157 @end
1158
1159 #endif