Add a basic HTTP server that streams any clementine url except spotify.
This commit is contained in:
parent
4e8dba16d4
commit
343d738da2
|
@ -311,6 +311,8 @@ set(SOURCES
|
|||
songinfo/ultimatelyricsprovider.cpp
|
||||
songinfo/ultimatelyricsreader.cpp
|
||||
|
||||
streaming/streamserver.cpp
|
||||
|
||||
transcoder/transcodedialog.cpp
|
||||
transcoder/transcoder.cpp
|
||||
transcoder/transcoderoptionsaac.cpp
|
||||
|
@ -595,6 +597,8 @@ set(HEADERS
|
|||
songinfo/ultimatelyricsprovider.h
|
||||
songinfo/ultimatelyricsreader.h
|
||||
|
||||
streaming/streamserver.h
|
||||
|
||||
transcoder/transcodedialog.h
|
||||
transcoder/transcoder.h
|
||||
transcoder/transcoderoptionsdialog.h
|
||||
|
|
|
@ -580,7 +580,7 @@ void Player::UnregisterUrlHandler(UrlHandler* handler) {
|
|||
this, SLOT(HandleLoadResult(UrlHandler::LoadResult)));
|
||||
}
|
||||
|
||||
const UrlHandler* Player::HandlerForUrl(const QUrl& url) const {
|
||||
UrlHandler* Player::HandlerForUrl(const QUrl& url) const {
|
||||
QMap<QString, UrlHandler*>::const_iterator it = url_handlers_.constFind(url.scheme());
|
||||
if (it == url_handlers_.constEnd()) {
|
||||
return NULL;
|
||||
|
|
|
@ -120,7 +120,7 @@ public:
|
|||
void RegisterUrlHandler(UrlHandler* handler);
|
||||
void UnregisterUrlHandler(UrlHandler* handler);
|
||||
|
||||
const UrlHandler* HandlerForUrl(const QUrl& url) const;
|
||||
UrlHandler* HandlerForUrl(const QUrl& url) const;
|
||||
|
||||
public slots:
|
||||
void ReloadSettings();
|
||||
|
|
|
@ -109,6 +109,8 @@ using boost::scoped_ptr;
|
|||
Q_IMPORT_PLUGIN(qsqlite)
|
||||
#endif
|
||||
|
||||
#include "streaming/streamserver.h"
|
||||
|
||||
void LoadTranslation(const QString& prefix, const QString& path,
|
||||
const QString& language) {
|
||||
#if QT_VERSION < 0x040700
|
||||
|
@ -451,6 +453,9 @@ int main(int argc, char *argv[]) {
|
|||
QObject::connect(&a, SIGNAL(messageReceived(QByteArray)), &w, SLOT(CommandlineOptionsReceived(QByteArray)));
|
||||
w.CommandlineOptionsReceived(options);
|
||||
|
||||
StreamServer stream_server(app.player());;
|
||||
stream_server.Listen();
|
||||
|
||||
int ret = a.exec();
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
#include "streamserver.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QTcpSocket>
|
||||
#include <QTcpServer>
|
||||
|
||||
#include "core/closure.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/player.h"
|
||||
#include "core/urlhandler.h"
|
||||
#include "core/signalchecker.h"
|
||||
#include "engines/gstengine.h"
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
StreamServer::StreamServer(Player* player, QObject* parent)
|
||||
: QObject(parent),
|
||||
player_(player),
|
||||
server_(new QTcpServer(this)) {
|
||||
GstEngine::InitialiseGstreamer();
|
||||
}
|
||||
|
||||
void StreamServer::Listen(quint16 port) {
|
||||
server_->listen(QHostAddress::LocalHost, port);
|
||||
connect(server_, SIGNAL(newConnection()), SLOT(AcceptConnection()));
|
||||
}
|
||||
|
||||
void StreamServer::AcceptConnection() {
|
||||
QTcpSocket* socket = server_->nextPendingConnection();
|
||||
QByteArray buffer;
|
||||
NewClosure(socket, SIGNAL(readyRead()),
|
||||
this, SLOT(ReadyRead(QTcpSocket*, QByteArray)), socket, buffer);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
static void NewPadCallback(GstElement*, GstPad* pad, gpointer data) {
|
||||
qLog(Debug) << Q_FUNC_INFO;
|
||||
GstElement* converter = reinterpret_cast<GstElement*>(data);
|
||||
GstPad* const audiopad = gst_element_get_static_pad(converter, "sink");
|
||||
|
||||
if (GST_PAD_IS_LINKED(audiopad)) {
|
||||
qLog(Warning) << "Pad already linked";
|
||||
gst_pad_unlink(audiopad, GST_PAD_PEER(audiopad));
|
||||
}
|
||||
|
||||
gst_pad_link(pad, audiopad);
|
||||
gst_object_unref(audiopad);
|
||||
}
|
||||
|
||||
static GstBusSyncReply BusCallbackSync(GstBus*, GstMessage* msg, gpointer data) {
|
||||
QTcpSocket* socket = reinterpret_cast<QTcpSocket*>(data);
|
||||
|
||||
switch (GST_MESSAGE_TYPE(msg)) {
|
||||
case GST_MESSAGE_ERROR: {
|
||||
GError* err = NULL;
|
||||
gchar* dbg_info = NULL;
|
||||
gst_message_parse_error(msg, &err, &dbg_info);
|
||||
g_printerr("Error from element %s: %s\n",
|
||||
GST_OBJECT_NAME(msg->src), err->message);
|
||||
g_error_free(err);
|
||||
g_free(dbg_info);
|
||||
socket->deleteLater();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return GST_BUS_PASS;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void StreamServer::ReadyRead(QTcpSocket* socket, QByteArray buffer) {
|
||||
buffer.append(socket->readAll());
|
||||
if (socket->atEnd() || buffer.endsWith("\r\n\r\n")) {
|
||||
QByteArray response = ParseRequest(buffer);
|
||||
qLog(Debug) << response;
|
||||
|
||||
socket->write("HTTP/1.0 200 OK\r\n");
|
||||
socket->write("Content-type: application/ogg\r\n");
|
||||
socket->write("Connection: close\r\n");
|
||||
socket->write("\r\n");
|
||||
socket->flush();
|
||||
|
||||
QUrl url(QString::fromUtf8(response), QUrl::StrictMode);
|
||||
UrlHandler* handler = player_->HandlerForUrl(url);
|
||||
connect(handler, SIGNAL(AsyncLoadComplete(const UrlHandler::LoadResult&)),
|
||||
SLOT(AsyncLoadComplete(const UrlHandler::LoadResult&)), Qt::UniqueConnection);
|
||||
if (handler) {
|
||||
UrlHandler::LoadResult result = handler->StartLoading(url);
|
||||
if (result.type_ == UrlHandler::LoadResult::TrackAvailable) {
|
||||
SendStream(result.media_url_, socket);
|
||||
} else if (result.type_ == UrlHandler::LoadResult::WillLoadAsynchronously) {
|
||||
sockets_[url] = socket;
|
||||
}
|
||||
} else {
|
||||
SendStream(url, socket);
|
||||
}
|
||||
|
||||
} else {
|
||||
NewClosure(socket, SIGNAL(readyRead()),
|
||||
this, SLOT(ReadyRead(QTcpSocket*, QByteArray)), socket, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void StreamServer::SendStream(const QUrl& url, QTcpSocket* socket) {
|
||||
GstElement* pipeline = gst_pipeline_new("stream_pipeline");
|
||||
|
||||
GstElement* decodebin = gst_element_factory_make("uridecodebin", NULL);
|
||||
GstElement* audioconvert = gst_element_factory_make("audioconvert", NULL);
|
||||
GstElement* audioresample = gst_element_factory_make("audioresample", NULL);
|
||||
GstElement* vorbisenc = gst_element_factory_make("vorbisenc", NULL);
|
||||
GstElement* oggmux = gst_element_factory_make("oggmux", NULL);
|
||||
GstElement* fdsink = gst_element_factory_make("fdsink", NULL);
|
||||
gst_bin_add_many(GST_BIN(pipeline),
|
||||
decodebin, audioconvert, audioresample, vorbisenc, oggmux, fdsink,
|
||||
NULL);
|
||||
|
||||
g_object_set(decodebin, "uri", url.toString().toUtf8().constData(), NULL);
|
||||
g_object_set(fdsink, "fd", socket->socketDescriptor(), NULL);
|
||||
|
||||
gst_element_link_many(
|
||||
audioconvert, audioresample, vorbisenc, oggmux, fdsink, NULL);
|
||||
|
||||
CHECKED_GCONNECT(decodebin, "pad-added", &NewPadCallback, audioconvert);
|
||||
|
||||
gst_bus_set_sync_handler(
|
||||
gst_pipeline_get_bus(GST_PIPELINE(pipeline)), &BusCallbackSync, socket);
|
||||
|
||||
gst_element_set_state(pipeline, GST_STATE_PLAYING);
|
||||
}
|
||||
|
||||
QByteArray StreamServer::ParseRequest(const QByteArray& data) {
|
||||
QList<QByteArray> lines = data.split('\r');
|
||||
QByteArray path = lines[0].split(' ')[1];
|
||||
QByteArray id = path.mid(1);
|
||||
return id;
|
||||
}
|
||||
|
||||
void StreamServer::AsyncLoadComplete(const UrlHandler::LoadResult& result) {
|
||||
QTcpSocket* socket = sockets_.take(result.original_url_);
|
||||
SendStream(result.media_url_, socket);
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
#ifndef STREAMSERVER_H
|
||||
#define STREAMSERVER_h
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QObject>
|
||||
#include <QMap>
|
||||
#include <QUrl>
|
||||
|
||||
#include "core/urlhandler.h"
|
||||
|
||||
class Player;
|
||||
class QTcpServer;
|
||||
class QTcpSocket;
|
||||
|
||||
class StreamServer : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
StreamServer(Player* player, QObject* parent = 0);
|
||||
void Listen(quint16 port = 8080);
|
||||
|
||||
private slots:
|
||||
void AcceptConnection();
|
||||
void ReadyRead(QTcpSocket* socket, QByteArray buffer);
|
||||
void AsyncLoadComplete(const UrlHandler::LoadResult& result);
|
||||
|
||||
private:
|
||||
QByteArray ParseRequest(const QByteArray& data);
|
||||
void SendStream(const QUrl& url, QTcpSocket* socket);
|
||||
|
||||
Player* player_;
|
||||
QTcpServer* server_;
|
||||
QMap<QUrl, QTcpSocket*> sockets_;
|
||||
};
|
||||
|
||||
#endif // STREAMSERVER_H
|
Loading…
Reference in New Issue