Compare commits
12 Commits
master
...
spotify-ga
Author | SHA1 | Date | |
---|---|---|---|
|
dd2ba0fe33 | ||
|
e916db9ae1 | ||
|
a1855f84bc | ||
|
c62cc10759 | ||
|
8ab13d2b23 | ||
|
341d5d3722 | ||
|
a759fdb2e0 | ||
|
db14b75fe7 | ||
|
fbc25a0d87 | ||
|
b5a6d42d32 | ||
|
8e3fa5552a | ||
|
509b28aaeb |
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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_
|
||||
|
@ -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) {
|
||||
|
@ -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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user