|
|
|
|
@ -1,5 +1,6 @@
|
|
|
|
|
import 'dart:convert';
|
|
|
|
|
import 'dart:async';
|
|
|
|
|
import 'package:diplomaticquarterapp/uitl/SignalRUtil.dart';
|
|
|
|
|
import 'package:flutter/cupertino.dart';
|
|
|
|
|
import 'package:flutter_webrtc/flutter_webrtc.dart';
|
|
|
|
|
|
|
|
|
|
@ -50,6 +51,8 @@ class SocketUser{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class Signaling {
|
|
|
|
|
SignalRUtil signalrUtil;
|
|
|
|
|
var connectToSignalR = true;
|
|
|
|
|
var _host;
|
|
|
|
|
var _turnCredential;
|
|
|
|
|
|
|
|
|
|
@ -76,15 +79,14 @@ class Signaling {
|
|
|
|
|
|
|
|
|
|
Map<String, dynamic> _iceServers = {
|
|
|
|
|
'iceServers': [
|
|
|
|
|
{'url': 'stun:stun.l.google.com:19302'},
|
|
|
|
|
/*
|
|
|
|
|
* turn server configuration example.
|
|
|
|
|
// {'url': 'stun:stun.l.google.com:19302'},
|
|
|
|
|
// {'url': 'stun:15.185.116.59:3478'},
|
|
|
|
|
// {'url': 'stun:34.135.78.17:19302'},
|
|
|
|
|
{
|
|
|
|
|
'url': 'turn:123.45.67.89:3478',
|
|
|
|
|
'username': 'change_to_real_user',
|
|
|
|
|
'credential': 'change_to_real_secret'
|
|
|
|
|
'url': "turn:34.135.78.17:19302?transport=udp",
|
|
|
|
|
'username': "1659428372:flutter-webrtc",
|
|
|
|
|
'credential': "/fPfuS6Nf6hpl5LypJniRoAx9CI"
|
|
|
|
|
},
|
|
|
|
|
*/
|
|
|
|
|
]
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
@ -247,28 +249,105 @@ class Signaling {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<void> connect() async {
|
|
|
|
|
|
|
|
|
|
if(connectToSignalR){
|
|
|
|
|
final hub = "https://VCallApi.hmg.com/WebRTCHub?source=web&username=zik";
|
|
|
|
|
signalrUtil = SignalRUtil(hubName: hub);
|
|
|
|
|
signalrUtil.setContributors(caller: "zik", receiver: "s1");
|
|
|
|
|
|
|
|
|
|
final connected = await signalrUtil.openConnection();
|
|
|
|
|
if(connected){
|
|
|
|
|
signalrUtil.callUser("zik", "s1");
|
|
|
|
|
signalrUtil.listen(
|
|
|
|
|
onIncomingCall: (user) async{
|
|
|
|
|
await Future.delayed(Duration(seconds: 1));
|
|
|
|
|
signalrUtil.acceptCall("zik", "s1");
|
|
|
|
|
},
|
|
|
|
|
onAcceptCall: (user){
|
|
|
|
|
_initPeerConnection(session, media: "video", screenSharing: false);
|
|
|
|
|
},
|
|
|
|
|
onDeclineCall: (str, user){
|
|
|
|
|
print(str);
|
|
|
|
|
},
|
|
|
|
|
onHangupCall: (user){
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
onCandidate: (candidate_str) async{
|
|
|
|
|
print(candidate_str);
|
|
|
|
|
|
|
|
|
|
final candidateMap = jsonDecode(candidate_str)["candidate"];
|
|
|
|
|
RTCIceCandidate candidate = RTCIceCandidate(candidateMap['candidate'],
|
|
|
|
|
candidateMap['sdpMid'], candidateMap['sdpMLineIndex']);
|
|
|
|
|
|
|
|
|
|
if (session != null) {
|
|
|
|
|
if (session.pc != null) {
|
|
|
|
|
await session.pc?.addCandidate(candidate);
|
|
|
|
|
} else {
|
|
|
|
|
session.remoteCandidates.add(candidate);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// _sessions[sessionId] = SessionOneToOne(pid: peerId, sid: sessionId)
|
|
|
|
|
// ..remoteCandidates.add(candidate);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
onOffer: (offer_str,user) async{
|
|
|
|
|
print(offer_str);
|
|
|
|
|
|
|
|
|
|
final offer = jsonDecode(offer_str);
|
|
|
|
|
final caller = offer['caller'];
|
|
|
|
|
final receiver = offer['target'];
|
|
|
|
|
final offerSdp = offer['sdp'];
|
|
|
|
|
await _initPeerConnection(session, media: "video", screenSharing: false);
|
|
|
|
|
await session.pc?.setRemoteDescription(RTCSessionDescription(offerSdp['sdp'], offerSdp['type']));
|
|
|
|
|
await _createAnswer(session, "video");
|
|
|
|
|
|
|
|
|
|
if (session.remoteCandidates.isNotEmpty) {
|
|
|
|
|
session.remoteCandidates.forEach((candidate) async {
|
|
|
|
|
await session.pc?.addCandidate(candidate);
|
|
|
|
|
});
|
|
|
|
|
session.remoteCandidates.clear();
|
|
|
|
|
}
|
|
|
|
|
onCallStateChange?.call(session, CallState.Calling);
|
|
|
|
|
},
|
|
|
|
|
onAnswer: (answer_str) async{
|
|
|
|
|
final answer = jsonDecode(answer_str);
|
|
|
|
|
await session.pc?.setRemoteDescription(RTCSessionDescription(answer['sdp'], answer['type']));
|
|
|
|
|
if (session.remoteCandidates.isNotEmpty) {
|
|
|
|
|
session.remoteCandidates.forEach((candidate) async {
|
|
|
|
|
await session.pc?.addCandidate(candidate);
|
|
|
|
|
});
|
|
|
|
|
session.remoteCandidates.clear();
|
|
|
|
|
}
|
|
|
|
|
onCallStateChange?.call(session, CallState.Calling);
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_socket = SimpleWebSocket('$_host/ws');
|
|
|
|
|
|
|
|
|
|
if (_turnCredential == null) {
|
|
|
|
|
try {
|
|
|
|
|
_turnCredential = await getTurnCredential(_host);
|
|
|
|
|
/*
|
|
|
|
|
{
|
|
|
|
|
"username": "1584195784:mbzrxpgjys",
|
|
|
|
|
"password": "isyl6FF6nqMTB9/ig5MrMRUXqZg",
|
|
|
|
|
"ttl": 86400,
|
|
|
|
|
"uris": ["turn:127.0.0.1:19302?transport=udp"]
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
_iceServers = {
|
|
|
|
|
'iceServers': [
|
|
|
|
|
{
|
|
|
|
|
'urls': _turnCredential['uris'][0],
|
|
|
|
|
'username': _turnCredential['username'],
|
|
|
|
|
'credential': _turnCredential['password']
|
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
};
|
|
|
|
|
// _turnCredential = await getTurnCredential(_host);
|
|
|
|
|
// /*
|
|
|
|
|
// {
|
|
|
|
|
// "username": "1584195784:mbzrxpgjys",
|
|
|
|
|
// "password": "isyl6FF6nqMTB9/ig5MrMRUXqZg",
|
|
|
|
|
// "ttl": 86400,
|
|
|
|
|
// "uris": ["turn:127.0.0.1:19302?transport=udp"]
|
|
|
|
|
// }
|
|
|
|
|
// */
|
|
|
|
|
// _iceServers = {
|
|
|
|
|
// 'iceServers': [
|
|
|
|
|
// {
|
|
|
|
|
// 'urls': _turnCredential['uris'][0],
|
|
|
|
|
// 'username': _turnCredential['username'],
|
|
|
|
|
// 'credential': _turnCredential['password']
|
|
|
|
|
// },
|
|
|
|
|
// ]
|
|
|
|
|
// };
|
|
|
|
|
} catch (e) {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -323,6 +402,7 @@ class Signaling {
|
|
|
|
|
..._iceServers,
|
|
|
|
|
...{'sdpSemantics': sdpSemantics}
|
|
|
|
|
}, _config);
|
|
|
|
|
|
|
|
|
|
if (media != 'data') {
|
|
|
|
|
switch (sdpSemantics) {
|
|
|
|
|
case 'plan-b':
|
|
|
|
|
@ -389,11 +469,28 @@ class Signaling {
|
|
|
|
|
sender.setParameters(parameters);
|
|
|
|
|
*/
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pc.onRenegotiationNeeded = (){
|
|
|
|
|
offer("video", false);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pc.onIceCandidate = (candidate) async {
|
|
|
|
|
if (candidate == null) {
|
|
|
|
|
print('onIceCandidate: complete!');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(connectToSignalR){
|
|
|
|
|
// This delay is needed to allow enough time to try an ICE candidate
|
|
|
|
|
// before skipping to the next one. 1 second is just an heuristic value
|
|
|
|
|
// and should be thoroughly tested in your own environment.
|
|
|
|
|
await Future.delayed(const Duration(seconds: 1), () {
|
|
|
|
|
final candidateMap = {"candidate":candidate.toMap()};
|
|
|
|
|
signalrUtil.addIceCandidate(jsonEncode(candidateMap));
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// This delay is needed to allow enough time to try an ICE candidate
|
|
|
|
|
// before skipping to the next one. 1 second is just an heuristic value
|
|
|
|
|
// and should be thoroughly tested in your own environment.
|
|
|
|
|
@ -447,9 +544,16 @@ class Signaling {
|
|
|
|
|
|
|
|
|
|
Future<void> _createOffer(SessionOneToOne session, String media) async {
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
|
|
RTCSessionDescription s =
|
|
|
|
|
await session.pc .createOffer(media == 'data' ? _dcConstraints : {});
|
|
|
|
|
await session.pc.createOffer(media == 'data' ? _dcConstraints : {});
|
|
|
|
|
await session.pc .setLocalDescription(s);
|
|
|
|
|
|
|
|
|
|
if(connectToSignalR){
|
|
|
|
|
final offer = jsonEncode(s.toMap());
|
|
|
|
|
signalrUtil.offer(signalrUtil.sourceUser, signalrUtil.destinationUser, offer);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
_send('offer', {
|
|
|
|
|
'to': session.remote_user?.id,
|
|
|
|
|
'from': session.local_user.id,
|
|
|
|
|
@ -464,9 +568,14 @@ class Signaling {
|
|
|
|
|
|
|
|
|
|
Future<void> _createAnswer(SessionOneToOne session, String media) async {
|
|
|
|
|
try {
|
|
|
|
|
RTCSessionDescription s =
|
|
|
|
|
await session.pc .createAnswer(media == 'data' ? _dcConstraints : {});
|
|
|
|
|
RTCSessionDescription s = await session.pc.createAnswer(media == 'data' ? _dcConstraints : {});
|
|
|
|
|
await session.pc .setLocalDescription(s);
|
|
|
|
|
|
|
|
|
|
if(connectToSignalR){
|
|
|
|
|
signalrUtil.answerOffer(s);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_send('answer', {
|
|
|
|
|
'to': session.remote_user?.id,
|
|
|
|
|
'from': session.local_user.id,
|
|
|
|
|
@ -487,6 +596,7 @@ class Signaling {
|
|
|
|
|
|
|
|
|
|
Future<void> finishSessions() async {
|
|
|
|
|
_closeSessionById(session.id);
|
|
|
|
|
signalrUtil?.closeConnection();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _closeSessionById(String sessionId) {
|
|
|
|
|
|