Wait for set state to finish before deleting pipeline
Setting state to GST_STATE_NULL sometimes blocks, to fix this use the threadpool to set the state to NULL and wait with deleting the pipeline until the state is changed. This fixes blocking the main thread when switching Spotify songs.
This commit is contained in:
@@ -98,13 +98,15 @@ GstEngine::GstEngine(SharedPtr<TaskManager> task_manager, QObject *parent)
|
||||
waiting_to_seek_(false),
|
||||
seek_pos_(0),
|
||||
timer_id_(-1),
|
||||
is_fading_out_to_pause_(false),
|
||||
has_faded_out_(false),
|
||||
has_faded_out_to_pause_(false),
|
||||
scope_chunk_(0),
|
||||
have_new_buffer_(false),
|
||||
scope_chunks_(0),
|
||||
discovery_finished_cb_id_(-1),
|
||||
discovery_discovered_cb_id_(-1) {
|
||||
discovery_discovered_cb_id_(-1),
|
||||
delayed_state_(State::Empty),
|
||||
delayed_state_pause_(false),
|
||||
delayed_state_offset_nanosec_(0) {
|
||||
|
||||
seek_timer_->setSingleShot(true);
|
||||
seek_timer_->setInterval(kSeekDelayNanosec / kNsecPerMsec);
|
||||
@@ -205,20 +207,27 @@ bool GstEngine::Load(const QUrl &media_url, const QUrl &stream_url, const Engine
|
||||
return true;
|
||||
}
|
||||
|
||||
SharedPtr<GstEnginePipeline> pipeline = CreatePipeline(media_url, stream_url, gst_url, force_stop_at_end ? end_nanosec : 0, ebur128_loudness_normalizing_gain_db_);
|
||||
GstEnginePipelinePtr pipeline = CreatePipeline(media_url, stream_url, gst_url, force_stop_at_end ? end_nanosec : 0, ebur128_loudness_normalizing_gain_db_);
|
||||
if (!pipeline) return false;
|
||||
|
||||
if (crossfade) StartFadeout();
|
||||
if (crossfade && current_pipeline_ && !ExclusivePipelineActive() && !fadeout_pipelines_.contains(current_pipeline_->id())) {
|
||||
StartFadeout();
|
||||
}
|
||||
|
||||
BufferingFinished();
|
||||
|
||||
GstEnginePipelinePtr old_pipeline = current_pipeline_;
|
||||
current_pipeline_ = pipeline;
|
||||
if (old_pipeline && !fadeout_pipelines_.contains(old_pipeline->id())) {
|
||||
FinishPipeline(old_pipeline);
|
||||
}
|
||||
|
||||
SetVolume(volume_);
|
||||
SetStereoBalance(stereo_balance_);
|
||||
SetEqualizerParameters(equalizer_preamp_, equalizer_gains_);
|
||||
|
||||
// Maybe fade in this track
|
||||
if (crossfade) {
|
||||
if (crossfade && !ExclusivePipelineActive()) {
|
||||
current_pipeline_->StartFader(fadeout_duration_nanosec_, QTimeLine::Forward);
|
||||
}
|
||||
|
||||
@@ -243,26 +252,38 @@ bool GstEngine::Load(const QUrl &media_url, const QUrl &stream_url, const Engine
|
||||
|
||||
}
|
||||
|
||||
bool GstEngine::Play(const quint64 offset_nanosec) {
|
||||
bool GstEngine::Play(const bool pause, const quint64 offset_nanosec) {
|
||||
|
||||
EnsureInitialized();
|
||||
|
||||
if (!current_pipeline_ || current_pipeline_->is_buffering()) return false;
|
||||
|
||||
if (ExclusivePipelineActive()) {
|
||||
qLog(Debug) << "Delaying play because a exclusive pipeline is already active...";
|
||||
delayed_state_ = pause ? State::Paused : State::Playing;
|
||||
delayed_state_pause_ = pause;
|
||||
delayed_state_offset_nanosec_ = offset_nanosec;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (fadeout_pause_pipeline_) {
|
||||
StopFadeoutPause();
|
||||
}
|
||||
|
||||
delayed_state_ = State::Empty;
|
||||
delayed_state_pause_ = false;
|
||||
delayed_state_offset_nanosec_ = 0;
|
||||
|
||||
QFutureWatcher<GstStateChangeReturn> *watcher = new QFutureWatcher<GstStateChangeReturn>();
|
||||
const int pipeline_id = current_pipeline_->id();
|
||||
QObject::connect(watcher, &QFutureWatcher<GstStateChangeReturn>::finished, this, [this, watcher, pipeline_id, offset_nanosec]() {
|
||||
QObject::connect(watcher, &QFutureWatcher<GstStateChangeReturn>::finished, this, [this, watcher, pipeline_id, pause, offset_nanosec]() {
|
||||
const GstStateChangeReturn ret = watcher->result();
|
||||
watcher->deleteLater();
|
||||
PlayDone(ret, offset_nanosec, pipeline_id);
|
||||
PlayDone(ret, pause, offset_nanosec, pipeline_id);
|
||||
});
|
||||
QFuture<GstStateChangeReturn> future = current_pipeline_->SetState(GST_STATE_PLAYING);
|
||||
QFuture<GstStateChangeReturn> future = current_pipeline_->Play(pause, offset_nanosec);
|
||||
watcher->setFuture(future);
|
||||
|
||||
if (is_fading_out_to_pause_) {
|
||||
current_pipeline_->SetState(GST_STATE_PAUSED);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
@@ -271,24 +292,33 @@ void GstEngine::Stop(const bool stop_after) {
|
||||
|
||||
StopTimers();
|
||||
|
||||
delayed_state_ = State::Empty;
|
||||
delayed_state_pause_ = false;
|
||||
delayed_state_offset_nanosec_ = 0;
|
||||
|
||||
media_url_.clear();
|
||||
stream_url_.clear(); // To ensure we return Empty from state()
|
||||
beginning_nanosec_ = end_nanosec_ = 0;
|
||||
|
||||
// Check if we started a fade out. If it isn't finished yet and the user pressed stop, we cancel the fader and just stop the playback.
|
||||
if (is_fading_out_to_pause_) {
|
||||
QObject::disconnect(&*current_pipeline_, &GstEnginePipeline::FaderFinished, nullptr, nullptr);
|
||||
is_fading_out_to_pause_ = false;
|
||||
has_faded_out_ = true;
|
||||
|
||||
fadeout_pause_pipeline_.reset();
|
||||
fadeout_pipeline_.reset();
|
||||
if (fadeout_pause_pipeline_) {
|
||||
StopFadeoutPause();
|
||||
}
|
||||
|
||||
if (fadeout_enabled_ && current_pipeline_ && !stop_after) StartFadeout();
|
||||
if (current_pipeline_) {
|
||||
if (fadeout_enabled_ && !stop_after && !ExclusivePipelineActive()) {
|
||||
StartFadeout();
|
||||
current_pipeline_ = GstEnginePipelinePtr();
|
||||
}
|
||||
else {
|
||||
GstEnginePipelinePtr old_pipeline = current_pipeline_;
|
||||
current_pipeline_ = GstEnginePipelinePtr();
|
||||
FinishPipeline(old_pipeline);
|
||||
}
|
||||
}
|
||||
|
||||
current_pipeline_.reset();
|
||||
BufferingFinished();
|
||||
|
||||
emit StateChanged(State::Empty);
|
||||
|
||||
}
|
||||
@@ -297,22 +327,20 @@ void GstEngine::Pause() {
|
||||
|
||||
if (!current_pipeline_ || current_pipeline_->is_buffering()) return;
|
||||
|
||||
// Check if we started a fade out. If it isn't finished yet and the user pressed play, we inverse the fader and resume the playback.
|
||||
if (is_fading_out_to_pause_) {
|
||||
QObject::disconnect(&*current_pipeline_, &GstEnginePipeline::FaderFinished, nullptr, nullptr);
|
||||
current_pipeline_->StartFader(fadeout_pause_duration_nanosec_, QTimeLine::Forward, QEasingCurve::InOutQuad, false);
|
||||
is_fading_out_to_pause_ = false;
|
||||
has_faded_out_ = false;
|
||||
emit StateChanged(State::Playing);
|
||||
delayed_state_ = State::Empty;
|
||||
delayed_state_pause_ = false;
|
||||
delayed_state_offset_nanosec_ = 0;
|
||||
|
||||
if (fadeout_pause_pipeline_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (current_pipeline_->state() == GST_STATE_PLAYING) {
|
||||
if (fadeout_pause_enabled_) {
|
||||
if (fadeout_pause_enabled_ && !ExclusivePipelineActive()) {
|
||||
StartFadeoutPause();
|
||||
}
|
||||
else {
|
||||
current_pipeline_->SetState(GST_STATE_PAUSED);
|
||||
current_pipeline_->SetStateAsync(GST_STATE_PAUSED);
|
||||
emit StateChanged(State::Paused);
|
||||
StopTimers();
|
||||
}
|
||||
@@ -325,20 +353,22 @@ void GstEngine::Unpause() {
|
||||
if (!current_pipeline_ || current_pipeline_->is_buffering()) return;
|
||||
|
||||
if (current_pipeline_->state() == GST_STATE_PAUSED) {
|
||||
current_pipeline_->SetState(GST_STATE_PLAYING);
|
||||
|
||||
// Check if we faded out last time. If yes, fade in no matter what the settings say.
|
||||
// If we pause with fadeout, deactivate fadeout and resume playback, the player would be muted if not faded in.
|
||||
if (has_faded_out_) {
|
||||
if (has_faded_out_to_pause_ && !ExclusivePipelineActive()) {
|
||||
QObject::disconnect(&*current_pipeline_, &GstEnginePipeline::FaderFinished, nullptr, nullptr);
|
||||
current_pipeline_->StartFader(fadeout_pause_duration_nanosec_, QTimeLine::Forward, QEasingCurve::InOutQuad, false);
|
||||
has_faded_out_ = false;
|
||||
current_pipeline_->StartFader(fadeout_pause_duration_nanosec_, QTimeLine::Forward, QEasingCurve::Linear, false);
|
||||
has_faded_out_to_pause_ = false;
|
||||
}
|
||||
|
||||
current_pipeline_->SetStateAsync(GST_STATE_PLAYING);
|
||||
|
||||
emit StateChanged(State::Playing);
|
||||
|
||||
StartTimers();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::Seek(const quint64 offset_nanosec) {
|
||||
@@ -352,6 +382,7 @@ void GstEngine::Seek(const quint64 offset_nanosec) {
|
||||
SeekNow();
|
||||
seek_timer_->start(); // Stop us from seeking again for a little while
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::SetVolumeSW(const uint volume) {
|
||||
@@ -621,25 +652,27 @@ void GstEngine::AddBufferToScope(GstBuffer *buf, const int pipeline_id, const QS
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::FadeoutFinished() {
|
||||
void GstEngine::FadeoutFinished(const int pipeline_id) {
|
||||
|
||||
if (!fadeout_pipelines_.contains(pipeline_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
GstEnginePipelinePtr pipeline = fadeout_pipelines_.value(pipeline_id);
|
||||
fadeout_pipelines_.remove(pipeline_id);
|
||||
FinishPipeline(pipeline);
|
||||
|
||||
fadeout_pipeline_.reset();
|
||||
emit FadeoutFinishedSignal();
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::FadeoutPauseFinished() {
|
||||
|
||||
fadeout_pause_pipeline_->SetState(GST_STATE_PAUSED);
|
||||
current_pipeline_->SetState(GST_STATE_PAUSED);
|
||||
fadeout_pause_pipeline_->SetStateAsync(GST_STATE_PAUSED);
|
||||
emit StateChanged(State::Paused);
|
||||
StopTimers();
|
||||
|
||||
is_fading_out_to_pause_ = false;
|
||||
has_faded_out_ = true;
|
||||
fadeout_pause_pipeline_.reset();
|
||||
fadeout_pipeline_.reset();
|
||||
|
||||
has_faded_out_to_pause_ = true;
|
||||
fadeout_pause_pipeline_ = GstEnginePipelinePtr();
|
||||
emit FadeoutFinishedSignal();
|
||||
|
||||
}
|
||||
@@ -657,7 +690,7 @@ void GstEngine::SeekNow() {
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::PlayDone(const GstStateChangeReturn ret, const quint64 offset_nanosec, const int pipeline_id) {
|
||||
void GstEngine::PlayDone(const GstStateChangeReturn ret, const bool pause, const quint64 offset_nanosec, const int pipeline_id) {
|
||||
|
||||
if (!current_pipeline_ || pipeline_id != current_pipeline_->id()) {
|
||||
return;
|
||||
@@ -668,26 +701,33 @@ void GstEngine::PlayDone(const GstStateChangeReturn ret, const quint64 offset_na
|
||||
const QByteArray redirect_url = current_pipeline_->redirect_url();
|
||||
if (!redirect_url.isEmpty() && redirect_url != current_pipeline_->gst_url()) {
|
||||
qLog(Info) << "Redirecting to" << redirect_url;
|
||||
GstEnginePipelinePtr old_pipeline = current_pipeline_;
|
||||
current_pipeline_ = GstEnginePipelinePtr();
|
||||
if (old_pipeline) {
|
||||
FinishPipeline(old_pipeline);
|
||||
}
|
||||
current_pipeline_ = CreatePipeline(current_pipeline_->media_url(), current_pipeline_->stream_url(), redirect_url, end_nanosec_, current_pipeline_->ebur128_loudness_normalizing_gain_db());
|
||||
Play(offset_nanosec);
|
||||
Play(pause, offset_nanosec);
|
||||
return;
|
||||
}
|
||||
|
||||
// Failure - give up
|
||||
qLog(Warning) << "Could not set thread to PLAYING.";
|
||||
current_pipeline_.reset();
|
||||
GstEnginePipelinePtr old_pipeline = current_pipeline_;
|
||||
current_pipeline_ = GstEnginePipelinePtr();
|
||||
if (old_pipeline) {
|
||||
FinishPipeline(old_pipeline);
|
||||
}
|
||||
BufferingFinished();
|
||||
return;
|
||||
}
|
||||
|
||||
StartTimers();
|
||||
|
||||
// Initial offset
|
||||
if (offset_nanosec != 0 || beginning_nanosec_ != 0) {
|
||||
Seek(offset_nanosec);
|
||||
if (!pause) {
|
||||
StartTimers();
|
||||
}
|
||||
|
||||
emit StateChanged(State::Playing);
|
||||
emit StateChanged(pause ? State::Paused : State::Playing);
|
||||
|
||||
// We've successfully started playing a media stream with this url
|
||||
emit ValidSongRequested(stream_url_);
|
||||
|
||||
@@ -757,28 +797,37 @@ QByteArray GstEngine::FixupUrl(const QUrl &url) {
|
||||
|
||||
void GstEngine::StartFadeout() {
|
||||
|
||||
if (is_fading_out_to_pause_) return;
|
||||
GstEnginePipelinePtr pipeline = current_pipeline_;
|
||||
|
||||
fadeout_pipeline_ = current_pipeline_;
|
||||
QObject::disconnect(&*fadeout_pipeline_, nullptr, nullptr, nullptr);
|
||||
fadeout_pipeline_->RemoveAllBufferConsumers();
|
||||
if (fadeout_pipelines_.contains(pipeline->id())) {
|
||||
return;
|
||||
}
|
||||
|
||||
fadeout_pipeline_->StartFader(fadeout_duration_nanosec_, QTimeLine::Backward);
|
||||
QObject::connect(&*fadeout_pipeline_, &GstEnginePipeline::FaderFinished, this, &GstEngine::FadeoutFinished);
|
||||
fadeout_pipelines_.insert(pipeline->id(), pipeline);
|
||||
pipeline->RemoveAllBufferConsumers();
|
||||
|
||||
pipeline->StartFader(fadeout_duration_nanosec_, QTimeLine::Backward);
|
||||
QObject::connect(&*pipeline, &GstEnginePipeline::FaderFinished, this, &GstEngine::FadeoutFinished);
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::StartFadeoutPause() {
|
||||
|
||||
fadeout_pause_pipeline_ = current_pipeline_;
|
||||
QObject::disconnect(&*fadeout_pause_pipeline_, &GstEnginePipeline::FaderFinished, nullptr, nullptr);
|
||||
if (!current_pipeline_ || fadeout_pipelines_.contains(current_pipeline_->id())) return;
|
||||
|
||||
fadeout_pause_pipeline_->StartFader(fadeout_pause_duration_nanosec_, QTimeLine::Backward, QEasingCurve::InOutQuad, false);
|
||||
if (fadeout_pipeline_ && fadeout_pipeline_->state() == GST_STATE_PLAYING) {
|
||||
fadeout_pipeline_->StartFader(fadeout_pause_duration_nanosec_, QTimeLine::Backward, QEasingCurve::Linear, false);
|
||||
}
|
||||
fadeout_pause_pipeline_ = current_pipeline_;
|
||||
QObject::connect(&*fadeout_pause_pipeline_, &GstEnginePipeline::FaderFinished, this, &GstEngine::FadeoutPauseFinished);
|
||||
is_fading_out_to_pause_ = true;
|
||||
fadeout_pause_pipeline_->StartFader(fadeout_pause_duration_nanosec_, QTimeLine::Backward, QEasingCurve::Linear, false);
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::StopFadeoutPause() {
|
||||
|
||||
if (!fadeout_pause_pipeline_) return;
|
||||
|
||||
QObject::disconnect(&*fadeout_pause_pipeline_, &GstEnginePipeline::FaderFinished, this, &GstEngine::FadeoutPauseFinished);
|
||||
has_faded_out_to_pause_ = true;
|
||||
fadeout_pause_pipeline_ = GstEnginePipelinePtr();
|
||||
|
||||
}
|
||||
|
||||
@@ -798,11 +847,11 @@ void GstEngine::StopTimers() {
|
||||
|
||||
}
|
||||
|
||||
SharedPtr<GstEnginePipeline> GstEngine::CreatePipeline() {
|
||||
GstEnginePipelinePtr GstEngine::CreatePipeline() {
|
||||
|
||||
EnsureInitialized();
|
||||
|
||||
SharedPtr<GstEnginePipeline> ret = make_shared<GstEnginePipeline>();
|
||||
GstEnginePipelinePtr ret = make_shared<GstEnginePipeline>();
|
||||
ret->set_output_device(output_, device_);
|
||||
ret->set_exclusive_mode(exclusive_mode_);
|
||||
ret->set_volume_enabled(volume_control_);
|
||||
@@ -841,9 +890,9 @@ SharedPtr<GstEnginePipeline> GstEngine::CreatePipeline() {
|
||||
|
||||
}
|
||||
|
||||
SharedPtr<GstEnginePipeline> GstEngine::CreatePipeline(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 end_nanosec, const double ebur128_loudness_normalizing_gain_db) {
|
||||
GstEnginePipelinePtr GstEngine::CreatePipeline(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 end_nanosec, const double ebur128_loudness_normalizing_gain_db) {
|
||||
|
||||
SharedPtr<GstEnginePipeline> ret = CreatePipeline();
|
||||
GstEnginePipelinePtr ret = CreatePipeline();
|
||||
QString error;
|
||||
if (!ret->InitFromUrl(media_url, stream_url, gst_url, end_nanosec, ebur128_loudness_normalizing_gain_db, error)) {
|
||||
ret.reset();
|
||||
@@ -856,6 +905,50 @@ SharedPtr<GstEnginePipeline> GstEngine::CreatePipeline(const QUrl &media_url, co
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::FinishPipeline(GstEnginePipelinePtr pipeline) {
|
||||
|
||||
const int pipeline_id = pipeline->id();
|
||||
|
||||
if (!pipeline->Finish() && !old_pipelines_.contains(pipeline->id())) {
|
||||
old_pipelines_.insert(pipeline_id, pipeline);
|
||||
QObject::connect(&*pipeline, &GstEnginePipeline::Finished, this, [this, pipeline_id]() {
|
||||
PipelineFinished(pipeline_id);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::PipelineFinished(const int pipeline_id) {
|
||||
|
||||
qLog(Debug) << "Pipeline" << pipeline_id << "finished";
|
||||
|
||||
GstEnginePipelinePtr pipeline = old_pipelines_[pipeline_id];
|
||||
old_pipelines_.remove(pipeline_id);
|
||||
if (pipeline == fadeout_pause_pipeline_) {
|
||||
StopFadeoutPause();
|
||||
}
|
||||
pipeline = GstEnginePipelinePtr();
|
||||
|
||||
if (current_pipeline_ && old_pipelines_.isEmpty() && delayed_state_ != State::Empty) {
|
||||
switch (delayed_state_) {
|
||||
case State::Playing:
|
||||
Play(delayed_state_pause_, delayed_state_offset_nanosec_);
|
||||
break;
|
||||
case State::Paused:
|
||||
Pause();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
delayed_state_ = State::Empty;
|
||||
delayed_state_pause_ = false;
|
||||
delayed_state_offset_nanosec_ = 0;
|
||||
}
|
||||
|
||||
qLog(Debug) << (current_pipeline_ ? 1 : 0) + old_pipelines_.count() << "pipelines are active";
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::UpdateScope(const int chunk_length) {
|
||||
|
||||
using sample_type = EngineBase::Scope::value_type;
|
||||
@@ -1007,3 +1100,23 @@ QString GstEngine::GSTdiscovererErrorMessage(GstDiscovererResult result) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool GstEngine::ExclusivePipelineActive() const {
|
||||
|
||||
if (old_pipelines_.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (current_pipeline_ && current_pipeline_->exclusive_mode()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const GstEnginePipelinePtr &pipeline : std::as_const(old_pipelines_)) {
|
||||
if (pipeline->exclusive_mode()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user