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 事件。