/* 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 <http://www.gnu.org/licenses/>. | |
*/ | |
#include "quicktimevideoplayer.h" | |
#include "mediaobject.h" | |
#include "videowidget.h" | |
#include "audiodevice.h" | |
#include "quicktimestreamreader.h" | |
#include <QtCore/QCoreApplication> | |
#include <QtCore/QEventLoop> | |
#include <QtCore/QFileInfo> | |
#include <QtCore/QUrl> | |
#include <QtOpenGL/QGLContext> | |
#import <QTKit/QTTrack.h> | |
#import <QTKit/QTMedia.h> | |
#import <QuartzCore/CIContext.h> | |
#import <QuartzCore/CIFilter.h> | |
#ifdef QUICKTIME_C_API_AVAILABLE | |
#include <QuickTime/QuickTime.h> | |
#undef check // avoid name clash; | |
#include <AGL/agl.h> | |
#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_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_playbackRateSat = false; | |
m_isDrmProtected = false; | |
m_isDrmAuthorized = true; | |
m_primaryRenderingTarget = 0; | |
m_primaryRenderingCIImage = 0; | |
m_QImagePixelBuffer = 0; | |
#ifdef QUICKTIME_C_API_AVAILABLE | |
OSStatus err = EnterMovies(); | |
BACKEND_ASSERT2(err == noErr, "Could not initialize QuickTime", FATAL_ERROR) | |
createVisualContext(); | |
#endif | |
} | |
QuickTimeVideoPlayer::~QuickTimeVideoPlayer() | |
{ | |
unsetVideo(); | |
[(NSObject*)m_primaryRenderingTarget release]; | |
m_primaryRenderingTarget = 0; | |
#ifdef QUICKTIME_C_API_AVAILABLE | |
if (m_visualContext) | |
CFRelease(m_visualContext); | |
#endif | |
} | |
void QuickTimeVideoPlayer::createVisualContext() | |
{ | |
#ifdef QUICKTIME_C_API_AVAILABLE | |
PhononSharedQGLWidget()->makeCurrent(); | |
PhononAutoReleasePool pool; | |
CGLContextObj cglContext = CGLGetCurrentContext(); | |
NSOpenGLPixelFormat *nsglPixelFormat = [NSOpenGLView defaultPixelFormat]; | |
CGLPixelFormatObj cglPixelFormat = static_cast<CGLPixelFormatObj>([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); | |
return QTVisualContextIsNewImageAvailable(m_visualContext, 0); | |
#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; | |
CVOpenGLTextureRef texture = 0; | |
OSStatus err = QTVisualContextCopyImageForTime(m_visualContext, 0, 0, &texture); | |
BACKEND_ASSERT3(err == noErr, "Could not copy image for time in QuickTime player", FATAL_ERROR, 0) | |
return texture; | |
#else | |
return 0; | |
#endif | |
} | |
QImage QuickTimeVideoPlayer::currentFrameAsQImage() | |
{ | |
#ifdef QUICKTIME_C_API_AVAILABLE | |
QGLContext *prevContext = const_cast<QGLContext *>(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(); | |
QImage image = m_QImagePixelBuffer->toImage(); | |
CVOpenGLTextureRelease(texture); | |
// Because of QuickTime, m_QImagePixelBuffer->doneCurrent() will fail. | |
// So we store, and restore, the context our selves: | |
prevContext->makeCurrent(); | |
return image; | |
#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); | |
QImage swapped = qImg.rgbSwapped(); | |
[bitmap release]; | |
[img release]; | |
return swapped; | |
#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]; | |
CVOpenGLTextureRelease(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; | |
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 | |
} | |
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::unsetVideo() | |
{ | |
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_mediaSource = MediaSource(); | |
[(CIImage *)m_primaryRenderingCIImage release]; | |
m_primaryRenderingCIImage = 0; | |
delete m_QImagePixelBuffer; | |
m_QImagePixelBuffer = 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<quint64>(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; | |
unsetVideo(); | |
m_mediaSource = mediaSource; | |
if (mediaSource.type() == MediaSource::Empty || mediaSource.type() == MediaSource::Invalid){ | |
m_state = NoMedia; | |
return; | |
} | |
openMovieFromCurrentMediaSource(); | |
if (errorOccured()){ | |
unsetVideo(); | |
return; | |
} | |
#ifdef QUICKTIME_C_API_AVAILABLE | |
if (m_visualContext) | |
SetMovieVisualContext([m_QTMovie quickTimeMovie], m_visualContext); | |
#endif | |
waitStatePlayable(); | |
if (errorOccured()){ | |
unsetVideo(); | |
return; | |
} | |
readProtection(); | |
preRollMovie(); | |
if (errorOccured()){ | |
unsetVideo(); | |
return; | |
} | |
if (!m_playbackRateSat) | |
m_playbackRate = prefferedPlaybackRate(); | |
checkIfVideoAwailable(); | |
enableAudio(m_audioEnabled); | |
setMute(m_mute); | |
setVolume(m_masterVolume, m_relativeVolume); | |
pause(); | |
} | |
void QuickTimeVideoPlayer::openMovieFromCurrentMediaSource() | |
{ | |
switch (m_mediaSource.type()){ | |
case MediaSource::LocalFile: | |
openMovieFromFile(); | |
break; | |
case MediaSource::Url: | |
openMovieFromUrl(); | |
break; | |
case MediaSource::Disc: | |
CASE_UNSUPPORTED("Could not open media source.", FATAL_ERROR) | |
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, "."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()); | |
} | |
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<quint64>(float(qtTime.timeValue) / float(qtTime.timeScale) * 1000.0f); | |
const_cast<QuickTimeVideoPlayer *>(this)->m_currentTime = t; | |
return m_currentTime; | |
} | |
long QuickTimeVideoPlayer::timeScale() const | |
{ | |
if (!m_QTMovie) | |
return 0; | |
PhononAutoReleasePool pool; | |
return [[m_QTMovie attributeForKey:@"QTMovieTimeScaleAttribute"] longValue]; | |
} | |
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<quint64>(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<quint64> | |
(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<QuickTimeVideoPlayer *>(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; | |
} | |
} | |
}} | |
QT_END_NAMESPACE |