summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLars Knoll <lars.knoll@qt.io>2021-02-14 21:54:37 +0100
committerLars Knoll <lars.knoll@qt.io>2021-03-02 14:50:02 +0000
commit6d20a812549f312e7a3f6dcc60a2e8c4221dca80 (patch)
treef5653c6a98cabc61aa7ac3c80d4abe42b64289a3
parenteb98dd37047de88f8eefcae5c3ef651da1a260df (diff)
Start rewriting the gstreamer player backend
Use our QGst* wrappers to simplify the code base. Those classes are significantly easier to use than the Gstreamer C API. The new implementation doesn't use playbin, but does recreate a similar pipeline as playbin uses. This simplifies the Qt integration as we can now deal with a directly defined pipeline and don't have to insert our own elements into the pre-existing pipeline at the right moment. Handling of multiple streams is missing right now, but that had never been implemented in QMediaPlayer before neither. Change-Id: I77a2df246e0ab20b307d0c45645f896bf127d756 Reviewed-by: Doris Verria <doris.verria@qt.io> Reviewed-by: Lars Knoll <lars.knoll@qt.io>
-rw-r--r--examples/multimediawidgets/player/main.cpp2
-rw-r--r--src/multimedia/platform/gstreamer/common/qgst_p.h25
-rw-r--r--src/multimedia/platform/gstreamer/common/qgstappsrc.cpp24
-rw-r--r--src/multimedia/platform/gstreamer/common/qgstappsrc_p.h4
-rw-r--r--src/multimedia/platform/gstreamer/common/qgstreamermediaplayer.cpp836
-rw-r--r--src/multimedia/platform/gstreamer/common/qgstreamermediaplayer_p.h76
-rw-r--r--src/multimedia/platform/gstreamer/common/qgstreamermessage_p.h6
7 files changed, 613 insertions, 360 deletions
diff --git a/examples/multimediawidgets/player/main.cpp b/examples/multimediawidgets/player/main.cpp
index 4e2b9b6b3..cc4418a1d 100644
--- a/examples/multimediawidgets/player/main.cpp
+++ b/examples/multimediawidgets/player/main.cpp
@@ -82,7 +82,7 @@ int main(int argc, char *argv[])
if (!parser.positionalArguments().isEmpty() && player.isPlayerAvailable()) {
QList<QUrl> urls;
for (auto &a: parser.positionalArguments())
- urls.append(QUrl::fromUserInput(a, QDir::currentPath(), QUrl::AssumeLocalFile));
+ urls.append(QUrl::fromUserInput(a, QDir::currentPath()));
player.addToPlaylist(urls);
}
diff --git a/src/multimedia/platform/gstreamer/common/qgst_p.h b/src/multimedia/platform/gstreamer/common/qgst_p.h
index 5e95ebe0c..dab5219b9 100644
--- a/src/multimedia/platform/gstreamer/common/qgst_p.h
+++ b/src/multimedia/platform/gstreamer/common/qgst_p.h
@@ -276,6 +276,8 @@ public:
const char *name() const { return GST_OBJECT_NAME(m_object); }
};
+class QGstElement;
+
class QGstPad : public QGstObject
{
public:
@@ -292,6 +294,9 @@ public:
bool isLinked() const { return gst_pad_is_linked(pad()); }
bool link(const QGstPad &sink) const { return gst_pad_link(pad(), sink.pad()) == GST_PAD_LINK_OK; }
+ bool unlink(const QGstPad &sink) const { return gst_pad_unlink(pad(), sink.pad()); }
+ QGstPad peer() const { return QGstPad(gst_pad_get_peer(pad()), HasRef); }
+ inline QGstElement parent() const;
GstPad *pad() const { return GST_PAD_CAST(object()); }
@@ -332,9 +337,12 @@ public:
{ return gst_element_link_many(element(), n1.element(), n2.element(), n3.element(), nullptr); }
bool link(const QGstElement &n1, const QGstElement &n2, const QGstElement &n3, const QGstElement &n4)
{ return gst_element_link_many(element(), n1.element(), n2.element(), n3.element(), n4.element(), nullptr); }
+ bool link(const QGstElement &n1, const QGstElement &n2, const QGstElement &n3, const QGstElement &n4, const QGstElement &n5)
+ { return gst_element_link_many(element(), n1.element(), n2.element(), n3.element(), n4.element(), n5.element(), nullptr); }
QGstPad staticPad(const char *name) { return QGstPad(gst_element_get_static_pad(element(), name), HasRef); }
QGstPad getRequestPad(const char *name) { return QGstPad(gst_element_get_request_pad(element(), name), HasRef); }
+ void releaseRequestPad(const QGstPad &pad) { return gst_element_release_request_pad(element(), pad.pad()); }
GstState state() const
{
@@ -384,10 +392,25 @@ public:
g_signal_connect (element(), "pad-added", G_CALLBACK(Impl::callback), instance);
}
+ template<auto Member, typename T>
+ void onPadRemoved(T *instance) {
+ struct Impl {
+ static void callback(GstElement *e, GstPad *pad, gpointer userData) {
+ (static_cast<T *>(userData)->*Member)(QGstElement(e, NeedsRef), QGstPad(pad, NeedsRef));
+ };
+ };
+
+ g_signal_connect (element(), "pad-removed", G_CALLBACK(Impl::callback), instance);
+ }
GstElement *element() const { return GST_ELEMENT_CAST(m_object); }
};
+inline QGstElement QGstPad::parent() const
+{
+ return QGstElement(gst_pad_get_parent_element(pad()), HasRef);
+}
+
class QGstBin : public QGstElement
{
public:
@@ -412,6 +435,8 @@ public:
{ gst_bin_add_many(bin(), e1.element(), e2.element(), e3.element(), e4.element(), nullptr); }
void add(const QGstElement &e1, const QGstElement &e2, const QGstElement &e3, const QGstElement &e4, const QGstElement &e5)
{ gst_bin_add_many(bin(), e1.element(), e2.element(), e3.element(), e4.element(), e5.element(), nullptr); }
+ void add(const QGstElement &e1, const QGstElement &e2, const QGstElement &e3, const QGstElement &e4, const QGstElement &e5, const QGstElement &e6)
+ { gst_bin_add_many(bin(), e1.element(), e2.element(), e3.element(), e4.element(), e5.element(), e6.element(), nullptr); }
void remove(const QGstElement &element)
{ gst_bin_remove(bin(), element.element()); }
diff --git a/src/multimedia/platform/gstreamer/common/qgstappsrc.cpp b/src/multimedia/platform/gstreamer/common/qgstappsrc.cpp
index 9ba6fc09b..834a29c61 100644
--- a/src/multimedia/platform/gstreamer/common/qgstappsrc.cpp
+++ b/src/multimedia/platform/gstreamer/common/qgstappsrc.cpp
@@ -41,6 +41,10 @@
#include "qgstappsrc_p.h"
#include "qgstutils_p.h"
+#include "qnetworkreply.h"
+#include "qloggingcategory.h"
+
+Q_LOGGING_CATEGORY(qLcAppSrc, "qt.multimedia.appsrc")
QGstAppSrc::QGstAppSrc(QObject *parent)
: QObject(parent)
@@ -84,10 +88,12 @@ bool QGstAppSrc::setup(GstElement* appsrc)
g_object_set(m_appSrc, "caps", caps, nullptr);
g_object_set(m_appSrc, "format", GST_FORMAT_TIME, nullptr);
} else {
- qWarning() << "QGstAppSrc: Invalid caps";
+ qCWarning(qLcAppSrc) << "Invalid caps";
}
}
+ m_networkReply = qobject_cast<QNetworkReply *>(m_stream);
+
return true;
}
@@ -131,6 +137,7 @@ GstAppSrc *QGstAppSrc::element()
void QGstAppSrc::onDataReady()
{
+ qCDebug(qLcAppSrc) << "onDataReady" << m_stream->bytesAvailable() << m_stream->size();
if (!m_enoughData) {
m_dataRequested = true;
pushDataToAppSrc();
@@ -147,10 +154,11 @@ void QGstAppSrc::streamDestroyed()
void QGstAppSrc::pushDataToAppSrc()
{
+ qCDebug(qLcAppSrc) << "pushData" << m_stream->bytesAvailable();
if ((!isStreamValid() && !m_buffer) || !m_appSrc)
return;
- if (m_stream->atEnd()) {
+ if (m_stream->atEnd() && (!m_networkReply || !m_networkReply->isRunning())) {
sendEOS();
return;
}
@@ -176,15 +184,20 @@ void QGstAppSrc::pushDataToAppSrc()
gst_buffer_map(buffer, &mapInfo, GST_MAP_WRITE);
void* bufferData = mapInfo.data;
- buffer->offset = m_stream->pos();
+ if (m_sequential)
+ buffer->offset = bytesReadSoFar;
+ else
+ buffer->offset = m_stream->pos();
qint64 bytesRead;
if (m_buffer)
bytesRead = m_buffer->read((char*)bufferData, size);
else
bytesRead = m_stream->read((char*)bufferData, size);
buffer->offset_end = buffer->offset + bytesRead - 1;
+ bytesReadSoFar += bytesRead;
gst_buffer_unmap(buffer, &mapInfo);
+ qCDebug(qLcAppSrc) << "pushing bytes into gstreamer" << m_stream->pos() << buffer->offset << bytesRead;
if (bytesRead > 0) {
m_dataRequested = false;
@@ -198,7 +211,7 @@ void QGstAppSrc::pushDataToAppSrc()
}
}
- if (m_stream->atEnd())
+ if (m_stream->atEnd() && (!m_networkReply || !m_networkReply->isRunning()))
sendEOS();
}
@@ -229,6 +242,7 @@ gboolean QGstAppSrc::on_seek_data(GstAppSrc *element, guint64 arg0, gpointer use
void QGstAppSrc::on_enough_data(GstAppSrc *element, gpointer userdata)
{
+ qCDebug(qLcAppSrc) << "on_enough_data";
Q_UNUSED(element);
QGstAppSrc *self = static_cast<QGstAppSrc*>(userdata);
if (self)
@@ -237,6 +251,7 @@ void QGstAppSrc::on_enough_data(GstAppSrc *element, gpointer userdata)
void QGstAppSrc::on_need_data(GstAppSrc *element, guint arg0, gpointer userdata)
{
+ qCDebug(qLcAppSrc) << "on_need_data requesting bytes" << arg0;
Q_UNUSED(element);
QGstAppSrc *self = static_cast<QGstAppSrc*>(userdata);
if (self) {
@@ -254,6 +269,7 @@ void QGstAppSrc::destroy_notify(gpointer data)
void QGstAppSrc::sendEOS()
{
+ qCDebug(qLcAppSrc) << "sending EOS";
if (!m_appSrc)
return;
diff --git a/src/multimedia/platform/gstreamer/common/qgstappsrc_p.h b/src/multimedia/platform/gstreamer/common/qgstappsrc_p.h
index 29603eb98..3350c8a45 100644
--- a/src/multimedia/platform/gstreamer/common/qgstappsrc_p.h
+++ b/src/multimedia/platform/gstreamer/common/qgstappsrc_p.h
@@ -63,6 +63,8 @@
QT_BEGIN_NAMESPACE
+class QNetworkReply;
+
class Q_MULTIMEDIA_EXPORT QGstAppSrc : public QObject
{
Q_OBJECT
@@ -119,6 +121,7 @@ private:
void sendEOS();
QIODevice *m_stream = nullptr;
+ QNetworkReply *m_networkReply = nullptr;
QRingBuffer *m_buffer = nullptr;
QAudioFormat m_format;
@@ -127,6 +130,7 @@ private:
GstAppStreamType m_streamType = GST_APP_STREAM_TYPE_RANDOM_ACCESS;
GstAppSrcCallbacks m_callbacks;
qint64 m_maxBytes = 0;
+ qint64 bytesReadSoFar = 0;
unsigned int m_dataRequestSize = ~0;
bool m_dataRequested = false;
bool m_enoughData = false;
diff --git a/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer.cpp b/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer.cpp
index 2a4fb269f..2e7df3c91 100644
--- a/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer.cpp
+++ b/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer.cpp
@@ -40,59 +40,82 @@
#include <private/qgstreamermediaplayer_p.h>
#include <private/qgstreamerplayersession_p.h>
#include <private/qgstreamerstreamscontrol_p.h>
+#include <private/qgstreamervideorenderer_p.h>
+#include <private/qgstreamerbushelper_p.h>
+#include <private/qgstreamermetadata_p.h>
+#include <private/qaudiodeviceinfo_gstreamer_p.h>
#include <qaudiodeviceinfo.h>
#include <QtCore/qdir.h>
#include <QtCore/qsocketnotifier.h>
#include <QtCore/qurl.h>
#include <QtCore/qdebug.h>
+#include <QtCore/qloggingcategory.h>
+#include <QtNetwork/qnetworkaccessmanager.h>
+#include <QtNetwork/qnetworkreply.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
-//#define DEBUG_PLAYBIN
+Q_LOGGING_CATEGORY(qLcMediaPlayer, "qt.multimedia.player")
QT_BEGIN_NAMESPACE
QGstreamerMediaPlayer::QGstreamerMediaPlayer(QObject *parent)
: QPlatformMediaPlayer(parent)
{
- m_session = new QGstreamerPlayerSession(this);
- connect(m_session, &QGstreamerPlayerSession::positionChanged, this, &QGstreamerMediaPlayer::positionChanged);
- connect(m_session, &QGstreamerPlayerSession::durationChanged, this, &QGstreamerMediaPlayer::durationChanged);
- connect(m_session, &QGstreamerPlayerSession::mutedStateChanged, this, &QGstreamerMediaPlayer::mutedChanged);
- connect(m_session, &QGstreamerPlayerSession::volumeChanged, this, &QGstreamerMediaPlayer::volumeChanged);
- connect(m_session, &QGstreamerPlayerSession::stateChanged, this, &QGstreamerMediaPlayer::updateSessionState);
- connect(m_session, &QGstreamerPlayerSession::bufferingProgressChanged, this, &QGstreamerMediaPlayer::setBufferProgress);
- connect(m_session, &QGstreamerPlayerSession::playbackFinished, this, &QGstreamerMediaPlayer::processEOS);
- connect(m_session, &QGstreamerPlayerSession::audioAvailableChanged, this, &QGstreamerMediaPlayer::audioAvailableChanged);
- connect(m_session, &QGstreamerPlayerSession::videoAvailableChanged, this, &QGstreamerMediaPlayer::videoAvailableChanged);
- connect(m_session, &QGstreamerPlayerSession::seekableChanged, this, &QGstreamerMediaPlayer::seekableChanged);
- connect(m_session, &QGstreamerPlayerSession::error, this, &QGstreamerMediaPlayer::error);
- connect(m_session, &QGstreamerPlayerSession::invalidMedia, this, &QGstreamerMediaPlayer::handleInvalidMedia);
- connect(m_session, &QGstreamerPlayerSession::playbackRateChanged, this, &QGstreamerMediaPlayer::playbackRateChanged);
- connect(m_session, &QGstreamerPlayerSession::metaDataChanged, this, &QGstreamerMediaPlayer::metaDataChanged);
-}
-
-QGstreamerMediaPlayer::~QGstreamerMediaPlayer() = default;
+ audioInputSelector = QGstElement("input-selector", "audioInputSelector");
+ audioQueue = QGstElement("queue", "audioQueue");
+ audioConvert = QGstElement("audioconvert", "audioConvert");
+ audioResample = QGstElement("audioresample", "audioResample");
+ audioVolume = QGstElement("volume", "volume");
+ audioSink = QGstElement("autoaudiosink", "autoAudioSink");
+ playerPipeline.add(audioInputSelector, audioQueue, audioConvert, audioResample, audioVolume, audioSink);
+ audioInputSelector.link(audioQueue, audioConvert, audioResample, audioVolume, audioSink);
+
+ videoInputSelector = QGstElement("input-selector", "videoInputSelector");
+ videoQueue = QGstElement("queue", "videoQueue");
+ videoConvert = QGstElement("videoconvert", "videoConvert");
+ videoScale = QGstElement("videoscale", "videoScale");
+ playerPipeline.add(videoInputSelector, videoQueue, videoConvert, videoScale);
+ videoInputSelector.link(videoQueue, videoConvert, videoScale);
+
+ subTitleInputSelector = QGstElement("input-selector", "subTitleInputSelector");
+ playerPipeline.add(subTitleInputSelector);
+
+ playerPipeline.setState(GST_STATE_NULL);
+
+ busHelper = new QGstreamerBusHelper(playerPipeline.bus().bus(), this);
+ qRegisterMetaType<QGstreamerMessage>();
+ connect(busHelper, &QGstreamerBusHelper::message, this, &QGstreamerMediaPlayer::busMessage);
+}
+
+QGstreamerMediaPlayer::~QGstreamerMediaPlayer()
+{
+ playerPipeline.setStateSync(GST_STATE_NULL);
+ if (ownStream)
+ delete m_stream;
+ if (networkManager)
+ delete networkManager;
+}
qint64 QGstreamerMediaPlayer::position() const
{
- if (m_mediaStatus == QMediaPlayer::EndOfMedia)
- return m_session->duration();
+ if (playerPipeline.isNull())
+ return 0;
- return m_pendingSeekPosition != -1 ? m_pendingSeekPosition : m_session->position();
+ return playerPipeline.position()/1e6;
}
qint64 QGstreamerMediaPlayer::duration() const
{
- return m_session->duration();
+ return m_duration;
}
QMediaPlayer::State QGstreamerMediaPlayer::state() const
{
- return m_currentState;
+ return m_state;
}
QMediaPlayer::MediaStatus QGstreamerMediaPlayer::mediaStatus() const
@@ -102,447 +125,596 @@ QMediaPlayer::MediaStatus QGstreamerMediaPlayer::mediaStatus() const
int QGstreamerMediaPlayer::bufferStatus() const
{
- if (m_bufferProgress == -1)
- return m_session->state() == QMediaPlayer::StoppedState ? 0 : 100;
-
return m_bufferProgress;
}
int QGstreamerMediaPlayer::volume() const
{
- return m_session->volume();
+ return m_volume;
}
bool QGstreamerMediaPlayer::isMuted() const
{
- return m_session->isMuted();
+ return m_muted;
}
bool QGstreamerMediaPlayer::isSeekable() const
{
- return m_session->isSeekable();
+ return true;
}
QMediaTimeRange QGstreamerMediaPlayer::availablePlaybackRanges() const
{
- return m_session->availablePlaybackRanges();
+ return QMediaTimeRange();
}
qreal QGstreamerMediaPlayer::playbackRate() const
{
- return m_session->playbackRate();
+ return m_playbackRate;
}
void QGstreamerMediaPlayer::setPlaybackRate(qreal rate)
{
- m_session->setPlaybackRate(rate);
+ if (rate == m_playbackRate)
+ return;
+ m_playbackRate = rate;
+ playerPipeline.seek(playerPipeline.position(), m_playbackRate);
+ emit playbackRateChanged(rate);
}
void QGstreamerMediaPlayer::setPosition(qint64 pos)
{
-#ifdef DEBUG_PLAYBIN
- qDebug() << Q_FUNC_INFO << pos/1000.0;
-#endif
-
- pushState();
-
- if (m_mediaStatus == QMediaPlayer::EndOfMedia) {
- m_mediaStatus = QMediaPlayer::LoadedMedia;
- }
-
- if (m_currentState == QMediaPlayer::StoppedState) {
- m_pendingSeekPosition = pos;
- emit positionChanged(m_pendingSeekPosition);
- } else if (m_session->isSeekable()) {
- m_session->showPrerollFrames(true);
- m_session->seek(pos);
- m_pendingSeekPosition = -1;
- } else if (m_session->state() == QMediaPlayer::StoppedState) {
- m_pendingSeekPosition = pos;
- emit positionChanged(m_pendingSeekPosition);
- } else if (m_pendingSeekPosition != -1) {
- m_pendingSeekPosition = -1;
- emit positionChanged(m_pendingSeekPosition);
- }
-
- popAndNotifyState();
+ qCDebug(qLcMediaPlayer) << Q_FUNC_INFO << pos/1000.0;
+ playerPipeline.seek(pos*1e6, m_playbackRate);
}
void QGstreamerMediaPlayer::play()
{
-#ifdef DEBUG_PLAYBIN
- qDebug() << Q_FUNC_INFO;
-#endif
- //m_userRequestedState is needed to know that we need to resume playback when resource-policy
- //regranted the resources after lost, since m_currentState will become paused when resources are
- //lost.
- m_userRequestedState = QMediaPlayer::PlayingState;
- playOrPause(QMediaPlayer::PlayingState);
+ int ret = playerPipeline.setState(GST_STATE_PLAYING);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ qCDebug(qLcMediaPlayer) << "Unable to set the pipeline to the playing state.";
+ m_state = QMediaPlayer::PlayingState;
+ emit stateChanged(m_state);
}
void QGstreamerMediaPlayer::pause()
{
-#ifdef DEBUG_PLAYBIN
- qDebug() << Q_FUNC_INFO;
-#endif
- m_userRequestedState = QMediaPlayer::PausedState;
- // If the playback has not been started yet but pause is requested.
- // Seek to the beginning to show first frame.
- if (m_pendingSeekPosition == -1 && m_session->position() == 0)
- m_pendingSeekPosition = 0;
+ int ret = playerPipeline.setState(GST_STATE_PAUSED);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ qCDebug(qLcMediaPlayer) << "Unable to set the pipeline to the paused state.";
+ m_state = QMediaPlayer::PausedState;
+ emit stateChanged(m_state);
+}
- playOrPause(QMediaPlayer::PausedState);
+void QGstreamerMediaPlayer::stop()
+{
+ int ret = playerPipeline.setState(GST_STATE_NULL);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ qCDebug(qLcMediaPlayer) << "Unable to set the pipeline to the stopped state.";
+ m_state = QMediaPlayer::StoppedState;
+ emit stateChanged(m_state);
}
-void QGstreamerMediaPlayer::playOrPause(QMediaPlayer::State newState)
+void QGstreamerMediaPlayer::setVolume(int vol)
{
- if (m_mediaStatus == QMediaPlayer::NoMedia)
+ if (vol == m_volume)
return;
+ m_volume = vol;
+ audioVolume.set("volume", vol/100.);
+ emit volumeChanged(m_volume);
+}
- pushState();
-
- if (m_setMediaPending) {
- m_mediaStatus = QMediaPlayer::LoadingMedia;
- setMedia(m_currentResource, m_stream);
- }
-
- if (m_mediaStatus == QMediaPlayer::EndOfMedia && m_pendingSeekPosition == -1) {
- m_pendingSeekPosition = 0;
- }
-
- // show prerolled frame if switching from stopped state
- if (m_pendingSeekPosition == -1) {
- m_session->showPrerollFrames(true);
- } else if (m_session->state() == QMediaPlayer::StoppedState) {
- // Don't evaluate the next two conditions.
- } else if (m_session->isSeekable()) {
- m_session->pause();
- m_session->showPrerollFrames(true);
- m_session->seek(m_pendingSeekPosition);
- m_pendingSeekPosition = -1;
- } else {
- m_pendingSeekPosition = -1;
- }
+void QGstreamerMediaPlayer::setMuted(bool muted)
+{
+ if (muted == m_muted)
+ return;
+ m_muted = muted;
+ audioVolume.set("mute", muted);
+ emit mutedChanged(muted);
+}
- bool ok = false;
+void QGstreamerMediaPlayer::busMessage(const QGstreamerMessage &message)
+{
+ if (message.isNull())
+ return;
- //To prevent displaying the first video frame when playback is resumed
- //the pipeline is paused instead of playing, seeked to requested position,
- //and after seeking is finished (position updated) playback is restarted
- //with show-preroll-frame enabled.
- if (newState == QMediaPlayer::PlayingState && m_pendingSeekPosition == -1)
- ok = m_session->play();
- else
- ok = m_session->pause();
+ qCDebug(qLcMediaPlayer) << "received bus message from" << message.source().name() << message.type();
+ if (message.source() != playerPipeline && message.source() != decoder)
+ return;
- if (!ok)
- newState = QMediaPlayer::StoppedState;
+ GstMessage* gm = message.rawMessage();
+ //tag message comes from elements inside playbin, not from playbin itself
+ switch (message.type()) {
+ case GST_MESSAGE_TAG: {
+ GstTagList *tag_list;
+ gst_message_parse_tag(gm, &tag_list);
- if (m_mediaStatus == QMediaPlayer::InvalidMedia)
- m_mediaStatus = QMediaPlayer::LoadingMedia;
+ m_metaData = QGstreamerMetaData::fromGstTagList(tag_list);
- m_currentState = newState;
+ gst_tag_list_free(tag_list);
- 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;
+ emit metaDataChanged();
+ break;
}
+ case GST_MESSAGE_ASYNC_DONE:
+ case GST_MESSAGE_DURATION_CHANGED: {
+ qint64 d = playerPipeline.duration()/1e6;
+ qCDebug(qLcMediaPlayer) << " duration changed message" << d;
+ if (d != m_duration) {
+ m_duration = d;
+ emit durationChanged(duration());
+ }
+ return;
+ }
+ case GST_MESSAGE_EOS:
+ stop();
+ // anything to do here?
+ break;
+ case GST_MESSAGE_BUFFERING: {
+ int progress = 0;
+ gst_message_parse_buffering(gm, &progress);
+ m_bufferProgress = progress;
+ emit bufferStatusChanged(progress);
+ break;
+ }
+ case GST_MESSAGE_STATE_CHANGED: {
+ GstState oldState;
+ GstState newState;
+ GstState pending;
- popAndNotifyState();
-
- emit positionChanged(position());
-}
+ gst_message_parse_state_changed(gm, &oldState, &newState, &pending);
-void QGstreamerMediaPlayer::stop()
-{
#ifdef DEBUG_PLAYBIN
- qDebug() << Q_FUNC_INFO;
+ static QStringList states = {
+ QStringLiteral("GST_STATE_VOID_PENDING"), QStringLiteral("GST_STATE_NULL"),
+ QStringLiteral("GST_STATE_READY"), QStringLiteral("GST_STATE_PAUSED"),
+ QStringLiteral("GST_STATE_PLAYING") };
+
+ qDebug() << QStringLiteral("state changed: old: %1 new: %2 pending: %3") \
+ .arg(states[oldState]) \
+ .arg(states[newState]) \
+ .arg(states[pending]);
#endif
- m_userRequestedState = QMediaPlayer::StoppedState;
-
- pushState();
-
- if (m_currentState != QMediaPlayer::StoppedState) {
- m_currentState = QMediaPlayer::StoppedState;
- m_session->showPrerollFrames(false); // stop showing prerolled frames in stop state
- // Since gst is not going to send GST_STATE_PAUSED
- // when pipeline is already paused,
- // needs to update media status directly.
- if (m_session->state() == QMediaPlayer::PausedState)
- updateMediaStatus();
- else
- m_session->pause();
- if (m_mediaStatus != QMediaPlayer::EndOfMedia) {
- m_pendingSeekPosition = 0;
- emit positionChanged(position());
+ switch (newState) {
+ case GST_STATE_VOID_PENDING:
+ case GST_STATE_NULL:
+ 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) {
+// getStreamsInfo();
+// updateVideoResolutionTag();
+
+ if (!qFuzzyCompare(m_playbackRate, qreal(1.0)))
+ playerPipeline.seek(playerPipeline.position(), m_playbackRate);
+ }
+
+ if (m_state != prevState)
+ emit stateChanged(m_state);
+
+ break;
+ }
+ case GST_STATE_PLAYING:
+ if (m_state != QMediaPlayer::PlayingState)
+ emit stateChanged(m_state = QMediaPlayer::PlayingState);
+ break;
+ }
+ 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)
+ emit error(QMediaPlayer::FormatError, tr("Cannot play stream of type: <unknown>"));
+ else
+ emit error(QMediaPlayer::ResourceError, 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);
+ qCWarning(qLcMediaPlayer) << "Warning:" << QString::fromUtf8(err->message);
+ g_error_free (err);
+ g_free (debug);
+ break;
+ }
+ case GST_MESSAGE_INFO: {
+ if (qLcMediaPlayer().isDebugEnabled()) {
+ GError *err;
+ gchar *debug;
+ gst_message_parse_info (gm, &err, &debug);
+ qCDebug(qLcMediaPlayer) << "Info:" << QString::fromUtf8(err->message);
+ g_error_free (err);
+ g_free (debug);
}
+ 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;
+ emit positionChanged(position);
+ }
+ default:
+ break;
}
- popAndNotifyState();
+#if 0
+ } 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 = true;
+ }
+ 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 = true;
+ }
+ 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>"));
+ // GStreamer shows warning for HTTP playlists
+ if (err && err->message)
+ 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);
+
+ // Nearly all errors map to ResourceError
+ QMediaPlayer::Error qerror = QMediaPlayer::ResourceError;
+ if (err->domain == GST_STREAM_ERROR
+ && (err->code == GST_STREAM_ERROR_DECRYPT
+ || err->code == GST_STREAM_ERROR_DECRYPT_NOKEY)) {
+ qerror = QMediaPlayer::AccessDeniedError;
+ }
+ processInvalidMedia(qerror, QString::fromUtf8(err->message));
+ if (err && err->message)
+ qWarning() << "Error:" << QString::fromUtf8(err->message);
+
+ g_error_free(err);
+ g_free(debug);
+ }
+ }
+#endif
}
-void QGstreamerMediaPlayer::setVolume(int volume)
+QUrl QGstreamerMediaPlayer::media() const
{
- m_session->setVolume(volume);
+ return m_url;
}
-void QGstreamerMediaPlayer::setMuted(bool muted)
+const QIODevice *QGstreamerMediaPlayer::mediaStream() const
{
- m_session->setMuted(muted);
+ return m_stream;
}
-QUrl QGstreamerMediaPlayer::media() const
+void QGstreamerMediaPlayer::decoderPadAdded(const QGstElement &src, const QGstPad &pad)
{
- return m_currentResource;
+ if (src != decoder)
+ return;
+
+ auto caps = pad.currentCaps();
+ auto type = caps.at(0).name();
+ qCDebug(qLcMediaPlayer) << "Received new pad" << pad.name() << "from" << src.name() << "type" << type;
+
+ QGstElement selector;
+ if (type.startsWith("video/x-raw")) {
+ selector = videoInputSelector;
+ } else if (type.startsWith("audio/x-raw")) {
+ selector = audioInputSelector;
+ } else if (type.startsWith("text/")) {
+ selector = subTitleInputSelector;
+ } else {
+ qCWarning(qLcMediaPlayer) << "Failed to add fake sink to unknown pad." << pad.name() << type;
+ return;
+ }
+ QGstPad sinkPad = selector.getRequestPad("sink_%u");
+ if (!pad.link(sinkPad))
+ qCWarning(qLcMediaPlayer) << "Failed to link video pads.";
+ decoderOutputMap.insert(pad.name(), sinkPad);
}
-const QIODevice *QGstreamerMediaPlayer::mediaStream() const
+void QGstreamerMediaPlayer::decoderPadRemoved(const QGstElement &src, const QGstPad &pad)
{
- return m_stream;
+ qCDebug(qLcMediaPlayer) << "Removed pad" << pad.name() << "from" << src.name();
+ QGstPad peer = decoderOutputMap.value(pad.name());
+ if (peer.isNull())
+ return;
+ QGstElement peerParent = peer.parent();
+ qCDebug(qLcMediaPlayer) << " was linked to pad" << peer.name() << "from" << peerParent.name();
+ peerParent.releaseRequestPad(peer);
}
void QGstreamerMediaPlayer::setMedia(const QUrl &content, QIODevice *stream)
{
-#ifdef DEBUG_PLAYBIN
- qDebug() << Q_FUNC_INFO;
-#endif
-
- pushState();
-
- m_currentState = QMediaPlayer::StoppedState;
- QUrl oldMedia = m_currentResource;
- m_pendingSeekPosition = -1;
- m_session->showPrerollFrames(false); // do not show prerolled frames until pause() or play() explicitly called
- m_setMediaPending = false;
-
- m_session->stop();
-
- bool userStreamValid = false;
+ qCDebug(qLcMediaPlayer) << Q_FUNC_INFO << "setting location to" << content;
- if (m_bufferProgress != -1) {
- m_bufferProgress = -1;
- emit bufferStatusChanged(0);
- }
+ int ret = playerPipeline.setStateSync(GST_STATE_NULL);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ qCDebug(qLcMediaPlayer) << "Unable to set the pipeline to the stopped state.";
- m_currentResource = content;
+ m_url = content;
m_stream = stream;
- QNetworkRequest request(content);
-
- if (m_stream)
- userStreamValid = stream->isOpen() && m_stream->isReadable();
+ if (!src.isNull())
+ playerPipeline.remove(src);
+ if (!decoder.isNull())
+ playerPipeline.remove(decoder);
+ src = QGstElement();
+ decoder = QGstElement();
-#if !QT_CONFIG(gstreamer_app)
- 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"));
- popAndNotifyState();
- return;
- }
- } else
- m_session->loadFromUri(request);
-#endif
-
-#if QT_CONFIG(gstreamer_app)
- if (!request.url().isEmpty() || userStreamValid) {
-#else
- if (!request.url().isEmpty()) {
-#endif
- m_mediaStatus = QMediaPlayer::LoadingMedia;
- m_session->pause();
+ if (!m_appSrc)
+ m_appSrc = new QGstAppSrc(this);
+ src = QGstElement("appsrc", "appsrc");
+ decoder = QGstElement("decodebin", "decoder");
+ playerPipeline.add(src, decoder);
+ src.link(decoder);
+
+ m_appSrc->setStream(m_stream);
+ m_appSrc->setup(src.element());
} else {
- m_mediaStatus = QMediaPlayer::NoMedia;
- setBufferProgress(0);
+ // use uridecodebin
+ decoder = QGstElement("uridecodebin", "uridecoder");
+ playerPipeline.add(decoder);
+
+ decoder.set("uri", content.toEncoded().constData());
+ if (m_bufferProgress != -1) {
+ m_bufferProgress = -1;
+ emit bufferStatusChanged(0);
+ }
+ }
+ decoder.onPadAdded<&QGstreamerMediaPlayer::decoderPadAdded>(this);
+ decoder.onPadRemoved<&QGstreamerMediaPlayer::decoderPadRemoved>(this);
+
+ if (m_state == QMediaPlayer::PausedState) {
+ int ret = playerPipeline.setState(GST_STATE_PAUSED);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ qCDebug(qLcMediaPlayer) << "Unable to set the pipeline to the paused state.";
+ } else if (m_state == QMediaPlayer::PlayingState) {
+ int ret = playerPipeline.setState(GST_STATE_PLAYING);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ qCDebug(qLcMediaPlayer) << "Unable to set the pipeline to the playing state.";
}
emit positionChanged(position());
-
- popAndNotifyState();
}
bool QGstreamerMediaPlayer::setAudioOutput(const QAudioDeviceInfo &info)
{
- m_session->setAudioOutputDevice(info);
+ if (info == m_audioOutput)
+ return true;
+ qCDebug(qLcMediaPlayer) << "setAudioOutput" << info.description() << info.isNull();
+ m_audioOutput = info;
+
+ if (m_state == QMediaPlayer::StoppedState)
+ return changeAudioOutput();
+
+ auto pad = audioVolume.staticPad("src");
+ pad.addProbe<&QGstreamerMediaPlayer::prepareAudioOutputChange>(this, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM);
+
return true;
}
-QAudioDeviceInfo QGstreamerMediaPlayer::audioOutput() const
+bool QGstreamerMediaPlayer::changeAudioOutput()
{
- return m_session->audioOutputDevice();
-}
+ qCDebug(qLcMediaPlayer) << "Changing audio output";
+ QGstElement newSink;
+ auto *deviceInfo = static_cast<const QGStreamerAudioDeviceInfo *>(m_audioOutput.handle());
+ if (deviceInfo && deviceInfo->gstDevice)
+ newSink = QGstElement(gst_device_create_element(deviceInfo->gstDevice , "audiosink"), QGstElement::NeedsRef);
-QMediaMetaData QGstreamerMediaPlayer::metaData() const
-{
- return m_session->metaData();
-}
+ if (newSink.isNull())
+ newSink = QGstElement("autoaudiosink", "audiosink");
-void QGstreamerMediaPlayer::setVideoSurface(QAbstractVideoSurface *surface)
-{
- m_session->setVideoRenderer(surface);
+ playerPipeline.remove(audioSink);
+ audioSink = newSink;
+ playerPipeline.add(audioSink);
+ audioVolume.link(audioSink);
+
+ return true;
}
-QMediaStreamsControl *QGstreamerMediaPlayer::mediaStreams()
+void QGstreamerMediaPlayer::prepareAudioOutputChange(const QGstPad &/*pad*/)
{
- if (!m_streamsControl)
- m_streamsControl = new QGstreamerStreamsControl(m_session, this);
- return m_streamsControl;
+ qCDebug(qLcMediaPlayer) << "Reconfiguring audio output";
+
+ Q_ASSERT(m_state != QMediaPlayer::StoppedState);
+
+ auto state = playerPipeline.state();
+ if (state == GST_STATE_PLAYING)
+ playerPipeline.setStateSync(GST_STATE_PAUSED);
+ audioSink.setStateSync(GST_STATE_NULL);
+ changeAudioOutput();
+ audioSink.setStateSync(GST_STATE_PAUSED);
+ if (state == GST_STATE_PLAYING)
+ playerPipeline.setStateSync(state);
+
+ GST_DEBUG_BIN_TO_DOT_FILE(playerPipeline.bin(), GST_DEBUG_GRAPH_SHOW_ALL, "newAudio.dot");
}
-bool QGstreamerMediaPlayer::isAudioAvailable() const
+QAudioDeviceInfo QGstreamerMediaPlayer::audioOutput() const
{
- return m_session->isAudioAvailable();
+ return m_audioOutput;
}
-bool QGstreamerMediaPlayer::isVideoAvailable() const
+QMediaMetaData QGstreamerMediaPlayer::metaData() const
{
- return m_session->isVideoAvailable();
+// return m_session->metaData();
+ return QMediaMetaData();
}
-void QGstreamerMediaPlayer::updateSessionState(QMediaPlayer::State state)
+void QGstreamerMediaPlayer::updateVideoSink()
{
- pushState();
+ qCDebug(qLcMediaPlayer) << "Video sink has changed, reload video output";
- if (state == QMediaPlayer::StoppedState) {
- m_session->showPrerollFrames(false);
- m_currentState = QMediaPlayer::StoppedState;
- }
+ QGstElement newSink;
+ if (m_videoOutput && m_videoOutput->isReady())
+ newSink = QGstElement(m_videoOutput->videoSink(), QGstObject::NeedsRef);
- if (state == QMediaPlayer::PausedState && m_currentState != QMediaPlayer::StoppedState) {
- if (m_pendingSeekPosition != -1 && m_session->isSeekable()) {
- m_session->showPrerollFrames(true);
- m_session->seek(m_pendingSeekPosition);
- }
- m_pendingSeekPosition = -1;
+ if (newSink.isNull())
+ newSink = QGstElement("fakesink", "fakevideosink");
- if (m_currentState == QMediaPlayer::PlayingState) {
- if (m_bufferProgress == -1 || m_bufferProgress == 100)
- m_session->play();
- }
- }
-
- updateMediaStatus();
-
- popAndNotifyState();
-}
-
-void QGstreamerMediaPlayer::updateMediaStatus()
-{
- //EndOfMedia status should be kept, until reset by pause, play or setMedia
- if (m_mediaStatus == QMediaPlayer::EndOfMedia)
+ if (newSink == videoSink)
return;
- pushState();
- QMediaPlayer::MediaStatus oldStatus = m_mediaStatus;
+ qCDebug(qLcMediaPlayer) << "Reconfiguring video output";
- switch (m_session->state()) {
- case QMediaPlayer::StoppedState:
- if (m_currentResource.isEmpty())
- m_mediaStatus = QMediaPlayer::NoMedia;
- else if (oldStatus != QMediaPlayer::InvalidMedia)
- m_mediaStatus = QMediaPlayer::LoadingMedia;
- break;
+ if (m_state == QMediaPlayer::StoppedState) {
+ qCDebug(qLcMediaPlayer) << "The pipeline has not started yet";
- case QMediaPlayer::PlayingState:
- case QMediaPlayer::PausedState:
- if (m_currentState == QMediaPlayer::StoppedState) {
- m_mediaStatus = QMediaPlayer::LoadedMedia;
- } else {
- if (m_bufferProgress == -1 || m_bufferProgress == 100)
- m_mediaStatus = QMediaPlayer::BufferedMedia;
- else
- m_mediaStatus = QMediaPlayer::StalledMedia;
+ //the pipeline has not started yet
+ playerPipeline.setState(GST_STATE_NULL);
+ if (!videoSink.isNull()) {
+ videoSink.setState(GST_STATE_NULL);
+ playerPipeline.remove(videoSink);
}
- break;
- }
-
- popAndNotifyState();
-}
-
-void QGstreamerMediaPlayer::processEOS()
-{
- pushState();
- m_mediaStatus = QMediaPlayer::EndOfMedia;
- emit positionChanged(position());
- m_session->endOfMediaReset();
+ videoSink = newSink;
+ playerPipeline.add(videoSink);
+ if (!videoScale.link(videoSink))
+ qCWarning(qLcMediaPlayer) << "Linking new video output failed";
+
+ if (g_object_class_find_property(G_OBJECT_GET_CLASS(videoSink.object()), "show-preroll-frame") != nullptr)
+ videoSink.set("show-preroll-frame", true);
+
+// 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;
+// }
- if (m_currentState != QMediaPlayer::StoppedState) {
- m_currentState = QMediaPlayer::StoppedState;
- m_session->showPrerollFrames(false); // stop showing prerolled frames in stop state
+ } 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");
+// this->pad_probe_id = gst_pad_add_probe(srcPad, (GstPadProbeType)(GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_BLOCKING), block_pad_cb, this, nullptr);
+// 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);
+// }
}
-
- popAndNotifyState();
}
-void QGstreamerMediaPlayer::setBufferProgress(int progress)
+void QGstreamerMediaPlayer::setSeekable(bool seekable)
{
- if (m_bufferProgress == progress || m_mediaStatus == QMediaPlayer::NoMedia)
+ qCDebug(qLcMediaPlayer) << Q_FUNC_INFO << seekable;
+ if (seekable == m_seekable)
return;
+ m_seekable = seekable;
+ emit seekableChanged(m_seekable);
+}
+void QGstreamerMediaPlayer::setVideoSurface(QAbstractVideoSurface *surface)
+{
+ if (!m_videoOutput) {
+ m_videoOutput = new QGstreamerVideoRenderer;
#ifdef DEBUG_PLAYBIN
- qDebug() << Q_FUNC_INFO << progress;
+ qDebug() << Q_FUNC_INFO;
#endif
- m_bufferProgress = progress;
-
- if (m_currentState == QMediaPlayer::PlayingState &&
- m_bufferProgress == 100 &&
- m_session->state() != QMediaPlayer::PlayingState)
- m_session->play();
-
- if (!m_session->isLiveSource() && m_bufferProgress < 100 &&
- (m_session->state() == QMediaPlayer::PlayingState ||
- m_session->pendingState() == QMediaPlayer::PlayingState))
- m_session->pause();
-
- updateMediaStatus();
+ connect(m_videoOutput, SIGNAL(sinkChanged()),
+ this, SLOT(updateVideoRenderer()));
+ }
- emit bufferStatusChanged(m_bufferProgress);
+ m_videoOutput->setSurface(surface);
+ updateVideoSink();
}
-void QGstreamerMediaPlayer::handleInvalidMedia()
+QMediaStreamsControl *QGstreamerMediaPlayer::mediaStreams()
{
- pushState();
- m_mediaStatus = QMediaPlayer::InvalidMedia;
- m_currentState = QMediaPlayer::StoppedState;
- m_setMediaPending = true;
- popAndNotifyState();
+// if (!m_streamsControl)
+// m_streamsControl = new QGstreamerStreamsControl(m_session, this);
+ return m_streamsControl;
}
-void QGstreamerMediaPlayer::pushState()
+bool QGstreamerMediaPlayer::isAudioAvailable() const
{
- m_stateStack.push(m_currentState);
- m_mediaStatusStack.push(m_mediaStatus);
+// return m_session->isAudioAvailable();
+ return true;
}
-void QGstreamerMediaPlayer::popAndNotifyState()
+bool QGstreamerMediaPlayer::isVideoAvailable() const
{
- Q_ASSERT(!m_stateStack.isEmpty());
-
- QMediaPlayer::State oldState = m_stateStack.pop();
- QMediaPlayer::MediaStatus oldMediaStatus = m_mediaStatusStack.pop();
-
- if (m_stateStack.isEmpty()) {
- if (m_mediaStatus != oldMediaStatus) {
-#ifdef DEBUG_PLAYBIN
- qDebug() << "Media status changed:" << m_mediaStatus;
-#endif
- emit mediaStatusChanged(m_mediaStatus);
- }
-
- if (m_currentState != oldState) {
-#ifdef DEBUG_PLAYBIN
- qDebug() << "State changed:" << m_currentState;
-#endif
- emit stateChanged(m_currentState);
- }
- }
+// return m_session->isVideoAvailable();
+ return true;
}
QT_END_NAMESPACE
diff --git a/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer_p.h b/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer_p.h
index 48c7f8c37..07746293b 100644
--- a/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer_p.h
+++ b/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer_p.h
@@ -55,12 +55,17 @@
#include <qplatformmediaplayer_p.h>
#include <private/qtmultimediaglobal_p.h>
#include <qurl.h>
-#include <private/qgstutils_p.h>
+#include <private/qgst_p.h>
QT_BEGIN_NAMESPACE
+class QNetworkAccessManager;
class QGstreamerPlayerSession;
class QGstreamerStreamsControl;
+class QGstreamerVideoRenderer;
+class QGstreamerBusHelper;
+class QGstreamerMessage;
+class QGstAppSrc;
class Q_MULTIMEDIA_EXPORT QGstreamerMediaPlayer : public QPlatformMediaPlayer
{
@@ -70,8 +75,6 @@ public:
QGstreamerMediaPlayer(QObject *parent = 0);
~QGstreamerMediaPlayer();
- QGstreamerPlayerSession *session() { return m_session; }
-
QMediaPlayer::State state() const override;
QMediaPlayer::MediaStatus mediaStatus() const override;
@@ -117,34 +120,63 @@ public Q_SLOTS:
void setVolume(int volume) override;
void setMuted(bool muted) override;
-private Q_SLOTS:
- void updateSessionState(QMediaPlayer::State state);
- void updateMediaStatus();
- void processEOS();
- void setBufferProgress(int progress);
-
- void handleInvalidMedia();
+ void busMessage(const QGstreamerMessage& message);
private:
- void playOrPause(QMediaPlayer::State state);
-
- void pushState();
- void popAndNotifyState();
+ friend class QGstreamerStreamsControl;
+ void decoderPadAdded(const QGstElement &src, const QGstPad &pad);
+ void decoderPadRemoved(const QGstElement &src, const QGstPad &pad);
+ void prepareAudioOutputChange(const QGstPad &pad);
+ bool changeAudioOutput();
+ void updateVideoSink();
+ void setSeekable(bool seekable);
- QGstreamerPlayerSession *m_session = nullptr;
QGstreamerStreamsControl *m_streamsControl = nullptr;
+ QMediaMetaData m_metaData;
- QMediaPlayer::State m_userRequestedState = QMediaPlayer::StoppedState;
- QMediaPlayer::State m_currentState = QMediaPlayer::StoppedState;
+ QMediaPlayer::State m_state = QMediaPlayer::StoppedState;
QMediaPlayer::MediaStatus m_mediaStatus = QMediaPlayer::NoMedia;
- QStack<QMediaPlayer::State> m_stateStack;
- QStack<QMediaPlayer::MediaStatus> m_mediaStatusStack;
int m_bufferProgress = -1;
- qint64 m_pendingSeekPosition = -1;
- bool m_setMediaPending = false;
- QUrl m_currentResource;
+ QUrl m_url;
+ QNetworkAccessManager *networkManager = nullptr;
QIODevice *m_stream = nullptr;
+ bool ownStream = false;
+
+ int m_volume = 100.;
+ bool m_muted = false;
+ double m_playbackRate = 1.;
+ bool m_seekable = false;
+ qint64 m_duration = 0;
+
+ QAudioDeviceInfo m_audioOutput;
+ QAbstractVideoSurface *m_videoSurface = nullptr;
+ QGstreamerVideoRenderer *m_videoOutput = nullptr;
+
+ QGstreamerBusHelper *busHelper;
+ QGstAppSrc *m_appSrc;
+
+ // Gst elements
+ QGstPipeline playerPipeline;
+ QGstElement src;
+ QGstElement decoder;
+ QGstElement audioInputSelector;
+ QGstElement videoInputSelector;
+ QGstElement subTitleInputSelector;
+// QGstElement streamSynchronizer;
+
+ QGstElement audioQueue;
+ QGstElement audioConvert;
+ QGstElement audioResample;
+ QGstElement audioVolume;
+ QGstElement audioSink;
+
+ QGstElement videoQueue;
+ QGstElement videoConvert;
+ QGstElement videoScale;
+ QGstElement videoSink;
+
+ QHash<QByteArray, QGstPad> decoderOutputMap;
};
QT_END_NAMESPACE
diff --git a/src/multimedia/platform/gstreamer/common/qgstreamermessage_p.h b/src/multimedia/platform/gstreamer/common/qgstreamermessage_p.h
index e6d9fa08c..b6ba8df26 100644
--- a/src/multimedia/platform/gstreamer/common/qgstreamermessage_p.h
+++ b/src/multimedia/platform/gstreamer/common/qgstreamermessage_p.h
@@ -52,7 +52,7 @@
//
#include <private/qtmultimediaglobal_p.h>
-#include <gst/gst.h>
+#include <private/qgst_p.h>
QT_BEGIN_NAMESPACE
@@ -67,6 +67,10 @@ public:
QGstreamerMessage(QGstreamerMessage const& m);
~QGstreamerMessage();
+ bool isNull() const { return !m_message; }
+ GstMessageType type() const { return GST_MESSAGE_TYPE(m_message); }
+ QGstObject source() const { return QGstObject(GST_MESSAGE_SRC(m_message), QGstObject::NeedsRef); }
+
GstMessage* rawMessage() const;
QGstreamerMessage& operator=(QGstreamerMessage const& rhs);