From a783abfdc5e998fcd51f0ef3ce43e82f39084a85 Mon Sep 17 00:00:00 2001 From: mamena2020 Date: Fri, 2 Dec 2022 19:35:45 +0900 Subject: [PATCH] [1]. config-> add default TURN servers, [2]. exchange ice candidate after 2 seconds after sdp offer anser --- README.md | 16 +-- dotenv.example | 8 +- lib/Services/WebRTC/Audio/WRTCAudio.dart | 2 +- lib/Services/WebRTC/Config/WRTCConfig.dart | 106 +++++++++--------- .../WebRTC/RTCConnection/WRTCProducer.dart | 53 +++++++-- .../WebRTC/Signaling/WRTCSocketEvent.dart | 7 +- 6 files changed, 112 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index 82ea483..c49a2cd 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,9 @@ Online meeting app like google meet, build with flutter for all platforms. this app uses WebRTC for media real-time communication, and socket.io for signaling & messaging. Server running on nodejs with SFU architecture which features the following data transmission processes between the media server and the endpoints (client). -This app also using Zomie TURN Server as relays media, - TURN Server work as a backup if STUN Server won't work because client device behind of symmetric NAT +This app also using TURN Server as relays media, work as a backup plan if STUN Server won't work because client device behind of symmetric NAT. TURN server is already end-to-end encrypted by the peers and the TURN Server cannot decode/read the encrypted packet, it just relays the packet to other peers. By default TURN already setup in file (lib/Services/WebRTC/Config/WRTCConfig.dart) +using free TURN Servers from OPEN RELAY. +but you can add your own Turn server using Zomie TURN Server as relays media. - Features @@ -62,14 +63,14 @@ This app also using Zo #How to use - create dotenv file - cmd: cp dotenv.example dotenv - - fill in credentials + - fill in credentials, you can ignore (TURN_SERVER_HOST, TURN_SERVER_USERNAME, TURN_SERVER_PASSWORD), because by default its already setup, but its okay if you want to add more your own TURN server. ``` MEDIA_SERVER_HOST = "localhost:5000" - ALLOW_TURN_SERVER = "true" + ALLOW_TURN_SERVER = "false" TURN_SERVER_HOST = "turn:ip:port" #example: "turn:192.168.1.9:3478" - TURN_SERVER_USERNAME = "zomie" - TURN_SERVER_PASSWORD = "password" + TURN_SERVER_USERNAME = "" #exampe: "zomie" + TURN_SERVER_PASSWORD = "" #example: "password" ``` @@ -116,7 +117,8 @@ This app also using Zo - All Platform - STUNT/TURN server - STUNT: "urls": "stun:stun.stunprotocol.org" - - TURN: Zomie TURN Server + - Stunt will not working if client is under symmetric NAT. + - TURN: by default TURN server using from Open Relay, or you can add more using Zomie TURN Server - Socket io diff --git a/dotenv.example b/dotenv.example index 411b16b..3a9a573 100644 --- a/dotenv.example +++ b/dotenv.example @@ -1,7 +1,7 @@ MEDIA_SERVER_HOST = "http://localhost:5000" -ALLOW_TURN_SERVER = "true" # "true" or "false" -TURN_SERVER_HOST = "turn:ip:port" #example: "turn:192.168.1.9:3478" -TURN_SERVER_USERNAME = "zomie" -TURN_SERVER_PASSWORD = "password" \ No newline at end of file +ALLOW_TURN_SERVER = "false" +TURN_SERVER_HOST = "" #example: "turn:192.168.1.9:3478" +TURN_SERVER_USERNAME = "" #exampe: "zomie" +TURN_SERVER_PASSWORD = "" #example: "password" \ No newline at end of file diff --git a/lib/Services/WebRTC/Audio/WRTCAudio.dart b/lib/Services/WebRTC/Audio/WRTCAudio.dart index 5a12dcc..36a330e 100644 --- a/lib/Services/WebRTC/Audio/WRTCAudio.dart +++ b/lib/Services/WebRTC/Audio/WRTCAudio.dart @@ -38,7 +38,7 @@ class WRTCAudio { // _player.play(DeviceFileSource('assets/assets/audio/notif_1.wav')); } else { // _player.play(DeviceFileSource('asset/audio/notif_1.wav')); - _player.play(DeviceFileSource('asset/audio/' + audioName)); + _player.play(DeviceFileSource('audio/' + audioName)); } } catch (e) { print(e); diff --git a/lib/Services/WebRTC/Config/WRTCConfig.dart b/lib/Services/WebRTC/Config/WRTCConfig.dart index bc74d11..ee57434 100644 --- a/lib/Services/WebRTC/Config/WRTCConfig.dart +++ b/lib/Services/WebRTC/Config/WRTCConfig.dart @@ -3,70 +3,66 @@ import 'package:flutter_dotenv/flutter_dotenv.dart'; class WRTCCOnfig { static String host = dotenv.get("MEDIA_SERVER_HOST", fallback: ""); - // static const configurationPeerConnection = { - // "sdpSemantics": "unified-plan", // Add this line for support windows - // "iceServers": [ - // { - // "urls": "stun:stun.stunprotocol.org" - // // urls: "stun:stun.l.google.com:19302?transport=tcp" - // }, - // // { - // // // "urls": "stun:stun.stunprotocol.org" - // // "urls": "stun:stun.l.google.com:19302?transport=tcp" - // // } - // ] - // }; - static Map configurationPeerConnection() { + // Map stun2 = {"urls": "stun:stun.l.google.com:19302"}; + + var iceServers = [ + // //---------------------------- open relay + {"urls": "stun:stun.stunprotocol.org"}, + { + "urls": "stun:openrelay.metered.ca:80", + "username": "openrelayproject", + "credential": "openrelayproject", + }, + { + "urls": "turn:openrelay.metered.ca:80", + "username": "openrelayproject", + "credential": "openrelayproject", + }, + { + "urls": "turn:openrelay.metered.ca:443", + "username": "openrelayproject", + "credential": "openrelayproject", + }, + { + "urls": "turn:openrelay.metered.ca:80?transport=tcp", + "username": "openrelayproject", + "credential": "openrelayproject", + }, + { + "urls": "turn:openrelay.metered.ca:443?transport=tcp", + "username": "openrelayproject", + "credential": "openrelayproject", + }, + { + "urls": "turns:openrelay.metered.ca:443", + "username": "openrelayproject", + "credential": "openrelayproject", + }, + ]; + + String dotenvAllowTurnServer = + dotenv.get("ALLOW_TURN_SERVER", fallback: "false"); bool allowTurnServer = - dotenv.get("ALLOW_TURN_SERVER", fallback: "false") == "true" + dotenvAllowTurnServer == "true" || dotenvAllowTurnServer == true ? true : false; - Map stun = {"urls": "stun:stun.stunprotocol.org"}; - if (allowTurnServer) { - print("using turn server"); - String turnServerHost = dotenv.get("TURN_SERVER_HOST", fallback: ""); - String turnServerUsername = - dotenv.get("TURN_SERVER_USERNAME", fallback: ""); - String turnServerPassword = - dotenv.get("TURN_SERVER_PASSWORD", fallback: ""); - print(turnServerHost); - print(turnServerUsername); - print(turnServerPassword); - - return { - "sdpSemantics": "unified-plan", - 'iceServers': [ - stun, - { - 'url': turnServerHost, - 'username': turnServerUsername, - 'credential': turnServerPassword - }, - ] + String turnServerHost = dotenv.get("TURN_SERVER_HOST"); + String turnServerUsername = dotenv.get("TURN_SERVER_USERNAME"); + String turnServerPassword = dotenv.get("TURN_SERVER_PASSWORD"); + if (turnServerHost != "" && turnServerHost.length > 3 && allowTurnServer) { + print("using turn server costume"); + var turn = { + 'urls': turnServerHost, + 'username': turnServerUsername, + 'credential': turnServerPassword }; + iceServers.add(turn); } - return { - "sdpSemantics": "unified-plan", - 'iceServers': [stun] - }; + return {"sdpSemantics": "unified-plan", 'iceServers': iceServers}; } - // static const configurationPeerConnection = { - // 'iceServers': [ - // {'url': 'stun:stun.l.google.com:19302'}, - // /* - // * turn server configuration example. - // { - // 'url': 'turn:123.45.67.89:3478', - // 'username': 'change_to_real_user', - // 'credential': 'change_to_real_secret' - // }, - // */ - // ] - // }; - static const offerSdpConstraints = { "mandatory": { "OfferToReceiveAudio": true, diff --git a/lib/Services/WebRTC/RTCConnection/WRTCProducer.dart b/lib/Services/WebRTC/RTCConnection/WRTCProducer.dart index cf61aef..833bd40 100644 --- a/lib/Services/WebRTC/RTCConnection/WRTCProducer.dart +++ b/lib/Services/WebRTC/RTCConnection/WRTCProducer.dart @@ -46,6 +46,9 @@ class WRTCProducer { this.videoRenderer.initialize(); } + List localCandidates = []; + List remoteCandidates = []; + Future MuteUnMute() async { if (this.stream != null) { this.stream!.getAudioTracks()[0].enabled = @@ -103,8 +106,6 @@ class WRTCProducer { } } - List candidates = []; - bool firstConnect = false; GetUserMedia() async { @@ -131,7 +132,8 @@ class WRTCProducer { * create peer connection to server */ Future CreateConnection() async { - candidates.clear(); + localCandidates.clear(); + remoteCandidates.clear(); if (this.stream == null) { if (this.callType == CallType.screenSharing) { await GetDisplayMedia(); @@ -158,6 +160,7 @@ class WRTCProducer { await _setTrack(); + _onIceCandidate(); //----------------- process handshake // renegitiaion needed allways call wen setTrack it trigger to addtrack await _sdpProccess(); @@ -169,8 +172,6 @@ class WRTCProducer { // }; onStopLocalStream(); - _onIceCandidate(); - this.peer!.onIceConnectionState = (e) { try { if (this.peer != null) { @@ -247,14 +248,13 @@ class WRTCProducer { await WRTCUtils.SetRemoteDescriptionFromJson( peer: peer!, sdpRemote: body["data"]["sdp"]); print("p-@@@ success set remote producer"); - + ExchangeIceCandidate(); await WRTCUtils.setBitrate( peer: this.peer!, bitrate: this.producerType == ProducerType.user ? this.room.video_bitrate : this.room.screen_bitrate); - await _AddCandidatesToServer(); WRTCSocketFunction.NotifyServer( type: NotifyType.join, producer_id: this.producer.id, @@ -266,6 +266,16 @@ class WRTCProducer { // } } + ExchangeIceCandidate() async { + Future.delayed( + Duration( + seconds: 3, + ), () async { + await _AddRemoteCandidateToLocal(); + _SendLocalCandidatesToServer(); + }); + } + // onTrack() { // try { // // this.peer!.onTrack = (e) { @@ -328,8 +338,9 @@ class WRTCProducer { _onIceCandidate() { this.peer!.onIceCandidate = (e) { if (e.candidate != null) { - print("p-fire candidate to stored in candidates"); - candidates.add(Candidate( + print("p- add local candidate"); + print(e.candidate); + localCandidates.add(Candidate( candidate: e.candidate!, sdpMid: e.sdpMid!, sdpMLineIndex: e.sdpMLineIndex!)); @@ -337,13 +348,31 @@ class WRTCProducer { }; } - _AddCandidatesToServer() async { - for (var c in this.candidates) { + _SendLocalCandidatesToServer() async { + print("p=> SEND ALL CANDIDATE TO SERVER:" + + localCandidates.length.toString()); + for (var c in this.localCandidates) { print("p-add candidate to server"); await WRTCSocketFunction.addCandidateToServer( producer_id: this.producer.id, candidate: c); - this.candidates.clear(); } + this.localCandidates.clear(); + } + + //1 + storeRemoteCandidateFromServer(RTCIceCandidate c) async { + remoteCandidates.add(c); + } + +//2 + _AddRemoteCandidateToLocal() async { + print("p=> ADD ALL CANDIDATE TO LOCAL: " + + remoteCandidates.length.toString()); + for (var c in this.remoteCandidates) { + print("p-add candidate to local"); + this.peer!.addCandidate(c); + } + this.remoteCandidates.clear(); } bool _isMinimizeMedia = false; diff --git a/lib/Services/WebRTC/Signaling/WRTCSocketEvent.dart b/lib/Services/WebRTC/Signaling/WRTCSocketEvent.dart index 8599fa8..f2fc0f6 100644 --- a/lib/Services/WebRTC/Signaling/WRTCSocketEvent.dart +++ b/lib/Services/WebRTC/Signaling/WRTCSocketEvent.dart @@ -72,7 +72,12 @@ class WRTCSocketEvent { WRTCService.instance().wrtcProducer!.peer != null && WRTCService.instance().wrtcProducer!.producer.id == data["producer_id"]) { - WRTCService.instance().wrtcProducer!.peer!.addCandidate(_candidate); + print("<<<<<~ incoming candidate from server"); + print(data["candidate"]); + print("<<<<<| incoming candidate from server"); + WRTCService.instance() + .wrtcProducer! + .storeRemoteCandidateFromServer(_candidate); } if (data["type"] == "screen" && WRTCService.instance().wrtcShareScreen != null &&