summaryrefslogtreecommitdiffstats
path: root/src/plugins/blackberry/mediaplayer/bbmediaplayercontrol.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/blackberry/mediaplayer/bbmediaplayercontrol.cpp')
-rw-r--r--src/plugins/blackberry/mediaplayer/bbmediaplayercontrol.cpp646
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