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.
This commit is contained in:
Andreas 2015-03-14 15:22:39 +01:00 committed by Andreas
parent 6ecd47011a
commit db14b75fe7
10 changed files with 109 additions and 19 deletions

View File

@ -116,7 +116,7 @@ bool MediaPipeline::Init(int sample_rate, int channels) {
// Set size
byte_rate_ = quint64(sample_rate) * channels * 2;
const quint64 bytes = byte_rate_ * length_msec_ / 1000;
const quint64 bytes = 2* byte_rate_ * length_msec_ / 1000;
gst_app_src_set_size(appsrc_, bytes);
// Ready to go

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());
}
}
@ -905,20 +907,36 @@ int SpotifyClient::GetDownloadProgress(sp_playlist* playlist) {
}
void SpotifyClient::StartPlayback(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;
QString uri = QString::fromStdString(req.track_uri());
sp_link* link;
sp_track* track;
if (prefetched_tracks_.contains(uri)) {
PrefetchTrackRequest prefetch_request;
prefetch_request = prefetched_tracks_.take(uri);
link = prefetch_request.link_;
track = prefetch_request.track_;
qLog(Debug) << "Using prefetched track";
} else {
// Get a link object from the URI
link = sp_link_create_from_string(req.track_uri().c_str());
if (!link) {
SendPlaybackError("Invalid Spotify URI");
return;
}
// Get the track from the link
track = sp_link_as_track(link);
if (!track) {
SendPlaybackError("Spotify URI was not a track");
sp_link_release(link);
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;
}
prefetched_tracks_.clear();
PendingPlaybackRequest pending_playback;
pending_playback.request_ = req;
@ -970,6 +988,39 @@ void SpotifyClient::TryPlaybackAgain(const PendingPlaybackRequest& req) {
pending_playback_requests_.removeAll(req);
}
void SpotifyClient::PrefetchTrack(const pb::spotify::PrefetchRequest &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);
}
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::PrefetchRequest& req);
void SendPlaylistList();
@ -167,6 +168,12 @@ 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);
@ -194,6 +201,7 @@ 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_;
QMap<sp_search*, QList<sp_albumbrowse*>> pending_search_album_browses_;
QMap<sp_albumbrowse*, sp_search*> pending_search_album_browse_responses_;

View File

@ -108,6 +108,10 @@ message PlaybackRequest {
required int32 media_port = 2;
}
message PrefetchRequest {
required string track_uri = 1;
}
message PlaybackError {
required string error = 1;
}
@ -204,7 +208,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 +236,5 @@ message Message {
// ID 22 unused.
optional AddTracksToPlaylistRequest add_tracks_to_playlist = 23;
optional RemoveTracksFromPlaylistRequest remove_tracks_from_playlist = 24;
optional PrefetchRequest 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,
@ -696,10 +699,10 @@ void GstEngine::EndOfStreamReached(int pipeline_id, bool has_next_track) {
if (!current_pipeline_.get() || current_pipeline_->id() != pipeline_id)
return;
if (!has_next_track) {
/*if (!has_next_track) {
current_pipeline_.reset();
BufferingFinished();
}
}*/
emit TrackEnded();
}

View File

@ -487,8 +487,8 @@ 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:
@ -867,6 +867,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();
}
@ -1164,4 +1166,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") {
SpotifyService* spotify = InternetModel::Service<SpotifyService>();
QMetaObject::invokeMethod(spotify, "SetNextUrl", Qt::QueuedConnection,
Q_ARG(QUrl, url));
}
}

View File

@ -328,3 +328,10 @@ void SpotifyServer::SetPaused(const bool paused) {
req->set_paused(paused);
SendOrQueueMessage(message);
}
void SpotifyServer::PrefetchTrack(const QUrl& url) {
pb::spotify::Message message;
pb::spotify::PrefetchRequest* req = message.mutable_prefetch_request();
req->set_track_uri(DataCommaSizeFromQString(url.toString()));
SendOrQueueMessage(message);
}

View File

@ -61,6 +61,7 @@ class SpotifyServer : public AbstractMessageHandler<pb::spotify::Message> {
bool volume_normalisation);
void LoadToplist();
void SetPaused(const bool paused);
void PrefetchTrack(const QUrl& url);
int server_port() const;

View File

@ -871,6 +871,12 @@ void SpotifyService::SetPaused(bool paused) {
server_->SetPaused(paused);
}
void SpotifyService::SetNextUrl(const QUrl &url) {
qLog(Debug) << "Next Spotify Url" << url;
EnsureServerCreated();
server_->PrefetchTrack(url);
}
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);
SpotifyServer* server() const;