[WebRTC] Expose more of the WebRTC API.

Add get_gathering_state() returning the iceGatheringState of the
connection.

Add get_signaling_state() returning the signalingState of the
connection.

Improve JS library.
This commit is contained in:
Fabio Alessandrelli 2022-09-08 04:39:32 +02:00
parent 22a09fef5d
commit dbff58ebdd
9 changed files with 237 additions and 85 deletions

View File

@ -67,6 +67,18 @@
Returns the connection state. See [enum ConnectionState]. Returns the connection state. See [enum ConnectionState].
</description> </description>
</method> </method>
<method name="get_gathering_state" qualifiers="const">
<return type="int" enum="WebRTCPeerConnection.GatheringState" />
<description>
Returns the ICE [enum GatheringState] of the connection. This lets you detect, for example, when collection of ICE candidates has finished.
</description>
</method>
<method name="get_signaling_state" qualifiers="const">
<return type="int" enum="WebRTCPeerConnection.SignalingState" />
<description>
Returns the [enum SignalingState] on the local end of the connection while connecting or reconnecting to another peer.
</description>
</method>
<method name="initialize"> <method name="initialize">
<return type="int" enum="Error" /> <return type="int" enum="Error" />
<param index="0" name="configuration" type="Dictionary" default="{}" /> <param index="0" name="configuration" type="Dictionary" default="{}" />
@ -165,5 +177,32 @@
<constant name="STATE_CLOSED" value="5" enum="ConnectionState"> <constant name="STATE_CLOSED" value="5" enum="ConnectionState">
The peer connection is closed (after calling [method close] for example). The peer connection is closed (after calling [method close] for example).
</constant> </constant>
<constant name="GATHERING_STATE_NEW" value="0" enum="GatheringState">
The peer connection was just created and hasn't done any networking yet.
</constant>
<constant name="GATHERING_STATE_GATHERING" value="1" enum="GatheringState">
The ICE agent is in the process of gathering candidates for the connection.
</constant>
<constant name="GATHERING_STATE_COMPLETE" value="2" enum="GatheringState">
The ICE agent has finished gathering candidates. If something happens that requires collecting new candidates, such as a new interface being added or the addition of a new ICE server, the state will revert to gathering to gather those candidates.
</constant>
<constant name="SIGNALING_STATE_STABLE" value="0" enum="SignalingState">
There is no ongoing exchange of offer and answer underway. This may mean that the [WebRTCPeerConnection] is new ([constant STATE_NEW]) or that negotiation is complete and a connection has been established ([constant STATE_CONNECTED]).
</constant>
<constant name="SIGNALING_STATE_HAVE_LOCAL_OFFER" value="1" enum="SignalingState">
The local peer has called [method set_local_description], passing in SDP representing an offer (usually created by calling [method create_offer]), and the offer has been applied successfully.
</constant>
<constant name="SIGNALING_STATE_HAVE_REMOTE_OFFER" value="2" enum="SignalingState">
The remote peer has created an offer and used the signaling server to deliver it to the local peer, which has set the offer as the remote description by calling [method set_remote_description].
</constant>
<constant name="SIGNALING_STATE_HAVE_LOCAL_PRANSWER" value="3" enum="SignalingState">
The offer sent by the remote peer has been applied and an answer has been created and applied by calling [method set_local_description]. This provisional answer describes the supported media formats and so forth, but may not have a complete set of ICE candidates included. Further candidates will be delivered separately later.
</constant>
<constant name="SIGNALING_STATE_HAVE_REMOTE_PRANSWER" value="4" enum="SignalingState">
A provisional answer has been received and successfully applied in response to an offer previously sent and established by calling [method set_local_description].
</constant>
<constant name="SIGNALING_STATE_CLOSED" value="5" enum="SignalingState">
The [WebRTCPeerConnection] has been closed.
</constant>
</constants> </constants>
</class> </class>

View File

@ -37,6 +37,16 @@
<description> <description>
</description> </description>
</method> </method>
<method name="_get_gathering_state" qualifiers="virtual const">
<return type="int" enum="WebRTCPeerConnection.GatheringState" />
<description>
</description>
</method>
<method name="_get_signaling_state" qualifiers="virtual const">
<return type="int" enum="WebRTCPeerConnection.SignalingState" />
<description>
</description>
</method>
<method name="_initialize" qualifiers="virtual"> <method name="_initialize" qualifiers="virtual">
<return type="int" enum="Error" /> <return type="int" enum="Error" />
<param index="0" name="p_config" type="Dictionary" /> <param index="0" name="p_config" type="Dictionary" />

View File

@ -220,64 +220,123 @@ mergeInto(LibraryManager.library, GodotRTCDataChannel);
const GodotRTCPeerConnection = { const GodotRTCPeerConnection = {
$GodotRTCPeerConnection__deps: ['$IDHandler', '$GodotRuntime', '$GodotRTCDataChannel'], $GodotRTCPeerConnection__deps: ['$IDHandler', '$GodotRuntime', '$GodotRTCDataChannel'],
$GodotRTCPeerConnection: { $GodotRTCPeerConnection: {
onstatechange: function (p_id, p_conn, callback, event) { // Enums
const ref = IDHandler.get(p_id); ConnectionState: {
if (!ref) { 'new': 0,
return; 'connecting': 1,
} 'connected': 2,
let state; 'disconnected': 3,
switch (p_conn.iceConnectionState) { 'failed': 4,
case 'new': 'closed': 5,
state = 0;
break;
case 'checking':
state = 1;
break;
case 'connected':
case 'completed':
state = 2;
break;
case 'disconnected':
state = 3;
break;
case 'failed':
state = 4;
break;
case 'closed':
default:
state = 5;
break;
}
callback(state);
}, },
onicecandidate: function (p_id, callback, event) { ConnectionStateCompat: {
const ref = IDHandler.get(p_id); // Using values from IceConnectionState for browsers that do not support ConnectionState (notably Firefox).
if (!ref || !event.candidate) { 'new': 0,
return; 'checking': 1,
} 'connected': 2,
'completed': 2,
const c = event.candidate; 'disconnected': 3,
const candidate_str = GodotRuntime.allocString(c.candidate); 'failed': 4,
const mid_str = GodotRuntime.allocString(c.sdpMid); 'closed': 5,
callback(mid_str, c.sdpMLineIndex, candidate_str);
GodotRuntime.free(candidate_str);
GodotRuntime.free(mid_str);
}, },
ondatachannel: function (p_id, callback, event) { IceGatheringState: {
const ref = IDHandler.get(p_id); 'new': 0,
if (!ref) { 'gathering': 1,
return; 'complete': 2,
},
SignalingState: {
'stable': 0,
'have-local-offer': 1,
'have-remote-offer': 2,
'have-local-pranswer': 3,
'have-remote-pranswer': 4,
'closed': 5,
},
// Callbacks
create: function (config, onConnectionChange, onSignalingChange, onIceGatheringChange, onIceCandidate, onDataChannel) {
let conn = null;
try {
conn = new RTCPeerConnection(config);
} catch (e) {
GodotRuntime.error(e);
return 0;
} }
const cid = IDHandler.add(event.channel); const id = IDHandler.add(conn);
callback(cid);
if ('connectionState' in conn && conn['connectionState'] !== undefined) {
// Use "connectionState" if supported
conn.onconnectionstatechange = function (event) {
if (!IDHandler.get(id)) {
return;
}
onConnectionChange(GodotRTCPeerConnection.ConnectionState[conn.connectionState] || 0);
};
} else {
// Fall back to using "iceConnectionState" when "connectionState" is not supported (notably Firefox).
conn.oniceconnectionstatechange = function (event) {
if (!IDHandler.get(id)) {
return;
}
onConnectionChange(GodotRTCPeerConnection.ConnectionStateCompat[conn.iceConnectionState] || 0);
};
}
conn.onicegatheringstatechange = function (event) {
if (!IDHandler.get(id)) {
return;
}
onIceGatheringChange(GodotRTCPeerConnection.IceGatheringState[conn.iceGatheringState] || 0);
};
conn.onsignalingstatechange = function (event) {
if (!IDHandler.get(id)) {
return;
}
onSignalingChange(GodotRTCPeerConnection.SignalingState[conn.signalingState] || 0);
};
conn.onicecandidate = function (event) {
if (!IDHandler.get(id)) {
return;
}
const c = event.candidate;
if (!c || !c.candidate) {
return;
}
const candidate_str = GodotRuntime.allocString(c.candidate);
const mid_str = GodotRuntime.allocString(c.sdpMid);
onIceCandidate(mid_str, c.sdpMLineIndex, candidate_str);
GodotRuntime.free(candidate_str);
GodotRuntime.free(mid_str);
};
conn.ondatachannel = function (event) {
if (!IDHandler.get(id)) {
return;
}
const cid = IDHandler.add(event.channel);
onDataChannel(cid);
};
return id;
},
destroy: function (p_id) {
const conn = IDHandler.get(p_id);
if (!conn) {
return;
}
conn.onconnectionstatechange = null;
conn.oniceconnectionstatechange = null;
conn.onicegatheringstatechange = null;
conn.onsignalingstatechange = null;
conn.onicecandidate = null;
conn.ondatachannel = null;
IDHandler.remove(p_id);
}, },
onsession: function (p_id, callback, session) { onsession: function (p_id, callback, session) {
const ref = IDHandler.get(p_id); if (!IDHandler.get(p_id)) {
if (!ref) {
return; return;
} }
const type_str = GodotRuntime.allocString(session.type); const type_str = GodotRuntime.allocString(session.type);
@ -297,27 +356,19 @@ const GodotRTCPeerConnection = {
}, },
}, },
godot_js_rtc_pc_create__sig: 'iiiiii', godot_js_rtc_pc_create__sig: 'iiiiiiii',
godot_js_rtc_pc_create: function (p_config, p_ref, p_on_state_change, p_on_candidate, p_on_datachannel) { godot_js_rtc_pc_create: function (p_config, p_ref, p_on_connection_state_change, p_on_ice_gathering_state_change, p_on_signaling_state_change, p_on_ice_candidate, p_on_datachannel) {
const onstatechange = GodotRuntime.get_func(p_on_state_change).bind(null, p_ref); const wrap = function (p_func) {
const oncandidate = GodotRuntime.get_func(p_on_candidate).bind(null, p_ref); return GodotRuntime.get_func(p_func).bind(null, p_ref);
const ondatachannel = GodotRuntime.get_func(p_on_datachannel).bind(null, p_ref); };
return GodotRTCPeerConnection.create(
const config = JSON.parse(GodotRuntime.parseString(p_config)); JSON.parse(GodotRuntime.parseString(p_config)),
let conn = null; wrap(p_on_connection_state_change),
try { wrap(p_on_signaling_state_change),
conn = new RTCPeerConnection(config); wrap(p_on_ice_gathering_state_change),
} catch (e) { wrap(p_on_ice_candidate),
GodotRuntime.error(e); wrap(p_on_datachannel)
return 0; );
}
const base = GodotRTCPeerConnection;
const id = IDHandler.add(conn);
conn.oniceconnectionstatechange = base.onstatechange.bind(null, id, conn, onstatechange);
conn.onicecandidate = base.onicecandidate.bind(null, id, oncandidate);
conn.ondatachannel = base.ondatachannel.bind(null, id, ondatachannel);
return id;
}, },
godot_js_rtc_pc_close__sig: 'vi', godot_js_rtc_pc_close__sig: 'vi',
@ -331,14 +382,7 @@ const GodotRTCPeerConnection = {
godot_js_rtc_pc_destroy__sig: 'vi', godot_js_rtc_pc_destroy__sig: 'vi',
godot_js_rtc_pc_destroy: function (p_id) { godot_js_rtc_pc_destroy: function (p_id) {
const ref = IDHandler.get(p_id); GodotRTCPeerConnection.destroy(p_id);
if (!ref) {
return;
}
ref.oniceconnectionstatechange = null;
ref.onicecandidate = null;
ref.ondatachannel = null;
IDHandler.remove(p_id);
}, },
godot_js_rtc_pc_offer_create__sig: 'viiii', godot_js_rtc_pc_offer_create__sig: 'viiii',

View File

@ -69,6 +69,8 @@ void WebRTCPeerConnection::_bind_methods() {
ClassDB::bind_method(D_METHOD("close"), &WebRTCPeerConnection::close); ClassDB::bind_method(D_METHOD("close"), &WebRTCPeerConnection::close);
ClassDB::bind_method(D_METHOD("get_connection_state"), &WebRTCPeerConnection::get_connection_state); ClassDB::bind_method(D_METHOD("get_connection_state"), &WebRTCPeerConnection::get_connection_state);
ClassDB::bind_method(D_METHOD("get_gathering_state"), &WebRTCPeerConnection::get_gathering_state);
ClassDB::bind_method(D_METHOD("get_signaling_state"), &WebRTCPeerConnection::get_signaling_state);
ADD_SIGNAL(MethodInfo("session_description_created", PropertyInfo(Variant::STRING, "type"), PropertyInfo(Variant::STRING, "sdp"))); ADD_SIGNAL(MethodInfo("session_description_created", PropertyInfo(Variant::STRING, "type"), PropertyInfo(Variant::STRING, "sdp")));
ADD_SIGNAL(MethodInfo("ice_candidate_created", PropertyInfo(Variant::STRING, "media"), PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::STRING, "name"))); ADD_SIGNAL(MethodInfo("ice_candidate_created", PropertyInfo(Variant::STRING, "media"), PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::STRING, "name")));
@ -80,6 +82,17 @@ void WebRTCPeerConnection::_bind_methods() {
BIND_ENUM_CONSTANT(STATE_DISCONNECTED); BIND_ENUM_CONSTANT(STATE_DISCONNECTED);
BIND_ENUM_CONSTANT(STATE_FAILED); BIND_ENUM_CONSTANT(STATE_FAILED);
BIND_ENUM_CONSTANT(STATE_CLOSED); BIND_ENUM_CONSTANT(STATE_CLOSED);
BIND_ENUM_CONSTANT(GATHERING_STATE_NEW);
BIND_ENUM_CONSTANT(GATHERING_STATE_GATHERING);
BIND_ENUM_CONSTANT(GATHERING_STATE_COMPLETE);
BIND_ENUM_CONSTANT(SIGNALING_STATE_STABLE);
BIND_ENUM_CONSTANT(SIGNALING_STATE_HAVE_LOCAL_OFFER);
BIND_ENUM_CONSTANT(SIGNALING_STATE_HAVE_REMOTE_OFFER);
BIND_ENUM_CONSTANT(SIGNALING_STATE_HAVE_LOCAL_PRANSWER);
BIND_ENUM_CONSTANT(SIGNALING_STATE_HAVE_REMOTE_PRANSWER);
BIND_ENUM_CONSTANT(SIGNALING_STATE_CLOSED);
} }
WebRTCPeerConnection::WebRTCPeerConnection() { WebRTCPeerConnection::WebRTCPeerConnection() {

View File

@ -47,6 +47,21 @@ public:
STATE_CLOSED STATE_CLOSED
}; };
enum GatheringState {
GATHERING_STATE_NEW,
GATHERING_STATE_GATHERING,
GATHERING_STATE_COMPLETE,
};
enum SignalingState {
SIGNALING_STATE_STABLE,
SIGNALING_STATE_HAVE_LOCAL_OFFER,
SIGNALING_STATE_HAVE_REMOTE_OFFER,
SIGNALING_STATE_HAVE_LOCAL_PRANSWER,
SIGNALING_STATE_HAVE_REMOTE_PRANSWER,
SIGNALING_STATE_CLOSED,
};
private: private:
static StringName default_extension; static StringName default_extension;
@ -57,6 +72,8 @@ public:
static void set_default_extension(const StringName &p_name); static void set_default_extension(const StringName &p_name);
virtual ConnectionState get_connection_state() const = 0; virtual ConnectionState get_connection_state() const = 0;
virtual GatheringState get_gathering_state() const = 0;
virtual SignalingState get_signaling_state() const = 0;
virtual Error initialize(Dictionary p_config = Dictionary()) = 0; virtual Error initialize(Dictionary p_config = Dictionary()) = 0;
virtual Ref<WebRTCDataChannel> create_data_channel(String p_label, Dictionary p_options = Dictionary()) = 0; virtual Ref<WebRTCDataChannel> create_data_channel(String p_label, Dictionary p_options = Dictionary()) = 0;
@ -74,5 +91,7 @@ public:
}; };
VARIANT_ENUM_CAST(WebRTCPeerConnection::ConnectionState); VARIANT_ENUM_CAST(WebRTCPeerConnection::ConnectionState);
VARIANT_ENUM_CAST(WebRTCPeerConnection::GatheringState);
VARIANT_ENUM_CAST(WebRTCPeerConnection::SignalingState);
#endif // WEBRTC_PEER_CONNECTION_H #endif // WEBRTC_PEER_CONNECTION_H

View File

@ -32,6 +32,8 @@
void WebRTCPeerConnectionExtension::_bind_methods() { void WebRTCPeerConnectionExtension::_bind_methods() {
GDVIRTUAL_BIND(_get_connection_state); GDVIRTUAL_BIND(_get_connection_state);
GDVIRTUAL_BIND(_get_gathering_state);
GDVIRTUAL_BIND(_get_signaling_state);
GDVIRTUAL_BIND(_initialize, "p_config"); GDVIRTUAL_BIND(_initialize, "p_config");
GDVIRTUAL_BIND(_create_data_channel, "p_label", "p_config"); GDVIRTUAL_BIND(_create_data_channel, "p_label", "p_config");
GDVIRTUAL_BIND(_create_offer); GDVIRTUAL_BIND(_create_offer);

View File

@ -53,6 +53,8 @@ public:
/** GDExtension **/ /** GDExtension **/
EXBIND0RC(ConnectionState, get_connection_state); EXBIND0RC(ConnectionState, get_connection_state);
EXBIND0RC(GatheringState, get_gathering_state);
EXBIND0RC(SignalingState, get_signaling_state);
EXBIND1R(Error, initialize, Dictionary); EXBIND1R(Error, initialize, Dictionary);
EXBIND0R(Error, create_offer); EXBIND0R(Error, create_offer);
EXBIND2R(Error, set_remote_description, String, String); EXBIND2R(Error, set_remote_description, String, String);

View File

@ -51,6 +51,16 @@ void WebRTCPeerConnectionJS::_on_connection_state_changed(void *p_obj, int p_sta
peer->_conn_state = (ConnectionState)p_state; peer->_conn_state = (ConnectionState)p_state;
} }
void WebRTCPeerConnectionJS::_on_gathering_state_changed(void *p_obj, int p_state) {
WebRTCPeerConnectionJS *peer = static_cast<WebRTCPeerConnectionJS *>(p_obj);
peer->_gathering_state = (GatheringState)p_state;
}
void WebRTCPeerConnectionJS::_on_signaling_state_changed(void *p_obj, int p_state) {
WebRTCPeerConnectionJS *peer = static_cast<WebRTCPeerConnectionJS *>(p_obj);
peer->_signaling_state = (SignalingState)p_state;
}
void WebRTCPeerConnectionJS::_on_error(void *p_obj) { void WebRTCPeerConnectionJS::_on_error(void *p_obj) {
ERR_PRINT("RTCPeerConnection error!"); ERR_PRINT("RTCPeerConnection error!");
} }
@ -100,7 +110,7 @@ Error WebRTCPeerConnectionJS::initialize(Dictionary p_config) {
_conn_state = STATE_NEW; _conn_state = STATE_NEW;
String config = Variant(p_config).to_json_string(); String config = Variant(p_config).to_json_string();
_js_id = godot_js_rtc_pc_create(config.utf8().get_data(), this, &_on_connection_state_changed, &_on_ice_candidate, &_on_data_channel); _js_id = godot_js_rtc_pc_create(config.utf8().get_data(), this, &_on_connection_state_changed, &_on_gathering_state_changed, &_on_signaling_state_changed, &_on_ice_candidate, &_on_data_channel);
return _js_id ? OK : FAILED; return _js_id ? OK : FAILED;
} }
@ -117,14 +127,19 @@ Error WebRTCPeerConnectionJS::poll() {
return OK; return OK;
} }
WebRTCPeerConnection::GatheringState WebRTCPeerConnectionJS::get_gathering_state() const {
return _gathering_state;
}
WebRTCPeerConnection::SignalingState WebRTCPeerConnectionJS::get_signaling_state() const {
return _signaling_state;
}
WebRTCPeerConnection::ConnectionState WebRTCPeerConnectionJS::get_connection_state() const { WebRTCPeerConnection::ConnectionState WebRTCPeerConnectionJS::get_connection_state() const {
return _conn_state; return _conn_state;
} }
WebRTCPeerConnectionJS::WebRTCPeerConnectionJS() { WebRTCPeerConnectionJS::WebRTCPeerConnectionJS() {
_conn_state = STATE_NEW;
_js_id = 0;
Dictionary config; Dictionary config;
initialize(config); initialize(config);
} }

View File

@ -37,11 +37,13 @@
extern "C" { extern "C" {
typedef void (*RTCOnIceConnectionStateChange)(void *p_obj, int p_state); typedef void (*RTCOnIceConnectionStateChange)(void *p_obj, int p_state);
typedef void (*RTCOnIceGatheringStateChange)(void *p_obj, int p_state);
typedef void (*RTCOnSignalingStateChange)(void *p_obj, int p_state);
typedef void (*RTCOnIceCandidate)(void *p_obj, const char *p_mid, int p_mline_idx, const char *p_candidate); typedef void (*RTCOnIceCandidate)(void *p_obj, const char *p_mid, int p_mline_idx, const char *p_candidate);
typedef void (*RTCOnDataChannel)(void *p_obj, int p_id); typedef void (*RTCOnDataChannel)(void *p_obj, int p_id);
typedef void (*RTCOnSession)(void *p_obj, const char *p_type, const char *p_sdp); typedef void (*RTCOnSession)(void *p_obj, const char *p_type, const char *p_sdp);
typedef void (*RTCOnError)(void *p_obj); typedef void (*RTCOnError)(void *p_obj);
extern int godot_js_rtc_pc_create(const char *p_config, void *p_obj, RTCOnIceConnectionStateChange p_on_state_change, RTCOnIceCandidate p_on_candidate, RTCOnDataChannel p_on_datachannel); extern int godot_js_rtc_pc_create(const char *p_config, void *p_obj, RTCOnIceConnectionStateChange p_on_connection_state_change, RTCOnIceGatheringStateChange p_on_gathering_state_change, RTCOnSignalingStateChange p_on_signaling_state_change, RTCOnIceCandidate p_on_candidate, RTCOnDataChannel p_on_datachannel);
extern void godot_js_rtc_pc_close(int p_id); extern void godot_js_rtc_pc_close(int p_id);
extern void godot_js_rtc_pc_destroy(int p_id); extern void godot_js_rtc_pc_destroy(int p_id);
extern void godot_js_rtc_pc_offer_create(int p_id, void *p_obj, RTCOnSession p_on_session, RTCOnError p_on_error); extern void godot_js_rtc_pc_offer_create(int p_id, void *p_obj, RTCOnSession p_on_session, RTCOnError p_on_error);
@ -55,10 +57,14 @@ class WebRTCPeerConnectionJS : public WebRTCPeerConnection {
GDCLASS(WebRTCPeerConnectionJS, WebRTCPeerConnection); GDCLASS(WebRTCPeerConnectionJS, WebRTCPeerConnection);
private: private:
int _js_id; int _js_id = 0;
ConnectionState _conn_state; ConnectionState _conn_state = STATE_NEW;
GatheringState _gathering_state = GATHERING_STATE_NEW;
SignalingState _signaling_state = SIGNALING_STATE_STABLE;
static void _on_connection_state_changed(void *p_obj, int p_state); static void _on_connection_state_changed(void *p_obj, int p_state);
static void _on_gathering_state_changed(void *p_obj, int p_state);
static void _on_signaling_state_changed(void *p_obj, int p_state);
static void _on_ice_candidate(void *p_obj, const char *p_mid_name, int p_mline_idx, const char *p_candidate); static void _on_ice_candidate(void *p_obj, const char *p_mid_name, int p_mline_idx, const char *p_candidate);
static void _on_data_channel(void *p_obj, int p_channel); static void _on_data_channel(void *p_obj, int p_channel);
static void _on_session_created(void *p_obj, const char *p_type, const char *p_session); static void _on_session_created(void *p_obj, const char *p_type, const char *p_session);
@ -66,6 +72,8 @@ private:
public: public:
virtual ConnectionState get_connection_state() const override; virtual ConnectionState get_connection_state() const override;
virtual GatheringState get_gathering_state() const override;
virtual SignalingState get_signaling_state() const override;
virtual Error initialize(Dictionary configuration = Dictionary()) override; virtual Error initialize(Dictionary configuration = Dictionary()) override;
virtual Ref<WebRTCDataChannel> create_data_channel(String p_channel_name, Dictionary p_channel_config = Dictionary()) override; virtual Ref<WebRTCDataChannel> create_data_channel(String p_channel_name, Dictionary p_channel_config = Dictionary()) override;