mirror of
https://github.com/badaix/snapcast.git
synced 2025-05-22 13:36:18 +02:00
configuration is stored in file snapserver.conf
This commit is contained in:
parent
fa508eafba
commit
72bd17cb20
11 changed files with 266 additions and 89 deletions
|
@ -4,7 +4,7 @@
|
||||||
<title>Snapcast Interface</title>
|
<title>Snapcast Interface</title>
|
||||||
Taken from <a href="https://github.com/derglaus/snapcast-websockets-ui">derglaus/snapcast-websockets-ui</a> for testing purposes
|
Taken from <a href="https://github.com/derglaus/snapcast-websockets-ui">derglaus/snapcast-websockets-ui</a> for testing purposes
|
||||||
<script>
|
<script>
|
||||||
var connection = new WebSocket('ws://127.0.0.1:8080/jsonrpc');
|
var connection = new WebSocket('ws://127.0.0.1:1780/jsonrpc');
|
||||||
var server;
|
var server;
|
||||||
|
|
||||||
connection.onmessage = function (e) {
|
connection.onmessage = function (e) {
|
||||||
|
|
|
@ -32,8 +32,9 @@ using namespace std;
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
|
||||||
ControlServer::ControlServer(boost::asio::io_context* io_context, size_t port, ControlMessageReceiver* controlMessageReceiver)
|
ControlServer::ControlServer(boost::asio::io_context* io_context, const ServerSettings::TcpSettings& tcp_settings,
|
||||||
: io_context_(io_context), port_(port), controlMessageReceiver_(controlMessageReceiver)
|
const ServerSettings::HttpSettings& http_settings, ControlMessageReceiver* controlMessageReceiver)
|
||||||
|
: io_context_(io_context), controlMessageReceiver_(controlMessageReceiver), tcp_settings_(tcp_settings), http_settings_(http_settings)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +96,7 @@ void ControlServer::startAccept()
|
||||||
|
|
||||||
auto accept_handler_http = [this](error_code ec, tcp::socket socket) {
|
auto accept_handler_http = [this](error_code ec, tcp::socket socket) {
|
||||||
if (!ec)
|
if (!ec)
|
||||||
handleAccept<ControlSessionHttp>(std::move(socket));
|
handleAccept<ControlSessionHttp>(std::move(socket), http_settings_);
|
||||||
else
|
else
|
||||||
LOG(ERROR) << "Error while accepting socket connection: " << ec.message() << "\n";
|
LOG(ERROR) << "Error while accepting socket connection: " << ec.message() << "\n";
|
||||||
};
|
};
|
||||||
|
@ -114,8 +115,8 @@ void ControlServer::startAccept()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
template <typename SessionType>
|
template <typename SessionType, typename... Args>
|
||||||
void ControlServer::handleAccept(tcp::socket socket)
|
void ControlServer::handleAccept(tcp::socket socket, Args&&... args)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -126,7 +127,7 @@ void ControlServer::handleAccept(tcp::socket socket)
|
||||||
setsockopt(socket.native_handle(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
|
setsockopt(socket.native_handle(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
|
||||||
// socket->set_option(boost::asio::ip::tcp::no_delay(false));
|
// socket->set_option(boost::asio::ip::tcp::no_delay(false));
|
||||||
SLOG(NOTICE) << "ControlServer::NewConnection: " << socket.remote_endpoint().address().to_string() << endl;
|
SLOG(NOTICE) << "ControlServer::NewConnection: " << socket.remote_endpoint().address().to_string() << endl;
|
||||||
shared_ptr<SessionType> session = make_shared<SessionType>(this, std::move(socket));
|
shared_ptr<SessionType> session = make_shared<SessionType>(this, std::move(socket), std::forward<Args>(args)...);
|
||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> mlock(session_mutex_);
|
std::lock_guard<std::recursive_mutex> mlock(session_mutex_);
|
||||||
session->start();
|
session->start();
|
||||||
|
@ -179,10 +180,10 @@ std::pair<acceptor_ptr, acceptor_ptr> ControlServer::createAcceptors(size_t port
|
||||||
|
|
||||||
void ControlServer::start()
|
void ControlServer::start()
|
||||||
{
|
{
|
||||||
// TODO: should be possible to be disabled
|
if (tcp_settings_.enabled)
|
||||||
acceptor_tcp_ = createAcceptors(port_);
|
acceptor_tcp_ = createAcceptors(tcp_settings_.port);
|
||||||
// TODO: make port configurable, should be possible to be disabled
|
if (http_settings_.enabled)
|
||||||
acceptor_http_ = createAcceptors(8080);
|
acceptor_http_ = createAcceptors(http_settings_.port);
|
||||||
startAccept();
|
startAccept();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
#include "message/codecHeader.h"
|
#include "message/codecHeader.h"
|
||||||
#include "message/message.h"
|
#include "message/message.h"
|
||||||
#include "message/serverSettings.h"
|
#include "message/serverSettings.h"
|
||||||
|
#include "server_settings.hpp"
|
||||||
|
|
||||||
using boost::asio::ip::tcp;
|
using boost::asio::ip::tcp;
|
||||||
using acceptor_ptr = std::unique_ptr<tcp::acceptor>;
|
using acceptor_ptr = std::unique_ptr<tcp::acceptor>;
|
||||||
|
@ -44,7 +45,8 @@ using acceptor_ptr = std::unique_ptr<tcp::acceptor>;
|
||||||
class ControlServer : public ControlMessageReceiver
|
class ControlServer : public ControlMessageReceiver
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ControlServer(boost::asio::io_context* io_context, size_t port, ControlMessageReceiver* controlMessageReceiver = nullptr);
|
ControlServer(boost::asio::io_context* io_context, const ServerSettings::TcpSettings& tcp_settings, const ServerSettings::HttpSettings& http_settings,
|
||||||
|
ControlMessageReceiver* controlMessageReceiver = nullptr);
|
||||||
virtual ~ControlServer();
|
virtual ~ControlServer();
|
||||||
|
|
||||||
void start();
|
void start();
|
||||||
|
@ -60,9 +62,8 @@ private:
|
||||||
void startAccept();
|
void startAccept();
|
||||||
std::pair<acceptor_ptr, acceptor_ptr> createAcceptors(size_t port);
|
std::pair<acceptor_ptr, acceptor_ptr> createAcceptors(size_t port);
|
||||||
|
|
||||||
template <typename SessionType>
|
template <typename SessionType, typename... Args>
|
||||||
void handleAccept(tcp::socket socket);
|
void handleAccept(tcp::socket socket, Args&&... args);
|
||||||
// void handleAcceptWs(tcp::socket socket);
|
|
||||||
void cleanup();
|
void cleanup();
|
||||||
|
|
||||||
mutable std::recursive_mutex session_mutex_;
|
mutable std::recursive_mutex session_mutex_;
|
||||||
|
@ -72,7 +73,8 @@ private:
|
||||||
std::pair<acceptor_ptr, acceptor_ptr> acceptor_http_;
|
std::pair<acceptor_ptr, acceptor_ptr> acceptor_http_;
|
||||||
|
|
||||||
boost::asio::io_context* io_context_;
|
boost::asio::io_context* io_context_;
|
||||||
size_t port_;
|
ServerSettings::TcpSettings tcp_settings_;
|
||||||
|
ServerSettings::HttpSettings http_settings_;
|
||||||
ControlMessageReceiver* controlMessageReceiver_;
|
ControlMessageReceiver* controlMessageReceiver_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
|
|
||||||
#include "common/queue.h"
|
#include "common/queue.h"
|
||||||
#include "message/message.h"
|
#include "message/message.h"
|
||||||
|
#include "server_settings.hpp"
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <boost/asio.hpp>
|
#include <boost/asio.hpp>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
|
@ -30,7 +31,6 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
|
|
||||||
using boost::asio::ip::tcp;
|
using boost::asio::ip::tcp;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -98,7 +98,8 @@ std::string path_cat(boost::beast::string_view base, boost::beast::string_view p
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
ControlSessionHttp::ControlSessionHttp(ControlMessageReceiver* receiver, tcp::socket&& socket) : ControlSession(receiver), socket_(std::move(socket))
|
ControlSessionHttp::ControlSessionHttp(ControlMessageReceiver* receiver, tcp::socket&& socket, const ServerSettings::HttpSettings& settings)
|
||||||
|
: ControlSession(receiver), socket_(std::move(socket)), settings_(settings)
|
||||||
{
|
{
|
||||||
LOG(DEBUG) << "ControlSessionHttp\n";
|
LOG(DEBUG) << "ControlSessionHttp\n";
|
||||||
}
|
}
|
||||||
|
@ -183,10 +184,11 @@ void ControlSessionHttp::handle_request(http::request<Body, http::basic_fields<A
|
||||||
if (req.target().empty() || req.target()[0] != '/' || req.target().find("..") != beast::string_view::npos)
|
if (req.target().empty() || req.target()[0] != '/' || req.target().find("..") != beast::string_view::npos)
|
||||||
return send(bad_request("Illegal request-target"));
|
return send(bad_request("Illegal request-target"));
|
||||||
|
|
||||||
// TODO: configurable, enable/disable
|
if (settings_.doc_root.empty())
|
||||||
std::string doc_root = "../control";
|
return send(not_found(req.target()));
|
||||||
|
|
||||||
// Build the path to the requested file
|
// Build the path to the requested file
|
||||||
std::string path = path_cat(doc_root, req.target());
|
std::string path = path_cat(settings_.doc_root, req.target());
|
||||||
if (req.target().back() == '/')
|
if (req.target().back() == '/')
|
||||||
path.append("index.html");
|
path.append("index.html");
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ class ControlSessionHttp : public ControlSession, public std::enable_shared_from
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/// ctor. Received message from the client are passed to MessageReceiver
|
/// ctor. Received message from the client are passed to MessageReceiver
|
||||||
ControlSessionHttp(ControlMessageReceiver* receiver, tcp::socket&& socket);
|
ControlSessionHttp(ControlMessageReceiver* receiver, tcp::socket&& socket, const ServerSettings::HttpSettings& settings);
|
||||||
~ControlSessionHttp() override;
|
~ControlSessionHttp() override;
|
||||||
void start() override;
|
void start() override;
|
||||||
void stop() override;
|
void stop() override;
|
||||||
|
@ -71,6 +71,7 @@ protected:
|
||||||
protected:
|
protected:
|
||||||
tcp::socket socket_;
|
tcp::socket socket_;
|
||||||
beast::flat_buffer buffer_;
|
beast::flat_buffer buffer_;
|
||||||
|
ServerSettings::HttpSettings settings_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
63
server/server_settings.hpp
Normal file
63
server/server_settings.hpp
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
/***
|
||||||
|
This file is part of snapcast
|
||||||
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
|
This program 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.
|
||||||
|
|
||||||
|
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
***/
|
||||||
|
|
||||||
|
#ifndef SERVER_SETTINGS_HPP
|
||||||
|
#define SERVER_SETTINGS_HPP
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
struct ServerSettings
|
||||||
|
{
|
||||||
|
struct HttpSettings
|
||||||
|
{
|
||||||
|
bool enabled{true};
|
||||||
|
size_t port{1780};
|
||||||
|
std::string doc_root{""};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TcpSettings
|
||||||
|
{
|
||||||
|
bool enabled{true};
|
||||||
|
size_t port{1705};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StreamSettings
|
||||||
|
{
|
||||||
|
size_t port{1704};
|
||||||
|
std::vector<std::string> pcmStreams;
|
||||||
|
std::string codec{"flac"};
|
||||||
|
int32_t bufferMs{1000};
|
||||||
|
std::string sampleFormat{"48000:16:2"};
|
||||||
|
size_t streamReadMs{20};
|
||||||
|
bool sendAudioToMutedClients{false};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LoggingSettings
|
||||||
|
{
|
||||||
|
bool debug{false};
|
||||||
|
std::string debug_logfile{""};
|
||||||
|
};
|
||||||
|
|
||||||
|
HttpSettings http;
|
||||||
|
TcpSettings tcp;
|
||||||
|
StreamSettings stream;
|
||||||
|
LoggingSettings logging;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -20,7 +20,7 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <sys/resource.h>
|
#include <sys/resource.h>
|
||||||
|
|
||||||
#include "popl.hpp"
|
#include "../externals/popl/include/popl.hpp"
|
||||||
#ifdef HAS_DAEMON
|
#ifdef HAS_DAEMON
|
||||||
#include "common/daemon.h"
|
#include "common/daemon.h"
|
||||||
#endif
|
#endif
|
||||||
|
@ -31,6 +31,7 @@
|
||||||
#include "common/utils/string_utils.h"
|
#include "common/utils/string_utils.h"
|
||||||
#include "encoder/encoderFactory.h"
|
#include "encoder/encoderFactory.h"
|
||||||
#include "message/message.h"
|
#include "message/message.h"
|
||||||
|
#include "server_settings.hpp"
|
||||||
#include "streamServer.h"
|
#include "streamServer.h"
|
||||||
#if defined(HAS_AVAHI) || defined(HAS_BONJOUR)
|
#if defined(HAS_AVAHI) || defined(HAS_BONJOUR)
|
||||||
#include "publishZeroConf/publishmDNS.h"
|
#include "publishZeroConf/publishmDNS.h"
|
||||||
|
@ -55,35 +56,57 @@ int main(int argc, char* argv[])
|
||||||
int exitcode = EXIT_SUCCESS;
|
int exitcode = EXIT_SUCCESS;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
StreamServerSettings settings;
|
ServerSettings settings;
|
||||||
std::string pcmStream = "pipe:///tmp/snapfifo?name=default";
|
std::string pcmStream = "pipe:///tmp/snapfifo?name=default";
|
||||||
|
std::string config_file = "/etc/snapserver.conf";
|
||||||
|
|
||||||
OptionParser op("Allowed options");
|
OptionParser op("Allowed options");
|
||||||
auto helpSwitch = op.add<Switch>("h", "help", "Produce help message");
|
auto helpSwitch = op.add<Switch>("h", "help", "Produce help message");
|
||||||
auto groffSwitch = op.add<Switch, Attribute::hidden>("", "groff", "produce groff message");
|
auto groffSwitch = op.add<Switch, Attribute::hidden>("", "groff", "produce groff message");
|
||||||
auto debugOption = op.add<Implicit<string>, Attribute::hidden>("", "debug", "enable debug logging", "");
|
|
||||||
auto versionSwitch = op.add<Switch>("v", "version", "Show version number");
|
auto versionSwitch = op.add<Switch>("v", "version", "Show version number");
|
||||||
op.add<Value<size_t>>("p", "port", "Server port", settings.port, &settings.port);
|
|
||||||
op.add<Value<size_t>>("", "controlPort", "Remote control port", settings.controlPort, &settings.controlPort);
|
|
||||||
auto streamValue = op.add<Value<string>>(
|
|
||||||
"s", "stream", "URI of the PCM input stream.\nFormat: TYPE://host/path?name=NAME\n[&codec=CODEC]\n[&sampleformat=SAMPLEFORMAT]", pcmStream,
|
|
||||||
&pcmStream);
|
|
||||||
|
|
||||||
op.add<Value<string>>("", "sampleformat", "Default sample format", settings.sampleFormat, &settings.sampleFormat);
|
|
||||||
op.add<Value<string>>("c", "codec", "Default transport codec\n(flac|ogg|pcm)[:options]\nType codec:? to get codec specific options", settings.codec,
|
|
||||||
&settings.codec);
|
|
||||||
op.add<Value<size_t>>("", "streamBuffer", "Default stream read buffer [ms]", settings.streamReadMs, &settings.streamReadMs);
|
|
||||||
op.add<Value<int>>("b", "buffer", "Buffer [ms]", settings.bufferMs, &settings.bufferMs);
|
|
||||||
op.add<Switch>("", "sendToMuted", "Send audio to muted clients", &settings.sendAudioToMutedClients);
|
|
||||||
#ifdef HAS_DAEMON
|
#ifdef HAS_DAEMON
|
||||||
int processPriority(0);
|
int processPriority(0);
|
||||||
auto daemonOption = op.add<Implicit<int>>("d", "daemon", "Daemonize\noptional process priority [-20..19]", 0, &processPriority);
|
auto daemonOption = op.add<Implicit<int>>("d", "daemon", "Daemonize\noptional process priority [-20..19]", 0, &processPriority);
|
||||||
auto userValue = op.add<Value<string>>("", "user", "the user[:group] to run snapserver as when daemonized", "");
|
auto userValue = op.add<Value<string>>("", "user", "the user[:group] to run snapserver as when daemonized", "");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
op.add<Value<string>>("c", "config", "path to the configuration file", config_file, &config_file);
|
||||||
|
|
||||||
|
// debug settings
|
||||||
|
OptionParser conf("");
|
||||||
|
conf.add<Switch>("", "logging.debug", "enable debug logging", &settings.logging.debug);
|
||||||
|
conf.add<Value<string>>("", "logging.debug_logfile", "log file name for the debug logs (debug must be enabled)", settings.logging.debug_logfile,
|
||||||
|
&settings.logging.debug_logfile);
|
||||||
|
|
||||||
|
// stream settings
|
||||||
|
conf.add<Value<size_t>>("p", "stream.port", "Server port", settings.stream.port, &settings.stream.port);
|
||||||
|
conf.add<Value<size_t>>("", "stream.controlPort", "Remote control port", settings.tcp.port, &settings.tcp.port);
|
||||||
|
auto streamValue = conf.add<Value<string>>(
|
||||||
|
"s", "stream.stream", "URI of the PCM input stream.\nFormat: TYPE://host/path?name=NAME\n[&codec=CODEC]\n[&sampleformat=SAMPLEFORMAT]", pcmStream,
|
||||||
|
&pcmStream);
|
||||||
|
|
||||||
|
conf.add<Value<string>>("", "stream.sampleformat", "Default sample format", settings.stream.sampleFormat, &settings.stream.sampleFormat);
|
||||||
|
conf.add<Value<string>>("c", "stream.codec", "Default transport codec\n(flac|ogg|pcm)[:options]\nType codec:? to get codec specific options",
|
||||||
|
settings.stream.codec, &settings.stream.codec);
|
||||||
|
conf.add<Value<size_t>>("", "stream.streamBuffer", "Default stream read buffer [ms]", settings.stream.streamReadMs, &settings.stream.streamReadMs);
|
||||||
|
conf.add<Value<int>>("b", "stream.buffer", "Buffer [ms]", settings.stream.bufferMs, &settings.stream.bufferMs);
|
||||||
|
conf.add<Switch>("", "stream.sendToMuted", "Send audio to muted clients", &settings.stream.sendAudioToMutedClients);
|
||||||
|
|
||||||
|
// HTTP RPC settings
|
||||||
|
conf.add<Switch>("", "http.enabled", "enable HTTP Json RPC (HTTP POST and websockets)", &settings.http.enabled);
|
||||||
|
conf.add<Value<size_t>>("", "http.port", "which port the server should listen to", settings.http.port, &settings.http.port);
|
||||||
|
conf.add<Value<string>>("", "http.doc_root", "serve a website from the doc_root location", settings.http.doc_root, &settings.http.doc_root);
|
||||||
|
|
||||||
|
// TCP RPC settings
|
||||||
|
conf.add<Switch>("", "tcp.enabled", "enable TCP Json RPC)", &settings.tcp.enabled);
|
||||||
|
conf.add<Value<size_t>>("", "tcp.port", "which port the server should listen to", settings.tcp.port, &settings.tcp.port);
|
||||||
|
|
||||||
|
// TODO: Should be possible to override settings on command line
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
op.parse(argc, argv);
|
op.parse(argc, argv);
|
||||||
|
conf.parse(config_file);
|
||||||
}
|
}
|
||||||
catch (const std::invalid_argument& e)
|
catch (const std::invalid_argument& e)
|
||||||
{
|
{
|
||||||
|
@ -116,34 +139,25 @@ int main(int argc, char* argv[])
|
||||||
exit(EXIT_SUCCESS);
|
exit(EXIT_SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!streamValue->is_set())
|
if (settings.stream.codec.find(":?") != string::npos)
|
||||||
settings.pcmStreams.push_back(streamValue->value());
|
|
||||||
|
|
||||||
for (size_t n = 0; n < streamValue->count(); ++n)
|
|
||||||
{
|
|
||||||
cout << streamValue->value(n) << "\n";
|
|
||||||
settings.pcmStreams.push_back(streamValue->value(n));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (settings.codec.find(":?") != string::npos)
|
|
||||||
{
|
{
|
||||||
EncoderFactory encoderFactory;
|
EncoderFactory encoderFactory;
|
||||||
std::unique_ptr<Encoder> encoder(encoderFactory.createEncoder(settings.codec));
|
std::unique_ptr<Encoder> encoder(encoderFactory.createEncoder(settings.stream.codec));
|
||||||
if (encoder)
|
if (encoder)
|
||||||
{
|
{
|
||||||
cout << "Options for codec \"" << encoder->name() << "\":\n"
|
cout << "Options for codec \"" << encoder->name() << "\":\n"
|
||||||
<< " " << encoder->getAvailableOptions() << "\n"
|
<< " " << encoder->getAvailableOptions() << "\n"
|
||||||
<< " Default: \"" << encoder->getDefaultOptions() << "\"\n";
|
<< " Default: \"" << encoder->getDefaultOptions() << "\"\n";
|
||||||
}
|
}
|
||||||
return 1;
|
exit(EXIT_SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
AixLog::Log::init<AixLog::SinkNative>("snapserver", AixLog::Severity::trace, AixLog::Type::special);
|
AixLog::Log::init<AixLog::SinkNative>("snapserver", AixLog::Severity::trace, AixLog::Type::special);
|
||||||
if (debugOption->is_set())
|
if (settings.logging.debug)
|
||||||
{
|
{
|
||||||
AixLog::Log::instance().add_logsink<AixLog::SinkCout>(AixLog::Severity::trace, AixLog::Type::all, "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)");
|
AixLog::Log::instance().add_logsink<AixLog::SinkCout>(AixLog::Severity::trace, AixLog::Type::all, "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)");
|
||||||
if (!debugOption->value().empty())
|
if (!settings.logging.debug_logfile.empty())
|
||||||
AixLog::Log::instance().add_logsink<AixLog::SinkFile>(AixLog::Severity::trace, AixLog::Type::all, debugOption->value(),
|
AixLog::Log::instance().add_logsink<AixLog::SinkFile>(AixLog::Severity::trace, AixLog::Type::all, settings.logging.debug_logfile,
|
||||||
"%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)");
|
"%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -151,6 +165,14 @@ int main(int argc, char* argv[])
|
||||||
AixLog::Log::instance().add_logsink<AixLog::SinkCout>(AixLog::Severity::info, AixLog::Type::all, "%Y-%m-%d %H-%M-%S [#severity]");
|
AixLog::Log::instance().add_logsink<AixLog::SinkCout>(AixLog::Severity::info, AixLog::Type::all, "%Y-%m-%d %H-%M-%S [#severity]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!streamValue->is_set())
|
||||||
|
settings.stream.pcmStreams.push_back(streamValue->value());
|
||||||
|
|
||||||
|
for (size_t n = 0; n < streamValue->count(); ++n)
|
||||||
|
{
|
||||||
|
LOG(INFO) << "Adding stream: " << streamValue->value(n) << "\n";
|
||||||
|
settings.stream.pcmStreams.push_back(streamValue->value(n));
|
||||||
|
}
|
||||||
|
|
||||||
signal(SIGHUP, signal_handler);
|
signal(SIGHUP, signal_handler);
|
||||||
signal(SIGTERM, signal_handler);
|
signal(SIGTERM, signal_handler);
|
||||||
|
@ -194,12 +216,26 @@ int main(int argc, char* argv[])
|
||||||
|
|
||||||
#if defined(HAS_AVAHI) || defined(HAS_BONJOUR)
|
#if defined(HAS_AVAHI) || defined(HAS_BONJOUR)
|
||||||
PublishZeroConf publishZeroConfg("Snapcast");
|
PublishZeroConf publishZeroConfg("Snapcast");
|
||||||
publishZeroConfg.publish({mDNSService("_snapcast._tcp", settings.port), mDNSService("_snapcast-jsonrpc._tcp", settings.controlPort),
|
vector<mDNSService> dns_services;
|
||||||
mDNSService("_snapcastjsonrpc._tcp", settings.controlPort)});
|
dns_services.emplace_back("_snapcast._tcp", settings.stream.port);
|
||||||
|
dns_services.emplace_back("_snapcast-stream._tcp", settings.stream.port);
|
||||||
|
if (settings.tcp.enabled)
|
||||||
|
{
|
||||||
|
dns_services.emplace_back("_snapcast-jsonrpc._tcp", settings.tcp.port);
|
||||||
|
dns_services.emplace_back("_snapcast-tcp._tcp", settings.tcp.port);
|
||||||
|
}
|
||||||
|
if (settings.http.enabled)
|
||||||
|
{
|
||||||
|
dns_services.emplace_back("_snapcast-http._tcp", settings.http.port);
|
||||||
|
}
|
||||||
|
publishZeroConfg.publish(dns_services);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (settings.bufferMs < 400)
|
if (settings.stream.bufferMs < 400)
|
||||||
settings.bufferMs = 400;
|
{
|
||||||
|
LOG(WARNING) << "Buffer is less than 400ms, changing to 400ms\n";
|
||||||
|
settings.stream.bufferMs = 400;
|
||||||
|
}
|
||||||
|
|
||||||
boost::asio::io_context io_context;
|
boost::asio::io_context io_context;
|
||||||
std::unique_ptr<StreamServer> streamServer(new StreamServer(&io_context, settings));
|
std::unique_ptr<StreamServer> streamServer(new StreamServer(&io_context, settings));
|
||||||
|
|
87
server/snapserver.conf
Normal file
87
server/snapserver.conf
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
##################################
|
||||||
|
### ###
|
||||||
|
### Snapserver config file ###
|
||||||
|
### ###
|
||||||
|
##################################
|
||||||
|
|
||||||
|
# default values are commented
|
||||||
|
# to change them, just uncomment
|
||||||
|
|
||||||
|
# HTTP RPC ####################################################################
|
||||||
|
#
|
||||||
|
[http]
|
||||||
|
# enable HTTP Json RPC (HTTP POST and websockets)
|
||||||
|
enabled = true
|
||||||
|
|
||||||
|
# address to listen on
|
||||||
|
# TODO: not implemented yet
|
||||||
|
bind_to_address = 127.0.0.1
|
||||||
|
|
||||||
|
# which port the server should listen to
|
||||||
|
port = 1780
|
||||||
|
|
||||||
|
# serve a website from the doc_root location
|
||||||
|
doc_root = /home/johannes/Develop/snapcast/control
|
||||||
|
#
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
# TCP RPC #####################################################################
|
||||||
|
#
|
||||||
|
[tcp]
|
||||||
|
# enable TCP Json RPC
|
||||||
|
enabled = true_
|
||||||
|
|
||||||
|
# address to listen on
|
||||||
|
# TODO: not implemented yet
|
||||||
|
bind_to_address = 127.0.0.1
|
||||||
|
|
||||||
|
# which port the server should listen to
|
||||||
|
port = 1705
|
||||||
|
#
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
# Stream settings #############################################################
|
||||||
|
#
|
||||||
|
[stream]
|
||||||
|
# address to listen on
|
||||||
|
# TODO: not implemented yet
|
||||||
|
#bind_to_address = 0.0.0.0
|
||||||
|
|
||||||
|
# which port the server should listen to
|
||||||
|
#port = 1704
|
||||||
|
|
||||||
|
# stream URI of the PCM input stream, can be configured multiple times
|
||||||
|
# Format: TYPE://host/path?name=NAME[&codec=CODEC][&sampleformat=SAMPLEFORMAT]
|
||||||
|
stream = pipe:///tmp/snapfifo?name=default
|
||||||
|
stream = pipe:///tmp/snapfifo2?name=default2
|
||||||
|
|
||||||
|
# Default sample format
|
||||||
|
#sampleformat = 48000:16:2
|
||||||
|
|
||||||
|
# Default transport codec
|
||||||
|
# (flac|ogg|pcm)[:options]
|
||||||
|
# Type codec:? to get codec specific options
|
||||||
|
#codec = flac
|
||||||
|
|
||||||
|
# Default stream read buffer [ms]
|
||||||
|
#streamBuffer = 20
|
||||||
|
|
||||||
|
# Buffer [ms]
|
||||||
|
#buffer = 1000
|
||||||
|
|
||||||
|
# Send audio to muted clients
|
||||||
|
#sendToMuted = false
|
||||||
|
#
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
# Logging options #############################################################
|
||||||
|
#
|
||||||
|
[logging]
|
||||||
|
|
||||||
|
# enable debug logging
|
||||||
|
#debug = false
|
||||||
|
|
||||||
|
# log file name for the debug logs (debug must be enabled)
|
||||||
|
#debug_logfile =
|
||||||
|
#
|
||||||
|
###############################################################################
|
|
@ -29,8 +29,8 @@ using namespace std;
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
|
||||||
StreamServer::StreamServer(boost::asio::io_context* io_context, const StreamServerSettings& streamServerSettings)
|
StreamServer::StreamServer(boost::asio::io_context* io_context, const ServerSettings& serverSettings)
|
||||||
: io_context_(io_context), acceptor_v4_(nullptr), acceptor_v6_(nullptr), settings_(streamServerSettings)
|
: io_context_(io_context), acceptor_v4_(nullptr), acceptor_v6_(nullptr), settings_(serverSettings)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ void StreamServer::onChunkRead(const PcmStream* pcmStream, msg::PcmChunk* chunk,
|
||||||
std::lock_guard<std::recursive_mutex> mlock(sessionsMutex_);
|
std::lock_guard<std::recursive_mutex> mlock(sessionsMutex_);
|
||||||
for (auto s : sessions_)
|
for (auto s : sessions_)
|
||||||
{
|
{
|
||||||
if (!settings_.sendAudioToMutedClients)
|
if (!settings_.stream.sendAudioToMutedClients)
|
||||||
{
|
{
|
||||||
GroupPtr group = Config::instance().getGroupFromClient(s->clientId);
|
GroupPtr group = Config::instance().getGroupFromClient(s->clientId);
|
||||||
if (group)
|
if (group)
|
||||||
|
@ -195,9 +195,9 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp
|
||||||
int latency = request->params().get("latency");
|
int latency = request->params().get("latency");
|
||||||
if (latency < -10000)
|
if (latency < -10000)
|
||||||
latency = -10000;
|
latency = -10000;
|
||||||
else if (latency > settings_.bufferMs)
|
else if (latency > settings_.stream.bufferMs)
|
||||||
latency = settings_.bufferMs;
|
latency = settings_.stream.bufferMs;
|
||||||
clientInfo->config.latency = latency; //, -10000, settings_.bufferMs);
|
clientInfo->config.latency = latency; //, -10000, settings_.stream.bufferMs);
|
||||||
result["latency"] = clientInfo->config.latency;
|
result["latency"] = clientInfo->config.latency;
|
||||||
notification.reset(
|
notification.reset(
|
||||||
new jsonrpcpp::Notification("Client.OnLatencyChanged", jsonrpcpp::Parameter("id", clientInfo->id, "latency", clientInfo->config.latency)));
|
new jsonrpcpp::Notification("Client.OnLatencyChanged", jsonrpcpp::Parameter("id", clientInfo->id, "latency", clientInfo->config.latency)));
|
||||||
|
@ -223,7 +223,7 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp
|
||||||
if (session != nullptr)
|
if (session != nullptr)
|
||||||
{
|
{
|
||||||
auto serverSettings = make_shared<msg::ServerSettings>();
|
auto serverSettings = make_shared<msg::ServerSettings>();
|
||||||
serverSettings->setBufferMs(settings_.bufferMs);
|
serverSettings->setBufferMs(settings_.stream.bufferMs);
|
||||||
serverSettings->setVolume(clientInfo->config.volume.percent);
|
serverSettings->setVolume(clientInfo->config.volume.percent);
|
||||||
GroupPtr group = Config::instance().getGroupFromClient(clientInfo);
|
GroupPtr group = Config::instance().getGroupFromClient(clientInfo);
|
||||||
serverSettings->setMuted(clientInfo->config.volume.muted || group->muted);
|
serverSettings->setMuted(clientInfo->config.volume.muted || group->muted);
|
||||||
|
@ -274,7 +274,7 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp
|
||||||
if (session != nullptr)
|
if (session != nullptr)
|
||||||
{
|
{
|
||||||
auto serverSettings = make_shared<msg::ServerSettings>();
|
auto serverSettings = make_shared<msg::ServerSettings>();
|
||||||
serverSettings->setBufferMs(settings_.bufferMs);
|
serverSettings->setBufferMs(settings_.stream.bufferMs);
|
||||||
serverSettings->setVolume(client->config.volume.percent);
|
serverSettings->setVolume(client->config.volume.percent);
|
||||||
GroupPtr group = Config::instance().getGroupFromClient(client);
|
GroupPtr group = Config::instance().getGroupFromClient(client);
|
||||||
serverSettings->setMuted(client->config.volume.muted || group->muted);
|
serverSettings->setMuted(client->config.volume.muted || group->muted);
|
||||||
|
@ -667,7 +667,7 @@ void StreamServer::onMessageReceived(StreamSession* streamSession, const msg::Ba
|
||||||
serverSettings->setVolume(client->config.volume.percent);
|
serverSettings->setVolume(client->config.volume.percent);
|
||||||
serverSettings->setMuted(client->config.volume.muted || group->muted);
|
serverSettings->setMuted(client->config.volume.muted || group->muted);
|
||||||
serverSettings->setLatency(client->config.latency);
|
serverSettings->setLatency(client->config.latency);
|
||||||
serverSettings->setBufferMs(settings_.bufferMs);
|
serverSettings->setBufferMs(settings_.stream.bufferMs);
|
||||||
serverSettings->refersTo = helloMsg.id;
|
serverSettings->refersTo = helloMsg.id;
|
||||||
streamSession->sendAsync(serverSettings);
|
streamSession->sendAsync(serverSettings);
|
||||||
|
|
||||||
|
@ -793,7 +793,7 @@ void StreamServer::handleAccept(socket_ptr socket)
|
||||||
SLOG(NOTICE) << "StreamServer::NewConnection: " << socket->remote_endpoint().address().to_string() << endl;
|
SLOG(NOTICE) << "StreamServer::NewConnection: " << socket->remote_endpoint().address().to_string() << endl;
|
||||||
shared_ptr<StreamSession> session = make_shared<StreamSession>(this, socket);
|
shared_ptr<StreamSession> session = make_shared<StreamSession>(this, socket);
|
||||||
|
|
||||||
session->setBufferMs(settings_.bufferMs);
|
session->setBufferMs(settings_.stream.bufferMs);
|
||||||
session->start();
|
session->start();
|
||||||
|
|
||||||
std::lock_guard<std::recursive_mutex> mlock(sessionsMutex_);
|
std::lock_guard<std::recursive_mutex> mlock(sessionsMutex_);
|
||||||
|
@ -811,12 +811,12 @@ void StreamServer::start()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
controlServer_.reset(new ControlServer(io_context_, settings_.controlPort, this));
|
controlServer_.reset(new ControlServer(io_context_, settings_.tcp, settings_.http, this));
|
||||||
controlServer_->start();
|
controlServer_->start();
|
||||||
|
|
||||||
streamManager_.reset(new StreamManager(this, settings_.sampleFormat, settings_.codec, settings_.streamReadMs));
|
streamManager_.reset(new StreamManager(this, settings_.stream.sampleFormat, settings_.stream.codec, settings_.stream.streamReadMs));
|
||||||
// throw SnapException("xxx");
|
// throw SnapException("xxx");
|
||||||
for (const auto& streamUri : settings_.pcmStreams)
|
for (const auto& streamUri : settings_.stream.pcmStreams)
|
||||||
{
|
{
|
||||||
PcmStreamPtr stream = streamManager_->addStream(streamUri);
|
PcmStreamPtr stream = streamManager_->addStream(streamUri);
|
||||||
if (stream)
|
if (stream)
|
||||||
|
@ -825,7 +825,7 @@ void StreamServer::start()
|
||||||
streamManager_->start();
|
streamManager_->start();
|
||||||
|
|
||||||
bool is_v6_only(true);
|
bool is_v6_only(true);
|
||||||
tcp::endpoint endpoint_v6(tcp::v6(), settings_.port);
|
tcp::endpoint endpoint_v6(tcp::v6(), settings_.stream.port);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
acceptor_v6_ = make_shared<tcp::acceptor>(*io_context_, endpoint_v6);
|
acceptor_v6_ = make_shared<tcp::acceptor>(*io_context_, endpoint_v6);
|
||||||
|
@ -843,7 +843,7 @@ void StreamServer::start()
|
||||||
|
|
||||||
if (!acceptor_v6_ || is_v6_only)
|
if (!acceptor_v6_ || is_v6_only)
|
||||||
{
|
{
|
||||||
tcp::endpoint endpoint_v4(tcp::v4(), settings_.port);
|
tcp::endpoint endpoint_v4(tcp::v4(), settings_.stream.port);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
acceptor_v4_ = make_shared<tcp::acceptor>(*io_context_, endpoint_v4);
|
acceptor_v4_ = make_shared<tcp::acceptor>(*io_context_, endpoint_v4);
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
#include "message/codecHeader.h"
|
#include "message/codecHeader.h"
|
||||||
#include "message/message.h"
|
#include "message/message.h"
|
||||||
#include "message/serverSettings.h"
|
#include "message/serverSettings.h"
|
||||||
|
#include "server_settings.hpp"
|
||||||
#include "streamSession.h"
|
#include "streamSession.h"
|
||||||
#include "streamreader/streamManager.h"
|
#include "streamreader/streamManager.h"
|
||||||
|
|
||||||
|
@ -42,22 +43,6 @@ using boost::asio::ip::tcp;
|
||||||
typedef std::shared_ptr<tcp::socket> socket_ptr;
|
typedef std::shared_ptr<tcp::socket> socket_ptr;
|
||||||
typedef std::shared_ptr<StreamSession> session_ptr;
|
typedef std::shared_ptr<StreamSession> session_ptr;
|
||||||
|
|
||||||
struct StreamServerSettings
|
|
||||||
{
|
|
||||||
StreamServerSettings()
|
|
||||||
: port(1704), controlPort(1705), codec("flac"), bufferMs(1000), sampleFormat("48000:16:2"), streamReadMs(20), sendAudioToMutedClients(false)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
size_t port;
|
|
||||||
size_t controlPort;
|
|
||||||
std::vector<std::string> pcmStreams;
|
|
||||||
std::string codec;
|
|
||||||
int32_t bufferMs;
|
|
||||||
std::string sampleFormat;
|
|
||||||
size_t streamReadMs;
|
|
||||||
bool sendAudioToMutedClients;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/// Forwars PCM data to the connected clients
|
/// Forwars PCM data to the connected clients
|
||||||
/**
|
/**
|
||||||
|
@ -69,7 +54,7 @@ struct StreamServerSettings
|
||||||
class StreamServer : public MessageReceiver, ControlMessageReceiver, PcmListener
|
class StreamServer : public MessageReceiver, ControlMessageReceiver, PcmListener
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
StreamServer(boost::asio::io_context* io_context, const StreamServerSettings& streamServerSettings);
|
StreamServer(boost::asio::io_context* io_context, const ServerSettings& serverSettings);
|
||||||
virtual ~StreamServer();
|
virtual ~StreamServer();
|
||||||
|
|
||||||
void start();
|
void start();
|
||||||
|
@ -103,7 +88,7 @@ private:
|
||||||
std::shared_ptr<tcp::acceptor> acceptor_v4_;
|
std::shared_ptr<tcp::acceptor> acceptor_v4_;
|
||||||
std::shared_ptr<tcp::acceptor> acceptor_v6_;
|
std::shared_ptr<tcp::acceptor> acceptor_v6_;
|
||||||
|
|
||||||
StreamServerSettings settings_;
|
ServerSettings settings_;
|
||||||
Queue<std::shared_ptr<msg::BaseMessage>> messages_;
|
Queue<std::shared_ptr<msg::BaseMessage>> messages_;
|
||||||
std::unique_ptr<ControlServer> controlServer_;
|
std::unique_ptr<ControlServer> controlServer_;
|
||||||
std::unique_ptr<StreamManager> streamManager_;
|
std::unique_ptr<StreamManager> streamManager_;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue