From 7238d6ac736a9eda47dc4664c04e65f1c127a91d Mon Sep 17 00:00:00 2001 From: CloudWebRTC Date: Sat, 14 Mar 2020 19:44:01 +0800 Subject: [PATCH 1/4] Changes for golang server. --- lib/src/basic_sample/loopback_sample.dart | 2 ++ lib/src/call_sample/signaling.dart | 12 ++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/src/basic_sample/loopback_sample.dart b/lib/src/basic_sample/loopback_sample.dart index 32702bd..993f080 100644 --- a/lib/src/basic_sample/loopback_sample.dart +++ b/lib/src/basic_sample/loopback_sample.dart @@ -151,6 +151,8 @@ class _MyAppState extends State { //change for loopback. description.type = 'answer'; _peerConnection.setRemoteDescription(description); + + _localStream.getAudioTracks()[0].setMicrophoneMute(false); } catch (e) { print(e.toString()); } diff --git a/lib/src/call_sample/signaling.dart b/lib/src/call_sample/signaling.dart index 11ef011..307b9e9 100644 --- a/lib/src/call_sample/signaling.dart +++ b/lib/src/call_sample/signaling.dart @@ -220,7 +220,6 @@ class Signaling { break; case 'bye': { - var from = data['from']; var to = data['to']; var sessionId = data['session_id']; print('bye: ' + sessionId); @@ -259,7 +258,7 @@ class Signaling { } void connect() async { - var url = 'wss://$_host:$_port'; + var url = 'wss://$_host:$_port/ws'; _socket = SimpleWebSocket(url); print('connect to $url'); @@ -321,6 +320,7 @@ class Signaling { pc.onIceCandidate = (candidate) { _send('candidate', { 'to': id, + 'from': _selfId, 'candidate': { 'sdpMLineIndex': candidate.sdpMlineIndex, 'sdpMid': candidate.sdpMid, @@ -375,6 +375,7 @@ class Signaling { pc.setLocalDescription(s); _send('offer', { 'to': id, + 'from': _selfId, 'description': {'sdp': s.sdp, 'type': s.type}, 'session_id': this._sessionId, 'media': media, @@ -391,6 +392,7 @@ class Signaling { pc.setLocalDescription(s); _send('answer', { 'to': id, + 'from': _selfId, 'description': {'sdp': s.sdp, 'type': s.type}, 'session_id': this._sessionId, }); @@ -400,8 +402,10 @@ class Signaling { } _send(event, data) { - data['type'] = event; + var request = new Map(); + request["type"] = event; + request["data"] = data; JsonEncoder encoder = new JsonEncoder(); - _socket.send(encoder.convert(data)); + _socket.send(encoder.convert(request)); } } From 5c8b48566155fb6445e7323bce2974d07b12624a Mon Sep 17 00:00:00 2001 From: CloudWebRTC Date: Sat, 14 Mar 2020 22:41:11 +0800 Subject: [PATCH 2/4] Add getTurnCredential. --- lib/src/call_sample/signaling.dart | 40 ++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/lib/src/call_sample/signaling.dart b/lib/src/call_sample/signaling.dart index 307b9e9..a88d32c 100644 --- a/lib/src/call_sample/signaling.dart +++ b/lib/src/call_sample/signaling.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'dart:async'; +import 'dart:io'; import 'package:flutter_webrtc/webrtc.dart'; import 'random_string.dart'; @@ -30,6 +31,8 @@ typedef void DataChannelMessageCallback( typedef void DataChannelCallback(RTCDataChannel dc); class Signaling { + JsonEncoder _encoder = new JsonEncoder(); + JsonDecoder _decoder = new JsonDecoder(); String _selfId = randomNumeric(6); SimpleWebSocket _socket; var _sessionId; @@ -38,6 +41,7 @@ class Signaling { var _peerConnections = new Map(); var _dataChannels = new Map(); var _remoteCandidates = []; + var _turnCredential; MediaStream _localStream; List _remoteStreams; @@ -100,6 +104,17 @@ class Signaling { if (_socket != null) _socket.close(); } + getTurnCredential(String host) async { + var httpClient = new HttpClient(); + var uri = new Uri.http('$host', '/api/turn', + {'service': 'turn', 'username': 'flutter-webrtc'}); + var request = await httpClient.getUrl(uri); + var response = await request.close(); + var responseBody = await response.transform(Utf8Decoder()).join(); + Map data = _decoder.convert(responseBody); + return data; + } + void switchCamera() { if (_localStream != null) { _localStream.getVideoTracks()[0].switchCamera(); @@ -263,6 +278,28 @@ class Signaling { print('connect to $url'); + 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': [ + { + 'url': _turnCredential['uris'][0], + 'username': _turnCredential['username'], + 'credential': _turnCredential['password'] + }, + ] + }; + } catch (e) {} + } + _socket.onOpen = () { print('onOpen'); this?.onStateChange(SignalingState.ConnectionOpen); @@ -405,7 +442,6 @@ class Signaling { var request = new Map(); request["type"] = event; request["data"] = data; - JsonEncoder encoder = new JsonEncoder(); - _socket.send(encoder.convert(request)); + _socket.send(_encoder.convert(request)); } } From ff4a8118d46c831eb5ee972900f652b337b9a014 Mon Sep 17 00:00:00 2001 From: CloudWebRTC Date: Sun, 15 Mar 2020 00:02:17 +0800 Subject: [PATCH 3/4] Add turn relay support for web. --- lib/main.dart | 26 +++++++++++++----------- lib/src/call_sample/signaling.dart | 21 ++++++------------- lib/src/utils/key_value_store.dart | 16 --------------- lib/src/utils/key_value_store_web.dart | 28 -------------------------- lib/src/utils/turn.dart | 19 +++++++++++++++++ lib/src/utils/turn_web.dart | 13 ++++++++++++ lib/src/utils/websocket.dart | 17 ++++++++-------- lib/src/utils/websocket_web.dart | 8 +++++--- pubspec.yaml | 3 ++- 9 files changed, 67 insertions(+), 84 deletions(-) delete mode 100644 lib/src/utils/key_value_store.dart delete mode 100644 lib/src/utils/key_value_store_web.dart create mode 100644 lib/src/utils/turn.dart create mode 100644 lib/src/utils/turn_web.dart diff --git a/lib/main.dart b/lib/main.dart index d51bb5e..6fddb5f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,8 @@ -import 'package:flutter/material.dart'; -import 'src/utils/key_value_store.dart' - if (dart.library.js) 'src/utils/key_value_store_web.dart'; import 'dart:core'; + +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + import 'src/basic_sample/basic_sample.dart'; import 'src/call_sample/call_sample.dart'; import 'src/call_sample/data_channel_sample.dart'; @@ -21,8 +22,9 @@ enum DialogDemoAction { class _MyAppState extends State { List items; - String _serverAddress = ''; - KeyValueStore keyValueStore = KeyValueStore(); + String _server = ''; + SharedPreferences _prefs; + bool _datachannel = false; @override initState() { @@ -60,9 +62,9 @@ class _MyAppState extends State { } _initData() async { - await keyValueStore.init(); + _prefs = await SharedPreferences.getInstance(); setState(() { - _serverAddress = keyValueStore.getString('server') ?? 'demo.cloudwebrtc.com'; + _server = _prefs.getString('server') ?? 'demo.cloudwebrtc.com'; }); } @@ -74,13 +76,13 @@ class _MyAppState extends State { // The value passed to Navigator.pop() or null. if (value != null) { if (value == DialogDemoAction.connect) { - keyValueStore.setString('server', _serverAddress); + _prefs.setString('server', _server); Navigator.push( context, MaterialPageRoute( builder: (BuildContext context) => _datachannel - ? DataChannelSample(ip: _serverAddress) - : CallSample(ip: _serverAddress))); + ? DataChannelSample(ip: _server) + : CallSample(ip: _server))); } } }); @@ -94,11 +96,11 @@ class _MyAppState extends State { content: TextField( onChanged: (String text) { setState(() { - _serverAddress = text; + _server = text; }); }, decoration: InputDecoration( - hintText: _serverAddress, + hintText: _server, ), textAlign: TextAlign.center, ), diff --git a/lib/src/call_sample/signaling.dart b/lib/src/call_sample/signaling.dart index a88d32c..76a19f5 100644 --- a/lib/src/call_sample/signaling.dart +++ b/lib/src/call_sample/signaling.dart @@ -1,13 +1,15 @@ import 'dart:convert'; import 'dart:async'; -import 'dart:io'; import 'package:flutter_webrtc/webrtc.dart'; + import 'random_string.dart'; import '../utils/device_info.dart' if (dart.library.js) '../utils/device_info_web.dart'; import '../utils/websocket.dart' if (dart.library.js) '../utils/websocket_web.dart'; +import '../utils/turn.dart' + if (dart.library.js) '../utils/turn_web.dart'; enum SignalingState { CallStateNew, @@ -37,7 +39,7 @@ class Signaling { SimpleWebSocket _socket; var _sessionId; var _host; - var _port = 4443; + var _port = 8086; var _peerConnections = new Map(); var _dataChannels = new Map(); var _remoteCandidates = []; @@ -104,17 +106,6 @@ class Signaling { if (_socket != null) _socket.close(); } - getTurnCredential(String host) async { - var httpClient = new HttpClient(); - var uri = new Uri.http('$host', '/api/turn', - {'service': 'turn', 'username': 'flutter-webrtc'}); - var request = await httpClient.getUrl(uri); - var response = await request.close(); - var responseBody = await response.transform(Utf8Decoder()).join(); - Map data = _decoder.convert(responseBody); - return data; - } - void switchCamera() { if (_localStream != null) { _localStream.getVideoTracks()[0].switchCamera(); @@ -273,14 +264,14 @@ class Signaling { } void connect() async { - var url = 'wss://$_host:$_port/ws'; + var url = 'https://$_host:$_port/ws'; _socket = SimpleWebSocket(url); print('connect to $url'); if (_turnCredential == null) { try { - _turnCredential = await getTurnCredential(_host); + _turnCredential = await getTurnCredential(_host, _port); /*{ "username": "1584195784:mbzrxpgjys", "password": "isyl6FF6nqMTB9/ig5MrMRUXqZg", diff --git a/lib/src/utils/key_value_store.dart b/lib/src/utils/key_value_store.dart deleted file mode 100644 index 56e5eb5..0000000 --- a/lib/src/utils/key_value_store.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'dart:async'; -import 'package:shared_preferences/shared_preferences.dart'; - -class KeyValueStore { - KeyValueStore(); - SharedPreferences _preferences; - - init() async { - _preferences = await SharedPreferences.getInstance(); - } - - String getString(String key) => _preferences.getString(key); - - Future setString(String key, String value) => - _preferences.setString(key, value); -} diff --git a/lib/src/utils/key_value_store_web.dart b/lib/src/utils/key_value_store_web.dart deleted file mode 100644 index 0fc445a..0000000 --- a/lib/src/utils/key_value_store_web.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:html'; - -class KeyValueStore { - KeyValueStore(); - Storage _storage; - - init() async { - _storage = window.localStorage; - } - - String getString(String key) => _storage[key]; - - Future setString(String key, String value) => _setValue(key, value); - - Future _setValue(String key, dynamic value) { - if (value is String) { - _storage[key] = value; - } else if (value is bool || value is double || value is int) { - _storage[key] = value.toString(); - } else if (value is List) { - _storage[key] = json.encode(value); - } - - return Future.value(true); - } -} diff --git a/lib/src/utils/turn.dart b/lib/src/utils/turn.dart new file mode 100644 index 0000000..0d4a00f --- /dev/null +++ b/lib/src/utils/turn.dart @@ -0,0 +1,19 @@ +import 'dart:convert'; +import 'dart:async'; +import 'dart:io'; + +Future getTurnCredential(String host, int port) async { + HttpClient client = HttpClient(context: SecurityContext()); + client.badCertificateCallback = + (X509Certificate cert, String host, int port) { + print('getTurnCredential: Allow self-signed certificate => $host:$port. '); + return true; + }; + var url = 'https://$host:$port/api/turn?service=turn&username=flutter-webrtc'; + var request = await client.getUrl(Uri.parse(url)); + var response = await request.close(); + var responseBody = await response.transform(Utf8Decoder()).join(); + print('getTurnCredential:response => $responseBody.'); + Map data = JsonDecoder().convert(responseBody); + return data; + } diff --git a/lib/src/utils/turn_web.dart b/lib/src/utils/turn_web.dart new file mode 100644 index 0000000..63c16e9 --- /dev/null +++ b/lib/src/utils/turn_web.dart @@ -0,0 +1,13 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; + +Future getTurnCredential(String host, int port) async { + var url = 'https://$host:$port/api/turn?service=turn&username=flutter-webrtc'; + final res = await http.get(url); + if (res.statusCode == 200) { + var data = json.decode(res.body); + print('getTurnCredential:response => $data.'); + return data; + } + return {}; + } \ No newline at end of file diff --git a/lib/src/utils/websocket.dart b/lib/src/utils/websocket.dart index cd49115..4cd39f1 100644 --- a/lib/src/utils/websocket.dart +++ b/lib/src/utils/websocket.dart @@ -17,8 +17,8 @@ class SimpleWebSocket { connect() async { try { - _socket = await WebSocket.connect(_url); - //socket = await _connectForSelfSignedCert(_host, _port); + //_socket = await WebSocket.connect(_url); + _socket = await _connectForSelfSignedCert(_url); this?.onOpen(); _socket.listen((data) { this?.onMessage(data); @@ -38,23 +38,22 @@ class SimpleWebSocket { } close() { - _socket.close(); + if (_socket != null) + _socket.close(); } - Future _connectForSelfSignedCert(String host, int port) async { + Future _connectForSelfSignedCert(url) async { try { Random r = new Random(); String key = base64.encode(List.generate(8, (_) => r.nextInt(255))); - SecurityContext securityContext = new SecurityContext(); - HttpClient client = HttpClient(context: securityContext); + HttpClient client = HttpClient(context: SecurityContext()); client.badCertificateCallback = (X509Certificate cert, String host, int port) { - print('Allow self-signed certificate => $host:$port. '); + print('SimpleWebSocket: Allow self-signed certificate => $host:$port. '); return true; }; - HttpClientRequest request = await client.getUrl( - Uri.parse('https://$host:$port/ws')); // form the correct url here + HttpClientRequest request = await client.getUrl(Uri.parse(url)); // form the correct url here request.headers.add('Connection', 'Upgrade'); request.headers.add('Upgrade', 'websocket'); request.headers.add( diff --git a/lib/src/utils/websocket_web.dart b/lib/src/utils/websocket_web.dart index f7ecdc5..e33cf17 100644 --- a/lib/src/utils/websocket_web.dart +++ b/lib/src/utils/websocket_web.dart @@ -11,7 +11,9 @@ class SimpleWebSocket { OnMessageCallback onMessage; OnCloseCallback onClose; - SimpleWebSocket(this._url); + SimpleWebSocket(this._url) { + _url = _url.replaceAll('https:', 'wss:'); + } connect() async { try { @@ -28,7 +30,7 @@ class SimpleWebSocket { this?.onClose(e.code, e.reason); }); } catch (e) { - this?.onClose(e.code, e.reason); + this?.onClose(500, e.toString()); } } @@ -42,6 +44,6 @@ class SimpleWebSocket { } close() { - _socket.close(); + if (_socket != null) _socket.close(); } } diff --git a/pubspec.yaml b/pubspec.yaml index d82dbab..29e959b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,9 +19,10 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^0.1.2 - flutter_webrtc: ^0.2.2 + flutter_webrtc: ^0.2.6 shared_preferences: shared_preferences_macos: + shared_preferences_web: # Required for MediaRecorder example path_provider: From b896df87f5785a051d6fa9e1459062070e6b523b Mon Sep 17 00:00:00 2001 From: CloudWebRTC Date: Sun, 15 Mar 2020 00:11:12 +0800 Subject: [PATCH 4/4] Add http to pubspec.yaml. --- pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/pubspec.yaml b/pubspec.yaml index 29e959b..76f3edb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,6 +23,7 @@ dependencies: shared_preferences: shared_preferences_macos: shared_preferences_web: + http: ^0.12.0+4 # Required for MediaRecorder example path_provider: