diff -r 000000000000 -r 1918ee327afb src/3rdparty/phonon/qt7/quicktimevideoplayer.mm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/3rdparty/phonon/qt7/quicktimevideoplayer.mm Mon Jan 11 14:00:40 2010 +0000 @@ -0,0 +1,1168 @@ +/* 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