blob: fc48ed81822d325c1acd429f833bc7a5af700ee7 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 Research In Motion
** Contact: https://www.qt.io/licensing/
**
** 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 The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/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 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "mmrendereraudiorolecontrol.h"
#include "mmrenderercustomaudiorolecontrol.h"
#include "mmrenderermediaplayercontrol.h"
#include "mmrenderermetadatareadercontrol.h"
#include "mmrendererplayervideorenderercontrol.h"
#include "mmrendererutil.h"
#include "mmrenderervideowindowcontrol.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 <errno.h>
#include <sys/strm.h>
#include <sys/stat.h>
QT_BEGIN_NAMESPACE
static int idCounter = 0;
MmRendererMediaPlayerControl::MmRendererMediaPlayerControl(QObject *parent)
: QMediaPlayerControl(parent),
m_context(0),
m_id(-1),
m_connection(0),
m_audioId(-1),
m_state(QMediaPlayer::StoppedState),
m_volume(100),
m_muted(false),
m_rate(1),
m_position(0),
m_mediaStatus(QMediaPlayer::NoMedia),
m_playAfterMediaLoaded(false),
m_inputAttached(false),
m_bufferLevel(0)
{
m_loadingTimer.setSingleShot(true);
m_loadingTimer.setInterval(0);
connect(&m_loadingTimer, SIGNAL(timeout()), this, SLOT(continueLoadMedia()));
QCoreApplication::eventDispatcher()->installNativeEventFilter(this);
}
void MmRendererMediaPlayerControl::destroy()
{
stop();
detach();
closeConnection();
QCoreApplication::eventDispatcher()->removeNativeEventFilter(this);
}
void MmRendererMediaPlayerControl::openConnection()
{
m_connection = mmr_connect(NULL);
if (!m_connection) {
emitPError("Unable to connect to the multimedia renderer");
return;
}
m_id = idCounter++;
m_contextName = QString("MmRendererMediaPlayerControl_%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;
}
startMonitoring();
}
void MmRendererMediaPlayerControl::handleMmStopped()
{
// 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, such as calling mmr_stop() ourselves.
if (m_state != QMediaPlayer::StoppedState) {
setMediaStatus(QMediaPlayer::EndOfMedia);
stopInternal(IgnoreMmRenderer);
}
}
void MmRendererMediaPlayerControl::handleMmSuspend(const QString &reason)
{
if (m_state == QMediaPlayer::StoppedState)
return;
Q_UNUSED(reason);
setMediaStatus(QMediaPlayer::StalledMedia);
}
void MmRendererMediaPlayerControl::handleMmSuspendRemoval(const QString &bufferStatus)
{
if (m_state == QMediaPlayer::StoppedState)
return;
if (bufferStatus == QLatin1String("buffering"))
setMediaStatus(QMediaPlayer::BufferingMedia);
else
setMediaStatus(QMediaPlayer::BufferedMedia);
}
void MmRendererMediaPlayerControl::handleMmPause()
{
if (m_state == QMediaPlayer::PlayingState) {
setState(QMediaPlayer::PausedState);
}
}
void MmRendererMediaPlayerControl::handleMmPlay()
{
if (m_state == QMediaPlayer::PausedState) {
setState(QMediaPlayer::PlayingState);
}
}
void MmRendererMediaPlayerControl::closeConnection()
{
stopMonitoring();
if (m_context) {
mmr_context_destroy(m_context);
m_context = 0;
m_contextName.clear();
}
if (m_connection) {
mmr_disconnect(m_connection);
m_connection = 0;
}
}
QByteArray MmRendererMediaPlayerControl::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 QFile::encodeName(QStringLiteral("file://") + fileInfo.absoluteFilePath());
// HTTP or similar URL
} else {
return url.toEncoded();
}
}
void MmRendererMediaPlayerControl::attach()
{
// Should only be called in detached state
Q_ASSERT(m_audioId == -1 && !m_inputAttached);
if (m_media.isNull() || !m_context) {
setMediaStatus(QMediaPlayer::NoMedia);
return;
}
resetMonitoring();
if (m_videoRendererControl)
m_videoRendererControl->attachDisplay(m_context);
if (m_videoWindowControl)
m_videoWindowControl->attachDisplay(m_context);
const QByteArray defaultAudioDevice = qgetenv("QQNX_RENDERER_DEFAULT_AUDIO_SINK");
m_audioId = mmr_output_attach(m_context, defaultAudioDevice.isEmpty() ? "audio:default" : defaultAudioDevice.constData(), "audio");
if (m_audioId == -1) {
emitMmError("mmr_output_attach() for audio failed");
return;
}
if (m_audioId != -1 && m_audioRoleControl) {
QAudio::Role audioRole = m_audioRoleControl->audioRole();
QString audioType = (audioRole == QAudio::CustomRole && m_customAudioRoleControl)
? m_customAudioRoleControl->customAudioRole()
: qnxAudioType(audioRole);
QByteArray latin1AudioType = audioType.toLatin1();
if (!audioType.isEmpty() && latin1AudioType == audioType) {
strm_dict_t *dict = strm_dict_new();
dict = strm_dict_set(dict, "audio_type", latin1AudioType.constData());
if (mmr_output_parameters(m_context, m_audioId, dict) != 0)
emitMmError("mmr_output_parameters: Setting audio_type failed");
}
}
const QByteArray resourcePath = resourcePathForUrl(m_media.request().url());
if (resourcePath.isEmpty()) {
detach();
return;
}
if (mmr_input_attach(m_context, resourcePath.constData(), "track") != 0) {
emitMmError(QStringLiteral("mmr_input_attach() failed for ") + QString(resourcePath));
setMediaStatus(QMediaPlayer::InvalidMedia);
detach();
return;
}
m_inputAttached = true;
setMediaStatus(QMediaPlayer::LoadedMedia);
// mm-renderer has buffer properties "status" and "level"
// QMediaPlayer's buffer status maps to mm-renderer's buffer level
m_bufferLevel = 0;
emit bufferStatusChanged(m_bufferLevel);
}
void MmRendererMediaPlayerControl::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;
}
}
m_loadingTimer.stop();
}
QMediaPlayer::State MmRendererMediaPlayerControl::state() const
{
return m_state;
}
QMediaPlayer::MediaStatus MmRendererMediaPlayerControl::mediaStatus() const
{
return m_mediaStatus;
}
qint64 MmRendererMediaPlayerControl::duration() const
{
return m_metaData.duration();
}
qint64 MmRendererMediaPlayerControl::position() const
{
return m_position;
}
void MmRendererMediaPlayerControl::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 MmRendererMediaPlayerControl::volume() const
{
return m_volume;
}
void MmRendererMediaPlayerControl::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 MmRendererMediaPlayerControl::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 MmRendererMediaPlayerControl::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 MmRendererMediaPlayerControl::setMediaStatus(QMediaPlayer::MediaStatus status)
{
if (m_mediaStatus != status) {
m_mediaStatus = status;
emit mediaStatusChanged(m_mediaStatus);
}
}
void MmRendererMediaPlayerControl::setState(QMediaPlayer::State state)
{
if (m_state != state) {
if (m_videoRendererControl) {
if (state == QMediaPlayer::PausedState || state == QMediaPlayer::StoppedState) {
m_videoRendererControl->pause();
} else if ((state == QMediaPlayer::PlayingState)
&& (m_state == QMediaPlayer::PausedState
|| m_state == QMediaPlayer::StoppedState)) {
m_videoRendererControl->resume();
}
}
m_state = state;
emit stateChanged(m_state);
}
}
void MmRendererMediaPlayerControl::stopInternal(StopCommand stopCommand)
{
resetMonitoring();
setPosition(0);
if (m_state != QMediaPlayer::StoppedState) {
if (stopCommand == StopMmRenderer) {
mmr_stop(m_context);
}
setState(QMediaPlayer::StoppedState);
}
}
void MmRendererMediaPlayerControl::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 MmRendererMediaPlayerControl::isMuted() const
{
return m_muted;
}
void MmRendererMediaPlayerControl::setMuted(bool muted)
{
if (m_muted != muted) {
m_muted = muted;
setVolumeInternal(muted ? 0 : m_volume);
emit mutedChanged(muted);
}
}
int MmRendererMediaPlayerControl::bufferStatus() const
{
// mm-renderer has buffer properties "status" and "level"
// QMediaPlayer's buffer status maps to mm-renderer's buffer level
return m_bufferLevel;
}
bool MmRendererMediaPlayerControl::isAudioAvailable() const
{
return m_metaData.hasAudio();
}
bool MmRendererMediaPlayerControl::isVideoAvailable() const
{
return m_metaData.hasVideo();
}
bool MmRendererMediaPlayerControl::isSeekable() const
{
return m_metaData.isSeekable();
}
QMediaTimeRange MmRendererMediaPlayerControl::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 MmRendererMediaPlayerControl::playbackRate() const
{
return m_rate;
}
void MmRendererMediaPlayerControl::setPlaybackRate(qreal rate)
{
if (m_rate != rate) {
m_rate = rate;
setPlaybackRateInternal(m_rate);
emit playbackRateChanged(m_rate);
}
}
QMediaContent MmRendererMediaPlayerControl::media() const
{
return m_media;
}
const QIODevice *MmRendererMediaPlayerControl::mediaStream() const
{
// Always 0, we don't support QIODevice streams
return 0;
}
void MmRendererMediaPlayerControl::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 MmRendererMediaPlayerControl::continueLoadMedia()
{
updateMetaData(nullptr);
attach();
if (m_playAfterMediaLoaded)
play();
}
MmRendererVideoWindowControl *MmRendererMediaPlayerControl::videoWindowControl() const
{
return m_videoWindowControl;
}
void MmRendererMediaPlayerControl::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;
}
if (m_mediaStatus == QMediaPlayer::EndOfMedia)
m_position = 0;
resetMonitoring();
setPositionInternal(m_position);
setVolumeInternal(m_muted ? 0 : m_volume);
setPlaybackRateInternal(m_rate);
if (mmr_play(m_context) != 0) {
setState(QMediaPlayer::StoppedState);
emitMmError("mmr_play() failed");
return;
}
setState( QMediaPlayer::PlayingState);
}
void MmRendererMediaPlayerControl::pause()
{
if (m_state == QMediaPlayer::PlayingState) {
setPlaybackRateInternal(0);
setState(QMediaPlayer::PausedState);
}
}
void MmRendererMediaPlayerControl::stop()
{
stopInternal(StopMmRenderer);
}
MmRendererPlayerVideoRendererControl *MmRendererMediaPlayerControl::videoRendererControl() const
{
return m_videoRendererControl;
}
void MmRendererMediaPlayerControl::setVideoRendererControl(MmRendererPlayerVideoRendererControl *videoControl)
{
m_videoRendererControl = videoControl;
}
void MmRendererMediaPlayerControl::setVideoWindowControl(MmRendererVideoWindowControl *videoControl)
{
m_videoWindowControl = videoControl;
}
void MmRendererMediaPlayerControl::setMetaDataReaderControl(MmRendererMetaDataReaderControl *metaDataReaderControl)
{
m_metaDataReaderControl = metaDataReaderControl;
}
void MmRendererMediaPlayerControl::setAudioRoleControl(MmRendererAudioRoleControl *audioRoleControl)
{
m_audioRoleControl = audioRoleControl;
}
void MmRendererMediaPlayerControl::setCustomAudioRoleControl(MmRendererCustomAudioRoleControl *customAudioRoleControl)
{
m_customAudioRoleControl = customAudioRoleControl;
}
void MmRendererMediaPlayerControl::setMmPosition(qint64 newPosition)
{
if (newPosition != 0 && newPosition != m_position) {
m_position = newPosition;
emit positionChanged(m_position);
}
}
void MmRendererMediaPlayerControl::setMmBufferStatus(const QString &bufferStatus)
{
if (bufferStatus == QLatin1String("buffering"))
setMediaStatus(QMediaPlayer::BufferingMedia);
else if (bufferStatus == QLatin1String("playing"))
setMediaStatus(QMediaPlayer::BufferedMedia);
// ignore "idle" buffer status
}
void MmRendererMediaPlayerControl::setMmBufferLevel(int level, int capacity)
{
m_bufferLevel = capacity == 0 ? 0 : level / static_cast<float>(capacity) * 100.0f;
m_bufferLevel = qBound(0, m_bufferLevel, 100);
emit bufferStatusChanged(m_bufferLevel);
}
void MmRendererMediaPlayerControl::updateMetaData(const strm_dict *dict)
{
m_metaData.update(dict);
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 MmRendererMediaPlayerControl::emitMmError(const QString &msg)
{
int errorCode = MMR_ERROR_NONE;
const QString errorMessage = mmErrorMessage(msg, m_context, &errorCode);
qDebug() << errorMessage;
emit error(errorCode, errorMessage);
}
void MmRendererMediaPlayerControl::emitPError(const QString &msg)
{
const QString errorMessage = QString("%1: %2").arg(msg).arg(strerror(errno));
qDebug() << errorMessage;
emit error(errno, errorMessage);
}
QT_END_NAMESPACE