diff --git a/ext/clementine-spotifyblob/mediapipeline.cpp b/ext/clementine-spotifyblob/mediapipeline.cpp index c44172917..31f211c78 100644 --- a/ext/clementine-spotifyblob/mediapipeline.cpp +++ b/ext/clementine-spotifyblob/mediapipeline.cpp @@ -53,17 +53,21 @@ bool MediaPipeline::Init(int sample_rate, int channels) { if (!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; @@ -116,7 +120,7 @@ bool MediaPipeline::Init(int sample_rate, int channels) { // Set size byte_rate_ = quint64(sample_rate) * channels * 2; - const quint64 bytes = -1; //byte_rate_ * length_msec_ / 1000; + const quint64 bytes = byte_rate_ * length_msec_ / 1000; gst_app_src_set_size(appsrc_, bytes); // Ready to go @@ -143,12 +147,6 @@ void MediaPipeline::WriteData(const char* data, qint64 length) { gst_app_src_push_buffer(appsrc_, buffer); } -void MediaPipeline::SendEvent() { - GstTagList* list = gst_tag_list_new_empty(); - GstEvent* tag = gst_event_new_tag(list); - gst_element_send_event(pipeline_, tag); -} - void MediaPipeline::EndStream() { if (!is_initialised()) return; diff --git a/ext/clementine-spotifyblob/mediapipeline.h b/ext/clementine-spotifyblob/mediapipeline.h index dc5b5d15b..90a4b1bf5 100644 --- a/ext/clementine-spotifyblob/mediapipeline.h +++ b/ext/clementine-spotifyblob/mediapipeline.h @@ -37,7 +37,6 @@ class MediaPipeline { void WriteData(const char* data, qint64 length); void EndStream(); - void SendEvent(); int port() const { return port_; } diff --git a/ext/clementine-spotifyblob/spotifyclient.cpp b/ext/clementine-spotifyblob/spotifyclient.cpp index 151d92de7..2387a8f58 100644 --- a/ext/clementine-spotifyblob/spotifyclient.cpp +++ b/ext/clementine-spotifyblob/spotifyclient.cpp @@ -851,9 +851,16 @@ void SpotifyClient::EndOfTrackCallback(sp_session* session) { SpotifyClient* me = reinterpret_cast(sp_session_userdata(session)); + qLog(Debug) << "EndOfTrackCallback"; + if (me->gapless_playback_ && !me->prefetched_tracks_.isEmpty()) { for (const PrefetchTrackRequest req : me->prefetched_tracks_.values()) { - me->ContinueGaplessPlayback(req); + if (me->gapless_playback_ && me->media_pipeline_prefetch_) { + qLog(Debug) << "EndOfTrackCallback - new pipeline"; + me->media_pipeline_.reset(me->media_pipeline_prefetch_); + me->media_pipeline_prefetch_ = nullptr; + me->ContinueGaplessPlayback(req); + } } } else { me->media_pipeline_.reset(); @@ -1030,7 +1037,7 @@ void SpotifyClient::TryPlaybackAgain(const PendingPlaybackRequest& req) { pending_playback_requests_.removeAll(req); } -void SpotifyClient::PrefetchTrack(const pb::spotify::PrefetchRequest &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) { @@ -1062,8 +1069,8 @@ void SpotifyClient::PrefetchTrack(const pb::spotify::PrefetchRequest &req) { prefetched_tracks_.insert(uri, prefetch_request); - qDebug() << "Sending event"; - media_pipeline_->SendEvent(); + media_pipeline_prefetch_ = new MediaPipeline(req.media_port(), + sp_track_duration(track)); } void SpotifyClient::SendPlaybackError(const QString& error) { diff --git a/ext/clementine-spotifyblob/spotifyclient.h b/ext/clementine-spotifyblob/spotifyclient.h index 1c6cfa428..5f2639768 100644 --- a/ext/clementine-spotifyblob/spotifyclient.h +++ b/ext/clementine-spotifyblob/spotifyclient.h @@ -132,7 +132,7 @@ class SpotifyClient : public AbstractMessageHandler { 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 PrefetchTrack(const pb::spotify::PlaybackRequest& req); void SendPlaylistList(); @@ -210,6 +210,7 @@ class SpotifyClient : public AbstractMessageHandler { QMap pending_search_album_browse_responses_; QScopedPointer media_pipeline_; + MediaPipeline* media_pipeline_prefetch_; }; #endif // SPOTIFYCLIENT_H diff --git a/ext/libclementine-spotifyblob/spotifymessages.proto b/ext/libclementine-spotifyblob/spotifymessages.proto index 28e2aaa18..508ccfda9 100644 --- a/ext/libclementine-spotifyblob/spotifymessages.proto +++ b/ext/libclementine-spotifyblob/spotifymessages.proto @@ -108,10 +108,6 @@ message PlaybackRequest { required int32 media_port = 2; } -message PrefetchRequest { - required string track_uri = 1; -} - message PlaybackError { required string error = 1; } @@ -237,5 +233,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; + optional PlaybackRequest prefetch_request = 25; } diff --git a/src/engines/gstengine.cpp b/src/engines/gstengine.cpp index 3dd6e411a..dfd2cfb18 100755 --- a/src/engines/gstengine.cpp +++ b/src/engines/gstengine.cpp @@ -657,12 +657,6 @@ void GstEngine::timerEvent(QTimerEvent* e) { // only if we know the length of the current stream... if (current_length > 0) { - if (remaining < 0) { - qDebug() << "Remaning 0" << current_pipeline_->has_next_valid_url(); - EndOfStreamReached(current_pipeline_->id(), current_pipeline_->has_next_valid_url()); - //current_pipeline_->SpotifyMovedToNextTrack(); - return; - } // emit TrackAboutToEnd when we're a few seconds away from finishing if (remaining < gap + fudge) { EmitAboutToEnd(); diff --git a/src/engines/gstenginepipeline.cpp b/src/engines/gstenginepipeline.cpp index 172714233..b569d7b00 100644 --- a/src/engines/gstenginepipeline.cpp +++ b/src/engines/gstenginepipeline.cpp @@ -79,6 +79,7 @@ GstEnginePipeline::GstEnginePipeline(GstEngine* engine) volume_modifier_(1.0), pipeline_(nullptr), uridecodebin_(nullptr), + uridecodebin_next_spotify_(nullptr), audiobin_(nullptr), queue_(nullptr), audioconvert_(nullptr), @@ -89,7 +90,8 @@ GstEnginePipeline::GstEnginePipeline(GstEngine* engine) stereo_panorama_(nullptr), volume_(nullptr), audioscale_(nullptr), - audiosink_(nullptr) { + audiosink_(nullptr), + spotify_port_(0) { if (!sElementDeleter) { sElementDeleter = new GstElementDeleter; } @@ -147,29 +149,16 @@ bool GstEnginePipeline::ReplaceDecodeBin(const QUrl& url) { GstElement* new_bin = nullptr; if (url.scheme() == "spotify") { - new_bin = gst_bin_new("spotify_bin"); - - // Create elements - GstElement* src = engine_->CreateElement("tcpserversrc", new_bin); - GstElement* gdp = engine_->CreateElement("gdpdepay", new_bin); - if (!src || !gdp) return false; - - // Pick a port number - const int port = Utilities::PickUnusedPort(); - g_object_set(G_OBJECT(src), "host", "127.0.0.1", nullptr); - g_object_set(G_OBJECT(src), "port", port, nullptr); - - // Link the elements - gst_element_link(src, gdp); - - // Add a ghost pad - GstPad* pad = gst_element_get_static_pad(gdp, "src"); - gst_element_add_pad(GST_ELEMENT(new_bin), gst_ghost_pad_new("src", pad)); - gst_object_unref(GST_OBJECT(pad)); + if (uridecodebin_next_spotify_) { + new_bin = uridecodebin_next_spotify_; + uridecodebin_next_spotify_ = nullptr; + } else { + new_bin = NewSpotifyBin(); + } // Tell spotify to start sending data to us. InternetModel::Service()->server()->StartPlaybackLater( - url.toString(), port); + url.toString(), spotify_port_); } else { new_bin = engine_->CreateElement("uridecodebin"); g_object_set(G_OBJECT(new_bin), "uri", url.toEncoded().constData(), @@ -184,6 +173,30 @@ bool GstEnginePipeline::ReplaceDecodeBin(const QUrl& url) { return ReplaceDecodeBin(new_bin); } +GstElement* GstEnginePipeline::NewSpotifyBin() { + GstElement* new_bin = gst_bin_new("spotify_bin"); + + // Create elements + GstElement* src = engine_->CreateElement("tcpserversrc", new_bin); + GstElement* gdp = engine_->CreateElement("gdpdepay", new_bin); + if (!src || !gdp) return nullptr; + + // Pick a port number + spotify_port_ = Utilities::PickUnusedPort(); + g_object_set(G_OBJECT(src), "host", "127.0.0.1", nullptr); + g_object_set(G_OBJECT(src), "port", spotify_port_, nullptr); + + // Link the elements + gst_element_link(src, gdp); + + // Add a ghost pad + GstPad* pad = gst_element_get_static_pad(gdp, "src"); + gst_element_add_pad(GST_ELEMENT(new_bin), gst_ghost_pad_new("src", pad)); + gst_object_unref(GST_OBJECT(pad)); + + return new_bin; +} + GstElement* GstEnginePipeline::CreateDecodeBinFromString(const char* pipeline) { GError* error = nullptr; GstElement* bin = gst_parse_bin_from_description(pipeline, TRUE, &error); @@ -492,7 +505,11 @@ GstBusSyncReply GstEnginePipeline::BusCallbackSync(GstBus*, GstMessage* msg, switch (GST_MESSAGE_TYPE(msg)) { case GST_MESSAGE_EOS: - emit instance->EndOfStreamReached(instance->id(), instance->has_next_valid_url()); + if (instance->has_next_valid_url()) { + QMetaObject::invokeMethod(instance, "TransitionToNext", Qt::QueuedConnection); + } else { + emit instance->EndOfStreamReached(instance->id(), instance->has_next_valid_url()); + } break; case GST_MESSAGE_TAG: @@ -1168,9 +1185,12 @@ void GstEnginePipeline::SetNextUrl(const QUrl& url, qint64 beginning_nanosec, next_end_offset_nanosec_ = end_nanosec; if (url.scheme() == "spotify") { + // Call this first, then we have a new spotify_port_ + uridecodebin_next_spotify_ = NewSpotifyBin(); + SpotifyService* spotify = InternetModel::Service(); QMetaObject::invokeMethod(spotify, "SetNextUrl", Qt::QueuedConnection, - Q_ARG(QUrl, url)); + Q_ARG(QUrl, url), Q_ARG(int, spotify_port_)); } } diff --git a/src/engines/gstenginepipeline.h b/src/engines/gstenginepipeline.h index 53ab67b4a..ca818c2e7 100644 --- a/src/engines/gstenginepipeline.h +++ b/src/engines/gstenginepipeline.h @@ -158,8 +158,9 @@ signals: void UpdateStereoBalance(); bool ReplaceDecodeBin(GstElement* new_bin); bool ReplaceDecodeBin(const QUrl& url); + GstElement* NewSpotifyBin(); - 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. @@ -272,6 +273,7 @@ signals: // Bins // uridecodebin ! audiobin GstElement* uridecodebin_; + GstElement* uridecodebin_next_spotify_; GstElement* audiobin_; // Elements in the audiobin. See comments in Init()'s definition. @@ -292,6 +294,8 @@ signals: QThreadPool set_state_threadpool_; GstSegment last_decodebin_segment_; + + int spotify_port_; }; #endif // GSTENGINEPIPELINE_H diff --git a/src/internet/spotify/spotifyserver.cpp b/src/internet/spotify/spotifyserver.cpp index f39bbe455..16c710a80 100644 --- a/src/internet/spotify/spotifyserver.cpp +++ b/src/internet/spotify/spotifyserver.cpp @@ -39,7 +39,7 @@ SpotifyServer::SpotifyServer(QObject* parent) } void SpotifyServer::Init() { - if (!server_->listen(QHostAddress::LocalHost)) { + if (!server_->listen(QHostAddress::LocalHost, 56154)) { qLog(Error) << "Couldn't open server socket" << server_->errorString(); } } @@ -331,9 +331,11 @@ void SpotifyServer::SetPaused(const bool paused) { SendOrQueueMessage(message); } -void SpotifyServer::PrefetchTrack(const QUrl& url) { +void SpotifyServer::PrefetchTrack(const QUrl& url, const int port) { pb::spotify::Message message; - pb::spotify::PrefetchRequest* req = message.mutable_prefetch_request(); + pb::spotify::PlaybackRequest* req = message.mutable_prefetch_request(); req->set_track_uri(DataCommaSizeFromQString(url.toString())); + req->set_media_port(port); + SendOrQueueMessage(message); } diff --git a/src/internet/spotify/spotifyserver.h b/src/internet/spotify/spotifyserver.h index d0bad67bf..3849fb1b5 100644 --- a/src/internet/spotify/spotifyserver.h +++ b/src/internet/spotify/spotifyserver.h @@ -61,7 +61,7 @@ class SpotifyServer : public AbstractMessageHandler { bool volume_normalisation); void LoadToplist(); void SetPaused(const bool paused); - void PrefetchTrack(const QUrl& url); + void PrefetchTrack(const QUrl& url, const int port); int server_port() const; diff --git a/src/internet/spotify/spotifyservice.cpp b/src/internet/spotify/spotifyservice.cpp index 1fe77614d..90b90eb24 100644 --- a/src/internet/spotify/spotifyservice.cpp +++ b/src/internet/spotify/spotifyservice.cpp @@ -342,7 +342,6 @@ void SpotifyService::StartBlobProcess() { InstallBlob(); } #endif - return; } @@ -355,8 +354,8 @@ void SpotifyService::StartBlobProcess() { SLOT(BlobProcessError(QProcess::ProcessError))); qLog(Info) << "Starting" << blob_path; - blob_process_->start( - blob_path, QStringList() << QString::number(server_->server_port())); + //blob_process_->start( + // blob_path, QStringList() << QString::number(server_->server_port())); } bool SpotifyService::IsBlobInstalled() const { @@ -873,10 +872,10 @@ void SpotifyService::SetPaused(bool paused) { server_->SetPaused(paused); } -void SpotifyService::SetNextUrl(const QUrl &url) { - qLog(Debug) << "Next Spotify Url" << url; +void SpotifyService::SetNextUrl(const QUrl &url, const int port) { + qLog(Debug) << "Next Spotify Url" << url << port; EnsureServerCreated(); - server_->PrefetchTrack(url); + server_->PrefetchTrack(url, port); } void SpotifyService::SyncPlaylistProgress( diff --git a/src/internet/spotify/spotifyservice.h b/src/internet/spotify/spotifyservice.h index 10a53f81d..95a30fa68 100644 --- a/src/internet/spotify/spotifyservice.h +++ b/src/internet/spotify/spotifyservice.h @@ -83,7 +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); + Q_INVOKABLE void SetNextUrl(const QUrl& url, const int port); SpotifyServer* server() const;