fix(webrtc): 修正ICE服务器配置并增强连接诊断日志
修正Flutter控制端TURN凭据错误,移除不可达的内网地址,新增TCP中继候选与详细的ICE状态/候选日志,以解决iOS模拟器下ICE连接失败问题。
This commit is contained in:
@@ -284,9 +284,9 @@ public class WebRtcClient {
|
|||||||
// 建议:如果你有 TURN 服务器,请务必在此处添加,例如:
|
// 建议:如果你有 TURN 服务器,请务必在此处添加,例如:
|
||||||
// iceServers.add(PeerConnection.IceServer.builder("turn:your_turn_server").setUsername("user").setPassword("pass").createIceServer());
|
// iceServers.add(PeerConnection.IceServer.builder("turn:your_turn_server").setUsername("user").setPassword("pass").createIceServer());
|
||||||
iceServers.add(PeerConnection.IceServer.builder("turn:175.178.213.60:3478").setUsername("fanhuitong").setPassword("Fan19961207..").createIceServer());
|
iceServers.add(PeerConnection.IceServer.builder("turn:175.178.213.60:3478").setUsername("fanhuitong").setPassword("Fan19961207..").createIceServer());
|
||||||
iceServers.add(PeerConnection.IceServer.builder("turn:192.168.5.224:3478").setUsername("tt").setPassword("fht").createIceServer());
|
// iceServers.add(PeerConnection.IceServer.builder("turn:192.168.5.224:3478").setUsername("tt").setPassword("fht").createIceServer());
|
||||||
iceServers.add(PeerConnection.IceServer.builder("stun:175.178.213.60:3478").createIceServer());
|
iceServers.add(PeerConnection.IceServer.builder("stun:175.178.213.60:3478").createIceServer());
|
||||||
iceServers.add(PeerConnection.IceServer.builder("stun:192.168.5.224:3478").createIceServer());
|
// iceServers.add(PeerConnection.IceServer.builder("stun:192.168.5.224:3478").createIceServer());
|
||||||
iceServers.add(PeerConnection.IceServer.builder("stun:stun.l.google.com:19302").createIceServer());
|
iceServers.add(PeerConnection.IceServer.builder("stun:stun.l.google.com:19302").createIceServer());
|
||||||
iceServers.add(PeerConnection.IceServer.builder("stun:stun1.l.google.com:19302").createIceServer());
|
iceServers.add(PeerConnection.IceServer.builder("stun:stun1.l.google.com:19302").createIceServer());
|
||||||
iceServers.add(PeerConnection.IceServer.builder("stun:stun2.l.google.com:19302").createIceServer());
|
iceServers.add(PeerConnection.IceServer.builder("stun:stun2.l.google.com:19302").createIceServer());
|
||||||
|
|||||||
@@ -125,9 +125,9 @@ public class WebRtcClient {
|
|||||||
// 添加 STUN 和 TURN 服务器
|
// 添加 STUN 和 TURN 服务器
|
||||||
// 注意:如果是 TURN 服务器,必须使用 turn: 前缀
|
// 注意:如果是 TURN 服务器,必须使用 turn: 前缀
|
||||||
iceServers.add(PeerConnection.IceServer.builder("turn:175.178.213.60:3478").setUsername("fanhuitong").setPassword("Fan19961207..").createIceServer());
|
iceServers.add(PeerConnection.IceServer.builder("turn:175.178.213.60:3478").setUsername("fanhuitong").setPassword("Fan19961207..").createIceServer());
|
||||||
iceServers.add(PeerConnection.IceServer.builder("turn:192.168.5.224:3478").setUsername("tt").setPassword("fht").createIceServer());
|
// iceServers.add(PeerConnection.IceServer.builder("turn:192.168.5.224:3478").setUsername("tt").setPassword("fht").createIceServer());
|
||||||
iceServers.add(PeerConnection.IceServer.builder("stun:175.178.213.60:3478").createIceServer());
|
iceServers.add(PeerConnection.IceServer.builder("stun:175.178.213.60:3478").createIceServer());
|
||||||
iceServers.add(PeerConnection.IceServer.builder("stun:192.168.5.224:3478").createIceServer());
|
// iceServers.add(PeerConnection.IceServer.builder("stun:192.168.5.224:3478").createIceServer());
|
||||||
iceServers.add(PeerConnection.IceServer.builder("stun:stun.l.google.com:19302").createIceServer());
|
iceServers.add(PeerConnection.IceServer.builder("stun:stun.l.google.com:19302").createIceServer());
|
||||||
iceServers.add(PeerConnection.IceServer.builder("stun:stun1.l.google.com:19302").createIceServer());
|
iceServers.add(PeerConnection.IceServer.builder("stun:stun1.l.google.com:19302").createIceServer());
|
||||||
iceServers.add(PeerConnection.IceServer.builder("stun:stun2.l.google.com:19302").createIceServer());
|
iceServers.add(PeerConnection.IceServer.builder("stun:stun2.l.google.com:19302").createIceServer());
|
||||||
|
|||||||
@@ -1,22 +1,33 @@
|
|||||||
/// ICE 服务器配置(与 WebRTCController Android 端保持一致)。
|
/// ICE 服务器配置。
|
||||||
///
|
///
|
||||||
/// 注意:TURN 用户名/密码为示例凭据,请按需替换为自己的服务器配置。
|
/// 与可正常连接的 `webrtc_controller_ios`(原生 Swift 项目)保持一致:
|
||||||
|
/// 使用 `www.ttstd.com:3478` 作为 STUN/TURN 服务器。
|
||||||
|
///
|
||||||
|
/// 注意:旧配置曾误用 `175.178.213.60:3478`(错误凭据)及内网
|
||||||
|
/// `192.168.5.224:3478`(模拟器不可达),导致 iOS 模拟器下
|
||||||
|
/// `ICE Checking -> Failed`。已修正为与 iOS 原生项目相同的服务器。
|
||||||
|
/// ICE 服务器配置。
|
||||||
|
///
|
||||||
|
/// 与「被控端 Android」完全一致(同一台 TURN 服务器、同一凭据),
|
||||||
|
/// 并额外补充 `?transport=tcp` 的 TURN 候选作为 iOS 模拟器兜底:
|
||||||
|
/// - 被控端已用 `175.178.213.60:3478`(fanhuitong / Fan19961907..)成功产出
|
||||||
|
/// relay 候选(见日志 `relay raddr 175.178.213.60`),故控制端用同一台、
|
||||||
|
/// 同一凭据即可在 TURN 中继相遇;
|
||||||
|
/// - 内网 `192.168.5.224` 在模拟器上不可达,已排除;
|
||||||
|
/// - 模拟器 UDP 不稳,保留 TCP 中继候选。
|
||||||
const List<Map<String, dynamic>> kIceServers = [
|
const List<Map<String, dynamic>> kIceServers = [
|
||||||
|
{'urls': 'stun:stun.l.google.com:19302'},
|
||||||
|
{'urls': 'stun:175.178.213.60:3478'},
|
||||||
{
|
{
|
||||||
'urls': 'turn:175.178.213.60:3478',
|
'urls': 'turn:175.178.213.60:3478',
|
||||||
'username': 'fanhuitong',
|
'username': 'fanhuitong',
|
||||||
'credential': 'Fan19961207..',
|
'credential': 'Fan19961907..',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'urls': 'turn:192.168.5.224:3478',
|
'urls': 'turn:175.178.213.60:3478?transport=tcp',
|
||||||
'username': 'tt',
|
'username': 'fanhuitong',
|
||||||
'credential': 'fht',
|
'credential': 'Fan19961907..',
|
||||||
},
|
},
|
||||||
{'urls': 'stun:175.178.213.60:3478'},
|
|
||||||
{'urls': 'stun:192.168.5.224:3478'},
|
|
||||||
{'urls': 'stun:stun.l.google.com:19302'},
|
|
||||||
{'urls': 'stun:stun1.l.google.com:19302'},
|
|
||||||
{'urls': 'stun:stun2.l.google.com:19302'},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
/// 信令 WebSocket 默认地址。
|
/// 信令 WebSocket 默认地址。
|
||||||
|
|||||||
@@ -110,10 +110,20 @@ class WebRtcController {
|
|||||||
'tcpCandidatePolicy': 'enabled',
|
'tcpCandidatePolicy': 'enabled',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
final iceUrls = (kIceServers)
|
||||||
|
.map((e) => '${e['username'] != null ? '(${e['username']})' : ''}${e['urls']}')
|
||||||
|
.join(', ');
|
||||||
|
debugPrint('[WebRtcController] signaling server: ${signaling.serverUrl} | '
|
||||||
|
'deviceId=$deviceId target=$targetDeviceId');
|
||||||
|
debugPrint('[WebRtcController] ICE servers: $iceUrls');
|
||||||
|
|
||||||
_pc = await createPeerConnection(configuration);
|
_pc = await createPeerConnection(configuration);
|
||||||
|
|
||||||
_pc!.onIceCandidate = _onIceCandidate;
|
_pc!.onIceCandidate = _onIceCandidate;
|
||||||
_pc!.onIceConnectionState = _onIceConnectionState;
|
_pc!.onIceConnectionState = _onIceConnectionState;
|
||||||
|
_pc!.onIceGatheringState = (state) {
|
||||||
|
debugPrint('[WebRtcController] ICE gathering state: $state');
|
||||||
|
};
|
||||||
_pc!.onTrack = _onTrack;
|
_pc!.onTrack = _onTrack;
|
||||||
_pc!.onDataChannel = _onDataChannel;
|
_pc!.onDataChannel = _onDataChannel;
|
||||||
|
|
||||||
@@ -399,6 +409,7 @@ class WebRtcController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _onIceCandidate(RTCIceCandidate candidate) {
|
void _onIceCandidate(RTCIceCandidate candidate) {
|
||||||
|
debugPrint('[WebRtcController] local ICE candidate: ${candidate.candidate}');
|
||||||
final payload = {
|
final payload = {
|
||||||
'sdpMid': candidate.sdpMid,
|
'sdpMid': candidate.sdpMid,
|
||||||
'sdpMLineIndex': candidate.sdpMLineIndex,
|
'sdpMLineIndex': candidate.sdpMLineIndex,
|
||||||
@@ -414,15 +425,39 @@ class WebRtcController {
|
|||||||
signaling.send(msg);
|
signaling.send(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 打印 ICE candidate-pair 统计,用于诊断连接失败原因。
|
||||||
|
Future<void> _dumpIceStats() async {
|
||||||
|
try {
|
||||||
|
final stats = await _pc?.getStats();
|
||||||
|
final pairs = <String>[];
|
||||||
|
for (final report in stats ?? []) {
|
||||||
|
if (report.type == 'candidate-pair') {
|
||||||
|
final state = report.values['state'] ?? report.values['nominated'];
|
||||||
|
pairs.add('${report.values['localCandidateId']}->'
|
||||||
|
'${report.values['remoteCandidateId']} state=$state');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debugPrint('[WebRtcController] ICE Failed. candidate-pair 数量=${pairs.length}');
|
||||||
|
for (final p in pairs) {
|
||||||
|
debugPrint('[WebRtcController] pair: $p');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('[WebRtcController] getStats 失败: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _onIceConnectionState(RTCIceConnectionState state) {
|
void _onIceConnectionState(RTCIceConnectionState state) {
|
||||||
// ignore: avoid_print
|
// ignore: avoid_print
|
||||||
print('ICE connection state: $state');
|
print('ICE connection state: $state');
|
||||||
if (state == RTCIceConnectionState.RTCIceConnectionStateConnected) {
|
if (state == RTCIceConnectionState.RTCIceConnectionStateChecking) {
|
||||||
|
debugPrint('[WebRtcController] ICE Checking: 正在收集/交换候选,等待连通...');
|
||||||
|
} else if (state == RTCIceConnectionState.RTCIceConnectionStateConnected) {
|
||||||
_connectedAt = DateTime.now().millisecondsSinceEpoch;
|
_connectedAt = DateTime.now().millisecondsSinceEpoch;
|
||||||
onConnectionEstablished?.call();
|
onConnectionEstablished?.call();
|
||||||
} else if (state == RTCIceConnectionState.RTCIceConnectionStateDisconnected) {
|
} else if (state == RTCIceConnectionState.RTCIceConnectionStateDisconnected) {
|
||||||
onIceDisconnected?.call('ICE 连接已断开');
|
onIceDisconnected?.call('ICE 连接已断开');
|
||||||
} else if (state == RTCIceConnectionState.RTCIceConnectionStateFailed) {
|
} else if (state == RTCIceConnectionState.RTCIceConnectionStateFailed) {
|
||||||
|
_dumpIceStats();
|
||||||
onConnectionFailed?.call('ICE 连接失败');
|
onConnectionFailed?.call('ICE 连接失败');
|
||||||
onDisconnected?.call();
|
onDisconnected?.call();
|
||||||
}
|
}
|
||||||
@@ -455,6 +490,7 @@ class WebRtcController {
|
|||||||
payload['sdpMid'] as String?,
|
payload['sdpMid'] as String?,
|
||||||
payload['sdpMLineIndex'] as int?,
|
payload['sdpMLineIndex'] as int?,
|
||||||
);
|
);
|
||||||
|
debugPrint('[WebRtcController] remote ICE candidate: ${candidate.candidate}');
|
||||||
await _pc?.addCandidate(candidate);
|
await _pc?.addCandidate(candidate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user