diff --git a/spotifyblob/spotifyclient.cpp b/spotifyblob/spotifyclient.cpp index ef6172d98..2c3046812 100644 --- a/spotifyblob/spotifyclient.cpp +++ b/spotifyblob/spotifyclient.cpp @@ -402,7 +402,7 @@ int SpotifyClient::MusicDeliveryCallback( const int bytes_per_sample = 2; const int byte_rate = format->sample_rate * format->channels * bytes_per_sample; - const quint32 data_size = me->media_length_msec_ * byte_rate / 1000; + const quint32 data_size = me->media_length_msec_ * byte_rate; // RIFF header s.writeRawData("RIFF", 4); @@ -479,13 +479,15 @@ void SpotifyClient::StartPlayback(const protobuf::PlaybackRequest& req) { } // Create the media socket - if (media_socket_) { - media_socket_->close(); - } + QTcpSocket* old_media_socket = media_socket_; media_socket_ = new QTcpSocket(this); media_socket_->connectToHost(QHostAddress::LocalHost, req.media_port()); connect(media_socket_, SIGNAL(disconnected()), SLOT(MediaSocketDisconnected())); + if (old_media_socket) { + old_media_socket->close(); + } + qLog(Info) << "Starting playback of uri" << req.track_uri().c_str() << "to port" << req.media_port(); diff --git a/src/engines/gstenginepipeline.cpp b/src/engines/gstenginepipeline.cpp index 24804ee18..63c50b34b 100644 --- a/src/engines/gstenginepipeline.cpp +++ b/src/engines/gstenginepipeline.cpp @@ -64,6 +64,7 @@ GstEnginePipeline::GstEnginePipeline(GstEngine* engine) volume_modifier_(1.0), fader_(NULL), pipeline_(NULL), + tcpsrc_(NULL), uridecodebin_(NULL), audiobin_(NULL), queue_(NULL), @@ -101,35 +102,59 @@ void GstEnginePipeline::set_buffer_duration_nanosec(qint64 buffer_duration_nanos buffer_duration_nanosec_ = buffer_duration_nanosec; } -bool GstEnginePipeline::ReplaceDecodeBin(GstElement* new_bin) { +bool GstEnginePipeline::ReplaceDecodeBin(GstElement* new_bin, + GstElement* new_tcpsrc) { if (!new_bin) return false; - // Destroy the old one, if any + // Destroy the old elements if they are set + // Note that the caller to this function MUST schedule the old uridecodebin_ + // or tcpsrc_ for deletion in the main thread. if (uridecodebin_) { gst_bin_remove(GST_BIN(pipeline_), uridecodebin_); - - // Note that the caller to this function MUST schedule the old bin for - // deletion in the main thread + } + if (tcpsrc_) { + gst_bin_remove(GST_BIN(pipeline_), tcpsrc_); } uridecodebin_ = new_bin; + tcpsrc_ = new_tcpsrc; segment_start_ = 0; segment_start_received_ = false; pipeline_is_connected_ = false; gst_bin_add(GST_BIN(pipeline_), uridecodebin_); + if (new_tcpsrc) { + gst_bin_add(GST_BIN(pipeline_), tcpsrc_); + gst_element_link(tcpsrc_, uridecodebin_); + } + return true; } bool GstEnginePipeline::ReplaceDecodeBin(const QUrl& url) { - GstElement* new_bin = engine_->CreateElement("uridecodebin"); - g_object_set(G_OBJECT(new_bin), "uri", url.toEncoded().constData(), NULL); - g_object_set(G_OBJECT(new_bin), "buffer-duration", buffer_duration_nanosec_, NULL); - g_object_set(G_OBJECT(new_bin), "download", true, NULL); - g_object_set(G_OBJECT(new_bin), "use-buffering", true, NULL); - g_signal_connect(G_OBJECT(new_bin), "drained", G_CALLBACK(SourceDrainedCallback), this); - g_signal_connect(G_OBJECT(new_bin), "pad-added", G_CALLBACK(NewPadCallback), this); - return ReplaceDecodeBin(new_bin); + if (url.scheme() == "tcp") { + qLog(Info) << "Listening on TCP port" << url.port(); + + // Hackity hack + GstElement* src = engine_->CreateElement("tcpserversrc"); + g_object_set(G_OBJECT(src), "host", url.host().toUtf8().constData(), NULL); + g_object_set(G_OBJECT(src), "port", url.port(), NULL); + + GstElement* decodebin = engine_->CreateElement("decodebin2"); + g_signal_connect(G_OBJECT(decodebin), "drained", G_CALLBACK(SourceDrainedCallback), this); + g_signal_connect(G_OBJECT(decodebin), "pad-added", G_CALLBACK(NewPadCallback), this); + + return ReplaceDecodeBin(decodebin, src); + } else { + GstElement* new_bin = engine_->CreateElement("uridecodebin"); + g_object_set(G_OBJECT(new_bin), "uri", url.toEncoded().constData(), NULL); + g_object_set(G_OBJECT(new_bin), "buffer-duration", buffer_duration_nanosec_, NULL); + g_object_set(G_OBJECT(new_bin), "download", true, NULL); + g_object_set(G_OBJECT(new_bin), "use-buffering", true, NULL); + g_signal_connect(G_OBJECT(new_bin), "drained", G_CALLBACK(SourceDrainedCallback), this); + g_signal_connect(G_OBJECT(new_bin), "pad-added", G_CALLBACK(NewPadCallback), this); + return ReplaceDecodeBin(new_bin); + } } GstElement* GstEnginePipeline::CreateDecodeBinFromString(const char* pipeline) { @@ -537,6 +562,7 @@ void GstEnginePipeline::SourceDrainedCallback(GstURIDecodeBin* bin, gpointer sel void GstEnginePipeline::TransitionToNext() { GstElement* old_decode_bin = uridecodebin_; + GstElement* old_tcpsrc = tcpsrc_; ignore_tags_ = true; @@ -558,6 +584,10 @@ void GstEnginePipeline::TransitionToNext() { // fix an occasional race condition deadlock. sElementDeleter->DeleteElementLater(old_decode_bin); + if (old_tcpsrc) { + sElementDeleter->DeleteElementLater(old_tcpsrc); + } + ignore_tags_ = false; } diff --git a/src/engines/gstenginepipeline.h b/src/engines/gstenginepipeline.h index e6eaf8666..ff7a01bc2 100644 --- a/src/engines/gstenginepipeline.h +++ b/src/engines/gstenginepipeline.h @@ -127,7 +127,7 @@ class GstEnginePipeline : public QObject { void UpdateVolume(); void UpdateEqualizer(); - bool ReplaceDecodeBin(GstElement* new_bin); + bool ReplaceDecodeBin(GstElement* new_bin, GstElement* new_tcpsrc = NULL); bool ReplaceDecodeBin(const QUrl& url); void TransitionToNext(); @@ -220,7 +220,8 @@ class GstEnginePipeline : public QObject { GstElement* pipeline_; // Bins - // uridecodebin ! audiobin + // [tcpsrc !] uridecodebin ! audiobin + GstElement* tcpsrc_; GstElement* uridecodebin_; GstElement* audiobin_; diff --git a/src/radio/spotifyserver.cpp b/src/radio/spotifyserver.cpp index 938c0aa59..4d768c05c 100644 --- a/src/radio/spotifyserver.cpp +++ b/src/radio/spotifyserver.cpp @@ -145,3 +145,12 @@ void SpotifyServer::LoadStarred() { void SpotifyServer::LoadUserPlaylist(int index) { LoadPlaylist(protobuf::LoadPlaylistRequest_Type_UserPlaylist, index); } + +void SpotifyServer::StartPlayback(const QString& uri, quint16 port) { + protobuf::SpotifyMessage message; + protobuf::PlaybackRequest* req = message.mutable_playback_request(); + + req->set_track_uri(DataCommaSizeFromQString(uri)); + req->set_media_port(port); + SendMessage(message); +} diff --git a/src/radio/spotifyserver.h b/src/radio/spotifyserver.h index f12290f5a..fe389f30d 100644 --- a/src/radio/spotifyserver.h +++ b/src/radio/spotifyserver.h @@ -39,6 +39,8 @@ public: void LoadInbox(); void LoadUserPlaylist(int index); + void StartPlayback(const QString& uri, quint16 port); + int server_port() const; signals: diff --git a/src/radio/spotifyservice.cpp b/src/radio/spotifyservice.cpp index 5852e6d98..db9b0de89 100644 --- a/src/radio/spotifyservice.cpp +++ b/src/radio/spotifyservice.cpp @@ -8,6 +8,7 @@ #include #include #include +#include const char* SpotifyService::kServiceName = "Spotify"; const char* SpotifyService::kSettingsGroup = "Spotify"; @@ -194,6 +195,8 @@ void SpotifyService::FillPlaylist(QStandardItem* item, const protobuf::LoadPlayl QStandardItem* child = new QStandardItem(song.PrettyTitleWithArtist()); child->setData(Type_Track, RadioModel::Role_Type); child->setData(QVariant::fromValue(song), Role_Metadata); + child->setData(RadioModel::PlayBehaviour_SingleItem, RadioModel::Role_PlayBehaviour); + child->setData(QUrl(song.filename()), RadioModel::Role_Url); item->appendRow(child); } @@ -220,3 +223,35 @@ void SpotifyService::SongFromProtobuf(const protobuf::Track& track, Song* song) song->set_filetype(Song::Type_Stream); song->set_valid(true); } + +PlaylistItem::Options SpotifyService::playlistitem_options() const { + return PlaylistItem::SpecialPlayBehaviour | + PlaylistItem::PauseDisabled; +} + +PlaylistItem::SpecialLoadResult SpotifyService::StartLoading(const QUrl& url) { + // Pick an unused local port. There's a possible race condition here - + // something else might grab the port before gstreamer does. + quint16 port = 0; + + { + QTcpServer server; + server.listen(QHostAddress::LocalHost); + port = server.serverPort(); + } + + if (port == 0) { + qLog(Warning) << "Couldn't pick an unused port"; + return PlaylistItem::SpecialLoadResult(); + } + + // Tell Spotify to start sending to this port + EnsureServerCreated(); + server_->StartPlayback(url.toString(), port); + + // Tell gstreamer to listen on this port + return PlaylistItem::SpecialLoadResult( + PlaylistItem::SpecialLoadResult::TrackAvailable, + url, + QUrl("tcp://localhost:" + QString::number(port))); +} diff --git a/src/radio/spotifyservice.h b/src/radio/spotifyservice.h index c6423234d..69be9224b 100644 --- a/src/radio/spotifyservice.h +++ b/src/radio/spotifyservice.h @@ -34,6 +34,9 @@ public: void Login(const QString& username, const QString& password); + PlaylistItem::SpecialLoadResult StartLoading(const QUrl& url); + PlaylistItem::Options playlistitem_options() const; + static const char* kServiceName; static const char* kSettingsGroup;