验证本地http可以查看webrtc.

This commit is contained in:
2025-03-17 20:52:00 +08:00
parent 70763da9b0
commit ee1aaa1bf3
23 changed files with 1131 additions and 39 deletions

View File

@ -0,0 +1,41 @@
#include "SignalServer.h"
#include "../Application.h"
#include "../HttpSession.h"
#include "Core/Logger.h"
#include "WebSocketSignalSession.h"
#include <boost/beast/websocket/rfc6455.hpp>
namespace Danki {
SignalServer::SignalServer(Application &app) {
using namespace boost::urls;
// clang-format off
app.insertUrl("/api/v1/webrtc/signal/{id}", [this](HttpSession &session, const Application::Request &request, const matches &matches) {
auto id = matches.at("id");
if (boost::beast::websocket::is_upgrade(request)) {
auto ws = std::make_shared<WebSocketSignalSession>(session.releaseSocket(), *this, id);
ws->run(request);
} else {
LOG(error) << "webrtc client[" << id << "] not upgrade connection, request: " << std::endl << request;
}
});
// clang-format on
}
void SignalServer::join(const std::string &id, WebSocketSignalSession *client) {
m_clients.insert({id, client});
}
void SignalServer::leave(const std::string &id) {
if (m_clients.count(id) > 0) {
m_clients.erase(id);
}
}
WebSocketSignalSession *SignalServer::client(const std::string &id) {
WebSocketSignalSession *ret = nullptr;
if (m_clients.count(id) > 0) {
ret = m_clients.at(id);
}
return ret;
}
} // namespace Danki

View File

@ -0,0 +1,24 @@
#ifndef __SIGNALSERVER_H__
#define __SIGNALSERVER_H__
#include <string>
#include <unordered_map>
namespace Danki {
class Application;
class WebSocketSignalSession;
class SignalServer {
public:
SignalServer(Application &app);
void join(const std::string &id, WebSocketSignalSession *client);
void leave(const std::string &id);
WebSocketSignalSession *client(const std::string &id);
private:
std::unordered_map<std::string, WebSocketSignalSession *> m_clients;
};
} // namespace Danki
#endif // __SIGNALSERVER_H__

View File

@ -7,6 +7,8 @@
#include <boost/json/serialize.hpp>
#include <rtc/rtc.hpp>
namespace Danki {
class WebRTCStreamerPrivate {
public:
WebRTCStreamerPrivate(boost::asio::io_context &ioContext) : strand{ioContext.get_executor()} {
@ -135,7 +137,13 @@ void Streamer::start(const std::string &signalServerAddress, uint16_t signalServ
m_d->websocket = std::make_shared<rtc::WebSocket>(c);
m_d->websocket->onOpen([]() { LOG(info) << "WebSocket connected, signaling ready"; });
m_d->websocket->onClosed([]() { LOG(info) << "WebSocket closed"; });
m_d->websocket->onError([](const std::string &error) { LOG(error) << "WebSocket failed: " << error; });
m_d->websocket->onError([this, signalServerAddress, signalServerPort](const std::string &error) {
LOG(error) << "WebSocket failed: " << error;
boost::asio::post(m_d->strand, [this, signalServerAddress, signalServerPort]() {
start(signalServerAddress, signalServerPort);
});
});
m_d->websocket->onMessage([this](std::variant<rtc::binary, std::string> data) {
if (!std::holds_alternative<std::string>(data)) return;
@ -147,7 +155,7 @@ void Streamer::start(const std::string &signalServerAddress, uint16_t signalServ
});
const std::string url =
"wss://" + signalServerAddress + ":" + std::to_string(signalServerPort) + "/api/v1/webrtc/signal/" + localId;
"ws://" + signalServerAddress + ":" + std::to_string(signalServerPort) + "/api/v1/webrtc/signal/" + localId;
LOG(info) << "URL is " << url;
m_d->websocket->open(url);
LOG(info) << "Waiting for signaling to be connected...";
@ -175,7 +183,7 @@ void Streamer::push(const uint8_t *data, uint32_t size) {
}
Streamer::Streamer(boost::asio::io_context &ioContext) : m_d{new WebRTCStreamerPrivate(ioContext)} {
rtc::InitLogger(rtc::LogLevel::Debug);
rtc::InitLogger(rtc::LogLevel::Info);
std::string stunServer = "stun:amass.fun:5349"; // ssl
m_d->configuration.iceServers.emplace_back(stunServer);
LOG(info) << "STUN server is " << stunServer;
@ -185,3 +193,4 @@ Streamer::Streamer(boost::asio::io_context &ioContext) : m_d{new WebRTCStreamerP
m_d->configuration.disableAutoNegotiation = true;
}
}

View File

@ -9,6 +9,8 @@ namespace asio {
class io_context;
}
} // namespace boost
namespace Danki {
class WebRTCStreamerPrivate;
class Streamer {
@ -21,5 +23,6 @@ public:
private:
WebRTCStreamerPrivate *m_d = nullptr;
};
} // namespace Danki
#endif // __WEBRTCSTREAMER_H__

View File

@ -0,0 +1,109 @@
#include "WebSocketSignalSession.h"
#include "Core/Logger.h"
#include "SignalServer.h"
#include <boost/json/parse.hpp>
#include <boost/json/serialize.hpp>
#include <boost/scope/scope_exit.hpp>
namespace Danki {
WebSocketSignalSession::WebSocketSignalSession(boost::asio::ip::tcp::socket &&socket, SignalServer &server,
const std::string &id)
: m_ws(std::move(socket)), m_server(server), m_id(id) {
m_server.join(m_id, this);
}
WebSocketSignalSession::~WebSocketSignalSession() {
m_server.leave(m_id);
}
void WebSocketSignalSession::onAccept(boost::beast::error_code ec) {
if (ec) {
if (ec == boost::asio::error::operation_aborted || ec == boost::beast::websocket::error::closed) return;
LOG(error) << "accept: " << ec.message() << "\n";
return;
}
LOG(info) << "accept websocket target: " << m_target << ", id: " << m_id;
// Read a message
m_ws.async_read(m_buffer, boost::beast::bind_front_handler(&WebSocketSignalSession::onRead, shared_from_this()));
}
void WebSocketSignalSession::onRead(const boost::beast::error_code &error, std::size_t bytesTransferred) {
if (error) {
if (error == boost::asio::error::operation_aborted || error == boost::beast::websocket::error::closed) return;
LOG(error) << error << ": " << error.message();
return;
}
boost::scope::scope_exit raii([this] {
m_buffer.consume(m_buffer.size()); // Clear the buffer
m_ws.async_read(m_buffer,
boost::beast::bind_front_handler(&WebSocketSignalSession::onRead, shared_from_this()));
});
if (!m_ws.got_text()) {
LOG(warning) << "current not supported binary message.";
return;
}
auto message = boost::beast::buffers_to_string(m_buffer.data());
auto rootObject = boost::json::parse(message);
auto &root = rootObject.as_object();
if (root.contains("id")) {
if (!root.at("id").is_string()) {
LOG(warning) << "wrong format.";
m_ws.close(boost::beast::websocket::close_code::normal);
return;
}
auto destinationId = std::string(root["id"].as_string());
auto destination = m_server.client(destinationId);
if (destination == nullptr) {
LOG(info) << "client " << destinationId << " not found.";
} else {
root["id"] = m_id;
auto reply = std::make_shared<std::string>(boost::json::serialize(root));
destination->send(reply);
}
LOG(info) << message;
} else if (root.contains("type")) {
auto &type = root.at("type").as_string();
if (type == "ping") {
boost::json::object object;
object["type"] = "pong";
send(boost::json::serialize(object));
}
} else {
LOG(info) << message;
}
}
void WebSocketSignalSession::send(const std::shared_ptr<std::string> &ss) {
boost::asio::post(m_ws.get_executor(), [ptr = weak_from_this(), ss]() {
if (ptr.expired()) return;
auto self = ptr.lock();
self->m_queue.push_back(ss);
if (self->m_queue.size() > 1) return; // 之前已经有发送了
self->m_ws.text();
self->m_ws.async_write(
boost::asio::buffer(*self->m_queue.front()),
boost::beast::bind_front_handler(&WebSocketSignalSession::on_write, self->shared_from_this()));
});
}
void WebSocketSignalSession::send(std::string &&message) {
return send(std::make_shared<std::string>(std::move(message)));
}
void WebSocketSignalSession::on_write(boost::beast::error_code ec, std::size_t) {
if (ec) {
// Don't report these
if (ec == boost::asio::error::operation_aborted || ec == boost::beast::websocket::error::closed) return;
std::cerr << "write: " << ec.message() << "\n";
return;
}
// Remove the string from the queue
m_queue.erase(m_queue.begin());
if (!m_queue.empty())
m_ws.async_write(boost::asio::buffer(*m_queue.front()),
boost::beast::bind_front_handler(&WebSocketSignalSession::on_write, shared_from_this()));
}
} // namespace Danki

View File

@ -0,0 +1,58 @@
#ifndef __WEBSOCKETSIGNALSESSION_H__
#define __WEBSOCKETSIGNALSESSION_H__
#include <boost/beast.hpp>
#include <cstdlib>
#include <memory>
#include <string>
#include <vector>
namespace Danki {
class SignalServer;
/**
* @brief Represents an active WebSocket connection to the server
*/
class WebSocketSignalSession : public std::enable_shared_from_this<WebSocketSignalSession> {
public:
WebSocketSignalSession(boost::asio::ip::tcp::socket &&socket, SignalServer &server, const std::string &id);
~WebSocketSignalSession();
template <class Body, class Allocator>
void run(boost::beast::http::request<Body, boost::beast::http::basic_fields<Allocator>> request) {
using namespace boost::beast::http;
using namespace boost::beast::websocket;
// Set suggested timeout settings for the websocket
m_ws.set_option(stream_base::timeout::suggested(boost::beast::role_type::server));
m_target = request.target();
// Set a decorator to change the Server of the handshake
m_ws.set_option(stream_base::decorator([](response_type &response) {
response.set(field::server, std::string(BOOST_BEAST_VERSION_STRING) + " websocket-chat-multi");
}));
// LOG(info) << request.base().target(); //get path
// Accept the websocket handshake
m_ws.async_accept(request, [self{shared_from_this()}](boost::beast::error_code ec) { self->onAccept(ec); });
}
// Send a message
void send(const std::shared_ptr<std::string> &ss);
void send(std::string &&message);
protected:
void onAccept(boost::beast::error_code ec);
void onRead(const boost::beast::error_code &error, std::size_t bytesTransferred);
void on_write(boost::beast::error_code ec, std::size_t bytes_transferred);
private:
boost::beast::websocket::stream<boost::beast::tcp_stream> m_ws;
SignalServer &m_server;
std::string m_id;
std::string m_target;
boost::beast::flat_buffer m_buffer;
std::vector<std::shared_ptr<std::string>> m_queue;
};
} // namespace Danki
#endif // __WEBSOCKETSIGNALSESSION_H__