You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular+Firefox环境下WebRTC ICE失败问题求助

WebRTC ICE交换错误排查求助

严格遵循WebRTC开发流程,整体流程顺畅,但在对等端交换ICE时出现错误。使用谷歌STUN服务器,本地测试时临时安装了CoTurn(sudo pacman -S coturn)。

配置信息

const configuration = {
          'iceServers': [
            { 'urls': 'stun:stun.l.google.com:19302' },
            { urls: 'turn:192.168.1.71:3478', username: 'username', credential: 'password' }
          ]
        }

Angular客户端TypeScript代码

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")
      })
    }
  }
})

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)
        }
    }
}

内容的提问来源于stack exchange,提问作者Alms

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 15:22:05