blob: 85c7a9e48987450c3c8ce8bee48a410dc7c6502a [file] [log] [blame]
/*
* Copyright (C) 2007 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "config.h"
#if ENABLE(VIDEO)
#import "MediaPlayerPrivateQTKit.h"
#import "BlockExceptions.h"
#import "GraphicsContext.h"
#import "KURL.h"
#import "ScrollView.h"
#import "SoftLinking.h"
#import "WebCoreSystemInterface.h"
#import <QTKit/QTKit.h>
#import <objc/objc-runtime.h>
#ifdef BUILDING_ON_TIGER
static IMP method_setImplementation(Method m, IMP imp)
{
IMP result = m->method_imp;
m->method_imp = imp;
return result;
}
#endif
SOFT_LINK_FRAMEWORK(QTKit)
SOFT_LINK(QTKit, QTMakeTime, QTTime, (long long timeValue, long timeScale), (timeValue, timeScale))
SOFT_LINK_CLASS(QTKit, QTMovie)
SOFT_LINK_CLASS(QTKit, QTMovieView)
SOFT_LINK_POINTER(QTKit, QTMediaTypeAttribute, NSString *)
SOFT_LINK_POINTER(QTKit, QTMediaTypeBase, NSString *)
SOFT_LINK_POINTER(QTKit, QTMediaTypeSound, NSString *)
SOFT_LINK_POINTER(QTKit, QTMediaTypeText, NSString *)
SOFT_LINK_POINTER(QTKit, QTMediaTypeVideo, NSString *)
SOFT_LINK_POINTER(QTKit, QTMovieDataSizeAttribute, NSString *)
SOFT_LINK_POINTER(QTKit, QTMovieDidEndNotification, NSString *)
SOFT_LINK_POINTER(QTKit, QTMovieHasVideoAttribute, NSString *)
SOFT_LINK_POINTER(QTKit, QTMovieIsActiveAttribute, NSString *)
SOFT_LINK_POINTER(QTKit, QTMovieLoadStateAttribute, NSString *)
SOFT_LINK_POINTER(QTKit, QTMovieLoadStateDidChangeNotification, NSString *)
SOFT_LINK_POINTER(QTKit, QTMovieNaturalSizeAttribute, NSString *)
SOFT_LINK_POINTER(QTKit, QTMoviePreventExternalURLLinksAttribute, NSString *)
SOFT_LINK_POINTER(QTKit, QTMovieRateDidChangeNotification, NSString *)
SOFT_LINK_POINTER(QTKit, QTMovieSizeDidChangeNotification, NSString *)
SOFT_LINK_POINTER(QTKit, QTMovieTimeDidChangeNotification, NSString *)
SOFT_LINK_POINTER(QTKit, QTMovieTimeScaleAttribute, NSString *)
SOFT_LINK_POINTER(QTKit, QTMovieURLAttribute, NSString *)
SOFT_LINK_POINTER(QTKit, QTMovieVolumeDidChangeNotification, NSString *)
SOFT_LINK_POINTER(QTKit, QTSecurityPolicyNoCrossSiteAttribute, NSString *)
#define QTMovie getQTMovieClass()
#define QTMovieView getQTMovieViewClass()
#define QTMediaTypeAttribute getQTMediaTypeAttribute()
#define QTMediaTypeBase getQTMediaTypeBase()
#define QTMediaTypeSound getQTMediaTypeSound()
#define QTMediaTypeText getQTMediaTypeText()
#define QTMediaTypeVideo getQTMediaTypeVideo()
#define QTMovieDataSizeAttribute getQTMovieDataSizeAttribute()
#define QTMovieDidEndNotification getQTMovieDidEndNotification()
#define QTMovieHasVideoAttribute getQTMovieHasVideoAttribute()
#define QTMovieIsActiveAttribute getQTMovieIsActiveAttribute()
#define QTMovieLoadStateAttribute getQTMovieLoadStateAttribute()
#define QTMovieLoadStateDidChangeNotification getQTMovieLoadStateDidChangeNotification()
#define QTMovieNaturalSizeAttribute getQTMovieNaturalSizeAttribute()
#define QTMoviePreventExternalURLLinksAttribute getQTMoviePreventExternalURLLinksAttribute()
#define QTMovieRateDidChangeNotification getQTMovieRateDidChangeNotification()
#define QTMovieSizeDidChangeNotification getQTMovieSizeDidChangeNotification()
#define QTMovieTimeDidChangeNotification getQTMovieTimeDidChangeNotification()
#define QTMovieTimeScaleAttribute getQTMovieTimeScaleAttribute()
#define QTMovieURLAttribute getQTMovieURLAttribute()
#define QTMovieVolumeDidChangeNotification getQTMovieVolumeDidChangeNotification()
#define QTSecurityPolicyNoCrossSiteAttribute getQTSecurityPolicyNoCrossSiteAttribute()
// Older versions of the QTKit header don't have these constants.
#if !defined QTKIT_VERSION_MAX_ALLOWED || QTKIT_VERSION_MAX_ALLOWED <= QTKIT_VERSION_7_0
enum {
QTMovieLoadStateError = -1L,
QTMovieLoadStateLoaded = 2000L,
QTMovieLoadStatePlayable = 10000L,
QTMovieLoadStatePlaythroughOK = 20000L,
QTMovieLoadStateComplete = 100000L
};
#endif
using namespace WebCore;
using namespace std;
@interface WebCoreMovieObserver : NSObject
{
MediaPlayerPrivate* m_callback;
BOOL m_delayCallbacks;
}
-(id)initWithCallback:(MediaPlayerPrivate*)callback;
-(void)disconnect;
-(void)repaint;
-(void)setDelayCallbacks:(BOOL)shouldDelay;
-(void)loadStateChanged:(NSNotification *)notification;
-(void)rateChanged:(NSNotification *)notification;
-(void)sizeChanged:(NSNotification *)notification;
-(void)timeChanged:(NSNotification *)notification;
-(void)didEnd:(NSNotification *)notification;
@end
namespace WebCore {
static const float endPointTimerInterval = 0.020f;
static const long minimumQuickTimeVersion = 0x07300000; // 7.3
MediaPlayerPrivate::MediaPlayerPrivate(MediaPlayer* player)
: m_player(player)
, m_objcObserver(AdoptNS, [[WebCoreMovieObserver alloc] initWithCallback:this])
, m_seekTo(-1)
, m_endTime(numeric_limits<float>::infinity())
, m_seekTimer(this, &MediaPlayerPrivate::seekTimerFired)
, m_endPointTimer(this, &MediaPlayerPrivate::endPointTimerFired)
, m_networkState(MediaPlayer::Empty)
, m_readyState(MediaPlayer::DataUnavailable)
, m_startedPlaying(false)
, m_isStreaming(false)
{
}
MediaPlayerPrivate::~MediaPlayerPrivate()
{
detachQTMovieView();
[[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()];
[m_objcObserver.get() disconnect];
}
void MediaPlayerPrivate::createQTMovie(const String& url)
{
[[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()];
m_qtMovie = 0;
// Disable streaming support for now, <rdar://problem/5693967>
if (url.startsWith("rtsp:"))
return;
NSDictionary* movieAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
KURL(url.deprecatedString()).getNSURL(), QTMovieURLAttribute,
[NSNumber numberWithBool:YES], QTMoviePreventExternalURLLinksAttribute,
[NSNumber numberWithBool:YES], QTSecurityPolicyNoCrossSiteAttribute,
nil];
NSError* error = nil;
m_qtMovie.adoptNS([[QTMovie alloc] initWithAttributes:movieAttributes error:&error]);
// FIXME: Find a proper way to detect streaming content.
m_isStreaming = url.startsWith("rtsp:");
if (!m_qtMovie)
return;
[m_qtMovie.get() setVolume:m_player->volume()];
[[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
selector:@selector(loadStateChanged:)
name:QTMovieLoadStateDidChangeNotification
object:m_qtMovie.get()];
[[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
selector:@selector(rateChanged:)
name:QTMovieRateDidChangeNotification
object:m_qtMovie.get()];
[[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
selector:@selector(sizeChanged:)
name:QTMovieSizeDidChangeNotification
object:m_qtMovie.get()];
[[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
selector:@selector(timeChanged:)
name:QTMovieTimeDidChangeNotification
object:m_qtMovie.get()];
[[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
selector:@selector(didEnd:)
name:QTMovieDidEndNotification
object:m_qtMovie.get()];
}
static void mainThreadSetNeedsDisplay(id self, SEL _cmd)
{
id movieView = [self superview];
ASSERT(!movieView || [movieView isKindOfClass:[QTMovieView class]]);
if (!movieView || ![movieView isKindOfClass:[QTMovieView class]])
return;
WebCoreMovieObserver* delegate = [movieView delegate];
ASSERT(!delegate || [delegate isKindOfClass:[WebCoreMovieObserver class]]);
if (!delegate || ![delegate isKindOfClass:[WebCoreMovieObserver class]])
return;
[delegate repaint];
}
void MediaPlayerPrivate::createQTMovieView()
{
detachQTMovieView();
if (!m_player->m_parentWidget || !m_qtMovie)
return;
static bool addedCustomMethods = false;
if (!addedCustomMethods) {
Class QTMovieContentViewClass = NSClassFromString(@"QTMovieContentView");
ASSERT(QTMovieContentViewClass);
Method mainThreadSetNeedsDisplayMethod = class_getInstanceMethod(QTMovieContentViewClass, @selector(_mainThreadSetNeedsDisplay));
ASSERT(mainThreadSetNeedsDisplayMethod);
method_setImplementation(mainThreadSetNeedsDisplayMethod, reinterpret_cast<IMP>(mainThreadSetNeedsDisplay));
addedCustomMethods = true;
}
m_qtMovieView.adoptNS([[QTMovieView alloc] init]);
setRect(m_player->rect());
NSView* parentView = static_cast<ScrollView*>(m_player->m_parentWidget)->getDocumentView();
[parentView addSubview:m_qtMovieView.get()];
#ifdef BUILDING_ON_TIGER
// setDelegate: isn't a public call in Tiger, so use performSelector to keep the compiler happy
[m_qtMovieView.get() performSelector:@selector(setDelegate:) withObject:m_objcObserver.get()];
#else
[m_qtMovieView.get() setDelegate:m_objcObserver.get()];
#endif
[m_qtMovieView.get() setMovie:m_qtMovie.get()];
[m_qtMovieView.get() setControllerVisible:NO];
[m_qtMovieView.get() setPreservesAspectRatio:NO];
// the area not covered by video should be transparent
[m_qtMovieView.get() setFillColor:[NSColor clearColor]];
wkQTMovieViewSetDrawSynchronously(m_qtMovieView.get(), YES);
}
void MediaPlayerPrivate::detachQTMovieView()
{
if (m_qtMovieView) {
#ifdef BUILDING_ON_TIGER
// setDelegate: isn't a public call in Tiger, so use performSelector to keep the compiler happy
[m_qtMovieView.get() performSelector:@selector(setDelegate:) withObject:nil];
#else
[m_qtMovieView.get() setDelegate:nil];
#endif
[m_qtMovieView.get() removeFromSuperview];
m_qtMovieView = nil;
}
}
QTTime MediaPlayerPrivate::createQTTime(float time) const
{
if (!m_qtMovie)
return QTMakeTime(0, 600);
long timeScale = [[m_qtMovie.get() attributeForKey:QTMovieTimeScaleAttribute] longValue];
return QTMakeTime(time * timeScale, timeScale);
}
void MediaPlayerPrivate::load(const String& url)
{
if (m_networkState != MediaPlayer::Loading) {
m_networkState = MediaPlayer::Loading;
m_player->networkStateChanged();
}
if (m_readyState != MediaPlayer::DataUnavailable) {
m_readyState = MediaPlayer::DataUnavailable;
m_player->readyStateChanged();
}
cancelSeek();
m_endPointTimer.stop();
[m_objcObserver.get() setDelayCallbacks:YES];
createQTMovie(url);
if (m_player->visible())
createQTMovieView();
[m_objcObserver.get() loadStateChanged:nil];
[m_objcObserver.get() setDelayCallbacks:NO];
}
void MediaPlayerPrivate::play()
{
if (!m_qtMovie)
return;
m_startedPlaying = true;
[m_objcObserver.get() setDelayCallbacks:YES];
[m_qtMovie.get() setRate:m_player->rate()];
[m_objcObserver.get() setDelayCallbacks:NO];
startEndPointTimerIfNeeded();
}
void MediaPlayerPrivate::pause()
{
if (!m_qtMovie)
return;
m_startedPlaying = false;
[m_objcObserver.get() setDelayCallbacks:YES];
[m_qtMovie.get() stop];
[m_objcObserver.get() setDelayCallbacks:NO];
m_endPointTimer.stop();
}
float MediaPlayerPrivate::duration() const
{
if (!m_qtMovie)
return 0;
QTTime time = [m_qtMovie.get() duration];
if (time.flags == kQTTimeIsIndefinite)
return numeric_limits<float>::infinity();
return static_cast<float>(time.timeValue) / time.timeScale;
}
float MediaPlayerPrivate::currentTime() const
{
if (!m_qtMovie)
return 0;
QTTime time = [m_qtMovie.get() currentTime];
return min(static_cast<float>(time.timeValue) / time.timeScale, m_endTime);
}
void MediaPlayerPrivate::seek(float time)
{
cancelSeek();
if (!m_qtMovie)
return;
if (time > duration())
time = duration();
m_seekTo = time;
if (maxTimeLoaded() >= m_seekTo)
doSeek();
else
m_seekTimer.start(0, 0.5f);
}
void MediaPlayerPrivate::doSeek()
{
QTTime qttime = createQTTime(m_seekTo);
// setCurrentTime generates several event callbacks, update afterwards
[m_objcObserver.get() setDelayCallbacks:YES];
float oldRate = [m_qtMovie.get() rate];
[m_qtMovie.get() setRate:0];
[m_qtMovie.get() setCurrentTime:qttime];
float timeAfterSeek = currentTime();
// restore playback only if not at end, othewise QTMovie will loop
if (timeAfterSeek < duration() && timeAfterSeek < m_endTime)
[m_qtMovie.get() setRate:oldRate];
cancelSeek();
[m_objcObserver.get() setDelayCallbacks:NO];
}
void MediaPlayerPrivate::cancelSeek()
{
m_seekTo = -1;
m_seekTimer.stop();
}
void MediaPlayerPrivate::seekTimerFired(Timer<MediaPlayerPrivate>*)
{
if (!m_qtMovie || !seeking() || currentTime() == m_seekTo) {
cancelSeek();
updateStates();
m_player->timeChanged();
return;
}
if (maxTimeLoaded() >= m_seekTo)
doSeek();
else {
MediaPlayer::NetworkState state = networkState();
if (state == MediaPlayer::Empty || state == MediaPlayer::Loaded) {
cancelSeek();
updateStates();
m_player->timeChanged();
}
}
}
void MediaPlayerPrivate::setEndTime(float time)
{
m_endTime = time;
startEndPointTimerIfNeeded();
}
void MediaPlayerPrivate::startEndPointTimerIfNeeded()
{
if (m_endTime < duration() && m_startedPlaying && !m_endPointTimer.isActive())
m_endPointTimer.startRepeating(endPointTimerInterval);
}
void MediaPlayerPrivate::endPointTimerFired(Timer<MediaPlayerPrivate>*)
{
float time = currentTime();
// just do end for now
if (time >= m_endTime) {
pause();
didEnd();
}
}
bool MediaPlayerPrivate::paused() const
{
if (!m_qtMovie)
return true;
return [m_qtMovie.get() rate] == 0;
}
bool MediaPlayerPrivate::seeking() const
{
if (!m_qtMovie)
return false;
return m_seekTo >= 0;
}
IntSize MediaPlayerPrivate::naturalSize() const
{
if (!m_qtMovie)
return IntSize();
return IntSize([[m_qtMovie.get() attributeForKey:QTMovieNaturalSizeAttribute] sizeValue]);
}
bool MediaPlayerPrivate::hasVideo() const
{
if (!m_qtMovie)
return false;
return [[m_qtMovie.get() attributeForKey:QTMovieHasVideoAttribute] boolValue];
}
void MediaPlayerPrivate::setVolume(float volume)
{
if (!m_qtMovie)
return;
[m_qtMovie.get() setVolume:volume];
}
void MediaPlayerPrivate::setRate(float rate)
{
if (!m_qtMovie)
return;
if (!paused())
[m_qtMovie.get() setRate:rate];
}
int MediaPlayerPrivate::dataRate() const
{
if (!m_qtMovie)
return 0;
return wkQTMovieDataRate(m_qtMovie.get());
}
float MediaPlayerPrivate::maxTimeBuffered() const
{
// rtsp streams are not buffered
return m_isStreaming ? 0 : maxTimeLoaded();
}
float MediaPlayerPrivate::maxTimeSeekable() const
{
// infinite duration means live stream
return isinf(duration()) ? 0 : maxTimeLoaded();
}
float MediaPlayerPrivate::maxTimeLoaded() const
{
if (!m_qtMovie)
return 0;
return wkQTMovieMaxTimeLoaded(m_qtMovie.get());
}
unsigned MediaPlayerPrivate::bytesLoaded() const
{
float dur = duration();
if (!dur)
return 0;
return totalBytes() * maxTimeLoaded() / dur;
}
bool MediaPlayerPrivate::totalBytesKnown() const
{
return totalBytes() > 0;
}
unsigned MediaPlayerPrivate::totalBytes() const
{
if (!m_qtMovie)
return 0;
return [[m_qtMovie.get() attributeForKey:QTMovieDataSizeAttribute] intValue];
}
void MediaPlayerPrivate::cancelLoad()
{
// FIXME: Is there a better way to check for this?
if (m_networkState < MediaPlayer::Loading || m_networkState == MediaPlayer::Loaded)
return;
detachQTMovieView();
m_qtMovie = nil;
updateStates();
}
void MediaPlayerPrivate::updateStates()
{
MediaPlayer::NetworkState oldNetworkState = m_networkState;
MediaPlayer::ReadyState oldReadyState = m_readyState;
long loadState = m_qtMovie ? [[m_qtMovie.get() attributeForKey:QTMovieLoadStateAttribute] longValue] : static_cast<long>(QTMovieLoadStateError);
if (loadState >= QTMovieLoadStateLoaded && m_networkState < MediaPlayer::LoadedMetaData) {
unsigned enabledTrackCount;
disableUnsupportedTracks(enabledTrackCount);
// FIXME: We should differentiate between load errors and decode errors <rdar://problem/5605692>
if (!enabledTrackCount)
loadState = QTMovieLoadStateError;
}
// "Loaded" is reserved for fully buffered movies, never the case when streaming
if (loadState >= QTMovieLoadStateComplete && !m_isStreaming) {
if (m_networkState < MediaPlayer::Loaded)
m_networkState = MediaPlayer::Loaded;
m_readyState = MediaPlayer::CanPlayThrough;
} else if (loadState >= QTMovieLoadStatePlaythroughOK) {
if (m_networkState < MediaPlayer::LoadedFirstFrame && !seeking())
m_networkState = MediaPlayer::LoadedFirstFrame;
m_readyState = MediaPlayer::CanPlayThrough;
} else if (loadState >= QTMovieLoadStatePlayable) {
if (m_networkState < MediaPlayer::LoadedFirstFrame && !seeking())
m_networkState = MediaPlayer::LoadedFirstFrame;
// FIXME: This might not work correctly in streaming case, <rdar://problem/5693967>
m_readyState = currentTime() < maxTimeLoaded() ? MediaPlayer::CanPlay : MediaPlayer::DataUnavailable;
} else if (loadState >= QTMovieLoadStateLoaded) {
if (m_networkState < MediaPlayer::LoadedMetaData)
m_networkState = MediaPlayer::LoadedMetaData;
m_readyState = MediaPlayer::DataUnavailable;
} else if (loadState > QTMovieLoadStateError) {
if (m_networkState < MediaPlayer::Loading)
m_networkState = MediaPlayer::Loading;
m_readyState = MediaPlayer::DataUnavailable;
} else {
m_networkState = MediaPlayer::LoadFailed;
m_readyState = MediaPlayer::DataUnavailable;
}
if (seeking())
m_readyState = MediaPlayer::DataUnavailable;
if (m_networkState != oldNetworkState)
m_player->networkStateChanged();
if (m_readyState != oldReadyState)
m_player->readyStateChanged();
}
void MediaPlayerPrivate::loadStateChanged()
{
updateStates();
}
void MediaPlayerPrivate::rateChanged()
{
updateStates();
}
void MediaPlayerPrivate::sizeChanged()
{
}
void MediaPlayerPrivate::timeChanged()
{
updateStates();
m_player->timeChanged();
}
void MediaPlayerPrivate::didEnd()
{
m_endPointTimer.stop();
m_startedPlaying = false;
updateStates();
m_player->timeChanged();
}
void MediaPlayerPrivate::setRect(const IntRect& r)
{
if (!m_qtMovieView)
return;
// We don't really need the QTMovieView in any specific location so let's just get it out of the way
// where it won't intercept events or try to bring up the context menu.
IntRect farAwayButCorrectSize(r);
farAwayButCorrectSize.move(-1000000, -1000000);
[m_qtMovieView.get() setFrame:farAwayButCorrectSize];
}
void MediaPlayerPrivate::setVisible(bool b)
{
if (b)
createQTMovieView();
else
detachQTMovieView();
}
void MediaPlayerPrivate::repaint()
{
m_player->repaint();
}
void MediaPlayerPrivate::paint(GraphicsContext* context, const IntRect& r)
{
if (context->paintingDisabled())
return;
NSView *view = m_qtMovieView.get();
if (view == nil)
return;
[m_objcObserver.get() setDelayCallbacks:YES];
BEGIN_BLOCK_OBJC_EXCEPTIONS;
context->save();
context->translate(r.x(), r.y() + r.height());
context->scale(FloatSize(1.0f, -1.0f));
IntRect paintRect(IntPoint(0, 0), IntSize(r.width(), r.height()));
NSGraphicsContext* newContext = [NSGraphicsContext graphicsContextWithGraphicsPort:context->platformContext() flipped:NO];
[view displayRectIgnoringOpacity:paintRect inContext:newContext];
context->restore();
END_BLOCK_OBJC_EXCEPTIONS;
[m_objcObserver.get() setDelayCallbacks:NO];
}
void MediaPlayerPrivate::getSupportedTypes(HashSet<String>& types)
{
NSArray* fileTypes = [QTMovie movieFileTypes:QTIncludeCommonTypes];
int count = [fileTypes count];
for (int n = 0; n < count; n++) {
CFStringRef ext = reinterpret_cast<CFStringRef>([fileTypes objectAtIndex:n]);
RetainPtr<CFStringRef> uti(AdoptCF, UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, ext, NULL));
if (!uti)
continue;
RetainPtr<CFStringRef> mime(AdoptCF, UTTypeCopyPreferredTagWithClass(uti.get(), kUTTagClassMIMEType));
if (!mime)
continue;
types.add(mime.get());
}
}
bool MediaPlayerPrivate::isAvailable()
{
SInt32 version;
OSErr result;
// This Carbon API is available in 64 bit too
result = Gestalt(gestaltQuickTime, &version);
if (result != noErr) {
LOG_ERROR("No QuickTime available. Disabling <video> and <audio> support.");
return false;
}
if (version < minimumQuickTimeVersion) {
LOG_ERROR("QuickTime version %x detected, at least %x required. Disabling <video> and <audio> support.", version, minimumQuickTimeVersion);
return false;
}
return true;
}
void MediaPlayerPrivate::disableUnsupportedTracks(unsigned& enabledTrackCount)
{
if (!m_qtMovie) {
enabledTrackCount = 0;
return;
}
static HashSet<String>* allowedTrackTypes = 0;
if (!allowedTrackTypes) {
allowedTrackTypes = new HashSet<String>;
allowedTrackTypes->add(QTMediaTypeVideo);
allowedTrackTypes->add(QTMediaTypeSound);
allowedTrackTypes->add(QTMediaTypeText);
allowedTrackTypes->add(QTMediaTypeBase);
allowedTrackTypes->add("clcp");
allowedTrackTypes->add("sbtl");
}
NSArray *tracks = [m_qtMovie.get() tracks];
unsigned trackCount = [tracks count];
enabledTrackCount = trackCount;
for (unsigned trackIndex = 0; trackIndex < trackCount; trackIndex++) {
// Grab the track at the current index. If there isn't one there, then
// we can move onto the next one.
QTTrack *track = [tracks objectAtIndex:trackIndex];
if (!track)
continue;
// Check to see if the track is disabled already, we should move along.
// We don't need to re-disable it.
if (![track isEnabled])
continue;
// Grab the track's media. We're going to check to see if we need to
// disable the tracks. They could be unsupported.
QTMedia *trackMedia = [track media];
if (!trackMedia)
continue;
// Grab the media type for this track.
NSString *mediaType = [trackMedia attributeForKey:QTMediaTypeAttribute];
if (!mediaType)
continue;
// Test whether the media type is in our white list.
if (!allowedTrackTypes->contains(mediaType)) {
// If this track type is not allowed, then we need to disable it.
[track setEnabled:NO];
--enabledTrackCount;
}
// Disable chapter tracks. These are most likely to lead to trouble, as
// they will be composited under the video tracks, forcing QT to do extra
// work.
QTTrack *chapterTrack = [track performSelector:@selector(chapterlist)];
if (!chapterTrack)
continue;
// Try to grab the media for the track.
QTMedia *chapterMedia = [chapterTrack media];
if (!chapterMedia)
continue;
// Grab the media type for this track.
id chapterMediaType = [chapterMedia attributeForKey:QTMediaTypeAttribute];
if (!chapterMediaType)
continue;
// Check to see if the track is a video track. We don't care about
// other non-video tracks.
if (![chapterMediaType isEqual:QTMediaTypeVideo])
continue;
// Check to see if the track is already disabled. If it is, we
// should move along.
if (![chapterTrack isEnabled])
continue;
// Disable the evil, evil track.
[chapterTrack setEnabled:NO];
--enabledTrackCount;
}
}
}
@implementation WebCoreMovieObserver
- (id)initWithCallback:(MediaPlayerPrivate *)callback
{
m_callback = callback;
return [super init];
}
- (void)disconnect
{
[NSObject cancelPreviousPerformRequestsWithTarget:self];
m_callback = 0;
}
-(void)repaint
{
if (m_delayCallbacks)
[self performSelector:_cmd withObject:nil afterDelay:0.];
else if (m_callback)
m_callback->repaint();
}
- (void)loadStateChanged:(NSNotification *)notification
{
if (m_delayCallbacks)
[self performSelector:_cmd withObject:nil afterDelay:0];
else
m_callback->loadStateChanged();
}
- (void)rateChanged:(NSNotification *)notification
{
if (m_delayCallbacks)
[self performSelector:_cmd withObject:nil afterDelay:0];
else
m_callback->rateChanged();
}
- (void)sizeChanged:(NSNotification *)notification
{
if (m_delayCallbacks)
[self performSelector:_cmd withObject:nil afterDelay:0];
else
m_callback->sizeChanged();
}
- (void)timeChanged:(NSNotification *)notification
{
if (m_delayCallbacks)
[self performSelector:_cmd withObject:nil afterDelay:0];
else
m_callback->timeChanged();
}
- (void)didEnd:(NSNotification *)notification
{
if (m_delayCallbacks)
[self performSelector:_cmd withObject:nil afterDelay:0];
else
m_callback->didEnd();
}
- (void)setDelayCallbacks:(BOOL)shouldDelay
{
m_delayCallbacks = shouldDelay;
}
@end
#endif