Fixed issue 578: fade-in fade-out on (un)pause.

This commit is contained in:
Andreas 2013-04-22 21:42:04 +02:00
parent cc80d08121
commit ec481d5874
8 changed files with 175 additions and 17 deletions

View File

@ -79,6 +79,8 @@ void Engine::Base::ReloadSettings() {
crossfade_enabled_ = s.value("CrossfadeEnabled", true).toBool();
autocrossfade_enabled_ = s.value("AutoCrossfadeEnabled", false).toBool();
crossfade_same_album_ = !s.value("NoCrossfadeSameAlbum", true).toBool();
fadeout_pause_enabled_ = s.value("FadeoutPauseEnabled", false).toBool();
fadeout_pause_duration_nanosec_ = s.value("FadeoutPauseDuration", 250).toLongLong() * kNsecPerMsec;
}
void Engine::Base::EmitAboutToEnd() {

View File

@ -147,6 +147,8 @@ class Base : public QObject, boost::noncopyable {
bool autocrossfade_enabled_;
bool crossfade_same_album_;
int next_background_stream_id_;
bool fadeout_pause_enabled_;
qint64 fadeout_pause_duration_nanosec_;
private:
bool about_to_end_emitted_;

View File

@ -85,7 +85,9 @@ GstEngine::GstEngine(TaskManager* task_manager)
mono_playback_(false),
seek_timer_(new QTimer(this)),
timer_id_(-1),
next_element_id_(0)
next_element_id_(0),
is_fading_out_to_pause_(false),
has_faded_out_(false)
{
seek_timer_->setSingleShot(true);
seek_timer_->setInterval(kSeekDelayNanosec / kNsecPerMsec);
@ -358,6 +360,9 @@ bool GstEngine::Load(const QUrl& url, Engine::TrackChangeFlags change,
}
void GstEngine::StartFadeout() {
if (is_fading_out_to_pause_)
return;
fadeout_pipeline_ = current_pipeline_;
disconnect(fadeout_pipeline_.get(), 0, 0, 0);
fadeout_pipeline_->RemoveAllBufferConsumers();
@ -367,6 +372,24 @@ void GstEngine::StartFadeout() {
connect(fadeout_pipeline_.get(), SIGNAL(FaderFinished()), SLOT(FadeoutFinished()));
}
void GstEngine::StartFadeoutPause() {
fadeout_pause_pipeline_ = current_pipeline_;
disconnect(fadeout_pause_pipeline_.get(), SIGNAL(FaderFinished()), 0, 0);
fadeout_pause_pipeline_->StartFader(fadeout_pause_duration_nanosec_,
QTimeLine::Backward,
QTimeLine::EaseInOutCurve,
false);
if (fadeout_pipeline_ && fadeout_pipeline_->state() == GST_STATE_PLAYING) {
qLog(Debug) << "start fadeout pipeline";
fadeout_pipeline_->StartFader(fadeout_pause_duration_nanosec_,
QTimeLine::Backward,
QTimeLine::LinearCurve,
false);
}
connect(fadeout_pause_pipeline_.get(), SIGNAL(FaderFinished()), SLOT(FadeoutPauseFinished()));
is_fading_out_to_pause_ = true;
}
bool GstEngine::Play(quint64 offset_nanosec) {
EnsureInitialised();
@ -380,6 +403,10 @@ bool GstEngine::Play(quint64 offset_nanosec) {
watcher->setFuture(future);
connect(watcher, SIGNAL(finished()), SLOT(PlayDone()));
if (is_fading_out_to_pause_) {
current_pipeline_->SetState(GST_STATE_PAUSED);
}
return true;
}
@ -445,15 +472,46 @@ void GstEngine::FadeoutFinished() {
emit FadeoutFinishedSignal();
}
void GstEngine::FadeoutPauseFinished() {
fadeout_pause_pipeline_->SetState(GST_STATE_PAUSED);
current_pipeline_->SetState(GST_STATE_PAUSED);
emit StateChanged(Engine::Paused);
StopTimers();
is_fading_out_to_pause_ = false;
has_faded_out_ = true;
fadeout_pause_pipeline_.reset();
fadeout_pipeline_.reset();
emit FadeoutFinishedSignal();
}
void GstEngine::Pause() {
if (!current_pipeline_ || current_pipeline_->is_buffering())
return;
if ( current_pipeline_->state() == GST_STATE_PLAYING ) {
current_pipeline_->SetState(GST_STATE_PAUSED);
emit StateChanged(Engine::Paused);
// 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_) {
disconnect(current_pipeline_.get(), SIGNAL(FaderFinished()), 0, 0);
current_pipeline_->StartFader(fadeout_pause_duration_nanosec_,
QTimeLine::Forward,
QTimeLine::EaseInOutCurve,
false);
is_fading_out_to_pause_ = false;
has_faded_out_ = false;
emit StateChanged(Engine::Playing);
return;
}
StopTimers();
if ( current_pipeline_->state() == GST_STATE_PLAYING ) {
if (fadeout_pause_enabled_) {
StartFadeoutPause();
} else {
current_pipeline_->SetState(GST_STATE_PAUSED);
emit StateChanged(Engine::Paused);
StopTimers();
}
}
}
@ -463,6 +521,19 @@ void GstEngine::Unpause() {
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_) {
disconnect(current_pipeline_.get(), SIGNAL(FaderFinished()), 0, 0);
current_pipeline_->StartFader(fadeout_pause_duration_nanosec_,
QTimeLine::Forward,
QTimeLine::EaseInOutCurve,
false);
has_faded_out_ = false;
}
emit StateChanged(Engine::Playing);
StartTimers();

View File

@ -121,6 +121,7 @@ class GstEngine : public Engine::Base, public BufferConsumer {
void ClearScopeBuffers();
void AddBufferToScope(GstBuffer* buf, int pipeline_id);
void FadeoutFinished();
void FadeoutPauseFinished();
void SeekNow();
void BackgroundStreamFinished();
void BackgroundStreamPlayDone();
@ -138,6 +139,7 @@ class GstEngine : public Engine::Base, public BufferConsumer {
PluginDetailsList GetPluginList(const QString& classname) const;
void StartFadeout();
void StartFadeoutPause();
void StartTimers();
void StopTimers();
@ -170,6 +172,7 @@ class GstEngine : public Engine::Base, public BufferConsumer {
boost::shared_ptr<GstEnginePipeline> current_pipeline_;
boost::shared_ptr<GstEnginePipeline> fadeout_pipeline_;
boost::shared_ptr<GstEnginePipeline> fadeout_pause_pipeline_;
QUrl preloaded_url_;
QList<BufferConsumer*> buffer_consumers_;
@ -203,6 +206,9 @@ class GstEngine : public Engine::Base, public BufferConsumer {
int next_element_id_;
QHash<int, boost::shared_ptr<GstEnginePipeline> > background_streams_;
bool is_fading_out_to_pause_;
bool has_faded_out_;
};

View File

@ -700,7 +700,6 @@ bool GstEnginePipeline::HandoffCallback(GstPad*, GstBuffer* buf, gpointer self)
// GstEngine will try to seek to the start of the new section, but
// we're already there so ignore it.
instance->ignore_next_seek_ = true;
emit instance->EndOfStreamReached(instance->id(), true);
} else {
// We have a next song but we can't cheat, so move to it normally.
@ -918,14 +917,23 @@ void GstEnginePipeline::UpdateVolume() {
void GstEnginePipeline::StartFader(qint64 duration_nanosec,
QTimeLine::Direction direction,
QTimeLine::CurveShape shape) {
QTimeLine::CurveShape shape,
bool use_fudge_timer) {
const int duration_msec = duration_nanosec / kNsecPerMsec;
// If there's already another fader running then start from the same time
// that one was already at.
int start_time = direction == QTimeLine::Forward ? 0 : duration_msec;
if (fader_ && fader_->state() == QTimeLine::Running)
start_time = fader_->currentTime();
if (fader_ && fader_->state() == QTimeLine::Running) {
if (duration_msec == fader_->duration()) {
start_time = fader_->currentTime();
} else {
// Calculate the position in the new fader with the same value from
// the old fader, so no volume jumps appear
qreal time = qreal(duration_msec) * (qreal(fader_->currentTime()) / qreal(fader_->duration()));
start_time = qRound(time);
}
}
fader_.reset(new QTimeLine(duration_msec, this));
connect(fader_.get(), SIGNAL(valueChanged(qreal)), SLOT(SetVolumeModifier(qreal)));
@ -936,6 +944,7 @@ void GstEnginePipeline::StartFader(qint64 duration_nanosec,
fader_->resume();
fader_fudge_timer_.stop();
use_fudge_timer_ = use_fudge_timer;
SetVolumeModifier(fader_->currentValue());
}
@ -946,7 +955,15 @@ void GstEnginePipeline::FaderTimelineFinished() {
// Wait a little while longer before emitting the finished signal (and
// probably distroying the pipeline) to account for delays in the audio
// server/driver.
fader_fudge_timer_.start(kFaderFudgeMsec, this);
if (use_fudge_timer_) {
fader_fudge_timer_.start(kFaderFudgeMsec, this);
} else {
// Even here we cannot emit the signal directly, as it result in a
// stutter when resuming playback. So use a quest small time, so you
// won't notice the difference when resuming playback
// (You get here when the pause fading is active)
fader_fudge_timer_.start(250, this);
}
}
void GstEnginePipeline::timerEvent(QTimerEvent* e) {

View File

@ -71,7 +71,8 @@ class GstEnginePipeline : public QObject {
void SetVolume(int percent);
void StartFader(qint64 duration_nanosec,
QTimeLine::Direction direction = QTimeLine::Forward,
QTimeLine::CurveShape shape = QTimeLine::LinearCurve);
QTimeLine::CurveShape shape = QTimeLine::LinearCurve,
bool use_fudge_timer = true);
// If this is set then it will be loaded automatically when playback finishes
// for gapless playback
@ -245,6 +246,7 @@ class GstEnginePipeline : public QObject {
boost::scoped_ptr<QTimeLine> fader_;
QBasicTimer fader_fudge_timer_;
bool use_fudge_timer_;
GstElement* pipeline_;

View File

@ -74,6 +74,8 @@ void PlaybackSettingsPage::Load() {
ui_->fading_auto->setChecked(s.value("AutoCrossfadeEnabled", false).toBool());
ui_->fading_duration->setValue(s.value("FadeoutDuration", 2000).toInt());
ui_->fading_samealbum->setChecked(s.value("NoCrossfadeSameAlbum", true).toBool());
ui_->fadeout_pause->setChecked(s.value("FadeoutPauseEnabled", false).toBool());
ui_->fading_pause_duration->setValue(s.value("FadeoutPauseDuration", 250).toInt());
s.endGroup();
s.beginGroup(GstEngine::kSettingsGroup);
@ -108,6 +110,8 @@ void PlaybackSettingsPage::Save() {
s.setValue("CrossfadeEnabled", ui_->fading_cross->isChecked());
s.setValue("AutoCrossfadeEnabled", ui_->fading_auto->isChecked());
s.setValue("NoCrossfadeSameAlbum", ui_->fading_samealbum->isChecked());
s.setValue("FadeoutPauseEnabled", ui_->fadeout_pause->isChecked());
s.setValue("FadeoutPauseDuration", ui_->fading_pause_duration->value());
s.endGroup();
s.beginGroup(GstEngine::kSettingsGroup);

View File

@ -115,6 +115,56 @@
</layout>
</widget>
</item>
<item>
<widget class="QCheckBox" name="fadeout_pause">
<property name="text">
<string>Fade out on pause / fade in on resume</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Fading duration</string>
</property>
<property name="indent">
<number>22</number>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="fading_pause_duration">
<property name="suffix">
<string> ms</string>
</property>
<property name="maximum">
<number>10000</number>
</property>
<property name="singleStep">
<number>50</number>
</property>
<property name="value">
<number>250</number>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
</item>
@ -146,7 +196,7 @@
<item row="0" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Replay Gain mode</string>
<string>Fading duration</string>
</property>
</widget>
</item>
@ -167,14 +217,18 @@
<item row="1" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Pre-amp</string>
<string>Fading duration</string>
</property>
</widget>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="replaygain_preamp_label"/>
<widget class="QLabel" name="replaygain_preamp_label">
<property name="text">
<string>Fading duration</string>
</property>
</widget>
</item>
<item>
<widget class="StickySlider" name="replaygain_preamp">
@ -222,7 +276,7 @@
<item row="0" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Output plugin</string>
<string>Fading duration</string>
</property>
</widget>
</item>
@ -241,7 +295,7 @@
<bool>false</bool>
</property>
<property name="text">
<string>Output device</string>
<string>Fading duration</string>
</property>
</widget>
</item>
@ -258,7 +312,7 @@
<item row="2" column="0">
<widget class="QLabel" name="buffer_duration_label">
<property name="text">
<string>Buffer duration</string>
<string>Fading duration</string>
</property>
</widget>
</item>