Add a basic HTTP server that streams any clementine url except spotify.

This commit is contained in:
John Maguire 2013-09-13 17:08:03 +02:00
parent 4e8dba16d4
commit 343d738da2
6 changed files with 191 additions and 2 deletions

View File

@ -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

View File

@ -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;

View File

@ -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();

View File

@ -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

View File

@ -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);
}

View File

@ -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