diff options
Diffstat (limited to 'src/plugins/blackberry/mediaplayer/bbmediaplayercontrol.cpp')
-rw-r--r-- | src/plugins/blackberry/mediaplayer/bbmediaplayercontrol.cpp | 646 |
1 files changed, 646 insertions, 0 deletions
diff --git a/src/plugins/blackberry/mediaplayer/bbmediaplayercontrol.cpp b/src/plugins/blackberry/mediaplayer/bbmediaplayercontrol.cpp new file mode 100644 index 000000000..1ef68cb12 --- /dev/null +++ b/src/plugins/blackberry/mediaplayer/bbmediaplayercontrol.cpp @@ -0,0 +1,646 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Research In Motion +** 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 "bbmediaplayercontrol.h" +#include "bbmetadatareadercontrol.h" +#include "bbplayervideorenderercontrol.h" +#include "bbutil.h" +#include "bbvideowindowcontrol.h" +#include <QtCore/qabstracteventdispatcher.h> +#include <QtCore/qcoreapplication.h> +#include <QtCore/qdir.h> +#include <QtCore/qfileinfo.h> +#include <QtCore/quuid.h> +#include <mm/renderer.h> +#include <bps/mmrenderer.h> +#include <bps/screen.h> +#include <errno.h> +#include <sys/strm.h> +#include <sys/stat.h> + +QT_BEGIN_NAMESPACE + +static int idCounter = 0; + +BbMediaPlayerControl::BbMediaPlayerControl(QObject *parent) + : QMediaPlayerControl(parent), + m_connection(0), + m_context(0), + m_audioId(-1), + m_state(QMediaPlayer::StoppedState), + m_volume(100), + m_muted(false), + m_rate(1), + m_id(-1), + m_eventMonitor(0), + m_position(0), + m_mediaStatus(QMediaPlayer::NoMedia), + m_playAfterMediaLoaded(false), + m_inputAttached(false), + m_stopEventsToIgnore(0), + m_bufferStatus(0) +{ + m_loadingTimer.setSingleShot(true); + m_loadingTimer.setInterval(0); + connect(&m_loadingTimer, SIGNAL(timeout()), this, SLOT(continueLoadMedia())); + QCoreApplication::eventDispatcher()->installNativeEventFilter(this); + openConnection(); +} + +BbMediaPlayerControl::~BbMediaPlayerControl() +{ + stop(); + detach(); + closeConnection(); + QCoreApplication::eventDispatcher()->removeNativeEventFilter(this); +} + +void BbMediaPlayerControl::openConnection() +{ + m_connection = mmr_connect(NULL); + if (!m_connection) { + emitPError("Unable to connect to the multimedia renderer"); + return; + } + + m_id = idCounter++; + m_contextName = QString("BbMediaPlayerControl_%1_%2").arg(m_id) + .arg(QCoreApplication::applicationPid()); + m_context = mmr_context_create(m_connection, m_contextName.toLatin1(), + 0, S_IRWXU|S_IRWXG|S_IRWXO); + if (!m_context) { + emitPError("Unable to create context"); + closeConnection(); + return; + } + + m_eventMonitor = mmrenderer_request_events(m_contextName.toLatin1(), 0, m_id); + if (!m_eventMonitor) { + qDebug() << "Unable to request multimedia events"; + emit error(0, "Unable to request multimedia events"); + } +} + +void BbMediaPlayerControl::closeConnection() +{ + if (m_eventMonitor) { + mmrenderer_stop_events(m_eventMonitor); + m_eventMonitor = 0; + } + + if (m_context) { + mmr_context_destroy(m_context); + m_context = 0; + m_contextName.clear(); + } + + if (m_connection) { + mmr_disconnect(m_connection); + m_connection = 0; + } +} + +QString BbMediaPlayerControl::resourcePathForUrl(const QUrl &url) +{ + // If this is a local file, mmrenderer expects the file:// prefix and an absolute path. + // We treat URLs without scheme as local files, most likely someone just forgot to set the + // file:// prefix when constructing the URL. + if (url.isLocalFile() || url.scheme().isEmpty()) { + QString relativeFilePath; + if (!url.scheme().isEmpty()) + relativeFilePath = url.toLocalFile(); + else + relativeFilePath = url.path(); + const QFileInfo fileInfo(relativeFilePath); + return QStringLiteral("file://") + fileInfo.absoluteFilePath(); + + // QRC, copy to temporary file, as mmrenderer does not support resource files + } else if (url.scheme() == QStringLiteral("qrc")) { + const QString qrcPath = ':' + url.path(); + const QFileInfo resourceFileInfo(qrcPath); + m_tempMediaFileName = QDir::tempPath() + QStringLiteral("/qtmedia_") + + QUuid::createUuid().toString() + QStringLiteral(".") + + resourceFileInfo.suffix(); + if (!QFile::copy(qrcPath, m_tempMediaFileName)) { + const QString errorMsg = + QString("Failed to copy resource file to temporary file %1 for playback").arg(m_tempMediaFileName); + qDebug() << errorMsg; + emit error(0, errorMsg); + return QString(); + } + return m_tempMediaFileName; + + // HTTP or similar URL, use as-is + } else { + return url.toString(); + } +} + +void BbMediaPlayerControl::attach() +{ + // Should only be called in detached state + Q_ASSERT(m_audioId == -1 && !m_inputAttached && m_tempMediaFileName.isEmpty()); + + if (m_media.isNull() || !m_context) { + setMediaStatus(QMediaPlayer::NoMedia); + return; + } + + if (m_videoRendererControl) + m_videoRendererControl->attachDisplay(m_context); + + if (m_videoWindowControl) + m_videoWindowControl->attachDisplay(m_context); + + m_audioId = mmr_output_attach(m_context, "audio:default", "audio"); + if (m_audioId == -1) { + emitMmError("mmr_output_attach() for audio failed"); + return; + } + + const QString resourcePath = resourcePathForUrl(m_media.canonicalUrl()); + if (resourcePath.isEmpty()) { + detach(); + return; + } + + if (mmr_input_attach(m_context, QFile::encodeName(resourcePath), "track") != 0) { + emitMmError(QString("mmr_input_attach() for %1 failed").arg(resourcePath)); + setMediaStatus(QMediaPlayer::InvalidMedia); + detach(); + return; + } + + // For whatever reason, the mmrenderer sends out a MMR_STOPPED event when calling + // mmr_input_attach() above. Ignore it, as otherwise we'll trigger stopping right after we + // started. + m_stopEventsToIgnore++; + + m_inputAttached = true; + setMediaStatus(QMediaPlayer::LoadedMedia); + m_bufferStatus = 0; + emit bufferStatusChanged(m_bufferStatus); +} + +void BbMediaPlayerControl::detach() +{ + if (m_context) { + if (m_inputAttached) { + mmr_input_detach(m_context); + m_inputAttached = false; + } + if (m_videoRendererControl) + m_videoRendererControl->detachDisplay(); + if (m_videoWindowControl) + m_videoWindowControl->detachDisplay(); + if (m_audioId != -1 && m_context) { + mmr_output_detach(m_context, m_audioId); + m_audioId = -1; + } + } + + if (!m_tempMediaFileName.isEmpty()) { + QFile::remove(m_tempMediaFileName); + m_tempMediaFileName.clear(); + } + m_loadingTimer.stop(); +} + +QMediaPlayer::State BbMediaPlayerControl::state() const +{ + return m_state; +} + +QMediaPlayer::MediaStatus BbMediaPlayerControl::mediaStatus() const +{ + return m_mediaStatus; +} + +qint64 BbMediaPlayerControl::duration() const +{ + return m_metaData.duration(); +} + +qint64 BbMediaPlayerControl::position() const +{ + return m_position; +} + +void BbMediaPlayerControl::setPosition(qint64 position) +{ + if (m_position != position) { + m_position = position; + + // Don't update in stopped state, it would not have any effect. Instead, the position is + // updated in play(). + if (m_state != QMediaPlayer::StoppedState) + setPositionInternal(m_position); + + emit positionChanged(m_position); + } +} + +int BbMediaPlayerControl::volume() const +{ + return m_volume; +} + +void BbMediaPlayerControl::setVolumeInternal(int newVolume) +{ + if (!m_context) + return; + + newVolume = qBound(0, newVolume, 100); + if (m_audioId != -1) { + strm_dict_t * dict = strm_dict_new(); + dict = strm_dict_set(dict, "volume", QString::number(newVolume).toLatin1()); + if (mmr_output_parameters(m_context, m_audioId, dict) != 0) + emitMmError("mmr_output_parameters: Setting volume failed"); + } +} + +void BbMediaPlayerControl::setPlaybackRateInternal(qreal rate) +{ + if (!m_context) + return; + + const int mmRate = rate * 1000; + if (mmr_speed_set(m_context, mmRate) != 0) + emitMmError("mmr_speed_set failed"); +} + +void BbMediaPlayerControl::setPositionInternal(qint64 position) +{ + if (!m_context) + return; + + if (m_metaData.isSeekable()) { + if (mmr_seek(m_context, QString::number(position).toLatin1()) != 0) + emitMmError("Seeking failed"); + } +} + +void BbMediaPlayerControl::setMediaStatus(QMediaPlayer::MediaStatus status) +{ + if (m_mediaStatus != status) { + m_mediaStatus = status; + emit mediaStatusChanged(m_mediaStatus); + } +} + +void BbMediaPlayerControl::setState(QMediaPlayer::State state) +{ + if (m_state != state) { + if (m_videoRendererControl) { + if (state == QMediaPlayer::PausedState) + m_videoRendererControl->pause(); + else if ((state == QMediaPlayer::PlayingState) + && (m_state == QMediaPlayer::PausedState)) { + m_videoRendererControl->resume(); + } + } + + m_state = state; + emit stateChanged(m_state); + } +} + +void BbMediaPlayerControl::stopInternal(StopCommand stopCommand) +{ + if (m_state != QMediaPlayer::StoppedState) { + + if (stopCommand == StopMmRenderer) { + ++m_stopEventsToIgnore; + mmr_stop(m_context); + } + + setState(QMediaPlayer::StoppedState); + } + + if (m_position != 0) { + m_position = 0; + emit positionChanged(0); + } +} + +void BbMediaPlayerControl::setVolume(int volume) +{ + const int newVolume = qBound(0, volume, 100); + if (m_volume != newVolume) { + m_volume = newVolume; + if (!m_muted) + setVolumeInternal(m_volume); + emit volumeChanged(m_volume); + } +} + +bool BbMediaPlayerControl::isMuted() const +{ + return m_muted; +} + +void BbMediaPlayerControl::setMuted(bool muted) +{ + if (m_muted != muted) { + m_muted = muted; + setVolumeInternal(muted ? 0 : m_volume); + emit mutedChanged(muted); + } +} + +int BbMediaPlayerControl::bufferStatus() const +{ + return m_bufferStatus; +} + +bool BbMediaPlayerControl::isAudioAvailable() const +{ + return m_metaData.hasAudio(); +} + +bool BbMediaPlayerControl::isVideoAvailable() const +{ + return m_metaData.hasVideo(); +} + +bool BbMediaPlayerControl::isSeekable() const +{ + return m_metaData.isSeekable(); +} + +QMediaTimeRange BbMediaPlayerControl::availablePlaybackRanges() const +{ + // We can't get this information from the mmrenderer API yet, so pretend we can seek everywhere + return QMediaTimeRange(0, m_metaData.duration()); +} + +qreal BbMediaPlayerControl::playbackRate() const +{ + return m_rate; +} + +void BbMediaPlayerControl::setPlaybackRate(qreal rate) +{ + if (m_rate != rate) { + m_rate = rate; + setPlaybackRateInternal(m_rate); + emit playbackRateChanged(m_rate); + } +} + +QMediaContent BbMediaPlayerControl::media() const +{ + return m_media; +} + +const QIODevice *BbMediaPlayerControl::mediaStream() const +{ + // Always 0, we don't support QIODevice streams + return 0; +} + +void BbMediaPlayerControl::setMedia(const QMediaContent &media, QIODevice *stream) +{ + Q_UNUSED(stream); // not supported + + stop(); + detach(); + + m_media = media; + emit mediaChanged(m_media); + + // Slight hack: With MediaPlayer QtQuick elements that have autoPlay set to true, playback + // would start before the QtQuick canvas is propagated to all elements, and therefore our + // video output would not work. Therefore, delay actually playing the media a bit so that the + // canvas is ready. + // The mmrenderer doesn't allow to attach video outputs after playing has started, otherwise + // this would be unnecessary. + if (!m_media.isNull()) { + setMediaStatus(QMediaPlayer::LoadingMedia); + m_loadingTimer.start(); // singleshot timer to continueLoadMedia() + } else { + continueLoadMedia(); // still needed, as it will update the media status and clear metadata + } +} + +void BbMediaPlayerControl::continueLoadMedia() +{ + attach(); + updateMetaData(); + if (m_playAfterMediaLoaded) + play(); +} + +void BbMediaPlayerControl::play() +{ + if (m_playAfterMediaLoaded) + m_playAfterMediaLoaded = false; + + // No-op if we are already playing, except if we were called from continueLoadMedia(), in which + // case m_playAfterMediaLoaded is true (hence the 'else'). + else if (m_state == QMediaPlayer::PlayingState) + return; + + if (m_mediaStatus == QMediaPlayer::LoadingMedia) { + + // State changes are supposed to be synchronous + setState(QMediaPlayer::PlayingState); + + // Defer playing to later, when the timer triggers continueLoadMedia() + m_playAfterMediaLoaded = true; + return; + } + + // Un-pause the state when it is paused + if (m_state == QMediaPlayer::PausedState) { + setPlaybackRateInternal(m_rate); + setState(QMediaPlayer::PlayingState); + return; + } + + if (m_media.isNull() || !m_connection || !m_context || m_audioId == -1) { + setState(QMediaPlayer::StoppedState); + return; + } + + setPositionInternal(m_position); + setVolumeInternal(m_volume); + setPlaybackRateInternal(m_rate); + + if (mmr_play(m_context) != 0) { + setState(QMediaPlayer::StoppedState); + emitMmError("mmr_play() failed"); + return; + } + + setState( QMediaPlayer::PlayingState); +} + +void BbMediaPlayerControl::pause() +{ + if (m_state == QMediaPlayer::PlayingState) { + setPlaybackRateInternal(0); + setState(QMediaPlayer::PausedState); + } +} + +void BbMediaPlayerControl::stop() +{ + stopInternal(StopMmRenderer); +} + +void BbMediaPlayerControl::setVideoRendererControl(BbPlayerVideoRendererControl *videoControl) +{ + m_videoRendererControl = videoControl; +} + +void BbMediaPlayerControl::setVideoWindowControl(BbVideoWindowControl *videoControl) +{ + m_videoWindowControl = videoControl; +} + +void BbMediaPlayerControl::setMetaDataReaderControl(BbMetaDataReaderControl *metaDataReaderControl) +{ + m_metaDataReaderControl = metaDataReaderControl; +} + +bool BbMediaPlayerControl::nativeEventFilter(const QByteArray &eventType, void *message, long *result) +{ + Q_UNUSED(eventType); + Q_UNUSED(result); + + bps_event_t * const event = static_cast<bps_event_t *>(message); + if (!event || + (bps_event_get_domain(event) != mmrenderer_get_domain() && + bps_event_get_domain(event) != screen_get_domain())) + return false; + + if (m_videoWindowControl) + m_videoWindowControl->bpsEventHandler(event); + + if (bps_event_get_domain(event) == mmrenderer_get_domain()) { + if (bps_event_get_code(event) == MMRENDERER_STATE_CHANGE) { + const mmrenderer_state_t newState = mmrenderer_event_get_state(event); + if (newState == MMR_STOPPED) { + + // Only react to stop events that happen when the end of the stream is reached and + // playback is stopped because of this. + // Ignore other stop event sources, souch as calling mmr_stop() ourselves and + // mmr_input_attach(). + if (m_stopEventsToIgnore > 0) { + --m_stopEventsToIgnore; + } else { + setMediaStatus(QMediaPlayer::EndOfMedia); + stopInternal(IgnoreMmRenderer); + } + return false; + } + } + + if (bps_event_get_code(event) == MMRENDERER_STATUS_UPDATE) { + + // Prevent spurious position change events from overriding our own position, for example + // when setting the position to 0 in stop(). + // Also, don't change the position while we're loading the media, as then play() would + // set a wrong initial position. + if (m_state != QMediaPlayer::PlayingState || + m_mediaStatus == QMediaPlayer::LoadingMedia || + m_mediaStatus == QMediaPlayer::NoMedia || + m_mediaStatus == QMediaPlayer::InvalidMedia) + return false; + + const qint64 newPosition = QString::fromLatin1(mmrenderer_event_get_position(event)).toLongLong(); + if (newPosition != 0 && newPosition != m_position) { + m_position = newPosition; + emit positionChanged(m_position); + } + + const QString bufferStatus = QString::fromLatin1(mmrenderer_event_get_bufferlevel(event)); + const int slashPos = bufferStatus.indexOf('/'); + if (slashPos != -1) { + const int fill = bufferStatus.left(slashPos).toInt(); + const int capacity = bufferStatus.mid(slashPos + 1).toInt(); + if (capacity != 0) { + m_bufferStatus = fill / static_cast<float>(capacity) * 100.0f; + emit bufferStatusChanged(m_bufferStatus); + } + } + } + } + + return false; +} + +void BbMediaPlayerControl::updateMetaData() +{ + if (m_mediaStatus == QMediaPlayer::LoadedMedia) + m_metaData.parse(m_contextName); + else + m_metaData.clear(); + + if (m_videoWindowControl) + m_videoWindowControl->setMetaData(m_metaData); + + if (m_metaDataReaderControl) + m_metaDataReaderControl->setMetaData(m_metaData); + + emit durationChanged(m_metaData.duration()); + emit audioAvailableChanged(m_metaData.hasAudio()); + emit videoAvailableChanged(m_metaData.hasVideo()); + emit availablePlaybackRangesChanged(availablePlaybackRanges()); + emit seekableChanged(m_metaData.isSeekable()); +} + +void BbMediaPlayerControl::emitMmError(const QString &msg) +{ + int errorCode = MMR_ERROR_NONE; + const QString errorMessage = mmErrorMessage(msg, m_context, &errorCode); + qDebug() << errorMessage; + emit error(errorCode, errorMessage); +} + +void BbMediaPlayerControl::emitPError(const QString &msg) +{ + const QString errorMessage = QString("%1: %2").arg(msg).arg(strerror(errno)); + qDebug() << errorMessage; + emit error(errno, errorMessage); +} + +QT_END_NAMESPACE |