I carefully followed the WebRTC Documentation (https://webrtc.org/getting-started/overview), went great, but when the peers exchange the ICE, it goes wrong. Im using Google's STUN servers, and installed CoTurn (sudo pacman -S coturn) temporally for testing locally.
Configuration:
const configuration = {
'iceServers': [
{ 'urls': 'stun:stun.l.google.com:19302' },
{ urls: 'turn:192.168.1.71:3478', username: 'username', credential: 'password' }
]
}
Heres my client side code written in TypeScript (Angular). It's using Websockets to communicate with the API:
var peerConnection = new RTCPeerConnection(configuration);
peerConnection.addEventListener('connectionstatechange', event => {
if (peerConnection.connectionState === 'connected') {
console.warn("SUCCESS! PEERS CONNECTED")
}
});
this.checkloginservice.socket$.subscribe(
async (message) => {
let action = (message as websocketDef).action
switch (action) {
case "offer":
peerConnection.addEventListener('icecandidate', (event) => {
if (event.candidate) {
console.log(event.candidate)
this.checkloginservice.socket$.next({
Action: 'icecandidate',
Value: JSON.stringify({
for: this.callData.peerdata.userid,
icecandidate: JSON.stringify(event.candidate),
action: "icecandidate"
})
})
}
});
let data = message as incomingCallData
this.incomingCall = {
callerDisplayname: data.displayName,
callerId: data.userid,
callerPfp: data.pfp,
chatid: data.chatid,
}
setTimeout(() => {
this.document.querySelector(".ans")?.addEventListener("click", async () => {
peerConnection.setRemoteDescription(new RTCSessionDescription(JSON.parse(data.offer)))
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
this.checkloginservice.socket$.next({
Action: "answer",
Value: JSON.stringify({
for: data.userid,
answer: JSON.stringify(answer),
action: "answer"
})
})
})
}, 10);
break;
case "answer":
peerConnection.addEventListener('icecandidate', (event) => {
if (event.candidate) {
console.log(event.candidate)
this.checkloginservice.socket$.next({
Action: 'icecandidate',
Value: JSON.stringify({
for: this.callData.peerdata.userid,
icecandidate: JSON.stringify(event.candidate),
action: "icecandidate"
})
})
}
});
let answerData = message as answerCallData
const remoteDesc = new RTCSessionDescription(JSON.parse(answerData.answer));
await peerConnection.setRemoteDescription(remoteDesc);
break;
case "icecandidate":
let icacand = message as iceCandData
try {
await peerConnection.addIceCandidate(JSON.parse(icacand.icecandidate));
} catch (e) {
console.error('Error adding received ice candidate', e);
}
}
}
)
this.msgService.callFriend$.subscribe(async (calldata) => {
if (calldata.friendid != '') {
if (await this.chkMicPerms()) {
let stream = await this.getUserMedia(false)
if (stream == typeof String) {
this.toastService.add({
severity: "error",
summary: this.translateService.instant("callFailed"),
detail: this.translateService.instant(stream)
})
} else {
localStream = stream as MediaStream
this.callData.peerdata.userid = calldata.friendid
this.callData.peerdata.displayName = calldata.friendname;
this.callData.peerdata.pfp = calldata.friendpfp;
this.callData.chatid = calldata.chatid;
const offer = await peerConnection.createOffer({
offerToReceiveAudio: true
});
await peerConnection.setLocalDescription(offer)
this.checkloginservice.socket$.next({
Action: 'offer',
Value: JSON.stringify({
for: calldata.friendid,
offer: JSON.stringify(offer),
username: userData.username,
userid: userData.userid,
displayName: userData.displayName,
pfp: userData.pfp,
chatid: calldata.chatid,
action: "offer"
})
})
}
} else {
this.toastService.add({
severity: "error",
summary: this.translateService.instant("callFailed"),
detail: this.translateService.instant("provideMic")
})
}
}
})
Server-side code written in go:
var (
upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
Rm = &roomManager{
rooms: make(map[string]*Client),
mu: sync.Mutex{},
}
)
func (rm *roomManager) Broadcast(message interface{}, userid string) {
rm.mu.Lock()
defer rm.mu.Unlock()
for _, userConnection := range rm.rooms {
if userConnection.Connection != nil && userConnection.Userid == userid {
// if err := userConnection.Connection.WriteJSON(message); err != nil {}
userConnection.Connection.WriteJSON(message)
}
}
}
func HandleWebSocket(c *gin.Context) {
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
return
}
client := &Client{
Connection: conn,
}
for {
_, p, err := conn.ReadMessage()
if err != nil {
Rm.removeUser(client.Userid)
return
}
var message config.Websocket_DefaultMessage
if err := json.Unmarshal(p, &message); err != nil {
return
}
switch message.Action {
case "joinRealtime":
Rm.connectUser(conn, message.Value)
client.Userid = message.Value
Rm.Broadcast("Connected to WebSocket /user", message.Value)
case "offer":
type offerData struct {
For string `json:"for"`
Offer string `json:"offer"`
Username string `json:"username"`
Userid string `json:"userid"`
DisplayName string `json:"displayName"`
Pfp string `json:"pfp"`
Chatid string `json:"chatid"`
Action string `json:"action"`
}
var offer offerData
if err := json.Unmarshal([]byte(message.Value), &offer); err != nil {
return
}
Rm.Broadcast(offer, offer.For)
case "answer":
type answerData struct {
For string `json:"for"`
Answer string `json:"answer"`
Action string `json:"action"`
}
var answer answerData
if err := json.Unmarshal([]byte(message.Value), &answer); err != nil {
return
}
Rm.Broadcast(answer, answer.For)
case "icecandidate":
type iceCandData struct {
For string `json:"for"`
Action string `json:"action"`
Icecandidate string `json:"icecandidate"`
}
var iceCand iceCandData
if err := json.Unmarshal([]byte(message.Value), &iceCand); err != nil {
return
}
Rm.Broadcast(iceCand, iceCand.For)
}
}
}
Help would be appreciated. Thanks!