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

基于JavaScript类实现动态RTCPeerConnection的问题求助

问题修复与思路验证

核心问题分析

你的代码存在三个关键问题:

  • PeerConnection类中事件监听器的语法错误(直接在类体中写this.connection.ontrack不符合类语法规范)
  • setRemoteDescriptionFromOffer中调用createAnswer时的引用错误(未通过类实例调用)
  • createAnswer方法中存在未定义变量answer的错误

另外,事件监听时的this指向需要注意,避免丢失类实例上下文。

修复后的PeerConnection类代码

class PeerConnection {
    constructor(onRemoteStreamReceived) {
        this.connection = new RTCPeerConnection(null);
        this.remoteStream = null;
        this.candidates = [];
        // 保存回调,用于通知外部收到远程流
        this.onRemoteStreamReceived = onRemoteStreamReceived;

        // 初始化ontrack事件监听
        this.connection.ontrack = (e) => {
            console.log("onTrack", e.streams[0]);
            this.remoteStream = e.streams[0];
            // 通知外部组件获取远程流
            if (this.onRemoteStreamReceived) {
                this.onRemoteStreamReceived(this.remoteStream);
            }
        };

        // 初始化onicecandidate事件监听
        this.connection.onicecandidate = (e) => {
            if (e.candidate != null) {
                this.candidates.push(e.candidate);
                console.log("sendet Candidate", JSON.stringify(e.candidate));
                // 这里后续可以通过WebSocket发送候选,比如:
                // webSocket.send(JSON.stringify({ type: "candidate", candidate: e.candidate }));
            }
        };
    }

    createOffer() {
        console.log("offer from JS");
        this.connection.createOffer({
            offerToReceiveAudio: 1,
            offerToReceiveVideo: 1,
        }).then(async sdp => {
            console.log(JSON.stringify(sdp));
            if (!this.connection.localDescription) {
                await this.connection.setLocalDescription(sdp);
                // 这里需要将offer通过WebSocket发送给对方,比如:
                // webSocket.send(JSON.stringify(sdp));
            }
        });
    }

    createAnswer() {
        console.log("answer");
        this.connection.createAnswer({
            offerToReceiveAudio: 1,
            offerToReceiveVideo: 1,
        }).then(async sdp => {
            console.log(JSON.stringify(sdp));
            await this.connection.setLocalDescription(sdp);
            // 这里需要将answer通过WebSocket发送给对方
            // webSocket.send(JSON.stringify(sdp));
        });
    }

    async setRemoteDescriptionFromOffer(sdp) {
        console.log("remoteDescription Offer");
        console.log(this.connection);
        try {
            await this.connection.setRemoteDescription(new RTCSessionDescription(sdp));
            // 成功设置远程offer后,调用类自身的createAnswer方法
            this.createAnswer();
        } catch (e) {
            console.log(`failure MESSAGE:${e.name}`);
        }
    }

    async setRemoteDescriptionFromAnswer(sdp) {
        console.log("remoteDescription Answer");
        try {
            await this.connection.setRemoteDescription(new RTCSessionDescription(sdp));
            // 如果之前收集了ICE候选,可以在这里添加
            this.candidates.forEach(candidate => {
                this.connection.addIceCandidate(candidate);
            });
            this.candidates = [];
        } catch (e) {
            console.log(`failure MESSAGE:${e.name}`);
        }
    }

    // 添加ICE候选的方法
    addIceCandidate(candidate) {
        if (this.connection.remoteDescription) {
            this.connection.addIceCandidate(new RTCIceCandidate(candidate));
        } else {
            // 远程描述未设置时,先缓存候选
            this.candidates.push(candidate);
        }
    }
}

export { PeerConnection }

关键修改点说明

  1. 事件监听移至constructor:将ontrack和onicecandidate的初始化逻辑放在constructor中,符合类的语法规范。
  2. 箭头函数确保this指向:使用箭头函数绑定事件回调,保证回调中的this始终指向PeerConnection实例,避免上下文丢失。
  3. 修复createAnswer调用:在setRemoteDescriptionFromOffer中,将createAnswer()改为this.createAnswer(),通过类实例调用方法。
  4. 移除未定义变量:删除createAnswer中的console.log(answer),因为answer未定义。
  5. 添加远程流回调:在constructor中接收onRemoteStreamReceived回调,当收到远程流时通知外部组件(比如App.jsx)更新视频元素。
  6. ICE候选缓存机制:添加候选缓存逻辑,在远程描述未设置时先缓存候选,设置后再批量添加。

修复后的App.jsx关键部分调整

// ... 其他代码保持不变
useEffect(() => {
    // 创建PeerConnection实例时传入远程流回调
    const peerConn = new PeerConnection((remoteStream) => {
        remoteVideoStream.current.srcObject = remoteStream;
        remoteAudioStream.current.srcObject = remoteStream;
    });
    peerConnections.current.push(peerConn);

    getMedia();
}, [])

// 修改getMedia中的addTrack逻辑,确保peerConnection实例已创建
async function getMedia() {
    const constraints = {
        audio: true,
        video: true,
    }
    console.log("getStream");
    try {
        const stream = await navigator.mediaDevices.getUserMedia(constraints);
        console.log("stream", stream);
        localVideoStream.current.srcObject = stream;
        localAudioStream.current.srcObject = stream;

        localVideoStream.current.muted = true;
        localAudioStream.current.muted = true;

        // 确保peerConnections中有实例
        if (peerConnections.current.length > 0) {
            stream.getTracks().forEach(track => {
                peerConnections.current[0].connection.addTrack(track, stream);
            });
        }
    } catch (err) {
        console.error("获取媒体流失败:", err);
    }
}

App.jsx修改说明

  • 在创建PeerConnection实例时传入回调函数,当收到远程流时自动更新视频和音频元素的srcObject。
  • 优化getMedia方法,添加try-catch捕获媒体流获取失败的错误,并确保peerConnections实例存在后再添加轨道。

动态连接思路验证

你的思路是正确的:为每个加入会议的用户创建一个独立的PeerConnection实例,这是WebRTC多人会议中最常用的星型拓扑方案。每个客户端与其他客户端建立一对一的PeerConnection,这种方案实现简单,适合中小规模的会议场景。

如果后续需要支持更大规模的会议,可以考虑引入SFU(Selective Forwarding Unit)服务器来转发媒体流,减少客户端的带宽消耗和连接数量,但当前的星型拓扑方案对于初期开发和小规模会议完全适用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 20:47:03