Line data Source code
1 : import 'dart:async';
2 :
3 : import 'package:webrtc_interface/webrtc_interface.dart';
4 :
5 : import 'package:matrix/matrix.dart';
6 :
7 : class ConnectionTester {
8 : Client client;
9 : WebRTCDelegate delegate;
10 : RTCPeerConnection? pc1, pc2;
11 0 : ConnectionTester(this.client, this.delegate);
12 : TurnServerCredentials? _turnServerCredentials;
13 :
14 0 : Future<bool> verifyTurnServer() async {
15 0 : final iceServers = await getIceServers();
16 0 : final configuration = <String, dynamic>{
17 : 'iceServers': iceServers,
18 : 'sdpSemantics': 'unified-plan',
19 : 'iceCandidatePoolSize': 1,
20 : 'iceTransportPolicy': 'relay',
21 : };
22 0 : pc1 = await delegate.createPeerConnection(configuration);
23 0 : pc2 = await delegate.createPeerConnection(configuration);
24 :
25 0 : pc1!.onIceCandidate = (candidate) {
26 0 : if (candidate.candidate!.contains('relay')) {
27 0 : pc2!.addCandidate(candidate);
28 : }
29 : };
30 0 : pc2!.onIceCandidate = (candidate) {
31 0 : if (candidate.candidate!.contains('relay')) {
32 0 : pc1!.addCandidate(candidate);
33 : }
34 : };
35 :
36 0 : await pc1!.createDataChannel('conn-tester', RTCDataChannelInit());
37 :
38 0 : final offer = await pc1!.createOffer();
39 :
40 0 : await pc2!.setRemoteDescription(offer);
41 0 : final answer = await pc2!.createAnswer();
42 :
43 0 : await pc1!.setLocalDescription(offer);
44 0 : await pc2!.setLocalDescription(answer);
45 :
46 0 : await pc1!.setRemoteDescription(answer);
47 :
48 0 : Future<void> dispose() async {
49 0 : await Future.wait([
50 0 : pc1!.close(),
51 0 : pc2!.close(),
52 : ]);
53 0 : await Future.wait([
54 0 : pc1!.dispose(),
55 0 : pc2!.dispose(),
56 : ]);
57 : }
58 :
59 : bool connected = false;
60 : try {
61 0 : await waitUntilAsync(() async {
62 0 : if (pc1!.connectionState ==
63 : RTCPeerConnectionState.RTCPeerConnectionStateConnected &&
64 0 : pc2!.connectionState ==
65 : RTCPeerConnectionState.RTCPeerConnectionStateConnected) {
66 : connected = true;
67 : return true;
68 : }
69 : return false;
70 : });
71 : } catch (e, s) {
72 0 : Logs()
73 0 : .e('[VOIP] ConnectionTester Error while testing TURN server: ', e, s);
74 : }
75 :
76 : // ignore: unawaited_futures
77 0 : dispose();
78 : return connected;
79 : }
80 :
81 0 : Future<int> waitUntilAsync(
82 : Future<bool> Function() test, {
83 : final int maxIterations = 1000,
84 : final Duration step = const Duration(milliseconds: 10),
85 : }) async {
86 : int iterations = 0;
87 0 : for (; iterations < maxIterations; iterations++) {
88 0 : await Future.delayed(step);
89 0 : if (await test()) {
90 : break;
91 : }
92 : }
93 0 : if (iterations >= maxIterations) {
94 0 : throw TimeoutException(
95 0 : 'Condition not reached within ${iterations * step.inMilliseconds}ms',
96 : );
97 : }
98 : return iterations;
99 : }
100 :
101 0 : Future<List<Map<String, dynamic>>> getIceServers() async {
102 0 : if (_turnServerCredentials == null) {
103 : try {
104 0 : _turnServerCredentials = await client.getTurnServer();
105 : } catch (e) {
106 0 : Logs().v('[VOIP] getTurnServerCredentials error => ${e.toString()}');
107 : }
108 : }
109 :
110 0 : if (_turnServerCredentials == null) {
111 0 : return [];
112 : }
113 :
114 0 : return [
115 0 : {
116 0 : 'username': _turnServerCredentials!.username,
117 0 : 'credential': _turnServerCredentials!.password,
118 0 : 'url': _turnServerCredentials!.uris[0],
119 : }
120 : ];
121 : }
122 : }
|