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:
Jonas Kvinge
2024-08-03 00:05:06 +02:00
parent 8ddd309d5d
commit 548fa3f6ee
11 changed files with 520 additions and 214 deletions

View File

@@ -1371,8 +1371,12 @@ void MainWindow::MediaStopped() {
ui_->button_love->setEnabled(false); ui_->button_love->setEnabled(false);
tray_icon_->LoveStateChanged(false); tray_icon_->LoveStateChanged(false);
track_position_timer_->stop(); if (track_position_timer_->isActive()) {
track_slider_timer_->stop(); track_position_timer_->stop();
}
if (track_slider_timer_->isActive()) {
track_slider_timer_->stop();
}
ui_->track_slider->SetStopped(); ui_->track_slider->SetStopped();
tray_icon_->SetProgress(0); tray_icon_->SetProgress(0);
tray_icon_->SetStopped(); tray_icon_->SetStopped();
@@ -1400,8 +1404,12 @@ void MainWindow::MediaPaused() {
ui_->action_play_pause->setEnabled(true); ui_->action_play_pause->setEnabled(true);
track_position_timer_->stop(); if (!track_position_timer_->isActive()) {
track_slider_timer_->stop(); track_position_timer_->start();
}
if (!track_slider_timer_->isActive()) {
track_slider_timer_->start();
}
tray_icon_->SetPaused(); tray_icon_->SetPaused();
@@ -1426,8 +1434,13 @@ void MainWindow::MediaPlaying() {
ui_->track_slider->SetCanSeek(can_seek); ui_->track_slider->SetCanSeek(can_seek);
tray_icon_->SetPlaying(enable_play_pause); tray_icon_->SetPlaying(enable_play_pause);
track_position_timer_->start(); if (!track_position_timer_->isActive()) {
track_slider_timer_->start(); track_position_timer_->start();
}
if (!track_slider_timer_->isActive()) {
track_slider_timer_->start();
}
UpdateTrackPosition(); UpdateTrackPosition();
} }
@@ -1566,7 +1579,7 @@ void MainWindow::LoadPlaybackStatus() {
const EngineBase::State playback_state = static_cast<EngineBase::State>(s.value("playback_state", static_cast<int>(EngineBase::State::Empty)).toInt()); const EngineBase::State playback_state = static_cast<EngineBase::State>(s.value("playback_state", static_cast<int>(EngineBase::State::Empty)).toInt());
s.endGroup(); s.endGroup();
if (resume_playback && playback_state != EngineBase::State::Empty && playback_state != EngineBase::State::Idle) { if (resume_playback && (playback_state == EngineBase::State::Playing || playback_state == EngineBase::State::Paused)) {
SharedPtr<QMetaObject::Connection> connection = make_shared<QMetaObject::Connection>(); SharedPtr<QMetaObject::Connection> connection = make_shared<QMetaObject::Connection>();
*connection = QObject::connect(&*app_->playlist_manager(), &PlaylistManager::AllPlaylistsLoaded, this, [this, connection]() { *connection = QObject::connect(&*app_->playlist_manager(), &PlaylistManager::AllPlaylistsLoaded, this, [this, connection]() {
QObject::disconnect(*connection); QObject::disconnect(*connection);
@@ -1583,21 +1596,19 @@ void MainWindow::ResumePlayback() {
Settings s; Settings s;
s.beginGroup(Player::kSettingsGroup); s.beginGroup(Player::kSettingsGroup);
const EngineBase::State playback_state = static_cast<EngineBase::State>(s.value("playback_state", static_cast<int>(EngineBase::State::Empty)).toInt()); const EngineBase::State playback_state = static_cast<EngineBase::State>(s.value("playback_state", static_cast<int>(EngineBase::State::Empty)).toInt());
int playback_playlist = s.value("playback_playlist", -1).toInt(); const int playback_playlist = s.value("playback_playlist", -1).toInt();
int playback_position = s.value("playback_position", 0).toInt(); const int playback_position = s.value("playback_position", 0).toInt();
s.endGroup(); s.endGroup();
if (playback_playlist == app_->playlist_manager()->current()->id()) { if (playback_playlist == app_->playlist_manager()->current()->id()) {
// Set active to current to resume playback on correct playlist. // Set active to current to resume playback on correct playlist.
app_->playlist_manager()->SetActiveToCurrent(); app_->playlist_manager()->SetActiveToCurrent();
if (playback_state == EngineBase::State::Paused) { if (playback_state == EngineBase::State::Playing) {
SharedPtr<QMetaObject::Connection> connection = make_shared<QMetaObject::Connection>(); app_->player()->Play(playback_position * kNsecPerSec);
*connection = QObject::connect(&*app_->player(), &Player::Playing, &*app_->player(), [this, connection]() { }
QObject::disconnect(*connection); else if (playback_state == EngineBase::State::Paused) {
QTimer::singleShot(300, &*app_->player(), &Player::PlayPauseHelper); app_->player()->PlayWithPause(playback_position * kNsecPerSec);
});
} }
app_->player()->Play(playback_position * kNsecPerSec);
} }
// Reset saved playback status so we don't resume again from the same position. // Reset saved playback status so we don't resume again from the same position.
@@ -1620,7 +1631,7 @@ void MainWindow::PlayIndex(const QModelIndex &idx, Playlist::AutoScroll autoscro
} }
app_->playlist_manager()->SetActiveToCurrent(); app_->playlist_manager()->SetActiveToCurrent();
app_->player()->PlayAt(row, 0, EngineBase::TrackChangeType::Manual, autoscroll, true); app_->player()->PlayAt(row, false, 0, EngineBase::TrackChangeType::Manual, autoscroll, true);
} }
@@ -1637,14 +1648,14 @@ void MainWindow::PlaylistDoubleClick(const QModelIndex &idx) {
switch (doubleclick_playlist_addmode_) { switch (doubleclick_playlist_addmode_) {
case BehaviourSettingsPage::PlaylistAddBehaviour::Play: case BehaviourSettingsPage::PlaylistAddBehaviour::Play:
app_->playlist_manager()->SetActiveToCurrent(); app_->playlist_manager()->SetActiveToCurrent();
app_->player()->PlayAt(source_idx.row(), 0, EngineBase::TrackChangeType::Manual, Playlist::AutoScroll::Never, true, true); app_->player()->PlayAt(source_idx.row(), false, 0, EngineBase::TrackChangeType::Manual, Playlist::AutoScroll::Never, true, true);
break; break;
case BehaviourSettingsPage::PlaylistAddBehaviour::Enqueue: case BehaviourSettingsPage::PlaylistAddBehaviour::Enqueue:
app_->playlist_manager()->current()->queue()->ToggleTracks(QModelIndexList() << source_idx); app_->playlist_manager()->current()->queue()->ToggleTracks(QModelIndexList() << source_idx);
if (app_->player()->GetState() != EngineBase::State::Playing) { if (app_->player()->GetState() != EngineBase::State::Playing) {
app_->playlist_manager()->SetActiveToCurrent(); app_->playlist_manager()->SetActiveToCurrent();
app_->player()->PlayAt(app_->playlist_manager()->current()->queue()->TakeNext(), 0, EngineBase::TrackChangeType::Manual, Playlist::AutoScroll::Never, true); app_->player()->PlayAt(app_->playlist_manager()->current()->queue()->TakeNext(), false, 0, EngineBase::TrackChangeType::Manual, Playlist::AutoScroll::Never, true);
} }
break; break;
} }
@@ -2585,7 +2596,7 @@ void MainWindow::CommandlineOptionsReceived(const CommandlineOptions &options) {
app_->player()->SeekTo(app_->player()->engine()->position_nanosec() / kNsecPerSec + options.seek_by()); app_->player()->SeekTo(app_->player()->engine()->position_nanosec() / kNsecPerSec + options.seek_by());
} }
if (options.play_track_at() != -1) app_->player()->PlayAt(options.play_track_at(), 0, EngineBase::TrackChangeType::Manual, Playlist::AutoScroll::Maybe, true); if (options.play_track_at() != -1) app_->player()->PlayAt(options.play_track_at(), false, 0, EngineBase::TrackChangeType::Manual, Playlist::AutoScroll::Maybe, true);
if (options.show_osd()) app_->player()->ShowOSD(); if (options.show_osd()) app_->player()->ShowOSD();

View File

@@ -354,7 +354,7 @@ void Player::HandleLoadResult(const UrlHandler::LoadResult &result) {
if (is_current) { if (is_current) {
qLog(Debug) << "Playing song" << current_item->Metadata().title() << result.stream_url_ << "position" << play_offset_nanosec_; qLog(Debug) << "Playing song" << current_item->Metadata().title() << result.stream_url_ << "position" << play_offset_nanosec_;
engine_->Play(result.media_url_, result.stream_url_, stream_change_type_, song.has_cue(), song.beginning_nanosec(), song.end_nanosec(), play_offset_nanosec_, song.ebur128_integrated_loudness_lufs()); engine_->Play(result.media_url_, result.stream_url_, pause_, stream_change_type_, song.has_cue(), song.beginning_nanosec(), song.end_nanosec(), play_offset_nanosec_, song.ebur128_integrated_loudness_lufs());
current_item_ = current_item; current_item_ = current_item;
play_offset_nanosec_ = 0; play_offset_nanosec_ = 0;
} }
@@ -426,7 +426,7 @@ void Player::NextItem(const EngineBase::TrackChangeFlags change, const Playlist:
return; return;
} }
PlayAt(i, 0, change, autoscroll, false, true); PlayAt(i, false, 0, change, autoscroll, false, true);
} }
@@ -461,11 +461,10 @@ void Player::PlayPlaylistInternal(const EngineBase::TrackChangeFlags change, con
if (i == -1) i = app_->playlist_manager()->active()->last_played_row(); if (i == -1) i = app_->playlist_manager()->active()->last_played_row();
if (i == -1) i = 0; if (i == -1) i = 0;
PlayAt(i, 0, change, autoscroll, true); PlayAt(i, false, 0, change, autoscroll, true);
} }
bool Player::HandleStopAfter(const Playlist::AutoScroll autoscroll) { bool Player::HandleStopAfter(const Playlist::AutoScroll autoscroll) {
if (app_->playlist_manager()->active()->stop_after_current()) { if (app_->playlist_manager()->active()->stop_after_current()) {
@@ -527,7 +526,7 @@ void Player::PlayPause(const quint64 offset_nanosec, const Playlist::AutoScroll
int i = app_->playlist_manager()->active()->current_row(); int i = app_->playlist_manager()->active()->current_row();
if (i == -1) i = app_->playlist_manager()->active()->last_played_row(); if (i == -1) i = app_->playlist_manager()->active()->last_played_row();
if (i == -1) i = 0; if (i == -1) i = 0;
PlayAt(i, offset_nanosec, EngineBase::TrackChangeType::First, autoscroll, true); PlayAt(i, false, offset_nanosec, EngineBase::TrackChangeType::First, autoscroll, true);
break; break;
} }
} }
@@ -606,7 +605,7 @@ void Player::PreviousItem(const EngineBase::TrackChangeFlags change) {
QDateTime now = QDateTime::currentDateTime(); QDateTime now = QDateTime::currentDateTime();
if (last_pressed_previous_.isValid() && last_pressed_previous_.secsTo(now) >= 2) { if (last_pressed_previous_.isValid() && last_pressed_previous_.secsTo(now) >= 2) {
last_pressed_previous_ = now; last_pressed_previous_ = now;
PlayAt(app_->playlist_manager()->active()->current_row(), 0, change, Playlist::AutoScroll::Always, false, true); PlayAt(app_->playlist_manager()->active()->current_row(), false, 0, change, Playlist::AutoScroll::Always, false, true);
return; return;
} }
last_pressed_previous_ = now; last_pressed_previous_ = now;
@@ -616,11 +615,11 @@ void Player::PreviousItem(const EngineBase::TrackChangeFlags change) {
app_->playlist_manager()->active()->set_current_row(i, Playlist::AutoScroll::Always, false); app_->playlist_manager()->active()->set_current_row(i, Playlist::AutoScroll::Always, false);
if (i == -1) { if (i == -1) {
Stop(); Stop();
PlayAt(i, 0, change, Playlist::AutoScroll::Always, true); PlayAt(i, false, 0, change, Playlist::AutoScroll::Always, true);
return; return;
} }
PlayAt(i, 0, change, Playlist::AutoScroll::Always, false); PlayAt(i, false, 0, change, Playlist::AutoScroll::Always, false);
} }
@@ -718,9 +717,9 @@ void Player::VolumeDown() {
} }
void Player::PlayAt(const int index, const quint64 offset_nanosec, EngineBase::TrackChangeFlags change, const Playlist::AutoScroll autoscroll, const bool reshuffle, const bool force_inform) { void Player::PlayAt(const int index, const bool pause, const quint64 offset_nanosec, EngineBase::TrackChangeFlags change, const Playlist::AutoScroll autoscroll, const bool reshuffle, const bool force_inform) {
pause_time_ = QDateTime(); pause_time_ = pause ? QDateTime::currentDateTime() : QDateTime();
play_offset_nanosec_ = offset_nanosec; play_offset_nanosec_ = offset_nanosec;
if (current_item_ && change & EngineBase::TrackChangeType::Manual && engine_->position_nanosec() != engine_->length_nanosec()) { if (current_item_ && change & EngineBase::TrackChangeType::Manual && engine_->position_nanosec() != engine_->length_nanosec()) {
@@ -748,13 +747,14 @@ void Player::PlayAt(const int index, const quint64 offset_nanosec, EngineBase::T
return; return;
} }
pause_ = pause;
stream_change_type_ = change; stream_change_type_ = change;
autoscroll_ = autoscroll; autoscroll_ = autoscroll;
HandleLoadResult(url_handlers_[url.scheme()]->StartLoading(url)); HandleLoadResult(url_handlers_[url.scheme()]->StartLoading(url));
} }
else { else {
qLog(Debug) << "Playing song" << current_item_->Metadata().title() << url << "position" << offset_nanosec; qLog(Debug) << "Playing song" << current_item_->Metadata().title() << url << "position" << offset_nanosec;
engine_->Play(current_item_->Url(), url, change, current_item_->Metadata().has_cue(), current_item_->effective_beginning_nanosec(), current_item_->effective_end_nanosec(), offset_nanosec, current_item_->effective_ebur128_integrated_loudness_lufs()); engine_->Play(current_item_->Url(), url, pause, change, current_item_->Metadata().has_cue(), current_item_->effective_beginning_nanosec(), current_item_->effective_end_nanosec(), offset_nanosec, current_item_->effective_ebur128_integrated_loudness_lufs());
} }
} }
@@ -866,6 +866,19 @@ void Player::Play(const quint64 offset_nanosec) {
} }
void Player::PlayWithPause(const quint64 offset_nanosec) {
pause_time_ = QDateTime();
play_offset_nanosec_ = offset_nanosec;
app_->playlist_manager()->SetActivePlaylist(app_->playlist_manager()->current_id());
if (app_->playlist_manager()->active()->rowCount() == 0) return;
int i = app_->playlist_manager()->active()->current_row();
if (i == -1) i = app_->playlist_manager()->active()->last_played_row();
if (i == -1) i = 0;
PlayAt(i, true, offset_nanosec, EngineBase::TrackChangeType::First, Playlist::AutoScroll::Always, true);
}
void Player::ShowOSD() { void Player::ShowOSD() {
if (current_item_) emit ForceShowOSD(current_item_->Metadata(), false); if (current_item_) emit ForceShowOSD(current_item_->Metadata(), false);
} }

View File

@@ -70,7 +70,7 @@ class PlayerInterface : public QObject {
virtual void SaveVolume() = 0; virtual void SaveVolume() = 0;
// Manual track change to the specified track // Manual track change to the specified track
virtual void PlayAt(const int index, const quint64 offset_nanosec, EngineBase::TrackChangeFlags change, const Playlist::AutoScroll autoscroll, const bool reshuffle, const bool force_inform = false) = 0; virtual void PlayAt(const int index, const bool pause, const quint64 offset_nanosec, EngineBase::TrackChangeFlags change, const Playlist::AutoScroll autoscroll, const bool reshuffle, const bool force_inform = false) = 0;
// If there's currently a song playing, pause it, otherwise play the track that was playing last, or the first one on the playlist // If there's currently a song playing, pause it, otherwise play the track that was playing last, or the first one on the playlist
virtual void PlayPause(const quint64 offset_nanosec = 0, const Playlist::AutoScroll autoscroll = Playlist::AutoScroll::Always) = 0; virtual void PlayPause(const quint64 offset_nanosec = 0, const Playlist::AutoScroll autoscroll = Playlist::AutoScroll::Always) = 0;
@@ -98,6 +98,7 @@ class PlayerInterface : public QObject {
virtual void Pause() = 0; virtual void Pause() = 0;
virtual void Stop(const bool stop_after = false) = 0; virtual void Stop(const bool stop_after = false) = 0;
virtual void Play(const quint64 offset_nanosec = 0) = 0; virtual void Play(const quint64 offset_nanosec = 0) = 0;
virtual void PlayWithPause(const quint64 offset_nanosec) = 0;
virtual void PlayHelper() = 0; virtual void PlayHelper() = 0;
virtual void ShowOSD() = 0; virtual void ShowOSD() = 0;
@@ -159,7 +160,7 @@ class Player : public PlayerInterface {
void LoadVolume() override; void LoadVolume() override;
void SaveVolume() override; void SaveVolume() override;
void PlayAt(const int index, const quint64 offset_nanosec, EngineBase::TrackChangeFlags change, const Playlist::AutoScroll autoscroll, const bool reshuffle, const bool force_inform = false) override; void PlayAt(const int index, const bool pause, const quint64 offset_nanosec, EngineBase::TrackChangeFlags change, const Playlist::AutoScroll autoscroll, const bool reshuffle, const bool force_inform = false) override;
void PlayPause(const quint64 offset_nanosec = 0, const Playlist::AutoScroll autoscroll = Playlist::AutoScroll::Always) override; void PlayPause(const quint64 offset_nanosec = 0, const Playlist::AutoScroll autoscroll = Playlist::AutoScroll::Always) override;
void PlayPauseHelper() override { PlayPause(play_offset_nanosec_); } void PlayPauseHelper() override { PlayPause(play_offset_nanosec_); }
void RestartOrPrevious() override; void RestartOrPrevious() override;
@@ -182,6 +183,7 @@ class Player : public PlayerInterface {
void Stop(const bool stop_after = false) override; void Stop(const bool stop_after = false) override;
void StopAfterCurrent(); void StopAfterCurrent();
void Play(const quint64 offset_nanosec = 0) override; void Play(const quint64 offset_nanosec = 0) override;
void PlayWithPause(const quint64 offset_nanosec) override;
void PlayHelper() override { Play(); } void PlayHelper() override { Play(); }
void ShowOSD() override; void ShowOSD() override;
void TogglePrettyOSD(); void TogglePrettyOSD();
@@ -228,6 +230,7 @@ class Player : public PlayerInterface {
PlaylistItemPtr current_item_; PlaylistItemPtr current_item_;
bool pause_;
EngineBase::TrackChangeFlags stream_change_type_; EngineBase::TrackChangeFlags stream_change_type_;
Playlist::AutoScroll autoscroll_; Playlist::AutoScroll autoscroll_;
EngineBase::State last_state_; EngineBase::State last_state_;

View File

@@ -139,13 +139,13 @@ bool EngineBase::Load(const QUrl &media_url, const QUrl &stream_url, const Track
} }
bool EngineBase::Play(const QUrl &media_url, const QUrl &stream_url, const TrackChangeFlags flags, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec, const quint64 offset_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs) { bool EngineBase::Play(const QUrl &media_url, const QUrl &stream_url, const bool pause, const TrackChangeFlags flags, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec, const quint64 offset_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs) {
if (!Load(media_url, stream_url, flags, force_stop_at_end, beginning_nanosec, end_nanosec, ebur128_integrated_loudness_lufs)) { if (!Load(media_url, stream_url, flags, force_stop_at_end, beginning_nanosec, end_nanosec, ebur128_integrated_loudness_lufs)) {
return false; return false;
} }
return Play(offset_nanosec); return Play(pause, offset_nanosec);
} }

View File

@@ -105,7 +105,7 @@ class EngineBase : public QObject {
virtual State state() const = 0; virtual State state() const = 0;
virtual void StartPreloading(const QUrl&, const QUrl&, const bool, const qint64, const qint64) {} virtual void StartPreloading(const QUrl&, const QUrl&, const bool, const qint64, const qint64) {}
virtual bool Load(const QUrl &media_url, const QUrl &stream_url, const TrackChangeFlags change, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs); virtual bool Load(const QUrl &media_url, const QUrl &stream_url, const TrackChangeFlags change, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs);
virtual bool Play(const quint64 offset_nanosec) = 0; virtual bool Play(const bool pause, const quint64 offset_nanosec) = 0;
virtual void Stop(const bool stop_after = false) = 0; virtual void Stop(const bool stop_after = false) = 0;
virtual void Pause() = 0; virtual void Pause() = 0;
virtual void Unpause() = 0; virtual void Unpause() = 0;
@@ -133,7 +133,7 @@ class EngineBase : public QObject {
// Plays a media stream represented with the URL 'u' from the given 'beginning' to the given 'end' (usually from 0 to a song's length). // Plays a media stream represented with the URL 'u' from the given 'beginning' to the given 'end' (usually from 0 to a song's length).
// Both markers should be passed in nanoseconds. 'end' can be negative, indicating that the real length of 'u' stream is unknown. // Both markers should be passed in nanoseconds. 'end' can be negative, indicating that the real length of 'u' stream is unknown.
bool Play(const QUrl &media_url, const QUrl &stream_url, const TrackChangeFlags flags, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec, const quint64 offset_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs); bool Play(const QUrl &media_url, const QUrl &stream_url, const bool pause, const TrackChangeFlags flags, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec, const quint64 offset_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs);
void SetVolume(const uint volume); void SetVolume(const uint volume);
public slots: public slots:

View File

@@ -98,13 +98,15 @@ GstEngine::GstEngine(SharedPtr<TaskManager> task_manager, QObject *parent)
waiting_to_seek_(false), waiting_to_seek_(false),
seek_pos_(0), seek_pos_(0),
timer_id_(-1), timer_id_(-1),
is_fading_out_to_pause_(false), has_faded_out_to_pause_(false),
has_faded_out_(false),
scope_chunk_(0), scope_chunk_(0),
have_new_buffer_(false), have_new_buffer_(false),
scope_chunks_(0), scope_chunks_(0),
discovery_finished_cb_id_(-1), 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_->setSingleShot(true);
seek_timer_->setInterval(kSeekDelayNanosec / kNsecPerMsec); seek_timer_->setInterval(kSeekDelayNanosec / kNsecPerMsec);
@@ -205,20 +207,27 @@ bool GstEngine::Load(const QUrl &media_url, const QUrl &stream_url, const Engine
return true; 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 (!pipeline) return false;
if (crossfade) StartFadeout(); if (crossfade && current_pipeline_ && !ExclusivePipelineActive() && !fadeout_pipelines_.contains(current_pipeline_->id())) {
StartFadeout();
}
BufferingFinished(); BufferingFinished();
GstEnginePipelinePtr old_pipeline = current_pipeline_;
current_pipeline_ = pipeline; current_pipeline_ = pipeline;
if (old_pipeline && !fadeout_pipelines_.contains(old_pipeline->id())) {
FinishPipeline(old_pipeline);
}
SetVolume(volume_); SetVolume(volume_);
SetStereoBalance(stereo_balance_); SetStereoBalance(stereo_balance_);
SetEqualizerParameters(equalizer_preamp_, equalizer_gains_); SetEqualizerParameters(equalizer_preamp_, equalizer_gains_);
// Maybe fade in this track // Maybe fade in this track
if (crossfade) { if (crossfade && !ExclusivePipelineActive()) {
current_pipeline_->StartFader(fadeout_duration_nanosec_, QTimeLine::Forward); 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(); EnsureInitialized();
if (!current_pipeline_ || current_pipeline_->is_buffering()) return false; 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>(); QFutureWatcher<GstStateChangeReturn> *watcher = new QFutureWatcher<GstStateChangeReturn>();
const int pipeline_id = current_pipeline_->id(); 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(); const GstStateChangeReturn ret = watcher->result();
watcher->deleteLater(); 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); watcher->setFuture(future);
if (is_fading_out_to_pause_) {
current_pipeline_->SetState(GST_STATE_PAUSED);
}
return true; return true;
} }
@@ -271,24 +292,33 @@ void GstEngine::Stop(const bool stop_after) {
StopTimers(); StopTimers();
delayed_state_ = State::Empty;
delayed_state_pause_ = false;
delayed_state_offset_nanosec_ = 0;
media_url_.clear(); media_url_.clear();
stream_url_.clear(); // To ensure we return Empty from state() stream_url_.clear(); // To ensure we return Empty from state()
beginning_nanosec_ = end_nanosec_ = 0; 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. // 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_) { if (fadeout_pause_pipeline_) {
QObject::disconnect(&*current_pipeline_, &GstEnginePipeline::FaderFinished, nullptr, nullptr); StopFadeoutPause();
is_fading_out_to_pause_ = false;
has_faded_out_ = true;
fadeout_pause_pipeline_.reset();
fadeout_pipeline_.reset();
} }
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(); BufferingFinished();
emit StateChanged(State::Empty); emit StateChanged(State::Empty);
} }
@@ -297,22 +327,20 @@ void GstEngine::Pause() {
if (!current_pipeline_ || current_pipeline_->is_buffering()) return; 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. delayed_state_ = State::Empty;
if (is_fading_out_to_pause_) { delayed_state_pause_ = false;
QObject::disconnect(&*current_pipeline_, &GstEnginePipeline::FaderFinished, nullptr, nullptr); delayed_state_offset_nanosec_ = 0;
current_pipeline_->StartFader(fadeout_pause_duration_nanosec_, QTimeLine::Forward, QEasingCurve::InOutQuad, false);
is_fading_out_to_pause_ = false; if (fadeout_pause_pipeline_) {
has_faded_out_ = false;
emit StateChanged(State::Playing);
return; return;
} }
if (current_pipeline_->state() == GST_STATE_PLAYING) { if (current_pipeline_->state() == GST_STATE_PLAYING) {
if (fadeout_pause_enabled_) { if (fadeout_pause_enabled_ && !ExclusivePipelineActive()) {
StartFadeoutPause(); StartFadeoutPause();
} }
else { else {
current_pipeline_->SetState(GST_STATE_PAUSED); current_pipeline_->SetStateAsync(GST_STATE_PAUSED);
emit StateChanged(State::Paused); emit StateChanged(State::Paused);
StopTimers(); StopTimers();
} }
@@ -325,20 +353,22 @@ void GstEngine::Unpause() {
if (!current_pipeline_ || current_pipeline_->is_buffering()) return; if (!current_pipeline_ || current_pipeline_->is_buffering()) return;
if (current_pipeline_->state() == GST_STATE_PAUSED) { 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. // 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 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); QObject::disconnect(&*current_pipeline_, &GstEnginePipeline::FaderFinished, nullptr, nullptr);
current_pipeline_->StartFader(fadeout_pause_duration_nanosec_, QTimeLine::Forward, QEasingCurve::InOutQuad, false); current_pipeline_->StartFader(fadeout_pause_duration_nanosec_, QTimeLine::Forward, QEasingCurve::Linear, false);
has_faded_out_ = false; has_faded_out_to_pause_ = false;
} }
current_pipeline_->SetStateAsync(GST_STATE_PLAYING);
emit StateChanged(State::Playing); emit StateChanged(State::Playing);
StartTimers(); StartTimers();
} }
} }
void GstEngine::Seek(const quint64 offset_nanosec) { void GstEngine::Seek(const quint64 offset_nanosec) {
@@ -352,6 +382,7 @@ void GstEngine::Seek(const quint64 offset_nanosec) {
SeekNow(); SeekNow();
seek_timer_->start(); // Stop us from seeking again for a little while seek_timer_->start(); // Stop us from seeking again for a little while
} }
} }
void GstEngine::SetVolumeSW(const uint volume) { 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(); emit FadeoutFinishedSignal();
} }
void GstEngine::FadeoutPauseFinished() { void GstEngine::FadeoutPauseFinished() {
fadeout_pause_pipeline_->SetState(GST_STATE_PAUSED); fadeout_pause_pipeline_->SetStateAsync(GST_STATE_PAUSED);
current_pipeline_->SetState(GST_STATE_PAUSED);
emit StateChanged(State::Paused); emit StateChanged(State::Paused);
StopTimers(); StopTimers();
has_faded_out_to_pause_ = true;
is_fading_out_to_pause_ = false; fadeout_pause_pipeline_ = GstEnginePipelinePtr();
has_faded_out_ = true;
fadeout_pause_pipeline_.reset();
fadeout_pipeline_.reset();
emit FadeoutFinishedSignal(); 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()) { if (!current_pipeline_ || pipeline_id != current_pipeline_->id()) {
return; return;
@@ -668,26 +701,33 @@ void GstEngine::PlayDone(const GstStateChangeReturn ret, const quint64 offset_na
const QByteArray redirect_url = current_pipeline_->redirect_url(); const QByteArray redirect_url = current_pipeline_->redirect_url();
if (!redirect_url.isEmpty() && redirect_url != current_pipeline_->gst_url()) { if (!redirect_url.isEmpty() && redirect_url != current_pipeline_->gst_url()) {
qLog(Info) << "Redirecting to" << redirect_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()); 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; return;
} }
// Failure - give up // Failure - give up
qLog(Warning) << "Could not set thread to PLAYING."; 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(); BufferingFinished();
return; return;
} }
StartTimers(); if (!pause) {
StartTimers();
// Initial offset
if (offset_nanosec != 0 || beginning_nanosec_ != 0) {
Seek(offset_nanosec);
} }
emit StateChanged(State::Playing); emit StateChanged(pause ? State::Paused : State::Playing);
// We've successfully started playing a media stream with this url // We've successfully started playing a media stream with this url
emit ValidSongRequested(stream_url_); emit ValidSongRequested(stream_url_);
@@ -757,28 +797,37 @@ QByteArray GstEngine::FixupUrl(const QUrl &url) {
void GstEngine::StartFadeout() { void GstEngine::StartFadeout() {
if (is_fading_out_to_pause_) return; GstEnginePipelinePtr pipeline = current_pipeline_;
fadeout_pipeline_ = current_pipeline_; if (fadeout_pipelines_.contains(pipeline->id())) {
QObject::disconnect(&*fadeout_pipeline_, nullptr, nullptr, nullptr); return;
fadeout_pipeline_->RemoveAllBufferConsumers(); }
fadeout_pipeline_->StartFader(fadeout_duration_nanosec_, QTimeLine::Backward); fadeout_pipelines_.insert(pipeline->id(), pipeline);
QObject::connect(&*fadeout_pipeline_, &GstEnginePipeline::FaderFinished, this, &GstEngine::FadeoutFinished); pipeline->RemoveAllBufferConsumers();
pipeline->StartFader(fadeout_duration_nanosec_, QTimeLine::Backward);
QObject::connect(&*pipeline, &GstEnginePipeline::FaderFinished, this, &GstEngine::FadeoutFinished);
} }
void GstEngine::StartFadeoutPause() { void GstEngine::StartFadeoutPause() {
fadeout_pause_pipeline_ = current_pipeline_; if (!current_pipeline_ || fadeout_pipelines_.contains(current_pipeline_->id())) return;
QObject::disconnect(&*fadeout_pause_pipeline_, &GstEnginePipeline::FaderFinished, nullptr, nullptr);
fadeout_pause_pipeline_->StartFader(fadeout_pause_duration_nanosec_, QTimeLine::Backward, QEasingCurve::InOutQuad, false); fadeout_pause_pipeline_ = current_pipeline_;
if (fadeout_pipeline_ && fadeout_pipeline_->state() == GST_STATE_PLAYING) {
fadeout_pipeline_->StartFader(fadeout_pause_duration_nanosec_, QTimeLine::Backward, QEasingCurve::Linear, false);
}
QObject::connect(&*fadeout_pause_pipeline_, &GstEnginePipeline::FaderFinished, this, &GstEngine::FadeoutPauseFinished); 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(); EnsureInitialized();
SharedPtr<GstEnginePipeline> ret = make_shared<GstEnginePipeline>(); GstEnginePipelinePtr ret = make_shared<GstEnginePipeline>();
ret->set_output_device(output_, device_); ret->set_output_device(output_, device_);
ret->set_exclusive_mode(exclusive_mode_); ret->set_exclusive_mode(exclusive_mode_);
ret->set_volume_enabled(volume_control_); 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; QString error;
if (!ret->InitFromUrl(media_url, stream_url, gst_url, end_nanosec, ebur128_loudness_normalizing_gain_db, error)) { if (!ret->InitFromUrl(media_url, stream_url, gst_url, end_nanosec, ebur128_loudness_normalizing_gain_db, error)) {
ret.reset(); 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) { void GstEngine::UpdateScope(const int chunk_length) {
using sample_type = EngineBase::Scope::value_type; 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;
}

View File

@@ -35,18 +35,19 @@
#include <QFuture> #include <QFuture>
#include <QByteArray> #include <QByteArray>
#include <QList> #include <QList>
#include <QMap>
#include <QString> #include <QString>
#include <QUrl> #include <QUrl>
#include "core/shared_ptr.h" #include "core/shared_ptr.h"
#include "enginebase.h" #include "enginebase.h"
#include "gststartup.h" #include "gststartup.h"
#include "gstenginepipeline.h"
#include "gstbufferconsumer.h" #include "gstbufferconsumer.h"
class QTimer; class QTimer;
class QTimerEvent; class QTimerEvent;
class TaskManager; class TaskManager;
class GstEnginePipeline;
class GstEngine : public EngineBase, public GstBufferConsumer { class GstEngine : public EngineBase, public GstBufferConsumer {
Q_OBJECT Q_OBJECT
@@ -63,7 +64,7 @@ class GstEngine : public EngineBase, public GstBufferConsumer {
State state() const override; State state() const override;
void StartPreloading(const QUrl &media_url, const QUrl &stream_url, const bool force_stop_at_end, const qint64 beginning_nanosec, const qint64 end_nanosec) override; void StartPreloading(const QUrl &media_url, const QUrl &stream_url, const bool force_stop_at_end, const qint64 beginning_nanosec, const qint64 end_nanosec) override;
bool Load(const QUrl &media_url, const QUrl &stream_url, const EngineBase::TrackChangeFlags change, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs) override; bool Load(const QUrl &media_url, const QUrl &stream_url, const EngineBase::TrackChangeFlags change, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs) override;
bool Play(const quint64 offset_nanosec) override; bool Play(const bool pause, const quint64 offset_nanosec) override;
void Stop(const bool stop_after = false) override; void Stop(const bool stop_after = false) override;
void Pause() override; void Pause() override;
void Unpause() override; void Unpause() override;
@@ -115,26 +116,31 @@ class GstEngine : public EngineBase, public GstBufferConsumer {
void HandlePipelineError(const int pipeline_id, const int domain, const int error_code, const QString &message, const QString &debugstr); void HandlePipelineError(const int pipeline_id, const int domain, const int error_code, const QString &message, const QString &debugstr);
void NewMetaData(const int pipeline_id, const EngineMetadata &engine_metadata); void NewMetaData(const int pipeline_id, const EngineMetadata &engine_metadata);
void AddBufferToScope(GstBuffer *buf, const int pipeline_id, const QString &format); void AddBufferToScope(GstBuffer *buf, const int pipeline_id, const QString &format);
void FadeoutFinished(); void FadeoutFinished(const int pipeline_id);
void FadeoutPauseFinished(); void FadeoutPauseFinished();
void SeekNow(); void SeekNow();
void PlayDone(const GstStateChangeReturn ret, const quint64, const int); void PlayDone(const GstStateChangeReturn ret, const bool pause, const quint64 offset_nanosec, const int pipeline_id);
void BufferingStarted(); void BufferingStarted();
void BufferingProgress(int percent); void BufferingProgress(int percent);
void BufferingFinished(); void BufferingFinished();
void PipelineFinished(const int pipeline_id);
private: private:
QByteArray FixupUrl(const QUrl &url); QByteArray FixupUrl(const QUrl &url);
void StartFadeout(); void StartFadeout();
void StartFadeoutPause(); void StartFadeoutPause();
void StopFadeoutPause();
void StartTimers(); void StartTimers();
void StopTimers(); void StopTimers();
SharedPtr<GstEnginePipeline> CreatePipeline(); GstEnginePipelinePtr CreatePipeline();
SharedPtr<GstEnginePipeline> 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 CreatePipeline(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 end_nanosec, const double ebur128_loudness_normalizing_gain_db);
void FinishPipeline(GstEnginePipelinePtr pipeline);
void UpdateScope(int chunk_length); void UpdateScope(int chunk_length);
@@ -142,6 +148,8 @@ class GstEngine : public EngineBase, public GstBufferConsumer {
static void StreamDiscoveryFinished(GstDiscoverer*, gpointer); static void StreamDiscoveryFinished(GstDiscoverer*, gpointer);
static QString GSTdiscovererErrorMessage(GstDiscovererResult result); static QString GSTdiscovererErrorMessage(GstDiscovererResult result);
bool ExclusivePipelineActive() const;
private: private:
SharedPtr<TaskManager> task_manager_; SharedPtr<TaskManager> task_manager_;
GstStartup *gst_startup_; GstStartup *gst_startup_;
@@ -149,9 +157,10 @@ class GstEngine : public EngineBase, public GstBufferConsumer {
int buffering_task_id_; int buffering_task_id_;
SharedPtr<GstEnginePipeline> current_pipeline_; GstEnginePipelinePtr current_pipeline_;
SharedPtr<GstEnginePipeline> fadeout_pipeline_; QMap<int, GstEnginePipelinePtr> fadeout_pipelines_;
SharedPtr<GstEnginePipeline> fadeout_pause_pipeline_; GstEnginePipelinePtr fadeout_pause_pipeline_;
QMap<int, GstEnginePipelinePtr> old_pipelines_;
QList<GstBufferConsumer*> buffer_consumers_; QList<GstBufferConsumer*> buffer_consumers_;
@@ -171,8 +180,7 @@ class GstEngine : public EngineBase, public GstBufferConsumer {
int timer_id_; int timer_id_;
bool is_fading_out_to_pause_; bool has_faded_out_to_pause_;
bool has_faded_out_;
int scope_chunk_; int scope_chunk_;
bool have_new_buffer_; bool have_new_buffer_;
@@ -181,6 +189,10 @@ class GstEngine : public EngineBase, public GstBufferConsumer {
int discovery_finished_cb_id_; int discovery_finished_cb_id_;
int discovery_discovered_cb_id_; int discovery_discovered_cb_id_;
State delayed_state_;
bool delayed_state_pause_;
quint64 delayed_state_offset_nanosec_;
}; };
#endif // GSTENGINE_H #endif // GSTENGINE_H

View File

@@ -49,6 +49,7 @@
#include <QVariant> #include <QVariant>
#include <QString> #include <QString>
#include <QUrl> #include <QUrl>
#include <QTimer>
#include <QTimeLine> #include <QTimeLine>
#include <QEasingCurve> #include <QEasingCurve>
#include <QMetaObject> #include <QMetaObject>
@@ -64,6 +65,12 @@
namespace { namespace {
constexpr int GST_PLAY_FLAG_VIDEO = 0x00000001;
constexpr int GST_PLAY_FLAG_AUDIO = 0x00000002;
constexpr int GST_PLAY_FLAG_DOWNLOAD = 0x00000080;
constexpr int GST_PLAY_FLAG_BUFFERING = 0x00000100;
constexpr int GST_PLAY_FLAG_SOFT_VOLUME = 0x00000010;
constexpr int kGstStateTimeoutNanosecs = 10000000; constexpr int kGstStateTimeoutNanosecs = 10000000;
constexpr int kFaderFudgeMsec = 2000; constexpr int kFaderFudgeMsec = 2000;
@@ -109,6 +116,7 @@ GstEnginePipeline::GstEnginePipeline(QObject *parent)
ignore_tags_(false), ignore_tags_(false),
pipeline_connected_(false), pipeline_connected_(false),
pipeline_active_(false), pipeline_active_(false),
pending_state_(GST_STATE_NULL),
pending_seek_nanosec_(-1), pending_seek_nanosec_(-1),
last_known_position_ns_(0), last_known_position_ns_(0),
next_uri_set_(false), next_uri_set_(false),
@@ -141,7 +149,9 @@ GstEnginePipeline::GstEnginePipeline(QObject *parent)
about_to_finish_cb_id_(-1), about_to_finish_cb_id_(-1),
notify_volume_cb_id_(-1), notify_volume_cb_id_(-1),
logged_unsupported_analyzer_format_(false), logged_unsupported_analyzer_format_(false),
about_to_finish_(false) { about_to_finish_(false),
finish_requested_(false),
finished_(false) {
eq_band_gains_.reserve(kEqBandCount); eq_band_gains_.reserve(kEqBandCount);
for (int i = 0; i < kEqBandCount; ++i) eq_band_gains_ << 0; for (int i = 0; i < kEqBandCount; ++i) eq_band_gains_ << 0;
@@ -150,78 +160,22 @@ GstEnginePipeline::GstEnginePipeline(QObject *parent)
GstEnginePipeline::~GstEnginePipeline() { GstEnginePipeline::~GstEnginePipeline() {
Disconnect();
if (pipeline_) { if (pipeline_) {
if (state() != GST_STATE_NULL) {
if (fader_) { gst_element_set_state(pipeline_, GST_STATE_NULL);
if (fader_->state() != QTimeLine::NotRunning) {
fader_->stop();
}
fader_.reset();
} }
if (element_added_cb_id_ != -1) {
g_signal_handler_disconnect(G_OBJECT(audiobin_), element_added_cb_id_);
}
if (element_removed_cb_id_ != -1) {
g_signal_handler_disconnect(G_OBJECT(audiobin_), element_removed_cb_id_);
}
if (pad_added_cb_id_ != -1) {
g_signal_handler_disconnect(G_OBJECT(pipeline_), pad_added_cb_id_);
}
if (notify_source_cb_id_ != -1) {
g_signal_handler_disconnect(G_OBJECT(pipeline_), notify_source_cb_id_);
}
if (about_to_finish_cb_id_ != -1) {
g_signal_handler_disconnect(G_OBJECT(pipeline_), about_to_finish_cb_id_);
}
if (notify_volume_cb_id_ != -1) {
g_signal_handler_disconnect(G_OBJECT(volume_), notify_volume_cb_id_);
}
if (upstream_events_probe_cb_id_ != 0) {
GstPad *pad = gst_element_get_static_pad(eventprobe_, "src");
if (pad) {
gst_pad_remove_probe(pad, upstream_events_probe_cb_id_);
gst_object_unref(pad);
}
}
if (buffer_probe_cb_id_ != 0) {
GstPad *pad = gst_element_get_static_pad(audioqueueconverter_, "src");
if (pad) {
gst_pad_remove_probe(pad, buffer_probe_cb_id_);
gst_object_unref(pad);
}
}
{
GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline_));
if (bus) {
gst_bus_remove_watch(bus);
gst_bus_set_sync_handler(bus, nullptr, nullptr, nullptr);
gst_object_unref(bus);
}
}
gst_element_set_state(pipeline_, GST_STATE_NULL);
gst_object_unref(GST_OBJECT(pipeline_)); gst_object_unref(GST_OBJECT(pipeline_));
pipeline_ = nullptr; pipeline_ = nullptr;
if (audiobin_ && !pipeline_connected_) { if (audiobin_ && !pipeline_connected_) {
gst_object_unref(GST_OBJECT(audiobin_)); gst_object_unref(GST_OBJECT(audiobin_));
} }
audiobin_ = nullptr; audiobin_ = nullptr;
} }
qLog(Debug) << "Pipeline" << id_ << "deleted";
} }
void GstEnginePipeline::set_output_device(const QString &output, const QVariant &device) { void GstEnginePipeline::set_output_device(const QString &output, const QVariant &device) {
@@ -348,6 +302,97 @@ GstElement *GstEnginePipeline::CreateElement(const QString &factory_name, const
} }
void GstEnginePipeline::Disconnect() {
if (pipeline_) {
if (fader_) {
if (fader_->state() != QTimeLine::NotRunning) {
fader_->stop();
}
fader_.reset();
}
if (element_added_cb_id_ != -1) {
g_signal_handler_disconnect(G_OBJECT(audiobin_), element_added_cb_id_);
element_added_cb_id_ = -1;
}
if (element_removed_cb_id_ != -1) {
g_signal_handler_disconnect(G_OBJECT(audiobin_), element_removed_cb_id_);
element_removed_cb_id_ = -1;
}
if (pad_added_cb_id_ != -1) {
g_signal_handler_disconnect(G_OBJECT(pipeline_), pad_added_cb_id_);
pad_added_cb_id_ = -1;
}
if (notify_source_cb_id_ != -1) {
g_signal_handler_disconnect(G_OBJECT(pipeline_), notify_source_cb_id_);
notify_source_cb_id_ = -1;
}
if (about_to_finish_cb_id_ != -1) {
g_signal_handler_disconnect(G_OBJECT(pipeline_), about_to_finish_cb_id_);
about_to_finish_cb_id_ = -1;
}
if (notify_volume_cb_id_ != -1) {
g_signal_handler_disconnect(G_OBJECT(volume_), notify_volume_cb_id_);
notify_volume_cb_id_ = -1;
}
if (upstream_events_probe_cb_id_ != 0) {
GstPad *pad = gst_element_get_static_pad(eventprobe_, "src");
if (pad) {
gst_pad_remove_probe(pad, upstream_events_probe_cb_id_);
gst_object_unref(pad);
}
upstream_events_probe_cb_id_ = 0;
}
if (buffer_probe_cb_id_ != 0) {
GstPad *pad = gst_element_get_static_pad(audioqueueconverter_, "src");
if (pad) {
gst_pad_remove_probe(pad, buffer_probe_cb_id_);
gst_object_unref(pad);
}
buffer_probe_cb_id_ = 0;
}
{
GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline_));
if (bus) {
gst_bus_remove_watch(bus);
gst_bus_set_sync_handler(bus, nullptr, nullptr, nullptr);
gst_object_unref(bus);
}
}
}
}
bool GstEnginePipeline::Finish() {
qLog(Debug) << "Finishing pipeline" << id_;
finish_requested_ = true;
Disconnect();
if (state() == GST_STATE_NULL) {
finished_ = true;
}
else {
SetStateAsync(GST_STATE_NULL);
}
return finished_;
}
bool GstEnginePipeline::InitFromUrl(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 end_nanosec, const double ebur128_loudness_normalizing_gain_db, QString &error) { bool GstEnginePipeline::InitFromUrl(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 end_nanosec, const double ebur128_loudness_normalizing_gain_db, QString &error) {
media_url_ = media_url; media_url_ = media_url;
@@ -395,9 +440,9 @@ bool GstEnginePipeline::InitFromUrl(const QUrl &media_url, const QUrl &stream_ur
gint flags = 0; gint flags = 0;
g_object_get(G_OBJECT(pipeline_), "flags", &flags, nullptr); g_object_get(G_OBJECT(pipeline_), "flags", &flags, nullptr);
flags |= 0x00000002; flags |= GST_PLAY_FLAG_AUDIO;
flags &= ~0x00000001; flags &= ~GST_PLAY_FLAG_VIDEO;
flags &= ~0x00000010; flags &= ~GST_PLAY_FLAG_SOFT_VOLUME;
g_object_set(G_OBJECT(pipeline_), "flags", flags, nullptr); g_object_set(G_OBJECT(pipeline_), "flags", flags, nullptr);
g_object_set(G_OBJECT(pipeline_), "uri", gst_url.constData(), nullptr); g_object_set(G_OBJECT(pipeline_), "uri", gst_url.constData(), nullptr);
@@ -434,10 +479,13 @@ bool GstEnginePipeline::InitAudioBin(QString &error) {
#else #else
case QVariant::String:{ case QVariant::String:{
#endif #endif
QString device = device_.toString(); const QString device = device_.toString();
if (!device.isEmpty()) { if (!device.isEmpty()) {
qLog(Debug) << "Setting device" << device << "for" << output_; qLog(Debug) << "Setting device" << device << "for" << output_;
g_object_set(G_OBJECT(audiosink_), "device", device.toUtf8().constData(), nullptr); g_object_set(G_OBJECT(audiosink_), "device", device.toUtf8().constData(), nullptr);
if (output_ == QLatin1String(GstEngine::kALSASink) && (device.startsWith(QLatin1String("hw:")) || device.startsWith(QLatin1String("plughw:")))) {
exclusive_mode_ = true;
}
} }
break; break;
} }
@@ -607,6 +655,9 @@ bool GstEnginePipeline::InitAudioBin(QString &error) {
if (!volume_fading_) { if (!volume_fading_) {
return false; return false;
} }
if (fader_) {
SetFaderVolume(fader_->currentValue());
}
} }
// Create the stereo balancer elements if it's enabled. // Create the stereo balancer elements if it's enabled.
@@ -1026,9 +1077,10 @@ void GstEnginePipeline::SourceSetupCallback(GstElement *playbin, GstElement *sou
// If the pipeline was buffering we stop that now. // If the pipeline was buffering we stop that now.
if (instance->buffering_) { if (instance->buffering_) {
qLog(Debug) << "Buffering finished";
instance->buffering_ = false; instance->buffering_ = false;
emit instance->BufferingFinished(); emit instance->BufferingFinished();
instance->SetState(GST_STATE_PLAYING); instance->SetStateAsync(GST_STATE_PLAYING);
} }
} }
@@ -1254,7 +1306,7 @@ GstPadProbeReturn GstEnginePipeline::BufferProbeCallback(GstPad *pad, GstPadProb
consumers = instance->buffer_consumers_; consumers = instance->buffer_consumers_;
} }
for (GstBufferConsumer *consumer : consumers) { for (GstBufferConsumer *consumer : std::as_const(consumers)) {
gst_buffer_ref(buf); gst_buffer_ref(buf);
consumer->ConsumeBuffer(buf, instance->id(), format); consumer->ConsumeBuffer(buf, instance->id(), format);
} }
@@ -1521,7 +1573,7 @@ void GstEnginePipeline::TagMessageReceived(GstMessage *msg) {
} }
if (!title_splitted.isEmpty() && title_splitted.count() >= 2) { if (!title_splitted.isEmpty() && title_splitted.count() >= 2) {
int i = 0; int i = 0;
for (const QString &title_part : title_splitted) { for (const QString &title_part : std::as_const(title_splitted)) {
++i; ++i;
switch (i) { switch (i) {
case 1: case 1:
@@ -1594,11 +1646,13 @@ void GstEnginePipeline::StateChangedMessageReceived(GstMessage *msg) {
if (next_uri_reset_ && new_state == GST_STATE_PAUSED) { if (next_uri_reset_ && new_state == GST_STATE_PAUSED) {
qLog(Debug) << "Reverting next uri and going to playing state."; qLog(Debug) << "Reverting next uri and going to playing state.";
next_uri_reset_ = false; next_uri_reset_ = false;
pending_state_ = GST_STATE_PLAYING;
SeekDelayed(pending_seek_nanosec_); SeekDelayed(pending_seek_nanosec_);
SetStateDelayed(GST_STATE_PLAYING); pending_seek_nanosec_ = -1;
} }
else { else {
SeekQueued(pending_seek_nanosec_); SeekAsync(pending_seek_nanosec_);
pending_seek_nanosec_ = -1;
} }
} }
} }
@@ -1612,16 +1666,30 @@ void GstEnginePipeline::StateChangedMessageReceived(GstMessage *msg) {
g_object_set(G_OBJECT(pipeline_), "uri", gst_url_.constData(), nullptr); g_object_set(G_OBJECT(pipeline_), "uri", gst_url_.constData(), nullptr);
if (pending_seek_nanosec_ == -1) { if (pending_seek_nanosec_ == -1) {
qLog(Debug) << "Reverting next uri and going to playing state."; qLog(Debug) << "Reverting next uri and going to playing state.";
SetState(GST_STATE_PLAYING); SetStateAsync(GST_STATE_PLAYING);
} }
else { else {
qLog(Debug) << "Reverting next uri and going to paused state."; qLog(Debug) << "Reverting next uri and going to paused state.";
next_uri_reset_ = true; next_uri_reset_ = true;
SetState(GST_STATE_PAUSED); SetStateAsync(GST_STATE_PAUSED);
} }
} }
} }
if (pipeline_active_) {
if (pending_seek_nanosec_ != -1 && new_state == GST_STATE_PAUSED) {
SeekAsync(pending_seek_nanosec_);
}
else if (pending_state_ != GST_STATE_NULL) {
SetStateAsync(pending_state_);
pending_state_ = GST_STATE_NULL;
}
if (fader_ && fader_->state() != QTimeLine::State::Running && new_state == GST_STATE_PLAYING) {
qLog(Debug) << "Resuming fader";
ResumeFaderAsync();
}
}
} }
void GstEnginePipeline::BufferingMessageReceived(GstMessage *msg) { void GstEnginePipeline::BufferingMessageReceived(GstMessage *msg) {
@@ -1637,16 +1705,18 @@ void GstEnginePipeline::BufferingMessageReceived(GstMessage *msg) {
const GstState current_state = state(); const GstState current_state = state();
if (percent == 0 && current_state == GST_STATE_PLAYING && !buffering_) { if (percent == 0 && current_state == GST_STATE_PLAYING && !buffering_) {
qLog(Debug) << "Buffering started";
buffering_ = true; buffering_ = true;
emit BufferingStarted(); emit BufferingStarted();
SetState(GST_STATE_PAUSED); SetStateAsync(GST_STATE_PAUSED);
} }
else if (percent == 100 && buffering_) { else if (percent == 100 && buffering_) {
qLog(Debug) << "Buffering finished";
buffering_ = false; buffering_ = false;
emit BufferingFinished(); emit BufferingFinished();
SetState(GST_STATE_PLAYING); SetStateAsync(GST_STATE_PLAYING);
} }
else if (buffering_) { else if (buffering_) {
emit BufferingProgress(percent); emit BufferingProgress(percent);
@@ -1687,18 +1757,54 @@ GstState GstEnginePipeline::state() const {
} }
QFuture<GstStateChangeReturn> GstEnginePipeline::SetState(const GstState state) { QFuture<GstStateChangeReturn> GstEnginePipeline::SetStateAsync(const GstState state) {
qLog(Debug) << "Setting pipeline state to" << GstStateText(state); qLog(Debug) << "Setting pipeline" << id_ << "state to" << GstStateText(state);
return QtConcurrent::run(&set_state_threadpool_, &gst_element_set_state, pipeline_, state);
QFutureWatcher<GstStateChangeReturn> *watcher = new QFutureWatcher<GstStateChangeReturn>();
QObject::connect(watcher, &QFutureWatcher<GstStateChangeReturn>::finished, this, [this, watcher, state]() {
const GstStateChangeReturn state_change = watcher->result();
watcher->deleteLater();
SetStateAsyncFinished(state, state_change);
});
QFuture<GstStateChangeReturn> future = QtConcurrent::run(&set_state_threadpool_, &gst_element_set_state, pipeline_, state);
watcher->setFuture(future);
return future;
} }
void GstEnginePipeline::SetStateDelayed(const GstState state) { void GstEnginePipeline::SetStateAsyncFinished(const GstState state, const GstStateChangeReturn state_change) {
QMetaObject::invokeMethod(this, [this, state]() { switch (state_change) {
QTimer::singleShot(300, this, [this, state]() { SetState(state); }); case GST_STATE_CHANGE_SUCCESS:
}, Qt::QueuedConnection); case GST_STATE_CHANGE_ASYNC:
case GST_STATE_CHANGE_NO_PREROLL:
qLog(Debug) << "Pipeline" << id_ << "state successfully set to" << GstStateText(state);
emit SetStateFinished(state_change);
if (!finished_ && finish_requested_) {
finished_ = true;
emit Finished();
}
break;
case GST_STATE_CHANGE_FAILURE:
qLog(Error) << "Failed to set pipeline to state" << GstStateText(state);
break;
}
}
QFuture<GstStateChangeReturn> GstEnginePipeline::Play(const bool pause, const quint64 offset_nanosec) {
if (offset_nanosec != 0) {
pending_seek_nanosec_ = static_cast<qint64>(offset_nanosec);
}
if (!pause) {
pending_state_ = GST_STATE_PLAYING;
}
return SetStateAsync(GST_STATE_PAUSED);
} }
@@ -1716,7 +1822,7 @@ bool GstEnginePipeline::Seek(const qint64 nanosec) {
if (next_uri_set_) { if (next_uri_set_) {
pending_seek_nanosec_ = nanosec; pending_seek_nanosec_ = nanosec;
SetState(GST_STATE_READY); SetStateAsync(GST_STATE_READY);
return true; return true;
} }
@@ -1725,11 +1831,22 @@ bool GstEnginePipeline::Seek(const qint64 nanosec) {
qLog(Debug) << "Seeking to" << nanosec; qLog(Debug) << "Seeking to" << nanosec;
return gst_element_seek_simple(pipeline_, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, nanosec); const bool success = gst_element_seek_simple(pipeline_, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, nanosec);
if (success) {
qLog(Debug) << "Seek succeeded";
if (pending_state_ != GST_STATE_NULL) {
qLog(Debug) << "Setting state from pending state" << GstStateText(pending_state_);
SetStateAsync(pending_state_);
pending_state_ = GST_STATE_NULL;
}
}
return success;
} }
void GstEnginePipeline::SeekQueued(const qint64 nanosec) { void GstEnginePipeline::SeekAsync(const qint64 nanosec) {
QMetaObject::invokeMethod(this, "Seek", Qt::QueuedConnection, Q_ARG(qint64, nanosec)); QMetaObject::invokeMethod(this, "Seek", Qt::QueuedConnection, Q_ARG(qint64, nanosec));
@@ -1865,17 +1982,32 @@ void GstEnginePipeline::StartFader(const qint64 duration_nanosec, const QTimeLin
fader_->setDirection(direction); fader_->setDirection(direction);
fader_->setEasingCurve(shape); fader_->setEasingCurve(shape);
fader_->setCurrentTime(static_cast<int>(start_time)); fader_->setCurrentTime(static_cast<int>(start_time));
fader_->resume();
fader_fudge_timer_.stop(); fader_fudge_timer_.stop();
use_fudge_timer_ = use_fudge_timer; use_fudge_timer_ = use_fudge_timer;
SetFaderVolume(fader_->currentValue()); SetFaderVolume(fader_->currentValue());
qLog(Debug) << "Pipeline" << id_ << "with state" << GstStateText(state()) << "set to fade from" << start_time;
if (pipeline_active_) {
fader_->resume();
}
}
void GstEnginePipeline::ResumeFaderAsync() {
if (fader_) {
QMetaObject::invokeMethod(&*fader_, "resume", Qt::QueuedConnection);
}
} }
void GstEnginePipeline::FaderTimelineFinished() { void GstEnginePipeline::FaderTimelineFinished() {
qLog(Debug) << "Pipeline" << id_ << "finished fading";
fader_.reset(); fader_.reset();
// Wait a little while longer before emitting the finished signal (and probably destroying the pipeline) to account for delays in the audio server/driver. // Wait a little while longer before emitting the finished signal (and probably destroying the pipeline) to account for delays in the audio server/driver.
@@ -1895,7 +2027,7 @@ void GstEnginePipeline::timerEvent(QTimerEvent *e) {
if (e->timerId() == fader_fudge_timer_.timerId()) { if (e->timerId() == fader_fudge_timer_.timerId()) {
fader_fudge_timer_.stop(); fader_fudge_timer_.stop();
emit FaderFinished(); emit FaderFinished(id_);
return; return;
} }

View File

@@ -46,6 +46,7 @@
#include "core/shared_ptr.h" #include "core/shared_ptr.h"
#include "enginemetadata.h" #include "enginemetadata.h"
class QTimer;
class QTimerEvent; class QTimerEvent;
class GstBufferConsumer; class GstBufferConsumer;
struct GstPlayBin; struct GstPlayBin;
@@ -80,6 +81,8 @@ class GstEnginePipeline : public QObject {
void set_spotify_login(const QString &spotify_username, const QString &spotify_password); void set_spotify_login(const QString &spotify_username, const QString &spotify_password);
#endif #endif
bool Finish();
// Creates the pipeline, returns false on error // Creates the pipeline, returns false on error
bool InitFromUrl(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 end_nanosec, const double ebur128_loudness_normalizing_gain_db, QString &error); bool InitFromUrl(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 end_nanosec, const double ebur128_loudness_normalizing_gain_db, QString &error);
@@ -89,10 +92,10 @@ class GstEnginePipeline : public QObject {
void RemoveAllBufferConsumers(); void RemoveAllBufferConsumers();
// Control the music playback // Control the music playback
Q_INVOKABLE QFuture<GstStateChangeReturn> SetState(const GstState state); Q_INVOKABLE QFuture<GstStateChangeReturn> SetStateAsync(const GstState state);
void SetStateDelayed(const GstState state); Q_INVOKABLE QFuture<GstStateChangeReturn> Play(const bool pause, const quint64 offset_nanosec);
Q_INVOKABLE bool Seek(const qint64 nanosec); Q_INVOKABLE bool Seek(const qint64 nanosec);
void SeekQueued(const qint64 nanosec); void SeekAsync(const qint64 nanosec);
void SeekDelayed(const qint64 nanosec); void SeekDelayed(const qint64 nanosec);
void SetEBUR128LoudnessNormalizingGain_dB(const double ebur128_loudness_normalizing_gain_db); void SetEBUR128LoudnessNormalizingGain_dB(const double ebur128_loudness_normalizing_gain_db);
void SetVolume(const uint volume_percent); void SetVolume(const uint volume_percent);
@@ -133,6 +136,8 @@ class GstEnginePipeline : public QObject {
QString source_device() const { return source_device_; } QString source_device() const { return source_device_; }
bool exclusive_mode() const { return exclusive_mode_; }
public slots: public slots:
void SetFaderVolume(const qreal volume); void SetFaderVolume(const qreal volume);
@@ -143,7 +148,7 @@ class GstEnginePipeline : public QObject {
void MetadataFound(const int pipeline_id, const EngineMetadata &bundle); void MetadataFound(const int pipeline_id, const EngineMetadata &bundle);
void VolumeChanged(const uint volume); void VolumeChanged(const uint volume);
void FaderFinished(); void FaderFinished(const int pipeline_id);
void BufferingStarted(); void BufferingStarted();
void BufferingProgress(const int percent); void BufferingProgress(const int percent);
@@ -151,6 +156,10 @@ class GstEnginePipeline : public QObject {
void AboutToFinish(); void AboutToFinish();
void Finished();
void SetStateFinished(const GstStateChangeReturn state);
protected: protected:
void timerEvent(QTimerEvent*) override; void timerEvent(QTimerEvent*) override;
@@ -189,7 +198,12 @@ class GstEnginePipeline : public QObject {
void UpdateStereoBalance(); void UpdateStereoBalance();
void UpdateEqualizer(); void UpdateEqualizer();
void Disconnect();
void ResumeFaderAsync();
private slots: private slots:
void SetStateAsyncFinished(const GstState state, const GstStateChangeReturn state_change);
void FaderTimelineFinished(); void FaderTimelineFinished();
private: private:
@@ -290,6 +304,8 @@ class GstEnginePipeline : public QObject {
// Also, we have to wait for the playbin to be connected. // Also, we have to wait for the playbin to be connected.
bool pipeline_connected_; bool pipeline_connected_;
bool pipeline_active_; bool pipeline_active_;
GstState pending_state_;
qint64 pending_seek_nanosec_; qint64 pending_seek_nanosec_;
// We can only use gst_element_query_position() when the pipeline is in // We can only use gst_element_query_position() when the pipeline is in
@@ -342,6 +358,10 @@ class GstEnginePipeline : public QObject {
bool about_to_finish_; bool about_to_finish_;
bool finish_requested_;
bool finished_;
}; };
using GstEnginePipelinePtr = SharedPtr<GstEnginePipeline>;
#endif // GSTENGINEPIPELINE_H #endif // GSTENGINEPIPELINE_H

View File

@@ -124,7 +124,9 @@ bool VLCEngine::Load(const QUrl &media_url, const QUrl &stream_url, const Engine
} }
bool VLCEngine::Play(const quint64 offset_nanosec) { bool VLCEngine::Play(const bool pause, const quint64 offset_nanosec) {
Q_UNUSED(pause);
if (!Initialized()) return false; if (!Initialized()) return false;

View File

@@ -52,7 +52,7 @@ class VLCEngine : public EngineBase {
bool Init() override; bool Init() override;
EngineBase::State state() const override { return state_; } EngineBase::State state() const override { return state_; }
bool Load(const QUrl &media_url, const QUrl &stream_url, const EngineBase::TrackChangeFlags change, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs) override; bool Load(const QUrl &media_url, const QUrl &stream_url, const EngineBase::TrackChangeFlags change, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs) override;
bool Play(const quint64 offset_nanosec) override; bool Play(const bool pause, const quint64 offset_nanosec) override;
void Stop(const bool stop_after = false) override; void Stop(const bool stop_after = false) override;
void Pause() override; void Pause() override;
void Unpause() override; void Unpause() override;