import 'dart:convert'; import 'package:diplomaticquarterapp/uitl/SignalRUtil.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart'; typedef void StreamStateCallback(MediaStream stream); typedef void RTCIceGatheringStateCallback(RTCIceGatheringState state); typedef void RTCPeerConnectionStateCallback(RTCPeerConnectionState state); typedef void RTCSignalingStateCallback(RTCSignalingState state); final Map constraints = { 'mandatory': {}, 'optional': [ {'DtlsSrtpKeyAgreement': true}, ] }; 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' }; class Signaling { dispose() { if (peerConnection != null) { peerConnection.dispose(); peerConnection.getLocalStreams().forEach((e) => e.dispose()); peerConnection.getRemoteStreams().forEach((e) => e.dispose()); } signalR.closeConnection(); } init() async{ // Create Peer Connection peerConnection = await createPeerConnection(google_ice_config, constraints); 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"); signalR = SignalRUtil(hubName: "http://35.193.237.29/webRTCHub?source=mobile&username=$userName"); final connected = await signalR.openConnection(); if (!connected) throw 'Failed to connect SignalR'; } Map configuration = { // 'iceServers': [ // { // 'urls': ['stun:stun1.l.google.com:19302', 'stun:stun2.l.google.com:19302'] // } // ] '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"} // {'url': "stun:15.185.116.59:3478"}, // { // "url":"turn:15.185.116.59:3479", // "username": "admin", // "credential":"admin" // } { "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": "f18fc347ba4aeeaa1b00f5199b1e900834b464962d434a4a89b4cdba02510047", "urls": "turn:global.turn.twilio.com:3478?transport=udp", "credential": "WX16BB+9nKm0f+Whf5EwpM8S/Yv+T2tlvQWLfdV7oqo=" } ] }; SignalRUtil signalR; RTCPeerConnection peerConnection; MediaStream localStream; MediaStream remoteStream; RTCDataChannel dataChannel; // Future call(String patientId, String mobile, {@required RTCVideoRenderer localVideo, @required RTCVideoRenderer remoteVideo}) async { // await initializeSignalR(patientId); // // // final isCallPlaced = await FCM.sendCallNotifcationTo(DOCTOR_TOKEN, patientId, mobile); // if(!isCallPlaced) // throw 'Failed to notify target for call'; // // return isCallPlaced; // } Future acceptCall(String caller, String receiver, {@required MediaStream localMediaStream, @required Function(MediaStream) onRemoteMediaStream, @required Function(RTCTrackEvent) onRemoteTrack}) async { await initializeSignalR(receiver); 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?.onTrack = (track){ // onRemoteTrack(track); // }; peerConnection?.onAddStream = (MediaStream stream) { remoteStream = stream; onRemoteMediaStream?.call(stream); }; return true; } Future declineCall(String caller, String receiver) async { await initializeSignalR(receiver); signalR.setContributors(caller: caller, receiver: receiver); await signalR.declineCall(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 hangupCall(String caller, String receiver) async { await signalR.hangupCall(caller, receiver); dispose(); } answerOffer(String sdp) async { final offer = jsonDecode(sdp); final caller = offer['caller']; final receiver = offer['target']; final offerSdp = offer['sdp']; peerConnection.setRemoteDescription(rtcSessionDescriptionFrom(offerSdp)).then((value) { return peerConnection.createAnswer().catchError((e){ print(e); }); }).then((anwser) { return peerConnection.setLocalDescription(anwser).catchError((e){ print(e); }); }).then((value) { return peerConnection.getLocalDescription().catchError((e){ print(e); }); }).then((answer) { return signalR.answerOffer(answer, caller, receiver).catchError((e){ print(e); }); }).catchError((e) { print(e); }); } Future hangUp(RTCVideoRenderer localVideo) async {} Future createSdpAnswer(String toOfferSdp) async { final offerSdp = rtcSessionDescriptionFrom(jsonDecode(toOfferSdp)); peerConnection.setRemoteDescription(offerSdp).catchError((e){ print(e); }); final answer = await peerConnection.createAnswer().catchError((e){ print(e); }); var answerSdp = json.encode(answer); // Send SDP via Push or any channel return answerSdp; } Future createSdpOffer() async { final offer = await peerConnection.createOffer(); await peerConnection.setLocalDescription(offer).catchError((e){ print(e); }); final map = offer.toMap(); var offerSdp = json.encode(map); // Send SDP via Push or any channel return offerSdp; } addCandidate(String candidateJson) { peerConnection.addCandidate(rtcIceCandidateFrom(candidateJson)).catchError((e){ print(e); }); } void registerPeerConnectionListeners() { peerConnection.onRenegotiationNeeded = (){ print('Renegotiation Needed...'); }; peerConnection.onIceCandidate = (RTCIceCandidate candidate) { // print(json.encode(candidate.toMap())); signalR.addIceCandidate(json.encode(candidate.toMap())).catchError((e){ print(e); }); }; peerConnection?.onIceGatheringState = (RTCIceGatheringState state) { print('ICE gathering state changed: $state'); }; peerConnection?.onConnectionState = (RTCPeerConnectionState state) { print('Connection state change: $state'); }; peerConnection?.onSignalingState = (RTCSignalingState state) { print('Signaling state change: $state'); }; } } rtcSessionDescriptionFrom(Map sdp) { return RTCSessionDescription( sdp['sdp'], sdp['type'], ); } rtcIceCandidateFrom(String json) { final map = jsonDecode(json)['candidate']; return RTCIceCandidate(map['candidate'], map['sdpMid'], map['sdpMLineIndex']); }