/* This file is part of the KDE project.
Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
This library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2.1 or 3 of the License.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this library. If not, see .
*/
#include "quicktimevideoplayer.h"
#include "mediaobject.h"
#include "videowidget.h"
#include "audiodevice.h"
#include "quicktimestreamreader.h"
#include "quicktimemetadata.h"
#include
#include
#include
#include
#include
#import
#import
#import
#import
#ifdef QUICKTIME_C_API_AVAILABLE
#include
#undef check // avoid name clash;
#include
#endif
QT_BEGIN_NAMESPACE
namespace Phonon
{
namespace QT7
{
// Defined in videowidget.cpp:
QGLWidget *PhononSharedQGLWidget();
QuickTimeVideoPlayer::QuickTimeVideoPlayer() : QObject(0)
{
m_state = NoMedia;
m_mediaSource = MediaSource();
m_metaData = new QuickTimeMetaData(this);
m_QTMovie = 0;
m_streamReader = 0;
m_playbackRate = 1.0f;
m_masterVolume = 1.0f;
m_relativeVolume = 1.0f;
m_currentTime = 0;
m_mute = false;
m_audioEnabled = false;
m_hasVideo = false;
m_staticFps = 0;
m_playbackRateSat = false;
m_isDrmProtected = false;
m_isDrmAuthorized = true;
m_primaryRenderingTarget = 0;
m_primaryRenderingCIImage = 0;
m_QImagePixelBuffer = 0;
m_cachedCVTextureRef = 0;
m_folderTracks = 0;
m_currentTrack = 0;
#ifdef QUICKTIME_C_API_AVAILABLE
OSStatus err = EnterMovies();
BACKEND_ASSERT2(err == noErr, "Could not initialize QuickTime", FATAL_ERROR)
createVisualContext();
#endif
}
QuickTimeVideoPlayer::~QuickTimeVideoPlayer()
{
PhononAutoReleasePool pool;
unsetCurrentMediaSource();
delete m_metaData;
[(NSObject*)m_primaryRenderingTarget release];
m_primaryRenderingTarget = 0;
#ifdef QUICKTIME_C_API_AVAILABLE
if (m_visualContext)
CFRelease(m_visualContext);
#endif
}
void QuickTimeVideoPlayer::releaseImageCache()
{
if (m_cachedCVTextureRef){
CVOpenGLTextureRelease(m_cachedCVTextureRef);
m_cachedCVTextureRef = 0;
}
m_cachedQImage = QImage();
}
void QuickTimeVideoPlayer::createVisualContext()
{
#ifdef QUICKTIME_C_API_AVAILABLE
PhononSharedQGLWidget()->makeCurrent();
PhononAutoReleasePool pool;
CGLContextObj cglContext = CGLGetCurrentContext();
NSOpenGLPixelFormat *nsglPixelFormat = [NSOpenGLView defaultPixelFormat];
CGLPixelFormatObj cglPixelFormat = static_cast([nsglPixelFormat CGLPixelFormatObj]);
BACKEND_ASSERT2(cglContext, "Could not get current CoreVideo GL context (OpenGL)", FATAL_ERROR)
BACKEND_ASSERT2(cglPixelFormat, "Could not get current CoreVideo pixel format (OpenGL)", FATAL_ERROR)
CFTypeRef keys[] = { kQTVisualContextWorkingColorSpaceKey };
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CFDictionaryRef textureContextAttributes = CFDictionaryCreate(kCFAllocatorDefault,
(const void **)keys,
(const void **)&colorSpace, 1,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
OSStatus err = QTOpenGLTextureContextCreate(kCFAllocatorDefault, cglContext,
cglPixelFormat, textureContextAttributes, &m_visualContext);
CFRelease(textureContextAttributes);
BACKEND_ASSERT2(err == noErr, "Could not create visual context (OpenGL)", FATAL_ERROR)
#endif // QUICKTIME_C_API_AVAILABLE
}
bool QuickTimeVideoPlayer::videoFrameChanged()
{
if (!m_QTMovie || !m_hasVideo)
return false;
#ifdef QUICKTIME_C_API_AVAILABLE
if (m_primaryRenderingTarget)
return true;
if (!m_visualContext)
return false;
QTVisualContextTask(m_visualContext);
bool changed = QTVisualContextIsNewImageAvailable(m_visualContext, 0);
if (changed)
releaseImageCache();
return changed;
#elif defined(QT_MAC_USE_COCOA)
return true;
#else
return false;
#endif
}
CVOpenGLTextureRef QuickTimeVideoPlayer::currentFrameAsCVTexture()
{
#ifdef QUICKTIME_C_API_AVAILABLE
if (!m_visualContext)
return 0;
if (!m_cachedCVTextureRef){
OSStatus err = QTVisualContextCopyImageForTime(m_visualContext, 0, 0, &m_cachedCVTextureRef);
BACKEND_ASSERT3(err == noErr, "Could not copy image for time in QuickTime player", FATAL_ERROR, 0)
}
return m_cachedCVTextureRef;
#else
return 0;
#endif
}
QImage QuickTimeVideoPlayer::currentFrameAsQImage()
{
if (!m_cachedQImage.isNull())
return m_cachedQImage;
#ifdef QUICKTIME_C_API_AVAILABLE
QGLContext *prevContext = const_cast(QGLContext::currentContext());
CVOpenGLTextureRef texture = currentFrameAsCVTexture();
GLenum target = CVOpenGLTextureGetTarget(texture);
GLfloat lowerLeft[2], lowerRight[2], upperRight[2], upperLeft[2];
if (!m_QImagePixelBuffer){
m_QImagePixelBuffer = new QGLPixelBuffer(videoRect().size(), QGLFormat::defaultFormat(), PhononSharedQGLWidget());
m_QImagePixelBuffer->makeCurrent();
glEnable(target);
glDisable(GL_BLEND);
glDisable(GL_CULL_FACE);
} else {
m_QImagePixelBuffer->makeCurrent();
}
CVOpenGLTextureGetCleanTexCoords(texture, upperLeft, upperRight, lowerRight, lowerLeft);
glBindTexture(target, CVOpenGLTextureGetName(texture));
glBegin(GL_QUADS);
glTexCoord2f(lowerLeft[0], lowerLeft[1]);
glVertex2i(-1, 1);
glTexCoord2f(lowerRight[0], lowerRight[1]);
glVertex2i(1, 1);
glTexCoord2f(upperRight[0], upperRight[1]);
glVertex2i(1, -1);
glTexCoord2f(upperLeft[0], upperLeft[1]);
glVertex2i(-1, -1);
glEnd();
m_cachedQImage = m_QImagePixelBuffer->toImage();
// Because of QuickTime, m_QImagePixelBuffer->doneCurrent() will fail.
// So we store, and restore, the context our selves:
prevContext->makeCurrent();
return m_cachedQImage;
#else
CIImage *img = (CIImage *)currentFrameAsCIImage();
if (!img)
return QImage();
NSBitmapImageRep* bitmap = [[NSBitmapImageRep alloc] initWithCIImage:img];
CGRect bounds = [img extent];
QImage qImg([bitmap bitmapData], bounds.size.width, bounds.size.height, QImage::Format_ARGB32);
m_cachedQImage = qImg.rgbSwapped();
[bitmap release];
[img release];
return m_cachedQImage;
#endif
}
void QuickTimeVideoPlayer::setPrimaryRenderingCIImage(void *ciImage)
{
[(CIImage *)m_primaryRenderingCIImage release];
m_primaryRenderingCIImage = ciImage;
[(CIImage *)m_primaryRenderingCIImage retain];
}
void QuickTimeVideoPlayer::setPrimaryRenderingTarget(NSObject *target)
{
[(NSObject*)m_primaryRenderingTarget release];
m_primaryRenderingTarget = target;
[(NSObject*)m_primaryRenderingTarget retain];
}
void *QuickTimeVideoPlayer::primaryRenderingCIImage()
{
return m_primaryRenderingCIImage;
}
void *QuickTimeVideoPlayer::currentFrameAsCIImage()
{
if (!m_QTMovie)
return 0;
#if defined(QT_MAC_USE_COCOA)
if (m_primaryRenderingCIImage){
CIImage *img = (CIImage *)m_primaryRenderingCIImage;
if (m_brightness || m_contrast || m_saturation){
CIFilter *colorFilter = [CIFilter filterWithName:@"CIColorControls"];
[colorFilter setValue:[NSNumber numberWithFloat:m_brightness] forKey:@"inputBrightness"];
[colorFilter setValue:[NSNumber numberWithFloat:(m_contrast < 1) ? m_contrast : 1 + ((m_contrast-1)*3)] forKey:@"inputContrast"];
[colorFilter setValue:[NSNumber numberWithFloat:m_saturation] forKey:@"inputSaturation"];
[colorFilter setValue:img forKey:@"inputImage"];
img = [colorFilter valueForKey:@"outputImage"];
}
if (m_hue){
CIFilter *colorFilter = [CIFilter filterWithName:@"CIHueAdjust"];
[colorFilter setValue:[NSNumber numberWithFloat:(m_hue * 3.14)] forKey:@"inputAngle"];
[colorFilter setValue:img forKey:@"inputImage"];
img = [colorFilter valueForKey:@"outputImage"];
}
return [img retain];
}
#endif
#ifdef QUICKTIME_C_API_AVAILABLE
CVOpenGLTextureRef cvImg = currentFrameAsCVTexture();
CIImage *img = [[CIImage alloc] initWithCVImageBuffer:cvImg];
return img;
#else
return 0;
#endif
}
GLuint QuickTimeVideoPlayer::currentFrameAsGLTexture()
{
CIImage *img = (CIImage *)currentFrameAsCIImage();
if (!img)
return 0;
NSBitmapImageRep* bitmap = [[NSBitmapImageRep alloc] initWithCIImage:img];
GLuint texName = 0;
glPixelStorei(GL_UNPACK_ROW_LENGTH, [bitmap pixelsWide]);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glGenTextures(1, &texName);
glBindTexture(GL_TEXTURE_RECTANGLE_EXT, texName);
glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
int samplesPerPixel = [bitmap samplesPerPixel];
if (![bitmap isPlanar] && (samplesPerPixel == 3 || samplesPerPixel == 4)){
glTexImage2D(GL_TEXTURE_RECTANGLE_EXT, 0,
samplesPerPixel == 4 ? GL_RGBA8 : GL_RGB8,
[bitmap pixelsWide], [bitmap pixelsHigh],
0, samplesPerPixel == 4 ? GL_RGBA : GL_RGB,
GL_UNSIGNED_BYTE, [bitmap bitmapData]);
} else {
// Handle other bitmap formats.
}
[bitmap release];
[img release];
return texName;
}
void QuickTimeVideoPlayer::setMasterVolume(float volume)
{
setVolume(volume, m_relativeVolume);
}
void QuickTimeVideoPlayer::setRelativeVolume(float volume)
{
setVolume(m_masterVolume, volume);
}
void QuickTimeVideoPlayer::setVolume(float masterVolume, float relativeVolume)
{
m_masterVolume = masterVolume;
m_relativeVolume = relativeVolume;
if (!m_QTMovie || !m_audioEnabled || m_mute)
return;
[m_QTMovie setVolume:(m_masterVolume * m_relativeVolume)];
}
void QuickTimeVideoPlayer::setMute(bool mute)
{
m_mute = mute;
if (!m_QTMovie || m_state != Playing || !m_audioEnabled)
return;
// Work-around bug that happends if you set/unset mute
// before movie is playing, and audio is not played
// through graph. Then audio is delayed.
[m_QTMovie setMuted:mute];
[m_QTMovie setVolume:(mute ? 0 : m_masterVolume * m_relativeVolume)];
}
void QuickTimeVideoPlayer::enableAudio(bool enable)
{
m_audioEnabled = enable;
if (!m_QTMovie || m_state != Playing)
return;
// Work-around bug that happends if you set/unset mute
// before movie is playing, and audio is not played
// through graph. Then audio is delayed.
[m_QTMovie setMuted:(!enable || m_mute)];
[m_QTMovie setVolume:((!enable || m_mute) ? 0 : m_masterVolume * m_relativeVolume)];
}
bool QuickTimeVideoPlayer::audioEnabled()
{
return m_audioEnabled;
}
bool QuickTimeVideoPlayer::setAudioDevice(int id)
{
if (!m_QTMovie)
return false;
#ifdef QUICKTIME_C_API_AVAILABLE
// The following code will not work for some media codecs that
// typically mingle audio/video frames (e.g mpeg).
CFStringRef idString = PhononCFString::toCFStringRef(AudioDevice::deviceUID(id));
QTAudioContextRef context;
QTAudioContextCreateForAudioDevice(kCFAllocatorDefault, idString, 0, &context);
OSStatus err = SetMovieAudioContext([m_QTMovie quickTimeMovie], context);
CFRelease(context);
if (err != noErr)
return false;
return true;
#else
Q_UNUSED(id);
return false;
#endif
}
void QuickTimeVideoPlayer::setColors(qreal brightness, qreal contrast, qreal hue, qreal saturation)
{
if (!m_QTMovie)
return;
// 0 is default value for the colors
// in phonon, so adjust scale:
contrast += 1;
saturation += 1;
if (m_brightness == brightness
&& m_contrast == contrast
&& m_hue == hue
&& m_saturation == saturation)
return;
m_brightness = brightness;
m_contrast = contrast;
m_hue = hue;
m_saturation = saturation;
#ifdef QUICKTIME_C_API_AVAILABLE
Float32 value;
value = brightness;
SetMovieVisualBrightness([m_QTMovie quickTimeMovie], value, 0);
value = contrast;
SetMovieVisualContrast([m_QTMovie quickTimeMovie], value, 0);
value = hue;
SetMovieVisualHue([m_QTMovie quickTimeMovie], value, 0);
value = saturation;
SetMovieVisualSaturation([m_QTMovie quickTimeMovie], value, 0);
#endif
releaseImageCache();
}
QRect QuickTimeVideoPlayer::videoRect() const
{
if (!m_QTMovie)
return QRect();
PhononAutoReleasePool pool;
NSSize size = [[m_QTMovie attributeForKey:@"QTMovieCurrentSizeAttribute"] sizeValue];
return QRect(0, 0, size.width, size.height);
}
void QuickTimeVideoPlayer::unsetCurrentMediaSource()
{
if (!m_QTMovie)
return;
[m_QTMovie release];
m_QTMovie = 0;
delete m_streamReader;
m_streamReader = 0;
m_currentTime = 0;
m_state = NoMedia;
m_isDrmProtected = false;
m_isDrmAuthorized = true;
m_hasVideo = false;
m_staticFps = 0;
m_mediaSource = MediaSource();
m_movieCompactDiscPath.clear();
[(CIImage *)m_primaryRenderingCIImage release];
m_primaryRenderingCIImage = 0;
delete m_QImagePixelBuffer;
m_QImagePixelBuffer = 0;
releaseImageCache();
[m_folderTracks release];
m_folderTracks = 0;
}
QuickTimeVideoPlayer::State QuickTimeVideoPlayer::state() const
{
return m_state;
}
quint64 QuickTimeVideoPlayer::timeLoaded()
{
if (!m_QTMovie)
return 0;
#ifdef QUICKTIME_C_API_AVAILABLE
TimeValue value;
GetMaxLoadedTimeInMovie([m_QTMovie quickTimeMovie], &value);
quint64 loaded = static_cast(float(value) / float(GetMovieTimeScale([m_QTMovie quickTimeMovie])) * 1000.0f);
return (loaded == INT_MAX) ? 0 : loaded;
#else
return 0;
#endif
}
float QuickTimeVideoPlayer::percentageLoaded()
{
if (!m_QTMovie || !isSeekable())
return 0;
#ifdef QUICKTIME_C_API_AVAILABLE
TimeValue loaded;
GetMaxLoadedTimeInMovie([m_QTMovie quickTimeMovie], &loaded);
float duration = GetMovieDuration([m_QTMovie quickTimeMovie]);
return duration ? float(loaded) / duration : 0;
#else
return 0;
#endif
}
void QuickTimeVideoPlayer::waitStatePlayable()
{
#if defined(QT_MAC_USE_COCOA)
long state = [[m_QTMovie attributeForKey:@"QTMovieLoadStateAttribute"] longValue];
while (state != QTMovieLoadStateError && state < QTMovieLoadStatePlayable)
state = [[m_QTMovie attributeForKey:@"QTMovieLoadStateAttribute"] longValue];
#elif defined(QUICKTIME_C_API_AVAILABLE)
long state = GetMovieLoadState([m_QTMovie quickTimeMovie]);
while (state != kMovieLoadStateError && state < kMovieLoadStatePlayable){
MoviesTask(0, 0);
state = GetMovieLoadState([m_QTMovie quickTimeMovie]);
}
#endif
}
bool QuickTimeVideoPlayer::movieNotLoaded()
{
if (!m_QTMovie)
return true;
#if defined(QT_MAC_USE_COCOA)
long state = [[m_QTMovie attributeForKey:@"QTMovieLoadStateAttribute"] longValue];
return state == QTMovieLoadStateError;
#elif defined(QUICKTIME_C_API_AVAILABLE)
long state = GetMovieLoadState([m_QTMovie quickTimeMovie]);
return state == kMovieLoadStateError;
#endif
}
void QuickTimeVideoPlayer::setError(NSError *error)
{
if (!error)
return;
QString desc = QString::fromUtf8([[error localizedDescription] UTF8String]);
if (desc == "The file is not a movie file.")
desc = QLatin1String("Could not decode media source.");
else if (desc == "A necessary data reference could not be resolved."){
if (codecExistsAccordingToSuffix(mediaSourcePath()))
desc = QLatin1String("Could not locate media source.");
else
desc = QLatin1String("Could not decode media source.");
} else if (desc == "You do not have sufficient permissions for this operation.")
desc = QLatin1String("Could not open media source.");
SET_ERROR(desc, FATAL_ERROR)
}
bool QuickTimeVideoPlayer::errorOccured()
{
if (gGetErrorType() != NO_ERROR){
return true;
} else if (movieNotLoaded()){
SET_ERROR("Could not open media source.", FATAL_ERROR)
return true;
}
return false;
}
bool QuickTimeVideoPlayer::codecExistsAccordingToSuffix(const QString &fileName)
{
PhononAutoReleasePool pool;
NSArray *fileTypes = [QTMovie movieFileTypes:QTIncludeAllTypes];
for (uint i=0; i<[fileTypes count]; ++i){
NSString *type = [fileTypes objectAtIndex:i];
QString formattedType = QString::fromUtf8([type UTF8String]);
formattedType.remove('\'').remove('.');
if (fileName.endsWith(QChar('.') + formattedType, Qt::CaseInsensitive))
return true;
}
return false;
}
void QuickTimeVideoPlayer::setMediaSource(const MediaSource &mediaSource)
{
PhononAutoReleasePool pool;
unsetCurrentMediaSource();
m_mediaSource = mediaSource;
if (mediaSource.type() == MediaSource::Empty || mediaSource.type() == MediaSource::Invalid){
m_state = NoMedia;
return;
}
openMovieFromCurrentMediaSource();
if (errorOccured()){
unsetCurrentMediaSource();
return;
}
prepareCurrentMovieForPlayback();
}
void QuickTimeVideoPlayer::prepareCurrentMovieForPlayback()
{
#ifdef QUICKTIME_C_API_AVAILABLE
if (m_visualContext)
SetMovieVisualContext([m_QTMovie quickTimeMovie], m_visualContext);
#endif
waitStatePlayable();
if (errorOccured()){
unsetCurrentMediaSource();
return;
}
readProtection();
preRollMovie();
if (errorOccured()){
unsetCurrentMediaSource();
return;
}
if (!m_playbackRateSat)
m_playbackRate = prefferedPlaybackRate();
checkIfVideoAwailable();
calculateStaticFps();
enableAudio(m_audioEnabled);
setMute(m_mute);
setVolume(m_masterVolume, m_relativeVolume);
m_metaData->update();
pause();
}
void QuickTimeVideoPlayer::openMovieFromCurrentMediaSource()
{
switch (m_mediaSource.type()){
case MediaSource::LocalFile:
openMovieFromFile();
break;
case MediaSource::Url:
openMovieFromUrl();
break;
case MediaSource::Disc:
openMovieFromCompactDisc();
break;
case MediaSource::Stream:
openMovieFromStream();
break;
case MediaSource::Empty:
case MediaSource::Invalid:
break;
}
}
QString QuickTimeVideoPlayer::mediaSourcePath()
{
switch (m_mediaSource.type()){
case MediaSource::LocalFile:{
QFileInfo fileInfo(m_mediaSource.fileName());
return fileInfo.isSymLink() ? fileInfo.symLinkTarget() : fileInfo.canonicalFilePath();
break;}
case MediaSource::Url:
return m_mediaSource.url().toEncoded();
break;
default:
break;
}
return QString();
}
void QuickTimeVideoPlayer::openMovieFromDataRef(QTDataReference *dataRef)
{
PhononAutoReleasePool pool;
NSDictionary *attr = [NSDictionary dictionaryWithObjectsAndKeys:
dataRef, QTMovieDataReferenceAttribute,
[NSNumber numberWithBool:YES], QTMovieOpenAsyncOKAttribute,
[NSNumber numberWithBool:YES], QTMovieIsActiveAttribute,
[NSNumber numberWithBool:YES], QTMovieResolveDataRefsAttribute,
[NSNumber numberWithBool:YES], QTMovieDontInteractWithUserAttribute,
nil];
NSError *err = 0;
m_QTMovie = [[QTMovie movieWithAttributes:attr error:&err] retain];
if (err){
[m_QTMovie release];
m_QTMovie = 0;
setError(err);
}
}
void QuickTimeVideoPlayer::openMovieFromData(QByteArray *data, char *fileType)
{
PhononAutoReleasePool pool;
NSString *type = [NSString stringWithUTF8String:fileType];
NSData *nsData = [NSData dataWithBytesNoCopy:data->data() length:data->size() freeWhenDone:NO];
QTDataReference *dataRef = [QTDataReference dataReferenceWithReferenceToData:nsData name:type MIMEType:@""];
openMovieFromDataRef(dataRef);
}
void QuickTimeVideoPlayer::openMovieFromDataGuessType(QByteArray *data)
{
// It turns out to be better to just try the standard file types rather
// than using e.g [QTMovie movieFileTypes:QTIncludeCommonTypes]. Some
// codecs *think* they can decode the stream, and crash...
#define TryOpenMovieWithCodec(type) gClearError(); \
openMovieFromData(data, (char *)"."type); \
if (m_QTMovie) return;
TryOpenMovieWithCodec("avi");
TryOpenMovieWithCodec("mp4");
TryOpenMovieWithCodec("m4p");
TryOpenMovieWithCodec("m1s");
TryOpenMovieWithCodec("mp3");
TryOpenMovieWithCodec("mpeg");
TryOpenMovieWithCodec("mov");
TryOpenMovieWithCodec("ogg");
TryOpenMovieWithCodec("wav");
TryOpenMovieWithCodec("wmv");
#undef TryOpenMovieWithCodec(type)
}
void QuickTimeVideoPlayer::openMovieFromFile()
{
NSString *nsFilename = (NSString *)PhononCFString::toCFStringRef(mediaSourcePath());
QTDataReference *dataRef = [QTDataReference dataReferenceWithReferenceToFile:nsFilename];
openMovieFromDataRef(dataRef);
}
void QuickTimeVideoPlayer::openMovieFromUrl()
{
PhononAutoReleasePool pool;
NSString *urlString = (NSString *)PhononCFString::toCFStringRef(mediaSourcePath());
NSURL *url = [NSURL URLWithString: urlString];
QTDataReference *dataRef = [QTDataReference dataReferenceWithReferenceToURL:url];
openMovieFromDataRef(dataRef);
}
void QuickTimeVideoPlayer::openMovieFromStream()
{
m_streamReader = new QuickTimeStreamReader(m_mediaSource);
if (!m_streamReader->readAllData())
return;
openMovieFromDataGuessType(m_streamReader->pointerToData());
}
typedef void (*qt_sighandler_t)(int);
static void sigtest(int) {
qApp->exit(0);
}
void QuickTimeVideoPlayer::openMovieFromCompactDisc()
{
// Interrupting the application while the device is open
// causes the application to hang. So we need to handle
// this in a more graceful way:
qt_sighandler_t hndl = signal(SIGINT, sigtest);
if (hndl)
signal(SIGINT, hndl);
PhononAutoReleasePool pool;
NSString *cd = 0;
QString devName = m_mediaSource.deviceName();
if (devName.isEmpty()) {
cd = pathToCompactDisc();
if (!cd) {
SET_ERROR("Could not open media source.", NORMAL_ERROR)
return;
}
m_movieCompactDiscPath = PhononCFString::toQString(reinterpret_cast(cd));
} else {
if (!QFileInfo(devName).isAbsolute())
devName = QLatin1String("/Volumes/") + devName;
cd = [reinterpret_cast(PhononCFString::toCFStringRef(devName)) autorelease];
if (!isCompactDisc(cd)) {
SET_ERROR("Could not open media source.", NORMAL_ERROR)
return;
}
m_movieCompactDiscPath = devName;
}
m_folderTracks = [scanFolder(cd) retain];
setCurrentTrack(0);
}
QString QuickTimeVideoPlayer::movieCompactDiscPath() const
{
return m_movieCompactDiscPath;
}
MediaSource QuickTimeVideoPlayer::mediaSource() const
{
return m_mediaSource;
}
QTMovie *QuickTimeVideoPlayer::qtMovie() const
{
return m_QTMovie;
}
void QuickTimeVideoPlayer::setPlaybackRate(float rate)
{
PhononAutoReleasePool pool;
m_playbackRateSat = true;
m_playbackRate = rate;
if (m_QTMovie)
[m_QTMovie setRate:m_playbackRate];
}
float QuickTimeVideoPlayer::playbackRate() const
{
return m_playbackRate;
}
quint64 QuickTimeVideoPlayer::currentTime() const
{
if (!m_QTMovie || m_state == Paused)
return m_currentTime;
PhononAutoReleasePool pool;
QTTime qtTime = [m_QTMovie currentTime];
quint64 t = static_cast(float(qtTime.timeValue) / float(qtTime.timeScale) * 1000.0f);
const_cast(this)->m_currentTime = t;
return m_currentTime;
}
long QuickTimeVideoPlayer::timeScale() const
{
if (!m_QTMovie)
return 0;
PhononAutoReleasePool pool;
return [[m_QTMovie attributeForKey:@"QTMovieTimeScaleAttribute"] longValue];
}
float QuickTimeVideoPlayer::staticFps()
{
return m_staticFps;
}
void QuickTimeVideoPlayer::calculateStaticFps()
{
if (!m_hasVideo){
m_staticFps = 0;
return;
}
#ifdef QT_ALLOW_QUICKTIME
Boolean isMpeg = false;
Track videoTrack = GetMovieIndTrackType([m_QTMovie quickTimeMovie], 1,
FOUR_CHAR_CODE('vfrr'), // 'vfrr' means: has frame rate
movieTrackCharacteristic | movieTrackEnabledOnly);
Media media = GetTrackMedia(videoTrack);
MediaHandler mediaH = GetMediaHandler(media);
MediaHasCharacteristic(mediaH, FOUR_CHAR_CODE('mpeg'), &isMpeg);
if (isMpeg){
MHInfoEncodedFrameRateRecord frameRate;
Size frameRateSize = sizeof(frameRate);
MediaGetPublicInfo(mediaH, kMHInfoEncodedFrameRate, &frameRate, &frameRateSize);
m_staticFps = float(Fix2X(frameRate.encodedFrameRate));
} else {
Media media = GetTrackMedia(videoTrack);
long sampleCount = GetMediaSampleCount(media);
TimeValue64 duration = GetMediaDisplayDuration(media);
TimeValue64 timeScale = GetMediaTimeScale(media);
m_staticFps = float((double)sampleCount * (double)timeScale / (double)duration);
}
#else
m_staticFps = 30.0f;
#endif
}
QString QuickTimeVideoPlayer::timeToString(quint64 ms)
{
int sec = ms/1000;
int min = sec/60;
int hour = min/60;
return QString(QLatin1String("%1:%2:%3:%4")).arg(hour%60).arg(min%60).arg(sec%60).arg(ms%1000);
}
QString QuickTimeVideoPlayer::currentTimeString()
{
return timeToString(currentTime());
}
quint64 QuickTimeVideoPlayer::duration() const
{
if (!m_QTMovie)
return 0;
PhononAutoReleasePool pool;
QTTime qtTime = [m_QTMovie duration];
return static_cast(float(qtTime.timeValue) / float(qtTime.timeScale) * 1000.0f);
}
void QuickTimeVideoPlayer::play()
{
if (!canPlayMedia())
return;
PhononAutoReleasePool pool;
m_state = Playing;
enableAudio(m_audioEnabled);
setMute(m_mute);
[m_QTMovie setRate:m_playbackRate];
}
void QuickTimeVideoPlayer::pause()
{
if (!canPlayMedia())
return;
PhononAutoReleasePool pool;
currentTime();
m_state = Paused;
if (isSeekable())
[m_QTMovie setRate:0];
else // pretend to be paused:
[m_QTMovie setMuted:0];
}
void QuickTimeVideoPlayer::seek(quint64 milliseconds)
{
if (!canPlayMedia() || !isSeekable() || milliseconds == currentTime())
return;
if (milliseconds > duration())
milliseconds = duration();
PhononAutoReleasePool pool;
QTTime newQTTime = [m_QTMovie currentTime];
newQTTime.timeValue = (milliseconds / 1000.0f) * newQTTime.timeScale;
[m_QTMovie setCurrentTime:newQTTime];
// The movie might not have been able to seek
// to the exact point we told it to. So set
// the current time according to what the movie says:
newQTTime = [m_QTMovie currentTime];
m_currentTime = static_cast
(float(newQTTime.timeValue) / float(newQTTime.timeScale) * 1000.0f);
if (m_state == Paused){
// We need (for reasons unknown) to task
// the movie twize to make sure that
// a subsequent call to frameAsCvTexture
// returns the correct frame:
#ifdef QUICKTIME_C_API_AVAILABLE
MoviesTask(0, 0);
MoviesTask(0, 0);
#elif defined(QT_MAC_USE_COCOA)
qApp->processEvents(QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers);
#endif
}
}
bool QuickTimeVideoPlayer::canPlayMedia() const
{
if (!m_QTMovie)
return false;
return m_isDrmAuthorized;
}
bool QuickTimeVideoPlayer::isPlaying() const
{
return m_state == Playing;
}
bool QuickTimeVideoPlayer::isSeekable() const
{
return canPlayMedia() && (duration()-1) != INT_MAX;
}
float QuickTimeVideoPlayer::prefferedPlaybackRate() const
{
if (!m_QTMovie)
return 0;
PhononAutoReleasePool pool;
return [[m_QTMovie attributeForKey:@"QTMoviePreferredRateAttribute"] floatValue];
}
#ifdef QUICKTIME_C_API_AVAILABLE
void MoviePrePrerollCompleteCallBack(Movie /*theMovie*/, OSErr /*thePrerollErr*/, void * /*userData*/)
{
// QuickTimeVideoPlayer *player = static_cast(userData);
}
#endif
bool QuickTimeVideoPlayer::preRollMovie(qint64 startTime)
{
if (!canPlayMedia())
return false;
#ifdef QUICKTIME_C_API_AVAILABLE
if (PrePrerollMovie([m_QTMovie quickTimeMovie], startTime, FloatToFixed(m_playbackRate),
0 /*MoviePrePrerollCompleteCallBack*/, this) != noErr) // No callback means wait (synch)
return false;
if (PrerollMovie([m_QTMovie quickTimeMovie], startTime, FloatToFixed(m_playbackRate)) != noErr)
return false;
return true;
#else
Q_UNUSED(startTime);
return false;
#endif
}
bool QuickTimeVideoPlayer::hasAudio() const
{
if (!m_QTMovie)
return false;
PhononAutoReleasePool pool;
return [[m_QTMovie attributeForKey:@"QTMovieHasAudioAttribute"] boolValue] == YES;
}
bool QuickTimeVideoPlayer::hasVideo() const
{
return m_hasVideo;
}
bool QuickTimeVideoPlayer::hasMovie() const
{
return m_QTMovie != 0;
}
void QuickTimeVideoPlayer::checkIfVideoAwailable()
{
PhononAutoReleasePool pool;
m_hasVideo = [[m_QTMovie attributeForKey:@"QTMovieHasVideoAttribute"] boolValue] == YES;
}
bool QuickTimeVideoPlayer::isDrmProtected() const
{
return m_isDrmProtected;
}
bool QuickTimeVideoPlayer::isDrmAuthorized() const
{
return m_isDrmAuthorized;
}
/*
void QuickTimeVideoPlayer::movieCodecIsMPEG()
{
NSArray *tracks = [m_QTMovie tracks];
for (QTTrack *track in tracks)
if ([[track media] hasCharacteristic:QTMediaTypeMPEG])
return true;
return false;
}
*/
static void QtGetTrackProtection(QTTrack *track, bool &isDrmProtected, bool &isDrmAuthorized)
{
isDrmProtected = false;
isDrmAuthorized = true;
#ifdef QUICKTIME_C_API_AVAILABLE
QTMedia *media = [track media];
MediaHandler mediaHandler = GetMediaHandler([media quickTimeMedia]);
if (mediaHandler){
// Regardless, skip message boxes pointing to iTunes regarding DRM:
Boolean boolFalse = false;
QTSetComponentProperty(mediaHandler,
kQTPropertyClass_DRM, kQTDRMPropertyID_InteractWithUser,
sizeof(boolFalse), &boolFalse);
// Check track:
Boolean value;
OSStatus err = QTGetComponentProperty(mediaHandler,
kQTPropertyClass_DRM, kQTDRMPropertyID_IsProtected,
sizeof(value), &value, 0);
isDrmProtected = (err == noErr) ? bool(value) : false;
err = QTGetComponentProperty(mediaHandler,
kQTPropertyClass_DRM, kQTDRMPropertyID_IsAuthorized,
sizeof(value), &value, 0);
isDrmAuthorized = (err == noErr) ? bool(value) : true;
}
#else
Q_UNUSED(track);
#endif // QUICKTIME_C_API_AVAILABLE
}
void QuickTimeVideoPlayer::readProtection()
{
m_isDrmProtected = false;
m_isDrmAuthorized = true;
NSArray *tracks = [m_QTMovie tracks];
for (uint i=0; i<[tracks count]; ++i){
QTTrack *track = [tracks objectAtIndex:i];
bool isDrmProtected = false;
bool isDrmAuthorized = true;
QtGetTrackProtection(track, isDrmProtected, isDrmAuthorized);
if (isDrmProtected)
m_isDrmProtected = true;
if (!isDrmAuthorized)
m_isDrmAuthorized = false;
}
}
QMultiMap QuickTimeVideoPlayer::metaData()
{
return m_metaData->metaData();
}
int QuickTimeVideoPlayer::trackCount() const
{
if (!m_folderTracks)
return 0;
return [m_folderTracks count];
}
int QuickTimeVideoPlayer::currentTrack() const
{
return m_currentTrack;
}
QString QuickTimeVideoPlayer::currentTrackPath() const
{
if (!m_folderTracks)
return QString();
PhononAutoReleasePool pool;
NSString *trackPath = [m_folderTracks objectAtIndex:m_currentTrack];
return PhononCFString::toQString(reinterpret_cast(trackPath));
}
NSString* QuickTimeVideoPlayer::pathToCompactDisc()
{
PhononAutoReleasePool pool;
NSArray *devices = [[NSWorkspace sharedWorkspace] mountedRemovableMedia];
for (unsigned int i=0; i<[devices count]; ++i) {
NSString *dev = [devices objectAtIndex:i];
if (isCompactDisc(dev))
return [dev retain];
}
return 0;
}
bool QuickTimeVideoPlayer::isCompactDisc(NSString *path)
{
PhononAutoReleasePool pool;
NSString *type = [NSString string];
[[NSWorkspace sharedWorkspace] getFileSystemInfoForPath:path
isRemovable:0
isWritable:0
isUnmountable:0
description:0
type:&type];
return [type hasPrefix:@"cdd"];
}
NSArray* QuickTimeVideoPlayer::scanFolder(NSString *path)
{
NSMutableArray *tracks = [NSMutableArray arrayWithCapacity:20];
if (!path)
return tracks;
NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager] enumeratorAtPath:path];
while (NSString *track = [enumerator nextObject]) {
if (![track hasPrefix:@"."])
[tracks addObject:[path stringByAppendingPathComponent:track]];
}
return tracks;
}
void QuickTimeVideoPlayer::setCurrentTrack(int track)
{
PhononAutoReleasePool pool;
[m_QTMovie release];
m_QTMovie = 0;
m_currentTime = 0;
m_currentTrack = track;
if (!m_folderTracks)
return;
if (track < 0 || track >= (int)[m_folderTracks count])
return;
NSString *trackPath = [m_folderTracks objectAtIndex:track];
QTDataReference *dataRef = [QTDataReference dataReferenceWithReferenceToFile:trackPath];
State currentState = m_state;
openMovieFromDataRef(dataRef);
prepareCurrentMovieForPlayback();
if (currentState == Playing)
play();
}
}}
QT_END_NAMESPACE