/*  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 <QtCore/QEvent>
#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; i<connection->m_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; i<node->m_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<MediaObject *>(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<MediaObject *>(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<QTimerEvent *>(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<QVariant> &params)
{
    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"

