flutter - Flutter WebRTC 无法连接到对等点。无法设置远程应答 sdp:在错误状态下调用:kStable
问题描述
所以我正在使用颤振(和 web 的颤振)来构建一个 WebRTC 客户端。我有一个 spring-boot 服务器充当两个客户端的中间人。他们都订阅了 WebSocket 以从对方那里获取消息。仅此而已。
我越来越Error: InvalidStateError: Failed to execute 'setRemoteDescription' on 'RTCPeerConnection': Failed to set remote answer sdp: Called in wrong state: kStable
我不知道为什么会发生这个错误。
这是信号的代码
typedef void StreamStateCallback(MediaStream stream);
class CallingService {
String sendToUserId;
String currentUserId;
final String authToken;
final StompClient _client;
final StreamStateCallback onAddRemoteStream;
final StreamStateCallback onRemoveRemoteStream;
final StreamStateCallback onAddLocalStream;
RTCPeerConnection _peerConnection;
List<RTCIceCandidate> _remoteCandidates = [];
String destination;
var hasOffer = false;
var isNegotiating = false;
final Map<String, dynamic> _constraints = {
'mandatory': {
'OfferToReceiveAudio': true,
'OfferToReceiveVideo': true,
},
'optional': [],
};
CallingService(
this._client,
this.sendToUserId,
this.currentUserId,
this.authToken,
this.onAddRemoteStream,
this.onRemoveRemoteStream,
this.onAddLocalStream) {
destination = '/app/start-call/$sendToUserId';
print("destination $destination");
_client.subscribe(
destination: destination,
headers: {'Authorization': "$authToken"},
callback: (StompFrame frame) => processMessage(jsonDecode(frame.body)));
}
Future<void> startCall() async {
await processRemoteStream();
RTCSessionDescription description =
await _peerConnection.createOffer(_constraints);
await _peerConnection.setLocalDescription(description);
var message = RtcMessage(RtcMessageType.OFFER, currentUserId, {
'description': {'sdp': description.sdp, 'type': description.type},
});
sendMessage(message);
}
Future<void> processMessage(Map<String, dynamic> messageJson) async {
var message = RtcMessage.fromJson(messageJson);
if (message.from == currentUserId) {
return;
}
print("processing ${message.messageType.toString()}");
switch (message.messageType) {
case RtcMessageType.BYE:
// TODO: Handle this case.
break;
case RtcMessageType.LEAVE:
// TODO: Handle this case.
break;
case RtcMessageType.CANDIDATE:
await processCandidate(message);
break;
case RtcMessageType.ANSWER:
await processAnswer(message);
break;
case RtcMessageType.OFFER:
await processOffer(message);
break;
}
}
Future<void> processCandidate(RtcMessage candidate) async {
Map<String, dynamic> map = candidate.data['candidate'];
var rtcCandidate = RTCIceCandidate(
map['candidate'],
map['sdpMid'],
map['sdpMLineIndex'],
);
if (_peerConnection != null) {
_peerConnection.addCandidate(rtcCandidate);
} else {
_remoteCandidates.add(rtcCandidate);
}
}
Future<void> processAnswer(RtcMessage answer) async {
if (isNegotiating){
return;
}
var description = answer.data['description'];
await _peerConnection.setRemoteDescription(
RTCSessionDescription(description['sdp'], description['type']));
}
Future<void> processOffer(RtcMessage offer) async {
await processRemoteStream();
var description = offer.data['description'];
await _peerConnection.setRemoteDescription(
new RTCSessionDescription(description['sdp'], description['type']));
var answerDescription = await _peerConnection.createAnswer(_constraints);
await _peerConnection.setLocalDescription(answerDescription);
var answerMessage = RtcMessage(RtcMessageType.ANSWER, currentUserId, {
'description': {
'sdp': answerDescription.sdp,
'type': answerDescription.type
},
});
sendMessage(answerMessage);
if (_remoteCandidates.isNotEmpty) {
_remoteCandidates
.forEach((candidate) => _peerConnection.addCandidate(candidate));
_remoteCandidates.clear();
}
}
Future<void> processRemoteStream() async {
await createStream();
_peerConnection = await createPeerConnection(_iceServers, _config);
_peerConnection.onSignalingState = (state) {
isNegotiating = state != RTCSignalingState.RTCSignalingStateStable;
};
_peerConnection.onAddTrack = (MediaStream stream, _) {
this.onAddRemoteStream(stream);
print("sending stream from track");
};
_peerConnection.onAddStream = (MediaStream stream) {
this.onAddRemoteStream(stream);
print("sending stream");
};
_peerConnection.onRemoveStream =
(MediaStream stream) => this.onRemoveRemoteStream(stream);
_peerConnection.onIceCandidate = (RTCIceCandidate candidate) {
print("sending candidate");
var data = {
'candidate': {
'sdpMLineIndex': candidate.sdpMlineIndex,
'sdpMid': candidate.sdpMid,
'candidate': candidate.candidate,
},
};
var message = RtcMessage(RtcMessageType.CANDIDATE, currentUserId, data);
sendMessage(message);
};
}
void sendMessage(RtcMessage message) {
_client.send(
destination: destination,
headers: {'Authorization': "$authToken"},
body: jsonEncode(message.toJson()));
}
Map<String, dynamic> _iceServers = {
'iceServers': [
{"url" : "stun:stun2.1.google.com:19302"},
{'url' : 'stun:stun.l.google.com:19302'},
/*
* turn server configuration example.
{
'url': 'turn:123.45.67.89:3478',
'username': 'change_to_real_user',
'credential': 'change_to_real_secret'
},
*/
]
};
final Map<String, dynamic> _config = {
'mandatory': {},
'optional': [
{'DtlsSrtpKeyAgreement': true},
],
};
Future<MediaStream> createStream() async {
final Map<String, dynamic> mediaConstraints = {
'audio': true,
'video': {
'mandatory': {
'minWidth':
'640', // Provide your own width, height and frame rate here
'minHeight': '480',
'minFrameRate': '30',
},
'facingMode': 'user',
'optional': [],
}
};
MediaStream stream = await navigator.getUserMedia(mediaConstraints);
if (this.onAddLocalStream != null) {
this.onAddLocalStream(stream);
}
return stream;
}
}
我的第一个问题是我没有为要约/回答阶段设置本地描述。
但是,当我添加一个新的 stun 服务器时,我得到了同样的异常。无论哪种方式,我都没有显示远程流。
解决方案
因此,当我创建报价和答案时,我没有设置本地描述。所以就是这样。
它仍然没有显示远程连接。
推荐阅读
- reactjs - 对至少一个值为真的对象数组进行 YUP 验证
- python - 这个 MB/s 公式对吗?Python
- python - 在python中存储自定义对象字典的最佳方法?
- python - Pandas:从另一个数据帧更新数据帧中的特定值
- javascript - 子组件不识别道具从父组件更改
- ruby-on-rails - 如何检查不同国家/地区的字符串中的特殊字符?
- google-cloud-platform - GCP Terraform google_project_iam_member 不会将手动添加的角色删除到成员
- typescript - '{ pathname: string; 类型的参数 装运:TSshipment;}' 不可分配给“To”类型的参数
- hashicorp-vault - Vault 中转引擎自动开封在启动时未通过 VAULT_TOKEN?
- reactjs - 在 API 调用后反应重新渲染导致不良 UI 显示一瞬间