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 : }