From baa3f7b2dfb9ce9e0737dd414e3fd6837aeb9e07 Mon Sep 17 00:00:00 2001 From: CloudWebRTC Date: Wed, 20 Nov 2019 21:05:16 +0800 Subject: [PATCH] Add web support. --- lib/main.dart | 16 ++-- lib/src/call_sample/call_sample.dart | 49 +++++----- lib/src/call_sample/data_channel_sample.dart | 6 +- lib/src/call_sample/signaling.dart | 97 ++++++-------------- lib/src/utils/device_info.dart | 11 +++ lib/src/utils/device_info_web.dart | 11 +++ lib/src/utils/key_value_store.dart | 16 ++++ lib/src/utils/key_value_store_web.dart | 28 ++++++ lib/src/utils/websocket.dart | 77 ++++++++++++++++ lib/src/utils/websocket_web.dart | 47 ++++++++++ web/index.html | 10 ++ 11 files changed, 263 insertions(+), 105 deletions(-) create mode 100644 lib/src/utils/device_info.dart create mode 100644 lib/src/utils/device_info_web.dart create mode 100644 lib/src/utils/key_value_store.dart create mode 100644 lib/src/utils/key_value_store_web.dart create mode 100644 lib/src/utils/websocket.dart create mode 100644 lib/src/utils/websocket_web.dart create mode 100644 web/index.html diff --git a/lib/main.dart b/lib/main.dart index 034070c..d51bb5e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:shared_preferences/shared_preferences.dart'; +import 'src/utils/key_value_store.dart' + if (dart.library.js) 'src/utils/key_value_store_web.dart'; import 'dart:core'; import 'src/basic_sample/basic_sample.dart'; import 'src/call_sample/call_sample.dart'; @@ -21,7 +22,7 @@ enum DialogDemoAction { class _MyAppState extends State { List items; String _serverAddress = ''; - SharedPreferences prefs; + KeyValueStore keyValueStore = KeyValueStore(); bool _datachannel = false; @override initState() { @@ -59,9 +60,9 @@ class _MyAppState extends State { } _initData() async { - prefs = await SharedPreferences.getInstance(); + await keyValueStore.init(); setState(() { - _serverAddress = prefs.getString('server') ?? 'demo.cloudwebrtc.com'; + _serverAddress = keyValueStore.getString('server') ?? 'demo.cloudwebrtc.com'; }); } @@ -73,12 +74,13 @@ class _MyAppState extends State { // The value passed to Navigator.pop() or null. if (value != null) { if (value == DialogDemoAction.connect) { - prefs.setString('server', _serverAddress); + keyValueStore.setString('server', _serverAddress); Navigator.push( context, MaterialPageRoute( - builder: (BuildContext context) => - _datachannel? DataChannelSample(ip: _serverAddress) : CallSample(ip: _serverAddress))); + builder: (BuildContext context) => _datachannel + ? DataChannelSample(ip: _serverAddress) + : CallSample(ip: _serverAddress))); } } }); diff --git a/lib/src/call_sample/call_sample.dart b/lib/src/call_sample/call_sample.dart index 62f06f4..273807b 100644 --- a/lib/src/call_sample/call_sample.dart +++ b/lib/src/call_sample/call_sample.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'dart:io'; import 'dart:core'; import 'signaling.dart'; import 'package:flutter_webrtc/webrtc.dart'; @@ -17,8 +16,6 @@ class CallSample extends StatefulWidget { class _CallSampleState extends State { Signaling _signaling; - String _displayName = - Platform.localHostname + '(' + Platform.operatingSystem + ")"; List _peers; var _selfId; RTCVideoRenderer _localRenderer = new RTCVideoRenderer(); @@ -50,8 +47,7 @@ class _CallSampleState extends State { void _connect() async { if (_signaling == null) { - _signaling = new Signaling(serverIP, _displayName) - ..connect(); + _signaling = new Signaling(serverIP)..connect(); _signaling.onStateChange = (SignalingState state) { switch (state) { @@ -114,9 +110,7 @@ class _CallSampleState extends State { _signaling.switchCamera(); } - _muteMic() { - - } + _muteMic() {} _buildRow(context, peer) { var self = (peer['id'] == _selfId); @@ -164,25 +158,26 @@ class _CallSampleState extends State { floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, floatingActionButton: _inCalling ? new SizedBox( - width: 200.0, - child: new Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - FloatingActionButton( - child: const Icon(Icons.switch_camera), - onPressed: _switchCamera, - ), - FloatingActionButton( - onPressed: _hangUp, - tooltip: 'Hangup', - child: new Icon(Icons.call_end), - backgroundColor: Colors.pink, - ), - FloatingActionButton( - child: const Icon(Icons.mic_off), - onPressed: _muteMic, - ) - ])) : null, + width: 200.0, + child: new Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + FloatingActionButton( + child: const Icon(Icons.switch_camera), + onPressed: _switchCamera, + ), + FloatingActionButton( + onPressed: _hangUp, + tooltip: 'Hangup', + child: new Icon(Icons.call_end), + backgroundColor: Colors.pink, + ), + FloatingActionButton( + child: const Icon(Icons.mic_off), + onPressed: _muteMic, + ) + ])) + : null, body: _inCalling ? OrientationBuilder(builder: (context, orientation) { return new Container( diff --git a/lib/src/call_sample/data_channel_sample.dart b/lib/src/call_sample/data_channel_sample.dart index 60d1db0..0a354c2 100644 --- a/lib/src/call_sample/data_channel_sample.dart +++ b/lib/src/call_sample/data_channel_sample.dart @@ -4,7 +4,7 @@ import 'dart:core'; import 'dart:async'; import 'dart:typed_data'; import 'signaling.dart'; -import 'package:flutter_webrtc/rtc_data_channel.dart'; +import 'package:flutter_webrtc/webrtc.dart'; class DataChannelSample extends StatefulWidget { static String tag = 'call_sample'; @@ -19,8 +19,6 @@ class DataChannelSample extends StatefulWidget { class _DataChannelSampleState extends State { Signaling _signaling; - String _displayName = - Platform.localHostname + '(' + Platform.operatingSystem + ")"; List _peers; var _selfId; bool _inCalling = false; @@ -47,7 +45,7 @@ class _DataChannelSampleState extends State { void _connect() async { if (_signaling == null) { - _signaling = new Signaling(serverIP, _displayName) + _signaling = new Signaling(serverIP) ..connect(); _signaling.onDataChannelMessage = (dc, RTCDataChannelMessage data){ diff --git a/lib/src/call_sample/signaling.dart b/lib/src/call_sample/signaling.dart index a993c83..76c9e83 100644 --- a/lib/src/call_sample/signaling.dart +++ b/lib/src/call_sample/signaling.dart @@ -1,10 +1,13 @@ import 'dart:convert'; import 'dart:async'; -import 'dart:io'; -import 'dart:math'; 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'; + enum SignalingState { CallStateNew, CallStateRinging, @@ -28,11 +31,10 @@ typedef void DataChannelCallback(RTCDataChannel dc); class Signaling { String _selfId = randomNumeric(6); - var _socket; + SimpleWebSocket _socket; var _sessionId; var _host; var _port = 4443; - var _displayName; var _peerConnections = new Map(); var _dataChannels = new Map(); var _remoteCandidates = []; @@ -84,7 +86,7 @@ class Signaling { 'optional': [], }; - Signaling(this._host, this._displayName); + Signaling(this._host); close() { if (_localStream != null) { @@ -258,74 +260,36 @@ class Signaling { } } - Future _connectForSelfSignedCert(String host, int port) 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); - client.badCertificateCallback = - (X509Certificate cert, String host, int port) { - print('Allow self-signed certificate => $host:$port. '); - return true; - }; - - HttpClientRequest request = await client.getUrl( - Uri.parse('https://$host:$port/ws')); // form the correct url here - request.headers.add('Connection', 'Upgrade'); - request.headers.add('Upgrade', 'websocket'); - request.headers.add( - 'Sec-WebSocket-Version', '13'); // insert the correct version here - request.headers.add('Sec-WebSocket-Key', key.toLowerCase()); - - HttpClientResponse response = await request.close(); - Socket socket = await response.detachSocket(); - var webSocket = WebSocket.fromUpgradedSocket( - socket, - protocol: 'signaling', - serverSide: false, - ); - - return webSocket; - } catch (e) { - throw e; - } - } - void connect() async { - try { - /* - var url = 'ws://$_host:$_port'; - _socket = await WebSocket.connect(url); - */ - _socket = await _connectForSelfSignedCert(_host, _port); + var url = 'wss://$_host:$_port'; + _socket = SimpleWebSocket(url); - if (this.onStateChange != null) { - this.onStateChange(SignalingState.ConnectionOpen); - } - - _socket.listen((data) { - print('Recivied data: ' + data); - JsonDecoder decoder = new JsonDecoder(); - this.onMessage(decoder.convert(data)); - }, onDone: () { - print('Closed by server!'); - if (this.onStateChange != null) { - this.onStateChange(SignalingState.ConnectionClosed); - } - }); + print('connect to $url'); + _socket.onOpen = () { + print('onOpen'); + this?.onStateChange(SignalingState.ConnectionOpen); _send('new', { - 'name': _displayName, + 'name': DeviceInfo.label, 'id': _selfId, - 'user_agent': - 'flutter-webrtc/' + Platform.operatingSystem + '-plugin 0.0.1' + 'user_agent': DeviceInfo.userAgent }); - } catch (e) { + }; + + _socket.onMessage = (message) { + print('Recivied data: ' + message); + JsonDecoder decoder = new JsonDecoder(); + this.onMessage(decoder.convert(message)); + }; + + _socket.onClose = (int code, String reason) { + print('Closed by server [$code => $reason]!'); if (this.onStateChange != null) { - this.onStateChange(SignalingState.ConnectionError); + this.onStateChange(SignalingState.ConnectionClosed); } - } + }; + + await _socket.connect(); } Future createStream(media, user_screen) async { @@ -440,7 +404,6 @@ class Signaling { _send(event, data) { data['type'] = event; JsonEncoder encoder = new JsonEncoder(); - if (_socket != null) _socket.add(encoder.convert(data)); - print('send: ' + encoder.convert(data)); + _socket.send(encoder.convert(data)); } } diff --git a/lib/src/utils/device_info.dart b/lib/src/utils/device_info.dart new file mode 100644 index 0000000..21617f4 --- /dev/null +++ b/lib/src/utils/device_info.dart @@ -0,0 +1,11 @@ +import 'dart:io'; + +class DeviceInfo { + static String get label { + return Platform.localHostname + '(' + Platform.operatingSystem + ")"; + } + + static String get userAgent { + return 'flutter-webrtc/' + Platform.operatingSystem + '-plugin 0.0.1'; + } +} diff --git a/lib/src/utils/device_info_web.dart b/lib/src/utils/device_info_web.dart new file mode 100644 index 0000000..0899668 --- /dev/null +++ b/lib/src/utils/device_info_web.dart @@ -0,0 +1,11 @@ +import 'dart:html' as HTML; + +class DeviceInfo { + static String get label { + return 'Flutter Web ( ' + HTML.window.navigator.userAgent + ' )'; + } + + static String get userAgent { + return 'flutter-webrtc/web-plugin 0.0.1'; + } +} diff --git a/lib/src/utils/key_value_store.dart b/lib/src/utils/key_value_store.dart new file mode 100644 index 0000000..56e5eb5 --- /dev/null +++ b/lib/src/utils/key_value_store.dart @@ -0,0 +1,16 @@ +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 new file mode 100644 index 0000000..0fc445a --- /dev/null +++ b/lib/src/utils/key_value_store_web.dart @@ -0,0 +1,28 @@ +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/websocket.dart b/lib/src/utils/websocket.dart new file mode 100644 index 0000000..6f031cc --- /dev/null +++ b/lib/src/utils/websocket.dart @@ -0,0 +1,77 @@ +import 'dart:io'; +import 'dart:math'; +import 'dart:convert'; +import 'dart:async'; + +typedef void OnMessageCallback(dynamic msg); +typedef void OnCloseCallback(int code, String reason); +typedef void OnOpenCallback(); + +class SimpleWebSocket { + String _url; + var _socket; + OnOpenCallback onOpen; + OnMessageCallback onMessage; + OnCloseCallback onClose; + SimpleWebSocket(this._url); + + connect() async { + try { + _socket = await WebSocket.connect(_url); + //socket = await _connectForSelfSignedCert(_host, _port); + this?.onOpen(); + _socket.listen((data) { + this?.onMessage(data); + }, onDone: () { + this?.onClose(_socket.closeCode, _socket.closeReason); + }); + } catch (e) { + this.onClose(_socket.closeCode, _socket.closeReason); + } + } + + send(data) { + if (_socket != null) { + _socket.add(data); + print('send: $data'); + } + } + + close() { + _socket.close(); + } + + Future _connectForSelfSignedCert(String host, int port) 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); + client.badCertificateCallback = + (X509Certificate cert, String host, int port) { + print('Allow self-signed certificate => $host:$port. '); + return true; + }; + + HttpClientRequest request = await client.getUrl( + Uri.parse('https://$host:$port/ws')); // form the correct url here + request.headers.add('Connection', 'Upgrade'); + request.headers.add('Upgrade', 'websocket'); + request.headers.add( + 'Sec-WebSocket-Version', '13'); // insert the correct version here + request.headers.add('Sec-WebSocket-Key', key.toLowerCase()); + + HttpClientResponse response = await request.close(); + Socket socket = await response.detachSocket(); + var webSocket = WebSocket.fromUpgradedSocket( + socket, + protocol: 'signaling', + serverSide: false, + ); + + return webSocket; + } catch (e) { + throw e; + } + } +} diff --git a/lib/src/utils/websocket_web.dart b/lib/src/utils/websocket_web.dart new file mode 100644 index 0000000..f7ecdc5 --- /dev/null +++ b/lib/src/utils/websocket_web.dart @@ -0,0 +1,47 @@ +import 'dart:html'; + +typedef void OnMessageCallback(dynamic msg); +typedef void OnCloseCallback(int code, String reason); +typedef void OnOpenCallback(); + +class SimpleWebSocket { + String _url; + var _socket; + OnOpenCallback onOpen; + OnMessageCallback onMessage; + OnCloseCallback onClose; + + SimpleWebSocket(this._url); + + connect() async { + try { + _socket = WebSocket(_url); + _socket.onOpen.listen((e) { + this?.onOpen(); + }); + + _socket.onMessage.listen((e) { + this?.onMessage(e.data); + }); + + _socket.onClose.listen((e) { + this?.onClose(e.code, e.reason); + }); + } catch (e) { + this?.onClose(e.code, e.reason); + } + } + + send(data) { + if (_socket != null && _socket.readyState == WebSocket.OPEN) { + _socket.send(data); + print('send: $data'); + } else { + print('WebSocket not connected, message $data not sent'); + } + } + + close() { + _socket.close(); + } +} diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..3940fd7 --- /dev/null +++ b/web/index.html @@ -0,0 +1,10 @@ + + + + + Flutter WebRTC Demo + + + + +