/* 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
#include "mediaobject.h"
#include "backendheader.h"
#include "videowidget.h"
#include "videoframe.h"
#include "audiooutput.h"
#include "quicktimevideoplayer.h"
#include "quicktimemetadata.h"
#include "audiograph.h"
#include "mediaobjectaudionode.h"
#include "quicktimeaudioplayer.h"
QT_BEGIN_NAMESPACE
namespace Phonon
{
namespace QT7
{
MediaObject::MediaObject(QObject *parent) : MediaNode(AudioSource | VideoSource, parent)
{
m_owningMediaObject = this;
m_state = Phonon::LoadingState;
m_videoPlayer = new QuickTimeVideoPlayer();
m_audioPlayer = new QuickTimeAudioPlayer();
m_nextVideoPlayer = new QuickTimeVideoPlayer();
m_nextAudioPlayer = new QuickTimeAudioPlayer();
m_mediaObjectAudioNode = new MediaObjectAudioNode(m_audioPlayer, m_nextAudioPlayer);
setAudioNode(m_mediaObjectAudioNode);
m_audioGraph = new AudioGraph(this);
m_tickInterval = 0;
m_prefinishMark = 0;
m_currentTime = 0;
m_transitionTime = 0;
m_percentageLoaded = 0;
m_waitNextSwap = false;
m_autoplayTitles = true;
m_audioEffectCount = 0;
m_audioOutputCount = 0;
m_videoEffectCount = 0;
m_videoOutputCount = 0;
m_audioSystem = AS_Unset;
m_errorType = Phonon::NoError;
m_tickTimer = 0;
m_videoTimer = 0;
m_audioTimer = 0;
m_rapidTimer = 0;
#if QT_ALLOW_QUICKTIME
m_displayLink = 0;
m_pendingDisplayLinkEvent = false;
#endif
checkForError();
}
MediaObject::~MediaObject()
{
// m_mediaObjectAudioNode is owned by super class.
#if QT_ALLOW_QUICKTIME
stopDisplayLink();
#endif
m_audioPlayer->unsetVideoPlayer();
m_nextAudioPlayer->unsetVideoPlayer();
delete m_videoPlayer;
delete m_nextVideoPlayer;
checkForError();
}
bool MediaObject::setState(Phonon::State state)
{
Phonon::State prevState = m_state;
m_state = state;
if (prevState != m_state){
emit stateChanged(m_state, prevState);
if (m_state != state){
// End-application did something
// upon receiving the signal.
return false;
}
}
return true;
}
void MediaObject::inspectAudioGraphRecursive(AudioConnection *connection, int &effectCount, int &outputCount)
{
if ((connection->m_sink->m_description & (AudioSource | AudioSink)) == (AudioSource | AudioSink))
++effectCount;
else if (connection->m_sink->m_description & AudioSink)
++outputCount;
for (int i=0; im_sink->m_audioSinkList.size(); ++i)
inspectAudioGraphRecursive(connection->m_sink->m_audioSinkList[i], effectCount, outputCount);
}
void MediaObject::inspectVideoGraphRecursive(MediaNode *node, int &effectCount, int &outputCount)
{
if ((node->m_description & (VideoSource | VideoSink)) == (VideoSource | VideoSink))
++effectCount;
else if (node->m_description & VideoSink)
++outputCount;
for (int i=0; im_videoSinkList.size(); ++i)
inspectVideoGraphRecursive(node->m_videoSinkList[i], effectCount, outputCount);
}
void MediaObject::inspectGraph()
{
// Inspect the graph to check wether there are any
// effects or outputs connected. This will have
// influence on the audio system and video system that ends up beeing used:
int prevVideoOutputCount = m_videoOutputCount;
m_audioEffectCount = 0;
m_audioOutputCount = 0;
m_videoEffectCount = 0;
m_videoOutputCount = 0;
AudioConnection rootConnection(this);
inspectAudioGraphRecursive(&rootConnection, m_audioEffectCount, m_audioOutputCount);
inspectVideoGraphRecursive(this, m_videoEffectCount, m_videoOutputCount);
if (m_videoOutputCount != prevVideoOutputCount){
MediaNodeEvent e1(MediaNodeEvent::VideoOutputCountChanged, &m_videoOutputCount);
notify(&e1);
}
}
void MediaObject::setupAudioSystem()
{
// Select which audio system to use:
AudioSystem newAudioSystem = AS_Unset;
if (!m_audioOutputCount || !m_videoPlayer->canPlayMedia()){
newAudioSystem = AS_Silent;
} else if (m_audioEffectCount == 0){
newAudioSystem = AS_Video;
} else if (QSysInfo::MacintoshVersion < QSysInfo::MV_10_4){
newAudioSystem = AS_Video;
SET_ERROR("Audio effects are not supported for Mac OS 10.3 and below", NORMAL_ERROR);
} else if (m_videoPlayer->isDrmProtected()){
newAudioSystem = AS_Video;
SET_ERROR("Audio effects are not supported for DRM protected media", NORMAL_ERROR);
} else if (m_audioGraph->graphCannotPlay()){
newAudioSystem = AS_Video;
SET_ERROR("Audio effects are not supported for the current codec", NORMAL_ERROR);
#ifdef QUICKTIME_C_API_AVAILABLE
} else {
newAudioSystem = AS_Graph;
}
#else
} else {
newAudioSystem = AS_Video;
SET_ERROR("Audio effects are not supported for the 64-bit version of the Phonon QT7 backend", NORMAL_ERROR);
}
#endif
if (newAudioSystem == m_audioSystem)
return;
// Enable selected audio system:
m_audioSystem = newAudioSystem;
switch (newAudioSystem){
case AS_Silent:
m_audioGraph->stop();
m_videoPlayer->enableAudio(false);
m_nextVideoPlayer->enableAudio(false);
m_audioPlayer->enableAudio(false);
m_nextAudioPlayer->enableAudio(false);
break;
case AS_Graph:
if (m_state == Phonon::PausedState)
m_audioGraph->prepare();
else
m_audioGraph->start();
// Starting the graph can lead to a recursive call
// telling us that we must direct audio through
// video. If that has happened, we must not proceed:
if (m_audioSystem != AS_Graph)
return;
m_videoPlayer->enableAudio(false);
m_nextVideoPlayer->enableAudio(false);
m_audioPlayer->enableAudio(true);
m_audioPlayer->seek(m_videoPlayer->currentTime());
m_nextAudioPlayer->enableAudio(true);
m_audioPlayer->seek(m_videoPlayer->currentTime());
m_nextAudioPlayer->seek(m_nextVideoPlayer->currentTime());
break;
case AS_Video:
case AS_Unset:
m_audioGraph->stop();
m_videoPlayer->enableAudio(true);
m_nextVideoPlayer->enableAudio(true);
m_audioPlayer->enableAudio(false);
m_nextAudioPlayer->enableAudio(false);
m_videoPlayer->seek(m_audioPlayer->currentTime());
m_nextVideoPlayer->seek(m_nextAudioPlayer->currentTime());
break;
}
}
void MediaObject::setSource(const MediaSource &source)
{
IMPLEMENTED;
PhononAutoReleasePool pool;
setState(Phonon::LoadingState);
// Save current state for event/signal handling below:
bool prevHasVideo = m_videoPlayer->hasVideo();
qint64 prevTotalTime = totalTime();
int prevTrackCount = m_videoPlayer->trackCount();
m_waitNextSwap = false;
// Cancel cross-fade if any:
m_nextVideoPlayer->pause();
m_nextAudioPlayer->pause();
m_mediaObjectAudioNode->cancelCrossFade();
// Set new source:
m_audioPlayer->unsetVideoPlayer();
m_videoPlayer->setMediaSource(source);
m_audioPlayer->setVideoPlayer(m_videoPlayer);
m_audioGraph->updateStreamSpecifications();
m_nextAudioPlayer->unsetVideoPlayer();
m_nextVideoPlayer->unsetCurrentMediaSource();
m_currentTime = 0;
// Emit/notify information about the new source:
QRect videoRect = m_videoPlayer->videoRect();
MediaNodeEvent e1(MediaNodeEvent::VideoFrameSizeChanged, &videoRect);
notify(&e1);
// Clear video widgets:
VideoFrame emptyFrame;
updateVideo(emptyFrame);
emit currentSourceChanged(source);
emit metaDataChanged(m_videoPlayer->metaData());
if (prevHasVideo != m_videoPlayer->hasVideo())
emit hasVideoChanged(m_videoPlayer->hasVideo());
if (prevTotalTime != totalTime())
emit totalTimeChanged(totalTime());
if (prevTrackCount != m_videoPlayer->trackCount())
emit availableTitlesChanged(m_videoPlayer->trackCount());
if (checkForError())
return;
if (!m_videoPlayer->isDrmAuthorized())
SET_ERROR("This computer is not authorized to play current media (DRM protected).", FATAL_ERROR)
if (checkForError())
return;
if (!m_videoPlayer->canPlayMedia())
SET_ERROR("Cannot play media.", FATAL_ERROR)
// The state might have changed from LoadingState
// as a response to an error state change. So we
// need to check it before stopping:
if (m_state == Phonon::LoadingState)
stop();
setupAudioSystem();
checkForError();
}
void MediaObject::setNextSource(const MediaSource &source)
{
IMPLEMENTED;
m_nextAudioPlayer->unsetVideoPlayer();
m_nextVideoPlayer->setMediaSource(source);
m_nextAudioPlayer->setVideoPlayer(m_nextVideoPlayer);
checkForError();
}
void MediaObject::swapCurrentWithNext(qint32 transitionTime)
{
PhononAutoReleasePool pool;
setState(Phonon::LoadingState);
// Save current state for event/signal handling below:
bool prevHasVideo = m_videoPlayer->hasVideo();
qint64 prevTotalTime = totalTime();
int prevTrackCount = m_videoPlayer->trackCount();
qSwap(m_audioPlayer, m_nextAudioPlayer);
qSwap(m_videoPlayer, m_nextVideoPlayer);
m_mediaObjectAudioNode->startCrossFade(transitionTime);
m_audioGraph->updateStreamSpecifications();
m_waitNextSwap = false;
m_currentTime = 0;
// Emit/notify information about the new source:
QRect videoRect = m_videoPlayer->videoRect();
MediaNodeEvent e1(MediaNodeEvent::VideoFrameSizeChanged, &videoRect);
notify(&e1);
emit currentSourceChanged(m_videoPlayer->mediaSource());
emit metaDataChanged(m_videoPlayer->metaData());
if (prevHasVideo != m_videoPlayer->hasVideo())
emit hasVideoChanged(m_videoPlayer->hasVideo());
if (prevTotalTime != totalTime())
emit totalTimeChanged(totalTime());
if (prevTrackCount != m_videoPlayer->trackCount())
emit availableTitlesChanged(m_videoPlayer->trackCount());
if (checkForError())
return;
if (!m_videoPlayer->isDrmAuthorized())
SET_ERROR("This computer is not authorized to play current media (DRM protected).", FATAL_ERROR)
if (checkForError())
return;
if (!m_videoPlayer->canPlayMedia())
SET_ERROR("Cannot play next media.", FATAL_ERROR)
setupAudioSystem();
checkForError();
if (m_state == Phonon::LoadingState){
if (setState(Phonon::PlayingState))
play_internal();
checkForError();
}
}
#if QT_ALLOW_QUICKTIME
static CVReturn displayLinkCallback(CVDisplayLinkRef /*displayLink*/,
const CVTimeStamp */*inNow*/,
const CVTimeStamp */*inOutputTime*/,
CVOptionFlags /*flagsIn*/,
CVOptionFlags */*flagsOut*/,
void *userData)
{
MediaObject *mediaObject = static_cast(userData);
mediaObject->displayLinkEvent();
return kCVReturnSuccess;
}
void MediaObject::displayLinkEvent()
{
// This function is called from a
// thread != gui thread. So we post the event.
// But we need to make sure that we don't post faster
// than the event loop can eat:
m_displayLinkMutex.lock();
bool pending = m_pendingDisplayLinkEvent;
m_pendingDisplayLinkEvent = true;
m_displayLinkMutex.unlock();
if (!pending)
qApp->postEvent(this, new QEvent(QEvent::User), Qt::HighEventPriority);
}
void MediaObject::startDisplayLink()
{
if (m_displayLink)
return;
OSStatus err = CVDisplayLinkCreateWithCGDisplay(kCGDirectMainDisplay, &m_displayLink);
if (err != noErr)
goto fail;
err = CVDisplayLinkSetCurrentCGDisplay(m_displayLink, kCGDirectMainDisplay);
if (err != noErr)
goto fail;
err = CVDisplayLinkSetOutputCallback(m_displayLink, displayLinkCallback, this);
if (err != noErr)
goto fail;
err = CVDisplayLinkStart(m_displayLink);
if (err != noErr)
goto fail;
return;
fail:
stopDisplayLink();
}
void MediaObject::stopDisplayLink()
{
if (!m_displayLink)
return;
CVDisplayLinkStop(m_displayLink);
CFRelease(m_displayLink);
m_displayLink = 0;
}
#endif
void MediaObject::restartAudioVideoTimers()
{
if (m_videoTimer)
killTimer(m_videoTimer);
if (m_audioTimer)
killTimer(m_audioTimer);
#if QT_ALLOW_QUICKTIME
// We prefer to use a display link as timer if available, since
// it is more steady, and results in better and smoother frame drawing:
startDisplayLink();
if (!m_displayLink){
float fps = m_videoPlayer->staticFps();
long videoUpdateFrequency = fps ? long(1000.0f / fps) : 0.001;
m_videoTimer = startTimer(videoUpdateFrequency);
}
#else
float fps = m_videoPlayer->staticFps();
long videoUpdateFrequency = fps ? long(1000.0f / fps) : 0.001;
m_videoTimer = startTimer(videoUpdateFrequency);
#endif
long audioUpdateFrequency = m_audioPlayer->regularTaskFrequency();
m_audioTimer = startTimer(audioUpdateFrequency);
updateVideoFrames();
updateAudioBuffers();
}
void MediaObject::play_internal()
{
// Play main audio/video:
m_videoPlayer->play();
m_audioPlayer->play();
updateLipSynch(0);
// Play old audio/video to finish cross-fade:
if (m_nextVideoPlayer->currentTime() > 0){
m_nextVideoPlayer->play();
m_nextAudioPlayer->play();
}
restartAudioVideoTimers();
if (!m_rapidTimer)
m_rapidTimer = startTimer(100);
}
void MediaObject::pause_internal()
{
m_audioGraph->stop();
m_audioPlayer->pause();
m_nextAudioPlayer->pause();
m_videoPlayer->pause();
m_nextVideoPlayer->pause();
killTimer(m_rapidTimer);
killTimer(m_videoTimer);
killTimer(m_audioTimer);
m_rapidTimer = 0;
m_videoTimer = 0;
m_audioTimer = 0;
#if QT_ALLOW_QUICKTIME
stopDisplayLink();
#endif
if (m_waitNextSwap)
m_swapTimeLeft = m_swapTime.msecsTo(QTime::currentTime());
}
void MediaObject::play()
{
IMPLEMENTED;
if (m_state == Phonon::PlayingState)
return;
if (m_waitNextSwap){
// update swap time after pause:
m_swapTime = QTime::currentTime();
m_swapTime.addMSecs(m_swapTimeLeft);
setState(Phonon::PlayingState);
return;
}
if (m_currentTime == m_videoPlayer->duration())
return;
if (!m_videoPlayer->canPlayMedia())
return;
if (!setState(Phonon::PlayingState))
return;
if (m_audioSystem == AS_Graph){
m_audioGraph->start();
m_mediaObjectAudioNode->setMute(true);
}
// Inform the graph that we are about to play:
bool playing = true;
MediaNodeEvent e1(MediaNodeEvent::MediaPlaying, &playing);
notify(&e1);
// Start to play:
play_internal();
m_mediaObjectAudioNode->setMute(false);
checkForError();
}
void MediaObject::pause()
{
IMPLEMENTED;
if (m_state == Phonon::PausedState)
return;
if (!setState(Phonon::PausedState))
return;
pause_internal();
// Inform the graph that we are no longer playing:
bool playing = false;
MediaNodeEvent e1(MediaNodeEvent::MediaPlaying, &playing);
notify(&e1);
// But be prepared:
if (m_audioSystem == AS_Graph)
m_audioGraph->prepare();
checkForError();
}
void MediaObject::stop()
{
IMPLEMENTED;
if (m_state == Phonon::StoppedState)
return;
if (!setState(Phonon::StoppedState))
return;
m_waitNextSwap = false;
m_nextVideoPlayer->unsetCurrentMediaSource();
m_nextAudioPlayer->unsetVideoPlayer();
pause_internal();
seek(0);
checkForError();
}
void MediaObject::seek(qint64 milliseconds)
{
IMPLEMENTED;
if (m_state == Phonon::ErrorState)
return;
// Stop cross-fade if any:
m_nextVideoPlayer->unsetCurrentMediaSource();
m_nextAudioPlayer->unsetVideoPlayer();
m_mediaObjectAudioNode->cancelCrossFade();
// Seek to new position:
m_mediaObjectAudioNode->setMute(true);
m_videoPlayer->seek(milliseconds);
m_audioPlayer->seek(m_videoPlayer->currentTime());
m_mediaObjectAudioNode->setMute(false);
// Update time and cancel pending swap:
if (m_currentTime < m_videoPlayer->duration())
m_waitNextSwap = false;
updateCurrentTime();
if (m_state != Phonon::PlayingState)
updateVideoFrames();
checkForError();
}
QStringList MediaObject::availableAudioStreams() const
{
NOT_IMPLEMENTED;
return QStringList();
}
QStringList MediaObject::availableVideoStreams() const
{
NOT_IMPLEMENTED;
return QStringList();
}
QStringList MediaObject::availableSubtitleStreams() const
{
NOT_IMPLEMENTED;
return QStringList();
}
QString MediaObject::currentAudioStream(const QObject */*audioPath*/) const
{
NOT_IMPLEMENTED;
return QString();
}
QString MediaObject::currentVideoStream(const QObject */*videoPath*/) const
{
NOT_IMPLEMENTED;
return QString();
}
QString MediaObject::currentSubtitleStream(const QObject */*videoPath*/) const
{
NOT_IMPLEMENTED;
return QString();
}
void MediaObject::setCurrentAudioStream(const QString &/*streamName*/,const QObject */*audioPath*/)
{
NOT_IMPLEMENTED;
}
void MediaObject::setCurrentVideoStream(const QString &/*streamName*/,const QObject */*videoPath*/)
{
NOT_IMPLEMENTED;
}
void MediaObject::setCurrentSubtitleStream(const QString &/*streamName*/,const QObject */*videoPath*/)
{
NOT_IMPLEMENTED;
}
int MediaObject::videoOutputCount()
{
return m_videoOutputCount;
}
void MediaObject::synchAudioVideo()
{
if (m_state != Phonon::PlayingState)
return;
if (m_videoSinkList.isEmpty() || m_audioSinkList.isEmpty())
return;
seek(m_currentTime);
checkForError();
}
qint32 MediaObject::tickInterval() const
{
IMPLEMENTED;
return m_tickInterval;
}
void MediaObject::setTickInterval(qint32 interval)
{
IMPLEMENTED;
m_tickInterval = interval;
if (m_tickInterval > 0)
m_tickTimer = startTimer(m_tickInterval);
else{
killTimer(m_tickTimer);
m_tickTimer = 0;
}
}
bool MediaObject::hasVideo() const
{
IMPLEMENTED;
return m_videoPlayer ? m_videoPlayer->hasVideo() : false;
}
bool MediaObject::isSeekable() const
{
IMPLEMENTED;
return m_videoPlayer ? m_videoPlayer->isSeekable() : false;
}
qint64 MediaObject::currentTime() const
{
IMPLEMENTED_SILENT;
const_cast(this)->updateCurrentTime();
return m_currentTime;
}
void MediaObject::updateCurrentTime()
{
quint64 lastUpdateTime = m_currentTime;
m_currentTime = (m_audioSystem == AS_Graph) ? m_audioPlayer->currentTime() : m_videoPlayer->currentTime();
quint64 total = m_videoPlayer->duration();
if (m_videoPlayer->currentTrack() < m_videoPlayer->trackCount() - 1){
// There are still more tracks to play after the current track.
if (m_autoplayTitles) {
if (lastUpdateTime < m_currentTime && m_currentTime == total)
setCurrentTrack(m_videoPlayer->currentTrack() + 1);
}
} else if (m_nextVideoPlayer->state() == QuickTimeVideoPlayer::NoMedia){
// There is no more sources or tracks to play after the current source.
// Check if it's time to emit aboutToFinish:
quint32 mark = qMax(quint64(0), qMin(total, total + m_transitionTime - 2000));
if (lastUpdateTime < mark && mark <= m_currentTime)
emit aboutToFinish();
// Check if it's time to emit prefinishMarkReached:
mark = qMax(quint64(0), total - m_prefinishMark);
if (lastUpdateTime < mark && mark <= m_currentTime)
emit prefinishMarkReached(total - m_currentTime);
if (lastUpdateTime < m_currentTime && m_currentTime == total){
emit finished();
m_currentTime = (m_audioSystem == AS_Graph) ? m_audioPlayer->currentTime() : m_videoPlayer->currentTime();
if (m_state == Phonon::PlayingState && m_currentTime == total)
pause();
}
} else {
// We have a next source.
// Check if it's time to swap to next source:
quint32 mark = qMax(quint64(0), total + m_transitionTime);
if (m_waitNextSwap && m_state == Phonon::PlayingState &&
m_transitionTime < m_swapTime.msecsTo(QTime::currentTime())){
swapCurrentWithNext(0);
} else if (mark >= total){
if (lastUpdateTime < total && total == m_currentTime){
m_swapTime = QTime::currentTime();
m_swapTime.addMSecs(mark - total);
m_waitNextSwap = true;
}
} else if (lastUpdateTime < mark && mark <= m_currentTime){
swapCurrentWithNext(total - m_currentTime);
}
}
}
qint64 MediaObject::totalTime() const
{
IMPLEMENTED_SILENT;
return m_videoPlayer->duration();
}
Phonon::State MediaObject::state() const
{
IMPLEMENTED;
return m_state;
}
QString MediaObject::errorString() const
{
IMPLEMENTED;
return m_errorString;
}
Phonon::ErrorType MediaObject::errorType() const
{
IMPLEMENTED;
return m_errorType;
}
bool MediaObject::checkForError()
{
int type = gGetErrorType();
if (type == NO_ERROR)
return false;
m_errorType = (type == NORMAL_ERROR) ? Phonon::NormalError : Phonon::FatalError;
m_errorString = gGetErrorString();
pause_internal();
gClearError();
setState(Phonon::ErrorState);
return true;
}
QuickTimeVideoPlayer* MediaObject::videoPlayer() const
{
return m_videoPlayer;
}
MediaSource MediaObject::source() const
{
IMPLEMENTED;
return m_videoPlayer->mediaSource();
}
qint32 MediaObject::prefinishMark() const
{
IMPLEMENTED;
return m_prefinishMark;
}
void MediaObject::setPrefinishMark(qint32 mark)
{
IMPLEMENTED;
m_prefinishMark = mark;
}
qint32 MediaObject::transitionTime() const
{
IMPLEMENTED;
return m_transitionTime;
}
void MediaObject::setTransitionTime(qint32 transitionTime)
{
IMPLEMENTED;
m_transitionTime = transitionTime;
}
void MediaObject::setVolumeOnMovie(float volume)
{
m_videoPlayer->setMasterVolume(volume);
m_nextVideoPlayer->setMasterVolume(volume);
}
bool MediaObject::setAudioDeviceOnMovie(int id)
{
m_nextVideoPlayer->setAudioDevice(id);
return m_videoPlayer->setAudioDevice(id);
}
void MediaObject::updateCrossFade()
{
m_mediaObjectAudioNode->updateCrossFade(m_currentTime);
// Clean-up previous movie if done fading:
if (m_mediaObjectAudioNode->m_fadeDuration == 0){
if (m_nextVideoPlayer->isPlaying() || m_nextAudioPlayer->isPlaying()){
m_nextVideoPlayer->unsetCurrentMediaSource();
m_nextAudioPlayer->unsetVideoPlayer();
}
}
}
void MediaObject::updateBufferStatus()
{
float percent = m_videoPlayer->percentageLoaded();
if (percent != m_percentageLoaded){
m_percentageLoaded = percent;
emit bufferStatus(m_percentageLoaded * 100);
}
}
void MediaObject::updateAudioBuffers()
{
// Schedule audio slices:
m_audioPlayer->scheduleAudioToGraph();
m_nextAudioPlayer->scheduleAudioToGraph();
}
bool MediaObject::isCrossFading()
{
return m_mediaObjectAudioNode->isCrossFading();
}
void MediaObject::updateVideoFrames()
{
// Draw next frame if awailable:
if (m_videoPlayer->videoFrameChanged()){
updateLipSynch(50);
VideoFrame frame(m_videoPlayer);
if (m_nextVideoPlayer->isPlaying()
&& m_nextVideoPlayer->hasVideo()
&& isCrossFading()){
VideoFrame bgFrame(m_nextVideoPlayer);
frame.setBackgroundFrame(bgFrame);
frame.setBaseOpacity(m_mediaObjectAudioNode->m_volume1);
}
// Send the frame through the graph:
updateVideo(frame);
checkForError();
}
}
void MediaObject::updateLipSynch(int allowedOffset)
{
if (m_audioSystem != AS_Graph || !m_audioGraph->isRunning())
return;
if (m_videoSinkList.isEmpty() || m_audioSinkList.isEmpty())
return;
if (m_videoPlayer->hasVideo()){
qint64 diff = m_audioPlayer->currentTime() - m_videoPlayer->currentTime();
if (-allowedOffset > diff || diff > allowedOffset)
m_audioPlayer->seek(m_videoPlayer->currentTime());
}
if (isCrossFading() && m_nextVideoPlayer->hasVideo()){
qint64 diff = m_nextAudioPlayer->currentTime() - m_nextVideoPlayer->currentTime();
if (-(allowedOffset*2) > diff || diff > (allowedOffset*2))
m_nextAudioPlayer->seek(m_nextVideoPlayer->currentTime());
}
}
void MediaObject::updateRapidly()
{
updateCurrentTime();
updateCrossFade();
updateBufferStatus();
}
void MediaObject::setMute(bool mute)
{
m_mediaObjectAudioNode->setMute(mute);
m_videoPlayer->setMute(mute);
m_nextVideoPlayer->setMute(mute);
}
void MediaObject::mediaNodeEvent(const MediaNodeEvent *event)
{
switch (event->type()){
case MediaNodeEvent::EndConnectionChange:
m_mediaObjectAudioNode->setMute(true);
inspectGraph();
setupAudioSystem();
synchAudioVideo();
checkForError();
m_mediaObjectAudioNode->setMute(false);
if (m_state == Phonon::PlayingState)
restartAudioVideoTimers();
break;
case MediaNodeEvent::AudioGraphCannotPlay:
case MediaNodeEvent::AudioGraphInitialized:
if (m_state != Phonon::LoadingState){
m_mediaObjectAudioNode->setMute(true);
setupAudioSystem();
updateLipSynch(0);
checkForError();
m_mediaObjectAudioNode->setMute(false);
}
break;
default:
break;
}
}
bool MediaObject::event(QEvent *event)
{
switch (event->type()){
#if QT_ALLOW_QUICKTIME
case QEvent::User:{
m_displayLinkMutex.lock();
m_pendingDisplayLinkEvent = false;
m_displayLinkMutex.unlock();
updateVideoFrames();
break; }
#endif
case QEvent::Timer:{
int timerId = static_cast(event)->timerId();
if (timerId == m_rapidTimer)
updateRapidly();
else if (timerId == m_tickTimer)
emit tick(currentTime());
else if (timerId == m_videoTimer)
updateVideoFrames();
else if (timerId == m_audioTimer)
updateAudioBuffers();
break; }
default:
break;
}
return QObject::event(event);
}
void MediaObject::setCurrentTrack(int track)
{
if (track == m_videoPlayer->currentTrack() || track < 0 || track >= m_videoPlayer->trackCount())
return;
m_videoPlayer->setCurrentTrack(track);
emit titleChanged(track);
emit metaDataChanged(m_videoPlayer->metaData());
}
bool MediaObject::hasInterface(Interface iface) const
{
return iface == AddonInterface::TitleInterface;
}
QVariant MediaObject::interfaceCall(Interface iface, int command, const QList ¶ms)
{
switch (iface) {
case TitleInterface:
switch (command) {
case availableTitles:
return m_videoPlayer->trackCount();
case title:
return m_videoPlayer->currentTrack();
case setTitle:
setCurrentTrack(params.first().toInt());
break;
case autoplayTitles:
return m_autoplayTitles;
case setAutoplayTitles:
m_autoplayTitles = params.first().toBool();
break;
}
default:
break;
}
return QVariant();
}
}} // namespace Phonon::QT7
QT_END_NAMESPACE
#include "moc_mediaobject.cpp"