Experiencing issues getting WebRTC to work

My Godot version is 4.3.rc3

I’m very inexperienced with WebRTC, so I’ve created a project file for testing purposes in-order to familiarize myself with it. I’m also using WebSocket for the initial hand-shake process, which fortunately is working correctly :grin:

The project consists of 1 scene with a ColorRect as the root node, and 2 Buttons as child nodes, with one button named “Host” and the other “Join”

The script named “Host.gd” is attached to the button named Host, whilst the script named “Join.gd” is attached to the button named Join

The contents of Host.gd are as follows (it deals with server-sided logic):

extends Button

var rtc_peer := WebRTCMultiplayerPeer.new()
var sock_peer := WebSocketMultiplayerPeer.new()


func _ready() -> void:
	set_process(false)


func _on_button_up() -> void:
	rtc_peer.create_server()
	sock_peer.create_server(1234)
	set_process(true)
	multiplayer.multiplayer_peer = rtc_peer

	multiplayer.peer_connected.connect(rtc_peer_connected)
	sock_peer.peer_connected.connect(sock_peer_connected)


func _process(_delta: float) -> void:
	sock_peer.poll()

	if 0 < sock_peer.get_available_packet_count():
		var data: Dictionary = JSON.parse_string(
			sock_peer.get_packet().get_string_from_utf8())

		match int(data.type):
			1:
				rtc_peer.get_peer(data.id).connection.set_remote_description(
					"offer", data.sdp)
			2:
				rtc_peer.get_peer(data.id).connection.set_remote_description(
					"answer", data.sdp)


func ice_candidate_created(media: String, index: int, Name: String, id: int) -> void:
	print("ice candidate created")
	send_to_peer(id, {
		"type" = 3,
		"media" = media,
		"index" = index,
		"name" = Name})


func rtc_peer_connected(id: int) -> void:
	print("%d connected to rtc server" % id)


func send_to_peer(id: int, data: Dictionary) -> void:
	sock_peer.get_peer(id).put_packet(JSON.stringify(data).to_utf8_buffer())


func session_description_created(type: String, sdp: String, id: int) -> void:
	print("type is %s" % type)
	rtc_peer.get_peer(id).connection.set_local_description(type, sdp)

	send_to_peer(id, {
		"type" = 1 if type == "offer" else 2,
		"sdp" = sdp})


func sock_peer_connected(id: int) -> void:
	print("%d connected to socket server" % id)
	send_to_peer(id, {"type" = 0, "id" = id})

	var peer := WebRTCPeerConnection.new()
	peer.initialize({
		"iceServers" = [{"urls" = ["stun:stun.l.google.com:19302"]}]})
	rtc_peer.add_peer(peer, id)
	peer.create_offer()

	peer.ice_candidate_created.connect(ice_candidate_created.bind(id))
	peer.session_description_created.connect(session_description_created.bind(id))

And the contents of Join.gd are as follows (it deals with client-sided logic):

extends Button

var my_id: int = 0

var rtc_peer := WebRTCMultiplayerPeer.new()
var sock_peer := WebSocketMultiplayerPeer.new()


func _ready() -> void:
	set_process(false)


func _on_button_up() -> void:
	sock_peer.create_client("ws://localhost:1234")
	set_process(true)


func _process(_delta: float) -> void:
	sock_peer.poll()

	if 0 < sock_peer.get_available_packet_count():
		var data: Dictionary = JSON.parse_string(sock_peer.get_packet().get_string_from_utf8())

		match int(data.type):
			0:
				my_id = data.id
				print("client - id set to %d" % my_id)
				connect_to_rtc_server(my_id)
			1:
				rtc_peer.get_peer(1).connection.set_remote_description(
					"offer", data.sdp)
			2:
				rtc_peer.get_peer(1).connection.set_remote_description(
					"answer", data.sdp)
			3:
				rtc_peer.get_peer(1).connection.add_ice_candidate(
					data.media, data.index, data.name)


func connect_to_rtc_server(id: int) -> void:
	rtc_peer.create_client(id)
	multiplayer.multiplayer_peer = rtc_peer

	var peer := WebRTCPeerConnection.new()
	peer.initialize({
		"iceServers" = [{"urls" = ["stun:stun.l.google.com:19302"]}]})
	rtc_peer.add_peer(peer, 1)
	peer.create_offer()

	peer.ice_candidate_created.connect(ice_candidate_created)
	peer.session_description_created.connect(session_description_created)


func ice_candidate_created(media: String, index: int, Name: String) -> void:
	print("client - ice candidate created")
	send_to_server({
		"type" = 3,
		"id" = my_id,
		"media" = media,
		"index" = index,
		"name" = Name})


func send_to_server(data: Dictionary) -> void:
	sock_peer.get_peer(1).put_packet(JSON.stringify(data).to_utf8_buffer())


func session_description_created(type: String, sdp: String) -> void:
	print("client - type is %s" % type)
	rtc_peer.get_peer(1).connection.set_local_description(type, sdp)

	send_to_server({
		"type" = 1 if type == "offer" else 2,
		"id" = my_id,
		"sdp" = sdp})

The project file also contains the native WebRTC plugin, and no issues showed up during its import

I’m testing the project using 2 Instances created using the debug menu, and when I click the Host button on one instance, then click Join on the other, the following error occur on the instance where I clicked Join:


And these are the errors that occur on the instance where I clicked Host:

This is the output result:

499800686 connected to socket server
type is offer
ice candidate created
ice candidate created
type is answer
client - id set to 499800686
client - type is offer
client - ice candidate created
client - type is answer
client - ice candidate created

I’m noticing that the ice candidate seems to be created twice, but I’m unsure as to whether it’s expected behavior, or what could be causing it to happen

I’ll also share some information about me since it’s my first post here: I’ve been using Godot for a reasonable while now, but so far sticking with single-player projects, although I’ve gotten the hang of using ENet and WebSockets for multiplayer. I’m enjoying using Godot so far, as well :grin:

I’ve got great news: I’ve managed to get WebRTC to work on a different project!

The new project’s node hierarchy is as follows: A ColorRect as the root node, named Main, and two Buttons as child nodes, one named Host and the other Join (same as in the original project)

The new project only contains one script, named Main.gd, which is attached to the root node (the ColorRect named Main). Here are its contents:

extends ColorRect

enum PacketType {id, offer, answer, candidate}

const PORT: int = 1234

var my_id: int = 0
var rtc_peer := WebRTCMultiplayerPeer.new()
var sock_peer := WebSocketMultiplayerPeer.new()


func _on_host_button_up() -> void:
	rtc_peer.create_server()
	sock_peer.create_server(PORT)
	multiplayer.multiplayer_peer = rtc_peer
	my_id = 1

	multiplayer.peer_connected.connect(rtc_peer_connected)
	sock_peer.peer_connected.connect(sock_peer_connected)


func _on_join_button_up() -> void:
	sock_peer.create_client("ws://localhost:%d" % PORT)


func _process(_delta: float) -> void:
	sock_peer.poll()

	if 0 < sock_peer.get_available_packet_count():
		var data: Dictionary = JSON.parse_string(
			sock_peer.get_packet().get_string_from_utf8())

		match int(data.type):
			PacketType.id:
				my_id = data.id
				print("client - id set to %d" % my_id)
				rtc_peer.create_client(my_id)
				multiplayer.multiplayer_peer = rtc_peer
				create_rtc_peer(1)
			PacketType.offer:
				print("%d offer from %d" % [my_id, data.id])
				rtc_peer.get_peer(data.id).connection.set_remote_description(
					"offer", data.sdp)
			PacketType.answer:
				print("%d answer from %d" % [my_id, data.id])
				rtc_peer.get_peer(data.id).connection.set_remote_description(
					"answer", data.sdp)
			PacketType.candidate:
				rtc_peer.get_peer(data.id).connection.add_ice_candidate(
					data.media, data.index, data.name)


func create_rtc_peer(id: int) -> WebRTCPeerConnection:
	var peer := WebRTCPeerConnection.new()
	peer.initialize({
		"iceServers" = [{"urls" = ["stun:stun.l.google.com:19302"]}]})
	rtc_peer.add_peer(peer, id)

	peer.ice_candidate_created.connect(ice_candidate_created.bind(id))
	peer.session_description_created.connect(session_description_created.bind(id))
	return peer


func ice_candidate_created(media: String, index: int, Name: String, id: int) -> void:
	send_to_peer(id, {
		"type" = PacketType.candidate,
		"id" = my_id,
		"media" = media,
		"index" = index,
		"name" = Name})


func rtc_peer_connected(id: int) -> void:
	print("%d connected to rtc server" % id)


func send_to_peer(id: int, data: Dictionary) -> void:
	sock_peer.get_peer(id).put_packet(JSON.stringify(data).to_utf8_buffer())


func session_description_created(type: String, sdp: String, id: int) -> void:
	rtc_peer.get_peer(id).connection.set_local_description(type, sdp)
	send_to_peer(id, {
		"type" = PacketType.offer if type == "offer" else PacketType.answer,
		"id" = my_id,
		"sdp" = sdp})


func sock_peer_connected(id: int) -> void:
	print("%d connected to socket server" % id)
	send_to_peer(id, {"type" = PacketType.id, "id" = id})
	create_rtc_peer(id).create_offer()

The main thing that was causing me trouble was that I was creating the offer on both the client and the server, and I wasn’t running things in the correct order. Here’s the order for anyone wondering: Connect the client to the socket server (this was ok in the original project) → Client and server create WebRTCPeerConnections for each other, and add them to the rtc_peer → ONLY the server creates the offer (client could also be the one to create the offer, but I haven’t tested this. As previously mentioned, both the client and the server creating the offer was causing most of the issues I was experiencing, so make sure it’s either or!) → session_description_created runs, and relays the offer and answer to the other peer → ice_candidate_created runs, and sends data to the other peer so that it adds the candidate → client successfully connects to the rtc server :smile:

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.