/**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Digia. For licensing terms and ** conditions see http://qt.digia.com/licensing. For further information ** use the contact form at http://qt.digia.com/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qandroidmediaplayercontrol.h" #include "jmediaplayer.h" #include "qandroidvideooutput.h" QT_BEGIN_NAMESPACE QAndroidMediaPlayerControl::QAndroidMediaPlayerControl(QObject *parent) : QMediaPlayerControl(parent), mMediaPlayer(new JMediaPlayer), mCurrentState(QMediaPlayer::StoppedState), mCurrentMediaStatus(QMediaPlayer::NoMedia), mMediaStream(0), mVideoOutput(0), mSeekable(true), mBufferPercent(-1), mAudioAvailable(false), mVideoAvailable(false), mBuffering(false), mMediaPlayerReady(false), mPendingPosition(-1), mPendingSetMedia(false) { connect(mMediaPlayer, SIGNAL(bufferingUpdate(qint32)), this, SLOT(onBufferChanged(qint32))); connect(mMediaPlayer, SIGNAL(info(qint32, qint32)), this, SLOT(onInfo(qint32, qint32))); connect(mMediaPlayer, SIGNAL(error(qint32, qint32)), this, SLOT(onError(qint32, qint32))); connect(mMediaPlayer, SIGNAL(mediaPlayerInfo(qint32, qint32)), this, SLOT(onMediaPlayerInfo(qint32, qint32))); connect(mMediaPlayer, SIGNAL(videoSizeChanged(qint32,qint32)), this, SLOT(onVideoSizeChanged(qint32,qint32))); } QAndroidMediaPlayerControl::~QAndroidMediaPlayerControl() { mMediaPlayer->stop(); mMediaPlayer->release(); delete mMediaPlayer; } QMediaPlayer::State QAndroidMediaPlayerControl::state() const { return mCurrentState; } QMediaPlayer::MediaStatus QAndroidMediaPlayerControl::mediaStatus() const { return mCurrentMediaStatus; } qint64 QAndroidMediaPlayerControl::duration() const { return (mCurrentMediaStatus == QMediaPlayer::InvalidMedia || mCurrentMediaStatus == QMediaPlayer::NoMedia || !mMediaPlayerReady) ? 0 : mMediaPlayer->getDuration(); } qint64 QAndroidMediaPlayerControl::position() const { if (!mMediaPlayerReady) return mPendingPosition < 0 ? 0 : mPendingPosition; return mMediaPlayer->getCurrentPosition(); } void QAndroidMediaPlayerControl::setPosition(qint64 position) { if (!mSeekable) return; const int seekPosition = (position > INT_MAX) ? INT_MAX : position; if (!mMediaPlayerReady) { mPendingPosition = seekPosition; Q_EMIT positionChanged(seekPosition); return; } mMediaPlayer->seekTo(seekPosition); mPendingPosition = -1; } int QAndroidMediaPlayerControl::volume() const { return mMediaPlayer->volume(); } void QAndroidMediaPlayerControl::setVolume(int volume) { mMediaPlayer->setVolume(volume); Q_EMIT volumeChanged(volume); } bool QAndroidMediaPlayerControl::isMuted() const { return mMediaPlayer->isMuted(); } void QAndroidMediaPlayerControl::setMuted(bool muted) { mMediaPlayer->setMuted(muted); Q_EMIT mutedChanged(muted); } int QAndroidMediaPlayerControl::bufferStatus() const { return mBufferPercent; } bool QAndroidMediaPlayerControl::isAudioAvailable() const { return mAudioAvailable; } bool QAndroidMediaPlayerControl::isVideoAvailable() const { return mVideoAvailable; } bool QAndroidMediaPlayerControl::isSeekable() const { return mSeekable; } QMediaTimeRange QAndroidMediaPlayerControl::availablePlaybackRanges() const { return mAvailablePlaybackRange; } void QAndroidMediaPlayerControl::updateAvailablePlaybackRanges() { if (mBuffering) { const qint64 pos = position(); const qint64 end = (duration() / 100) * mBufferPercent; mAvailablePlaybackRange.addInterval(pos, end); } else if (mSeekable) { mAvailablePlaybackRange = QMediaTimeRange(0, duration()); } else { mAvailablePlaybackRange = QMediaTimeRange(); } Q_EMIT availablePlaybackRangesChanged(mAvailablePlaybackRange); } qreal QAndroidMediaPlayerControl::playbackRate() const { return 1.0f; } void QAndroidMediaPlayerControl::setPlaybackRate(qreal rate) { Q_UNUSED(rate); } QMediaContent QAndroidMediaPlayerControl::media() const { return mMediaContent; } const QIODevice *QAndroidMediaPlayerControl::mediaStream() const { return mMediaStream; } void QAndroidMediaPlayerControl::setMedia(const QMediaContent &mediaContent, QIODevice *stream) { mMediaContent = mediaContent; mMediaStream = stream; if (mVideoOutput && !mMediaPlayer->display()) { // if a video output is set but the video texture is not ready, delay loading the media // since it can cause problems on some hardware mPendingSetMedia = true; return; } const QUrl url = mediaContent.canonicalUrl(); QString mediaPath; if (url.scheme() == QLatin1String("qrc")) { const QString path = url.toString().mid(3); mTempFile.reset(QTemporaryFile::createNativeFile(path)); if (!mTempFile.isNull()) mediaPath = QLatin1String("file://") + mTempFile->fileName(); } else { mediaPath = url.toString(); } if (!mediaPath.isEmpty()) mMediaPlayer->setDataSource(mediaPath); else setMediaStatus(QMediaPlayer::NoMedia); Q_EMIT mediaChanged(mMediaContent); resetBufferingProgress(); // reset some properties setAudioAvailable(false); setVideoAvailable(false); setSeekable(true); } void QAndroidMediaPlayerControl::setVideoOutput(QObject *videoOutput) { if (mVideoOutput) mVideoOutput->stop(); mVideoOutput = qobject_cast(videoOutput); if (mVideoOutput && !mMediaPlayer->display()) { if (mVideoOutput->isReady()) mMediaPlayer->setDisplay(mVideoOutput->surfaceHolder()); else connect(videoOutput, SIGNAL(readyChanged(bool)), this, SLOT(onVideoOutputReady(bool))); } } void QAndroidMediaPlayerControl::play() { if (!mMediaPlayerReady) { mPendingState = QMediaPlayer::PlayingState; if (mCurrentState == QMediaPlayer::StoppedState && !mMediaContent.isNull() && mCurrentMediaStatus != QMediaPlayer::LoadingMedia && !mPendingSetMedia) { setMedia(mMediaContent, 0); } return; } mMediaPlayer->play(); setState(QMediaPlayer::PlayingState); } void QAndroidMediaPlayerControl::pause() { if (!mMediaPlayerReady) { mPendingState = QMediaPlayer::PausedState; return; } mMediaPlayer->pause(); setState(QMediaPlayer::PausedState); } void QAndroidMediaPlayerControl::stop() { mMediaPlayer->stop(); setState(QMediaPlayer::StoppedState); } void QAndroidMediaPlayerControl::onInfo(qint32 what, qint32 extra) { Q_UNUSED(extra); switch (what) { case JMediaPlayer::MEDIA_INFO_UNKNOWN: break; case JMediaPlayer::MEDIA_INFO_VIDEO_TRACK_LAGGING: // IGNORE break; case JMediaPlayer::MEDIA_INFO_VIDEO_RENDERING_START: break; case JMediaPlayer::MEDIA_INFO_BUFFERING_START: mPendingState = mCurrentState; setState(QMediaPlayer::PausedState); setMediaStatus(QMediaPlayer::StalledMedia); break; case JMediaPlayer::MEDIA_INFO_BUFFERING_END: setMediaStatus(mBufferPercent == 100 ? QMediaPlayer::BufferedMedia : QMediaPlayer::BufferingMedia); flushPendingStates(); break; case JMediaPlayer::MEDIA_INFO_BAD_INTERLEAVING: break; case JMediaPlayer::MEDIA_INFO_NOT_SEEKABLE: setSeekable(false); break; case JMediaPlayer::MEDIA_INFO_METADATA_UPDATE: Q_EMIT metaDataUpdated(); break; } } void QAndroidMediaPlayerControl::onMediaPlayerInfo(qint32 what, qint32 extra) { switch (what) { case JMediaPlayer::MEDIA_PLAYER_INVALID_STATE: setError(what, QStringLiteral("Error: Invalid state")); break; case JMediaPlayer::MEDIA_PLAYER_PREPARING: setMediaStatus(QMediaPlayer::LoadingMedia); setState(QMediaPlayer::StoppedState); break; case JMediaPlayer::MEDIA_PLAYER_READY: setMediaStatus(QMediaPlayer::LoadedMedia); if (mBuffering) { setMediaStatus(mBufferPercent == 100 ? QMediaPlayer::BufferedMedia : QMediaPlayer::BufferingMedia); } else { onBufferChanged(100); } setAudioAvailable(true); mMediaPlayerReady = true; flushPendingStates(); break; case JMediaPlayer::MEDIA_PLAYER_DURATION: Q_EMIT durationChanged(extra); break; case JMediaPlayer::MEDIA_PLAYER_PROGRESS: Q_EMIT positionChanged(extra); break; case JMediaPlayer::MEDIA_PLAYER_FINISHED: stop(); setMediaStatus(QMediaPlayer::EndOfMedia); break; } } void QAndroidMediaPlayerControl::onError(qint32 what, qint32 extra) { QString errorString; QMediaPlayer::Error error = QMediaPlayer::ResourceError; switch (what) { case JMediaPlayer::MEDIA_ERROR_UNKNOWN: errorString = QLatin1String("Error:"); break; case JMediaPlayer::MEDIA_ERROR_SERVER_DIED: errorString = QLatin1String("Error: Server died"); error = QMediaPlayer::ServiceMissingError; break; } switch (extra) { case JMediaPlayer::MEDIA_ERROR_IO: // Network OR file error errorString += QLatin1String(" (I/O operation failed)"); error = QMediaPlayer::NetworkError; setMediaStatus(QMediaPlayer::InvalidMedia); break; case JMediaPlayer::MEDIA_ERROR_MALFORMED: errorString += QLatin1String(" (Malformed bitstream)"); error = QMediaPlayer::FormatError; setMediaStatus(QMediaPlayer::InvalidMedia); break; case JMediaPlayer::MEDIA_ERROR_UNSUPPORTED: errorString += QLatin1String(" (Unsupported media)"); error = QMediaPlayer::FormatError; setMediaStatus(QMediaPlayer::InvalidMedia); break; case JMediaPlayer::MEDIA_ERROR_TIMED_OUT: errorString += QLatin1String(" (Timed out)"); break; case JMediaPlayer::MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK: errorString += QLatin1String(" (Unable to start progressive playback')"); error = QMediaPlayer::FormatError; setMediaStatus(QMediaPlayer::InvalidMedia); break; } setError(error, errorString); } void QAndroidMediaPlayerControl::onBufferChanged(qint32 percent) { mBuffering = percent != 100; mBufferPercent = percent; Q_EMIT bufferStatusChanged(mBufferPercent); updateAvailablePlaybackRanges(); if (mBufferPercent == 100) setMediaStatus(QMediaPlayer::BufferedMedia); } void QAndroidMediaPlayerControl::onVideoSizeChanged(qint32 width, qint32 height) { QSize newSize(width, height); if (width == 0 || height == 0 || newSize == mVideoSize) return; setVideoAvailable(true); mVideoSize = newSize; if (mVideoOutput) mVideoOutput->setVideoSize(mVideoSize); } void QAndroidMediaPlayerControl::onVideoOutputReady(bool ready) { if (!mMediaPlayer->display() && mVideoOutput && ready) { mMediaPlayer->setDisplay(mVideoOutput->surfaceHolder()); flushPendingStates(); } } void QAndroidMediaPlayerControl::setState(QMediaPlayer::State state) { if (mCurrentState == state) return; if (state == QMediaPlayer::StoppedState) { if (mVideoOutput) mVideoOutput->stop(); resetBufferingProgress(); mMediaPlayerReady = false; mPendingPosition = -1; Q_EMIT positionChanged(0); } mCurrentState = state; Q_EMIT stateChanged(mCurrentState); } void QAndroidMediaPlayerControl::setMediaStatus(QMediaPlayer::MediaStatus status) { if (mCurrentMediaStatus == status) return; if (status == QMediaPlayer::NoMedia || status == QMediaPlayer::InvalidMedia) Q_EMIT durationChanged(0); mCurrentMediaStatus = status; Q_EMIT mediaStatusChanged(mCurrentMediaStatus); } void QAndroidMediaPlayerControl::setError(int error, const QString &errorString) { setState(QMediaPlayer::StoppedState); Q_EMIT QMediaPlayerControl::error(error, errorString); } void QAndroidMediaPlayerControl::setSeekable(bool seekable) { if (mSeekable == seekable) return; mSeekable = seekable; Q_EMIT seekableChanged(mSeekable); } void QAndroidMediaPlayerControl::setAudioAvailable(bool available) { if (mAudioAvailable == available) return; mAudioAvailable = available; Q_EMIT audioAvailableChanged(mAudioAvailable); } void QAndroidMediaPlayerControl::setVideoAvailable(bool available) { if (mVideoAvailable == available) return; if (!available) mVideoSize = QSize(); mVideoAvailable = available; Q_EMIT videoAvailableChanged(mVideoAvailable); } void QAndroidMediaPlayerControl::resetBufferingProgress() { mBuffering = false; mBufferPercent = 0; mAvailablePlaybackRange = QMediaTimeRange(); Q_EMIT bufferStatusChanged(mBufferPercent); } void QAndroidMediaPlayerControl::flushPendingStates() { if (mPendingSetMedia) { setMedia(mMediaContent, 0); mPendingSetMedia = false; return; } switch (mPendingState) { case QMediaPlayer::PlayingState: if (mPendingPosition > -1) setPosition(mPendingPosition); play(); break; case QMediaPlayer::PausedState: pause(); break; case QMediaPlayer::StoppedState: stop(); break; } } QT_END_NAMESPACE