16#include <QtCore/qdir.h>
17#include <QtCore/qsocketnotifier.h>
18#include <QtCore/qurl.h>
19#include <QtCore/qdebug.h>
20#include <QtCore/qloggingcategory.h>
21#include <QtNetwork/qnetworkaccessmanager.h>
22#include <QtNetwork/qnetworkreply.h>
38 if (
type == SubtitleStream)
42QGstPad QGstreamerMediaPlayer::TrackSelector::createInputPad()
44 auto pad =
selector.getRequestPad(
"sink_%u");
49void QGstreamerMediaPlayer::TrackSelector::removeAllInputPads()
51 for (
auto &pad : tracks)
56void QGstreamerMediaPlayer::TrackSelector::removeInputPad(
QGstPad pad)
59 tracks.removeOne(pad);
62QGstPad QGstreamerMediaPlayer::TrackSelector::inputPad(
int index)
69QGstreamerMediaPlayer::TrackSelector &QGstreamerMediaPlayer::trackSelector(TrackType
type)
71 auto &ts = trackSelectors[
type];
80 return videoOutput.error();
86 QGstElement videoInputSelector(
"input-selector",
"videoInputSelector");
87 if (!videoInputSelector)
90 QGstElement audioInputSelector(
"input-selector",
"audioInputSelector");
91 if (!audioInputSelector)
94 QGstElement subTitleInputSelector(
"input-selector",
"subTitleInputSelector");
95 if (!subTitleInputSelector)
99 audioInputSelector, subTitleInputSelector,
parent);
110 trackSelectors{ { {
VideoStream, videoInputSelector },
113 playerPipeline(
"playerPipeline"),
114 gstVideoOutput(videoOutput)
116 playerPipeline.setFlushOnConfigChanges(
true);
118 gstVideoOutput->setParent(
this);
119 gstVideoOutput->setPipeline(playerPipeline);
121 for (
auto &ts : trackSelectors)
124 playerPipeline.setState(GST_STATE_NULL);
128 gst_pipeline_use_clock(playerPipeline.pipeline(), gst_system_clock_obtain());
134 decodebinType = G_OBJECT_TYPE(decodebin.element());
151 return playerPipeline.
position()/1e6;
161 return m_bufferProgress/100.;
183 if (
pos == currentPos)
205 qCDebug(qLcMediaPlayer) <<
"play().";
206 int ret = playerPipeline.
setState(GST_STATE_PLAYING);
207 if (m_requiresSeekOnPlay) {
210 playerPipeline.
flush();
211 m_requiresSeekOnPlay =
false;
213 if (
ret == GST_STATE_CHANGE_FAILURE)
214 qCDebug(qLcMediaPlayer) <<
"Unable to set the pipeline to the playing state.";
218 positionUpdateTimer.
start(100);
226 positionUpdateTimer.
stop();
229 playerPipeline.
flush();
231 int ret = playerPipeline.
setState(GST_STATE_PAUSED);
232 if (
ret == GST_STATE_CHANGE_FAILURE)
233 qCDebug(qLcMediaPlayer) <<
"Unable to set the pipeline to the paused state.";
249void QGstreamerMediaPlayer::stopOrEOS(
bool eos)
251 positionUpdateTimer.
stop();
255 qCDebug(qLcMediaPlayer) <<
"Unable to set the pipeline to the stopped state.";
270 GstMessage* gm =
message.rawMessage();
272 case GST_MESSAGE_TAG: {
274 GstTagList *tag_list;
275 gst_message_parse_tag(gm, &tag_list);
282 case GST_MESSAGE_DURATION_CHANGED: {
284 qCDebug(qLcMediaPlayer) <<
" duration changed message" <<
d;
285 if (
d != m_duration) {
291 case GST_MESSAGE_EOS:
298 case GST_MESSAGE_BUFFERING: {
299 qCDebug(qLcMediaPlayer) <<
" buffering message";
301 gst_message_parse_buffering(gm, &progress);
302 m_bufferProgress = progress;
307 case GST_MESSAGE_STATE_CHANGED: {
308 if (
message.source() != playerPipeline)
331 case GST_STATE_VOID_PENDING:
333 case GST_STATE_READY:
335 case GST_STATE_PAUSED:
338 qCDebug(qLcMediaPlayer) <<
"Preroll done, setting status to Loaded";
340 GST_DEBUG_BIN_TO_DOT_FILE(playerPipeline.
bin(), GST_DEBUG_GRAPH_SHOW_ALL,
"playerPipeline");
343 if (
d != m_duration) {
345 qCDebug(qLcMediaPlayer) <<
" duration changed" <<
d;
349 parseStreamsAndMetadata();
354 GstQuery *
query = gst_query_new_seeking(GST_FORMAT_TIME);
355 gboolean canSeek =
false;
356 if (gst_element_query(playerPipeline.
element(),
query)) {
357 gst_query_parse_seeking(
query, NULL, &canSeek,
nullptr,
nullptr);
358 qCDebug(qLcMediaPlayer) <<
" pipeline is seekable:" << canSeek;
360 qCDebug(qLcMediaPlayer) <<
" query for seekable failed.";
362 gst_query_unref(
query);
368 case GST_STATE_PLAYING:
374 case GST_MESSAGE_ERROR: {
377 gst_message_parse_error(gm, &err, &
debug);
378 if (err->domain == GST_STREAM_ERROR && err->code == GST_STREAM_ERROR_CODEC_NOT_FOUND)
388 case GST_MESSAGE_WARNING: {
391 gst_message_parse_warning (gm, &err, &
debug);
398 case GST_MESSAGE_INFO: {
399 if (qLcMediaPlayer().isDebugEnabled()) {
402 gst_message_parse_info (gm, &err, &
debug);
409 case GST_MESSAGE_SEGMENT_START: {
410 qCDebug(qLcMediaPlayer) <<
" segment start message, updating position";
412 auto p = structure[
"position"].toInt64();
419 case GST_MESSAGE_ELEMENT: {
422 if (
type ==
"stream-topology") {
424 topology = structure.
copy();
440#if QT_CONFIG(gstreamer_gl)
441 if (
message.type() != GST_MESSAGE_NEED_CONTEXT)
443 const gchar *
type =
nullptr;
444 gst_message_parse_context_type (
message.rawMessage(), &
type);
445 if (strcmp(
type, GST_GL_DISPLAY_CONTEXT_TYPE))
452 gst_element_set_context(GST_ELEMENT(GST_MESSAGE_SRC(
message.rawMessage())),
context);
453 playerPipeline.
dumpGraph(
"need_context");
478 qCDebug(qLcMediaPlayer) <<
"Received new pad" << pad.
name() <<
"from" <<
src.name() <<
"type" <<
type;
479 qCDebug(qLcMediaPlayer) <<
" " << caps.toString();
482 if (
type.startsWith(
"video/x-raw")) {
484 }
else if (
type.startsWith(
"audio/x-raw")) {
486 }
else if (
type.startsWith(
"text/")) {
489 qCWarning(qLcMediaPlayer) <<
"Ignoring unknown media stream:" << pad.
name() <<
type;
493 auto &ts = trackSelector(streamType);
494 QGstPad sinkPad = ts.createInputPad();
495 if (!pad.
link(sinkPad)) {
496 qCWarning(qLcMediaPlayer) <<
"Failed to add track, cannot link pads";
499 qCDebug(qLcMediaPlayer) <<
"Adding track";
501 if (ts.trackCount() == 1) {
504 ts.setActiveInputPad(sinkPad);
509 ts.setActiveInputPad(sinkPad);
525 qCDebug(qLcMediaPlayer) <<
"Removed pad" << pad.
name() <<
"from" <<
src.name();
526 auto track = decoderOutputMap.
value(pad.
name());
530 auto ts = std::find_if(std::begin(trackSelectors), std::end(trackSelectors),
531 [&](TrackSelector &ts){
return ts.selector == track.parent(); });
532 if (ts == std::end(trackSelectors))
535 qCDebug(qLcMediaPlayer) <<
" was linked to pad" << track.name() <<
"from" << ts->selector.name();
536 ts->removeInputPad(track);
538 if (ts->trackCount() == 0) {
550void QGstreamerMediaPlayer::removeAllOutputs()
552 for (
auto &ts : trackSelectors) {
554 ts.removeAllInputPads();
560void QGstreamerMediaPlayer::connectOutput(TrackSelector &ts)
582 qCDebug(qLcMediaPlayer) <<
"connecting output for track type" << ts.type;
583 playerPipeline.
add(
e);
585 e.setState(GST_STATE_PAUSED);
588 ts.isConnected =
true;
591void QGstreamerMediaPlayer::removeOutput(TrackSelector &ts)
613 qCDebug(qLcMediaPlayer) <<
"removing output for track type" << ts.type;
615 e.setStateSync(GST_STATE_NULL);
618 ts.isConnected =
false;
624 qCDebug(qLcMediaPlayer) <<
"New element added to uridecodebin:" <<
c.name();
626 if (G_OBJECT_TYPE(
child) == that->decodebinType) {
627 qCDebug(qLcMediaPlayer) <<
" -> setting post-stream-topology property";
628 c.set(
"post-stream-topology",
true);
637 qCDebug(qLcMediaPlayer) <<
"Setting up source:" << g_type_name_from_instance((GTypeInstance*)
source);
646 qCDebug(qLcMediaPlayer) <<
" -> setting source latency to:" << latency <<
"ms";
647 s.set(
"latency", latency);
653 qCDebug(qLcMediaPlayer) <<
" -> setting drop-on-latency to:" << drop;
654 s.set(
"drop-on-latency", drop);
658 if (
ok &&
v not_eq 0)
660 qCDebug(qLcMediaPlayer) <<
" -> setting do-retransmission to:" << retrans;
661 s.set(
"do-retransmission", retrans);
673 qCDebug(qLcMediaPlayer) <<
"Unable to set the pipeline to the stopped state.";
681 playerPipeline.
remove(decoder);
688 if (m_duration != 0) {
708 m_appSrc = maybeAppSrc.value();
720 decoder.
set(
"post-stream-topology",
true);
721 playerPipeline.
add(
src, decoder);
724 m_appSrc->
setup(m_stream);
728 decoder =
QGstElement(
"uridecodebin",
"uridecoder");
733 playerPipeline.
add(decoder);
735 decoder.
connect(
"element-added", GCallback(QGstreamerMediaPlayer::uridecodebinElementAddedCallback),
this);
736 decoder.
connect(
"source-setup", GCallback(QGstreamerMediaPlayer::sourceSetupCallback),
this);
739 if (m_bufferProgress != 0) {
740 m_bufferProgress = 0;
744 decoder.
onPadAdded<&QGstreamerMediaPlayer::decoderPadAdded>(
this);
745 decoder.
onPadRemoved<&QGstreamerMediaPlayer::decoderPadRemoved>(
this);
750 int ret = playerPipeline.
setState(GST_STATE_PLAYING);
751 if (
ret == GST_STATE_CHANGE_FAILURE)
752 qCWarning(qLcMediaPlayer) <<
"Unable to set the pipeline to the playing state.";
754 int ret = playerPipeline.
setState(GST_STATE_PAUSED);
756 qCWarning(qLcMediaPlayer) <<
"Unable to set the pipeline to the paused state.";
765 if (gstAudioOutput ==
output)
771 if (gstAudioOutput) {
776 if (gstAudioOutput) {
797 auto next =
e[
"next"].toStructure();
806void QGstreamerMediaPlayer::parseStreamsAndMetadata()
808 qCDebug(qLcMediaPlayer) <<
"============== parse topology ============";
810 qCDebug(qLcMediaPlayer) <<
" null topology";
813 auto caps = topology[
"caps"].toCaps();
814 auto structure = caps.at(0);
820 QGValue tags = topology[
"tags"];
822 GstTagList *tagList =
nullptr;
823 gst_structure_get(topology.
structure,
"tags", GST_TYPE_TAG_LIST, &tagList,
nullptr);
830 auto next = demux[
"next"];
831 if (!
next.isList()) {
832 qCDebug(qLcMediaPlayer) <<
" no additional streams";
839 for (
int i = 0;
i <
size; ++
i) {
841 caps =
val.toStructure()[
"caps"].toCaps();
842 structure = caps.at(0);
843 if (structure.name().startsWith(
"audio/")) {
846 qCDebug(qLcMediaPlayer) <<
" audio" << caps.toString() << (int)
codec;
847 }
else if (structure.name().startsWith(
"video/")) {
850 qCDebug(qLcMediaPlayer) <<
" video" << caps.toString() << (int)
codec;
851 auto framerate = structure[
"framerate"].getFraction();
854 auto width = structure[
"width"].toInt();
855 auto height = structure[
"height"].toInt();
861 auto sinkPad = trackSelector(
VideoStream).activeInputPad();
863 bool hasTags = g_object_class_find_property (G_OBJECT_GET_CLASS (sinkPad.
object()),
"tags") != NULL;
865 GstTagList *tl =
nullptr;
866 g_object_get(sinkPad.
object(),
"tags", &tl,
nullptr);
867 qCDebug(qLcMediaPlayer) <<
" tags=" << hasTags << (tl ? gst_tag_list_to_string(tl) :
"(null)");
871 qCDebug(qLcMediaPlayer) <<
"============== end parse topology ============";
878 return trackSelector(
type).trackCount();
883 auto track = trackSelector(
type).inputPad(
index);
887 GstTagList *tagList =
nullptr;
888 g_object_get(track.object(),
"tags", &tagList,
nullptr);
895 return trackSelector(
type).activeInputIndex();
900 auto &ts = trackSelector(
type);
901 auto track = ts.inputPad(
index);
902 if (track.isNull() &&
index != -1) {
903 qCWarning(qLcMediaPlayer) <<
"Attempt to set an incorrect index" <<
index
904 <<
"for the track type" <<
type;
908 qCDebug(qLcMediaPlayer) <<
"Setting the index" <<
index <<
"for the track type" <<
type;
913 if (track.isNull()) {
916 ts.setActiveInputPad(track);
922 if (playerPipeline.
state() == GST_STATE_PLAYING)
923 playerPipeline.
flush();
925 m_requiresSeekOnPlay =
true;
930#include "moc_qgstreamermediaplayer_p.cpp"
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
bool setup(QIODevice *stream=nullptr, qint64 offset=0)
static QMaybe< QGstAppSrc * > create(QObject *parent=nullptr)
void remove(const QGstElement &element)
void add(const QGstElement &element)
QGstStructure at(int index) const
GstElement * element() const
void onPadRemoved(T *instance)
void onPadAdded(T *instance)
bool setStateSync(GstState state)
const char * name() const
void set(const char *property, const char *str)
void connect(const char *name, GCallback callback, gpointer userData)
GstObject * object() const
bool link(const QGstPad &sink) const
QGstCaps currentCaps() const
GstStateChangeReturn setState(GstState state)
bool inStoppedState() const
void dumpGraph(const char *fileName)
void removeMessageFilter(QGstreamerSyncMessageFilter *filter)
double playbackRate() const
bool setPlaybackRate(double rate)
void setInStoppedState(bool stopped)
bool setPosition(qint64 pos)
QGstStructure copy() const
const GstStructure * structure
QByteArrayView name() const
void setPipeline(const QGstPipeline &pipeline)
QGstElement gstElement() const
QGstreamerVideoSink * gstreamerVideoSink() const
QGstElement gstElement() const
void linkSubtitleStream(QGstElement subtitleSrc)
void setVideoSink(QVideoSink *sink)
static QMaybe< QGstreamerVideoOutput * > create(QObject *parent=nullptr)
void unlinkSubtitleStream()
GstContext * gstGlDisplayContext() const
T value(const Key &key) const noexcept
iterator insert(const Key &key, const T &value)
Inserts a new item with the key and a value of value.
\inmodule QtCore \reentrant
QObject * parent() const
Returns a pointer to the parent object.
static QString fromLocal8Bit(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
uint toUInt(bool *ok=nullptr, int base=10) const
Returns the string converted to an {unsigned int} using base base, which is 10 by default and must be...
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
void start(int msec)
Starts or restarts the timer with a timeout interval of msec milliseconds.
void stop()
Stops the timer.
void timeout(QPrivateSignal)
This signal is emitted when the timer times out.
QByteArray toEncoded(FormattingOptions options=FullyEncoded) const
Returns the encoded representation of the URL if it's valid; otherwise an empty QByteArray is returne...
bool isEmpty() const
Returns true if the URL has no data; otherwise returns false.
static auto fromValue(T &&value) noexcept(std::is_nothrow_copy_constructible_v< T > &&Private::CanUseInternalSpace< T >) -> std::enable_if_t< std::conjunction_v< std::is_copy_constructible< T >, std::is_destructible< T > >, QVariant >
The QVideoSink class represents a generic sink for video data.
cache insert(employee->id(), employee)
void newState(QList< State > &states, const char *token, const char *lexem, bool pre)
Combined button and popup list for selecting options.
DBusConnection const char DBusError * error
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char return DBusMessage return DBusMessage const char return DBusMessage dbus_bool_t return DBusMessage dbus_uint32_t return DBusMessage return DBusPendingCall * pending
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
QString errorMessageCannotFindElement(std::string_view element)
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
n void setPosition(void) \n\
GLsizei const GLfloat * v
[13]
GLint GLsizei GLsizei height
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLuint GLsizei const GLchar * message
GLsizei GLsizei GLchar * source
GLsizei GLenum GLboolean sink
static void add(QPainterPath &path, const QWingedEdge &list, int edge, QPathEdge::Traversal traversal)
QLatin1StringView QLatin1String
#define QStringLiteral(str)
Q_CORE_EXPORT QByteArray qgetenv(const char *varName)
QT_BEGIN_NAMESPACE typedef uchar * output
QFileSelector selector
[1]
connect(quitButton, &QPushButton::clicked, &app, &QCoreApplication::quit, Qt::QueuedConnection)
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent