flutter_webrtc二次呼叫报错:peerConnectionSetRemoteDescription状态异常
问题原因与解决方案
核心问题分析
第二次呼叫报错的根源是首次呼叫后的资源未彻底清理,导致旧的PeerConnection实例、Socket事件监听残留,第二次呼叫时操作了已处于closed/stable状态的旧连接。具体问题点:
- Socket事件监听未移除:每次创建控制器时绑定的
IceCandidate、callAnswered等监听,退出时未销毁,第二次呼叫时会触发旧监听操作已关闭的PeerConnection。 - PeerConnection未置空:
disposeConnection仅调用close()但未将_rtcPeerConnection设为null,第二次创建时可能复用旧实例引用。 - 媒体资源释放不彻底:
forEach循环中使用await track.stop()无效(forEach不等待异步操作),导致轨道未完全停止就执行后续销毁逻辑。 - Ice候选列表未清空:第二次呼叫时
rtcIceCandidates保留旧候选,可能引发错误。 - Renderer未彻底销毁:注释了
_localRTCVideoRenderer.dispose()和_remoteRTCVideoRenderer.dispose(),导致资源泄漏。
具体修复代码
1. 完善资源销毁方法disposeConnection
Future disposeConnection() async { // 修复轨道停止的异步问题:用for循环替代forEach,确保每个track都完成stop if (_localStream != null) { for (var track in _localStream!.getVideoTracks()) { await track.stop(); } for (var track in _localStream!.getAudioTracks()) { await track.stop(); } await _localStream!.dispose(); _localStream = null; } // 关闭并置空PeerConnection if (_rtcPeerConnection != null) { await _rtcPeerConnection!.close(); _rtcPeerConnection = null; } // 销毁Renderer并置空引用 await _localRTCVideoRenderer.dispose(); await _remoteRTCVideoRenderer.dispose(); // 清空Ice候选列表 rtcIceCandidates.clear(); // 移除所有Socket事件监听,避免残留 socket!.off("IceCandidate"); socket!.off("callAnswered"); socket!.off("isExitCall"); socket!.off("isCancelCall"); }
2. 修改setupPeerConnection中的Socket事件绑定逻辑
在绑定新的Socket监听前,先移除旧监听(避免重复绑定):
// for Incoming call if (offer != null) { // 先移除旧监听 socket!.off("IceCandidate"); socket!.on("IceCandidate", (data) { String candidate = data["iceCandidate"]["candidate"]; String sdpMid = data["iceCandidate"]["id"]; int sdpMLineIndex = data["iceCandidate"]["label"]; // 增加空判断,避免操作已销毁的PeerConnection if (_rtcPeerConnection != null) { _rtcPeerConnection!.addCandidate(RTCIceCandidate( candidate, sdpMid, sdpMLineIndex, )); } }); // ... 其余代码不变 } // for Outgoing call else { // 先移除旧监听 socket!.off("callAnswered"); socket!.on("callAnswered", (data) { // 增加空判断 if (_rtcPeerConnection != null) { _rtcPeerConnection!.setRemoteDescription( RTCSessionDescription( data["sdpAnswer"]["sdp"], data["sdpAnswer"]["type"], ), ); } // ... 其余代码不变 }); }
3. 完善onClose方法
确保销毁逻辑执行完成后再调用父类方法:
@override void onClose() async { await disposeConnection(); super.onClose(); }
4. 增加PeerConnection操作的空安全判断
在所有操作_rtcPeerConnection和_localStream的地方增加空判断,避免空指针或操作已销毁的实例:
toggleMic() { isAudioOn = !isAudioOn; if (_localStream != null) { _localStream!.getAudioTracks().forEach((track) { track.enabled = isAudioOn; }); } update(); } toggleCamera() { isVideoOn = !isVideoOn; if (_localStream != null) { _localStream!.getVideoTracks().forEach((track) { track.enabled = isVideoOn; }); } update(); }
额外注意事项
- 确保每次呼叫都创建全新的
VideoCallController实例,不要复用旧控制器。 - 检查Socket连接是否在每次呼叫后保持稳定,避免连接异常导致的事件监听混乱。
内容的提问来源于stack exchange,提问作者BodyKh
相关产品推荐
相关产品推荐

