summaryrefslogtreecommitdiff
path: root/src/WebRTCLibPeerConnection.cpp
diff options
context:
space:
mode:
authorFabio Alessandrelli <fabio.alessandrelli@gmail.com>2021-09-22 23:16:23 +0200
committerFabio Alessandrelli <fabio.alessandrelli@gmail.com>2022-06-15 21:38:12 +0200
commite19b3701260d4a855736c623b8c8a270413f041e (patch)
tree905bce590cbc44c4077defa52276bac245c22a2a /src/WebRTCLibPeerConnection.cpp
parent8c18112f5dcf96bf24fab14ab4470c00ab7f7f70 (diff)
downloadfork-godot-webrtc-native-e19b3701260d4a855736c623b8c8a270413f041e.tar.gz
fork-godot-webrtc-native-e19b3701260d4a855736c623b8c8a270413f041e.tar.bz2
fork-godot-webrtc-native-e19b3701260d4a855736c623b8c8a270413f041e.zip
Use libdatachannel library, add Godot 4 support.
Diffstat (limited to 'src/WebRTCLibPeerConnection.cpp')
-rw-r--r--src/WebRTCLibPeerConnection.cpp382
1 files changed, 192 insertions, 190 deletions
diff --git a/src/WebRTCLibPeerConnection.cpp b/src/WebRTCLibPeerConnection.cpp
index d090654..cda018a 100644
--- a/src/WebRTCLibPeerConnection.cpp
+++ b/src/WebRTCLibPeerConnection.cpp
@@ -29,214 +29,201 @@
/*************************************************************************/
#include "WebRTCLibPeerConnection.hpp"
-#include "WebRTCDataChannel.hpp"
-#include "WebRTCDataChannelGDNative.hpp"
#include "WebRTCLibDataChannel.hpp"
+using namespace godot;
using namespace godot_webrtc;
-std::unique_ptr<rtc::Thread> WebRTCLibPeerConnection::signaling_thread = nullptr;
-
-// PeerConnectionObserver
-void WebRTCLibPeerConnection::GodotPCO::OnIceCandidate(const webrtc::IceCandidateInterface *candidate) {
- godot::Dictionary candidateSDP;
- godot::String candidateSdpMidName = candidate->sdp_mid().c_str();
- int candidateSdpMlineIndexName = candidate->sdp_mline_index();
- std::string sdp;
- candidate->ToString(&sdp);
- godot::String candidateSdpName = sdp.c_str();
- parent->queue_signal("ice_candidate_created", 3, candidateSdpMidName, candidateSdpMlineIndexName, candidateSdpName);
-}
+#ifdef GDNATIVE_WEBRTC
+struct CastableError {
+ godot::Error err_enum;
+ int64_t err_int;
-// SetSessionDescriptionObserver
-void WebRTCLibPeerConnection::GodotSSDO::OnSuccess() {
- if (make_offer) {
- make_offer = false;
- parent->peer_connection->CreateAnswer(parent->ptr_csdo, webrtc::PeerConnectionInterface::RTCOfferAnswerOptions());
+ operator int64_t() { return err_int; }
+ operator godot::Error() { return err_enum; }
+ CastableError(godot::Error p_enum, int64_t p_int) {
+ err_enum = p_enum;
+ err_int = p_int;
}
-}
-
-// CreateSessionDescriptionObserver
-void WebRTCLibPeerConnection::GodotCSDO::OnSuccess(webrtc::SessionDescriptionInterface *desc) {
- // serialize this offer and send it to the remote peer:
- std::string sdp;
- desc->ToString(&sdp);
- parent->queue_signal("session_description_created", 2, desc->type().c_str(), sdp.c_str());
-}
+};
+#define MKERR(m_err) CastableError(godot::Error::m_err, GODOT_##m_err)
+#define OK MKERR(OK)
+#define FAILED MKERR(FAILED)
+#define ERR_UNCONFIGURED MKERR(ERR_UNCONFIGURED)
+#define ERR_UNAVAILABLE MKERR(ERR_UNAVAILABLE)
+#define ERR_INVALID_PARAMETER MKERR(ERR_INVALID_PARAMETER)
+#define ERR_BUG MKERR(ERR_BUG)
+#endif
void WebRTCLibPeerConnection::initialize_signaling() {
- if (signaling_thread.get() == nullptr) {
- signaling_thread = rtc::Thread::Create();
- }
- signaling_thread->Start();
+#ifdef DEBUG_ENABLED
+ rtc::InitLogger(rtc::LogLevel::Debug);
+#endif
}
void WebRTCLibPeerConnection::deinitialize_signaling() {
- if (signaling_thread.get() != nullptr) {
- signaling_thread->Stop();
- }
}
-godot_error _parse_ice_server(webrtc::PeerConnectionInterface::RTCConfiguration &r_config, godot::Dictionary p_server) {
- godot::Variant v;
- webrtc::PeerConnectionInterface::IceServer ice_server;
- godot::String url;
-
- ERR_FAIL_COND_V(!p_server.has("urls"), GODOT_ERR_INVALID_PARAMETER);
+Error WebRTCLibPeerConnection::_parse_ice_server(rtc::Configuration &r_config, Dictionary p_server) {
+ ERR_FAIL_COND_V(!p_server.has("urls"), ERR_INVALID_PARAMETER);
// Parse mandatory URL
- v = p_server["urls"];
- if (v.get_type() == godot::Variant::STRING) {
- url = v;
- ice_server.urls.push_back(url.utf8().get_data());
- } else if (v.get_type() == godot::Variant::ARRAY) {
- godot::Array names = v;
- for (int j = 0; j < names.size(); j++) {
- v = names[j];
- ERR_FAIL_COND_V(v.get_type() != godot::Variant::STRING, GODOT_ERR_INVALID_PARAMETER);
- url = v;
- ice_server.urls.push_back(url.utf8().get_data());
- }
+ Array urls;
+ Variant urls_var = p_server["urls"];
+ if (urls_var.get_type() == Variant::STRING) {
+ urls.push_back(urls_var);
+ } else if (urls_var.get_type() == Variant::ARRAY) {
+ urls = urls_var;
} else {
- ERR_FAIL_V(GODOT_ERR_INVALID_PARAMETER);
+ ERR_FAIL_V(ERR_INVALID_PARAMETER);
}
// Parse credentials (only meaningful for TURN, only support password)
- if (p_server.has("username") && (v = p_server["username"]) && v.get_type() == godot::Variant::STRING) {
- ice_server.username = (v.operator godot::String()).utf8().get_data();
+ String username;
+ String credential;
+ if (p_server.has("username") && p_server["username"].get_type() == Variant::STRING) {
+ username = p_server["username"];
}
- if (p_server.has("credential") && (v = p_server["credential"]) && v.get_type() == godot::Variant::STRING) {
- ice_server.password = (v.operator godot::String()).utf8().get_data();
+ if (p_server.has("credential") && p_server["credential"].get_type() == Variant::STRING) {
+ credential = p_server["credential"];
}
-
- r_config.servers.push_back(ice_server);
- return GODOT_OK;
+ for (int i = 0; i < urls.size(); i++) {
+ rtc::IceServer srv(urls[i].operator String().utf8().get_data());
+ srv.username = username.utf8().get_data();
+ srv.password = credential.utf8().get_data();
+ r_config.iceServers.push_back(srv);
+ }
+ return OK;
}
-godot_error _parse_channel_config(webrtc::DataChannelInit &r_config, godot::Dictionary p_dict) {
- godot::Variant v;
-#define _SET_N(PROP, PNAME, TYPE) \
- if (p_dict.has(#PROP)) { \
- v = p_dict[#PROP]; \
- if (v.get_type() == godot::Variant::TYPE) \
- r_config.PNAME = v; \
+Error WebRTCLibPeerConnection::_parse_channel_config(rtc::DataChannelInit &r_config, const Dictionary &p_dict) {
+ Variant nil;
+ Variant v;
+ if (p_dict.has("negotiated")) {
+ r_config.negotiated = p_dict["negotiated"].operator bool();
}
-#define _SET(PROP, TYPE) _SET_N(PROP, PROP, TYPE)
- _SET(negotiated, BOOL);
- _SET(id, INT);
- _SET_N(maxPacketLifeTime, maxRetransmitTime, INT);
- _SET(maxRetransmits, INT);
- _SET(ordered, BOOL);
-#undef _SET
- if (p_dict.has("protocol") && (v = p_dict["protocol"]) && v.get_type() == godot::Variant::STRING) {
- r_config.protocol = v.operator godot::String().utf8().get_data();
+ if (p_dict.has("id")) {
+ r_config.id = uint16_t(p_dict["id"].operator int32_t());
}
-
- // ID makes sense only when negotiated is true (and must be set in that case)
- ERR_FAIL_COND_V(r_config.negotiated ? r_config.id == -1 : r_config.id != -1, GODOT_ERR_INVALID_PARAMETER);
- // Only one of maxRetransmits and maxRetransmitTime can be set on a channel.
- ERR_FAIL_COND_V(r_config.maxRetransmits && r_config.maxRetransmitTime, GODOT_ERR_INVALID_PARAMETER);
- return GODOT_OK;
+ // If negotiated it must have an ID, and ID only makes sense when negotiated.
+ ERR_FAIL_COND_V(r_config.negotiated != r_config.id.has_value(), ERR_INVALID_PARAMETER);
+ // Channels cannot be both time-constrained and retry-constrained.
+ ERR_FAIL_COND_V(p_dict.has("maxPacketLifeTime") && p_dict.has("maxRetransmits"), ERR_INVALID_PARAMETER);
+ if (p_dict.has("maxPacketLifeTime")) {
+ r_config.reliability.type = rtc::Reliability::Type::Timed;
+ r_config.reliability.rexmit = std::chrono::milliseconds(p_dict["maxPacketLifeTime"].operator int32_t());
+ } else if (p_dict.has("maxRetransmits")) {
+ r_config.reliability.type = rtc::Reliability::Type::Rexmit;
+ r_config.reliability.rexmit = p_dict["maxRetransmits"].operator int32_t();
+ }
+ if (p_dict.has("ordered") && p_dict["ordered"].operator bool() == false) {
+ r_config.reliability.unordered = true;
+ }
+ if (p_dict.has("protocol")) {
+ r_config.protocol = p_dict["protocol"].operator String().utf8().get_data();
+ }
+ return OK;
}
-WebRTCLibPeerConnection::ConnectionState WebRTCLibPeerConnection::get_connection_state() const {
- ERR_FAIL_COND_V(peer_connection.get() == nullptr, STATE_CLOSED);
+int64_t WebRTCLibPeerConnection::_get_connection_state() const {
+ ERR_FAIL_COND_V(peer_connection == nullptr, STATE_CLOSED);
- webrtc::PeerConnectionInterface::IceConnectionState state = peer_connection->ice_connection_state();
+ rtc::PeerConnection::State state = peer_connection->state();
switch (state) {
- case webrtc::PeerConnectionInterface::kIceConnectionNew:
+ case rtc::PeerConnection::State::New:
return STATE_NEW;
- case webrtc::PeerConnectionInterface::kIceConnectionChecking:
+ case rtc::PeerConnection::State::Connecting:
return STATE_CONNECTING;
- case webrtc::PeerConnectionInterface::kIceConnectionConnected:
+ case rtc::PeerConnection::State::Connected:
return STATE_CONNECTED;
- case webrtc::PeerConnectionInterface::kIceConnectionCompleted:
- return STATE_CONNECTED;
- case webrtc::PeerConnectionInterface::kIceConnectionFailed:
- return STATE_FAILED;
- case webrtc::PeerConnectionInterface::kIceConnectionDisconnected:
+ case rtc::PeerConnection::State::Disconnected:
return STATE_DISCONNECTED;
- case webrtc::PeerConnectionInterface::kIceConnectionClosed:
- return STATE_CLOSED;
+ case rtc::PeerConnection::State::Failed:
+ return STATE_FAILED;
default:
return STATE_CLOSED;
}
}
-godot_error WebRTCLibPeerConnection::initialize(const godot_dictionary *p_config) {
- webrtc::PeerConnectionInterface::RTCConfiguration config;
- godot::Dictionary d = *(godot::Dictionary *)p_config;
- godot::Variant v;
- if (d.has("iceServers") && (v = d["iceServers"]) && v.get_type() == godot::Variant::ARRAY) {
- godot::Array servers = v;
+int64_t WebRTCLibPeerConnection::_initialize(const Dictionary &p_config) {
+ rtc::Configuration config = {};
+ if (p_config.has("iceServers") && p_config["iceServers"].get_type() == Variant::ARRAY) {
+ Array servers = p_config["iceServers"];
for (int i = 0; i < servers.size(); i++) {
- v = servers[i];
- ERR_FAIL_COND_V(v.get_type() != godot::Variant::DICTIONARY, GODOT_ERR_INVALID_PARAMETER);
- godot_error err;
- godot::Dictionary server = v;
- err = _parse_ice_server(config, server);
- ERR_FAIL_COND_V(err != GODOT_OK, err);
+ ERR_FAIL_COND_V(servers[i].get_type() != Variant::DICTIONARY, ERR_INVALID_PARAMETER);
+ Dictionary server = servers[i];
+ Error err = _parse_ice_server(config, server);
+ ERR_FAIL_COND_V(err != OK, FAILED);
}
}
- return _create_pc(config);
+ return (int64_t)_create_pc(config);
}
-godot_object *WebRTCLibPeerConnection::create_data_channel(const char *p_channel, const godot_dictionary *p_channel_config) {
- ERR_FAIL_COND_V(peer_connection.get() == nullptr, NULL);
+Object *WebRTCLibPeerConnection::_create_data_channel(const String &p_channel, const Dictionary &p_channel_config) try {
+ ERR_FAIL_COND_V(!peer_connection, nullptr);
// Read config from dictionary
- webrtc::DataChannelInit config;
- godot::Dictionary d = *(godot::Dictionary *)p_channel_config;
- godot_error err = _parse_channel_config(config, d);
- ERR_FAIL_COND_V(err != GODOT_OK, NULL);
-
- WebRTCLibDataChannel *wrapper = WebRTCLibDataChannel::new_data_channel(peer_connection->CreateDataChannel(p_channel, &config));
- ERR_FAIL_COND_V(wrapper == NULL, NULL);
- return wrapper->_owner;
+ rtc::DataChannelInit config;
+
+ Error err = _parse_channel_config(config, p_channel_config);
+ ERR_FAIL_COND_V(err != OK, nullptr);
+
+ std::shared_ptr<rtc::DataChannel> ch = peer_connection->createDataChannel(p_channel.utf8().get_data(), config);
+ ERR_FAIL_COND_V(ch == nullptr, nullptr);
+
+ WebRTCLibDataChannel *wrapper = WebRTCLibDataChannel::new_data_channel(ch, ch->id().has_value());
+ ERR_FAIL_COND_V(wrapper == nullptr, nullptr);
+ return wrapper;
+} catch (const std::exception &e) {
+ ERR_PRINT(e.what());
+ ERR_FAIL_V(nullptr);
}
-godot_error WebRTCLibPeerConnection::create_offer() {
- ERR_FAIL_COND_V(peer_connection.get() == nullptr, GODOT_ERR_UNCONFIGURED);
- peer_connection->CreateOffer(ptr_csdo, webrtc::PeerConnectionInterface::RTCOfferAnswerOptions());
- return GODOT_OK;
+int64_t WebRTCLibPeerConnection::_create_offer() try {
+ ERR_FAIL_COND_V(!peer_connection, ERR_UNCONFIGURED);
+ ERR_FAIL_COND_V(_get_connection_state() != STATE_NEW, FAILED);
+ peer_connection->setLocalDescription(rtc::Description::Type::Offer);
+ return OK;
+} catch (const std::exception &e) {
+ ERR_PRINT(e.what());
+ ERR_FAIL_V(FAILED);
}
-#define _MAKE_DESC(TYPE, SDP) webrtc::CreateSessionDescription((godot::String(TYPE) == godot::String("offer") ? webrtc::SdpType::kOffer : webrtc::SdpType::kAnswer), SDP)
-godot_error WebRTCLibPeerConnection::set_remote_description(const char *type, const char *sdp) {
- ERR_FAIL_COND_V(peer_connection.get() == nullptr, GODOT_ERR_UNCONFIGURED);
- std::unique_ptr<webrtc::SessionDescriptionInterface> desc = _MAKE_DESC(type, sdp);
- if (desc->GetType() == webrtc::SdpType::kOffer) {
- ptr_ssdo->make_offer = true;
+int64_t WebRTCLibPeerConnection::_set_remote_description(const String &p_type, const String &p_sdp) try {
+ ERR_FAIL_COND_V(!peer_connection, ERR_UNCONFIGURED);
+ std::string sdp(p_sdp.utf8().get_data());
+ std::string type(p_type.utf8().get_data());
+ rtc::Description desc(sdp, type);
+ peer_connection->setRemoteDescription(desc);
+ // Automatically create the answer.
+ if (p_type == String("offer")) {
+ peer_connection->setLocalDescription(rtc::Description::Type::Answer);
}
- peer_connection->SetRemoteDescription(ptr_ssdo, desc.release());
- return GODOT_OK;
+ return OK;
+} catch (const std::exception &e) {
+ ERR_PRINT(e.what());
+ ERR_FAIL_V(FAILED);
}
-godot_error WebRTCLibPeerConnection::set_local_description(const char *type, const char *sdp) {
- ERR_FAIL_COND_V(peer_connection.get() == nullptr, GODOT_ERR_UNCONFIGURED);
- std::unique_ptr<webrtc::SessionDescriptionInterface> desc = _MAKE_DESC(type, sdp);
- peer_connection->SetLocalDescription(ptr_ssdo, desc.release());
- return GODOT_OK;
+int64_t WebRTCLibPeerConnection::_set_local_description(const String &p_type, const String &p_sdp) {
+ ERR_FAIL_COND_V(!peer_connection, ERR_UNCONFIGURED);
+ // XXX Library quirk. It doesn't seem possible to create offers/answers without setting the local description.
+ // Ignore this call for now to avoid crash (it's already set automatically!).
+ //peer_connection->setLocalDescription(p_type == String("offer") ? rtc::Description::Type::Offer : rtc::Description::Type::Answer);
+ return OK;
}
-#undef _MAKE_DESC
-
-godot_error WebRTCLibPeerConnection::add_ice_candidate(const char *sdpMidName, int sdpMlineIndexName, const char *sdpName) {
- ERR_FAIL_COND_V(peer_connection.get() == nullptr, GODOT_ERR_UNCONFIGURED);
-
- webrtc::SdpParseError *error = nullptr;
- webrtc::IceCandidateInterface *candidate = webrtc::CreateIceCandidate(
- sdpMidName,
- sdpMlineIndexName,
- sdpName,
- error);
-
- ERR_FAIL_COND_V(error || !candidate, GODOT_ERR_INVALID_PARAMETER);
- ERR_FAIL_COND_V(!peer_connection->AddIceCandidate(candidate), GODOT_FAILED);
- return GODOT_OK;
+int64_t WebRTCLibPeerConnection::_add_ice_candidate(const String &sdpMidName, int64_t sdpMlineIndexName, const String &sdpName) try {
+ ERR_FAIL_COND_V(!peer_connection, ERR_UNCONFIGURED);
+ rtc::Candidate candidate(sdpName.utf8().get_data(), sdpMidName.utf8().get_data());
+ peer_connection->addRemoteCandidate(candidate);
+ return OK;
+} catch (const std::exception &e) {
+ ERR_PRINT(e.what());
+ ERR_FAIL_V(FAILED);
}
-godot_error WebRTCLibPeerConnection::poll() {
- ERR_FAIL_COND_V(peer_connection.get() == nullptr, GODOT_ERR_UNCONFIGURED);
+int64_t WebRTCLibPeerConnection::_poll() {
+ ERR_FAIL_COND_V(!peer_connection, ERR_UNCONFIGURED);
while (!signal_queue.empty()) {
mutex_signal_queue->lock();
@@ -245,68 +232,83 @@ godot_error WebRTCLibPeerConnection::poll() {
mutex_signal_queue->unlock();
signal.emit(this);
}
- return GODOT_OK;
+ return OK;
}
-void WebRTCLibPeerConnection::close() {
- if (peer_connection.get() != nullptr) {
- peer_connection->Close();
+void WebRTCLibPeerConnection::_close() {
+ if (peer_connection != nullptr) {
+ try {
+ peer_connection->close();
+ } catch (...) {
+ }
}
- peer_connection = nullptr;
+
while (!signal_queue.empty()) {
signal_queue.pop();
}
}
-void WebRTCLibPeerConnection::_register_methods() {
-}
-
void WebRTCLibPeerConnection::_init() {
+#ifdef GDNATIVE_WEBRTC
register_interface(&interface);
-
- // initialize variables:
+#endif
mutex_signal_queue = new std::mutex;
- // create a PeerConnectionFactoryInterface:
- webrtc::PeerConnectionFactoryDependencies deps;
-
- ERR_FAIL_COND(signaling_thread.get() == nullptr);
- deps.signaling_thread = signaling_thread.get();
- pc_factory = webrtc::CreateModularPeerConnectionFactory(std::move(deps));
-
- // Create peer connection with default configuration.
- webrtc::PeerConnectionInterface::RTCConfiguration config;
- _create_pc(config);
+ _initialize(Dictionary());
}
-godot_error WebRTCLibPeerConnection::_create_pc(webrtc::PeerConnectionInterface::RTCConfiguration &config) {
- ERR_FAIL_COND_V(pc_factory.get() == nullptr, GODOT_ERR_BUG);
- peer_connection = nullptr;
- peer_connection = pc_factory->CreatePeerConnection(config, nullptr, nullptr, &pco);
- if (peer_connection.get() == nullptr) { // PeerConnection couldn't be created. Fail the method call.
- ERR_PRINT("PeerConnection could not be created");
- return GODOT_FAILED;
- }
- return GODOT_OK;
+Error WebRTCLibPeerConnection::_create_pc(rtc::Configuration &r_config) try {
+ // Prevents libdatachannel from automatically creating offers.
+ r_config.disableAutoNegotiation = true;
+
+ peer_connection = std::make_shared<rtc::PeerConnection>(r_config);
+ ERR_FAIL_COND_V(!peer_connection, FAILED);
+
+ // Binding this should be fine as long as we call close when going out of scope.
+ peer_connection->onLocalDescription([this](rtc::Description description) {
+ String type = description.type() == rtc::Description::Type::Offer ? "offer" : "answer";
+ queue_signal("session_description_created", 2, type, String(std::string(description).c_str()));
+ });
+ peer_connection->onLocalCandidate([this](rtc::Candidate candidate) {
+ queue_signal("ice_candidate_created", 3, String(candidate.mid().c_str()), 0, String(candidate.candidate().c_str()));
+ });
+ peer_connection->onDataChannel([this](std::shared_ptr<rtc::DataChannel> channel) {
+ queue_signal("data_channel_received", 1, WebRTCLibDataChannel::new_data_channel(channel, false));
+ });
+ /*
+ peer_connection->onStateChange([](rtc::PeerConnection::State state) {
+ std::cout << "[State: " << state << "]" << std::endl;
+ });
+
+ peer_connection->onGatheringStateChange([](rtc::PeerConnection::GatheringState state) {
+ std::cout << "[Gathering State: " << state << "]" << std::endl;
+ });
+ */
+ return OK;
+} catch (const std::exception &e) {
+ ERR_PRINT(e.what());
+ ERR_FAIL_V(FAILED);
}
-WebRTCLibPeerConnection::WebRTCLibPeerConnection() :
- pco(this),
- ptr_csdo(new rtc::RefCountedObject<GodotCSDO>(this)),
- ptr_ssdo(new rtc::RefCountedObject<GodotSSDO>(this)) {
+WebRTCLibPeerConnection::WebRTCLibPeerConnection() {
+#ifndef GDNATIVE_WEBRTC
+ _init();
+#endif
}
WebRTCLibPeerConnection::~WebRTCLibPeerConnection() {
+#ifdef GDNATIVE_WEBRTC
if (_owner) {
- register_interface(NULL);
+ register_interface(nullptr);
}
- close();
+#endif
+ _close();
delete mutex_signal_queue;
}
-void WebRTCLibPeerConnection::queue_signal(godot::String p_name, int p_argc, const godot::Variant &p_arg1, const godot::Variant &p_arg2, const godot::Variant &p_arg3) {
+void WebRTCLibPeerConnection::queue_signal(String p_name, int p_argc, const Variant &p_arg1, const Variant &p_arg2, const Variant &p_arg3) {
mutex_signal_queue->lock();
- const godot::Variant argv[3] = { p_arg1, p_arg2, p_arg3 };
+ const Variant argv[3] = { p_arg1, p_arg2, p_arg3 };
signal_queue.push(Signal(p_name, p_argc, argv));
mutex_signal_queue->unlock();
}