diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 00bc85730..5161dd4ed 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -361,6 +361,7 @@ set(SOURCES ui/settingsdialog.cpp ui/settingspage.cpp ui/standarditemiconloader.cpp + ui/streamingsettingspage.cpp ui/systemtrayicon.cpp ui/trackselectiondialog.cpp ui/windows7thumbbar.cpp @@ -642,6 +643,7 @@ set(HEADERS ui/settingsdialog.h ui/settingspage.h ui/standarditemiconloader.h + ui/streamingsettingspage.h ui/systemtrayicon.h ui/trackselectiondialog.h ui/windows7thumbbar.h @@ -763,6 +765,7 @@ set(UI ui/organiseerrordialog.ui ui/playbacksettingspage.ui ui/settingsdialog.ui + ui/streamingsettingspage.ui ui/trackselectiondialog.ui widgets/equalizerslider.ui diff --git a/src/core/application.cpp b/src/core/application.cpp index cb06b4dd8..d811296c0 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -38,6 +38,7 @@ #include "podcasts/podcastbackend.h" #include "podcasts/podcastdownloader.h" #include "podcasts/podcastupdater.h" +#include "streaming/streamserver.h" #ifdef HAVE_LIBLASTFM #include "internet/lastfmservice.h" @@ -74,7 +75,8 @@ Application::Application(QObject* parent) moodbar_controller_(nullptr), network_remote_(nullptr), network_remote_helper_(nullptr), - scrobbler_(nullptr) { + scrobbler_(nullptr), + stream_server_(nullptr) { tag_reader_client_ = new TagReaderClient(this); MoveToNewThread(tag_reader_client_); tag_reader_client_->Start(); @@ -114,6 +116,10 @@ Application::Application(QObject* parent) network_remote_ = new NetworkRemote(this); MoveToNewThread(network_remote_); + // StreamServer + stream_server_ = new StreamServer(player_); + stream_server_->Listen(); + // This must be before libraray_->Init(); // In the constructor the helper waits for the signal // PlaylistManagerInitialized diff --git a/src/core/application.h b/src/core/application.h index 27fdc73ea..120025ded 100644 --- a/src/core/application.h +++ b/src/core/application.h @@ -45,6 +45,7 @@ class PlaylistManager; class PodcastBackend; class PodcastUpdater; class Scrobbler; +class StreamServer; class TagReaderClient; class TaskManager; @@ -88,6 +89,7 @@ class Application : public QObject { return network_remote_helper_; } Scrobbler* scrobbler() const { return scrobbler_; } + StreamServer* stream_server() const { return stream_server_; } LibraryBackend* library_backend() const; LibraryModel* library_model() const; @@ -131,6 +133,7 @@ signals: NetworkRemote* network_remote_; NetworkRemoteHelper* network_remote_helper_; Scrobbler* scrobbler_; + StreamServer* stream_server_; QList objects_in_threads_; QList threads_; diff --git a/src/main.cpp b/src/main.cpp index 796afbac7..90f16a909 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -114,8 +114,6 @@ Q_IMPORT_PLUGIN(qsqlite) namespace { -#include "streaming/streamserver.h" - void LoadTranslation(const QString& prefix, const QString& path, const QString& language) { #if QT_VERSION < 0x040700 @@ -496,9 +494,6 @@ int main(int argc, char* argv[]) { SLOT(CommandlineOptionsReceived(QByteArray))); w.CommandlineOptionsReceived(options); - StreamServer stream_server(app.player());; - stream_server.Listen(); - int ret = a.exec(); #ifdef Q_OS_LINUX diff --git a/src/streaming/streamserver.cpp b/src/streaming/streamserver.cpp index ababa3733..8a4ef34c8 100644 --- a/src/streaming/streamserver.cpp +++ b/src/streaming/streamserver.cpp @@ -1,6 +1,7 @@ #include "streamserver.h" #include +#include #include #include @@ -13,18 +14,34 @@ #include +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(); + //GstEngine::InitialiseGstreamer(); + gst_init(nullptr, nullptr); } -void StreamServer::Listen(quint16 port) { - server_->listen(QHostAddress::LocalHost, port); +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; @@ -60,7 +77,7 @@ static GstBusSyncReply BusCallbackSync(GstBus*, GstMessage* msg, gpointer data) GST_OBJECT_NAME(msg->src), err->message); g_error_free(err); g_free(dbg_info); - socket->deleteLater(); + socket->close(); break; } default: @@ -77,7 +94,6 @@ void StreamServer::ReadyRead(QTcpSocket* socket, QByteArray buffer) { 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"); @@ -86,9 +102,9 @@ void StreamServer::ReadyRead(QTcpSocket* socket, QByteArray buffer) { 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) { + 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); @@ -118,6 +134,7 @@ void StreamServer::SendStream(const QUrl& url, QTcpSocket* socket) { decodebin, audioconvert, audioresample, vorbisenc, oggmux, fdsink, NULL); + g_object_set(vorbisenc, "bitrate", getBitrate(), NULL); g_object_set(decodebin, "uri", url.toString().toUtf8().constData(), NULL); g_object_set(fdsink, "fd", socket->socketDescriptor(), NULL); @@ -143,3 +160,9 @@ void StreamServer::AsyncLoadComplete(const UrlHandler::LoadResult& result) { QTcpSocket* socket = sockets_.take(result.original_url_); SendStream(result.media_url_, socket); } + +int StreamServer::getBitrate() { + QSettings s; + s.beginGroup(kSettingsGroup); + return s.value("bitrate", 128).toInt() * 1000; +} diff --git a/src/streaming/streamserver.h b/src/streaming/streamserver.h index 32f7c35cf..04e1de245 100644 --- a/src/streaming/streamserver.h +++ b/src/streaming/streamserver.h @@ -1,5 +1,5 @@ #ifndef STREAMSERVER_H -#define STREAMSERVER_h +#define STREAMSERVER_H #include #include @@ -15,8 +15,12 @@ class QTcpSocket; class StreamServer : public QObject { Q_OBJECT public: - StreamServer(Player* player, QObject* parent = 0); - void Listen(quint16 port = 8080); + static const char* kSettingsGroup; + static const quint16 kDefaultServerPort; + + StreamServer(Player* player, QObject* parent = nullptr); + void Listen(); + void StopListening(); private slots: void AcceptConnection(); @@ -26,6 +30,7 @@ class StreamServer : public QObject { private: QByteArray ParseRequest(const QByteArray& data); void SendStream(const QUrl& url, QTcpSocket* socket); + int getBitrate(); Player* player_; QTcpServer* server_; diff --git a/src/ui/settingsdialog.cpp b/src/ui/settingsdialog.cpp index 8a6c1e70e..1f6b9f884 100644 --- a/src/ui/settingsdialog.cpp +++ b/src/ui/settingsdialog.cpp @@ -27,6 +27,7 @@ #include "notificationssettingspage.h" #include "mainwindow.h" #include "settingsdialog.h" +#include "streamingsettingspage.h" #include "core/application.h" #include "core/backgroundstreams.h" #include "core/logging.h" @@ -137,10 +138,14 @@ SettingsDialog::SettingsDialog(Application* app, BackgroundStreams* streams, AddPage(Page_Library, new LibrarySettingsPage(this), general); AddPage(Page_Proxy, new NetworkProxySettingsPage(this), general); AddPage(Page_Transcoding, new TranscoderSettingsPage(this), general); - AddPage(Page_NetworkRemote, new NetworkRemoteSettingsPage(this), general); + + QTreeWidgetItem* network = AddCategory(tr("Interfaces")); + + AddPage(Page_NetworkRemote, new NetworkRemoteSettingsPage(this), network); + AddPage(Page_Streaming, new StreamingSettingsPage(this), network); #ifdef HAVE_WIIMOTEDEV - AddPage(Page_Wiimotedev, new WiimoteSettingsPage(this), general); + AddPage(Page_Wiimotedev, new WiimoteSettingsPage(this), network); #endif // User interface diff --git a/src/ui/settingsdialog.h b/src/ui/settingsdialog.h index 2880a5423..39a21ed63 100644 --- a/src/ui/settingsdialog.h +++ b/src/ui/settingsdialog.h @@ -65,6 +65,7 @@ class SettingsDialog : public QDialog { Page_GlobalSearch, Page_Appearance, Page_NetworkRemote, + Page_Streaming, Page_Notifications, Page_Library, Page_Lastfm, diff --git a/src/ui/streamingsettingspage.cpp b/src/ui/streamingsettingspage.cpp new file mode 100644 index 000000000..d074745ca --- /dev/null +++ b/src/ui/streamingsettingspage.cpp @@ -0,0 +1,69 @@ +/* This file is part of Clementine. + Copyright 2010, David Sansome + + Clementine is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Clementine is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Clementine. If not, see . +*/ + +#include "core/application.h" +#include "iconloader.h" +#include "streamingsettingspage.h" +#include "ui_streamingsettingspage.h" +#include "streaming/streamserver.h" +#include "settingsdialog.h" + +#include +#include + +StreamingSettingsPage::StreamingSettingsPage(SettingsDialog* dialog) + : SettingsPage(dialog), ui_(new Ui_StreamingSettingsPage) { + ui_->setupUi(this); + setWindowIcon(IconLoader::Load("ipodtouchicon")); +} + +StreamingSettingsPage::~StreamingSettingsPage() { delete ui_; } + +void StreamingSettingsPage::Load() { + QSettings s; + + s.beginGroup(StreamServer::kSettingsGroup); + + ui_->use_streaming->setChecked(s.value("use_streaming").toBool()); + ui_->stream_port->setValue( + s.value("port", StreamServer::kDefaultServerPort).toInt()); + + ui_->bitrate_box->setValue(s.value("bitrate", 128).toInt()); + + s.endGroup(); +} + +void StreamingSettingsPage::Save() { + QSettings s; + + s.beginGroup(StreamServer::kSettingsGroup); + bool wasStreaming = s.value("use_streaming", false).toBool(); + + s.setValue("use_streaming", ui_->use_streaming->isChecked()); + s.setValue("port", ui_->stream_port->value()); + s.setValue("bitrate", ui_->bitrate_box->value()); + + s.endGroup(); + + if (wasStreaming != ui_->use_streaming->isChecked()) { + if (ui_->use_streaming->isChecked()) + dialog()->app()->stream_server()->Listen(); + else + dialog()->app()->stream_server()->StopListening(); + } + +} diff --git a/src/ui/streamingsettingspage.h b/src/ui/streamingsettingspage.h new file mode 100644 index 000000000..6cb0dbc2a --- /dev/null +++ b/src/ui/streamingsettingspage.h @@ -0,0 +1,39 @@ +/* This file is part of Clementine. + Copyright 2010, David Sansome + + Clementine is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Clementine is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Clementine. If not, see . +*/ + +#ifndef STREAMINGSETTINGSPAGE_H +#define STREAMINGSETTINGSPAGE_H + +#include "settingspage.h" + +class Ui_StreamingSettingsPage; + +class StreamingSettingsPage : public SettingsPage { + Q_OBJECT + + public: + StreamingSettingsPage(SettingsDialog* dialog); + ~StreamingSettingsPage(); + + void Load(); + void Save(); + + private: + Ui_StreamingSettingsPage* ui_; +}; + +#endif // STREAMINGSETTINGSPAGE_H diff --git a/src/ui/streamingsettingspage.ui b/src/ui/streamingsettingspage.ui new file mode 100644 index 000000000..e0989f598 --- /dev/null +++ b/src/ui/streamingsettingspage.ui @@ -0,0 +1,190 @@ + + + StreamingSettingsPage + + + + 0 + 0 + 385 + 528 + + + + Streaming + + + + + + Active steaming + + + + + + + + 0 + 0 + + + + This enables HTTP streaming. A client can stream any Clementine URL except for Spotify. + + + Qt::RichText + + + true + + + + + + + false + + + Settings + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + + 171 + 0 + + + + Qt::LeftToRight + + + Port + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + 65535 + + + 8080 + + + + + + + Bitrate + + + + + + + + + 250 + + + 128 + + + Qt::Horizontal + + + + + + + kBps + + + 250 + + + 128 + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + use_streaming + toggled(bool) + use_stream_container_2 + setEnabled(bool) + + + 59 + 22 + + + 57 + 43 + + + + + bitrate_box + valueChanged(int) + bitrate_slider + setValue(int) + + + 325 + 147 + + + 241 + 146 + + + + + bitrate_slider + valueChanged(int) + bitrate_box + setValue(int) + + + 241 + 146 + + + 325 + 147 + + + + +