import 'dart:convert'; import 'package:doctor_app_flutter/util/SignalRUtil.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart'; const PATIENT_PUSH_TOKEN = "eUA6FUiOTbSellej2JQ8yg:APA91bFuDledDvwbeZJ8KU8drHQ7kse7h9UjKPaBZsQgKi-0GCSPRB_yIO9O3PXysWLnLMJYwVmEJWd-jPIqbrLz_Z_yzE--3mC-GZJj92BT0nSzAVd-JVr28pUtejgSjTpZryNFlSn5"; typedef void StreamStateCallback(MediaStream stream); typedef void RTCIceGatheringStateCallback(RTCIceGatheringState state); typedef void RTCPeerConnectionStateCallback(RTCPeerConnectionState state); typedef void RTCSignalingStateCallback(RTCSignalingState state); Map snapsis_ice_config = { 'iceServers': [ { "urls": 'stun:15.185.116.59:3478' }, { "urls": "turn:15.185.116.59:3479", "username": "admin", "credential": "admin" }, ], // 'sdpSemantics': 'unified-plan' }; Map twilio_ice_config = { "ice_servers": [ { "url": "stun:global.stun.twilio.com:3478?transport=udp", "urls": "stun:global.stun.twilio.com:3478?transport=udp" }, { "url": "turn:global.turn.twilio.com:3478?transport=udp", "username": "ce8042842b62c21bd20b176f80d6067fd3db81b1e9766312418ef5421d9ca2a2", "urls": "turn:global.turn.twilio.com:3478?transport=udp", "credential": "UzGOsiLwPZJ32cjafAebfDDpVrqeQjgpFHZEdau/8r4=" }, { "url": "turn:global.turn.twilio.com:3478?transport=tcp", "username": "ce8042842b62c21bd20b176f80d6067fd3db81b1e9766312418ef5421d9ca2a2", "urls": "turn:global.turn.twilio.com:3478?transport=tcp", "credential": "UzGOsiLwPZJ32cjafAebfDDpVrqeQjgpFHZEdau/8r4=" }, { "url": "turn:global.turn.twilio.com:443?transport=tcp", "username": "ce8042842b62c21bd20b176f80d6067fd3db81b1e9766312418ef5421d9ca2a2", "urls": "turn:global.turn.twilio.com:443?transport=tcp", "credential": "UzGOsiLwPZJ32cjafAebfDDpVrqeQjgpFHZEdau/8r4=" } ], // 'sdpSemantics': 'unified-plan' }; Map google_ice_config = { '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' ] }, ], // 'sdpSemantics': 'unified-plan' }; Map aws_ice_config = { 'iceServers': [ {'url': "stun:ec2-15-185-116-59.me-south-1.compute.amazonaws.com:3478"}, {'url': "turn:ec2-15-185-116-59.me-south-1.compute.amazonaws.com:3479", 'credential': "admin", 'username': "admin"} ], // 'sdpSemantics': 'unified-plan' }; final Map _peer_config = { 'mandatory': {}, 'optional': [ {'DtlsSrtpKeyAgreement': true}, ] }; class Signaling { dispose(){ if(peerConnection != null) peerConnection.dispose(); signalR?.closeConnection(); } init(){ // Create Peer Connection createPeerConnection(google_ice_config,_peer_config).then((value){ peerConnection = value; registerPeerConnectionListeners(); }); } initializeSignalR(String userName) async{ if(signalR != null) await signalR?.closeConnection(); // https://vcallapi.hmg.com/webRTCHub?source=web&username=zohaib signalR = SignalRUtil(hubName: "https://vcallapi.hmg.com/webRTCHub?source=mobile&username=$userName"); final connected = await signalR?.openConnection(); if(connected != null && !connected) throw 'Failed to connect SignalR'; } SignalRUtil? signalR; late RTCPeerConnection peerConnection; MediaStream? localStream; MediaStream? remoteStream; RTCDataChannel? dataChannel; Future acceptCall(String caller, String receiver, {required MediaStream localMediaStream, required Function(MediaStream) onRemoteMediaStream}) async{ await initializeSignalR(caller); signalR?.setContributors(caller: caller, receiver: receiver); await signalR?.acceptCall(receiver, caller).catchError((e) => throw 'Failed to inform signalR that i accepted a call'); peerConnection.addStream(localMediaStream); peerConnection.onAddStream = (MediaStream stream) { remoteStream = stream; onRemoteMediaStream.call(stream); }; return true; } Future initiateCall(String caller, String receiver, {required MediaStream localMediaStream, required Function(MediaStream) onRemoteMediaStream}) async{ await initializeSignalR(caller); signalR?.setContributors(caller: caller, receiver: receiver); await signalR?.callUserMobile(caller, receiver).catchError((e) => throw 'Failed to inform signalR to call user: $receiver'); peerConnection.addStream(localMediaStream); peerConnection.onAddStream = (MediaStream stream) { remoteStream = stream; onRemoteMediaStream.call(stream); }; return true; } Future hangupCall(String caller, String receiver) async{ await signalR?.hangupCall(caller, receiver); dispose(); } answerOffer(Map offerSdp) async{ final caller = offerSdp['caller']; final receiver = offerSdp['target']; peerConnection.setRemoteDescription(rtcSessionDescriptionFrom(offerSdp)) .then((value) { return peerConnection.createAnswer(); }) .then((anwser) { return peerConnection.setLocalDescription(anwser); }) .then((value) { return peerConnection.getLocalDescription(); }) .then((answer) { return signalR?.answerOffer(answer, caller, receiver); }); } sdpOfferAnswered(Map sdp){ final answerSdp = rtcSessionDescriptionFrom(sdp); peerConnection.setRemoteDescription(answerSdp) .then((value) { }).catchError((e) => print(e)); } Future hangUp(RTCVideoRenderer localVideo) async { } Future createSdpAnswer(String toOfferSdp) async { final offerSdp = rtcSessionDescriptionFrom(jsonDecode(toOfferSdp)); peerConnection.setRemoteDescription(offerSdp); final answer = await peerConnection.createAnswer(); var answerSdp = json.encode(answer); // Send SDP via Push or any channel return answerSdp; } Future createSdpOffer(String caller, String receiver) async { await Future.delayed(Duration(seconds: 1)); final offer = await peerConnection.createOffer(); await peerConnection.setLocalDescription(offer); final map = offer.toMap(); var offerSdp = json.encode({'sdp':map, 'target':receiver, 'caller':caller}); // Send SDP via Push or any channel return offerSdp; } addCandidate(Map candidate){ peerConnection.addCandidate(rtcIceCandidateFrom(candidate)); } void registerPeerConnectionListeners() { peerConnection.onIceCandidate = (RTCIceCandidate candidate){ print(json.encode(candidate.toMap())); signalR?.addIceCandidate(candidate); }; peerConnection.onIceGatheringState = (RTCIceGatheringState state) { print('ICE gathering state changed: $state'); }; peerConnection.onConnectionState = (RTCPeerConnectionState state) { print('Connection state change: $state ${state.index}'); }; peerConnection.onSignalingState = (RTCSignalingState state) { print('Signaling state change: $state'); }; } } rtcSessionDescriptionFrom(Map sdp){ return RTCSessionDescription( sdp['sdp'],sdp['type'], ); } rtcIceCandidateFrom(Map candidate){ final _candidate = candidate['candidate']; return RTCIceCandidate(_candidate['candidate'], _candidate['sdpMid'], _candidate['sdpMLineIndex']); }