diff options
author | Michael Goddard <michael.goddard@nokia.com> | 2011-06-29 13:38:46 +1000 |
---|---|---|
committer | Michael Goddard <michael.goddard@nokia.com> | 2011-06-29 13:38:46 +1000 |
commit | 2a34e88c1e1ced28e75c487cd13402e1c9cf9fa3 (patch) | |
tree | e6c1b770c5c47212792a1f9344fa034ea3e54c44 /src/plugins/gstreamer/mediaplayer |
Initial copy of QtMultimediaKit.
Comes from original repo, with SHA1:
2c82d5611655e5967f5c5095af50c0991c4378b2
Diffstat (limited to 'src/plugins/gstreamer/mediaplayer')
15 files changed, 3939 insertions, 0 deletions
diff --git a/src/plugins/gstreamer/mediaplayer/mediaplayer.pri b/src/plugins/gstreamer/mediaplayer/mediaplayer.pri new file mode 100644 index 000000000..9045a80dd --- /dev/null +++ b/src/plugins/gstreamer/mediaplayer/mediaplayer.pri @@ -0,0 +1,30 @@ +INCLUDEPATH += $$PWD + +DEFINES += QMEDIA_GSTREAMER_PLAYER + +contains(gstreamer-appsrc_enabled, yes) { + HEADERS += $$PWD/qgstappsrc.h + SOURCES += $$PWD/qgstappsrc.cpp + + DEFINES += HAVE_GST_APPSRC + + LIBS += -lgstapp-0.10 +} + +HEADERS += \ + $$PWD/qgstreamerplayercontrol.h \ + $$PWD/qgstreamerplayerservice.h \ + $$PWD/qgstreamerplayersession.h \ + $$PWD/qgstreamerstreamscontrol.h \ + $$PWD/qgstreamermetadataprovider.h \ + $$PWD/playerresourcepolicy.h + +SOURCES += \ + $$PWD/qgstreamerplayercontrol.cpp \ + $$PWD/qgstreamerplayerservice.cpp \ + $$PWD/qgstreamerplayersession.cpp \ + $$PWD/qgstreamerstreamscontrol.cpp \ + $$PWD/qgstreamermetadataprovider.cpp \ + $$PWD/playerresourcepolicy.cpp + + diff --git a/src/plugins/gstreamer/mediaplayer/playerresourcepolicy.cpp b/src/plugins/gstreamer/mediaplayer/playerresourcepolicy.cpp new file mode 100644 index 000000000..b2cf3498d --- /dev/null +++ b/src/plugins/gstreamer/mediaplayer/playerresourcepolicy.cpp @@ -0,0 +1,180 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "playerresourcepolicy.h" + +#ifdef Q_WS_MAEMO_6 +#define HAVE_RESOURCE_POLICY +#endif + +//#define DEBUG_RESOURCE_POLICY +#include <QtCore/qdebug.h> + +#ifdef HAVE_RESOURCE_POLICY +#include <policy/resource.h> +#include <policy/resources.h> +#include <policy/resource-set.h> +#endif + +PlayerResourcePolicy::PlayerResourcePolicy(QObject *parent) : + QObject(parent), + m_videoEnabled(true), + m_resourceSet(0), + m_status(PlayerResourcePolicy::Initial) +{ +#ifdef HAVE_RESOURCE_POLICY + m_resourceSet = new ResourcePolicy::ResourceSet("player", this); + m_resourceSet->setAlwaysReply(); + + ResourcePolicy::AudioResource *audioResource = new ResourcePolicy::AudioResource("player"); + audioResource->setProcessID(QCoreApplication::applicationPid()); + audioResource->setStreamTag("media.name", "*"); + m_resourceSet->addResourceObject(audioResource); + + m_resourceSet->addResource(ResourcePolicy::VideoPlaybackType); + m_resourceSet->update(); + + connect(m_resourceSet, SIGNAL(resourcesGranted(const QList<ResourcePolicy::ResourceType>)), + this, SLOT(handleResourcesGranted())); + connect(m_resourceSet, SIGNAL(resourcesDenied()), + this, SLOT(handleResourcesDenied())); + connect(m_resourceSet, SIGNAL(lostResources()), + this, SLOT(handleResourcesLost())); + connect(m_resourceSet, SIGNAL(resourcesReleasedByManager()), + this, SLOT(handleResourcesLost())); +#endif +} + +PlayerResourcePolicy::~PlayerResourcePolicy() +{ +} + +bool PlayerResourcePolicy::isVideoEnabled() const +{ + return m_videoEnabled; +} + +void PlayerResourcePolicy::setVideoEnabled(bool enabled) +{ + if (m_videoEnabled != enabled) { + m_videoEnabled = enabled; + +#ifdef HAVE_RESOURCE_POLICY + if (enabled) + m_resourceSet->addResource(ResourcePolicy::VideoPlaybackType); + else + m_resourceSet->deleteResource(ResourcePolicy::VideoPlaybackType); + + m_resourceSet->update(); +#endif + } +} + +void PlayerResourcePolicy::acquire() +{ +#ifdef HAVE_RESOURCE_POLICY + +#ifdef DEBUG_RESOURCE_POLICY + qDebug() << Q_FUNC_INFO << "Acquire resource"; +#endif + m_status = RequestedResource; + m_resourceSet->acquire(); +#else + m_status = GrantedResource; +#endif +} + +void PlayerResourcePolicy::release() +{ +#ifdef HAVE_RESOURCE_POLICY + +#ifdef DEBUG_RESOURCE_POLICY + qDebug() << Q_FUNC_INFO << "Release resource"; +#endif + + m_resourceSet->release(); +#endif + m_status = Initial; + +} + +bool PlayerResourcePolicy::isGranted() const +{ + return m_status == GrantedResource; +} + +bool PlayerResourcePolicy::isRequested() const +{ + return m_status == RequestedResource; +} + +void PlayerResourcePolicy::handleResourcesGranted() +{ + m_status = GrantedResource; +#ifdef DEBUG_RESOURCE_POLICY + qDebug() << Q_FUNC_INFO << "Resource granted"; +#endif + emit resourcesGranted(); +} + +void PlayerResourcePolicy::handleResourcesDenied() +{ + m_status = Initial; +#ifdef DEBUG_RESOURCE_POLICY + qDebug() << Q_FUNC_INFO << "Resource denied"; +#endif + emit resourcesDenied(); +} + +void PlayerResourcePolicy::handleResourcesLost() +{ +#ifdef DEBUG_RESOURCE_POLICY + qDebug() << Q_FUNC_INFO << "Resource lost"; +#endif + if (m_status != Initial) { + m_status = Initial; + emit resourcesLost(); + } + +#ifdef HAVE_RESOURCE_POLICY + m_resourceSet->release(); +#endif +} diff --git a/src/plugins/gstreamer/mediaplayer/playerresourcepolicy.h b/src/plugins/gstreamer/mediaplayer/playerresourcepolicy.h new file mode 100644 index 000000000..fecf5fa80 --- /dev/null +++ b/src/plugins/gstreamer/mediaplayer/playerresourcepolicy.h @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef PLAYERRESOURCEPOLICY_H +#define PLAYERRESOURCEPOLICY_H + +#include <QtCore/qobject.h> + +namespace ResourcePolicy { +class ResourceSet; +}; + +class PlayerResourcePolicy : public QObject +{ + Q_OBJECT +public: + PlayerResourcePolicy(QObject *parent = 0); + ~PlayerResourcePolicy(); + + bool isVideoEnabled() const; + bool isGranted() const; + bool isRequested() const; + +Q_SIGNALS: + void resourcesDenied(); + void resourcesGranted(); + void resourcesLost(); + +public Q_SLOTS: + void acquire(); + void release(); + + void setVideoEnabled(bool enabled); + +private Q_SLOTS: + void handleResourcesGranted(); + void handleResourcesDenied(); + void handleResourcesLost(); + +private: + enum ResourceStatus { + Initial = 0, + RequestedResource, + GrantedResource + }; + + bool m_videoEnabled; + ResourcePolicy::ResourceSet *m_resourceSet; + ResourceStatus m_status; +}; + +#endif diff --git a/src/plugins/gstreamer/mediaplayer/qgstappsrc.cpp b/src/plugins/gstreamer/mediaplayer/qgstappsrc.cpp new file mode 100644 index 000000000..cf814c462 --- /dev/null +++ b/src/plugins/gstreamer/mediaplayer/qgstappsrc.cpp @@ -0,0 +1,224 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QDebug> + +#include "qgstappsrc.h" +#include <QtNetwork> + +QGstAppSrc::QGstAppSrc(QObject *parent) + :QObject(parent) + ,m_stream(0) + ,m_appSrc(0) + ,m_sequential(false) + ,m_maxBytes(0) + ,m_setup(false) + ,m_dataRequestSize(-1) + ,m_dataRequested(false) + ,m_enoughData(false) + ,m_forceData(false) +{ +} + +QGstAppSrc::~QGstAppSrc() +{ + if (m_appSrc) + gst_object_unref(G_OBJECT(m_appSrc)); +} + +bool QGstAppSrc::setup(GstElement* appsrc) +{ + if (m_setup || m_stream == 0 || appsrc == 0) + return false; + + m_appSrc = GST_APP_SRC(appsrc); + m_callbacks.need_data = &QGstAppSrc::on_need_data; + m_callbacks.enough_data = &QGstAppSrc::on_enough_data; + m_callbacks.seek_data = &QGstAppSrc::on_seek_data; + gst_app_src_set_callbacks(m_appSrc, (GstAppSrcCallbacks*)&m_callbacks, this, (GDestroyNotify)&QGstAppSrc::destroy_notify); + + g_object_get(G_OBJECT(m_appSrc), "max-bytes", &m_maxBytes, NULL); + + if (m_sequential) + m_streamType = GST_APP_STREAM_TYPE_STREAM; + else + m_streamType = GST_APP_STREAM_TYPE_RANDOM_ACCESS; + gst_app_src_set_stream_type(m_appSrc, m_streamType); + gst_app_src_set_size(m_appSrc, (m_sequential) ? -1 : m_stream->size()); + + return m_setup = true; +} + +void QGstAppSrc::setStream(QIODevice *stream) +{ + if (stream == 0) + return; + if (m_stream) { + disconnect(m_stream, SIGNAL(readyRead()), this, SLOT(onDataReady())); + disconnect(m_stream, SIGNAL(destroyed()), this, SLOT(streamDestroyed())); + } + if (m_appSrc) + gst_object_unref(G_OBJECT(m_appSrc)); + + m_dataRequestSize = -1; + m_dataRequested = false; + m_enoughData = false; + m_forceData = false; + m_maxBytes = 0; + + m_appSrc = 0; + m_stream = stream; + connect(m_stream, SIGNAL(destroyed()), SLOT(streamDestroyed())); + connect(m_stream, SIGNAL(readyRead()), this, SLOT(onDataReady())); + m_sequential = m_stream->isSequential(); + m_setup = false; +} + +QIODevice *QGstAppSrc::stream() const +{ + return m_stream; +} + +GstAppSrc *QGstAppSrc::element() +{ + return m_appSrc; +} + +void QGstAppSrc::onDataReady() +{ + if (!m_enoughData) { + m_dataRequested = true; + pushDataToAppSrc(); + } +} + +void QGstAppSrc::streamDestroyed() +{ + if (sender() == m_stream) { + m_stream = 0; + sendEOS(); + } +} + +void QGstAppSrc::pushDataToAppSrc() +{ + if (!isStreamValid() || !m_setup) + return; + + if (m_dataRequested && !m_enoughData) { + qint64 size; + if (m_dataRequestSize == (unsigned int)-1) + size = qMin(m_stream->bytesAvailable(), queueSize()); + else + size = qMin(m_stream->bytesAvailable(), (qint64)m_dataRequestSize); + void *data = g_malloc(size); + GstBuffer* buffer = gst_app_buffer_new(data, size, g_free, data); + buffer->offset = m_stream->pos(); + qint64 bytesRead = m_stream->read((char*)GST_BUFFER_DATA(buffer), size); + buffer->offset_end = buffer->offset + bytesRead - 1; + + if (bytesRead > 0) { + m_dataRequested = false; + m_enoughData = false; + GstFlowReturn ret = gst_app_src_push_buffer (GST_APP_SRC (element()), buffer); + if (ret == GST_FLOW_ERROR) { + qWarning()<<"appsrc: push buffer error"; + } else if (ret == GST_FLOW_WRONG_STATE) { + qWarning()<<"appsrc: push buffer wrong state"; + } else if (ret == GST_FLOW_RESEND) { + qWarning()<<"appsrc: push buffer resend"; + } + } + } else if (m_stream->atEnd()) { + sendEOS(); + } +} + +bool QGstAppSrc::doSeek(qint64 value) +{ + if (isStreamValid()) + return stream()->seek(value); + return false; +} + + +gboolean QGstAppSrc::on_seek_data(GstAppSrc *element, guint64 arg0, gpointer userdata) +{ + QGstAppSrc *self = reinterpret_cast<QGstAppSrc*>(userdata); + if (self && self->isStreamValid()) { + if (!self->stream()->isSequential()) + QMetaObject::invokeMethod(self, "doSeek", Qt::AutoConnection, Q_ARG(qint64, arg0)); + } + else + return false; + + return true; +} + +void QGstAppSrc::on_enough_data(GstAppSrc *element, gpointer userdata) +{ + QGstAppSrc *self = reinterpret_cast<QGstAppSrc*>(userdata); + if (self) + self->enoughData() = true; +} + +void QGstAppSrc::on_need_data(GstAppSrc *element, guint arg0, gpointer userdata) +{ + QGstAppSrc *self = reinterpret_cast<QGstAppSrc*>(userdata); + if (self) { + self->dataRequested() = true; + self->enoughData() = false; + self->dataRequestSize()= arg0; + QMetaObject::invokeMethod(self, "pushDataToAppSrc", Qt::AutoConnection); + } +} + +void QGstAppSrc::destroy_notify(gpointer data) +{ + Q_UNUSED(data); +} + +void QGstAppSrc::sendEOS() +{ + gst_app_src_end_of_stream(GST_APP_SRC(m_appSrc)); + if (isStreamValid() && !stream()->isSequential()) + stream()->reset(); +} diff --git a/src/plugins/gstreamer/mediaplayer/qgstappsrc.h b/src/plugins/gstreamer/mediaplayer/qgstappsrc.h new file mode 100644 index 000000000..17ee2af72 --- /dev/null +++ b/src/plugins/gstreamer/mediaplayer/qgstappsrc.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGSTAPPSRC_H +#define QGSTAPPSRC_H + +#include <QtCore/qobject.h> +#include <QtCore/qiodevice.h> + +#include <gst/gst.h> +#include <gst/app/gstappsrc.h> +#include <gst/app/gstappbuffer.h> + +class QGstAppSrc : public QObject +{ + Q_OBJECT +public: + QGstAppSrc(QObject *parent = 0); + ~QGstAppSrc(); + + bool setup(GstElement *); + bool isReady() const { return m_setup; } + + void setStream(QIODevice *); + QIODevice *stream() const; + + GstAppSrc *element(); + + qint64 queueSize() const { return m_maxBytes; } + + bool& enoughData() { return m_enoughData; } + bool& dataRequested() { return m_dataRequested; } + unsigned int& dataRequestSize() { return m_dataRequestSize; } + + bool isStreamValid() const + { + return m_stream != 0 && + m_stream->isOpen(); + } + +private slots: + void pushDataToAppSrc(); + bool doSeek(qint64); + void onDataReady(); + + void streamDestroyed(); +private: + static gboolean on_seek_data(GstAppSrc *element, guint64 arg0, gpointer userdata); + static void on_enough_data(GstAppSrc *element, gpointer userdata); + static void on_need_data(GstAppSrc *element, uint arg0, gpointer userdata); + static void destroy_notify(gpointer data); + + void sendEOS(); + + QIODevice *m_stream; + GstAppSrc *m_appSrc; + bool m_sequential; + GstAppStreamType m_streamType; + GstAppSrcCallbacks m_callbacks; + qint64 m_maxBytes; + bool m_setup; + unsigned int m_dataRequestSize; + bool m_dataRequested; + bool m_enoughData; + bool m_forceData; +}; + +#endif diff --git a/src/plugins/gstreamer/mediaplayer/qgstreamermetadataprovider.cpp b/src/plugins/gstreamer/mediaplayer/qgstreamermetadataprovider.cpp new file mode 100644 index 000000000..cdd6d89b4 --- /dev/null +++ b/src/plugins/gstreamer/mediaplayer/qgstreamermetadataprovider.cpp @@ -0,0 +1,192 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgstreamermetadataprovider.h" +#include "qgstreamerplayersession.h" +#include <QDebug> + +#include <gst/gstversion.h> + +struct QGstreamerMetaDataKeyLookup +{ + QtMultimediaKit::MetaData key; + const char *token; +}; + +static const QGstreamerMetaDataKeyLookup qt_gstreamerMetaDataKeys[] = +{ + { QtMultimediaKit::Title, GST_TAG_TITLE }, + //{ QtMultimediaKit::SubTitle, 0 }, + //{ QtMultimediaKit::Author, 0 }, + { QtMultimediaKit::Comment, GST_TAG_COMMENT }, + { QtMultimediaKit::Description, GST_TAG_DESCRIPTION }, + //{ QtMultimediaKit::Category, 0 }, + { QtMultimediaKit::Genre, GST_TAG_GENRE }, + { QtMultimediaKit::Year, "year" }, + //{ QtMultimediaKit::UserRating, 0 }, + + { QtMultimediaKit::Language, GST_TAG_LANGUAGE_CODE }, + + { QtMultimediaKit::Publisher, GST_TAG_ORGANIZATION }, + { QtMultimediaKit::Copyright, GST_TAG_COPYRIGHT }, + //{ QtMultimediaKit::ParentalRating, 0 }, + //{ QtMultimediaKit::RatingOrganisation, 0 }, + + // Media + //{ QtMultimediaKit::Size, 0 }, + //{ QtMultimediaKit::MediaType, 0 }, + { QtMultimediaKit::Duration, GST_TAG_DURATION }, + + // Audio + { QtMultimediaKit::AudioBitRate, GST_TAG_BITRATE }, + { QtMultimediaKit::AudioCodec, GST_TAG_AUDIO_CODEC }, + //{ QtMultimediaKit::ChannelCount, 0 }, + //{ QtMultimediaKit::SampleRate, 0 }, + + // Music + { QtMultimediaKit::AlbumTitle, GST_TAG_ALBUM }, + { QtMultimediaKit::AlbumArtist, GST_TAG_ARTIST}, + { QtMultimediaKit::ContributingArtist, GST_TAG_PERFORMER }, +#if (GST_VERSION_MAJOR >= 0) && (GST_VERSION_MINOR >= 10) && (GST_VERSION_MICRO >= 19) + { QtMultimediaKit::Composer, GST_TAG_COMPOSER }, +#endif + //{ QtMultimediaKit::Conductor, 0 }, + //{ QtMultimediaKit::Lyrics, 0 }, + //{ QtMultimediaKit::Mood, 0 }, + { QtMultimediaKit::TrackNumber, GST_TAG_TRACK_NUMBER }, + + //{ QtMultimediaKit::CoverArtUrlSmall, 0 }, + //{ QtMultimediaKit::CoverArtUrlLarge, 0 }, + + // Image/Video + { QtMultimediaKit::Resolution, "resolution" }, + { QtMultimediaKit::PixelAspectRatio, "pixel-aspect-ratio" }, + + // Video + //{ QtMultimediaKit::VideoFrameRate, 0 }, + //{ QtMultimediaKit::VideoBitRate, 0 }, + { QtMultimediaKit::VideoCodec, GST_TAG_VIDEO_CODEC }, + + //{ QtMultimediaKit::PosterUrl, 0 }, + + // Movie + //{ QtMultimediaKit::ChapterNumber, 0 }, + //{ QtMultimediaKit::Director, 0 }, + { QtMultimediaKit::LeadPerformer, GST_TAG_PERFORMER }, + //{ QtMultimediaKit::Writer, 0 }, + + // Photos + //{ QtMultimediaKit::CameraManufacturer, 0 }, + //{ QtMultimediaKit::CameraModel, 0 }, + //{ QtMultimediaKit::Event, 0 }, + //{ QtMultimediaKit::Subject, 0 } +}; + +QGstreamerMetaDataProvider::QGstreamerMetaDataProvider(QGstreamerPlayerSession *session, QObject *parent) + :QMetaDataReaderControl(parent), m_session(session) +{ + connect(m_session, SIGNAL(tagsChanged()), SLOT(updateTags())); +} + +QGstreamerMetaDataProvider::~QGstreamerMetaDataProvider() +{ +} + +bool QGstreamerMetaDataProvider::isMetaDataAvailable() const +{ + return !m_session->tags().isEmpty(); +} + +bool QGstreamerMetaDataProvider::isWritable() const +{ + return false; +} + +QVariant QGstreamerMetaDataProvider::metaData(QtMultimediaKit::MetaData key) const +{ + static const int count = sizeof(qt_gstreamerMetaDataKeys) / sizeof(QGstreamerMetaDataKeyLookup); + + for (int i = 0; i < count; ++i) { + if (qt_gstreamerMetaDataKeys[i].key == key) { + return m_session->tags().value(QByteArray(qt_gstreamerMetaDataKeys[i].token)); + } + } + return QVariant(); +} + +QList<QtMultimediaKit::MetaData> QGstreamerMetaDataProvider::availableMetaData() const +{ + static QMap<QByteArray, QtMultimediaKit::MetaData> keysMap; + if (keysMap.isEmpty()) { + const int count = sizeof(qt_gstreamerMetaDataKeys) / sizeof(QGstreamerMetaDataKeyLookup); + for (int i = 0; i < count; ++i) { + keysMap[QByteArray(qt_gstreamerMetaDataKeys[i].token)] = qt_gstreamerMetaDataKeys[i].key; + } + } + + QList<QtMultimediaKit::MetaData> res; + foreach (const QByteArray &key, m_session->tags().keys()) { + QtMultimediaKit::MetaData tag = keysMap.value(key, QtMultimediaKit::MetaData(-1)); + if (tag != -1) + res.append(tag); + } + + return res; +} + +QVariant QGstreamerMetaDataProvider::extendedMetaData(const QString &key) const +{ + return m_session->tags().value(key.toLatin1()); +} + +QStringList QGstreamerMetaDataProvider::availableExtendedMetaData() const +{ + QStringList res; + foreach (const QByteArray &key, m_session->tags().keys()) + res.append(QString(key)); + + return res; +} + +void QGstreamerMetaDataProvider::updateTags() +{ + emit metaDataChanged(); +} diff --git a/src/plugins/gstreamer/mediaplayer/qgstreamermetadataprovider.h b/src/plugins/gstreamer/mediaplayer/qgstreamermetadataprovider.h new file mode 100644 index 000000000..fa0c0243f --- /dev/null +++ b/src/plugins/gstreamer/mediaplayer/qgstreamermetadataprovider.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGSTREAMERMETADATAPROVIDER_H +#define QGSTREAMERMETADATAPROVIDER_H + +#include <qmetadatareadercontrol.h> + +QT_USE_NAMESPACE + +class QGstreamerPlayerSession; + +class QGstreamerMetaDataProvider : public QMetaDataReaderControl +{ + Q_OBJECT +public: + QGstreamerMetaDataProvider( QGstreamerPlayerSession *session, QObject *parent ); + virtual ~QGstreamerMetaDataProvider(); + + bool isMetaDataAvailable() const; + bool isWritable() const; + + QVariant metaData(QtMultimediaKit::MetaData key) const; + QList<QtMultimediaKit::MetaData> availableMetaData() const; + + QVariant extendedMetaData(const QString &key) const ; + QStringList availableExtendedMetaData() const; + +private slots: + void updateTags(); + +private: + QGstreamerPlayerSession *m_session; +}; + +#endif // QGSTREAMERMETADATAPROVIDER_H diff --git a/src/plugins/gstreamer/mediaplayer/qgstreamerplayercontrol.cpp b/src/plugins/gstreamer/mediaplayer/qgstreamerplayercontrol.cpp new file mode 100644 index 000000000..f36dd08a3 --- /dev/null +++ b/src/plugins/gstreamer/mediaplayer/qgstreamerplayercontrol.cpp @@ -0,0 +1,748 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgstreamerplayercontrol.h" +#include "qgstreamerplayersession.h" +#include "playerresourcepolicy.h" + +#include <qmediaplaylistnavigator.h> + + +#include <QtCore/qdir.h> +#include <QtCore/qsocketnotifier.h> +#include <QtCore/qurl.h> +#include <QtCore/qdebug.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + +//#define DEBUG_PLAYBIN + +QGstreamerPlayerControl::QGstreamerPlayerControl(QGstreamerPlayerSession *session, QObject *parent) + : QMediaPlayerControl(parent) + , m_ownStream(false) + , m_session(session) + , m_state(QMediaPlayer::StoppedState) + , m_mediaStatus(QMediaPlayer::NoMedia) + , m_bufferProgress(-1) + , m_seekToStartPending(false) + , m_pendingSeekPosition(-1) + , m_stream(0) + , m_fifoNotifier(0) + , m_fifoCanWrite(false) + , m_bufferSize(0) + , m_bufferOffset(0) +{ + m_fifoFd[0] = -1; + m_fifoFd[1] = -1; + + m_resources = new PlayerResourcePolicy(this); + + connect(m_session, SIGNAL(positionChanged(qint64)), + this, SIGNAL(positionChanged(qint64))); + connect(m_session, SIGNAL(durationChanged(qint64)), + this, SIGNAL(durationChanged(qint64))); + connect(m_session, SIGNAL(mutedStateChanged(bool)), + this, SIGNAL(mutedChanged(bool))); + connect(m_session, SIGNAL(volumeChanged(int)), + this, SIGNAL(volumeChanged(int))); + connect(m_session, SIGNAL(stateChanged(QMediaPlayer::State)), + this, SLOT(updateSessionState(QMediaPlayer::State))); + connect(m_session,SIGNAL(bufferingProgressChanged(int)), + this, SLOT(setBufferProgress(int))); + connect(m_session, SIGNAL(playbackFinished()), + this, SLOT(processEOS())); + connect(m_session, SIGNAL(audioAvailableChanged(bool)), + this, SIGNAL(audioAvailableChanged(bool))); + connect(m_session, SIGNAL(videoAvailableChanged(bool)), + this, SIGNAL(videoAvailableChanged(bool))); + connect(m_session, SIGNAL(seekableChanged(bool)), + this, SIGNAL(seekableChanged(bool))); + connect(m_session, SIGNAL(error(int,QString)), + this, SIGNAL(error(int,QString))); + connect(m_session, SIGNAL(invalidMedia()), + this, SLOT(handleInvalidMedia())); + connect(m_session, SIGNAL(playbackRateChanged(qreal)), + this, SIGNAL(playbackRateChanged(qreal))); + connect(m_session, SIGNAL(seekableChanged(bool)), + this, SLOT(applyPendingSeek(bool))); + + connect(m_resources, SIGNAL(resourcesGranted()), SLOT(handleResourcesGranted())); + connect(m_resources, SIGNAL(resourcesDenied()), SLOT(handleResourcesLost())); + connect(m_resources, SIGNAL(resourcesLost()), SLOT(handleResourcesLost())); +} + +QGstreamerPlayerControl::~QGstreamerPlayerControl() +{ + if (m_fifoFd[0] >= 0) { + ::close(m_fifoFd[0]); + ::close(m_fifoFd[1]); + m_fifoFd[0] = -1; + m_fifoFd[1] = -1; + } +} + +qint64 QGstreamerPlayerControl::position() const +{ + return m_seekToStartPending ? 0 : m_session->position(); +} + +qint64 QGstreamerPlayerControl::duration() const +{ + return m_session->duration(); +} + +QMediaPlayer::State QGstreamerPlayerControl::state() const +{ + return m_state; +} + +QMediaPlayer::MediaStatus QGstreamerPlayerControl::mediaStatus() const +{ + return m_mediaStatus; +} + +int QGstreamerPlayerControl::bufferStatus() const +{ + if (m_bufferProgress == -1) { + return m_session->state() == QMediaPlayer::StoppedState ? 0 : 100; + } else + return m_bufferProgress; +} + +int QGstreamerPlayerControl::volume() const +{ + return m_session->volume(); +} + +bool QGstreamerPlayerControl::isMuted() const +{ + return m_session->isMuted(); +} + +bool QGstreamerPlayerControl::isSeekable() const +{ + return m_session->isSeekable(); +} + +QMediaTimeRange QGstreamerPlayerControl::availablePlaybackRanges() const +{ + return m_session->availablePlaybackRanges(); +} + +qreal QGstreamerPlayerControl::playbackRate() const +{ + return m_session->playbackRate(); +} + +void QGstreamerPlayerControl::setPlaybackRate(qreal rate) +{ + m_session->setPlaybackRate(rate); +} + +void QGstreamerPlayerControl::setPosition(qint64 pos) +{ +#ifdef DEBUG_PLAYBIN + qDebug() << Q_FUNC_INFO << pos/1000.0; +#endif + + pushState(); + + if (m_mediaStatus == QMediaPlayer::EndOfMedia) { + m_mediaStatus = QMediaPlayer::LoadedMedia; + m_seekToStartPending = true; + } + + if (m_session->isSeekable() && m_session->seek(pos)) { + m_seekToStartPending = false; + m_pendingSeekPosition = -1; + } else { + m_pendingSeekPosition = pos; + } + + popAndNotifyState(); +} + +void QGstreamerPlayerControl::play() +{ +#ifdef DEBUG_PLAYBIN + qDebug() << Q_FUNC_INFO; +#endif + + playOrPause(QMediaPlayer::PlayingState); +} + +void QGstreamerPlayerControl::pause() +{ +#ifdef DEBUG_PLAYBIN + qDebug() << Q_FUNC_INFO; +#endif + + playOrPause(QMediaPlayer::PausedState); +} + +void QGstreamerPlayerControl::playOrPause(QMediaPlayer::State newState) +{ + if (m_mediaStatus == QMediaPlayer::NoMedia) + return; + + pushState(); +#ifdef Q_WS_MAEMO_6 + //this is a work around for the gstreamer bug, + //should be remove once it get fixed + if (newState == QMediaPlayer::PlayingState && m_mediaStatus == QMediaPlayer::InvalidMedia) { + setMedia(m_currentResource, m_stream); + } +#endif + + if (m_mediaStatus == QMediaPlayer::EndOfMedia) { + m_mediaStatus = QMediaPlayer::BufferedMedia; + m_seekToStartPending = true; + } + + if (!m_resources->isGranted() && !m_resources->isRequested()) + m_resources->acquire(); + + if (m_resources->isGranted()) { + if (m_seekToStartPending) { + m_session->pause(); + if (!m_session->seek(0)) { + m_bufferProgress = -1; + m_session->stop(); + m_mediaStatus = QMediaPlayer::LoadingMedia; + } + m_seekToStartPending = false; + } + + bool ok = false; + + if (newState == QMediaPlayer::PlayingState) + ok = m_session->play(); + else + ok = m_session->pause(); + + if (!ok) + newState = QMediaPlayer::StoppedState; + } + + if (m_mediaStatus == QMediaPlayer::InvalidMedia) + m_mediaStatus = QMediaPlayer::LoadingMedia; + + m_state = newState; + + if (m_mediaStatus == QMediaPlayer::EndOfMedia || m_mediaStatus == QMediaPlayer::LoadedMedia) { + if (m_bufferProgress == -1 || m_bufferProgress == 100) + m_mediaStatus = QMediaPlayer::BufferedMedia; + else + m_mediaStatus = QMediaPlayer::BufferingMedia; + } + + popAndNotifyState(); + + emit positionChanged(position()); +} + +void QGstreamerPlayerControl::stop() +{ +#ifdef DEBUG_PLAYBIN + qDebug() << Q_FUNC_INFO; +#endif + + pushState(); + + if (m_state != QMediaPlayer::StoppedState) { + m_state = QMediaPlayer::StoppedState; + if (m_resources->isGranted()) + m_session->pause(); + + if (m_mediaStatus != QMediaPlayer::EndOfMedia) { + m_seekToStartPending = true; + emit positionChanged(position()); + } + } + + popAndNotifyState(); +} + +void QGstreamerPlayerControl::setVolume(int volume) +{ + m_session->setVolume(volume); +} + +void QGstreamerPlayerControl::setMuted(bool muted) +{ + m_session->setMuted(muted); +} + +QMediaContent QGstreamerPlayerControl::media() const +{ + return m_currentResource; +} + +const QIODevice *QGstreamerPlayerControl::mediaStream() const +{ + return m_stream; +} + +void QGstreamerPlayerControl::setMedia(const QMediaContent &content, QIODevice *stream) +{ +#ifdef DEBUG_PLAYBIN + qDebug() << Q_FUNC_INFO; +#endif + + pushState(); + + m_state = QMediaPlayer::StoppedState; + QMediaContent oldMedia = m_currentResource; + m_pendingSeekPosition = -1; + + if (!content.isNull() || stream) { + if (!m_resources->isRequested() && !m_resources->isGranted()) + m_resources->acquire(); + + if (!m_resources->isGranted()) { + m_currentResource = content; + m_stream = stream; + + m_state = QMediaPlayer::StoppedState; + m_mediaStatus = QMediaPlayer::LoadingMedia; + if (m_currentResource != oldMedia) + emit mediaChanged(m_currentResource); + popAndNotifyState(); + return; + } + } else { + m_resources->release(); + } + + m_session->stop(); + + bool userStreamValid = false; + + if (m_bufferProgress != -1) { + m_bufferProgress = -1; + emit bufferStatusChanged(0); + } + + if (m_stream) { +#if !defined(HAVE_GST_APPSRC) + closeFifo(); + + disconnect(m_stream, SIGNAL(readyRead()), this, SLOT(writeFifo())); +#endif + + if (m_ownStream) + delete m_stream; + m_stream = 0; + m_ownStream = false; + } + + // If the canonical URL refers to a Qt resource, open with QFile and use + // the stream playback capability to play. + if (stream == 0 && content.canonicalUrl().scheme() == QLatin1String("qrc")) { + stream = new QFile(QLatin1Char(':') + content.canonicalUrl().path(), this); + if (!stream->open(QIODevice::ReadOnly)) { + delete stream; + m_mediaStatus = QMediaPlayer::InvalidMedia; + m_currentResource = content; + emit mediaChanged(m_currentResource); + emit error(QMediaPlayer::FormatError, tr("Attempting to play invalid Qt resource")); + if (m_state != QMediaPlayer::PlayingState) + m_resources->release(); + popAndNotifyState(); + return; + } + m_ownStream = true; + } + + m_currentResource = content; + m_stream = stream; + m_seekToStartPending = false; + + QNetworkRequest request; + + if (m_stream) { +#if !defined(HAVE_GST_APPSRC) + if (m_stream->isReadable() && openFifo()) { + request = QNetworkRequest(QUrl(QString(QLatin1String("fd://%1")).arg(m_fifoFd[0]))); + } +#else + userStreamValid = stream->isOpen() && m_stream->isReadable(); + request = content.canonicalRequest(); +#endif + } else if (!content.isNull()) { + request = content.canonicalRequest(); + } + +#if !defined(HAVE_GST_APPSRC) + m_session->loadFromUri(request); +#else + if (m_stream) { + if (userStreamValid){ + m_session->loadFromStream(request, m_stream); + } else { + m_mediaStatus = QMediaPlayer::InvalidMedia; + emit error(QMediaPlayer::FormatError, tr("Attempting to play invalid user stream")); + if (m_state != QMediaPlayer::PlayingState) + m_resources->release(); + popAndNotifyState(); + return; + } + } else + m_session->loadFromUri(request); +#endif + +#if !defined(HAVE_GST_APPSRC) + if (m_fifoFd[1] >= 0) { + m_fifoCanWrite = true; + + writeFifo(); + } +#endif + +#if defined(HAVE_GST_APPSRC) + if (!request.url().isEmpty() || userStreamValid) { +#else + if (!request.url().isEmpty()) { +#endif + m_mediaStatus = QMediaPlayer::LoadingMedia; + m_session->pause(); + } else { + m_mediaStatus = QMediaPlayer::NoMedia; + setBufferProgress(0); + } + + if (m_currentResource != oldMedia) + emit mediaChanged(m_currentResource); + + emit positionChanged(position()); + + if (content.isNull() && !stream) + m_resources->release(); + + popAndNotifyState(); +} + +void QGstreamerPlayerControl::setVideoOutput(QObject *output) +{ + m_session->setVideoRenderer(output); +} + +bool QGstreamerPlayerControl::isAudioAvailable() const +{ + return m_session->isAudioAvailable(); +} + +bool QGstreamerPlayerControl::isVideoAvailable() const +{ + return m_session->isVideoAvailable(); +} + +void QGstreamerPlayerControl::updateSessionState(QMediaPlayer::State state) +{ + pushState(); + + if (state == QMediaPlayer::StoppedState) + m_state = QMediaPlayer::StoppedState; + + updateMediaStatus(); + + popAndNotifyState(); +} + +void QGstreamerPlayerControl::updateMediaStatus() +{ + pushState(); + QMediaPlayer::MediaStatus oldStatus = m_mediaStatus; + + switch (m_session->state()) { + case QMediaPlayer::StoppedState: + if (m_currentResource.isNull()) + m_mediaStatus = QMediaPlayer::NoMedia; + else if (oldStatus != QMediaPlayer::InvalidMedia) + m_mediaStatus = QMediaPlayer::LoadingMedia; + break; + + case QMediaPlayer::PlayingState: + case QMediaPlayer::PausedState: + if (m_state == QMediaPlayer::StoppedState) { + m_mediaStatus = QMediaPlayer::LoadedMedia; + } else { + if (m_bufferProgress == -1 || m_bufferProgress == 100) + m_mediaStatus = QMediaPlayer::BufferedMedia; + else + m_mediaStatus = QMediaPlayer::StalledMedia; + } + break; + } + + if (m_state == QMediaPlayer::PlayingState && !m_resources->isGranted()) + m_mediaStatus = QMediaPlayer::StalledMedia; + + //EndOfMedia status should be kept, until reset by pause, play or setMedia + if (oldStatus == QMediaPlayer::EndOfMedia) + m_mediaStatus = QMediaPlayer::EndOfMedia; + + popAndNotifyState(); +} + +void QGstreamerPlayerControl::processEOS() +{ + pushState(); + m_mediaStatus = QMediaPlayer::EndOfMedia; + emit positionChanged(position()); + stop(); + popAndNotifyState(); +} + +void QGstreamerPlayerControl::setBufferProgress(int progress) +{ + if (m_bufferProgress == progress || m_mediaStatus == QMediaPlayer::NoMedia) + return; + +#ifdef DEBUG_PLAYBIN + qDebug() << Q_FUNC_INFO << progress; +#endif + m_bufferProgress = progress; + + if (m_resources->isGranted()) { + if (m_state == QMediaPlayer::PlayingState && + m_bufferProgress == 100 && + m_session->state() != QMediaPlayer::PlayingState) + m_session->play(); + + if (m_bufferProgress < 100 && + (m_session->state() == QMediaPlayer::PlayingState || + m_session->pendingState() == QMediaPlayer::PlayingState)) + m_session->pause(); + } + + updateMediaStatus(); + + emit bufferStatusChanged(m_bufferProgress); +} + +void QGstreamerPlayerControl::writeFifo() +{ + if (m_fifoCanWrite) { + qint64 bytesToRead = qMin<qint64>( + m_stream->bytesAvailable(), PIPE_BUF - m_bufferSize); + + if (bytesToRead > 0) { + int bytesRead = m_stream->read(&m_buffer[m_bufferOffset + m_bufferSize], bytesToRead); + + if (bytesRead > 0) + m_bufferSize += bytesRead; + } + + if (m_bufferSize > 0) { + int bytesWritten = ::write(m_fifoFd[1], &m_buffer[m_bufferOffset], size_t(m_bufferSize)); + + if (bytesWritten > 0) { + m_bufferOffset += bytesWritten; + m_bufferSize -= bytesWritten; + + if (m_bufferSize == 0) + m_bufferOffset = 0; + } else if (errno == EAGAIN) { + m_fifoCanWrite = false; + } else { + closeFifo(); + } + } + } + + m_fifoNotifier->setEnabled(m_stream->bytesAvailable() > 0); +} + +void QGstreamerPlayerControl::fifoReadyWrite(int socket) +{ + if (socket == m_fifoFd[1]) { + m_fifoCanWrite = true; + + writeFifo(); + } +} + +bool QGstreamerPlayerControl::openFifo() +{ + Q_ASSERT(m_fifoFd[0] < 0); + Q_ASSERT(m_fifoFd[1] < 0); + + if (::pipe(m_fifoFd) == 0) { + int flags = ::fcntl(m_fifoFd[1], F_GETFD); + + if (::fcntl(m_fifoFd[1], F_SETFD, flags | O_NONBLOCK) >= 0) { + m_fifoNotifier = new QSocketNotifier(m_fifoFd[1], QSocketNotifier::Write); + + connect(m_fifoNotifier, SIGNAL(activated(int)), this, SLOT(fifoReadyWrite(int))); + + return true; + } else { + qWarning("Failed to make pipe non blocking %d", errno); + + ::close(m_fifoFd[0]); + ::close(m_fifoFd[1]); + + m_fifoFd[0] = -1; + m_fifoFd[1] = -1; + + return false; + } + } else { + qWarning("Failed to create pipe %d", errno); + + return false; + } +} + +void QGstreamerPlayerControl::closeFifo() +{ + if (m_fifoFd[0] >= 0) { + delete m_fifoNotifier; + m_fifoNotifier = 0; + + ::close(m_fifoFd[0]); + ::close(m_fifoFd[1]); + m_fifoFd[0] = -1; + m_fifoFd[1] = -1; + + m_fifoCanWrite = false; + + m_bufferSize = 0; + m_bufferOffset = 0; + } +} + +void QGstreamerPlayerControl::applyPendingSeek(bool isSeekable) +{ + if (isSeekable && m_pendingSeekPosition != -1) + setPosition(m_pendingSeekPosition); +} + +void QGstreamerPlayerControl::handleInvalidMedia() +{ + pushState(); + m_mediaStatus = QMediaPlayer::InvalidMedia; + m_state = QMediaPlayer::StoppedState; + popAndNotifyState(); +} + +void QGstreamerPlayerControl::handleResourcesGranted() +{ + pushState(); + + QMediaPlayer::State state = m_state; + + //preserve m_pendingSeekPosition, it's reset on setMedia + qint64 pos = m_pendingSeekPosition; + setMedia(m_currentResource, m_stream); + + if (pos != -1) + setPosition(pos); + + if (state != QMediaPlayer::StoppedState) + playOrPause(state); + else + updateMediaStatus(); + + popAndNotifyState(); +} + +void QGstreamerPlayerControl::handleResourcesLost() +{ + //on resource lost the pipeline should be stopped + //player status is changed to paused + + pushState(); + QMediaPlayer::State oldState = m_state; + + qint64 pos = m_session->position(); + m_session->stop(); + m_pendingSeekPosition = pos; + + if (oldState != QMediaPlayer::StoppedState ) + m_state = QMediaPlayer::PausedState; + + popAndNotifyState(); +} + +bool QGstreamerPlayerControl::isMediaDownloadEnabled() const +{ + return m_session->property("mediaDownloadEnabled").toBool(); +} + +void QGstreamerPlayerControl::setMediaDownloadEnabled(bool enabled) +{ + m_session->setProperty("mediaDownloadEnabled", enabled); +} + +void QGstreamerPlayerControl::pushState() +{ + m_stateStack.push(m_state); + m_mediaStatusStack.push(m_mediaStatus); +} + +void QGstreamerPlayerControl::popAndNotifyState() +{ + Q_ASSERT(!m_stateStack.isEmpty()); + + QMediaPlayer::State oldState = m_stateStack.pop(); + QMediaPlayer::MediaStatus oldMediaStatus = m_mediaStatusStack.pop(); + + if (m_stateStack.isEmpty()) { + if (m_state != oldState) { +#ifdef DEBUG_PLAYBIN + qDebug() << "State changed:" << m_state; +#endif + emit stateChanged(m_state); + } + + if (m_mediaStatus != oldMediaStatus) { +#ifdef DEBUG_PLAYBIN + qDebug() << "Media status changed:" << m_mediaStatus; +#endif + emit mediaStatusChanged(m_mediaStatus); + } + } +} diff --git a/src/plugins/gstreamer/mediaplayer/qgstreamerplayercontrol.h b/src/plugins/gstreamer/mediaplayer/qgstreamerplayercontrol.h new file mode 100644 index 000000000..5a53a0713 --- /dev/null +++ b/src/plugins/gstreamer/mediaplayer/qgstreamerplayercontrol.h @@ -0,0 +1,157 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGSTREAMERPLAYERCONTROL_H +#define QGSTREAMERPLAYERCONTROL_H + +#include <QtCore/qobject.h> +#include <QtCore/qstack.h> + +#include <qmediaplayercontrol.h> +#include <qmediaplayer.h> + +#include <limits.h> + +QT_BEGIN_NAMESPACE +class QMediaPlaylist; +class QMediaPlaylistNavigator; +class QSocketNotifier; +QT_END_NAMESPACE + +QT_USE_NAMESPACE + +class QGstreamerPlayerSession; +class QGstreamerPlayerService; +class PlayerResourcePolicy; + +class QGstreamerPlayerControl : public QMediaPlayerControl +{ + Q_OBJECT + Q_PROPERTY(bool mediaDownloadEnabled READ isMediaDownloadEnabled WRITE setMediaDownloadEnabled) + +public: + QGstreamerPlayerControl(QGstreamerPlayerSession *session, QObject *parent = 0); + ~QGstreamerPlayerControl(); + + QMediaPlayer::State state() const; + QMediaPlayer::MediaStatus mediaStatus() const; + + qint64 position() const; + qint64 duration() const; + + int bufferStatus() const; + + int volume() const; + bool isMuted() const; + + bool isAudioAvailable() const; + bool isVideoAvailable() const; + void setVideoOutput(QObject *output); + + bool isSeekable() const; + QMediaTimeRange availablePlaybackRanges() const; + + qreal playbackRate() const; + void setPlaybackRate(qreal rate); + + QMediaContent media() const; + const QIODevice *mediaStream() const; + void setMedia(const QMediaContent&, QIODevice *); + + bool isMediaDownloadEnabled() const; + void setMediaDownloadEnabled(bool enabled); + +public Q_SLOTS: + void setPosition(qint64 pos); + + void play(); + void pause(); + void stop(); + + void setVolume(int volume); + void setMuted(bool muted); + +private Q_SLOTS: + void writeFifo(); + void fifoReadyWrite(int socket); + + void updateSessionState(QMediaPlayer::State state); + void updateMediaStatus(); + void processEOS(); + void setBufferProgress(int progress); + void applyPendingSeek(bool isSeekable); + + void handleInvalidMedia(); + + void handleResourcesGranted(); + void handleResourcesLost(); + +private: + bool openFifo(); + void closeFifo(); + void playOrPause(QMediaPlayer::State state); + + void pushState(); + void popAndNotifyState(); + + bool m_ownStream; + QGstreamerPlayerSession *m_session; + QMediaPlayer::State m_state; + QMediaPlayer::MediaStatus m_mediaStatus; + QStack<QMediaPlayer::State> m_stateStack; + QStack<QMediaPlayer::MediaStatus> m_mediaStatusStack; + + int m_bufferProgress; + bool m_seekToStartPending; + qint64 m_pendingSeekPosition; + QMediaContent m_currentResource; + QIODevice *m_stream; + QSocketNotifier *m_fifoNotifier; + int m_fifoFd[2]; + bool m_fifoCanWrite; + int m_bufferSize; + int m_bufferOffset; + char m_buffer[PIPE_BUF]; + + PlayerResourcePolicy *m_resources; +}; + +#endif diff --git a/src/plugins/gstreamer/mediaplayer/qgstreamerplayerservice.cpp b/src/plugins/gstreamer/mediaplayer/qgstreamerplayerservice.cpp new file mode 100644 index 000000000..6976c18ae --- /dev/null +++ b/src/plugins/gstreamer/mediaplayer/qgstreamerplayerservice.cpp @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/qvariant.h> +#include <QtCore/qdebug.h> +#include <QtGui/qwidget.h> + +#include "qgstreamerplayerservice.h" +#include "qgstreamerplayercontrol.h" +#include "qgstreamerplayersession.h" +#include "qgstreamermetadataprovider.h" + +#include "qgstreamervideooverlay.h" +#include "qgstreamervideowindow.h" +#include "qgstreamervideorenderer.h" + +#if defined(Q_WS_MAEMO_6) && defined(__arm__) +#include "qgstreamergltexturerenderer.h" +#endif + +#include "qgstreamervideowidget.h" +#include "qgstreamerstreamscontrol.h" + +#include <qmediaplaylistnavigator.h> +#include <qmediaplaylist.h> + +QGstreamerPlayerService::QGstreamerPlayerService(QObject *parent): + QMediaService(parent), + m_videoOutput(0), + m_videoRenderer(0), + m_videoWindow(0), + m_videoWidget(0) +{ + m_session = new QGstreamerPlayerSession(this); + m_control = new QGstreamerPlayerControl(m_session, this); + m_metaData = new QGstreamerMetaDataProvider(m_session, this); + m_streamsControl = new QGstreamerStreamsControl(m_session,this); + +#if defined(Q_WS_MAEMO_6) && defined(__arm__) + m_videoRenderer = new QGstreamerGLTextureRenderer(this); +#else + m_videoRenderer = new QGstreamerVideoRenderer(this); +#endif + +#if defined(Q_WS_X11) && !defined(QT_NO_XVIDEO) + +#ifdef Q_WS_MAEMO_6 + m_videoWindow = new QGstreamerVideoWindow(this, "omapxvsink"); +#else + m_videoWindow = new QGstreamerVideoOverlay(this); +#endif + + m_videoWidget = new QGstreamerVideoWidgetControl(this); +#endif +} + +QGstreamerPlayerService::~QGstreamerPlayerService() +{ +} + +QMediaControl *QGstreamerPlayerService::requestControl(const char *name) +{ + if (qstrcmp(name,QMediaPlayerControl_iid) == 0) + return m_control; + + if (qstrcmp(name,QMetaDataReaderControl_iid) == 0) + return m_metaData; + + if (qstrcmp(name,QMediaStreamsControl_iid) == 0) + return m_streamsControl; + + if (!m_videoOutput) { + if (qstrcmp(name, QVideoWidgetControl_iid) == 0) + m_videoOutput = m_videoWidget; + else if (qstrcmp(name, QVideoRendererControl_iid) == 0) + m_videoOutput = m_videoRenderer; + else if (qstrcmp(name, QVideoWindowControl_iid) == 0) + m_videoOutput = m_videoWindow; + + if (m_videoOutput) { + m_control->setVideoOutput(m_videoOutput); + return m_videoOutput; + } + } + + return 0; +} + +void QGstreamerPlayerService::releaseControl(QMediaControl *control) +{ + if (control == m_videoOutput) { + m_videoOutput = 0; + m_control->setVideoOutput(0); + } +} + diff --git a/src/plugins/gstreamer/mediaplayer/qgstreamerplayerservice.h b/src/plugins/gstreamer/mediaplayer/qgstreamerplayerservice.h new file mode 100644 index 000000000..92ea9ffa4 --- /dev/null +++ b/src/plugins/gstreamer/mediaplayer/qgstreamerplayerservice.h @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGSTREAMERPLAYERSERVICE_H +#define QGSTREAMERPLAYERSERVICE_H + +#include <QtCore/qobject.h> +#include <QtCore/qiodevice.h> + +#include <qmediaservice.h> + +QT_BEGIN_NAMESPACE +class QMediaMetaData; +class QMediaPlayerControl; +class QMediaPlaylist; +class QMediaPlaylistNavigator; +QT_END_NAMESPACE + +class QGstreamerMetaData; +class QGstreamerPlayerControl; +class QGstreamerPlayerSession; +class QGstreamerMetaDataProvider; +class QGstreamerStreamsControl; +class QGstreamerVideoRenderer; +class QGstreamerVideoOverlay; +class QGstreamerVideoWidgetControl; + +QT_USE_NAMESPACE + +class QGstreamerPlayerService : public QMediaService +{ + Q_OBJECT +public: + QGstreamerPlayerService(QObject *parent = 0); + ~QGstreamerPlayerService(); + + QMediaControl *requestControl(const char *name); + void releaseControl(QMediaControl *control); + +private: + QGstreamerPlayerControl *m_control; + QGstreamerPlayerSession *m_session; + QGstreamerMetaDataProvider *m_metaData; + QGstreamerStreamsControl *m_streamsControl; + + QMediaControl *m_videoOutput; + QMediaControl *m_videoRenderer; + QMediaControl *m_videoWindow; + QMediaControl *m_videoWidget; +}; + +#endif diff --git a/src/plugins/gstreamer/mediaplayer/qgstreamerplayersession.cpp b/src/plugins/gstreamer/mediaplayer/qgstreamerplayersession.cpp new file mode 100644 index 000000000..d12bb2697 --- /dev/null +++ b/src/plugins/gstreamer/mediaplayer/qgstreamerplayersession.cpp @@ -0,0 +1,1537 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgstreamerplayersession.h" +#include "qgstreamerbushelper.h" + +#include "qgstreamervideorendererinterface.h" +#include "gstvideoconnector.h" +#include "qgstutils.h" + +#include <gst/gstvalue.h> + +#include <QtCore/qdatetime.h> +#include <QtCore/qdebug.h> +#include <QtCore/qsize.h> +#include <QtCore/qtimer.h> +#include <QtCore/qdebug.h> +#include <QtCore/qdir.h> +#include <QtGui/qdesktopservices.h> + +#if defined(Q_WS_MAEMO_5) || defined(Q_WS_MAEMO_6) || (GST_VERSION_MICRO > 20) +#define USE_PLAYBIN2 +#endif + +//#define DEBUG_PLAYBIN +//#define DEBUG_VO_BIN_DUMP + +typedef enum { + GST_PLAY_FLAG_VIDEO = 0x00000001, + GST_PLAY_FLAG_AUDIO = 0x00000002, + GST_PLAY_FLAG_TEXT = 0x00000004, + GST_PLAY_FLAG_VIS = 0x00000008, + GST_PLAY_FLAG_SOFT_VOLUME = 0x00000010, + GST_PLAY_FLAG_NATIVE_AUDIO = 0x00000020, + GST_PLAY_FLAG_NATIVE_VIDEO = 0x00000040, + GST_PLAY_FLAG_DOWNLOAD = 0x00000080, + GST_PLAY_FLAG_BUFFERING = 0x000000100 +} GstPlayFlags; + +QGstreamerPlayerSession::QGstreamerPlayerSession(QObject *parent) + :QObject(parent), + m_state(QMediaPlayer::StoppedState), + m_pendingState(QMediaPlayer::StoppedState), + m_busHelper(0), + m_playbin(0), + m_usePlaybin2(false), + m_usingColorspaceElement(false), + m_videoSink(0), + m_pendingVideoSink(0), + m_nullVideoSink(0), + m_bus(0), + m_videoOutput(0), + m_renderer(0), + m_haveQueueElement(false), +#if defined(HAVE_GST_APPSRC) + m_appSrc(0), +#endif + m_volume(100), + m_playbackRate(1.0), + m_muted(false), + m_audioAvailable(false), + m_videoAvailable(false), + m_seekable(false), + m_lastPosition(0), + m_duration(-1), + m_durationQueries(0), + m_everPlayed(false) , + m_sourceType(UnknownSrc) +{ +#ifdef USE_PLAYBIN2 + m_playbin = gst_element_factory_make("playbin2", NULL); +#endif + + if (m_playbin) { + m_usePlaybin2 = true; + + //GST_PLAY_FLAG_NATIVE_VIDEO omits configuration of ffmpegcolorspace and videoscale, + //since those elements are included in the video output bin when necessary. +#ifdef Q_WS_MAEMO_6 + int flags = GST_PLAY_FLAG_VIDEO | GST_PLAY_FLAG_AUDIO | + GST_PLAY_FLAG_NATIVE_VIDEO | GST_PLAY_FLAG_NATIVE_AUDIO; +#else + int flags = 0; + g_object_get(G_OBJECT(m_playbin), "flags", &flags, NULL); + flags |= GST_PLAY_FLAG_NATIVE_VIDEO; +#endif + g_object_set(G_OBJECT(m_playbin), "flags", flags, NULL); + } else { + m_usePlaybin2 = false; + m_playbin = gst_element_factory_make("playbin", NULL); + } + + m_videoOutputBin = gst_bin_new("video-output-bin"); + gst_object_ref(GST_OBJECT(m_videoOutputBin)); + + m_videoIdentity = GST_ELEMENT(g_object_new(gst_video_connector_get_type(), 0)); + g_signal_connect(G_OBJECT(m_videoIdentity), "connection-failed", G_CALLBACK(insertColorSpaceElement), (gpointer)this); + m_colorSpace = gst_element_factory_make("ffmpegcolorspace", "ffmpegcolorspace-vo"); + gst_object_ref(GST_OBJECT(m_colorSpace)); + + m_nullVideoSink = gst_element_factory_make("fakesink", NULL); + g_object_set(G_OBJECT(m_nullVideoSink), "sync", true, NULL); + gst_object_ref(GST_OBJECT(m_nullVideoSink)); + gst_bin_add_many(GST_BIN(m_videoOutputBin), m_videoIdentity, m_nullVideoSink, NULL); + gst_element_link(m_videoIdentity, m_nullVideoSink); + + m_videoSink = m_nullVideoSink; + + // add ghostpads + GstPad *pad = gst_element_get_static_pad(m_videoIdentity,"sink"); + gst_element_add_pad(GST_ELEMENT(m_videoOutputBin), gst_ghost_pad_new("videosink", pad)); + gst_object_unref(GST_OBJECT(pad)); + + if (m_playbin != 0) { + // Sort out messages + m_bus = gst_element_get_bus(m_playbin); + m_busHelper = new QGstreamerBusHelper(m_bus, this); + connect(m_busHelper, SIGNAL(message(QGstreamerMessage)), SLOT(busMessage(QGstreamerMessage))); + m_busHelper->installSyncEventFilter(this); + + g_object_set(G_OBJECT(m_playbin), "video-sink", m_videoOutputBin, NULL); + + g_signal_connect(G_OBJECT(m_playbin), "notify::source", G_CALLBACK(playbinNotifySource), this); + g_signal_connect(G_OBJECT(m_playbin), "element-added", G_CALLBACK(handleElementAdded), this); + + // Initial volume + double volume = 1.0; + g_object_get(G_OBJECT(m_playbin), "volume", &volume, NULL); + m_volume = int(volume*100); + + g_signal_connect(G_OBJECT(m_playbin), "notify::volume", G_CALLBACK(handleVolumeChange), this); + if (m_usePlaybin2) + g_signal_connect(G_OBJECT(m_playbin), "notify::mute", G_CALLBACK(handleMutedChange), this); + } +} + +QGstreamerPlayerSession::~QGstreamerPlayerSession() +{ + if (m_playbin) { + stop(); + + delete m_busHelper; + gst_object_unref(GST_OBJECT(m_bus)); + gst_object_unref(GST_OBJECT(m_playbin)); + gst_object_unref(GST_OBJECT(m_colorSpace)); + gst_object_unref(GST_OBJECT(m_nullVideoSink)); + gst_object_unref(GST_OBJECT(m_videoOutputBin)); + } +} + +#if defined(HAVE_GST_APPSRC) +void QGstreamerPlayerSession::configureAppSrcElement(GObject* object, GObject *orig, GParamSpec *pspec, QGstreamerPlayerSession* self) +{ + if (self->appsrc()->isReady()) + return; + + GstElement *appsrc; + g_object_get(orig, "source", &appsrc, NULL); + + if (!self->appsrc()->setup(appsrc)) + qWarning()<<"Could not setup appsrc element"; +} +#endif + +void QGstreamerPlayerSession::loadFromStream(const QNetworkRequest &request, QIODevice *appSrcStream) +{ +#if defined(HAVE_GST_APPSRC) + m_request = request; + m_duration = -1; + m_lastPosition = 0; + m_haveQueueElement = false; + + if (m_appSrc) + m_appSrc->deleteLater(); + m_appSrc = new QGstAppSrc(this); + m_appSrc->setStream(appSrcStream); + + if (m_playbin) { + m_tags.clear(); + emit tagsChanged(); + + g_signal_connect(G_OBJECT(m_playbin), "deep-notify::source", (GCallback) &QGstreamerPlayerSession::configureAppSrcElement, (gpointer)this); + g_object_set(G_OBJECT(m_playbin), "uri", "appsrc://", NULL); + + if (!m_streamTypes.isEmpty()) { + m_streamProperties.clear(); + m_streamTypes.clear(); + + emit streamsChanged(); + } + } +#endif +} + +void QGstreamerPlayerSession::loadFromUri(const QNetworkRequest &request) +{ + m_request = request; + m_duration = -1; + m_lastPosition = 0; + m_haveQueueElement = false; + + if (m_playbin) { + m_tags.clear(); + emit tagsChanged(); + + g_object_set(G_OBJECT(m_playbin), "uri", m_request.url().toEncoded().constData(), NULL); + + if (!m_streamTypes.isEmpty()) { + m_streamProperties.clear(); + m_streamTypes.clear(); + + emit streamsChanged(); + } + } +} + +qint64 QGstreamerPlayerSession::duration() const +{ + return m_duration; +} + +qint64 QGstreamerPlayerSession::position() const +{ + GstFormat format = GST_FORMAT_TIME; + gint64 position = 0; + + if ( m_playbin && gst_element_query_position(m_playbin, &format, &position)) + m_lastPosition = position / 1000000; + + return m_lastPosition; +} + +qreal QGstreamerPlayerSession::playbackRate() const +{ + return m_playbackRate; +} + +void QGstreamerPlayerSession::setPlaybackRate(qreal rate) +{ + if (!qFuzzyCompare(m_playbackRate, rate)) { + m_playbackRate = rate; + if (m_playbin) { + gst_element_seek(m_playbin, rate, GST_FORMAT_TIME, + GstSeekFlags(GST_SEEK_FLAG_ACCURATE | GST_SEEK_FLAG_FLUSH), + GST_SEEK_TYPE_NONE,0, + GST_SEEK_TYPE_NONE,0 ); + } + emit playbackRateChanged(m_playbackRate); + } +} + +QMediaTimeRange QGstreamerPlayerSession::availablePlaybackRanges() const +{ + QMediaTimeRange ranges; +#if (GST_VERSION_MAJOR >= 0) && (GST_VERSION_MINOR >= 10) && (GST_VERSION_MICRO >= 31) + //GST_FORMAT_TIME would be more appropriate, but unfortunately it's not supported. + //with GST_FORMAT_PERCENT media is treated as encoded with constant bitrate. + GstQuery* query = gst_query_new_buffering(GST_FORMAT_PERCENT); + + if (gst_element_query(m_playbin, query)) { + for (guint index = 0; index < gst_query_get_n_buffering_ranges(query); index++) { + gint64 rangeStart = 0; + gint64 rangeStop = 0; + + //This query should return values in GST_FORMAT_PERCENT_MAX range, + //but queue2 returns values in 0..100 range instead + if (gst_query_parse_nth_buffering_range(query, index, &rangeStart, &rangeStop)) + ranges.addInterval(rangeStart * duration() / 100, + rangeStop * duration() / 100); + } + } + + gst_query_unref(query); +#endif + + //without queue2 element in pipeline all the media is considered available + if (ranges.isEmpty() && duration() > 0 && !m_haveQueueElement) + ranges.addInterval(0, duration()); + +#ifdef DEBUG_PLAYBIN + qDebug() << ranges; +#endif + + return ranges; +} + +int QGstreamerPlayerSession::activeStream(QMediaStreamsControl::StreamType streamType) const +{ + int streamNumber = -1; + if (m_playbin) { + switch (streamType) { + case QMediaStreamsControl::AudioStream: + g_object_get(G_OBJECT(m_playbin), "current-audio", streamNumber, NULL); + break; + case QMediaStreamsControl::VideoStream: + g_object_get(G_OBJECT(m_playbin), "current-video", streamNumber, NULL); + break; + case QMediaStreamsControl::SubPictureStream: + g_object_get(G_OBJECT(m_playbin), "current-text", streamNumber, NULL); + break; + default: + break; + } + } + + if (m_usePlaybin2 && streamNumber >= 0) + streamNumber += m_playbin2StreamOffset.value(streamType,0); + + return streamNumber; +} + +void QGstreamerPlayerSession::setActiveStream(QMediaStreamsControl::StreamType streamType, int streamNumber) +{ + + if (m_usePlaybin2 && streamNumber >= 0) + streamNumber -= m_playbin2StreamOffset.value(streamType,0); + + if (m_playbin) { + switch (streamType) { + case QMediaStreamsControl::AudioStream: + g_object_set(G_OBJECT(m_playbin), "current-audio", &streamNumber, NULL); + break; + case QMediaStreamsControl::VideoStream: + g_object_set(G_OBJECT(m_playbin), "current-video", &streamNumber, NULL); + break; + case QMediaStreamsControl::SubPictureStream: + g_object_set(G_OBJECT(m_playbin), "current-text", &streamNumber, NULL); + break; + default: + break; + } + } +} + + +bool QGstreamerPlayerSession::isBuffering() const +{ + return false; +} + +int QGstreamerPlayerSession::bufferingProgress() const +{ + return 0; +} + +int QGstreamerPlayerSession::volume() const +{ + return m_volume; +} + +bool QGstreamerPlayerSession::isMuted() const +{ + return m_muted; +} + +bool QGstreamerPlayerSession::isAudioAvailable() const +{ + return m_audioAvailable; +} + +static void block_pad_cb(GstPad *pad, gboolean blocked, gpointer user_data) +{ + Q_UNUSED(pad); +#ifdef DEBUG_PLAYBIN + qDebug() << "block_pad_cb, blocked:" << blocked; +#endif + + if (blocked && user_data) { + QGstreamerPlayerSession *session = reinterpret_cast<QGstreamerPlayerSession*>(user_data); + QMetaObject::invokeMethod(session, "finishVideoOutputChange", Qt::QueuedConnection); + } +} + +void QGstreamerPlayerSession::updateVideoRenderer() +{ +#ifdef DEBUG_PLAYBIN + qDebug() << "Video sink has chaged, reload video output"; +#endif + + if (m_videoOutput) + setVideoRenderer(m_videoOutput); +} + +void QGstreamerPlayerSession::setVideoRenderer(QObject *videoOutput) +{ + if (m_videoOutput != videoOutput) { + if (m_videoOutput) { + disconnect(m_videoOutput, SIGNAL(sinkChanged()), + this, SLOT(updateVideoRenderer())); + disconnect(m_videoOutput, SIGNAL(readyChanged(bool)), + this, SLOT(updateVideoRenderer())); + } + + if (videoOutput) { + connect(videoOutput, SIGNAL(sinkChanged()), + this, SLOT(updateVideoRenderer())); + connect(videoOutput, SIGNAL(readyChanged(bool)), + this, SLOT(updateVideoRenderer())); + } + + m_videoOutput = videoOutput; + } + + QGstreamerVideoRendererInterface* renderer = qobject_cast<QGstreamerVideoRendererInterface*>(videoOutput); + + m_renderer = renderer; + +#ifdef DEBUG_VO_BIN_DUMP + _gst_debug_bin_to_dot_file_with_ts(GST_BIN(m_playbin), + GstDebugGraphDetails(GST_DEBUG_GRAPH_SHOW_ALL /* GST_DEBUG_GRAPH_SHOW_MEDIA_TYPE | GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS | GST_DEBUG_GRAPH_SHOW_STATES*/), + "playbin_set"); +#endif + + GstElement *videoSink = 0; + if (m_renderer && m_renderer->isReady()) + videoSink = m_renderer->videoSink(); + + if (!videoSink) + videoSink = m_nullVideoSink; + +#ifdef DEBUG_PLAYBIN + qDebug() << "Set video output:" << videoOutput; + qDebug() << "Current sink:" << (m_videoSink ? GST_ELEMENT_NAME(m_videoSink) : "") << m_videoSink + << "pending:" << (m_pendingVideoSink ? GST_ELEMENT_NAME(m_pendingVideoSink) : "") << m_pendingVideoSink + << "new sink:" << (videoSink ? GST_ELEMENT_NAME(videoSink) : "") << videoSink; +#endif + + if (m_pendingVideoSink == videoSink || + (m_pendingVideoSink == 0 && m_videoSink == videoSink)) { +#ifdef DEBUG_PLAYBIN + qDebug() << "Video sink has not changed, skip video output reconfiguration"; +#endif + return; + } + +#ifdef DEBUG_PLAYBIN + qDebug() << "Reconfigure video output"; +#endif + + if (m_state == QMediaPlayer::StoppedState) { +#ifdef DEBUG_PLAYBIN + qDebug() << "The pipeline has not started yet, pending state:" << m_pendingState; +#endif + //the pipeline has not started yet + m_pendingVideoSink = 0; + gst_element_set_state(m_videoSink, GST_STATE_NULL); + gst_element_set_state(m_playbin, GST_STATE_NULL); + + if (m_usingColorspaceElement) { + gst_element_unlink(m_colorSpace, m_videoSink); + gst_bin_remove(GST_BIN(m_videoOutputBin), m_colorSpace); + } else { + gst_element_unlink(m_videoIdentity, m_videoSink); + } + + gst_bin_remove(GST_BIN(m_videoOutputBin), m_videoSink); + + m_videoSink = videoSink; + + gst_bin_add(GST_BIN(m_videoOutputBin), m_videoSink); + + m_usingColorspaceElement = false; + bool linked = gst_element_link(m_videoIdentity, m_videoSink); + if (!linked) { + m_usingColorspaceElement = true; +#ifdef DEBUG_PLAYBIN + qDebug() << "Failed to connect video output, inserting the colorspace element."; +#endif + gst_bin_add(GST_BIN(m_videoOutputBin), m_colorSpace); + linked = gst_element_link_many(m_videoIdentity, m_colorSpace, m_videoSink, NULL); + } + + switch (m_pendingState) { + case QMediaPlayer::PausedState: + gst_element_set_state(m_playbin, GST_STATE_PAUSED); + break; + case QMediaPlayer::PlayingState: + gst_element_set_state(m_playbin, GST_STATE_PLAYING); + break; + default: + break; + } + } else { + if (m_pendingVideoSink) { +#ifdef DEBUG_PLAYBIN + qDebug() << "already waiting for pad to be blocked, just change the pending sink"; +#endif + m_pendingVideoSink = videoSink; + return; + } + + m_pendingVideoSink = videoSink; + +#ifdef DEBUG_PLAYBIN + qDebug() << "Blocking the video output pad..."; +#endif + + //block pads, async to avoid locking in paused state + GstPad *srcPad = gst_element_get_static_pad(m_videoIdentity, "src"); + gst_pad_set_blocked_async(srcPad, true, &block_pad_cb, this); + gst_object_unref(GST_OBJECT(srcPad)); + + //Unpause the sink to avoid waiting until the buffer is processed + //while the sink is paused. The pad will be blocked as soon as the current + //buffer is processed. + if (m_state == QMediaPlayer::PausedState) { +#ifdef DEBUG_PLAYBIN + qDebug() << "Starting video output to avoid blocking in paused state..."; +#endif + gst_element_set_state(m_videoSink, GST_STATE_PLAYING); + } + } +} + +void QGstreamerPlayerSession::finishVideoOutputChange() +{ + if (!m_pendingVideoSink) + return; + +#ifdef DEBUG_PLAYBIN + qDebug() << "finishVideoOutputChange" << m_pendingVideoSink; +#endif + + GstPad *srcPad = gst_element_get_static_pad(m_videoIdentity, "src"); + + if (!gst_pad_is_blocked(srcPad)) { + //pad is not blocked, it's possible to swap outputs only in the null state + qWarning() << "Pad is not blocked yet, could not switch video sink"; + GstState identityElementState = GST_STATE_NULL; + gst_element_get_state(m_videoIdentity, &identityElementState, NULL, GST_CLOCK_TIME_NONE); + if (identityElementState != GST_STATE_NULL) { + gst_object_unref(GST_OBJECT(srcPad)); + return; //can't change vo yet, received async call from the previous change + } + } + + if (m_pendingVideoSink == m_videoSink) { + //video output was change back to the current one, + //no need to torment the pipeline, just unblock the pad + if (gst_pad_is_blocked(srcPad)) + gst_pad_set_blocked_async(srcPad, false, &block_pad_cb, 0); + + m_pendingVideoSink = 0; + gst_object_unref(GST_OBJECT(srcPad)); + return; + } + + if (m_usingColorspaceElement) { + gst_element_set_state(m_colorSpace, GST_STATE_NULL); + gst_element_set_state(m_videoSink, GST_STATE_NULL); + + gst_element_unlink(m_colorSpace, m_videoSink); + gst_bin_remove(GST_BIN(m_videoOutputBin), m_colorSpace); + } else { + gst_element_set_state(m_videoSink, GST_STATE_NULL); + gst_element_unlink(m_videoIdentity, m_videoSink); + } + + gst_bin_remove(GST_BIN(m_videoOutputBin), m_videoSink); + + m_videoSink = m_pendingVideoSink; + m_pendingVideoSink = 0; + + gst_bin_add(GST_BIN(m_videoOutputBin), m_videoSink); + + m_usingColorspaceElement = false; + bool linked = gst_element_link(m_videoIdentity, m_videoSink); + if (!linked) { + m_usingColorspaceElement = true; +#ifdef DEBUG_PLAYBIN + qDebug() << "Failed to connect video output, inserting the colorspace element."; +#endif + gst_bin_add(GST_BIN(m_videoOutputBin), m_colorSpace); + linked = gst_element_link_many(m_videoIdentity, m_colorSpace, m_videoSink, NULL); + } + + if (!linked) + qWarning() << "Linking video output element failed"; + +#ifdef DEBUG_PLAYBIN + qDebug() << "notify the video connector it has to emit a new segment message..."; +#endif + //it's necessary to send a new segment event just before + //the first buffer pushed to the new sink + g_signal_emit_by_name(m_videoIdentity, + "resend-new-segment", + true //emit connection-failed signal + //to have a chance to insert colorspace element + ); + + + GstState state; + + switch (m_pendingState) { + case QMediaPlayer::StoppedState: + state = GST_STATE_NULL; + break; + case QMediaPlayer::PausedState: + state = GST_STATE_PAUSED; + break; + case QMediaPlayer::PlayingState: + state = GST_STATE_PLAYING; + break; + } + + if (m_usingColorspaceElement) + gst_element_set_state(m_colorSpace, state); + + gst_element_set_state(m_videoSink, state); + + // Set state change that was deferred due the video output + // change being pending + gst_element_set_state(m_playbin, state); + + //don't have to wait here, it will unblock eventually + if (gst_pad_is_blocked(srcPad)) + gst_pad_set_blocked_async(srcPad, false, &block_pad_cb, 0); + gst_object_unref(GST_OBJECT(srcPad)); + +#ifdef DEBUG_VO_BIN_DUMP + _gst_debug_bin_to_dot_file_with_ts(GST_BIN(m_playbin), + GstDebugGraphDetails(GST_DEBUG_GRAPH_SHOW_ALL /* GST_DEBUG_GRAPH_SHOW_MEDIA_TYPE | GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS | GST_DEBUG_GRAPH_SHOW_STATES*/), + "playbin_finish"); +#endif +} + +void QGstreamerPlayerSession::insertColorSpaceElement(GstElement *element, gpointer data) +{ + Q_UNUSED(element); + QGstreamerPlayerSession* session = reinterpret_cast<QGstreamerPlayerSession*>(data); + + if (session->m_usingColorspaceElement) + return; + session->m_usingColorspaceElement = true; + +#ifdef DEBUG_PLAYBIN + qDebug() << "Failed to connect video output, inserting the colorspace elemnt."; + qDebug() << "notify the video connector it has to emit a new segment message..."; +#endif + //it's necessary to send a new segment event just before + //the first buffer pushed to the new sink + g_signal_emit_by_name(session->m_videoIdentity, + "resend-new-segment", + false // don't emit connection-failed signal + ); + + gst_element_unlink(session->m_videoIdentity, session->m_videoSink); + gst_bin_add(GST_BIN(session->m_videoOutputBin), session->m_colorSpace); + gst_element_link_many(session->m_videoIdentity, session->m_colorSpace, session->m_videoSink, NULL); + + GstState state; + + switch (session->m_pendingState) { + case QMediaPlayer::StoppedState: + state = GST_STATE_NULL; + break; + case QMediaPlayer::PausedState: + state = GST_STATE_PAUSED; + break; + case QMediaPlayer::PlayingState: + state = GST_STATE_PLAYING; + break; + } + + gst_element_set_state(session->m_colorSpace, state); +} + + +bool QGstreamerPlayerSession::isVideoAvailable() const +{ + return m_videoAvailable; +} + +bool QGstreamerPlayerSession::isSeekable() const +{ + return m_seekable; +} + +bool QGstreamerPlayerSession::play() +{ + m_everPlayed = false; + if (m_playbin) { + m_pendingState = QMediaPlayer::PlayingState; + if (gst_element_set_state(m_playbin, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) { + qWarning() << "GStreamer; Unable to play -" << m_request.url().toString(); + m_pendingState = m_state = QMediaPlayer::StoppedState; + + emit stateChanged(m_state); + } else + return true; + } + + return false; +} + +bool QGstreamerPlayerSession::pause() +{ + if (m_playbin) { + m_pendingState = QMediaPlayer::PausedState; + if (m_pendingVideoSink != 0) + return true; + + if (gst_element_set_state(m_playbin, GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE) { + qWarning() << "GStreamer; Unable to pause -" << m_request.url().toString(); + m_pendingState = m_state = QMediaPlayer::StoppedState; + + emit stateChanged(m_state); + } else { + return true; + } + } + + return false; +} + +void QGstreamerPlayerSession::stop() +{ + m_everPlayed = false; + if (m_playbin) { + if (m_renderer) + m_renderer->stopRenderer(); + + gst_element_set_state(m_playbin, GST_STATE_NULL); + + m_lastPosition = 0; + QMediaPlayer::State oldState = m_state; + m_pendingState = m_state = QMediaPlayer::StoppedState; + + finishVideoOutputChange(); + + //we have to do it here, since gstreamer will not emit bus messages any more + setSeekable(false); + if (oldState != m_state) + emit stateChanged(m_state); + } +} + +bool QGstreamerPlayerSession::seek(qint64 ms) +{ + //seek locks when the video output sink is changing and pad is blocked + if (m_playbin && !m_pendingVideoSink && m_state != QMediaPlayer::StoppedState) { + ms = qMax(ms,qint64(0)); + gint64 position = ms * 1000000; + bool isSeeking = gst_element_seek(m_playbin, + m_playbackRate, + GST_FORMAT_TIME, + GstSeekFlags(GST_SEEK_FLAG_ACCURATE | GST_SEEK_FLAG_FLUSH), + GST_SEEK_TYPE_SET, + position, + GST_SEEK_TYPE_NONE, + 0); + if (isSeeking) + m_lastPosition = ms; + + return isSeeking; + } + + return false; +} + +void QGstreamerPlayerSession::setVolume(int volume) +{ + if (m_volume != volume) { + m_volume = volume; + + if (m_playbin) { + //playbin2 allows to set volume and muted independently, + //with playbin1 it's necessary to keep volume at 0 while muted + if (!m_muted || m_usePlaybin2) + g_object_set(G_OBJECT(m_playbin), "volume", m_volume/100.0, NULL); + } + + emit volumeChanged(m_volume); + } +} + +void QGstreamerPlayerSession::setMuted(bool muted) +{ + if (m_muted != muted) { + m_muted = muted; + + if (m_usePlaybin2) + g_object_set(G_OBJECT(m_playbin), "mute", m_muted, NULL); + else + g_object_set(G_OBJECT(m_playbin), "volume", (m_muted ? 0 : m_volume/100.0), NULL); + + emit mutedStateChanged(m_muted); + } +} + + +void QGstreamerPlayerSession::setSeekable(bool seekable) +{ + if (seekable != m_seekable) { + m_seekable = seekable; + emit seekableChanged(m_seekable); + } +} + +bool QGstreamerPlayerSession::processSyncMessage(const QGstreamerMessage &message) +{ + GstMessage* gm = message.rawMessage(); + + if (gm && GST_MESSAGE_TYPE(gm) == GST_MESSAGE_ELEMENT) { + if (m_renderer) { + if (GST_MESSAGE_SRC(gm) == GST_OBJECT_CAST(m_videoSink)) + m_renderer->handleSyncMessage(gm); + + if (gst_structure_has_name(gm->structure, "prepare-xwindow-id")) { + m_renderer->precessNewStream(); + return true; + } + } + } + + return false; +} + +void QGstreamerPlayerSession::busMessage(const QGstreamerMessage &message) +{ + GstMessage* gm = message.rawMessage(); + + if (gm) { + //tag message comes from elements inside playbin, not from playbin itself + if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_TAG) { + //qDebug() << "tag message"; + GstTagList *tag_list; + gst_message_parse_tag(gm, &tag_list); + m_tags.unite(QGstUtils::gstTagListToMap(tag_list)); + + //qDebug() << m_tags; + + emit tagsChanged(); + } else if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_DURATION) { + updateDuration(); + } + +#ifdef DEBUG_PLAYBIN + if (m_sourceType == MMSSrc && qstrcmp(GST_OBJECT_NAME(GST_MESSAGE_SRC(gm)), "source") == 0) { + qDebug() << "Message from MMSSrc: " << GST_MESSAGE_TYPE(gm); + } +#endif + + if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_BUFFERING) { + int progress = 0; + gst_message_parse_buffering(gm, &progress); + emit bufferingProgressChanged(progress); + } + + bool handlePlaybin2 = false; + if (GST_MESSAGE_SRC(gm) == GST_OBJECT_CAST(m_playbin)) { + switch (GST_MESSAGE_TYPE(gm)) { + case GST_MESSAGE_STATE_CHANGED: + { + GstState oldState; + GstState newState; + GstState pending; + + gst_message_parse_state_changed(gm, &oldState, &newState, &pending); + +#ifdef DEBUG_PLAYBIN + QStringList states; + states << "GST_STATE_VOID_PENDING" << "GST_STATE_NULL" << "GST_STATE_READY" << "GST_STATE_PAUSED" << "GST_STATE_PLAYING"; + + qDebug() << QString("state changed: old: %1 new: %2 pending: %3") \ + .arg(states[oldState]) \ + .arg(states[newState]) \ + .arg(states[pending]); +#endif + + switch (newState) { + case GST_STATE_VOID_PENDING: + case GST_STATE_NULL: + setSeekable(false); + finishVideoOutputChange(); + if (m_state != QMediaPlayer::StoppedState) + emit stateChanged(m_state = QMediaPlayer::StoppedState); + break; + case GST_STATE_READY: + setSeekable(false); + if (m_state != QMediaPlayer::StoppedState) + emit stateChanged(m_state = QMediaPlayer::StoppedState); + break; + case GST_STATE_PAUSED: + { + QMediaPlayer::State prevState = m_state; + m_state = QMediaPlayer::PausedState; + + //check for seekable + if (oldState == GST_STATE_READY) { + if (m_sourceType == SoupHTTPSrc || m_sourceType == MMSSrc) { + //since udpsrc is a live source, it is not applicable here + m_everPlayed = true; + } + + getStreamsInfo(); + updateVideoResolutionTag(); + + //gstreamer doesn't give a reliable indication the duration + //information is ready, GST_MESSAGE_DURATION is not sent by most elements + //the duration is queried up to 5 times with increasing delay + m_durationQueries = 5; + updateDuration(); + + /* + //gst_element_seek_simple doesn't work reliably here, have to find a better solution + + GstFormat format = GST_FORMAT_TIME; + gint64 position = 0; + bool seekable = false; + if (gst_element_query_position(m_playbin, &format, &position)) { + seekable = gst_element_seek_simple(m_playbin, format, GST_SEEK_FLAG_NONE, position); + } + + setSeekable(seekable); + */ + + setSeekable(true); + + if (!qFuzzyCompare(m_playbackRate, qreal(1.0))) { + qreal rate = m_playbackRate; + m_playbackRate = 1.0; + setPlaybackRate(rate); + } + } + + if (m_state != prevState) + emit stateChanged(m_state); + + break; + } + case GST_STATE_PLAYING: + m_everPlayed = true; + if (m_state != QMediaPlayer::PlayingState) + emit stateChanged(m_state = QMediaPlayer::PlayingState); + + break; + } + } + break; + + case GST_MESSAGE_EOS: + emit playbackFinished(); + break; + + case GST_MESSAGE_TAG: + case GST_MESSAGE_STREAM_STATUS: + case GST_MESSAGE_UNKNOWN: + break; + case GST_MESSAGE_ERROR: { + GError *err; + gchar *debug; + gst_message_parse_error(gm, &err, &debug); + if (err->domain == GST_STREAM_ERROR && err->code == GST_STREAM_ERROR_CODEC_NOT_FOUND) + processInvalidMedia(QMediaPlayer::FormatError, tr("Cannot play stream of type: <unknown>")); + else + processInvalidMedia(QMediaPlayer::ResourceError, QString::fromUtf8(err->message)); + qWarning() << "Error:" << QString::fromUtf8(err->message); + g_error_free(err); + g_free(debug); + } + break; + case GST_MESSAGE_WARNING: + { + GError *err; + gchar *debug; + gst_message_parse_warning (gm, &err, &debug); + qWarning() << "Warning:" << QString::fromUtf8(err->message); + g_error_free (err); + g_free (debug); + } + break; + case GST_MESSAGE_INFO: +#ifdef DEBUG_PLAYBIN + { + GError *err; + gchar *debug; + gst_message_parse_info (gm, &err, &debug); + qDebug() << "Info:" << QString::fromUtf8(err->message); + g_error_free (err); + g_free (debug); + } +#endif + break; + case GST_MESSAGE_BUFFERING: + case GST_MESSAGE_STATE_DIRTY: + case GST_MESSAGE_STEP_DONE: + case GST_MESSAGE_CLOCK_PROVIDE: + case GST_MESSAGE_CLOCK_LOST: + case GST_MESSAGE_NEW_CLOCK: + case GST_MESSAGE_STRUCTURE_CHANGE: + case GST_MESSAGE_APPLICATION: + case GST_MESSAGE_ELEMENT: + break; + case GST_MESSAGE_SEGMENT_START: + { + const GstStructure *structure = gst_message_get_structure(gm); + qint64 position = g_value_get_int64(gst_structure_get_value(structure, "position")); + position /= 1000000; + m_lastPosition = position; + emit positionChanged(position); + } + break; + case GST_MESSAGE_SEGMENT_DONE: + break; + case GST_MESSAGE_LATENCY: +#if (GST_VERSION_MAJOR >= 0) && (GST_VERSION_MINOR >= 10) && (GST_VERSION_MICRO >= 13) + case GST_MESSAGE_ASYNC_START: + break; + case GST_MESSAGE_ASYNC_DONE: + { + GstFormat format = GST_FORMAT_TIME; + gint64 position = 0; + if (gst_element_query_position(m_playbin, &format, &position)) { + position /= 1000000; + m_lastPosition = position; + emit positionChanged(position); + } + break; + } +#if GST_VERSION_MICRO >= 23 + case GST_MESSAGE_REQUEST_STATE: +#endif +#endif + case GST_MESSAGE_ANY: + break; + default: + break; + } + } else if (m_videoSink + && m_renderer + && GST_MESSAGE_SRC(gm) == GST_OBJECT_CAST(m_videoSink)) { + + m_renderer->handleBusMessage(gm); + if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_STATE_CHANGED) { + GstState oldState; + GstState newState; + gst_message_parse_state_changed(gm, &oldState, &newState, 0); + + if (oldState == GST_STATE_READY && newState == GST_STATE_PAUSED) + m_renderer->precessNewStream(); + } + } else if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_ERROR) { + GError *err; + gchar *debug; + gst_message_parse_error(gm, &err, &debug); + // If the source has given up, so do we. + if (qstrcmp(GST_OBJECT_NAME(GST_MESSAGE_SRC(gm)), "source") == 0) { + bool everPlayed = m_everPlayed; + // Try and differentiate network related resource errors from the others + if (!m_request.url().isRelative() && m_request.url().scheme().compare(QLatin1String("file"), Qt::CaseInsensitive) != 0 ) { + if (everPlayed || + (err->domain == GST_RESOURCE_ERROR && ( + err->code == GST_RESOURCE_ERROR_BUSY || + err->code == GST_RESOURCE_ERROR_OPEN_READ || + err->code == GST_RESOURCE_ERROR_READ || + err->code == GST_RESOURCE_ERROR_SEEK || + err->code == GST_RESOURCE_ERROR_SYNC))) { + processInvalidMedia(QMediaPlayer::NetworkError, QString::fromUtf8(err->message)); + } else { + processInvalidMedia(QMediaPlayer::ResourceError, QString::fromUtf8(err->message)); + } + } + else + processInvalidMedia(QMediaPlayer::ResourceError, QString::fromUtf8(err->message)); + } else if (err->domain == GST_STREAM_ERROR + && (err->code == GST_STREAM_ERROR_DECRYPT || err->code == GST_STREAM_ERROR_DECRYPT_NOKEY)) { + processInvalidMedia(QMediaPlayer::AccessDeniedError, QString::fromUtf8(err->message)); + } else { + handlePlaybin2 = m_usePlaybin2; + } + if (!handlePlaybin2) + qWarning() << "Error:" << QString::fromUtf8(err->message); + g_error_free(err); + g_free(debug); + } else if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_ELEMENT + && qstrcmp(GST_OBJECT_NAME(GST_MESSAGE_SRC(gm)), "source") == 0 + && m_sourceType == UDPSrc + && gst_structure_has_name(gst_message_get_structure(gm), "GstUDPSrcTimeout")) { + //since udpsrc will not generate an error for the timeout event, + //we need to process its element message here and treat it as an error. + processInvalidMedia(m_everPlayed ? QMediaPlayer::NetworkError : QMediaPlayer::ResourceError, + tr("UDP source timeout")); + } else { + handlePlaybin2 = m_usePlaybin2; + } + + if (handlePlaybin2) { + if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_WARNING) { + GError *err; + gchar *debug; + gst_message_parse_warning(gm, &err, &debug); + if (err->domain == GST_STREAM_ERROR && err->code == GST_STREAM_ERROR_CODEC_NOT_FOUND) + emit error(int(QMediaPlayer::FormatError), tr("Cannot play stream of type: <unknown>")); + qWarning() << "Warning:" << QString::fromUtf8(err->message); + g_error_free(err); + g_free(debug); + } else if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_ERROR) { + GError *err; + gchar *debug; + gst_message_parse_error(gm, &err, &debug); + if (qstrncmp(GST_OBJECT_NAME(GST_MESSAGE_SRC(gm)), "decodebin2", 10) == 0 + || qstrncmp(GST_OBJECT_NAME(GST_MESSAGE_SRC(gm)), "uridecodebin", 12) == 0) { + processInvalidMedia(QMediaPlayer::ResourceError, QString::fromUtf8(err->message)); + } else if (err->domain == GST_STREAM_ERROR + && (err->code == GST_STREAM_ERROR_DECRYPT || err->code == GST_STREAM_ERROR_DECRYPT_NOKEY)) { + processInvalidMedia(QMediaPlayer::AccessDeniedError, QString::fromUtf8(err->message)); + } + qWarning() << "Error:" << QString::fromUtf8(err->message); + g_error_free(err); + g_free(debug); + } + } + } +} + +void QGstreamerPlayerSession::getStreamsInfo() +{ + //check if video is available: + bool haveAudio = false; + bool haveVideo = false; + m_streamProperties.clear(); + m_streamTypes.clear(); + + if (m_usePlaybin2) { + gint audioStreamsCount = 0; + gint videoStreamsCount = 0; + gint textStreamsCount = 0; + + g_object_get(G_OBJECT(m_playbin), "n-audio", &audioStreamsCount, NULL); + g_object_get(G_OBJECT(m_playbin), "n-video", &videoStreamsCount, NULL); + g_object_get(G_OBJECT(m_playbin), "n-text", &textStreamsCount, NULL); + + haveAudio = audioStreamsCount > 0; + haveVideo = videoStreamsCount > 0; + + m_playbin2StreamOffset[QMediaStreamsControl::AudioStream] = 0; + m_playbin2StreamOffset[QMediaStreamsControl::VideoStream] = audioStreamsCount; + m_playbin2StreamOffset[QMediaStreamsControl::SubPictureStream] = audioStreamsCount+videoStreamsCount; + + for (int i=0; i<audioStreamsCount; i++) + m_streamTypes.append(QMediaStreamsControl::AudioStream); + + for (int i=0; i<videoStreamsCount; i++) + m_streamTypes.append(QMediaStreamsControl::VideoStream); + + for (int i=0; i<textStreamsCount; i++) + m_streamTypes.append(QMediaStreamsControl::SubPictureStream); + + for (int i=0; i<m_streamTypes.count(); i++) { + QMediaStreamsControl::StreamType streamType = m_streamTypes[i]; + QMap<QtMultimediaKit::MetaData, QVariant> streamProperties; + + int streamIndex = i - m_playbin2StreamOffset[streamType]; + + GstTagList *tags = 0; + switch (streamType) { + case QMediaStreamsControl::AudioStream: + g_signal_emit_by_name(G_OBJECT(m_playbin), "get-audio-tags", streamIndex, &tags); + break; + case QMediaStreamsControl::VideoStream: + g_signal_emit_by_name(G_OBJECT(m_playbin), "get-video-tags", streamIndex, &tags); + break; + case QMediaStreamsControl::SubPictureStream: + g_signal_emit_by_name(G_OBJECT(m_playbin), "get-text-tags", streamIndex, &tags); + break; + default: + break; + } + + if (tags && gst_is_tag_list(tags)) { + gchar *languageCode = 0; + if (gst_tag_list_get_string(tags, GST_TAG_LANGUAGE_CODE, &languageCode)) + streamProperties[QtMultimediaKit::Language] = QString::fromUtf8(languageCode); + + //qDebug() << "language for setream" << i << QString::fromUtf8(languageCode); + g_free (languageCode); + } + + m_streamProperties.append(streamProperties); + } + } else { // PlayBin 1 + enum { + GST_STREAM_TYPE_UNKNOWN, + GST_STREAM_TYPE_AUDIO, + GST_STREAM_TYPE_VIDEO, + GST_STREAM_TYPE_TEXT, + GST_STREAM_TYPE_SUBPICTURE, + GST_STREAM_TYPE_ELEMENT + }; + + GList* streamInfoList; + g_object_get(G_OBJECT(m_playbin), "stream-info", &streamInfoList, NULL); + + for (; streamInfoList != 0; streamInfoList = g_list_next(streamInfoList)) { + gint type; + gchar *languageCode = 0; + + GObject* streamInfo = G_OBJECT(streamInfoList->data); + + g_object_get(streamInfo, "type", &type, NULL); + g_object_get(streamInfo, "language-code", &languageCode, NULL); + + QMediaStreamsControl::StreamType streamType = QMediaStreamsControl::UnknownStream; + + switch (type) { + case GST_STREAM_TYPE_VIDEO: + streamType = QMediaStreamsControl::VideoStream; + haveVideo = true; + break; + case GST_STREAM_TYPE_AUDIO: + streamType = QMediaStreamsControl::AudioStream; + haveAudio = true; + break; + case GST_STREAM_TYPE_SUBPICTURE: + streamType = QMediaStreamsControl::SubPictureStream; + break; + case GST_STREAM_TYPE_UNKNOWN: { + GstCaps *caps = 0; + g_object_get(streamInfo, "caps", &caps, NULL); + const GstStructure *structure = gst_caps_get_structure(caps, 0); + const gchar *media_type = gst_structure_get_name(structure); + emit error(int(QMediaPlayer::FormatError), QString::fromLatin1("Cannot play stream of type: %1").arg(QString::fromUtf8(media_type))); +#ifdef DEBUG_PLAYBIN + qDebug() << "Encountered unknown stream type"; +#endif + gst_caps_unref(caps); + } + default: + streamType = QMediaStreamsControl::UnknownStream; + break; + } + + QMap<QtMultimediaKit::MetaData, QVariant> streamProperties; + streamProperties[QtMultimediaKit::Language] = QString::fromUtf8(languageCode); + + m_streamProperties.append(streamProperties); + m_streamTypes.append(streamType); + } + } + + + if (haveAudio != m_audioAvailable) { + m_audioAvailable = haveAudio; + emit audioAvailableChanged(m_audioAvailable); + } + if (haveVideo != m_videoAvailable) { + m_videoAvailable = haveVideo; + emit videoAvailableChanged(m_videoAvailable); + } + + emit streamsChanged(); +} + +void QGstreamerPlayerSession::updateVideoResolutionTag() +{ + QSize size; + QSize aspectRatio; + + GstPad *pad = gst_element_get_static_pad(m_videoIdentity, "src"); + GstCaps *caps = gst_pad_get_negotiated_caps(pad); + + if (caps) { + const GstStructure *structure = gst_caps_get_structure(caps, 0); + gst_structure_get_int(structure, "width", &size.rwidth()); + gst_structure_get_int(structure, "height", &size.rheight()); + + gint aspectNum = 0; + gint aspectDenum = 0; + if (!size.isEmpty() && gst_structure_get_fraction( + structure, "pixel-aspect-ratio", &aspectNum, &aspectDenum)) { + if (aspectDenum > 0) + aspectRatio = QSize(aspectNum, aspectDenum); + } + gst_caps_unref(caps); + } + + gst_object_unref(GST_OBJECT(pad)); + + QSize currentSize = m_tags.value("resolution").toSize(); + QSize currentAspectRatio = m_tags.value("pixel-aspect-ratio").toSize(); + + if (currentSize != size || currentAspectRatio != aspectRatio) { + if (aspectRatio.isEmpty()) + m_tags.remove("pixel-aspect-ratio"); + + if (size.isEmpty()) { + m_tags.remove("resolution"); + } else { + m_tags.insert("resolution", QVariant(size)); + if (!aspectRatio.isEmpty()) + m_tags.insert("pixel-aspect-ratio", QVariant(aspectRatio)); + } + + emit tagsChanged(); + } +} + +void QGstreamerPlayerSession::updateDuration() +{ + GstFormat format = GST_FORMAT_TIME; + gint64 gstDuration = 0; + int duration = -1; + + if (m_playbin && gst_element_query_duration(m_playbin, &format, &gstDuration)) + duration = gstDuration / 1000000; + + if (m_duration != duration) { + m_duration = duration; + emit durationChanged(m_duration); + } + + if (m_duration > 0) + m_durationQueries = 0; + + if (m_durationQueries > 0) { + //increase delay between duration requests + int delay = 25 << (5 - m_durationQueries); + QTimer::singleShot(delay, this, SLOT(updateDuration())); + m_durationQueries--; + } +} + +void QGstreamerPlayerSession::playbinNotifySource(GObject *o, GParamSpec *p, gpointer d) +{ + Q_UNUSED(p); + + GstElement *source = 0; + g_object_get(o, "source", &source, NULL); + if (source == 0) + return; + +#ifdef DEBUG_PLAYBIN + qDebug() << "Playbin source added:" << G_OBJECT_CLASS_NAME(G_OBJECT_GET_CLASS(source)); +#endif + + // Turn off icecast metadata request, will be re-set if in QNetworkRequest + // (souphttpsrc docs say is false by default, but header appears in request + // @version 0.10.21) + if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), "iradio-mode") != 0) + g_object_set(G_OBJECT(source), "iradio-mode", FALSE, NULL); + + + // Set Headers + const QByteArray userAgentString("User-Agent"); + + QGstreamerPlayerSession *self = reinterpret_cast<QGstreamerPlayerSession *>(d); + + // User-Agent - special case, souphhtpsrc will always set something, even if + // defined in extra-headers + if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), "user-agent") != 0) { + g_object_set(G_OBJECT(source), "user-agent", + self->m_request.rawHeader(userAgentString).constData(), NULL); + } + + // The rest + if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), "extra-headers") != 0) { + GstStructure *extras = gst_structure_empty_new("extras"); + + foreach (const QByteArray &rawHeader, self->m_request.rawHeaderList()) { + if (rawHeader == userAgentString) // Filter User-Agent + continue; + else { + GValue headerValue; + + memset(&headerValue, 0, sizeof(GValue)); + g_value_init(&headerValue, G_TYPE_STRING); + + g_value_set_string(&headerValue, + self->m_request.rawHeader(rawHeader).constData()); + + gst_structure_set_value(extras, rawHeader.constData(), &headerValue); + } + } + + if (gst_structure_n_fields(extras) > 0) + g_object_set(G_OBJECT(source), "extra-headers", extras, NULL); + + gst_structure_free(extras); + } + + //set timeout property to 5 seconds + if (qstrcmp(G_OBJECT_CLASS_NAME(G_OBJECT_GET_CLASS(source)), "GstUDPSrc") == 0) { + //udpsrc timeout unit = microsecond + g_object_set(G_OBJECT(source), "timeout", G_GUINT64_CONSTANT(5000000), NULL); + self->m_sourceType = UDPSrc; + } else if (qstrcmp(G_OBJECT_CLASS_NAME(G_OBJECT_GET_CLASS(source)), "GstSoupHTTPSrc") == 0) { + //souphttpsrc timeout unit = second + g_object_set(G_OBJECT(source), "timeout", guint(5), NULL); + self->m_sourceType = SoupHTTPSrc; + } else if (qstrcmp(G_OBJECT_CLASS_NAME(G_OBJECT_GET_CLASS(source)), "GstMMSSrc") == 0) { + self->m_sourceType = MMSSrc; + g_object_set(G_OBJECT(source), "tcp-timeout", G_GUINT64_CONSTANT(5000000), NULL); + } else { + self->m_sourceType = UnknownSrc; + } + + gst_object_unref(source); +} + +void QGstreamerPlayerSession::handleVolumeChange(GObject *o, GParamSpec *p, gpointer d) +{ + Q_UNUSED(o); + Q_UNUSED(p); + QGstreamerPlayerSession *session = reinterpret_cast<QGstreamerPlayerSession *>(d); + QMetaObject::invokeMethod(session, "updateVolume", Qt::QueuedConnection); +} + +void QGstreamerPlayerSession::updateVolume() +{ + double volume = 1.0; + g_object_get(m_playbin, "volume", &volume, NULL); + + //special case for playbin1 volume changes in muted state + //playbin1 has no separate muted state, + //it's emulated with volume value saved and set to 0 + //this change should not be reported to user + if (!m_usePlaybin2 && m_muted) { + if (volume > 0.001) { + //volume is changed, player in not muted any more + m_muted = false; + emit mutedStateChanged(m_muted = false); + } else { + //don't emit volume changed to 0 when player is muted + return; + } + } + + if (m_volume != int(volume*100)) { + m_volume = int(volume*100); +#ifdef DEBUG_PLAYBIN + qDebug() << Q_FUNC_INFO << m_muted; +#endif + emit volumeChanged(m_volume); + } +} + +void QGstreamerPlayerSession::handleMutedChange(GObject *o, GParamSpec *p, gpointer d) +{ + Q_UNUSED(o); + Q_UNUSED(p); + QGstreamerPlayerSession *session = reinterpret_cast<QGstreamerPlayerSession *>(d); + QMetaObject::invokeMethod(session, "updateMuted", Qt::QueuedConnection); +} + +void QGstreamerPlayerSession::updateMuted() +{ + gboolean muted = false; + g_object_get(G_OBJECT(m_playbin), "mute", &muted, NULL); + if (m_muted != muted) { + m_muted = muted; +#ifdef DEBUG_PLAYBIN + qDebug() << Q_FUNC_INFO << m_muted; +#endif + emit mutedStateChanged(muted); + } +} + + +void QGstreamerPlayerSession::handleElementAdded(GstBin *bin, GstElement *element, QGstreamerPlayerSession *session) +{ + Q_UNUSED(bin); + //we have to configure queue2 element to enable media downloading + //and reporting available ranges, + //but it's added dynamically to playbin2 + + gchar *elementName = gst_element_get_name(element); + + if (g_str_has_prefix(elementName, "queue2")) { + session->m_haveQueueElement = true; + + if (session->property("mediaDownloadEnabled").toBool()) { + QDir cacheDir(QDesktopServices::storageLocation(QDesktopServices::CacheLocation)); + QString cacheLocation = cacheDir.absoluteFilePath("gstmedia__XXXXXX"); +#ifdef DEBUG_PLAYBIN + qDebug() << "set queue2 temp-location" << cacheLocation; +#endif + g_object_set(G_OBJECT(element), "temp-template", cacheLocation.toUtf8().constData(), NULL); + } else { + g_object_set(G_OBJECT(element), "temp-template", NULL, NULL); + } + } else if (g_str_has_prefix(elementName, "uridecodebin") || + g_str_has_prefix(elementName, "decodebin2")) { + //listen for queue2 element added to uridecodebin/decodebin2 as well. + //Don't touch other bins since they may have unrelated queues + g_signal_connect(element, "element-added", + G_CALLBACK(handleElementAdded), session); + } + + g_free(elementName); +} + +//doing proper operations when detecting an invalidMedia: change media status before signal the erorr +void QGstreamerPlayerSession::processInvalidMedia(QMediaPlayer::Error errorCode, const QString& errorString) +{ + emit invalidMedia(); + stop(); + emit error(int(errorCode), errorString); +} diff --git a/src/plugins/gstreamer/mediaplayer/qgstreamerplayersession.h b/src/plugins/gstreamer/mediaplayer/qgstreamerplayersession.h new file mode 100644 index 000000000..e6fa996b0 --- /dev/null +++ b/src/plugins/gstreamer/mediaplayer/qgstreamerplayersession.h @@ -0,0 +1,217 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGSTREAMERPLAYERSESSION_H +#define QGSTREAMERPLAYERSESSION_H + +#include <QObject> +#include <QtNetwork/qnetworkrequest.h> +#include "qgstreamerplayercontrol.h" +#include "qgstreamerbushelper.h" +#include <qmediaplayer.h> +#include <qmediastreamscontrol.h> + +#if defined(HAVE_GST_APPSRC) +#include "qgstappsrc.h" +#endif + +#include <gst/gst.h> + +class QGstreamerBusHelper; +class QGstreamerMessage; + +class QGstreamerVideoRendererInterface; + +QT_USE_NAMESPACE + +class QGstreamerPlayerSession : public QObject, public QGstreamerSyncEventFilter +{ +Q_OBJECT + +public: + QGstreamerPlayerSession(QObject *parent); + virtual ~QGstreamerPlayerSession(); + + QNetworkRequest request() const; + + QMediaPlayer::State state() const { return m_state; } + QMediaPlayer::State pendingState() const { return m_pendingState; } + + qint64 duration() const; + qint64 position() const; + + bool isBuffering() const; + + int bufferingProgress() const; + + int volume() const; + bool isMuted() const; + + bool isAudioAvailable() const; + + void setVideoRenderer(QObject *renderer); + bool isVideoAvailable() const; + + bool isSeekable() const; + + qreal playbackRate() const; + void setPlaybackRate(qreal rate); + + QMediaTimeRange availablePlaybackRanges() const; + + QMap<QByteArray ,QVariant> tags() const { return m_tags; } + QMap<QtMultimediaKit::MetaData,QVariant> streamProperties(int streamNumber) const { return m_streamProperties[streamNumber]; } + int streamCount() const { return m_streamProperties.count(); } + QMediaStreamsControl::StreamType streamType(int streamNumber) { return m_streamTypes.value(streamNumber, QMediaStreamsControl::UnknownStream); } + + int activeStream(QMediaStreamsControl::StreamType streamType) const; + void setActiveStream(QMediaStreamsControl::StreamType streamType, int streamNumber); + + bool processSyncMessage(const QGstreamerMessage &message); + +#if defined(HAVE_GST_APPSRC) + QGstAppSrc *appsrc() const { return m_appSrc; } + static void configureAppSrcElement(GObject*, GObject*, GParamSpec*,QGstreamerPlayerSession* _this); +#endif + +public slots: + void loadFromUri(const QNetworkRequest &url); + void loadFromStream(const QNetworkRequest &url, QIODevice *stream); + bool play(); + bool pause(); + void stop(); + + bool seek(qint64 pos); + + void setVolume(int volume); + void setMuted(bool muted); + +signals: + void durationChanged(qint64 duration); + void positionChanged(qint64 position); + void stateChanged(QMediaPlayer::State state); + void volumeChanged(int volume); + void mutedStateChanged(bool muted); + void audioAvailableChanged(bool audioAvailable); + void videoAvailableChanged(bool videoAvailable); + void bufferingChanged(bool buffering); + void bufferingProgressChanged(int percentFilled); + void playbackFinished(); + void tagsChanged(); + void streamsChanged(); + void seekableChanged(bool); + void error(int error, const QString &errorString); + void invalidMedia(); + void playbackRateChanged(qreal); + +private slots: + void busMessage(const QGstreamerMessage &message); + void getStreamsInfo(); + void setSeekable(bool); + void finishVideoOutputChange(); + void updateVideoRenderer(); + void updateVideoResolutionTag(); + void updateVolume(); + void updateMuted(); + void updateDuration(); + +private: + static void playbinNotifySource(GObject *o, GParamSpec *p, gpointer d); + static void handleVolumeChange(GObject *o, GParamSpec *p, gpointer d); + static void handleMutedChange(GObject *o, GParamSpec *p, gpointer d); + static void insertColorSpaceElement(GstElement *element, gpointer data); + static void handleElementAdded(GstBin *bin, GstElement *element, QGstreamerPlayerSession *session); + void processInvalidMedia(QMediaPlayer::Error errorCode, const QString& errorString); + + QNetworkRequest m_request; + QMediaPlayer::State m_state; + QMediaPlayer::State m_pendingState; + QGstreamerBusHelper* m_busHelper; + GstElement* m_playbin; + bool m_usePlaybin2; + + GstElement* m_videoOutputBin; + GstElement* m_videoIdentity; + GstElement* m_colorSpace; + bool m_usingColorspaceElement; + GstElement* m_videoSink; + GstElement* m_pendingVideoSink; + GstElement* m_nullVideoSink; + + GstBus* m_bus; + QObject *m_videoOutput; + QGstreamerVideoRendererInterface *m_renderer; + + bool m_haveQueueElement; + +#if defined(HAVE_GST_APPSRC) + QGstAppSrc *m_appSrc; +#endif + + QMap<QByteArray, QVariant> m_tags; + QList< QMap<QtMultimediaKit::MetaData,QVariant> > m_streamProperties; + QList<QMediaStreamsControl::StreamType> m_streamTypes; + QMap<QMediaStreamsControl::StreamType, int> m_playbin2StreamOffset; + + + int m_volume; + qreal m_playbackRate; + bool m_muted; + bool m_audioAvailable; + bool m_videoAvailable; + bool m_seekable; + + mutable qint64 m_lastPosition; + qint64 m_duration; + int m_durationQueries; + + enum SourceType + { + UnknownSrc, + SoupHTTPSrc, + UDPSrc, + MMSSrc + }; + SourceType m_sourceType; + bool m_everPlayed; +}; + +#endif // QGSTREAMERPLAYERSESSION_H diff --git a/src/plugins/gstreamer/mediaplayer/qgstreamerstreamscontrol.cpp b/src/plugins/gstreamer/mediaplayer/qgstreamerstreamscontrol.cpp new file mode 100644 index 000000000..0ab93022b --- /dev/null +++ b/src/plugins/gstreamer/mediaplayer/qgstreamerstreamscontrol.cpp @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgstreamerstreamscontrol.h" +#include "qgstreamerplayersession.h" + +QGstreamerStreamsControl::QGstreamerStreamsControl(QGstreamerPlayerSession *session, QObject *parent) + :QMediaStreamsControl(parent), m_session(session) +{ + connect(m_session, SIGNAL(streamsChanged()), SIGNAL(streamsChanged())); +} + +QGstreamerStreamsControl::~QGstreamerStreamsControl() +{ +} + +int QGstreamerStreamsControl::streamCount() +{ + return m_session->streamCount(); +} + +QMediaStreamsControl::StreamType QGstreamerStreamsControl::streamType(int streamNumber) +{ + return m_session->streamType(streamNumber); +} + +QVariant QGstreamerStreamsControl::metaData(int streamNumber, QtMultimediaKit::MetaData key) +{ + return m_session->streamProperties(streamNumber).value(key); +} + +bool QGstreamerStreamsControl::isActive(int streamNumber) +{ + return streamNumber != -1 && streamNumber == m_session->activeStream(streamType(streamNumber)); +} + +void QGstreamerStreamsControl::setActive(int streamNumber, bool state) +{ + QMediaStreamsControl::StreamType type = m_session->streamType(streamNumber); + if (type == QMediaStreamsControl::UnknownStream) + return; + + if (state) + m_session->setActiveStream(type, streamNumber); + else { + //only one active stream of certain type is supported + if (m_session->activeStream(type) == streamNumber) + m_session->setActiveStream(type, -1); + } +} + diff --git a/src/plugins/gstreamer/mediaplayer/qgstreamerstreamscontrol.h b/src/plugins/gstreamer/mediaplayer/qgstreamerstreamscontrol.h new file mode 100644 index 000000000..1213455b9 --- /dev/null +++ b/src/plugins/gstreamer/mediaplayer/qgstreamerstreamscontrol.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGSTREAMERSTREAMSCONTROL_H +#define QGSTREAMERSTREAMSCONTROL_H + +#include <qmediastreamscontrol.h> + +QT_USE_NAMESPACE + +class QGstreamerPlayerSession; + +class QGstreamerStreamsControl : public QMediaStreamsControl +{ + Q_OBJECT +public: + QGstreamerStreamsControl(QGstreamerPlayerSession *session, QObject *parent); + virtual ~QGstreamerStreamsControl(); + + virtual int streamCount(); + virtual StreamType streamType(int streamNumber); + + virtual QVariant metaData(int streamNumber, QtMultimediaKit::MetaData key); + + virtual bool isActive(int streamNumber); + virtual void setActive(int streamNumber, bool state); + +private: + QGstreamerPlayerSession *m_session; +}; + +#endif // QGSTREAMERSTREAMSCONTROL_H + |