169 lines
5.1 KiB
C++
169 lines
5.1 KiB
C++
#include "streamserver.h"
|
|
|
|
#include <QByteArray>
|
|
#include <QSettings>
|
|
#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>
|
|
|
|
const char* StreamServer::kSettingsGroup = "Streaming";
|
|
const quint16 StreamServer::kDefaultServerPort = 8080;
|
|
|
|
StreamServer::StreamServer(Player* player, QObject* parent)
|
|
: QObject(parent),
|
|
player_(player),
|
|
server_(new QTcpServer(this)) {
|
|
//GstEngine::InitialiseGstreamer();
|
|
gst_init(nullptr, nullptr);
|
|
}
|
|
|
|
void StreamServer::Listen() {
|
|
QSettings s;
|
|
s.beginGroup(kSettingsGroup);
|
|
|
|
// Streaming activated?
|
|
if (!s.value("use_streaming", false).toBool()) {
|
|
return;
|
|
}
|
|
|
|
server_->listen(QHostAddress::LocalHost, s.value("port", kDefaultServerPort).toInt());
|
|
connect(server_, SIGNAL(newConnection()), SLOT(AcceptConnection()));
|
|
}
|
|
|
|
void StreamServer::StopListening() {
|
|
server_->close();
|
|
}
|
|
|
|
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->close();
|
|
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);
|
|
if (handler) {
|
|
connect(handler, SIGNAL(AsyncLoadComplete(const UrlHandler::LoadResult&)),
|
|
SLOT(AsyncLoadComplete(const UrlHandler::LoadResult&)), Qt::UniqueConnection);
|
|
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(vorbisenc, "quality", getQuality(), 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);
|
|
}
|
|
|
|
double StreamServer::getQuality() {
|
|
QSettings s;
|
|
s.beginGroup(kSettingsGroup);
|
|
return s.value("quality", 0.3).toDouble();
|
|
}
|