diff options
Diffstat (limited to 'src/multimedia/platform/gstreamer/common/qgstreamermediaplayer.cpp')
-rw-r--r-- | src/multimedia/platform/gstreamer/common/qgstreamermediaplayer.cpp | 836 |
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 |