summaryrefslogtreecommitdiffstats
path: root/src/3rdparty/phonon/qt7/quicktimevideoplayer.mm
diff options
context:
space:
mode:
Diffstat (limited to 'src/3rdparty/phonon/qt7/quicktimevideoplayer.mm')
-rw-r--r--src/3rdparty/phonon/qt7/quicktimevideoplayer.mm955
1 files changed, 955 insertions, 0 deletions
diff --git a/src/3rdparty/phonon/qt7/quicktimevideoplayer.mm b/src/3rdparty/phonon/qt7/quicktimevideoplayer.mm
new file mode 100644
index 0000000000..3f76132f1e
--- /dev/null
+++ b/src/3rdparty/phonon/qt7/quicktimevideoplayer.mm
@@ -0,0 +1,955 @@
+/* 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