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(Future<bool> Function() test, 82 : {final int maxIterations = 1000, 83 : final Duration step = const Duration(milliseconds: 10)}) async { 84 : int iterations = 0; 85 0 : for (; iterations < maxIterations; iterations++) { 86 0 : await Future.delayed(step); 87 0 : if (await test()) { 88 : break; 89 : } 90 : } 91 0 : if (iterations >= maxIterations) { 92 0 : throw TimeoutException( 93 0 : 'Condition not reached within ${iterations * step.inMilliseconds}ms'); 94 : } 95 : return iterations; 96 : } 97 : 98 0 : Future<List<Map<String, dynamic>>> getIceServers() async { 99 0 : if (_turnServerCredentials == null) { 100 : try { 101 0 : _turnServerCredentials = await client.getTurnServer(); 102 : } catch (e) { 103 0 : Logs().v('[VOIP] getTurnServerCredentials error => ${e.toString()}'); 104 : } 105 : } 106 : 107 0 : if (_turnServerCredentials == null) { 108 0 : return []; 109 : } 110 : 111 0 : return [ 112 0 : { 113 0 : 'username': _turnServerCredentials!.username, 114 0 : 'credential': _turnServerCredentials!.password, 115 0 : 'url': _turnServerCredentials!.uris[0] 116 : } 117 : ]; 118 : } 119 : }