diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 6524c489..412112c2 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -152,7 +152,7 @@ jobs: imageTag: devcontainer imageName: ghcr.io/${{ github.repository_owner }}/wolf cacheFrom: ghcr.io/${{ github.repository_owner }}/wolf:${{github.ref_name}} - push: always + # push: always # TODO: # runCmd: | # cmake -Bbuild \ diff --git a/src/core/src/core/audio.hpp b/src/core/src/core/audio.hpp index db1dbf39..68514170 100644 --- a/src/core/src/core/audio.hpp +++ b/src/core/src/core/audio.hpp @@ -20,7 +20,7 @@ constexpr auto SAMPLE_RATE = 48000; struct AudioMode { - enum Speakers { + enum class Speakers { FRONT_LEFT, FRONT_RIGHT, FRONT_CENTER, diff --git a/src/core/src/platforms/linux/pulseaudio/pulse.cpp b/src/core/src/platforms/linux/pulseaudio/pulse.cpp index cfff4dce..35783747 100644 --- a/src/core/src/platforms/linux/pulseaudio/pulse.cpp +++ b/src/core/src/platforms/linux/pulseaudio/pulse.cpp @@ -16,31 +16,31 @@ template <> class formatter { // Mapping taken from // https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User/Modules/#module-null-sink switch (speaker) { - case wolf::core::audio::AudioMode::FRONT_LEFT: + case wolf::core::audio::AudioMode::Speakers::FRONT_LEFT: speaker_name = "front-left"; break; - case wolf::core::audio::AudioMode::FRONT_RIGHT: + case wolf::core::audio::AudioMode::Speakers::FRONT_RIGHT: speaker_name = "front-right"; break; - case wolf::core::audio::AudioMode::FRONT_CENTER: + case wolf::core::audio::AudioMode::Speakers::FRONT_CENTER: speaker_name = "front-center"; break; - case wolf::core::audio::AudioMode::LOW_FREQUENCY: + case wolf::core::audio::AudioMode::Speakers::LOW_FREQUENCY: speaker_name = "lfe"; break; - case wolf::core::audio::AudioMode::BACK_LEFT: + case wolf::core::audio::AudioMode::Speakers::BACK_LEFT: speaker_name = "rear-left"; break; - case wolf::core::audio::AudioMode::BACK_RIGHT: + case wolf::core::audio::AudioMode::Speakers::BACK_RIGHT: speaker_name = "rear-right"; break; - case wolf::core::audio::AudioMode::SIDE_LEFT: + case wolf::core::audio::AudioMode::Speakers::SIDE_LEFT: speaker_name = "side-left"; break; - case wolf::core::audio::AudioMode::SIDE_RIGHT: + case wolf::core::audio::AudioMode::Speakers::SIDE_RIGHT: speaker_name = "side-right"; break; - case wolf::core::audio::AudioMode::MAX_SPEAKERS: + case wolf::core::audio::AudioMode::Speakers::MAX_SPEAKERS: break; } return fmt::format_to(ctx.out(), "{}", speaker_name); diff --git a/src/moonlight-server/api/api.cpp b/src/moonlight-server/api/api.cpp index 9ba1c3d1..afc44a3b 100644 --- a/src/moonlight-server/api/api.cpp +++ b/src/moonlight-server/api/api.cpp @@ -1,210 +1,27 @@ #include -#include #include #include -#include #include -#include #include -#include namespace wolf::api { using namespace wolf::core; -std::string to_str(boost::asio::streambuf &streambuf) { - return {buffers_begin(streambuf.data()), buffers_end(streambuf.data())}; -} - -UnixSocketServer::UnixSocketServer(boost::asio::io_context &io_context, - const std::string &socket_path, - immer::box app_state) - : io_context_(io_context), app_state(app_state), - acceptor_(io_context, boost::asio::local::stream_protocol::endpoint(socket_path)) { - - http.add(HTTPMethod::GET, - "/api/v1/events", - {.summary = "Subscribe to events", - .description = "This endpoint allows clients to subscribe to events using HTTP 0.9. \n" - "Events are sent as JSON objects, one per line. Clients should expect to receive a newline " - "character after each event. The connection will be kept open until the client closes it.", - .handler = [this](auto req, auto socket) { endpoint_Events(req, socket); }}); - - http.add( - HTTPMethod::GET, - "/api/v1/pending-pair-requests", - { - .summary = "Get pending pair requests", - .description = "This endpoint returns a list of Moonlight clients that are currently waiting to be paired.", - .response_description = {{200, {.json_schema = rfl::json::to_schema()}}}, - .handler = [this](auto req, auto socket) { endpoint_PendingPairRequest(req, socket); }, - }); - - http.add(HTTPMethod::POST, - "/api/v1/pair-client", - { - .summary = "Pair a client", - .request_description = APIDescription{.json_schema = rfl::json::to_schema()}, - .response_description = {{200, {.json_schema = rfl::json::to_schema()}}, - {500, {.json_schema = rfl::json::to_schema()}}}, - .handler = [this](auto req, auto socket) { endpoint_Pair(req, socket); }, - }); - - auto openapi_schema = http.openapi_schema(); - http.add(HTTPMethod::GET, "/api/v1/openapi-schema", {.handler = [this, openapi_schema](auto req, auto socket) { - send_http(socket, 200, openapi_schema); - }}); - - start_accept(); -} - -void UnixSocketServer::broadcast_event(const std::string &event_json) { - logs::log(logs::trace, "[API] Sending event: {}", event_json); - for (auto &socket : sockets_) { - boost::asio::async_write(socket->socket, - boost::asio::buffer(event_json + "\n"), - [this, socket](const boost::system::error_code &ec, std::size_t /*length*/) { - if (ec) { - logs::log(logs::error, "[API] Error sending event: {}", ec.message()); - close(*socket); - } - }); - } -} - -void UnixSocketServer::cleanup_sockets() { - sockets_.erase(std::remove_if(sockets_.begin(), sockets_.end(), [](const auto &socket) { return !socket->is_alive; }), - sockets_.end()); -} - -void UnixSocketServer::send_http(std::shared_ptr socket, int status_code, std::string_view body) { - auto http_reply = fmt::format("HTTP/1.0 {} OK\r\nContent-Length: {}\r\n\r\n{}", status_code, body.size(), body); - boost::asio::async_write(socket->socket, - boost::asio::buffer(http_reply), - [this, socket](const boost::system::error_code &ec, std::size_t /*length*/) { - if (ec) { - logs::log(logs::error, "[API] Error sending HTTP: {}", ec.message()); - close(*socket); - } - }); -} - -void UnixSocketServer::handle_request(const HTTPRequest &req, std::shared_ptr socket) { - logs::log(logs::debug, "[API] Received request: {} {} - {}", rfl::enum_to_string(req.method), req.path, req.body); - - if (!http.handle_request(req, socket)) { - send_http(socket, 404, ""); - close(*socket); - } -} - -void UnixSocketServer::start_connection(std::shared_ptr socket) { - auto request_buf = std::make_shared(std::numeric_limits::max()); - boost::asio::async_read_until( - socket->socket, - *request_buf, - "\r\n\r\n", - [this, socket, request_buf](const boost::system::error_code &ec, std::size_t bytes_transferred) { - if (ec) { - logs::log(logs::error, "[API] Error reading request: {}", ec.message()); - close(*socket); - return; - } - HTTPRequest req = {}; - std::string method; - std::istream is(request_buf.get()); - SimpleWeb::RequestMessage::parse(is, method, req.path, req.query_string, req.http_version, req.headers); - if (method == "GET") - req.method = HTTPMethod::GET; - else if (method == "POST") - req.method = HTTPMethod::POST; - else if (method == "PUT") - req.method = HTTPMethod::PUT; - else if (method == "DELETE") - req.method = HTTPMethod::DELETE; - else - req.method = HTTPMethod::GET; - - if (req.headers.contains("Transfer-Encoding") && req.headers.find("Transfer-Encoding")->second == "chunked") { - logs::log(logs::error, "[API] Chunked encoding not supported, use HTTP/1.0 instead"); - close(*socket); - return; - } - - // Get the body payload - if (req.headers.contains("Content-Length")) { - auto content_length = std::stoul(req.headers.find("Content-Length")->second); - std::size_t num_additional_bytes = request_buf->size() - bytes_transferred; - if (content_length > num_additional_bytes) { - boost::asio::async_read(socket->socket, - *request_buf, - boost::asio::transfer_exactly(content_length - bytes_transferred), - [this, socket, request_buf, req = std::make_unique(req)]( - const boost::system::error_code &ec, - std::size_t /*bytes_transferred*/) { - if (ec) { - logs::log(logs::error, "[API] Error reading request body: {}", ec.message()); - close(*socket); - return; - } - req->body = to_str(*request_buf); - handle_request(*req, socket); - }); - } else { - req.body = to_str(*request_buf); - handle_request(req, socket); - } - } else { - handle_request(req, socket); - } - }); -} - -void UnixSocketServer::start_accept() { - auto socket = - std::make_shared(UnixSocket{.socket = boost::asio::local::stream_protocol::socket(io_context_)}); - acceptor_.async_accept(socket->socket, [this, socket](const boost::system::error_code &ec) { - if (!ec) { - start_accept(); // Immediately start accepting a new connection - start_connection(socket); // Start reading the request from the new connection - } else { - logs::log(logs::error, "[API] Error accepting connection: {}", ec.message()); - close(*socket); - } - }); -} - -void UnixSocketServer::close(UnixSocket &socket) { - socket.socket.close(); - socket.is_alive = false; -} - void start_server(immer::box app_state) { auto socket_path = "/tmp/wolf.sock"; logs::log(logs::info, "Starting API server on {}", socket_path); - auto event_queue = std::make_shared>(); - - auto global_ev_handler = app_state->event_bus->register_global_handler([event_queue](events::EventsVariant ev) { - /** - * We could do server.send_event() here, but since this callback will be running from the fire_event() thread, - * we don't want to block it. Instead, we'll push the event to a queue and have a separate thread handle the - * sending of the event. - */ - event_queue->push(std::move(ev)); - }); - ::unlink(socket_path); boost::asio::io_context io_context; UnixSocketServer server(io_context, socket_path, app_state); + auto server_ptr = std::make_shared(server); + + auto global_ev_handler = app_state->event_bus->register_global_handler([server_ptr](events::EventsVariant ev) { + std::visit([server_ptr](auto &&arg) { server_ptr->broadcast_event(arg->event_type, rfl::json::write(*arg)); }, ev); + }); - while (true) { - io_context.run_for(std::chrono::milliseconds(100)); - server.cleanup_sockets(); - while (auto ev = event_queue->pop(std::chrono::milliseconds(0))) { - std::visit([&server](auto &&arg) { server.broadcast_event(rfl::json::write(*arg)); }, *ev); - } - } + io_context.run(); } } // namespace wolf::api \ No newline at end of file diff --git a/src/moonlight-server/api/api.hpp b/src/moonlight-server/api/api.hpp index 58d0e7a9..f8961a92 100644 --- a/src/moonlight-server/api/api.hpp +++ b/src/moonlight-server/api/api.hpp @@ -38,26 +38,41 @@ class UnixSocketServer { const std::string &socket_path, immer::box app_state); - void broadcast_event(const std::string &event_json); + UnixSocketServer(const UnixSocketServer &) = default; - void cleanup_sockets(); + void broadcast_event(const std::string &event_type, const std::string &event_json); private: void endpoint_Events(const HTTPRequest &req, std::shared_ptr socket); void endpoint_PendingPairRequest(const HTTPRequest &req, std::shared_ptr socket); void endpoint_Pair(const HTTPRequest &req, std::shared_ptr socket); + void sse_broadcast(const std::string &payload); + void sse_keepalive(const boost::system::error_code &e); + void send_http(std::shared_ptr socket, int status_code, std::string_view body); + void send_http(std::shared_ptr socket, + int status_code, + const std::vector &http_headers, + std::string_view body); + void handle_request(const HTTPRequest &req, std::shared_ptr socket); void start_connection(std::shared_ptr socket); void start_accept(); + + void cleanup_sockets(); void close(UnixSocket &socket); - boost::asio::io_context &io_context_; - boost::asio::local::stream_protocol::acceptor acceptor_; - std::vector> sockets_ = {}; - immer::box app_state; - HTTPServer> http = {}; + struct UnixSocketState { + boost::asio::io_context &io_context; + immer::box app_state; + boost::asio::local::stream_protocol::acceptor acceptor; + std::vector> sockets; + HTTPServer> http; + boost::asio::steady_timer sse_keepalive_timer; + }; + + std::shared_ptr state_; }; } // namespace wolf::api \ No newline at end of file diff --git a/src/moonlight-server/api/endpoints.cpp b/src/moonlight-server/api/endpoints.cpp index ba1e1a3f..d306b23e 100644 --- a/src/moonlight-server/api/endpoints.cpp +++ b/src/moonlight-server/api/endpoints.cpp @@ -3,15 +3,19 @@ namespace wolf::api { void UnixSocketServer::endpoint_Events(const HTTPRequest &req, std::shared_ptr socket) { - // curl -v --http0.9 --unix-socket /tmp/wolf.sock http://localhost/api/v1/events - sockets_.push_back(socket); + // curl -N --unix-socket /tmp/wolf.sock http://localhost/api/v1/events + state_->sockets.push_back(socket); + send_http(socket, + 200, + {{"Content-Type: text/event-stream"}, {"Connection: keep-alive"}, {"Cache-Control: no-cache"}}, + ""); // Inform clients this is going to be SSE } void UnixSocketServer::endpoint_PendingPairRequest(const HTTPRequest &req, std::shared_ptr socket) { // curl -v --http1.0 --unix-socket /tmp/wolf.sock http://localhost/api/v1/pending-pair-requests auto res = PendingPairRequestsResponse{.success = true}; auto requests = std::vector(); - for (auto [secret, pair_request] : *app_state->pairing_atom->load()) { + for (auto [secret, pair_request] : *(state_->app_state)->pairing_atom->load()) { requests.push_back({.pair_secret = secret, .pin = pair_request->client_ip}); } send_http(socket, 200, rfl::json::write(res)); @@ -21,7 +25,7 @@ void UnixSocketServer::endpoint_Pair(const HTTPRequest &req, std::shared_ptr(req.body)) { - if (auto pair_request = app_state->pairing_atom->load()->find(event.value().pair_secret)) { + if (auto pair_request = state_->app_state->pairing_atom->load()->find(event.value().pair_secret)) { pair_request->get().user_pin->set_value(event.value().pin.value()); // Resolve the promise auto res = PairResponse{.success = true}; send_http(socket, 200, rfl::json::write(res)); diff --git a/src/moonlight-server/api/unix_socket_server.cpp b/src/moonlight-server/api/unix_socket_server.cpp new file mode 100644 index 00000000..ef1b4a3b --- /dev/null +++ b/src/moonlight-server/api/unix_socket_server.cpp @@ -0,0 +1,207 @@ +#include + +namespace wolf::api { + +using namespace wolf::core; + +constexpr auto SSE_KEEPALIVE_INTERVAL = std::chrono::seconds(15); + +std::string to_str(boost::asio::streambuf &streambuf) { + return {buffers_begin(streambuf.data()), buffers_end(streambuf.data())}; +} + +UnixSocketServer::UnixSocketServer(boost::asio::io_context &io_context, + const std::string &socket_path, + immer::box app_state) { + + state_ = std::make_shared( + UnixSocketState{.io_context = io_context, + .app_state = app_state, + .acceptor = {io_context, boost::asio::local::stream_protocol::endpoint(socket_path)}, + .http = HTTPServer>{}, + .sse_keepalive_timer = boost::asio::steady_timer{io_context}}); + + state_->http.add( + HTTPMethod::GET, + "/api/v1/events", + {.summary = "Subscribe to events", + .description = + "This endpoint allows clients to subscribe to events using " + "[SSE](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events). \n", + // TODO: json_schema = rfl::json::to_schema() + .handler = [this](auto req, auto socket) { endpoint_Events(req, socket); }}); + + state_->http.add( + HTTPMethod::GET, + "/api/v1/pending-pair-requests", + { + .summary = "Get pending pair requests", + .description = "This endpoint returns a list of Moonlight clients that are currently waiting to be paired.", + .response_description = {{200, {.json_schema = rfl::json::to_schema()}}}, + .handler = [this](auto req, auto socket) { endpoint_PendingPairRequest(req, socket); }, + }); + + state_->http.add(HTTPMethod::POST, + "/api/v1/pair-client", + { + .summary = "Pair a client", + .request_description = APIDescription{.json_schema = rfl::json::to_schema()}, + .response_description = {{200, {.json_schema = rfl::json::to_schema()}}, + {500, {.json_schema = rfl::json::to_schema()}}}, + .handler = [this](auto req, auto socket) { endpoint_Pair(req, socket); }, + }); + + state_->http.add(HTTPMethod::GET, + "/api/v1/openapi-schema", + {.summary = "Return this OpenAPI schema as JSON", .handler = [this](auto req, auto socket) { + send_http(socket, 200, state_->http.openapi_schema()); + }}); + + state_->sse_keepalive_timer.async_wait([this](auto e) { sse_keepalive(e); }); + start_accept(); +} + +void UnixSocketServer::sse_keepalive(const boost::system::error_code &e) { + if (e && e.value() != boost::asio::error::operation_aborted) { + logs::log(logs::warning, "[API] Error in keepalive timer: {}", e.message()); + return; + } + cleanup_sockets(); + sse_broadcast(":keepalive\n\n"); + state_->sse_keepalive_timer.expires_from_now(SSE_KEEPALIVE_INTERVAL); + state_->sse_keepalive_timer.async_wait([this](auto e) { sse_keepalive(e); }); +} + +void UnixSocketServer::sse_broadcast(const std::string &payload) { + for (auto &socket : state_->sockets) { + boost::asio::async_write(socket->socket, + boost::asio::buffer(payload), + [this, socket](const boost::system::error_code &ec, std::size_t /*length*/) { + if (ec) { + logs::log(logs::warning, "[API] Error sending event: {}", ec.message()); + close(*socket); + } + }); + } +} + +void UnixSocketServer::broadcast_event(const std::string &event_type, const std::string &event_data) { + sse_broadcast(fmt::format("event: {}\ndata: {}\n\n", event_type, event_data)); +} + +void UnixSocketServer::cleanup_sockets() { + state_->sockets.erase(std::remove_if(state_->sockets.begin(), + state_->sockets.end(), + [](const auto &socket) { return !socket->is_alive; }), + state_->sockets.end()); +} + +void UnixSocketServer::send_http(std::shared_ptr socket, int status_code, std::string_view body) { + send_http(socket, status_code, {{"Content-Length: " + std::to_string(body.size())}}, body); +} + +void UnixSocketServer::send_http(std::shared_ptr socket, + int status_code, + const std::vector &http_headers, + std::string_view body) { + auto http_reply = fmt::format("HTTP/1.0 {} OK\r\n{}\r\n\r\n{}", status_code, fmt::join(http_headers, "\r\n"), body); + boost::asio::async_write(socket->socket, + boost::asio::buffer(http_reply), + [this, socket](const boost::system::error_code &ec, std::size_t /*length*/) { + if (ec) { + logs::log(logs::error, "[API] Error sending HTTP: {}", ec.message()); + close(*socket); + } + }); +} + +void UnixSocketServer::handle_request(const HTTPRequest &req, std::shared_ptr socket) { + logs::log(logs::debug, "[API] Received request: {} {} - {}", rfl::enum_to_string(req.method), req.path, req.body); + + if (!state_->http.handle_request(req, socket)) { + send_http(socket, 404, ""); + close(*socket); + } +} + +void UnixSocketServer::start_connection(std::shared_ptr socket) { + auto request_buf = std::make_shared(std::numeric_limits::max()); + boost::asio::async_read_until( + socket->socket, + *request_buf, + "\r\n\r\n", + [this, socket, request_buf](const boost::system::error_code &ec, std::size_t bytes_transferred) { + if (ec) { + logs::log(logs::error, "[API] Error reading request: {}", ec.message()); + close(*socket); + return; + } + HTTPRequest req = {}; + std::string method; + std::istream is(request_buf.get()); + SimpleWeb::RequestMessage::parse(is, method, req.path, req.query_string, req.http_version, req.headers); + if (method == "GET") + req.method = HTTPMethod::GET; + else if (method == "POST") + req.method = HTTPMethod::POST; + else if (method == "PUT") + req.method = HTTPMethod::PUT; + else if (method == "DELETE") + req.method = HTTPMethod::DELETE; + else + req.method = HTTPMethod::GET; + + if (req.headers.contains("Transfer-Encoding") && req.headers.find("Transfer-Encoding")->second == "chunked") { + logs::log(logs::error, "[API] Chunked encoding not supported, use HTTP/1.0 instead"); + send_http(socket, 500, "Chunked encoding not supported, use HTTP/1.0 instead"); + return; + } + + // Get the body payload + if (req.headers.contains("Content-Length")) { + auto content_length = std::stoul(req.headers.find("Content-Length")->second); + std::size_t num_additional_bytes = request_buf->size() - bytes_transferred; + if (content_length > num_additional_bytes) { + boost::asio::async_read(socket->socket, + *request_buf, + boost::asio::transfer_exactly(content_length - bytes_transferred), + [this, socket, request_buf, req = std::make_unique(req)]( + const boost::system::error_code &ec, + std::size_t /*bytes_transferred*/) { + if (ec) { + logs::log(logs::error, "[API] Error reading request body: {}", ec.message()); + close(*socket); + return; + } + req->body = to_str(*request_buf); + handle_request(*req, socket); + }); + } else { + req.body = to_str(*request_buf); + handle_request(req, socket); + } + } else { + handle_request(req, socket); + } + }); +} + +void UnixSocketServer::start_accept() { + auto socket = std::make_shared( + UnixSocket{.socket = boost::asio::local::stream_protocol::socket(state_->io_context)}); + state_->acceptor.async_accept(socket->socket, [this, socket](const boost::system::error_code &ec) { + if (!ec) { + start_accept(); // Immediately start accepting a new connection + start_connection(socket); // Start reading the request from the new connection + } else { + logs::log(logs::error, "[API] Error accepting connection: {}", ec.message()); + close(*socket); + } + }); +} + +void UnixSocketServer::close(UnixSocket &socket) { + socket.socket.close(); + socket.is_alive = false; +} +} // namespace wolf::api \ No newline at end of file diff --git a/src/moonlight-server/control/control.cpp b/src/moonlight-server/control/control.cpp index d08fa26d..bb6883e3 100644 --- a/src/moonlight-server/control/control.cpp +++ b/src/moonlight-server/control/control.cpp @@ -173,10 +173,9 @@ void run_control(int port, immer::box(PauseStreamEvent{.session_id = client_session->session_id})); } else if (sub_type == INPUT_DATA) { handle_input(client_session.value(), connected_clients, (INPUT_PKT *)decrypted.data()); - } else { - auto ev = - ControlEvent{.session_id = client_session->session_id, .type = sub_type, .raw_packet = decrypted}; - event_bus->fire_event(immer::box{ev}); + } else if (sub_type == IDR_FRAME) { + auto ev = IDRRequestEvent{.session_id = client_session->session_id}; + event_bus->fire_event(immer::box{ev}); } } catch (std::runtime_error &e) { logs::log(logs::warning, "[ENET] Unable to decrypt incoming packet: {}", e.what()); diff --git a/src/moonlight-server/events/events.hpp b/src/moonlight-server/events/events.hpp index 7b516fbf..6e33162d 100644 --- a/src/moonlight-server/events/events.hpp +++ b/src/moonlight-server/events/events.hpp @@ -104,7 +104,7 @@ struct VideoSession { std::size_t session_id; std::uint16_t port; - std::chrono::milliseconds timeout; + int timeout_ms; int packet_size; int frames_with_invalid_ref_threshold; @@ -138,18 +138,12 @@ struct AudioSession { wolf::core::audio::AudioMode audio_mode; }; -/** - * Events received in the ENET Control Session - * TODO: break this down into more meaningful events - */ -struct ControlEvent { - const std::string event_type = "control"; + +struct IDRRequestEvent { + const std::string event_type = "idr_request"; // A unique ID that identifies this session std::size_t session_id; - - moonlight::control::pkts::PACKET_TYPE type; - std::string_view raw_packet; }; struct PauseStreamEvent { @@ -187,7 +181,7 @@ using EventBusHandlers = dp::handler_registration, immer::box, immer::box, immer::box, - immer::box, + immer::box, immer::box, immer::box, immer::box, @@ -199,7 +193,7 @@ using EventBusType = dp::event_bus, immer::box, immer::box, immer::box, - immer::box, + immer::box, immer::box, immer::box, immer::box, @@ -211,7 +205,7 @@ using EventsVariant = std::variant, immer::box, immer::box, immer::box, - immer::box, + immer::box, immer::box, immer::box, immer::box, diff --git a/src/moonlight-server/events/reflectors.hpp b/src/moonlight-server/events/reflectors.hpp index 9bb8ae41..49d76379 100644 --- a/src/moonlight-server/events/reflectors.hpp +++ b/src/moonlight-server/events/reflectors.hpp @@ -25,34 +25,6 @@ template <> struct Reflector { } }; -template <> struct Reflector { - struct ReflType { - int64_t milliseconds; - }; - - static std::chrono::milliseconds to(const ReflType &v) noexcept { - return std::chrono::milliseconds(v.milliseconds); - } - - static ReflType from(const std::chrono::milliseconds &v) { - return {.milliseconds = v.count()}; - } -}; - -template <> struct Reflector { - struct ReflType { - int speakers; - }; - - static wolf::core::audio::AudioMode::Speakers to(const ReflType &v) noexcept { - return static_cast(v.speakers); - } - - static ReflType from(const wolf::core::audio::AudioMode::Speakers &v) { - return {.speakers = static_cast(v)}; - } -}; - template <> struct Reflector { struct ReflType { moonlight::App base; @@ -141,21 +113,4 @@ template <> struct Reflector { } }; -template <> struct Reflector { - struct ReflType { - const std::string event_type = "control_event"; - std::size_t session_id; - int type; - }; - - static events::ControlEvent to(const ReflType &v) noexcept { - return {.session_id = v.session_id, .type = static_cast(v.type)}; - } - - // TODO: this doesn't make sense without raw_packet - static ReflType from(const events::ControlEvent &v) { - return {.session_id = v.session_id, .type = static_cast(v.type)}; - } -}; - } // namespace rfl \ No newline at end of file diff --git a/src/moonlight-server/rtsp/commands.hpp b/src/moonlight-server/rtsp/commands.hpp index e07486d9..c2a99fc6 100644 --- a/src/moonlight-server/rtsp/commands.hpp +++ b/src/moonlight-server/rtsp/commands.hpp @@ -58,24 +58,24 @@ describe(const RTSP_PACKET &req, const events::StreamSession &session) { if (audio_mode.channels == 6) { // 5.1 mapping_p = { // The mapping for 5.1 is: [0 1 4 5 2 3] - AudioMode::FRONT_LEFT, - AudioMode::FRONT_RIGHT, - AudioMode::BACK_LEFT, - AudioMode::BACK_RIGHT, - AudioMode::FRONT_CENTER, - AudioMode::LOW_FREQUENCY, + AudioMode::Speakers::FRONT_LEFT, + AudioMode::Speakers::FRONT_RIGHT, + AudioMode::Speakers::BACK_LEFT, + AudioMode::Speakers::BACK_RIGHT, + AudioMode::Speakers::FRONT_CENTER, + AudioMode::Speakers::LOW_FREQUENCY, }; } else if (audio_mode.channels == 8) { // 7.1 mapping_p = { // The mapping for 7.1 is: [0 1 4 5 2 3 6 7] - AudioMode::FRONT_LEFT, - AudioMode::FRONT_RIGHT, - AudioMode::BACK_LEFT, - AudioMode::BACK_RIGHT, - AudioMode::FRONT_CENTER, - AudioMode::LOW_FREQUENCY, - AudioMode::SIDE_LEFT, - AudioMode::SIDE_RIGHT, + AudioMode::Speakers::FRONT_LEFT, + AudioMode::Speakers::FRONT_RIGHT, + AudioMode::Speakers::BACK_LEFT, + AudioMode::Speakers::BACK_RIGHT, + AudioMode::Speakers::FRONT_CENTER, + AudioMode::Speakers::LOW_FREQUENCY, + AudioMode::Speakers::SIDE_LEFT, + AudioMode::Speakers::SIDE_RIGHT, }; } @@ -87,9 +87,10 @@ describe(const RTSP_PACKET &req, const events::StreamSession &session) { if (audio_mode.channels > 2) { // 5.1 and 7.1 std::rotate(mapping_p.begin() + 3, mapping_p.begin() + 4, mapping_p.end()); } - std::string audio_speakers = mapping_p // - | views::transform([](auto speaker) { return (char)(speaker + '0'); }) // - | to; + std::string audio_speakers = + mapping_p // + | views::transform([](auto speaker) { return (char)(static_cast(speaker) + '0'); }) // + | to; auto surround_params = fmt::format("fmtp:97 surround-params={}{}{}{}", audio_mode.channels, audio_mode.streams, @@ -217,7 +218,7 @@ announce(const RTSP_PACKET &req, const events::StreamSession &session) { .session_id = session.session_id, .port = session.video_stream_port, - .timeout = std::chrono::milliseconds(args["x-nv-video[0].timeoutLengthMs"].value_or(7000)), + .timeout_ms = args["x-nv-video[0].timeoutLengthMs"].value_or(7000), .packet_size = args["x-nv-video[0].packetSize"].value_or(1024), .frames_with_invalid_ref_threshold = args["x-nv-video[0].framesWithInvalidRefThreshold"].value_or(0), .fec_percentage = fec_percentage, diff --git a/src/moonlight-server/state/configTOML.cpp b/src/moonlight-server/state/configTOML.cpp index 5e49b77e..bb936113 100644 --- a/src/moonlight-server/state/configTOML.cpp +++ b/src/moonlight-server/state/configTOML.cpp @@ -249,9 +249,9 @@ Config load_or_default(const std::string &source, const std::shared_ptr{client}; }) // | ranges::to>>(); - auto default_h264 = utils::get_optional(default_gst_encoder_settings, h264_encoder->plugin_name); - auto default_hevc = utils::get_optional(default_gst_encoder_settings, hevc_encoder->plugin_name); - auto default_av1 = utils::get_optional(default_gst_encoder_settings, av1_encoder->plugin_name); + auto default_h264 = utils::get_optional(default_gst_encoder_settings, h264_encoder.value_or(GstEncoder{}).plugin_name); + auto default_hevc = utils::get_optional(default_gst_encoder_settings, hevc_encoder.value_or(GstEncoder{}).plugin_name); + auto default_av1 = utils::get_optional(default_gst_encoder_settings, av1_encoder.value_or(GstEncoder{}).plugin_name); /* Get apps, here we'll merge the default gstreamer settings with the app specific overrides */ auto cfg_apps = toml::find>(cfg, "apps"); diff --git a/src/moonlight-server/state/data-structures.hpp b/src/moonlight-server/state/data-structures.hpp index 3988fdde..3b7a6853 100644 --- a/src/moonlight-server/state/data-structures.hpp +++ b/src/moonlight-server/state/data-structures.hpp @@ -152,31 +152,31 @@ const static immer::array AUDIO_CONFIGURATIONS = { {.channels = 2, .streams = 1, .coupled_streams = 1, - .speakers = {audio::AudioMode::FRONT_LEFT, audio::AudioMode::FRONT_RIGHT}, + .speakers = {audio::AudioMode::Speakers::FRONT_LEFT, audio::AudioMode::Speakers::FRONT_RIGHT}, .bitrate = 96000}, // 5.1 {.channels = 6, .streams = 4, .coupled_streams = 2, - .speakers = {audio::AudioMode::FRONT_LEFT, - audio::AudioMode::FRONT_RIGHT, - audio::AudioMode::FRONT_CENTER, - audio::AudioMode::LOW_FREQUENCY, - audio::AudioMode::BACK_LEFT, - audio::AudioMode::BACK_RIGHT}, + .speakers = {audio::AudioMode::Speakers::FRONT_LEFT, + audio::AudioMode::Speakers::FRONT_RIGHT, + audio::AudioMode::Speakers::FRONT_CENTER, + audio::AudioMode::Speakers::LOW_FREQUENCY, + audio::AudioMode::Speakers::BACK_LEFT, + audio::AudioMode::Speakers::BACK_RIGHT}, .bitrate = 256000}, // 7.1 {.channels = 8, .streams = 5, .coupled_streams = 3, - .speakers = {audio::AudioMode::FRONT_LEFT, - audio::AudioMode::FRONT_RIGHT, - audio::AudioMode::FRONT_CENTER, - audio::AudioMode::LOW_FREQUENCY, - audio::AudioMode::BACK_LEFT, - audio::AudioMode::BACK_RIGHT, - audio::AudioMode::SIDE_LEFT, - audio::AudioMode::SIDE_RIGHT}, + .speakers = {audio::AudioMode::Speakers::FRONT_LEFT, + audio::AudioMode::Speakers::FRONT_RIGHT, + audio::AudioMode::Speakers::FRONT_CENTER, + audio::AudioMode::Speakers::LOW_FREQUENCY, + audio::AudioMode::Speakers::BACK_LEFT, + audio::AudioMode::Speakers::BACK_RIGHT, + audio::AudioMode::Speakers::SIDE_LEFT, + audio::AudioMode::Speakers::SIDE_RIGHT}, .bitrate = 450000}}}; static const audio::AudioMode &get_audio_mode(int channels, bool high_quality) { diff --git a/src/moonlight-server/streaming/streaming.cpp b/src/moonlight-server/streaming/streaming.cpp index 7766de32..99f507bf 100644 --- a/src/moonlight-server/streaming/streaming.cpp +++ b/src/moonlight-server/streaming/streaming.cpp @@ -158,17 +158,15 @@ void start_streaming_video(const immer::box &video_session * We have to pass this back into the gstreamer pipeline * in order to force the encoder to produce a new IDR packet */ - auto idr_handler = event_bus->register_handler>( - [sess_id = video_session->session_id, pipeline](const immer::box &ctrl_ev) { + auto idr_handler = event_bus->register_handler>( + [sess_id = video_session->session_id, pipeline](const immer::box &ctrl_ev) { if (ctrl_ev->session_id == sess_id) { - if (ctrl_ev->type == moonlight::control::pkts::IDR_FRAME) { - logs::log(logs::debug, "[GSTREAMER] Forcing IDR"); - // Force IDR event, see: https://github.com/centricular/gstwebrtc-demos/issues/186 - // https://gstreamer.freedesktop.org/documentation/additional/design/keyframe-force.html?gi-language=c - wolf::core::gstreamer::send_message( - pipeline.get(), - gst_structure_new("GstForceKeyUnit", "all-headers", G_TYPE_BOOLEAN, TRUE, NULL)); - } + logs::log(logs::debug, "[GSTREAMER] Forcing IDR"); + // Force IDR event, see: https://github.com/centricular/gstwebrtc-demos/issues/186 + // https://gstreamer.freedesktop.org/documentation/additional/design/keyframe-force.html?gi-language=c + wolf::core::gstreamer::send_message( + pipeline.get(), + gst_structure_new("GstForceKeyUnit", "all-headers", G_TYPE_BOOLEAN, TRUE, NULL)); } });