Compare commits

...

12 Commits

Author SHA1 Message Date
Andreas
dd2ba0fe33 Append id to spotify_bin gst_bin. 2015-04-21 11:47:32 +02:00
Andreas
e916db9ae1 Simplify code. 2015-04-21 10:49:45 +02:00
Andreas
a1855f84bc Almost. SpotifyClient streams new tracks to a new media pipeline.
Bugs:
- If the transition work, there is no audio output, but the pipeline is filled with data.
- Sometimes the transition doesn't work and a complete new gstpipeline is created. Not sure why yet.
2015-04-14 16:33:44 +02:00
Andreas
c62cc10759 Merge branch 'spotify-gapless' of https://github.com/clementine-player/Clementine into spotify-gapless 2015-04-14 14:40:41 +02:00
Andreas
8ab13d2b23 Work on other approach. 2015-04-14 14:39:56 +02:00
Andreas
341d5d3722 Once track can be played gaplessly. But the gst pipeline position isn't reset to 0 and this causes further problems. 2015-04-14 14:39:56 +02:00
Andreas
a759fdb2e0 Oops. 2015-04-14 14:39:56 +02:00
Andreas
db14b75fe7 Use Spotify prefetch to load next song before the current one ends.
We still have the problem that each track a new GstPipeline is created and the SpotifyClient creates a new connection to this pipeline.
Therefore playback is not gapless yet.
2015-04-14 14:39:56 +02:00
Andreas
fbc25a0d87 Work on other approach. 2015-04-05 12:07:55 +02:00
Andreas
b5a6d42d32 Once track can be played gaplessly. But the gst pipeline position isn't reset to 0 and this causes further problems. 2015-03-17 20:44:01 +01:00
Andreas
8e3fa5552a Oops. 2015-03-14 15:38:17 +01:00
Andreas
509b28aaeb Use Spotify prefetch to load next song before the current one ends.
We still have the problem that each track a new GstPipeline is created and the SpotifyClient creates a new connection to this pipeline.
Therefore playback is not gapless yet.
2015-03-14 15:22:39 +01:00
15 changed files with 231 additions and 27 deletions

View File

@ -41,7 +41,11 @@ MediaPipeline::~MediaPipeline() {
}
bool MediaPipeline::Init(int sample_rate, int channels) {
if (is_initialised()) return false;
qLog(Debug) << "MediaPipeline::Init";
if (is_initialised()) {
qLog(Debug) << "is_initialised() true";
return false;
}
pipeline_ = gst_pipeline_new("pipeline");
@ -51,19 +55,24 @@ bool MediaPipeline::Init(int sample_rate, int channels) {
tcpsink_ = gst_element_factory_make("tcpclientsink", nullptr);
if (!pipeline_ || !appsrc_ || !tcpsink_) {
qLog(Debug) << "!pipeline_ || !appsrc_ || !tcpsink_";
if (pipeline_) {
gst_object_unref(GST_OBJECT(pipeline_));
qLog(Debug) << "have pipeline_";
pipeline_ = nullptr;
}
if (appsrc_) {
gst_object_unref(GST_OBJECT(appsrc_));
qLog(Debug) << "have appsrc_";
appsrc_ = nullptr;
}
if (gdppay) {
gst_object_unref(GST_OBJECT(gdppay));
qLog(Debug) << "have gdppay";
}
if (tcpsink_) {
gst_object_unref(GST_OBJECT(tcpsink_));
qLog(Debug) << "have tcpsink_";
tcpsink_ = nullptr;
}
return false;

View File

@ -38,6 +38,8 @@ class MediaPipeline {
void WriteData(const char* data, qint64 length);
void EndStream();
int port() const { return port_; }
private:
static void NeedDataCallback(GstAppSrc* src, guint length, void* data);
static void EnoughDataCallback(GstAppSrc* src, void* data);

View File

@ -300,6 +300,8 @@ void SpotifyClient::MessageArrived(const pb::spotify::Message& message) {
AddTracksToPlaylist(message.add_tracks_to_playlist());
} else if (message.has_remove_tracks_from_playlist()) {
RemoveTracksFromPlaylist(message.remove_tracks_from_playlist());
} else if (message.has_prefetch_request()) {
PrefetchTrack(message.prefetch_request());
}
}
@ -319,11 +321,14 @@ void SpotifyClient::SetPlaybackSettings(
}
qLog(Debug) << "Setting playback settings: bitrate" << bitrate
<< "normalisation" << req.volume_normalisation();
<< "normalisation" << req.volume_normalisation()
<< "gapless" << req.gapless();
sp_session_preferred_bitrate(session_, bitrate);
sp_session_preferred_offline_bitrate(session_, bitrate, false);
sp_session_set_volume_normalization(session_, req.volume_normalisation());
gapless_playback_ = req.gapless();
}
void SpotifyClient::Login(const pb::spotify::LoginRequest& req) {
@ -760,6 +765,39 @@ void SpotifyClient::MetadataUpdatedCallback(sp_session* session) {
me->pending_playback_requests_) {
me->TryPlaybackAgain(playback);
}
if (me->gapless_playback_) {
for (const PrefetchTrackRequest req : me->prefetched_tracks_.values()) {
me->ContinueGaplessPlayback(req);
}
}
qLog(Debug) << "MetadataUpdatedCallback";
}
void SpotifyClient::ContinueGaplessPlayback(const PrefetchTrackRequest& req) {
qLog(Debug) << "ContinueGaplessPlayback" << gapless_playback_;
if (!gapless_playback_)
return;
// If the track was not loaded then we have to come back later
if (!sp_track_is_loaded(req.track_)) {
qLog(Debug) << "Playback track not loaded yet, will try again later";
return;
}
sp_error error = sp_session_player_load(session_, req.track_);
qLog(Debug) << Q_FUNC_INFO << sp_error_message(error);
qLog(Debug) << "EndOfTrackCallback - new pipeline";
media_pipeline_.reset(media_pipeline_prefetch_);
media_pipeline_prefetch_ = nullptr;
error = sp_session_player_play(session_, true);
qLog(Debug) << Q_FUNC_INFO << sp_error_message(error);
sp_link_release(req.link_);
prefetched_tracks_.clear();
}
int SpotifyClient::MusicDeliveryCallback(sp_session* session,
@ -786,11 +824,27 @@ int SpotifyClient::MusicDeliveryCallback(sp_session* session,
}
if (!me->media_pipeline_->is_accepting_data()) {
qLog(Info) << "Pipeline not accepting data";
return 0;
}
me->media_pipeline_->WriteData(reinterpret_cast<const char*>(frames),
num_frames * format->channels * 2);
const char* buf = reinterpret_cast<const char*>(frames);
bool buf_null = true;
// Spotify sends a buffer with only null at the end of each track. To get gapless playback
// we have to check if the buffer is empty and discard this data.
if (me->gapless_playback_) {
for (int i=0;i<num_frames;++i) {
if (buf[i] != '\0') {
buf_null = false;
break;
}
}
}
if (!me->gapless_playback_ || !buf_null) {
me->media_pipeline_->WriteData(buf, num_frames * format->channels * 2);
}
return num_frames;
}
@ -799,7 +853,19 @@ void SpotifyClient::EndOfTrackCallback(sp_session* session) {
SpotifyClient* me =
reinterpret_cast<SpotifyClient*>(sp_session_userdata(session));
me->media_pipeline_.reset();
qLog(Debug) << "EndOfTrackCallback";
if (me->gapless_playback_ && !me->prefetched_tracks_.isEmpty()) {
for (const PrefetchTrackRequest req : me->prefetched_tracks_.values()) {
qLog(Debug) << "EndOfTrackCallback - something prefetched";
if (me->gapless_playback_ && me->media_pipeline_prefetch_) {
me->ContinueGaplessPlayback(req);
}
}
} else {
qLog(Debug) << "EndOfTrackCallback - reset";
me->media_pipeline_.reset();
}
}
void SpotifyClient::StreamingErrorCallback(sp_session* session,
@ -905,6 +971,9 @@ int SpotifyClient::GetDownloadProgress(sp_playlist* playlist) {
}
void SpotifyClient::StartPlayback(const pb::spotify::PlaybackRequest& req) {
qLog(Debug) << "StartPlayback";
QString uri = QString::fromStdString(req.track_uri());
// Get a link object from the URI
sp_link* link = sp_link_create_from_string(req.track_uri().c_str());
if (!link) {
@ -970,6 +1039,45 @@ void SpotifyClient::TryPlaybackAgain(const PendingPlaybackRequest& req) {
pending_playback_requests_.removeAll(req);
}
void SpotifyClient::PrefetchTrack(const pb::spotify::PlaybackRequest &req) {
// Get a link object from the URI
sp_link* link = sp_link_create_from_string(req.track_uri().c_str());
if (!link) {
SendPlaybackError("Invalid Spotify URI");
return;
}
// Get the track from the link
sp_track* track = sp_link_as_track(link);
if (!track) {
SendPlaybackError("Spotify URI was not a track");
sp_link_release(link);
return;
}
// Prefetch track
sp_error error = sp_session_player_prefetch(session_, track);
if (error != SP_ERROR_OK) {
qLog(Debug) << sp_error_message(error);
return;
}
QString uri = QString::fromStdString(req.track_uri());
PrefetchTrackRequest prefetch_request;
prefetch_request.uri_ = uri;
prefetch_request.link_ = link;
prefetch_request.track_ = track;
prefetched_tracks_.insert(uri, prefetch_request);
qLog(Debug) << "media_pipeline_prefetch_ to port" << req.media_port();
media_pipeline_prefetch_ = new MediaPipeline(req.media_port(),
sp_track_duration(track));
ContinueGaplessPlayback(prefetch_request);
}
void SpotifyClient::SendPlaybackError(const QString& error) {
pb::spotify::Message message;
pb::spotify::PlaybackError* msg = message.mutable_playback_error();

View File

@ -132,6 +132,7 @@ class SpotifyClient : public AbstractMessageHandler<pb::spotify::Message> {
void BrowseToplist(const pb::spotify::BrowseToplistRequest& req);
void SetPlaybackSettings(const pb::spotify::PlaybackSettings& req);
void SetPaused(const pb::spotify::PauseRequest& req);
void PrefetchTrack(const pb::spotify::PlaybackRequest& req);
void SendPlaylistList();
@ -167,12 +168,20 @@ class SpotifyClient : public AbstractMessageHandler<pb::spotify::Message> {
sp_image* image_;
};
struct PrefetchTrackRequest {
QString uri_;
sp_link* link_;
sp_track* track_;
};
void TryPlaybackAgain(const PendingPlaybackRequest& req);
void TryImageAgain(sp_image* image);
int GetDownloadProgress(sp_playlist* playlist);
void SendDownloadProgress(pb::spotify::PlaylistType type, int index,
int download_progress);
void ContinueGaplessPlayback(const PrefetchTrackRequest& req);
QByteArray api_key_;
QTcpSocket* protocol_socket_;
@ -194,11 +203,14 @@ class SpotifyClient : public AbstractMessageHandler<pb::spotify::Message> {
QMap<sp_albumbrowse*, QString> pending_album_browses_;
QMap<sp_toplistbrowse*, pb::spotify::BrowseToplistRequest>
pending_toplist_browses_;
QMap<QString, PrefetchTrackRequest> prefetched_tracks_;
bool gapless_playback_;
QMap<sp_search*, QList<sp_albumbrowse*>> pending_search_album_browses_;
QMap<sp_albumbrowse*, sp_search*> pending_search_album_browse_responses_;
QScopedPointer<MediaPipeline> media_pipeline_;
MediaPipeline* media_pipeline_prefetch_;
};
#endif // SPOTIFYCLIENT_H

View File

@ -39,7 +39,7 @@ static Level sDefaultLevel = Level_Debug;
static QMap<QString, Level>* sClassLevels = nullptr;
static QIODevice* sNullDevice = nullptr;
const char* kDefaultLogLevels = "GstEnginePipeline:2,*:3";
const char* kDefaultLogLevels = "GstEnginePipeline:3,*:3";
static const char* kMessageHandlerMagic = "__logging_message__";
static const int kMessageHandlerMagicLength = strlen(kMessageHandlerMagic);

View File

@ -186,6 +186,7 @@ enum Bitrate {
message PlaybackSettings {
optional Bitrate bitrate = 1 [default = Bitrate320k];
optional bool volume_normalisation = 2 [default = false];
optional bool gapless = 3 [default = false];
}
message PauseRequest {
@ -204,7 +205,7 @@ message RemoveTracksFromPlaylistRequest {
repeated int64 track_index = 3;
}
// NEXT_ID: 25
// NEXT_ID: 26
message Message {
// Not currently used
optional int32 id = 18;
@ -232,4 +233,5 @@ message Message {
// ID 22 unused.
optional AddTracksToPlaylistRequest add_tracks_to_playlist = 23;
optional RemoveTracksFromPlaylistRequest remove_tracks_from_playlist = 24;
optional PlaybackRequest prefetch_request = 25;
}

View File

@ -329,9 +329,10 @@ void GstEngine::StartPreloading(const QUrl& url, bool force_stop_at_end,
// No crossfading, so we can just queue the new URL in the existing
// pipeline and get gapless playback (hopefully)
if (current_pipeline_)
if (current_pipeline_) {
current_pipeline_->SetNextUrl(gst_url, beginning_nanosec,
force_stop_at_end ? end_nanosec : 0);
}
}
QUrl GstEngine::FixupUrl(const QUrl& url) {
@ -351,6 +352,8 @@ QUrl GstEngine::FixupUrl(const QUrl& url) {
bool GstEngine::Load(const QUrl& url, Engine::TrackChangeFlags change,
bool force_stop_at_end, quint64 beginning_nanosec,
qint64 end_nanosec) {
qLog(Debug) << "GstEngine::Load";
EnsureInitialised();
Engine::Base::Load(url, change, force_stop_at_end, beginning_nanosec,
@ -366,10 +369,14 @@ bool GstEngine::Load(const QUrl& url, Engine::TrackChangeFlags change,
!crossfade_same_album_)
crossfade = false;
if (current_pipeline_)
qLog(Debug) << !crossfade << "true" << (current_pipeline_->url() == gst_url) <<
(change & Engine::Auto);
if (!crossfade && current_pipeline_ && current_pipeline_->url() == gst_url &&
change & Engine::Auto) {
// We're not crossfading, and the pipeline is already playing the URI we
// want, so just do nothing.
qLog(Debug) << "do nothing";
return true;
}
@ -647,7 +654,7 @@ void GstEngine::timerEvent(QTimerEvent* e) {
const qint64 remaining = current_length - current_position;
const qint64 fudge =
kTimerIntervalNanosec + 100 * kNsecPerMsec; // Mmm fudge
kTimerIntervalNanosec + 2500 * kNsecPerMsec; // Mmm fudge
const qint64 gap = buffer_duration_nanosec_ +
(autocrossfade_enabled_ ? fadeout_duration_nanosec_
: kPreloadGapNanosec);
@ -696,6 +703,8 @@ void GstEngine::EndOfStreamReached(int pipeline_id, bool has_next_track) {
if (!current_pipeline_.get() || current_pipeline_->id() != pipeline_id)
return;
qLog(Debug) << "EndOfStreamReached" << pipeline_id << has_next_track;
if (!has_next_track) {
current_pipeline_.reset();
BufferingFinished();

View File

@ -89,7 +89,8 @@ GstEnginePipeline::GstEnginePipeline(GstEngine* engine)
stereo_panorama_(nullptr),
volume_(nullptr),
audioscale_(nullptr),
audiosink_(nullptr) {
audiosink_(nullptr),
spotify_id_(0) {
if (!sElementDeleter) {
sElementDeleter = new GstElementDeleter;
}
@ -147,7 +148,9 @@ bool GstEnginePipeline::ReplaceDecodeBin(const QUrl& url) {
GstElement* new_bin = nullptr;
if (url.scheme() == "spotify") {
new_bin = gst_bin_new("spotify_bin");
QString name = "spotify_bin_" + QString::number(spotify_id_++);
qLog(Debug) << "Spotify bin name: " << name;
new_bin = gst_bin_new(name.toAscii().constData());
// Create elements
GstElement* src = engine_->CreateElement("tcpserversrc", new_bin);
@ -167,9 +170,16 @@ bool GstEnginePipeline::ReplaceDecodeBin(const QUrl& url) {
gst_element_add_pad(GST_ELEMENT(new_bin), gst_ghost_pad_new("src", pad));
gst_object_unref(GST_OBJECT(pad));
// Tell spotify to start sending data to us.
InternetModel::Service<SpotifyService>()->server()->StartPlaybackLater(
url.toString(), port);
qLog(Debug) << "has_next_valid_url()" << has_next_valid_url();
SpotifyService* spotify = InternetModel::Service<SpotifyService>();
if (has_next_valid_url()) {
QMetaObject::invokeMethod(spotify, "SetNextUrl", Qt::QueuedConnection,
Q_ARG(QUrl, url), Q_ARG(int, port));
} else {
// Tell spotify to start sending data to us.
spotify->server()->StartPlaybackLater(
url.toString(), port);
}
} else {
new_bin = engine_->CreateElement("uridecodebin");
g_object_set(G_OBJECT(new_bin), "uri", url.toEncoded().constData(),
@ -487,12 +497,12 @@ GstBusSyncReply GstEnginePipeline::BusCallbackSync(GstBus*, GstMessage* msg,
gpointer self) {
GstEnginePipeline* instance = reinterpret_cast<GstEnginePipeline*>(self);
qLog(Debug) << instance->id() << "sync bus message"
<< GST_MESSAGE_TYPE_NAME(msg);
//qLog(Debug) << instance->id() << "sync bus message"
// << GST_MESSAGE_TYPE_NAME(msg);
switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_EOS:
emit instance->EndOfStreamReached(instance->id(), false);
emit instance->EndOfStreamReached(instance->id(), instance->has_next_valid_url());
break;
case GST_MESSAGE_TAG:
@ -827,6 +837,7 @@ GstPadProbeReturn GstEnginePipeline::HandoffCallback(GstPad*,
GST_BUFFER_OFFSET(buf) < instance->last_buffer_offset_) {
qLog(Debug) << "Buffer discontinuity - emitting EOS";
instance->emit_track_ended_on_time_discontinuity_ = false;
instance->next_url_ = QUrl();
emit instance->EndOfStreamReached(instance->id(), true);
}
}
@ -867,6 +878,8 @@ void GstEnginePipeline::SourceDrainedCallback(GstURIDecodeBin* bin,
gpointer self) {
GstEnginePipeline* instance = reinterpret_cast<GstEnginePipeline*>(self);
qDebug() << "SourceDrainedCallback" << instance->has_next_valid_url();
if (instance->has_next_valid_url()) {
instance->TransitionToNext();
}
@ -931,6 +944,7 @@ void GstEnginePipeline::SourceSetupCallback(GstURIDecodeBin* bin,
}
void GstEnginePipeline::TransitionToNext() {
qLog(Debug) << "TransitionToNext";
GstElement* old_decode_bin = uridecodebin_;
ignore_tags_ = true;
@ -1164,4 +1178,10 @@ void GstEnginePipeline::SetNextUrl(const QUrl& url, qint64 beginning_nanosec,
next_url_ = url;
next_beginning_offset_nanosec_ = beginning_nanosec;
next_end_offset_nanosec_ = end_nanosec;
if (url.scheme() == "spotify") {
TransitionToNext();
next_url_ = url;
}
}

View File

@ -82,6 +82,7 @@ class GstEnginePipeline : public QObject {
void SetNextUrl(const QUrl& url, qint64 beginning_nanosec,
qint64 end_nanosec);
bool has_next_valid_url() const { return next_url_.isValid(); }
QUrl next_url() const { return next_url_; }
// Get information about the music playback
QUrl url() const { return url_; }
@ -157,7 +158,7 @@ signals:
bool ReplaceDecodeBin(GstElement* new_bin);
bool ReplaceDecodeBin(const QUrl& url);
void TransitionToNext();
Q_INVOKABLE void TransitionToNext();
// If the decodebin is special (ie. not really a uridecodebin) then it'll have
// a src pad immediately and we can link it after everything's created.
@ -290,6 +291,8 @@ signals:
QThreadPool set_state_threadpool_;
GstSegment last_decodebin_segment_;
int spotify_id_;
};
#endif // GSTENGINEPIPELINE_H

View File

@ -77,7 +77,8 @@ void SpotifyServer::SendOrQueueMessage(const pb::spotify::Message& message) {
void SpotifyServer::Login(const QString& username, const QString& password,
pb::spotify::Bitrate bitrate,
bool volume_normalisation) {
bool volume_normalisation,
bool gapless) {
pb::spotify::Message message;
pb::spotify::LoginRequest* request = message.mutable_login_request();
@ -88,6 +89,7 @@ void SpotifyServer::Login(const QString& username, const QString& password,
request->mutable_playback_settings()->set_bitrate(bitrate);
request->mutable_playback_settings()->set_volume_normalisation(
volume_normalisation);
request->mutable_playback_settings()->set_gapless(gapless);
SendOrQueueMessage(message);
}
@ -328,3 +330,12 @@ void SpotifyServer::SetPaused(const bool paused) {
req->set_paused(paused);
SendOrQueueMessage(message);
}
void SpotifyServer::PrefetchTrack(const QString& uri, quint16 port) {
pb::spotify::Message message;
pb::spotify::PlaybackRequest* req = message.mutable_prefetch_request();
req->set_track_uri(DataCommaSizeFromQString(uri));
req->set_media_port(port);
SendOrQueueMessage(message);
}

View File

@ -39,7 +39,7 @@ class SpotifyServer : public AbstractMessageHandler<pb::spotify::Message> {
void Init();
void Login(const QString& username, const QString& password,
pb::spotify::Bitrate bitrate, bool volume_normalisation);
pb::spotify::Bitrate bitrate, bool volume_normalisation, bool gapless);
void LoadStarred();
void SyncStarred();
@ -61,6 +61,7 @@ class SpotifyServer : public AbstractMessageHandler<pb::spotify::Message> {
bool volume_normalisation);
void LoadToplist();
void SetPaused(const bool paused);
void PrefetchTrack(const QString& uri, quint16 port);
int server_port() const;

View File

@ -86,7 +86,8 @@ SpotifyService::SpotifyService(Application* app, InternetModel* parent)
search_delay_(new QTimer(this)),
login_state_(LoginState_OtherError),
bitrate_(pb::spotify::Bitrate320k),
volume_normalisation_(false) {
volume_normalisation_(false),
gapless_(false) {
// Build the search path for the binary blob.
// Look for one distributed alongside clementine first, then check in the
// user's home directory for any that have been downloaded.
@ -249,6 +250,7 @@ void SpotifyService::ReloadSettings() {
bitrate_ = static_cast<pb::spotify::Bitrate>(
s.value("bitrate", pb::spotify::Bitrate320k).toInt());
volume_normalisation_ = s.value("volume_normalisation", false).toBool();
gapless_ = s.value("gapless", false).toBool();
if (server_ && blob_process_) {
server_->SetPlaybackSettings(bitrate_, volume_normalisation_);
@ -306,7 +308,7 @@ void SpotifyService::EnsureServerCreated(const QString& username,
}
server_->Login(login_username, login_password, bitrate_,
volume_normalisation_);
volume_normalisation_, gapless_);
StartBlobProcess();
}
@ -340,7 +342,6 @@ void SpotifyService::StartBlobProcess() {
InstallBlob();
}
#endif
return;
}
@ -871,6 +872,12 @@ void SpotifyService::SetPaused(bool paused) {
server_->SetPaused(paused);
}
void SpotifyService::SetNextUrl(const QUrl &url, const int port) {
qLog(Debug) << "Next Spotify Url" << url << port;
EnsureServerCreated();
server_->PrefetchTrack(url.toString(), port);
}
void SpotifyService::SyncPlaylistProgress(
const pb::spotify::SyncPlaylistProgress& progress) {
qLog(Debug) << "Sync progress:" << progress.sync_progress();

View File

@ -83,6 +83,7 @@ class SpotifyService : public InternetService {
void Login(const QString& username, const QString& password);
Q_INVOKABLE void LoadImage(const QString& id);
Q_INVOKABLE void SetPaused(bool paused);
Q_INVOKABLE void SetNextUrl(const QUrl& url, const int port);
SpotifyServer* server() const;
@ -190,6 +191,7 @@ signals:
LoginState login_state_;
pb::spotify::Bitrate bitrate_;
bool volume_normalisation_;
bool gapless_;
};
#endif // INTERNET_SPOTIFY_SPOTIFYSERVICE_H_

View File

@ -111,6 +111,7 @@ void SpotifySettingsPage::Load() {
s.value("bitrate", pb::spotify::Bitrate320k).toInt()));
ui_->volume_normalisation->setChecked(
s.value("volume_normalisation", false).toBool());
ui_->gapless->setChecked(s.value("gapless", false).toBool());
UpdateLoginState();
}
@ -125,6 +126,7 @@ void SpotifySettingsPage::Save() {
s.setValue("bitrate",
ui_->bitrate->itemData(ui_->bitrate->currentIndex()).toInt());
s.setValue("volume_normalisation", ui_->volume_normalisation->isChecked());
s.setValue("gapless", ui_->gapless->isChecked());
}
void SpotifySettingsPage::LoginFinished(bool success) {

View File

@ -29,7 +29,16 @@
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="margin">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="1" column="0">
@ -135,7 +144,14 @@
<item row="0" column="1">
<widget class="QComboBox" name="bitrate"/>
</item>
<item row="1" column="0" colspan="2">
<item row="2" column="0">
<widget class="QCheckBox" name="gapless">
<property name="text">
<string>Gapless playback</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="volume_normalisation">
<property name="text">
<string>Use volume normalisation</string>
@ -188,7 +204,7 @@
</size>
</property>
<property name="pixmap">
<pixmap resource="../../data/data.qrc">:/spotify-attribution.png</pixmap>
<pixmap resource="../../../data/data.qrc">:/spotify-attribution.png</pixmap>
</property>
</widget>
</item>
@ -205,7 +221,7 @@
</customwidget>
</customwidgets>
<resources>
<include location="../../data/data.qrc"/>
<include location="../../../data/data.qrc"/>
</resources>
<connections/>
</ui>