summaryrefslogtreecommitdiffstats
path: root/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/multimedia/platform/gstreamer/common/qgstreamermediaplayer.cpp')
-rw-r--r--src/multimedia/platform/gstreamer/common/qgstreamermediaplayer.cpp836
1 files changed, 504 insertions, 332 deletions
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