/* 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 "abstractmediaplayer.h" #include "defs.h" #include "mediaobject.h" #include "utils.h" QT_BEGIN_NAMESPACE using namespace Phonon; using namespace Phonon::MMF; /*! \class MMF::AbstractMediaPlayer \internal */ //----------------------------------------------------------------------------- // Constants //----------------------------------------------------------------------------- const int NullMaxVolume = -1; const int BufferStatusTimerInterval = 100; // ms //----------------------------------------------------------------------------- // Constructor / destructor //----------------------------------------------------------------------------- MMF::AbstractMediaPlayer::AbstractMediaPlayer (MediaObject *parent, const AbstractPlayer *player) : AbstractPlayer(player) , m_parent(parent) , m_playPending(false) , m_positionTimer(new QTimer(this)) , m_bufferStatusTimer(new QTimer(this)) , m_mmfMaxVolume(NullMaxVolume) , m_prefinishMarkSent(false) , m_aboutToFinishSent(false) { connect(m_positionTimer.data(), SIGNAL(timeout()), this, SLOT(positionTick())); connect(m_bufferStatusTimer.data(), SIGNAL(timeout()), this, SLOT(bufferStatusTick())); } //----------------------------------------------------------------------------- // MediaObjectInterface //----------------------------------------------------------------------------- void MMF::AbstractMediaPlayer::play() { TRACE_CONTEXT(AbstractMediaPlayer::play, EAudioApi); TRACE_ENTRY("state %d", privateState()); switch (privateState()) { case GroundState: setError(tr("Not ready to play")); break; case LoadingState: m_playPending = true; break; case StoppedState: case PausedState: doPlay(); startPositionTimer(); changeState(PlayingState); break; case PlayingState: case BufferingState: case ErrorState: // Do nothing break; // Protection against adding new states and forgetting to update this switch default: TRACE_PANIC(InvalidStatePanic); } TRACE_EXIT("state %d", privateState()); } void MMF::AbstractMediaPlayer::pause() { TRACE_CONTEXT(AbstractMediaPlayer::pause, EAudioApi); TRACE_ENTRY("state %d", privateState()); m_playPending = false; stopTimers(); switch (privateState()) { case GroundState: case LoadingState: case PausedState: case StoppedState: // Do nothing break; case PlayingState: case BufferingState: changeState(PausedState); // Fall through case ErrorState: doPause(); break; // Protection against adding new states and forgetting to update this switch default: TRACE_PANIC(InvalidStatePanic); } TRACE_EXIT("state %d", privateState()); } void MMF::AbstractMediaPlayer::stop() { TRACE_CONTEXT(AbstractMediaPlayer::stop, EAudioApi); TRACE_ENTRY("state %d", privateState()); m_playPending = false; stopTimers(); switch (privateState()) { case GroundState: case LoadingState: case StoppedState: case ErrorState: // Do nothing break; case PlayingState: case BufferingState: case PausedState: doStop(); changeState(StoppedState); break; // Protection against adding new states and forgetting to update this switch default: TRACE_PANIC(InvalidStatePanic); } TRACE_EXIT("state %d", privateState()); } void MMF::AbstractMediaPlayer::seek(qint64 ms) { TRACE_CONTEXT(AbstractMediaPlayer::seek, EAudioApi); TRACE_ENTRY("state %d pos %Ld", state(), ms); switch (privateState()) { // Fallthrough all these case GroundState: case StoppedState: case PausedState: case PlayingState: case LoadingState: { bool wasPlaying = false; if (state() == PlayingState) { stopPositionTimer(); doPause(); wasPlaying = true; } doSeek(ms); resetMarksIfRewound(); if(wasPlaying && state() != ErrorState) { doPlay(); startPositionTimer(); } break; } case BufferingState: // Fallthrough case ErrorState: // Do nothing break; } TRACE_EXIT_0(); } bool MMF::AbstractMediaPlayer::isSeekable() const { return true; } void MMF::AbstractMediaPlayer::doSetTickInterval(qint32 interval) { TRACE_CONTEXT(AbstractMediaPlayer::doSetTickInterval, EAudioApi); TRACE_ENTRY("state %d m_interval %d interval %d", privateState(), tickInterval(), interval); m_positionTimer->setInterval(interval); TRACE_EXIT_0(); } void MMF::AbstractMediaPlayer::open(const MediaSource &source, RFile& file) { TRACE_CONTEXT(AbstractMediaPlayer::setFileSource, EAudioApi); TRACE_ENTRY("state %d source.type %d", privateState(), source.type()); close(); changeState(GroundState); TInt symbianErr = KErrNone; QString errorMessage; switch (source.type()) { case MediaSource::LocalFile: { symbianErr = openFile(file); if (KErrNone != symbianErr) errorMessage = tr("Error opening file"); break; } case MediaSource::Url: { const QUrl url(source.url()); if (url.scheme() == QLatin1String("file")) { symbianErr = openFile(file); if (KErrNone != symbianErr) errorMessage = tr("Error opening file"); } else { symbianErr = openUrl(url.toString()); if (KErrNone != symbianErr) errorMessage = tr("Error opening URL"); } break; } // Other source types are handled in MediaObject::createPlayer // Protection against adding new media types and forgetting to update this switch default: TRACE_PANIC(InvalidMediaTypePanic); } if (errorMessage.isEmpty()) { changeState(LoadingState); } else { if (symbianErr) setError(errorMessage, symbianErr); else setError(errorMessage); } TRACE_EXIT_0(); } void MMF::AbstractMediaPlayer::volumeChanged(qreal volume) { TRACE_CONTEXT(AbstractMediaPlayer::volumeChanged, EAudioInternal); TRACE_ENTRY("state %d", privateState()); AbstractPlayer::volumeChanged(volume); doVolumeChanged(); TRACE_EXIT_0(); } //----------------------------------------------------------------------------- // Private functions //----------------------------------------------------------------------------- void MMF::AbstractMediaPlayer::startPositionTimer() { m_positionTimer->start(tickInterval()); } void MMF::AbstractMediaPlayer::stopPositionTimer() { m_positionTimer->stop(); } void MMF::AbstractMediaPlayer::startBufferStatusTimer() { m_bufferStatusTimer->start(BufferStatusTimerInterval); } void MMF::AbstractMediaPlayer::stopBufferStatusTimer() { m_bufferStatusTimer->stop(); } void MMF::AbstractMediaPlayer::stopTimers() { stopPositionTimer(); stopBufferStatusTimer(); } void MMF::AbstractMediaPlayer::doVolumeChanged() { switch (privateState()) { case GroundState: case LoadingState: case ErrorState: // Do nothing break; case StoppedState: case PausedState: case PlayingState: case BufferingState: { const qreal volume = (m_volume * m_mmfMaxVolume) + 0.5; const int err = setDeviceVolume(volume); if (KErrNone != err) { setError(tr("Setting volume failed"), err); } break; } // Protection against adding new states and forgetting to update this // switch default: Utils::panic(InvalidStatePanic); } } //----------------------------------------------------------------------------- // Protected functions //----------------------------------------------------------------------------- void MMF::AbstractMediaPlayer::bufferingStarted() { m_stateBeforeBuffering = privateState(); changeState(BufferingState); bufferStatusTick(); startBufferStatusTimer(); } void MMF::AbstractMediaPlayer::bufferingComplete() { stopBufferStatusTimer(); emit MMF::AbstractPlayer::bufferStatus(100); changeState(m_stateBeforeBuffering); } void MMF::AbstractMediaPlayer::maxVolumeChanged(int mmfMaxVolume) { m_mmfMaxVolume = mmfMaxVolume; doVolumeChanged(); } void MMF::AbstractMediaPlayer::playbackComplete(int error) { stopTimers(); if (KErrNone == error) { changeState(StoppedState); // MediaObject::switchToNextSource deletes the current player, so we // call it via delayed slot invokation to ensure that this object does // not get deleted during execution of a member function. QMetaObject::invokeMethod(m_parent, "switchToNextSource", Qt::QueuedConnection); } else { setError(tr("Playback complete"), error); } } qint64 MMF::AbstractMediaPlayer::toMilliSeconds(const TTimeIntervalMicroSeconds &in) { return in.Int64() / 1000; } //----------------------------------------------------------------------------- // Slots //----------------------------------------------------------------------------- void MMF::AbstractMediaPlayer::positionTick() { emitMarksIfReached(); const qint64 current = currentTime(); emit MMF::AbstractPlayer::tick(current); } void MMF::AbstractMediaPlayer::emitMarksIfReached() { const qint64 current = currentTime(); const qint64 total = totalTime(); const qint64 remaining = total - current; if (prefinishMark() && !m_prefinishMarkSent) { if (remaining < (prefinishMark() + tickInterval()/2)) { m_prefinishMarkSent = true; emit prefinishMarkReached(remaining); } } if (!m_aboutToFinishSent) { if (remaining < tickInterval()) { m_aboutToFinishSent = true; emit aboutToFinish(); } } } void MMF::AbstractMediaPlayer::resetMarksIfRewound() { const qint64 current = currentTime(); const qint64 total = totalTime(); const qint64 remaining = total - current; if (prefinishMark() && m_prefinishMarkSent) if (remaining >= (prefinishMark() + tickInterval()/2)) m_prefinishMarkSent = false; if (m_aboutToFinishSent) if (remaining >= tickInterval()) m_aboutToFinishSent = false; } void MMF::AbstractMediaPlayer::bufferStatusTick() { emit MMF::AbstractPlayer::bufferStatus(bufferStatus()); } void MMF::AbstractMediaPlayer::changeState(PrivateState newState) { TRACE_CONTEXT(AbstractMediaPlayer::changeState, EAudioInternal); const Phonon::State oldPhononState = phononState(privateState()); const Phonon::State newPhononState = phononState(newState); // TODO: add some invariants to check that the transition is valid AbstractPlayer::changeState(newState); if (LoadingState == oldPhononState && StoppedState == newPhononState) { // Ensure initial volume is set on MMF API before starting playback doVolumeChanged(); // Check whether play() was called while clip was being loaded. If so, // playback should be started now if (m_playPending) { TRACE_0("play was called while loading; starting playback now"); m_playPending = false; play(); } } } void MMF::AbstractMediaPlayer::updateMetaData() { TRACE_CONTEXT(AbstractMediaPlayer::updateMetaData, EAudioInternal); TRACE_ENTRY_0(); m_metaData.clear(); const int numberOfEntries = numberOfMetaDataEntries(); for(int i=0; i entry = metaDataEntry(i); // Note that we capitalize the key, as required by the Ogg Vorbis // metadata standard to which Phonon adheres: // http://xiph.org/vorbis/doc/v-comment.html m_metaData.insert(entry.first.toUpper(), entry.second); } emit metaDataChanged(m_metaData); TRACE_EXIT_0(); } QT_END_NAMESPACE