Allow songs from the spotify service to be added to the playlist, and bodge a tcpserversrc into GstEnginePipeline
This commit is contained in:
parent
232e1c2335
commit
6f1f4484f4
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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_;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -39,6 +39,8 @@ public:
|
|||
void LoadInbox();
|
||||
void LoadUserPlaylist(int index);
|
||||
|
||||
void StartPlayback(const QString& uri, quint16 port);
|
||||
|
||||
int server_port() const;
|
||||
|
||||
signals:
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <QCoreApplication>
|
||||
#include <QProcess>
|
||||
#include <QSettings>
|
||||
#include <QTcpServer>
|
||||
|
||||
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)));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in New Issue