|
|
|
|
@ -1,11 +1,45 @@
|
|
|
|
|
import 'dart:convert';
|
|
|
|
|
|
|
|
|
|
// import 'package:cloud_firestore/cloud_firestore.dart';
|
|
|
|
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
|
|
|
import 'package:diplomaticquarterapp/pages/webRTC/fcm/FCMSendNotification.dart';
|
|
|
|
|
import 'package:diplomaticquarterapp/uitl/SignalRUtil.dart';
|
|
|
|
|
import 'package:flutter/cupertino.dart';
|
|
|
|
|
import 'package:flutter_webrtc/flutter_webrtc.dart';
|
|
|
|
|
|
|
|
|
|
import 'call_page.dart';
|
|
|
|
|
|
|
|
|
|
typedef void StreamStateCallback(MediaStream stream);
|
|
|
|
|
typedef void RTCIceGatheringStateCallback(RTCIceGatheringState state);
|
|
|
|
|
typedef void RTCPeerConnectionStateCallback(RTCPeerConnectionState state);
|
|
|
|
|
typedef void RTCSignalingStateCallback(RTCSignalingState state);
|
|
|
|
|
|
|
|
|
|
class Signaling {
|
|
|
|
|
|
|
|
|
|
dispose(){
|
|
|
|
|
if(peerConnection != null)
|
|
|
|
|
peerConnection.dispose();
|
|
|
|
|
signalR.closeConnection();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
init(){
|
|
|
|
|
// Create Peer Connection
|
|
|
|
|
createPeerConnection(configuration).then((value){
|
|
|
|
|
peerConnection = value;
|
|
|
|
|
registerPeerConnectionListeners();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
initializeSignalR() async{
|
|
|
|
|
if(signalR != null)
|
|
|
|
|
await signalR.closeConnection();
|
|
|
|
|
|
|
|
|
|
signalR = SignalRUtil(hubName: "https://VCallApi.hmg.com/WebRTCHub?source=$My_Mobile&username=$My_ID");
|
|
|
|
|
final connected = await signalR.openConnection();
|
|
|
|
|
if(!connected)
|
|
|
|
|
throw 'Failed to connect SignalR';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Map<String, dynamic> configuration = {
|
|
|
|
|
'iceServers': [
|
|
|
|
|
{
|
|
|
|
|
@ -14,209 +48,96 @@ class Signaling {
|
|
|
|
|
]
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
SignalRUtil signalR;
|
|
|
|
|
|
|
|
|
|
RTCPeerConnection peerConnection;
|
|
|
|
|
MediaStream localStream;
|
|
|
|
|
MediaStream remoteStream;
|
|
|
|
|
String roomId;
|
|
|
|
|
String currentRoomText;
|
|
|
|
|
StreamStateCallback onAddRemoteStream;
|
|
|
|
|
|
|
|
|
|
// Future<String> createRoom(RTCVideoRenderer remoteRenderer) async {
|
|
|
|
|
// // FirebaseFirestore db = FirebaseFirestore.instance;
|
|
|
|
|
// // DocumentReference roomRef = db.collection('rooms').doc();
|
|
|
|
|
//
|
|
|
|
|
// print('Create PeerConnection with configuration: $configuration');
|
|
|
|
|
//
|
|
|
|
|
// peerConnection = await createPeerConnection(configuration);
|
|
|
|
|
//
|
|
|
|
|
// registerPeerConnectionListeners();
|
|
|
|
|
//
|
|
|
|
|
// localStream.getTracks().forEach((track) {
|
|
|
|
|
// peerConnection?.addTrack(track, localStream);
|
|
|
|
|
// });
|
|
|
|
|
//
|
|
|
|
|
// // Code for collecting ICE candidates below
|
|
|
|
|
// var callerCandidatesCollection = roomRef.collection('callerCandidates');
|
|
|
|
|
//
|
|
|
|
|
// peerConnection?.onIceCandidate = (RTCIceCandidate candidate) {
|
|
|
|
|
// print('Got candidate: ${candidate.toMap()}');
|
|
|
|
|
// callerCandidatesCollection.add(candidate.toMap());
|
|
|
|
|
// };
|
|
|
|
|
// // Finish Code for collecting ICE candidate
|
|
|
|
|
//
|
|
|
|
|
// // Add code for creating a room
|
|
|
|
|
// RTCSessionDescription offer = await peerConnection.createOffer();
|
|
|
|
|
// await peerConnection.setLocalDescription(offer);
|
|
|
|
|
// print('Created offer: $offer');
|
|
|
|
|
//
|
|
|
|
|
// Map<String, dynamic> roomWithOffer = {'offer': offer.toMap()};
|
|
|
|
|
//
|
|
|
|
|
// await roomRef.set(roomWithOffer);
|
|
|
|
|
// var roomId = roomRef.id;
|
|
|
|
|
// print('New room created with SDK offer. Room ID: $roomId');
|
|
|
|
|
// currentRoomText = 'Current room is $roomId - You are the caller!';
|
|
|
|
|
// // Created a Room
|
|
|
|
|
//
|
|
|
|
|
// peerConnection?.onTrack = (RTCTrackEvent event) {
|
|
|
|
|
// print('Got remote track: ${event.streams[0]}');
|
|
|
|
|
//
|
|
|
|
|
// event.streams[0].getTracks().forEach((track) {
|
|
|
|
|
// print('Add a track to the remoteStream $track');
|
|
|
|
|
// remoteStream?.addTrack(track);
|
|
|
|
|
// });
|
|
|
|
|
// };
|
|
|
|
|
//
|
|
|
|
|
// // Listening for remote session description below
|
|
|
|
|
// roomRef.snapshots().listen((snapshot) async {
|
|
|
|
|
// print('Got updated room: ${snapshot.data()}');
|
|
|
|
|
//
|
|
|
|
|
// Map<String, dynamic> data = snapshot.data() as Map<String, dynamic>;
|
|
|
|
|
// if (peerConnection?.getRemoteDescription() != null && data['answer'] != null) {
|
|
|
|
|
// var answer = RTCSessionDescription(
|
|
|
|
|
// data['answer']['sdp'],
|
|
|
|
|
// data['answer']['type'],
|
|
|
|
|
// );
|
|
|
|
|
//
|
|
|
|
|
// print("Someone tried to connect");
|
|
|
|
|
// await peerConnection?.setRemoteDescription(answer);
|
|
|
|
|
// }
|
|
|
|
|
// });
|
|
|
|
|
// // Listening for remote session description above
|
|
|
|
|
//
|
|
|
|
|
// // Listen for remote Ice candidates below
|
|
|
|
|
// roomRef.collection('calleeCandidates').snapshots().listen((snapshot) {
|
|
|
|
|
// snapshot.docChanges.forEach((change) {
|
|
|
|
|
// if (change.type == DocumentChangeType.added) {
|
|
|
|
|
// Map<String, dynamic> data = change.doc.data() as Map<String, dynamic>;
|
|
|
|
|
// print('Got new remote ICE candidate: ${jsonEncode(data)}');
|
|
|
|
|
// peerConnection.addCandidate(
|
|
|
|
|
// RTCIceCandidate(
|
|
|
|
|
// data['candidate'],
|
|
|
|
|
// data['sdpMid'],
|
|
|
|
|
// data['sdpMLineIndex'],
|
|
|
|
|
// ),
|
|
|
|
|
// );
|
|
|
|
|
// }
|
|
|
|
|
// });
|
|
|
|
|
// });
|
|
|
|
|
// // Listen for remote ICE candidates above
|
|
|
|
|
//
|
|
|
|
|
// return roomId;
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// Future<void> joinRoom(String roomId, RTCVideoRenderer remoteVideo) async {
|
|
|
|
|
// FirebaseFirestore db = FirebaseFirestore.instance;
|
|
|
|
|
// DocumentReference roomRef = db.collection('rooms').doc('$roomId');
|
|
|
|
|
// var roomSnapshot = await roomRef.get();
|
|
|
|
|
// print('Got room ${roomSnapshot.exists}');
|
|
|
|
|
//
|
|
|
|
|
// if (roomSnapshot.exists) {
|
|
|
|
|
// print('Create PeerConnection with configuration: $configuration');
|
|
|
|
|
// peerConnection = await createPeerConnection(configuration);
|
|
|
|
|
//
|
|
|
|
|
// registerPeerConnectionListeners();
|
|
|
|
|
//
|
|
|
|
|
// localStream.getTracks().forEach((track) {
|
|
|
|
|
// peerConnection?.addTrack(track, localStream);
|
|
|
|
|
// });
|
|
|
|
|
//
|
|
|
|
|
// // Code for collecting ICE candidates below
|
|
|
|
|
// var calleeCandidatesCollection = roomRef.collection('calleeCandidates');
|
|
|
|
|
// peerConnection.onIceCandidate = (RTCIceCandidate candidate) {
|
|
|
|
|
// if (candidate == null) {
|
|
|
|
|
// print('onIceCandidate: complete!');
|
|
|
|
|
// return;
|
|
|
|
|
// }
|
|
|
|
|
// print('onIceCandidate: ${candidate.toMap()}');
|
|
|
|
|
// calleeCandidatesCollection.add(candidate.toMap());
|
|
|
|
|
// };
|
|
|
|
|
// // Code for collecting ICE candidate above
|
|
|
|
|
//
|
|
|
|
|
// peerConnection?.onTrack = (RTCTrackEvent event) {
|
|
|
|
|
// print('Got remote track: ${event.streams[0]}');
|
|
|
|
|
// event.streams[0].getTracks().forEach((track) {
|
|
|
|
|
// print('Add a track to the remoteStream: $track');
|
|
|
|
|
// remoteStream?.addTrack(track);
|
|
|
|
|
// });
|
|
|
|
|
// };
|
|
|
|
|
//
|
|
|
|
|
// // Code for creating SDP answer below
|
|
|
|
|
// var data = roomSnapshot.data() as Map<String, dynamic>;
|
|
|
|
|
// print('Got offer $data');
|
|
|
|
|
// var offer = data['offer'];
|
|
|
|
|
// await peerConnection?.setRemoteDescription(
|
|
|
|
|
// RTCSessionDescription(offer['sdp'], offer['type']),
|
|
|
|
|
// );
|
|
|
|
|
// var answer = await peerConnection.createAnswer();
|
|
|
|
|
// print('Created Answer $answer');
|
|
|
|
|
//
|
|
|
|
|
// await peerConnection.setLocalDescription(answer);
|
|
|
|
|
//
|
|
|
|
|
// Map<String, dynamic> roomWithAnswer = {
|
|
|
|
|
// 'answer': {'type': answer.type, 'sdp': answer.sdp}
|
|
|
|
|
// };
|
|
|
|
|
//
|
|
|
|
|
// await roomRef.update(roomWithAnswer);
|
|
|
|
|
// // Finished creating SDP answer
|
|
|
|
|
//
|
|
|
|
|
// // Listening for remote ICE candidates below
|
|
|
|
|
// // roomRef.collection('callerCandidates').snapshots().listen((snapshot) {
|
|
|
|
|
// // snapshot.docChanges.forEach((document) {
|
|
|
|
|
// // var data = document.doc.data() as Map<String, dynamic>;
|
|
|
|
|
// // print(data);
|
|
|
|
|
// // print('Got new remote ICE candidate: $data');
|
|
|
|
|
// // peerConnection.addCandidate(
|
|
|
|
|
// // RTCIceCandidate(
|
|
|
|
|
// // data['candidate'],
|
|
|
|
|
// // data['sdpMid'],
|
|
|
|
|
// // data['sdpMLineIndex'],
|
|
|
|
|
// // ),
|
|
|
|
|
// // );
|
|
|
|
|
// // });
|
|
|
|
|
// // });
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
Future<void> openUserMedia(
|
|
|
|
|
RTCVideoRenderer localVideo,
|
|
|
|
|
RTCVideoRenderer remoteVideo,
|
|
|
|
|
) async {
|
|
|
|
|
var stream = await navigator.mediaDevices.getUserMedia({'video': true, 'audio': false});
|
|
|
|
|
|
|
|
|
|
localVideo.srcObject = stream;
|
|
|
|
|
localStream = stream;
|
|
|
|
|
|
|
|
|
|
remoteVideo.srcObject = await createLocalMediaStream('key');
|
|
|
|
|
RTCDataChannel dataChannel;
|
|
|
|
|
|
|
|
|
|
Future<bool> call(String patientId, String mobile, {@required RTCVideoRenderer localVideo, @required RTCVideoRenderer remoteVideo}) async {
|
|
|
|
|
initializeSignalR();
|
|
|
|
|
|
|
|
|
|
final isCallPlaced = await FCM.sendCallNotifcationTo(DOCTOR_TOKEN, patientId, mobile);
|
|
|
|
|
if(!isCallPlaced)
|
|
|
|
|
throw 'Failed to notify target for call';
|
|
|
|
|
|
|
|
|
|
return isCallPlaced;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Future<bool> acceptCall(String caller, String receiver, {@required MediaStream localMediaStream, @required Function(MediaStream) onRemoteMediaStream}) async{
|
|
|
|
|
await initializeSignalR();
|
|
|
|
|
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 hangupCall(String caller, String receiver) async{
|
|
|
|
|
await signalR.hangupCall(caller, receiver);
|
|
|
|
|
dispose();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Future<void> hangUp(RTCVideoRenderer localVideo) async {
|
|
|
|
|
// List<MediaStreamTrack> tracks = localVideo.srcObject.getTracks();
|
|
|
|
|
// tracks.forEach((track) {
|
|
|
|
|
// track.stop();
|
|
|
|
|
// });
|
|
|
|
|
//
|
|
|
|
|
// if (remoteStream != null) {
|
|
|
|
|
// remoteStream.getTracks().forEach((track) => track.stop());
|
|
|
|
|
// }
|
|
|
|
|
// if (peerConnection != null) peerConnection.close();
|
|
|
|
|
//
|
|
|
|
|
// if (roomId != null) {
|
|
|
|
|
// var db = FirebaseFirestore.instance;
|
|
|
|
|
// var roomRef = db.collection('rooms').doc(roomId);
|
|
|
|
|
// var calleeCandidates = await roomRef.collection('calleeCandidates').get();
|
|
|
|
|
// calleeCandidates.docs.forEach((document) => document.reference.delete());
|
|
|
|
|
//
|
|
|
|
|
// var callerCandidates = await roomRef.collection('callerCandidates').get();
|
|
|
|
|
// callerCandidates.docs.forEach((document) => document.reference.delete());
|
|
|
|
|
//
|
|
|
|
|
// await roomRef.delete();
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// localStream.dispose();
|
|
|
|
|
// remoteStream?.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();
|
|
|
|
|
})
|
|
|
|
|
.then((anwser) {
|
|
|
|
|
return peerConnection.setLocalDescription(anwser);
|
|
|
|
|
})
|
|
|
|
|
.then((value) {
|
|
|
|
|
return peerConnection.getLocalDescription();
|
|
|
|
|
})
|
|
|
|
|
.then((answer) {
|
|
|
|
|
return signalR.answerOffer(answer, caller, receiver);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<void> hangUp(RTCVideoRenderer localVideo) async {
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<String> 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<String> createSdpOffer() async {
|
|
|
|
|
final offer = await peerConnection.createOffer();
|
|
|
|
|
await peerConnection.setLocalDescription(offer);
|
|
|
|
|
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));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void registerPeerConnectionListeners() {
|
|
|
|
|
peerConnection.onIceCandidate = (RTCIceCandidate candidate){
|
|
|
|
|
print(json.encode(candidate.toMap()));
|
|
|
|
|
signalR.addIceCandidate(json.encode(candidate.toMap()));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
peerConnection?.onIceGatheringState = (RTCIceGatheringState state) {
|
|
|
|
|
print('ICE gathering state changed: $state');
|
|
|
|
|
};
|
|
|
|
|
@ -228,15 +149,17 @@ class Signaling {
|
|
|
|
|
peerConnection?.onSignalingState = (RTCSignalingState state) {
|
|
|
|
|
print('Signaling state change: $state');
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
peerConnection?.onIceGatheringState = (RTCIceGatheringState state) {
|
|
|
|
|
print('ICE connection state change: $state');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
peerConnection?.onAddStream = (MediaStream stream) {
|
|
|
|
|
print("Add remote stream");
|
|
|
|
|
onAddRemoteStream?.call(stream);
|
|
|
|
|
remoteStream = stream;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
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']);
|
|
|
|
|
}
|
|
|
|
|
|