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;
199 customPipeline.add(sink);
201 customPipelineSinks[VideoStream] = sink;
208 customPipeline.add(sink);
210 customPipelineSinks[AudioStream] = sink;
214 case SubtitleStream: {
218 customPipeline.add(sink);
220 customPipelineSinks[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 = TrackType(found - customPipelinePads.cbegin());
251 case SubtitleStream: {
252 if (customPipelineSinks[VideoStream]) {
253 customPipeline.stopAndRemoveElements(customPipelineSinks[VideoStream]);
254 customPipelineSinks[VideoStream] = {};
266 using namespace std::chrono_literals;
267 m_nativeSize.clear();
269 bool metadataNeedsSignal = !m_metaData.isEmpty();
270 bool tracksNeedsSignal = ranges::any_of(m_trackMetaData, [](
const auto &container) {
271 return !container.empty();
275 m_trackMetaData.fill({});
277 seekableChanged(
false);
279 videoAvailableChanged(
false);
280 audioAvailableChanged(
false);
282 m_activeTrack.fill(-1);
284 if (metadataNeedsSignal)
286 if (tracksNeedsSignal)
292 int activeVideoTrack = activeTrack(TrackType::VideoStream);
293 bool hasVideoTrack = activeVideoTrack != -1;
295 QSize nativeSize = hasVideoTrack ? m_nativeSize[activeTrack(TrackType::VideoStream)] : QSize{};
297 QVariant orientation = hasVideoTrack
298 ? m_trackMetaData[TrackType::VideoStream][activeTrack(TrackType::VideoStream)].value(
299 QMediaMetaData::Key::Orientation)
302 if (orientation.isValid()) {
303 auto rotation = orientation.value<QtVideo::Rotation>();
304 gstVideoOutput->setRotation(rotation);
311 qCDebug(qLcMediaPlayer) <<
"gst_play_seek";
312 gst_play_seek(m_gstPlay.get(), gst_play_get_position(m_gstPlay.get()));
317 bool hasTrack = m_activeTrack[TrackType::VideoStream] != -1;
321 gst_play_set_video_track_enabled(m_gstPlay.get(), hasTrack && hasSink);
326 bool hasTrack = m_activeTrack[TrackType::AudioStream] != -1;
327 bool hasAudioOut = gstAudioOutput;
329 gst_play_set_audio_track_enabled(m_gstPlay.get(), hasTrack && hasAudioOut);
334 if (qFuzzyIsNull(newProgress - m_bufferProgress))
337 m_bufferProgress = newProgress;
338 bufferProgressChanged(m_bufferProgress);
343 auto handlers = std::initializer_list<QGObjectHandlerScopedConnection *>{ &sourceSetup };
344 for (QGObjectHandlerScopedConnection *handler : handlers)
345 handler->disconnect();
350 auto videoOutput = QGstreamerVideoOutput::create();
352 return q23::unexpected{ videoOutput.error() };
360 gst_play_config_set_seek_accurate(config, accurate);
364 QMediaPlayer *parent)
366 QPlatformMediaPlayer(parent),
367 gstVideoOutput(videoOutput),
369 gst_play_new(
nullptr),
370 QGstPlayHandle::HasRef,
373 GST_PIPELINE_CAST(gst_play_get_pipeline(m_gstPlay.get())),
374 QGstPipeline::HasRef,
377 QGstBusHandle{ gst_play_get_message_bus(m_gstPlay.get()), QGstBusHandle::HasRef },
391 constexpr bool useNxpWorkaround = std::is_same_v<
decltype(&gst_play_config_set_seek_accurate),
392 void (*)(GstPlay *, gboolean)>;
394 QUniqueGstStructureHandle config{
395 gst_play_get_config(m_gstPlay.get()),
398 if constexpr (useNxpWorkaround)
399 setSeekAccurate(m_gstPlay.get(),
true);
401 setSeekAccurate(config.get(),
true);
403 gst_play_set_config(m_gstPlay.get(), config.release());
405 QUniqueGstStructureHandle config{
406 gst_play_get_config(m_gstPlay.get()),
408 gst_play_config_set_seek_accurate(config.get(),
true);
409 gst_play_set_config(m_gstPlay.get(), config.release());
412 gstVideoOutput->setParent(
this);
416 m_gstVideoSink =
new QGstreamerRelayVideoSink(
this);
419 m_playbin.set(
"video-sink", gstVideoOutput->gstElement());
420 m_playbin.set(
"text-sink", gstVideoOutput->gstSubtitleElement());
421 m_playbin.set(
"audio-sink", QGstElement::createFromPipelineDescription(
"fakesink"));
423 m_gstPlayBus.installMessageFilter(
this);
426 gst_play_set_subtitle_track_enabled(m_gstPlay.get(),
false);
428 sourceSetup = m_playbin.connect(
"source-setup", GCallback(sourceSetupCallback),
this);
430 m_activeTrack.fill(-1);
437 using namespace std::chrono_literals;
438 using namespace std::chrono;
439 qCDebug(qLcMediaPlayer) <<
"~QGstreamerMediaPlayer" << m_callStopInDestructor
440 << m_expectedStoppedMessages << state();
443 cleanupCustomPipeline();
445 m_gstPlayBus.removeMessageFilter(
static_cast<QGstreamerBusMessageFilter *>(
this));
447 if (m_callStopInDestructor) {
448 m_expectedStoppedMessages += 1;
449 qCDebug(qLcMediaPlayer) <<
"gst_play_stop";
450 gst_play_stop(m_gstPlay.get());
453 if (m_expectedStoppedMessages) {
454 auto processNextMessage = [
this](nanoseconds timeout) {
456 gst_bus_timed_pop_filtered(m_gstPlayBus.get(), timeout.count(),
457 GST_MESSAGE_APPLICATION), QGstreamerMessage::HasRef
459 if (!message || !gst_play_is_play_message(message.message()))
462 gst_play_message_parse_type(message.message(), &type);
464 if (type == GST_PLAY_MESSAGE_END_OF_STREAM) {
465 m_expectedStoppedMessages += 1;
468 if (type == GST_PLAY_MESSAGE_STATE_CHANGED) {
470 gst_play_message_parse_state_changed(message.message(), &state);
471 if (state == GST_PLAY_STATE_STOPPED) {
472 if (m_expectedStoppedMessages > 0)
473 m_expectedStoppedMessages -= 1;
478 constexpr auto timeout = 200ms;
479 QDeadlineTimer deadline{timeout};
480 while (!deadline.hasExpired() && m_expectedStoppedMessages)
481 processNextMessage(deadline.remainingTimeAsDuration());
483 if (m_expectedStoppedMessages)
484 qWarning() <<
"Did not receive expected STOPPED state change messages from GstPlay "
485 "in QGstreamerMediaPlayer destructor:" << m_expectedStoppedMessages;
489 m_playbin.setStateSync(GST_STATE_NULL);
491 m_playbin.set(
"video-sink", QGstElement::createFromPipelineDescription(
"fakesink"));
492 m_playbin.set(
"text-sink", QGstElement::createFromPipelineDescription(
"fakesink"));
493 m_playbin.set(
"audio-sink", QGstElement::createFromPipelineDescription(
"fakesink"));
495 gst_bus_set_flushing(m_gstPlayBus.get(), TRUE);
500 using namespace std::chrono;
502 positionChanged(round<milliseconds>(nanoseconds{
503 gst_play_get_position(m_gstPlay.get()),
509 if (isCustomSource()) {
510 constexpr bool traceBusMessages =
true;
511 if (traceBusMessages)
512 qCDebug(qLcMediaPlayer) <<
"received bus message:" << message;
514 switch (message.type()) {
515 case GST_MESSAGE_WARNING:
516 qWarning() <<
"received bus message:" << message;
519 case GST_MESSAGE_INFO:
520 qInfo() <<
"received bus message:" << message;
523 case GST_MESSAGE_ERROR:
524 qWarning() <<
"received bus message:" << message;
525 customPipeline.dumpPipelineGraph(
"GST_MESSAGE_ERROR");
528 case GST_MESSAGE_LATENCY:
529 customPipeline.recalculateLatency();
538 switch (message.type()) {
539 case GST_MESSAGE_APPLICATION:
540 if (gst_play_is_play_message(message.message()))
541 return processBusMessageApplication(message);
545 qCDebug(qLcMediaPlayer) << message;
555 using namespace std::chrono;
557 gst_play_message_parse_type(message.message(), &type);
561 case GST_PLAY_MESSAGE_URI_LOADED: {
562 mediaStatusChanged(QMediaPlayer::LoadedMedia);
566 case GST_PLAY_MESSAGE_POSITION_UPDATED: {
567 if (state() == QMediaPlayer::PlaybackState::PlayingState) {
569 constexpr bool usePayload =
false;
570 if constexpr (usePayload) {
571 GstClockTime position;
572 gst_play_message_parse_position_updated(message.message(), &position);
573 positionChanged(round<milliseconds>(nanoseconds{ position }));
575 GstClockTime position = gst_play_get_position(m_gstPlay.get());
576 positionChanged(round<milliseconds>(nanoseconds{ position }));
581 case GST_PLAY_MESSAGE_DURATION_CHANGED: {
582 GstClockTime duration;
583 gst_play_message_parse_duration_updated(message.message(), &duration);
584 milliseconds durationInMs = round<milliseconds>(nanoseconds{ duration });
585 durationChanged(durationInMs);
587 m_metaData.insert(QMediaMetaData::Duration,
int(durationInMs.count()));
592 case GST_PLAY_MESSAGE_BUFFERING: {
594 gst_play_message_parse_buffering_percent(message.message(), &percent);
595 updateBufferProgress(percent * 0.01f);
598 case GST_PLAY_MESSAGE_STATE_CHANGED: {
599 m_playOrPauseCalledSinceLastStateChangedOrEosMessage =
false;
601 gst_play_message_parse_state_changed(message.message(), &state);
604 case GstPlayState::GST_PLAY_STATE_STOPPED:
605 if (m_expectedStoppedMessages > 0)
606 m_expectedStoppedMessages -= 1;
608 if (stateChangeToSkip) {
609 qCDebug(qLcMediaPlayer) <<
" skipping StoppedState transition";
611 stateChangeToSkip -= 1;
614 stateChanged(QMediaPlayer::StoppedState);
615 updateBufferProgress(0);
618 case GstPlayState::GST_PLAY_STATE_PAUSED:
619 stateChanged(QMediaPlayer::PausedState);
620 mediaStatusChanged(QMediaPlayer::BufferedMedia);
622 updateBufferProgress(1);
624 case GstPlayState::GST_PLAY_STATE_BUFFERING:
625 mediaStatusChanged(QMediaPlayer::BufferingMedia);
627 case GstPlayState::GST_PLAY_STATE_PLAYING:
628 stateChanged(QMediaPlayer::PlayingState);
629 mediaStatusChanged(QMediaPlayer::BufferedMedia);
631 updateBufferProgress(1);
638 case GST_PLAY_MESSAGE_MEDIA_INFO_UPDATED: {
641 QUniqueGstPlayMediaInfoHandle info{};
642 gst_play_message_parse_media_info_updated(message.message(), &info);
644 seekableChanged(gst_play_media_info_is_seekable(info.get()));
646 const gchar *title = gst_play_media_info_get_title(info.get());
647 m_metaData.insert(QMediaMetaData::Title, QString::fromUtf8(title));
654 case GST_PLAY_MESSAGE_END_OF_STREAM: {
655 m_expectedStoppedMessages += 1;
658 if (!m_playOrPauseCalledSinceLastStateChangedOrEosMessage)
659 m_callStopInDestructor =
false;
660 m_playOrPauseCalledSinceLastStateChangedOrEosMessage =
false;
663 positionChanged(m_duration);
664 m_callStopInDestructor =
true;
665 qCDebug(qLcMediaPlayer) <<
"EOS: restarting loop (gst_play_play)";
666 gst_play_play(m_gstPlay.get());
667 positionChanged(0ms);
671 stateChangeToSkip += 1;
673 qCDebug(qLcMediaPlayer) <<
"EOS: done";
674 positionChanged(m_duration);
675 mediaStatusChanged(QMediaPlayer::EndOfMedia);
676 stateChanged(QMediaPlayer::StoppedState);
682 case GST_PLAY_MESSAGE_ERROR:
683 case GST_PLAY_MESSAGE_WARNING:
684 case GST_PLAY_MESSAGE_VIDEO_DIMENSIONS_CHANGED:
685 case GST_PLAY_MESSAGE_VOLUME_CHANGED:
686 case GST_PLAY_MESSAGE_MUTE_CHANGED:
687 case GST_PLAY_MESSAGE_SEEK_DONE:
691 Q_UNREACHABLE_RETURN(
false);
697 return m_duration.count();
702 return !m_url.isEmpty() || m_stream;
710 switch (mediaStatus()) {
711 case QMediaPlayer::MediaStatus::NoMedia:
712 case QMediaPlayer::MediaStatus::InvalidMedia:
722 return m_bufferProgress;
727 return QMediaTimeRange();
732 return gst_play_get_rate(m_gstPlay.get());
737 if (isCustomSource()) {
738 static std::once_flag flag;
739 std::call_once(flag, [] {
742 <<
"setPlaybackRate with custom gstreamer pipelines can cause pipeline hangs. "
746 customPipeline.setPlaybackRate(rate);
750 if (rate == playbackRate())
753 qCDebug(qLcMediaPlayer) <<
"gst_play_set_rate" << rate;
754 gst_play_set_rate(m_gstPlay.get(), rate);
755 playbackRateChanged(rate);
760 std::chrono::milliseconds posInMs{ pos };
762 setPosition(posInMs);
767 using namespace std::chrono;
769 if (isCustomSource()) {
770 static std::once_flag flag;
771 std::call_once(flag, [] {
773 qWarning() <<
"setPosition with custom gstreamer pipelines can cause pipeline hangs. "
777 customPipeline.setPosition(pos);
781 if (m_hasPendingMedia) {
785 qCDebug(qLcMediaPlayer) <<
"gst_play_seek" << pos;
786 gst_play_seek(m_gstPlay.get(), nanoseconds(pos).count());
788 if (mediaStatus() == QMediaPlayer::EndOfMedia)
789 mediaStatusChanged(QMediaPlayer::LoadedMedia);
790 positionChanged(pos);
795 if (isCustomSource()) {
797 customPipeline.setState(GST_STATE_PLAYING);
798 stateChanged(QMediaPlayer::PlayingState);
802 if (m_hasPendingMedia) {
805 m_requestedPlaybackState = QMediaPlayer::PlayingState;
809 QMediaPlayer::PlaybackState currentState = state();
810 if (currentState == QMediaPlayer::PlayingState || !hasValidMedia())
813 if (currentState != QMediaPlayer::PausedState)
816 if (mediaStatus() == QMediaPlayer::EndOfMedia) {
818 mediaStatusChanged(QMediaPlayer::LoadedMedia);
822 qCDebug(qLcMediaPlayer) <<
"gst_play_seek";
823 gst_play_seek(m_gstPlay.get(), m_pendingSeek->count());
824 m_pendingSeek = std::nullopt;
828 m_callStopInDestructor =
true;
829 m_playOrPauseCalledSinceLastStateChangedOrEosMessage =
true;
830 qCDebug(qLcMediaPlayer) <<
"gst_play_play";
831 gst_play_play(m_gstPlay.get());
832 stateChanged(QMediaPlayer::PlayingState);
837 if (isCustomSource()) {
839 customPipeline.setState(GST_STATE_PAUSED);
840 stateChanged(QMediaPlayer::PausedState);
844 if (m_hasPendingMedia) {
845 m_requestedPlaybackState = QMediaPlayer::PausedState;
849 if (state() == QMediaPlayer::PausedState || !hasMedia()
850 || m_resourceErrorState != ResourceErrorState::NoError)
855 m_callStopInDestructor =
true;
856 m_playOrPauseCalledSinceLastStateChangedOrEosMessage =
true;
857 qCDebug(qLcMediaPlayer) <<
"gst_play_pause";
858 gst_play_pause(m_gstPlay.get());
860 mediaStatusChanged(QMediaPlayer::BufferedMedia);
861 stateChanged(QMediaPlayer::PausedState);
866 if (isCustomSource()) {
867 customPipeline.setState(GST_STATE_READY);
868 stateChanged(QMediaPlayer::StoppedState);
873 if (m_hasPendingMedia) {
874 m_requestedPlaybackState = QMediaPlayer::StoppedState;
878 using namespace std::chrono_literals;
879 if (state() == QMediaPlayer::StoppedState) {
880 if (position() != 0) {
882 positionChanged(0ms);
883 mediaStatusChanged(QMediaPlayer::LoadedMedia);
889 m_expectedStoppedMessages += 1;
890 m_callStopInDestructor =
false;
891 qCDebug(qLcMediaPlayer) <<
"gst_play_stop";
892 gst_play_stop(m_gstPlay.get());
894 stateChanged(QMediaPlayer::StoppedState);
896 mediaStatusChanged(QMediaPlayer::LoadedMedia);
897 positionChanged(0ms);
902 if (isCustomSource())
903 return customPipeline;
921 return PitchCompensationAvailability::AlwaysOn;
939 const gchar *typeName = g_type_name_from_instance((GTypeInstance *)source);
940 qCDebug(qLcMediaPlayer) <<
"Setting up source:" << typeName;
942 if (typeName == std::string_view(
"GstRTSPSrc")) {
946 int v = qEnvironmentVariableIntValue(
"QT_MEDIA_RTSP_LATENCY", &ok);
949 qCDebug(qLcMediaPlayer) <<
" -> setting source latency to:" << latency <<
"ms";
950 s.set(
"latency", latency);
953 v = qEnvironmentVariableIntValue(
"QT_MEDIA_RTSP_DROP_ON_LATENCY", &ok);
956 qCDebug(qLcMediaPlayer) <<
" -> setting drop-on-latency to:" << drop;
957 s.set(
"drop-on-latency", drop);
960 v = qEnvironmentVariableIntValue(
"QT_MEDIA_RTSP_DO_RETRANSMISSION", &ok);
963 qCDebug(qLcMediaPlayer) <<
" -> setting do-retransmission to:" << retrans;
964 s.set(
"do-retransmission", retrans);
971 cleanupCustomPipeline();
973 m_resourceErrorState = ResourceErrorState::NoError;
978 m_discoveryHandler.cancel();
979 m_discoverFuture.cancel();
983 streamURL = qGstRegisterQIODevice(stream);
985 if (content.isEmpty() && !stream) {
986 mediaStatusChanged(QMediaPlayer::NoMedia);
987 resetStateForEmptyOrInvalidMedia();
991 if (isCustomSource()) {
992 setMediaCustomSource(content);
994 mediaStatusChanged(QMediaPlayer::LoadingMedia);
995 m_hasPendingMedia =
true;
996 m_requestedPlaybackState = std::nullopt;
998 const QUrl &playUrl = stream ? streamURL : content;
999 m_discoverFuture = discover(playUrl);
1001 m_discoveryHandler =
1002 m_discoverFuture.then(
this,[
this, playUrl](
const DiscoverResult &result) {
1003 handleDiscoverResult(result, playUrl);
1010 using namespace Qt::Literals;
1011 using namespace std::chrono;
1012 using namespace std::chrono_literals;
1017 m_playbin.set(
"video-sink", QGstElement::createFromPipelineDescription(
"fakesink"));
1018 m_playbin.set(
"text-sink", QGstElement::createFromPipelineDescription(
"fakesink"));
1019 m_playbin.set(
"audio-sink", QGstElement::createFromPipelineDescription(
"fakesink"));
1022 if (QGstElement sink = gstVideoOutput->gstreamerVideoSink()->gstSink())
1027 customPipeline = QGstPipeline::create(
"customPipeline");
1028 customPipeline.installMessageFilter(
this);
1029 positionUpdateTimer = std::make_unique<QTimer>();
1031 QObject::connect(positionUpdateTimer.get(), &QTimer::timeout,
this, [
this] {
1032 Q_ASSERT(customPipeline);
1033 auto position = customPipeline.position();
1035 positionChanged(round<milliseconds>(position));
1038 positionUpdateTimer->start(100ms);
1040 QByteArray gstLaunchString =
1041 content.toString(QUrl::RemoveScheme | QUrl::PrettyDecoded).toLatin1();
1042 qCDebug(qLcMediaPlayer) <<
"generating" << gstLaunchString;
1045 emit error(QMediaPlayer::ResourceError, u"Could not create custom pipeline"_s);
1050 customPipeline.add(decoder);
1053 qGstSafeCast<GstBin>(element.element()),
1057 elementBin.addUnlinkedGhostPads(GstPadDirection::GST_PAD_SRC);
1060 padAdded = decoder.onPadAdded<&QGstreamerMediaPlayer::decoderPadAddedCustomSource>(
this);
1061 padRemoved = decoder.onPadRemoved<&QGstreamerMediaPlayer::decoderPadRemovedCustomSource>(
this);
1063 customPipeline.setStateSync(GstState::GST_STATE_PAUSED);
1065 auto srcPadVisitor = [](GstElement *element, GstPad *pad,
void *self) -> gboolean {
1066 reinterpret_cast<QGstreamerMediaPlayer *>(self)->decoderPadAddedCustomSource(
1067 QGstElement{ element, QGstElement::NeedsRef }, QGstPad{ pad, QGstPad::NeedsRef });
1071 gst_element_foreach_pad(element.element(), srcPadVisitor,
this);
1073 mediaStatusChanged(QMediaPlayer::LoadedMedia);
1075 customPipeline.dumpGraph(
"setMediaCustomPipeline");
1080 customPipeline.setStateSync(GST_STATE_NULL);
1081 customPipeline.removeMessageFilter(
this);
1083 for (QGstElement &sink : customPipelineSinks)
1085 customPipeline.remove(sink);
1087 positionUpdateTimer = {};
1088 customPipeline = {};
1093 if (isCustomSource()) {
1094 qWarning() <<
"QMediaPlayer::setAudioOutput not supported when using custom sources";
1098 if (gstAudioOutput == output)
1103 gstOutput->setAsync(
true);
1107 m_playbin.set(
"audio-sink", gstAudioOutput->gstElement());
1109 m_playbin.set(
"audio-sink", QGstElement::createFromPipelineDescription(
"fakesink"));
1110 updateAudioTrackEnabled();
1114 if (!qmediaplayerDestructorCalled)
1115 m_playbin.finishStateChange();
1133 Q_ASSERT(pluggableSink);
1139 QSpan<
const QMediaMetaData> tracks = m_trackMetaData[type];
1140 return tracks.size();
1145 QSpan<
const QMediaMetaData> tracks = m_trackMetaData[type];
1146 if (index < tracks.size())
1147 return tracks[index];
1153 return m_activeTrack[type];
1158 if (m_activeTrack[type] == index)
1161 int formerTrack = m_activeTrack[type];
1162 m_activeTrack[type] = index;
1165 case TrackType::VideoStream: {
1167 gst_play_set_video_track(m_gstPlay.get(), index);
1168 updateVideoTrackEnabled();
1169 updateNativeSizeOnVideoOutput();
1172 case TrackType::AudioStream: {
1174 gst_play_set_audio_track(m_gstPlay.get(), index);
1175 updateAudioTrackEnabled();
1178 case TrackType::SubtitleStream: {
1180 gst_play_set_subtitle_track(m_gstPlay.get(), index);
1181 gst_play_set_subtitle_track_enabled(m_gstPlay.get(), index != -1);
1188 if (formerTrack != -1 && index != -1)
1191 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)