网站搜索

javascript - peerConnection.onicecandidate 回调未被调用(webRTC,Angular)


我是一名初学者,正在尝试制作一个 WebRTC 视频通话应用作为项目(我已经设法让它通过 websockets 工作,但在网络慢时会卡住)。我在前端使用 Angular,后端使用 Go。我遇到了一个问题,peerConnection.onicecandidate 回调没有触发。setLocalDescription 和 setRemoteDescription 方法似乎没有抛出任何错误,记录的 SDP 看起来也正常,所以问题不太可能出在后端,因为 SDP 的 offer 和 answer 都通过 websockets 正确传输。以下是应该处理连接的 Angular 服务代码:

import { HttpClient, HttpHeaders } from '@angular/common/http'
import { Injectable, OnInit } from '@angular/core'
import { from, lastValueFrom, Observable } from 'rxjs'
import { Router } from '@angular/router';

interface Member {
memberID: string
name: string
conn: RTCPeerConnection | null
}

@Injectable({
providedIn: 'root'
})
export class ApiService {

    constructor(private http: HttpClient, private router: Router) { }
    
    // members data
    public stableMembers: Member[] = []
    
    // private httpUrl = 'https://callgo-server-386137910114.europe-west1.run.app'
    // private webSocketUrl = 'wss://callgo-server-386137910114.europe-west1.run.app/ws'
    private httpUrl = 'http://localhost:8080'
    private webSocketUrl = 'http://localhost:8080/ws'
    
    // http
    createSession(): Promise<any> {
        return lastValueFrom(this.http.post(`${this.httpUrl}/initialize`, null))
    }
    
    kickSession(sessionID: string, memberID: string, password: string): Promise<any> {
        return lastValueFrom(this.http.post(`${this.httpUrl}/disconnect`, {
            "sessionID":`${sessionID}`,
            "memberID":`${memberID}`,
            "password":`${password}`
        }))
    }
    
    // websocket
    private webSocket!: WebSocket
    
    // stun server
    private config = {iceServers: [{ urls: ['stun:stun.l.google.com:19302', 'stun:stun2.1.google.com:19302'] }]}
    
    // callbacks that other classes can define using their context, but apiService calls them
    public initMemberDisplay = (newMember: Member) => {}
    public initMemberCamera = (newMember: Member) => {}
    
    async connect(sessionID: string, displayName: string) {
        console.log(sessionID)
    
        this.webSocket = new WebSocket(`${this.webSocketUrl}?sessionID=${sessionID}&displayName=${displayName}`)
    
        this.webSocket.onopen = (event: Event) => {
            console.log('WebSocket connection established')
        }
    
        this.webSocket.onmessage = async (message: MessageEvent) => {
            const data = JSON.parse(message.data)
            
            // when being asigned an ID
            if(data.type == "assignID") {
                sessionStorage.setItem("myID", data.memberID)
                this.stableMembers.push({
                    "name": data.memberName,
                    "memberID": data.memberID,
                    "conn": null
                })
            } 
    
            // when being notified about who is already in the meeting (on meeting join)
            if(data.type == "exist") {
                this.stableMembers.push({
                    "name": data.memberName,
                    "memberID": data.memberID,
                    "conn": null
                })
            }
    
            // when being notified about a new joining member
            if(data.type == "join") {
                // webRTC
                const peerConnection = new RTCPeerConnection(this.config)
                // send ICE
                peerConnection.onicecandidate = (event: RTCPeerConnectionIceEvent) => {
                    console.log(event)
                    event.candidate && console.log(event.candidate)
                }
                // send SDP
                try {
                    await peerConnection.setLocalDescription(await peerConnection.createOffer())
                    this.sendSDP(peerConnection.localDescription!, data.memberID, sessionStorage.getItem("myID")!)
                } catch(error) {
                    console.log(error)
                }
    
                this.stableMembers.push({
                    "name": data.memberName,
                    "memberID": data.memberID,
                    "conn": peerConnection
                })
            }
    
            // on member disconnect notification
            if(data.type == "leave") {
                this.stableMembers = this.stableMembers.filter(member => member.memberID != data.memberID)
            }
    
            // on received SDP
            if(data.sdp) {
                if(data.sdp.type == "offer") {
                    const peerConnection = new RTCPeerConnection(this.config)
                    try {
                        const findWithSameID = this.stableMembers.find(member => member?.memberID == data?.from)
                        findWithSameID!.conn = peerConnection
                        await peerConnection.setRemoteDescription(new RTCSessionDescription(data.sdp))
                        const answer: RTCSessionDescriptionInit = await peerConnection.createAnswer()
                        await peerConnection.setLocalDescription(answer)
                        this.sendSDP(answer, data.from, sessionStorage.getItem("myID")!)
    
                        this.initMemberDisplay(findWithSameID!)
                        this.initMemberCamera(findWithSameID!)
                    } catch(error) {
                        console.log(error)
                    }
                }
    
                if(data.sdp.type == "answer") {
                    try {
                        const findWithSameID = this.stableMembers.find(member => member?.memberID == data?.from)
                        await findWithSameID!.conn!.setRemoteDescription(new RTCSessionDescription(data.sdp))
    
                        this.initMemberDisplay(findWithSameID!)
                        this.initMemberCamera(findWithSameID!)
                    } catch(error) {
                        console.log(error)
                    }
                }
            }
        }
    
        this.webSocket.onclose = () => {
            console.log('WebSocket connection closed')
            this.stableMembers = []
            this.router.navigate(['/menu'])
        }
    
        this.webSocket.onerror = (error) => {
            console.error('WebSocket error:', error)
        }
    }   
    
    close() {
        if(this.webSocket && this.webSocket.readyState === WebSocket.OPEN) {
            this.webSocket.close()
        } else {
            console.error('WebSocket already closed.')
        }
    }
    
    sendSDP(sdp: RTCSessionDescriptionInit, to: string, from: string) {
        this.webSocket.send(JSON.stringify({
            "to": to,
            "from": from,
            "sdp": sdp
        }))
    }

}

作为一个快速说明,stableMembers 保存了客户端上所有成员的引用,其余的代码会根据需要修改它。回调函数 initMemberDisplay 和 initMemberCamera 应由其他组件定义,并用于处理接收和发送视频轨道。我还没有在前端或后端实现任何与 ICE 相关的功能,但当我尝试时,我注意到 onicecandidate 回调根本不会被调用。我正在使用免费的已知 Google STUN 服务器:private config={iceServers: [{ urls: ['stun:stun.l.google.com:19302', 'stun:stun2.1.google.com:19302'] }] }。如果你想查看其余的代码,仓库在这里:https://github.com/HoriaBosoanca/callgo-client 。在 README 中有一个指向后端代码的链接。

我尝试从 peerConnection.onicecandidate=(event: RTCPeerConnectionIceEvent) => {console.log(event)} 回调记录事件,但我注意到没有任何东西被记录。

答案#1

由于您没有创建数据通道,也没有向您的对等连接添加音频或视频流,因此创建的 SDP 将不包含任何“m=”行(应用、音频或视频)。由于 ICE 候选项是为这些“m=”行收集的,而您没有任何“m=”行,因此不会收集 ICE 候选项,也不会触发 onicecandidate 事件。