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:
parent
55e5eab157
commit
509b28aaeb
@ -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
|
||||
|
@ -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;
|
||||
@ -967,6 +985,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();
|
||||
|
@ -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_;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -332,9 +332,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) {
|
||||
@ -354,6 +355,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,
|
||||
@ -688,10 +691,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();
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
@ -1155,4 +1157,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));
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user