diff --git a/src/engine/gstenginepipeline.cpp b/src/engine/gstenginepipeline.cpp index 3bfb51355..405752275 100644 --- a/src/engine/gstenginepipeline.cpp +++ b/src/engine/gstenginepipeline.cpp @@ -94,11 +94,12 @@ GstEnginePipeline::GstEnginePipeline(QObject *parent) next_end_offset_nanosec_(-1), ignore_next_seek_(false), ignore_tags_(false), - pipeline_is_initialized_(false), + pipeline_is_active_(false), pipeline_is_connected_(false), pending_seek_nanosec_(-1), last_known_position_ns_(0), next_uri_set_(false), + next_uri_reset_(false), ebur128_loudness_normalizing_gain_db_(0.0), volume_set_(false), volume_internal_(-1.0), @@ -281,6 +282,25 @@ void GstEnginePipeline::set_fading_enabled(const bool enabled) { fading_enabled_ = enabled; } +QString GstEnginePipeline::GstStateText(const GstState state) { + + switch (state) { + case GST_STATE_VOID_PENDING: + return "Pending"; + case GST_STATE_NULL: + return "Null"; + case GST_STATE_READY: + return "Ready"; + case GST_STATE_PAUSED: + return "Paused"; + case GST_STATE_PLAYING: + return "Playing"; + default: + return "Unknown"; + } + +} + GstElement *GstEnginePipeline::CreateElement(const QString &factory_name, const QString &name, GstElement *bin, QString &error) const { QString unique_name = QString("pipeline") + "-" + QString::number(id_) + "-" + (name.isEmpty() ? factory_name : name); @@ -929,7 +949,7 @@ void GstEnginePipeline::PadAddedCallback(GstElement *element, GstPad *pad, gpoin instance->playbin_probe_cb_id_ = gst_pad_add_probe(pad, static_cast(GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM | GST_PAD_PROBE_TYPE_EVENT_FLUSH), PlaybinProbeCallback, instance, nullptr); instance->pipeline_is_connected_ = true; - if (instance->pending_seek_nanosec_ != -1 && instance->pipeline_is_initialized_) { + if (instance->pending_seek_nanosec_ != -1 && instance->pipeline_is_active_) { QMetaObject::invokeMethod(instance, "Seek", Qt::QueuedConnection, Q_ARG(qint64, instance->pending_seek_nanosec_)); } @@ -1252,6 +1272,7 @@ void GstEnginePipeline::StreamStartMessageReceived() { if (next_uri_set_) { qLog(Debug) << "Stream changed from URL" << gst_url_ << "to" << next_gst_url_; next_uri_set_ = false; + next_uri_reset_ = false; about_to_finish_ = false; media_url_ = next_media_url_; stream_url_ = next_stream_url_; @@ -1312,7 +1333,7 @@ void GstEnginePipeline::ErrorMessageReceived(GstMessage *msg) { g_error_free(error); g_free(debugs); - if (pipeline_is_initialized_ && next_uri_set_ && (domain == GST_CORE_ERROR || domain == GST_RESOURCE_ERROR || domain == GST_STREAM_ERROR)) { + if (pipeline_is_active_ && next_uri_set_ && (domain == GST_CORE_ERROR || domain == GST_RESOURCE_ERROR || domain == GST_STREAM_ERROR)) { // A track is still playing and the next uri is not playable. We ignore the error here so it can play until the end. // But there is no message send to the bus when the current track finishes, we have to add an EOS ourself. qLog(Info) << "Ignoring error" << domain << code << message << debugstr << "when loading next track"; @@ -1430,26 +1451,44 @@ void GstEnginePipeline::StateChangedMessageReceived(GstMessage *msg) { GstState old_state = GST_STATE_NULL, new_state = GST_STATE_NULL, pending = GST_STATE_NULL; gst_message_parse_state_changed(msg, &old_state, &new_state, &pending); - if (!pipeline_is_initialized_ && (new_state == GST_STATE_PAUSED || new_state == GST_STATE_PLAYING)) { - qLog(Debug) << "Pipeline initialized: State changed from" << old_state << "to" << new_state; - pipeline_is_initialized_ = true; - if (!volume_set_) { - SetVolume(volume_percent_); - } - if (pending_seek_nanosec_ != -1 && pipeline_is_connected_) { - QMetaObject::invokeMethod(this, "Seek", Qt::QueuedConnection, Q_ARG(qint64, pending_seek_nanosec_)); + qLog(Debug) << "Pipeline state changed from" << GstStateText(old_state) << "to" << GstStateText(new_state); + + if (!pipeline_is_active_ && (new_state == GST_STATE_PAUSED || new_state == GST_STATE_PLAYING)) { + qLog(Debug) << "Pipeline changed to active"; + pipeline_is_active_ = true; + if (pipeline_is_connected_) { + if (!volume_set_) { + SetVolume(volume_percent_); + } + if (pending_seek_nanosec_ != -1) { + if (next_uri_reset_ && new_state == GST_STATE_PAUSED) { + qLog(Debug) << "Reverting next uri and going to playing state."; + next_uri_reset_ = false; + SeekDelayed(pending_seek_nanosec_); + SetStateDelayed(GST_STATE_PLAYING); + } + else { + SeekQueued(pending_seek_nanosec_); + } + } } } - if (pipeline_is_initialized_ && new_state != GST_STATE_PAUSED && new_state != GST_STATE_PLAYING) { - qLog(Debug) << "Pipeline uninitialized: State changed from" << old_state << "to" << new_state; - pipeline_is_initialized_ = false; - + else if (pipeline_is_active_ && new_state != GST_STATE_PAUSED && new_state != GST_STATE_PLAYING) { + qLog(Debug) << "Pipeline changed to inactive"; + pipeline_is_active_ = false; if (next_uri_set_ && new_state == GST_STATE_READY) { - // Revert uri and go back to PLAY state again next_uri_set_ = false; g_object_set(G_OBJECT(pipeline_), "uri", gst_url_.constData(), nullptr); - SetState(GST_STATE_PLAYING); + if (pending_seek_nanosec_ == -1) { + qLog(Debug) << "Reverting next uri and going to playing state."; + SetState(GST_STATE_PLAYING); + } + else { + qLog(Debug) << "Reverting next uri and going to paused state."; + next_uri_reset_ = true; + SetState(GST_STATE_PAUSED); + } } } @@ -1487,7 +1526,7 @@ void GstEnginePipeline::BufferingMessageReceived(GstMessage *msg) { qint64 GstEnginePipeline::position() const { - if (pipeline_is_initialized_) { + if (pipeline_is_active_) { gint64 current_position = 0; if (gst_element_query_position(pipeline_, GST_FORMAT_TIME, ¤t_position)) { last_known_position_ns_ = current_position; @@ -1519,7 +1558,16 @@ GstState GstEnginePipeline::state() const { } QFuture GstEnginePipeline::SetState(const GstState state) { + + qLog(Debug) << "Setting pipeline state to" << GstStateText(state); return QtConcurrent::run(&set_state_threadpool_, &gst_element_set_state, pipeline_, state); + +} + +void GstEnginePipeline::SetStateDelayed(const GstState state) { + + QTimer::singleShot(300, this, [this, state]() { SetState(state); }); + } bool GstEnginePipeline::Seek(const qint64 nanosec) { @@ -1529,7 +1577,7 @@ bool GstEnginePipeline::Seek(const qint64 nanosec) { return true; } - if (!pipeline_is_connected_ || !pipeline_is_initialized_) { + if (!pipeline_is_connected_ || !pipeline_is_active_) { pending_seek_nanosec_ = nanosec; return true; } @@ -1542,10 +1590,25 @@ bool GstEnginePipeline::Seek(const qint64 nanosec) { pending_seek_nanosec_ = -1; last_known_position_ns_ = nanosec; + + qLog(Debug) << "Seeking to" << nanosec; + return gst_element_seek_simple(pipeline_, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, nanosec); } +void GstEnginePipeline::SeekQueued(const qint64 nanosec) { + + QMetaObject::invokeMethod(this, "Seek", Qt::QueuedConnection, Q_ARG(qint64, nanosec)); + +} + +void GstEnginePipeline::SeekDelayed(const qint64 nanosec) { + + QTimer::singleShot(100, this, [this, nanosec]() { SeekQueued(nanosec); }); + +} + void GstEnginePipeline::SetEBUR128LoudnessNormalizingGain_dB(const double ebur128_loudness_normalizing_gain_db) { ebur128_loudness_normalizing_gain_db_ = ebur128_loudness_normalizing_gain_db; @@ -1570,7 +1633,7 @@ void GstEnginePipeline::SetVolume(const uint volume_percent) { if (!volume_set_ || volume_internal != volume_internal_) { volume_internal_ = volume_internal; g_object_set(G_OBJECT(volume_), "volume", volume_internal, nullptr); - if (pipeline_is_initialized_) { + if (pipeline_is_active_) { volume_set_ = true; } } diff --git a/src/engine/gstenginepipeline.h b/src/engine/gstenginepipeline.h index a0db364fb..7119855c6 100644 --- a/src/engine/gstenginepipeline.h +++ b/src/engine/gstenginepipeline.h @@ -85,8 +85,11 @@ class GstEnginePipeline : public QObject { void RemoveAllBufferConsumers(); // Control the music playback - QFuture SetState(const GstState state); + Q_INVOKABLE QFuture SetState(const GstState state); + void SetStateDelayed(const GstState state); Q_INVOKABLE bool Seek(const qint64 nanosec); + void SeekQueued(const qint64 nanosec); + void SeekDelayed(const qint64 nanosec); void SetEBUR128LoudnessNormalizingGain_dB(const double ebur128_loudness_normalizing_gain_db); void SetVolume(const uint volume_percent); void SetStereoBalance(const float value); @@ -148,6 +151,7 @@ class GstEnginePipeline : public QObject { void timerEvent(QTimerEvent*) override; private: + static QString GstStateText(const GstState state); GstElement *CreateElement(const QString &factory_name, const QString &name, GstElement *bin, QString &error) const; bool InitAudioBin(QString &error); void SetupVolume(GstElement *element); @@ -277,7 +281,7 @@ class GstEnginePipeline : public QObject { // Seeking while the pipeline is in the READY state doesn't work, so we have to wait until it goes to PAUSED or PLAYING. // Also, we have to wait for the playbin to be connected. - bool pipeline_is_initialized_; + bool pipeline_is_active_; bool pipeline_is_connected_; qint64 pending_seek_nanosec_; @@ -288,6 +292,7 @@ class GstEnginePipeline : public QObject { // Complete the transition to the next song when it starts playing bool next_uri_set_; + bool next_uri_reset_; double ebur128_loudness_normalizing_gain_db_; bool volume_set_;