I’m debugging with a Physical Android phone,
I use it as the wifi hostpot for my PC
I’m running a Go socket server as the signalling server,
all data reaches the mobile client from the chrome client, and vice versa
but i keep getting:
RTCIceConnectionState.RTCIceConnectionStateFailed
I/flutter (31263): Connection state change: RTCPeerConnectionState.RTCPeerConnectionStateFailed
This is my Dart Client
import 'package:flutter_webrtc/flutter_webrtc.dart';
import '../core/services/socket_service.dart';
import 'enum/audio_call_event.dart';
import 'enum/audio_call_stage.dart';
class CallService {
String? _remoteUserId;
CallStage _callstage = CallStage.initial;
bool shouldSendIceCandidate = false;
List<RTCIceCandidate> _iceCandidates = [];
MediaStream? _localStream;
MediaStream? _remoteUserStream;
final RTCVideoRenderer remoteRenderer = RTCVideoRenderer();
RTCSessionDescription? _offer;
RTCSessionDescription? _answer;
RTCPeerConnection? userPeerConnection;
Map<String, dynamic> _iceServerConfig = {
'iceServers': [
{
'urls': [
"stun:stun.l.google.com:19302",
'stun:stun1.l.google.com:19302',
'stun:stun2.l.google.com:19302',
"stun:stun3.l.google.com:19302",
"stun:stun4.l.google.com:19302",
]
}
]
};
Map<String, dynamic> _voiceConstraints = {
"mandatory": {
"OfferToReceiveAudio": true,
"OfferToReceiveVideo": false,
},
"optional": [],
};
final SocketService _socketService;
CallService(this._socketService);
CallStage get callstage => _callstage;
Future<bool> makeACallTo(String userId) async {
try {
_remoteUserId = userId;
await initializeRenderer();
await startPeerConnection();
_registerPeerConnectionListeners();
await _getUserMedia();
await _addlocalUserAudioTracksToPeerConnection();
_listenForIncomingIceCandidate().listen((event) {
print("got a new candidate");
});
_offer = await userPeerConnection?.createOffer(_voiceConstraints);
await userPeerConnection?.setLocalDescription(_offer!);
final response = await sendCallOfferToUser(userId, data: _offer!);
if (response) {
shouldSendIceCandidate = true;
return response;
}
return response;
} catch (e) {
print("error making a call to: $userId");
print(e.toString());
rethrow;
}
}
Future<void> initializeRenderer() async {
try {
await remoteRenderer.initialize();
} catch (e) {
print("error initializing local and remote renderer");
print(e.toString());
rethrow;
}
}
Future<void> startPeerConnection() async {
try {
userPeerConnection ??= await createPeerConnection(_iceServerConfig);
await userPeerConnection?.addTransceiver(
kind: RTCRtpMediaType.RTCRtpMediaTypeAudio,
init: RTCRtpTransceiverInit(
direction: TransceiverDirection.RecvOnly,
),
);
} catch (e) {
print("error with starting peer connection");
print(e.toString());
rethrow;
}
}
Future<void> _getUserMedia() async {
try {
_localStream = await navigator.mediaDevices.getUserMedia({
'audio': true,
'video': false,
});
remoteRenderer.srcObject = await createLocalMediaStream("remote_key");
} catch (e) {
print("error getting user media");
print(e.toString());
rethrow;
}
}
Future<bool> sendCallOfferToUser(
String userId, {
required RTCSessionDescription data,
}) async {
try {
Map<String, dynamic> dataToBeSent = {
"caller": _socketService.user.fullName,
"callerId": _socketService.user.id,
"recipientId": userId,
"sdp": data.toMap(),
};
final response = _socketService.emitThisEvent(
AudioServiceSocketEvents.makeCall.description,
dataToBeSent,
);
final recievedData = await response.first;
final recievedDataMap = recievedData.$1;
print("printing response from call offer");
// print(recievedDataMap);
if (recievedDataMap["response"]) {
final sdpAnswer = recievedDataMap["sdp"] as Map<String, dynamic>;
_answer = RTCSessionDescription(sdpAnswer["sdp"], sdpAnswer["type"]);
await userPeerConnection?.setRemoteDescription(_answer!);
return true;
} else {
// await stopEverything();
return false;
}
} catch (e) {
print("error with sending offer for call");
print(e.toString());
rethrow;
}
}
Future<bool> _sendIceCandidate(
String userId,
RTCIceCandidate iceCandidate,
) async {
try {
print("about to start sending ice candidates");
final dataToBeSent = {
"recipientId": userId,
"candidate": iceCandidate.toMap(),
};
final result = _socketService.emitThisEvent(
AudioServiceSocketEvents.sendIceCandidates.description,
dataToBeSent,
);
final response = await result.first;
final responseMap = response.$1;
print("response from sending ice candidates:");
if (responseMap["isReady"]) {
return true;
}
return false;
} catch (e) {
print("error with sending ice candidates");
print(e.toString());
rethrow;
}
}
Future _addlocalUserAudioTracksToPeerConnection() async {
try {
print("adding audio tracks to call");
_localStream?.getAudioTracks().forEach((eachAudioTrack) {
userPeerConnection?.addTrack(eachAudioTrack, _localStream!);
});
print("finished adding local user audio tracks to call");
} catch (e) {
print("error adding user audio to peer connection");
print(e.toString());
rethrow;
}
}
Stream<void> _listenForIncomingIceCandidate() async* {
try {
_socketService
.listenForThisEvent(
AudioServiceSocketEvents.incomingIceCandidate.description,
)
.listen((event) async {
print("got some ice candidate data");
final payload = event.$1;
final candidateData = payload["candidate"];
final candidate = RTCIceCandidate(
candidateData['candidate'],
candidateData['sdpMid'],
candidateData['sdpMLineIndex'],
);
await userPeerConnection!.addCandidate(candidate);
});
} catch (e) {
print("error listening for incomming call offer");
print(e.toString());
rethrow;
}
}
void _registerPeerConnectionListeners() {
print("registering events");
userPeerConnection?.onIceGatheringState = (RTCIceGatheringState state) {
print("ICE gathering state changed: $state");
};
userPeerConnection?.onConnectionState =
(RTCPeerConnectionState state) async {
print("Connection state change: $state");
if (state == RTCPeerConnectionState.RTCPeerConnectionStateFailed) {
await userPeerConnection?.restartIce();
}
};
userPeerConnection?.onSignalingState = (RTCSignalingState state) {
print("Signaling state change: $state");
};
userPeerConnection?.onIceConnectionState = (state) {
print('iceConnectionState $state');
};
userPeerConnection?.onIceGatheringState = (RTCIceGatheringState state) {
print('ICE connection state change: $state');
};
userPeerConnection?.onTrack = (RTCTrackEvent event) {
print("got new track");
event.streams[0].getAudioTracks().forEach((eachAudioTrack) {
_remoteUserStream?.addTrack(eachAudioTrack);
remoteRenderer.srcObject = event.streams[0];
// remoteRenderer.muted = false;
});
};
userPeerConnection?.onAddStream = (MediaStream stream) {
print("Got new stream");
_remoteUserStream = stream;
};
userPeerConnection?.onIceConnectionState =
(RTCIceConnectionState state) async {
print("on ice connection state: ");
print(state);
};
userPeerConnection?.onRenegotiationNeeded = () {
print("renegotiation needed, what are we going to do?");
};
userPeerConnection?.onIceCandidate = (candidate) async {
print("on ice candidates ....");
if (_remoteUserId == null) {
print("remote user id is null");
_iceCandidates.add(candidate);
} else {
if (_iceCandidates.isNotEmpty) {
print("sending from buffererd list");
_iceCandidates.forEach((eachCandidate) async {
await _sendIceCandidate(_remoteUserId!, eachCandidate);
});
_iceCandidates = [];
}
await _sendIceCandidate(_remoteUserId!, candidate);
}
};
}
}
and this is my JS Client
// Establish Socket.io connection
const socket = io("http://127.0.0.1:5000", {
extraHeaders: {
"User-Id": "ChromeClient"
}
});
const recipientId = "Rider_Mike";
let callerId;
let localAudioStream;
let remoteStream;
let userPeerConnection;
let offer;
let answer;
let localCandidates = [];
const voiceConstraints = {
"mandatory": {
"OfferToReceiveAudio": true,
"OfferToReceiveVideo": false,
},
"optional": [],
};
// Variables to hold local media stream and RTCPeerConnection
// socket to listen to incoming call
socket.on('call', handleIncomingCall);
// socket to listen to incoming ICE candidates
socket.on('incoming-ice-candidate', handleIncomingIceCandidate);
// function to handle incoming call
async function handleIncomingCall(data, callback) {
try {
if (!userPeerConnection) {
console.log(data.callerId)
callerId = data.callerId
await createPeerConnection();
registerListeners();
}
console.log(data)
console.log("printing offer after data")
await answerACall(data, callback)
} catch (error) {
console.log(error)
}
}
// Function to handle ICE candidates from the other peer
async function handleIncomingIceCandidate(data, callbackFn) {
console.log("receiving candidates", data.candidate)
if (userPeerConnection) {
try {
await userPeerConnection.addIceCandidate(data.candidate);
} catch (error) {
console.error('Error adding ice candidate:', error);
}
}
await callbackFn(
{
"isReady": true,
"candidates": localCandidates,
}
);
}
async function answerACall(data, callbackFn) {
try {
// Create answer
// console.log(data.sdp)
await userPeerConnection.setRemoteDescription(new RTCSessionDescription(data.sdp));
const answer = await userPeerConnection.createAnswer(voiceConstraints);
await userPeerConnection.setLocalDescription(answer);
// console.log("printing answer")
// console.log(answer)
await callbackFn(
{
"response": true,
"sdp": answer,
}
);
} catch (error) {
console.log(error);
}
}
// Create PeerConnection and add event listeners
async function createPeerConnection() {
try {
const configuration = { iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] };
userPeerConnection = new RTCPeerConnection(configuration);
// // Add local audio stream to PeerConnection
// if (localAudioStream) {
// // console.log("streams have been added")
// localAudioStream.getTracks().forEach(track => {
// userPeerConnection.addTrack(track, localAudioStream);
// });
// }
} catch (error) {
console.error("error creating peer connection, ", error)
}
}
// Get user media (audio) and start call
navigator.mediaDevices.getUserMedia({ audio: true })
.then(stream => {
localAudioStream = stream;
const localAudioPlayer = document.getElementById('localAudio');
localAudioPlayer.srcObject = stream;
localAudioPlayer.autoplay = true;
})
.catch(error => {
console.error('Error accessing user media:', error);
});
// Function to initiate a call
function initiateCall() {
if (!userPeerConnection) {
createPeerConnection();
}
// Create offer
userPeerConnection.createOffer()
.then(offer => {
userPeerConnection.setLocalDescription(offer);
const dataToBeSent = {
"caller": "Chrome User Actually",
"recipientId": recipientId,
"sdp": offer,
};
socket.emit("make-a-call", dataToBeSent, (callback) => {
// callback();
});
})
.catch(error => {
console.error('Error creating offer:', error);
});
}
function registerListeners() {
try {
userPeerConnection.onConnectionState = (state) => {
console.log("Connection state change:", state);
};
// Listen for ICE candidates
userPeerConnection.onicecandidate = async (event) => {
console.log("sending ice candidates to the local candidate");
if (event.candidate) {
// Add the ICE candidate to the localCandidates array
await sendIceCandidate(
callerId,
event.candidate,
)
}
};
// Set local audio stream to <audio> element
userPeerConnection.ontrack = (event) => {
console.log("Received remote stream");
const remoteAudioPlayer = document.getElementById('remoteAudio');
const localAudioPlayer = document.getElementById('localAudio');
// console.log(event.streams)
remoteAudioPlayer.srcObject = event.streams[0];
localAudioPlayer.srcObject = event.streams[1];
remoteAudioPlayer.autoplay = true;
};
} catch (error) {
console.error('Error registering RTC Listeners:', error);
}
}
async function sendIceCandidate(
userId,
iceCandidate,
) {
try {
console.log("about to start sending ice candidates");
const dataToBeSent = {
"recipientId": userId,
"candidate": iceCandidate,
};
socket.emit("send-ice-candidate", dataToBeSent, (callback) => {
//
})
} catch (e) {
console.log("error with sending ice candidates");
console.log(e.toString());
rethrow;
}
}
I’ve been debugging this for weeks,
What could i be missing?
I’ve tried ordering and reordering how I create and send the offer, and answer
when to send the ice candidate betweent the peers
Asaboro Daniel is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.