Flutter+Firebase+WebRTC视频通话:主叫方无法查看被叫方视频
Flutter WebRTC 视频通话:发起方无法看到接收方视频的排查与修复
以下是针对你遇到的「用户A发起通话后能听到B的音频,但看不到B的视频,B能正常看到A」问题的常见排查点和修复方案:
1. 接收方(B)未将本地视频轨道添加到PeerConnection
这是最常见的原因:B端成功获取本地摄像头流,但未将视频轨道注入到WebRTC的PeerConnection中,导致A端无法收到B的视频媒体数据。
- 排查:检查B端代码,确认在获取
localStream后,是否调用了peerConnection.addStream(localStream)或peerConnection.addTrack(videoTrack, localStream)。 - 修复:在B端初始化本地媒体流后,立即执行轨道添加操作,且要在创建Answer SDP之前完成:
// 获取本地媒体流后 final localStream = await navigator.mediaDevices.getUserMedia(mediaConstraints); // 添加所有轨道到PeerConnection for (final track in localStream.getTracks()) { await peerConnection.addTrack(track, localStream); } // 再创建Answer final answer = await peerConnection.createAnswer(); await peerConnection.setLocalDescription(answer);
2. SDP交换时未启用视频接收/发送
如果SDP中缺失视频媒体描述,A端无法识别B的视频轨道。
- 排查:检查两端创建Offer/Answer时的约束,是否设置了
offerToReceiveVideo: true;同时确认媒体约束中包含视频请求。 - 修复:
- 统一两端的媒体约束:
final mediaConstraints = { 'audio': true, 'video': { 'facingMode': 'user', // 前置摄像头 'width': 640, 'height': 480 } }; - 创建Offer/Answer时明确启用视频接收:
// A端创建Offer final offer = await peerConnection.createOffer({ 'offerToReceiveAudio': true, 'offerToReceiveVideo': true, }); await peerConnection.setLocalDescription(offer); // B端创建Answer(也建议显式设置) final answer = await peerConnection.createAnswer({ 'offerToReceiveAudio': true, 'offerToReceiveVideo': true, }); await peerConnection.setLocalDescription(answer);
- 统一两端的媒体约束:
3. 发起方(A)未正确处理远程视频轨道
A端可能未监听WebRTC的轨道回调,导致收到B的视频轨道后无法挂载到渲染组件。
- 排查:检查A端是否设置了
onTrack或onAddStream回调,且正确处理了视频轨道。 - 修复:
使用WebRTC推荐的onTrack回调处理远程轨道:
若使用旧版API,同时保留peerConnection.onTrack = (RTCTrackEvent event) { // 区分视频和音频轨道 if (event.track.kind == 'video') { // 将远程流绑定到视频渲染器 remoteVideoRenderer.srcObject = event.streams.first; } };onAddStream兼容:peerConnection.onAddStream = (MediaStream stream) { remoteVideoRenderer.srcObject = stream; };
4. 接收方(B)摄像头权限异常
虽然音频正常,但B端可能未获取到摄像头权限,导致无法生成视频轨道。
- 排查:检查设备权限设置,确认B端APP已获得摄像头访问权限;同时检查代码中是否主动请求了权限。
- 修复:
- 配置平台权限:
- Android(
android/app/src/main/AndroidManifest.xml):<uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera" /> - iOS(
ios/Runner/Info.plist):<key>NSCameraUsageDescription</key> <string>需要访问摄像头进行视频通话</string>
- Android(
- 代码中主动请求权限(需依赖
permission_handler插件):import 'package:permission_handler/permission_handler.dart'; // 获取媒体流前先请求权限 final cameraStatus = await Permission.camera.request(); final micStatus = await Permission.microphone.request(); if (cameraStatus != PermissionStatus.granted || micStatus != PermissionStatus.granted) { // 提示用户开启权限 return; } // 再获取本地媒体流 final localStream = await navigator.mediaDevices.getUserMedia(mediaConstraints);
- 配置平台权限:
5. 信令通道(Firebase)丢失视频相关数据
Firebase传递SDP或ICE Candidate时,可能因异步逻辑或数据格式问题,导致A端未收到完整的视频媒体信息。
- 排查:在信令发送/接收处添加日志,确认B的Answer SDP中包含
m=video条目,且所有ICE Candidate都已传递到A端。 - 修复:
- 确保SDP以字符串形式完整传递,避免JSON序列化/反序列化时丢失字段;
- ICE Candidate要实时发送,不要批量延迟传递,确保A端能及时添加所有Candidate:
// B端监听ICE Candidate生成事件,实时发送到Firebase peerConnection.onIceCandidate = (RTCIceCandidate candidate) { // 通过Firebase发送candidate到A端 firebaseFirestore.collection('calls').doc(callId).update({ 'candidates': FieldValue.arrayUnion([candidate.toMap()]) }); }; // A端监听Firebase的candidates更新,及时添加到PeerConnection firebaseFirestore.collection('calls').doc(callId).snapshots().listen((snapshot) { final candidates = snapshot.data()?['candidates'] ?? []; for (final candidateMap in candidates) { final candidate = RTCIceCandidate.fromMap(candidateMap); peerConnection.addCandidate(candidate); } });
内容的提问来源于stack exchange,提问作者Yash
相关产品推荐
相关产品推荐

