4#include <common/qgstreamermediaplayer_p.h>
6#include <audio/qgstreameraudiodevice_p.h>
7#include <common/qglist_helper_p.h>
8#include <common/qgst_debug_p.h>
9#include <common/qgstutils_p.h>
10#include <common/qgst_discoverer_p.h>
11#include <common/qgst_play_p.h>
12#include <common/qgstpipeline_p.h>
13#include <common/qgstreameraudiooutput_p.h>
14#include <common/qgstreamermessage_p.h>
15#include <common/qgstreamermetadata_p.h>
16#include <common/qgstreamervideooutput_p.h>
17#include <common/qgstreamervideosink_p.h>
18#include <uri_handler/qgstreamer_qiodevice_handler_p.h>
19#include <qgstreamerformatinfo_p.h>
21#include <QtMultimedia/private/qthreadlocalrhi_p.h>
22#include <QtMultimedia/qaudiodevice.h>
23#include <QtConcurrent/qtconcurrentrun.h>
24#include <QtCore/qdeadlinetimer.h>
25#include <QtCore/qdebug.h>
26#include <QtMultimedia/private/qmultimedia_ranges_p.h>
27#include <QtCore/qiodevice.h>
28#include <QtCore/qloggingcategory.h>
29#include <QtCore/qthread.h>
30#include <QtCore/qurl.h>
31#include <QtCore/private/quniquehandle_p.h>
36namespace ranges = QtMultimediaPrivate::ranges;
42 using TrackType = QGstreamerMediaPlayer::TrackType;
44 QByteArrayView type = caps
.at(0
).name();
46 if (type.startsWith(
"video/x-raw"))
47 return TrackType::VideoStream;
48 if (type.startsWith(
"audio/x-raw"))
49 return TrackType::AudioStream;
50 if (type.startsWith(
"text"))
51 return TrackType::SubtitleStream;
60 return QtConcurrent::run([url = std::move(url)] {
61 QGst::QGstDiscoverer discoverer;
62 return discoverer.discover(url);
69 using namespace Qt::Literals;
70 using namespace std::chrono;
71 using namespace std::chrono_literals;
73 if (discoveryResult) {
75 qCDebug(qLcMediaPlayer) <<
"gst_play_set_uri";
76 gst_play_set_uri(m_gstPlay.get(), url.toEncoded().constData());
78 m_trackMetaData.fill({});
79 seekableChanged(discoveryResult->isSeekable);
80 if (discoveryResult->duration)
81 m_duration = round<milliseconds>(*discoveryResult->duration);
84 durationChanged(m_duration);
86 m_metaData = QGst::toContainerMetadata(*discoveryResult);
88 videoAvailableChanged(!discoveryResult->videoStreams.empty());
89 audioAvailableChanged(!discoveryResult->audioStreams.empty());
92 for (
const auto &videoInfo : discoveryResult->videoStreams) {
93 m_trackMetaData[0].emplace_back(QGst::toStreamMetadata(videoInfo));
94 QSize nativeSize = QGstUtils::qCalculateFrameSizeGStreamer(videoInfo.size,
95 videoInfo.pixelAspectRatio);
96 m_nativeSize.emplace_back(nativeSize);
98 for (
const auto &audioInfo : discoveryResult->audioStreams)
99 m_trackMetaData[1].emplace_back(QGst::toStreamMetadata(audioInfo));
100 for (
const auto &subtitleInfo : discoveryResult->subtitleStreams)
101 m_trackMetaData[2].emplace_back(QGst::toStreamMetadata(subtitleInfo));
103 using Key = QMediaMetaData::Key;
104 auto copyKeysToRootMetadata = [&](
const QMediaMetaData &reference, QSpan<
const Key> keys) {
105 for (QMediaMetaData::Key key : keys) {
106 QVariant referenceValue = reference.value(key);
107 if (referenceValue.isValid())
108 m_metaData.insert(key, referenceValue);
114 if (!m_trackMetaData[0].empty())
115 copyKeysToRootMetadata(m_trackMetaData[0].front(),
125 if (!m_trackMetaData[1].empty())
126 copyKeysToRootMetadata(m_trackMetaData[1].front(),
132 if (!m_url.isEmpty())
133 m_metaData.insert(QMediaMetaData::Key::Url, m_url);
135 qCDebug(qLcMediaPlayer) <<
"metadata:" << m_metaData;
136 qCDebug(qLcMediaPlayer) <<
"video metadata:" << m_trackMetaData[0];
137 qCDebug(qLcMediaPlayer) <<
"audio metadata:" << m_trackMetaData[1];
138 qCDebug(qLcMediaPlayer) <<
"subtitle metadata:" << m_trackMetaData[2];
143 isVideoAvailable() ? 0 : -1,
144 isAudioAvailable() ? 0 : -1,
147 updateVideoTrackEnabled();
148 updateAudioTrackEnabled();
149 updateNativeSizeOnVideoOutput();
150 positionChanged(0ms);
153 m_hasPendingMedia =
false;
154 if (m_requestedPlaybackState) {
155 switch (*m_requestedPlaybackState) {
156 case QMediaPlayer::PlayingState:
159 case QMediaPlayer::PausedState:
168 qCDebug(qLcMediaPlayer) <<
"Discovery error:" << discoveryResult.error();
169 m_resourceErrorState = ResourceErrorState::ErrorOccurred;
170 setInvalidMediaWithError(QMediaPlayer::Error::ResourceError,
171 u"Resource cannot be discovered"_s);
172 m_hasPendingMedia =
false;
173 resetStateForEmptyOrInvalidMedia();
183 qCDebug(qLcMediaPlayer) <<
"Added pad" << pad.name() <<
"from" << src.name();
187 std::optional<QGstreamerMediaPlayer::TrackType> type = toTrackType(caps);
191 customPipelinePads[*type] = pad;
194 case TrackType::VideoStream: {
199 customPipeline.add(sink);
201 customPipelineSinks[TrackType::VideoStream] = sink;
205 case TrackType::AudioStream: {
208 customPipeline.add(sink);
210 customPipelineSinks[TrackType::AudioStream] = sink;
214 case TrackType::SubtitleStream: {
218 customPipeline.add(sink);
220 customPipelineSinks[TrackType::SubtitleStream] = sink;
237 Q_ASSERT(thread()->isCurrentThread());
239 qCDebug(qLcMediaPlayer) <<
"Removed pad" << pad.name() <<
"from" << src.name() <<
"for stream"
242 auto found = ranges::find(customPipelinePads, pad);
243 if (found == customPipelinePads.end())
246 TrackType type =
static_cast<TrackType>(found - customPipelinePads.cbegin());
249 case TrackType::VideoStream:
250 case TrackType::AudioStream:
251 case TrackType::SubtitleStream: {
252 if (customPipelineSinks[TrackType::VideoStream]) {
253 customPipeline.stopAndRemoveElements(
254 customPipelineSinks[TrackType::VideoStream]);
255 customPipelineSinks[TrackType::VideoStream] = {};
267 using namespace std::chrono_literals;
268 m_nativeSize.clear();
270 bool metadataNeedsSignal = !m_metaData.isEmpty();
271 bool tracksNeedsSignal = ranges::any_of(m_trackMetaData, [](
const auto &container) {
272 return !container.empty();
276 m_trackMetaData.fill({});
278 seekableChanged(
false);
280 videoAvailableChanged(
false);
281 audioAvailableChanged(
false);
283 m_activeTrack.fill(-1);
285 if (metadataNeedsSignal)
287 if (tracksNeedsSignal)
293 int activeVideoTrack = activeTrack(TrackType::VideoStream);
294 bool hasVideoTrack = activeVideoTrack != -1;
296 QSize nativeSize = hasVideoTrack ? m_nativeSize[activeTrack(TrackType::VideoStream)] : QSize{};
298 QVariant orientation = hasVideoTrack
299 ? m_trackMetaData[TrackType::VideoStream][activeVideoTrack].value(
300 QMediaMetaData::Key::Orientation)
303 if (orientation.isValid()) {
304 auto rotation = orientation.value<QtVideo::Rotation>();
305 gstVideoOutput->setRotation(rotation);
312 qCDebug(qLcMediaPlayer) <<
"gst_play_seek";
313 gst_play_seek(m_gstPlay.get(), gst_play_get_position(m_gstPlay.get()));
318 bool hasTrack = m_activeTrack[TrackType::VideoStream] != -1;
322 gst_play_set_video_track_enabled(m_gstPlay.get(), hasTrack && hasSink);
327 bool hasTrack = m_activeTrack[TrackType::AudioStream] != -1;
328 bool hasAudioOut = gstAudioOutput;
330 gst_play_set_audio_track_enabled(m_gstPlay.get(), hasTrack && hasAudioOut);
335 if (qFuzzyIsNull(newProgress - m_bufferProgress))
338 m_bufferProgress = newProgress;
339 bufferProgressChanged(m_bufferProgress);
344 auto handlers = std::initializer_list<QGObjectHandlerScopedConnection *>{ &sourceSetup };
345 for (QGObjectHandlerScopedConnection *handler : handlers)
346 handler->disconnect();
351 auto videoOutput = QGstreamerVideoOutput::create();
353 return q23::unexpected{ videoOutput.error() };
361 gst_play_config_set_seek_accurate(config, accurate);
365 QMediaPlayer *parent)
367 QPlatformMediaPlayer(parent),
368 gstVideoOutput(videoOutput),
370 gst_play_new(
nullptr),
371 QGstPlayHandle::HasRef,
374 GST_PIPELINE_CAST(gst_play_get_pipeline(m_gstPlay.get())),
375 QGstPipeline::HasRef,
378 QGstBusHandle{ gst_play_get_message_bus(m_gstPlay.get()), QGstBusHandle::HasRef },
392 constexpr bool useNxpWorkaround = std::is_same_v<
decltype(&gst_play_config_set_seek_accurate),
393 void (*)(GstPlay *, gboolean)>;
395 QUniqueGstStructureHandle config{
396 gst_play_get_config(m_gstPlay.get()),
399 if constexpr (useNxpWorkaround)
400 setSeekAccurate(m_gstPlay.get(),
true);
402 setSeekAccurate(config.get(),
true);
404 gst_play_set_config(m_gstPlay.get(), config.release());
406 QUniqueGstStructureHandle config{
407 gst_play_get_config(m_gstPlay.get()),
409 gst_play_config_set_seek_accurate(config.get(),
true);
410 gst_play_set_config(m_gstPlay.get(), config.release());
413 gstVideoOutput->setParent(
this);
417 m_gstVideoSink =
new QGstreamerRelayVideoSink(
this);
420 m_playbin.set(
"video-sink", gstVideoOutput->gstElement());
421 m_playbin.set(
"text-sink", gstVideoOutput->gstSubtitleElement());
422 m_playbin.set(
"audio-sink", QGstElement::createFromPipelineDescription(
"fakesink"));
424 m_gstPlayBus.installMessageFilter(
this);
427 gst_play_set_subtitle_track_enabled(m_gstPlay.get(),
false);
429 sourceSetup = m_playbin.connect(
"source-setup", GCallback(sourceSetupCallback),
this);
431 m_activeTrack.fill(-1);
438 using namespace std::chrono_literals;
439 using namespace std::chrono;
440 qCDebug(qLcMediaPlayer) <<
"~QGstreamerMediaPlayer" << m_callStopInDestructor
441 << m_expectedStoppedMessages << state();
444 cleanupCustomPipeline();
446 m_gstPlayBus.removeMessageFilter(
static_cast<QGstreamerBusMessageFilter *>(
this));
448 if (m_callStopInDestructor) {
449 m_expectedStoppedMessages += 1;
450 qCDebug(qLcMediaPlayer) <<
"gst_play_stop";
451 gst_play_stop(m_gstPlay.get());
454 if (m_expectedStoppedMessages) {
455 auto processNextMessage = [
this](nanoseconds timeout) {
457 gst_bus_timed_pop_filtered(m_gstPlayBus.get(), timeout.count(),
458 GST_MESSAGE_APPLICATION), QGstreamerMessage::HasRef
460 if (!message || !gst_play_is_play_message(message.message()))
463 gst_play_message_parse_type(message.message(), &type);
465 if (type == GST_PLAY_MESSAGE_END_OF_STREAM) {
466 m_expectedStoppedMessages += 1;
469 if (type == GST_PLAY_MESSAGE_STATE_CHANGED) {
471 gst_play_message_parse_state_changed(message.message(), &state);
472 if (state == GST_PLAY_STATE_STOPPED) {
473 if (m_expectedStoppedMessages > 0)
474 m_expectedStoppedMessages -= 1;
479 constexpr auto timeout = 200ms;
480 QDeadlineTimer deadline{timeout};
481 while (!deadline.hasExpired() && m_expectedStoppedMessages)
482 processNextMessage(deadline.remainingTimeAsDuration());
484 if (m_expectedStoppedMessages)
485 qWarning() <<
"Did not receive expected STOPPED state change messages from GstPlay "
486 "in QGstreamerMediaPlayer destructor:" << m_expectedStoppedMessages;
490 m_playbin.setStateSync(GST_STATE_NULL);
492 m_playbin.set(
"video-sink", QGstElement::createFromPipelineDescription(
"fakesink"));
493 m_playbin.set(
"text-sink", QGstElement::createFromPipelineDescription(
"fakesink"));
494 m_playbin.set(
"audio-sink", QGstElement::createFromPipelineDescription(
"fakesink"));
496 gst_bus_set_flushing(m_gstPlayBus.get(), TRUE);
501 using namespace std::chrono;
503 positionChanged(round<milliseconds>(nanoseconds{
504 gst_play_get_position(m_gstPlay.get()),
510 if (isCustomSource()) {
511 constexpr bool traceBusMessages =
true;
512 if (traceBusMessages)
513 qCDebug(qLcMediaPlayer) <<
"received bus message:" << message;
515 switch (message.type()) {
516 case GST_MESSAGE_WARNING:
517 qWarning() <<
"received bus message:" << message;
520 case GST_MESSAGE_INFO:
521 qInfo() <<
"received bus message:" << message;
524 case GST_MESSAGE_ERROR:
525 qWarning() <<
"received bus message:" << message;
526 customPipeline.dumpPipelineGraph(
"GST_MESSAGE_ERROR");
529 case GST_MESSAGE_LATENCY:
530 customPipeline.recalculateLatency();
539 switch (message.type()) {
540 case GST_MESSAGE_APPLICATION:
541 if (gst_play_is_play_message(message.message()))
542 return processBusMessageApplication(message);
546 qCDebug(qLcMediaPlayer) << message;
556 using namespace std::chrono;
558 gst_play_message_parse_type(message.message(), &type);
562 case GST_PLAY_MESSAGE_URI_LOADED: {
563 mediaStatusChanged(QMediaPlayer::LoadedMedia);
567 case GST_PLAY_MESSAGE_POSITION_UPDATED: {
568 if (state() == QMediaPlayer::PlaybackState::PlayingState) {
570 constexpr bool usePayload =
false;
571 if constexpr (usePayload) {
572 GstClockTime position;
573 gst_play_message_parse_position_updated(message.message(), &position);
574 positionChanged(round<milliseconds>(nanoseconds{ position }));
576 GstClockTime position = gst_play_get_position(m_gstPlay.get());
577 positionChanged(round<milliseconds>(nanoseconds{ position }));
582 case GST_PLAY_MESSAGE_DURATION_CHANGED: {
583 GstClockTime duration;
584 gst_play_message_parse_duration_updated(message.message(), &duration);
585 milliseconds durationInMs = round<milliseconds>(nanoseconds{ duration });
586 durationChanged(durationInMs);
588 m_metaData.insert(QMediaMetaData::Duration,
int(durationInMs.count()));
593 case GST_PLAY_MESSAGE_BUFFERING: {
595 gst_play_message_parse_buffering_percent(message.message(), &percent);
596 updateBufferProgress(percent * 0.01f);
599 case GST_PLAY_MESSAGE_STATE_CHANGED: {
600 m_playOrPauseCalledSinceLastStateChangedOrEosMessage =
false;
602 gst_play_message_parse_state_changed(message.message(), &state);
605 case GstPlayState::GST_PLAY_STATE_STOPPED:
606 if (m_expectedStoppedMessages > 0)
607 m_expectedStoppedMessages -= 1;
609 if (stateChangeToSkip) {
610 qCDebug(qLcMediaPlayer) <<
" skipping StoppedState transition";
612 stateChangeToSkip -= 1;
615 stateChanged(QMediaPlayer::StoppedState);
616 updateBufferProgress(0);
619 case GstPlayState::GST_PLAY_STATE_PAUSED:
620 stateChanged(QMediaPlayer::PausedState);
621 mediaStatusChanged(QMediaPlayer::BufferedMedia);
623 updateBufferProgress(1);
625 case GstPlayState::GST_PLAY_STATE_BUFFERING:
626 mediaStatusChanged(QMediaPlayer::BufferingMedia);
628 case GstPlayState::GST_PLAY_STATE_PLAYING:
629 stateChanged(QMediaPlayer::PlayingState);
630 mediaStatusChanged(QMediaPlayer::BufferedMedia);
632 updateBufferProgress(1);
639 case GST_PLAY_MESSAGE_MEDIA_INFO_UPDATED: {
642 QUniqueGstPlayMediaInfoHandle info{};
643 gst_play_message_parse_media_info_updated(message.message(), &info);
645 seekableChanged(gst_play_media_info_is_seekable(info.get()));
647 const gchar *title = gst_play_media_info_get_title(info.get());
648 m_metaData.insert(QMediaMetaData::Title, QString::fromUtf8(title));
655 case GST_PLAY_MESSAGE_END_OF_STREAM: {
656 m_expectedStoppedMessages += 1;
659 if (!m_playOrPauseCalledSinceLastStateChangedOrEosMessage)
660 m_callStopInDestructor =
false;
661 m_playOrPauseCalledSinceLastStateChangedOrEosMessage =
false;
664 positionChanged(m_duration);
665 m_callStopInDestructor =
true;
666 qCDebug(qLcMediaPlayer) <<
"EOS: restarting loop (gst_play_play)";
667 gst_play_play(m_gstPlay.get());
668 positionChanged(0ms);
672 stateChangeToSkip += 1;
674 qCDebug(qLcMediaPlayer) <<
"EOS: done";
675 positionChanged(m_duration);
676 mediaStatusChanged(QMediaPlayer::EndOfMedia);
677 stateChanged(QMediaPlayer::StoppedState);
683 case GST_PLAY_MESSAGE_ERROR:
684 case GST_PLAY_MESSAGE_WARNING:
685 case GST_PLAY_MESSAGE_VIDEO_DIMENSIONS_CHANGED:
686 case GST_PLAY_MESSAGE_VOLUME_CHANGED:
687 case GST_PLAY_MESSAGE_MUTE_CHANGED:
688 case GST_PLAY_MESSAGE_SEEK_DONE:
692 Q_UNREACHABLE_RETURN(
false);
698 return m_duration.count();
703 return !m_url.isEmpty() || m_stream;
711 switch (mediaStatus()) {
712 case QMediaPlayer::MediaStatus::NoMedia:
713 case QMediaPlayer::MediaStatus::InvalidMedia:
723 return m_bufferProgress;
728 return QMediaTimeRange();
733 return gst_play_get_rate(m_gstPlay.get());
738 if (isCustomSource()) {
739 static std::once_flag flag;
740 std::call_once(flag, [] {
743 <<
"setPlaybackRate with custom gstreamer pipelines can cause pipeline hangs. "
747 customPipeline.setPlaybackRate(rate);
751 if (rate == playbackRate())
754 qCDebug(qLcMediaPlayer) <<
"gst_play_set_rate" << rate;
755 gst_play_set_rate(m_gstPlay.get(), rate);
756 playbackRateChanged(rate);
761 std::chrono::milliseconds posInMs{ pos };
763 setPosition(posInMs);
768 using namespace std::chrono;
770 if (isCustomSource()) {
771 static std::once_flag flag;
772 std::call_once(flag, [] {
774 qWarning() <<
"setPosition with custom gstreamer pipelines can cause pipeline hangs. "
778 customPipeline.setPosition(pos);
782 if (m_hasPendingMedia) {
786 qCDebug(qLcMediaPlayer) <<
"gst_play_seek" << pos;
787 gst_play_seek(m_gstPlay.get(), nanoseconds(pos).count());
789 if (mediaStatus() == QMediaPlayer::EndOfMedia)
790 mediaStatusChanged(QMediaPlayer::LoadedMedia);
791 positionChanged(pos);
796 if (isCustomSource()) {
798 customPipeline.setState(GST_STATE_PLAYING);
799 stateChanged(QMediaPlayer::PlayingState);
803 if (m_hasPendingMedia) {
806 m_requestedPlaybackState = QMediaPlayer::PlayingState;
810 QMediaPlayer::PlaybackState currentState = state();
811 if (currentState == QMediaPlayer::PlayingState || !hasValidMedia())
814 if (currentState != QMediaPlayer::PausedState)
817 if (mediaStatus() == QMediaPlayer::EndOfMedia) {
819 mediaStatusChanged(QMediaPlayer::LoadedMedia);
823 qCDebug(qLcMediaPlayer) <<
"gst_play_seek";
824 gst_play_seek(m_gstPlay.get(), m_pendingSeek->count());
825 m_pendingSeek = std::nullopt;
829 m_callStopInDestructor =
true;
830 m_playOrPauseCalledSinceLastStateChangedOrEosMessage =
true;
831 qCDebug(qLcMediaPlayer) <<
"gst_play_play";
832 gst_play_play(m_gstPlay.get());
833 stateChanged(QMediaPlayer::PlayingState);
838 if (isCustomSource()) {
840 customPipeline.setState(GST_STATE_PAUSED);
841 stateChanged(QMediaPlayer::PausedState);
845 if (m_hasPendingMedia) {
846 m_requestedPlaybackState = QMediaPlayer::PausedState;
850 if (state() == QMediaPlayer::PausedState || !hasMedia()
851 || m_resourceErrorState != ResourceErrorState::NoError)
856 m_callStopInDestructor =
true;
857 m_playOrPauseCalledSinceLastStateChangedOrEosMessage =
true;
858 qCDebug(qLcMediaPlayer) <<
"gst_play_pause";
859 gst_play_pause(m_gstPlay.get());
861 mediaStatusChanged(QMediaPlayer::BufferedMedia);
862 stateChanged(QMediaPlayer::PausedState);
867 if (isCustomSource()) {
868 customPipeline.setState(GST_STATE_READY);
869 stateChanged(QMediaPlayer::StoppedState);
874 if (m_hasPendingMedia) {
875 m_requestedPlaybackState = QMediaPlayer::StoppedState;
879 using namespace std::chrono_literals;
880 if (state() == QMediaPlayer::StoppedState) {
881 if (position() != 0) {
883 positionChanged(0ms);
884 mediaStatusChanged(QMediaPlayer::LoadedMedia);
890 m_expectedStoppedMessages += 1;
891 m_callStopInDestructor =
false;
892 qCDebug(qLcMediaPlayer) <<
"gst_play_stop";
893 gst_play_stop(m_gstPlay.get());
895 stateChanged(QMediaPlayer::StoppedState);
897 mediaStatusChanged(QMediaPlayer::LoadedMedia);
898 positionChanged(0ms);
903 if (isCustomSource())
904 return customPipeline;
922 return PitchCompensationAvailability::AlwaysOn;
940 const gchar *typeName = g_type_name_from_instance((GTypeInstance *)source);
941 qCDebug(qLcMediaPlayer) <<
"Setting up source:" << typeName;
943 if (typeName == std::string_view(
"GstRTSPSrc")) {
947 int v = qEnvironmentVariableIntValue(
"QT_MEDIA_RTSP_LATENCY", &ok);
950 qCDebug(qLcMediaPlayer) <<
" -> setting source latency to:" << latency <<
"ms";
951 s.set(
"latency", latency);
954 v = qEnvironmentVariableIntValue(
"QT_MEDIA_RTSP_DROP_ON_LATENCY", &ok);
957 qCDebug(qLcMediaPlayer) <<
" -> setting drop-on-latency to:" << drop;
958 s.set(
"drop-on-latency", drop);
961 v = qEnvironmentVariableIntValue(
"QT_MEDIA_RTSP_DO_RETRANSMISSION", &ok);
964 qCDebug(qLcMediaPlayer) <<
" -> setting do-retransmission to:" << retrans;
965 s.set(
"do-retransmission", retrans);
972 cleanupCustomPipeline();
974 m_resourceErrorState = ResourceErrorState::NoError;
979 m_discoveryHandler.cancel();
980 m_discoverFuture.cancel();
984 streamURL = qGstRegisterQIODevice(stream);
986 if (content.isEmpty() && !stream) {
987 mediaStatusChanged(QMediaPlayer::NoMedia);
988 resetStateForEmptyOrInvalidMedia();
992 if (isCustomSource()) {
993 setMediaCustomSource(content);
995 mediaStatusChanged(QMediaPlayer::LoadingMedia);
996 m_hasPendingMedia =
true;
997 m_requestedPlaybackState = std::nullopt;
999 const QUrl &playUrl = stream ? streamURL : content;
1000 m_discoverFuture = discover(playUrl);
1002 m_discoveryHandler =
1003 m_discoverFuture.then(
this,[
this, playUrl](
const DiscoverResult &result) {
1004 handleDiscoverResult(result, playUrl);
1011 using namespace Qt::Literals;
1012 using namespace std::chrono;
1013 using namespace std::chrono_literals;
1018 m_playbin.set(
"video-sink", QGstElement::createFromPipelineDescription(
"fakesink"));
1019 m_playbin.set(
"text-sink", QGstElement::createFromPipelineDescription(
"fakesink"));
1020 m_playbin.set(
"audio-sink", QGstElement::createFromPipelineDescription(
"fakesink"));
1023 if (QGstElement sink = gstVideoOutput->gstreamerVideoSink()->gstSink())
1028 customPipeline = QGstPipeline::create(
"customPipeline");
1029 customPipeline.installMessageFilter(
this);
1030 positionUpdateTimer = std::make_unique<QTimer>();
1032 QObject::connect(positionUpdateTimer.get(), &QTimer::timeout,
this, [
this] {
1033 Q_ASSERT(customPipeline);
1034 auto position = customPipeline.position();
1036 positionChanged(round<milliseconds>(position));
1039 positionUpdateTimer->start(100ms);
1041 QByteArray gstLaunchString =
1042 content.toString(QUrl::RemoveScheme | QUrl::PrettyDecoded).toLatin1();
1043 qCDebug(qLcMediaPlayer) <<
"generating" << gstLaunchString;
1046 emit error(QMediaPlayer::ResourceError, u"Could not create custom pipeline"_s);
1051 customPipeline.add(decoder);
1054 qGstSafeCast<GstBin>(element.element()),
1058 elementBin.addUnlinkedGhostPads(GstPadDirection::GST_PAD_SRC);
1061 padAdded = decoder.onPadAdded<&QGstreamerMediaPlayer::decoderPadAddedCustomSource>(
this);
1062 padRemoved = decoder.onPadRemoved<&QGstreamerMediaPlayer::decoderPadRemovedCustomSource>(
this);
1064 customPipeline.setStateSync(GstState::GST_STATE_PAUSED);
1066 auto srcPadVisitor = [](GstElement *element, GstPad *pad,
void *self) -> gboolean {
1067 reinterpret_cast<QGstreamerMediaPlayer *>(self)->decoderPadAddedCustomSource(
1068 QGstElement{ element, QGstElement::NeedsRef }, QGstPad{ pad, QGstPad::NeedsRef });
1072 gst_element_foreach_pad(element.element(), srcPadVisitor,
this);
1074 mediaStatusChanged(QMediaPlayer::LoadedMedia);
1076 customPipeline.dumpGraph(
"setMediaCustomPipeline");
1081 customPipeline.setStateSync(GST_STATE_NULL);
1082 customPipeline.removeMessageFilter(
this);
1084 for (QGstElement &sink : customPipelineSinks)
1086 customPipeline.remove(sink);
1088 positionUpdateTimer = {};
1089 customPipeline = {};
1094 if (isCustomSource()) {
1095 qWarning() <<
"QMediaPlayer::setAudioOutput not supported when using custom sources";
1099 if (gstAudioOutput == output)
1104 gstOutput->setAsync(
true);
1108 m_playbin.set(
"audio-sink", gstAudioOutput->gstElement());
1110 m_playbin.set(
"audio-sink", QGstElement::createFromPipelineDescription(
"fakesink"));
1111 updateAudioTrackEnabled();
1115 if (!qmediaplayerDestructorCalled)
1116 m_playbin.finishStateChange();
1134 Q_ASSERT(pluggableSink);
1140 QSpan<
const QMediaMetaData> tracks = m_trackMetaData[type];
1141 return tracks.size();
1146 QSpan<
const QMediaMetaData> tracks = m_trackMetaData[type];
1147 if (index < tracks.size())
1148 return tracks[index];
1154 return m_activeTrack[type];
1159 if (m_activeTrack[type] == index)
1162 int formerTrack = m_activeTrack[type];
1163 m_activeTrack[type] = index;
1166 case TrackType::VideoStream: {
1168 gst_play_set_video_track(m_gstPlay.get(), index);
1169 updateVideoTrackEnabled();
1170 updateNativeSizeOnVideoOutput();
1173 case TrackType::AudioStream: {
1175 gst_play_set_audio_track(m_gstPlay.get(), index);
1176 updateAudioTrackEnabled();
1179 case TrackType::SubtitleStream: {
1181 gst_play_set_subtitle_track(m_gstPlay.get(), index);
1182 gst_play_set_subtitle_track_enabled(m_gstPlay.get(), index != -1);
1189 if (formerTrack != -1 && index != -1)
1192 seekToCurrentPosition();
QGstStructureView at(int index) const
bool syncStateWithParent()
static QGstElement createFromPipelineDescription(const char *)
bool link(const QGstPad &sink) const
QGstCaps queryCaps() const
QGString streamId() const
QGstElement gstElement() const
void connectPluggableVideoSink(QGstreamerPluggableVideoSink *pluggableSink)
void disconnectPluggableVideoSink()
void setVideoSink(QGstreamerRelayVideoSink *sink)
QGstreamerRelayVideoSink * gstreamerVideoSink() const
void setNativeSize(QSize)
std::optional< QGstreamerMediaPlayer::TrackType > toTrackType(const QGstCaps &caps)
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)
QGstPlayMessageAdaptor(const QGstreamerMessage &m)