diff --git a/.flutter-plugins b/.flutter-plugins index 74ba8f7..c25d779 100644 --- a/.flutter-plugins +++ b/.flutter-plugins @@ -1,19 +1,25 @@ # This is a generated file; do not edit or check into version control. -firebase_core=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/firebase_core-2.24.0/ -firebase_core_web=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/firebase_core_web-2.9.0/ -firebase_messaging=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/firebase_messaging-14.7.6/ -firebase_messaging_web=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/firebase_messaging_web-3.5.15/ -flutter_callkit_incoming=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/flutter_callkit_incoming-2.0.0+2/ -flutter_local_notifications=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/flutter_local_notifications-16.2.0/ -flutter_webrtc=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/flutter_webrtc-0.9.47/ +audioplayers=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/audioplayers-5.2.1/ +audioplayers_android=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/audioplayers_android-4.0.3/ +audioplayers_darwin=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/audioplayers_darwin-5.0.2/ +audioplayers_linux=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/audioplayers_linux-3.1.0/ +audioplayers_web=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/audioplayers_web-4.1.0/ +audioplayers_windows=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/audioplayers_windows-3.1.0/ +firebase_core=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/firebase_core-2.25.4/ +firebase_core_web=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/firebase_core_web-2.11.4/ +firebase_messaging=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/firebase_messaging-14.7.16/ +firebase_messaging_web=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/firebase_messaging_web-3.6.5/ +flutter_callkit_incoming=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/flutter_callkit_incoming-2.0.1+2/ +flutter_local_notifications=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/flutter_local_notifications-16.3.2/ +flutter_webrtc=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/flutter_webrtc-0.9.48+hotfix.1/ fluttertoast=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/fluttertoast-8.2.4/ -path_provider=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/path_provider-2.1.1/ -path_provider_android=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/path_provider_android-2.2.1/ -path_provider_foundation=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/path_provider_foundation-2.3.1/ +path_provider=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/path_provider-2.1.2/ +path_provider_android=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/path_provider_android-2.2.2/ +path_provider_foundation=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/path_provider_foundation-2.3.2/ path_provider_linux=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/ path_provider_windows=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/path_provider_windows-2.2.1/ -permission_handler=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/permission_handler-11.1.0/ -permission_handler_android=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/permission_handler_android-12.0.1/ -permission_handler_apple=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/permission_handler_apple-9.2.0/ -permission_handler_html=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/permission_handler_html-0.1.0+1/ -permission_handler_windows=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/permission_handler_windows-0.2.0/ +permission_handler=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/permission_handler-11.3.0/ +permission_handler_android=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/permission_handler_android-12.0.5/ +permission_handler_apple=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.0/ +permission_handler_html=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/permission_handler_html-0.1.1/ +permission_handler_windows=/Users/isaacakakpo/.pub-cache/hosted/pub.dev/permission_handler_windows-0.2.1/ diff --git a/.flutter-plugins-dependencies b/.flutter-plugins-dependencies index aad0889..b231dd1 100644 --- a/.flutter-plugins-dependencies +++ b/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"firebase_core","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/firebase_core-2.24.0/","native_build":true,"dependencies":[]},{"name":"firebase_messaging","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/firebase_messaging-14.7.6/","native_build":true,"dependencies":["firebase_core"]},{"name":"flutter_callkit_incoming","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/flutter_callkit_incoming-2.0.0+2/","native_build":true,"dependencies":[]},{"name":"flutter_local_notifications","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/flutter_local_notifications-16.2.0/","native_build":true,"dependencies":[]},{"name":"flutter_webrtc","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/flutter_webrtc-0.9.47/","native_build":true,"dependencies":[]},{"name":"fluttertoast","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/fluttertoast-8.2.4/","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/path_provider_foundation-2.3.1/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"permission_handler_apple","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/permission_handler_apple-9.2.0/","native_build":true,"dependencies":[]}],"android":[{"name":"firebase_core","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/firebase_core-2.24.0/","native_build":true,"dependencies":[]},{"name":"firebase_messaging","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/firebase_messaging-14.7.6/","native_build":true,"dependencies":["firebase_core"]},{"name":"flutter_callkit_incoming","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/flutter_callkit_incoming-2.0.0+2/","native_build":true,"dependencies":[]},{"name":"flutter_local_notifications","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/flutter_local_notifications-16.2.0/","native_build":true,"dependencies":[]},{"name":"flutter_webrtc","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/flutter_webrtc-0.9.47/","native_build":true,"dependencies":[]},{"name":"fluttertoast","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/fluttertoast-8.2.4/","native_build":true,"dependencies":[]},{"name":"path_provider_android","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/path_provider_android-2.2.1/","native_build":true,"dependencies":[]},{"name":"permission_handler_android","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/permission_handler_android-12.0.1/","native_build":true,"dependencies":[]}],"macos":[{"name":"firebase_core","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/firebase_core-2.24.0/","native_build":true,"dependencies":[]},{"name":"firebase_messaging","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/firebase_messaging-14.7.6/","native_build":true,"dependencies":["firebase_core"]},{"name":"flutter_local_notifications","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/flutter_local_notifications-16.2.0/","native_build":true,"dependencies":[]},{"name":"flutter_webrtc","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/flutter_webrtc-0.9.47/","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/path_provider_foundation-2.3.1/","shared_darwin_source":true,"native_build":true,"dependencies":[]}],"linux":[{"name":"flutter_webrtc","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/flutter_webrtc-0.9.47/","native_build":true,"dependencies":[]},{"name":"path_provider_linux","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[]}],"windows":[{"name":"firebase_core","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/firebase_core-2.24.0/","native_build":true,"dependencies":[]},{"name":"flutter_webrtc","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/flutter_webrtc-0.9.47/","native_build":true,"dependencies":[]},{"name":"path_provider_windows","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/path_provider_windows-2.2.1/","native_build":false,"dependencies":[]},{"name":"permission_handler_windows","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/permission_handler_windows-0.2.0/","native_build":true,"dependencies":[]}],"web":[{"name":"firebase_core_web","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/firebase_core_web-2.9.0/","dependencies":[]},{"name":"firebase_messaging_web","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/firebase_messaging_web-3.5.15/","dependencies":["firebase_core_web"]},{"name":"fluttertoast","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/fluttertoast-8.2.4/","dependencies":[]},{"name":"permission_handler_html","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/permission_handler_html-0.1.0+1/","dependencies":[]}]},"dependencyGraph":[{"name":"firebase_core","dependencies":["firebase_core_web"]},{"name":"firebase_core_web","dependencies":[]},{"name":"firebase_messaging","dependencies":["firebase_core","firebase_messaging_web"]},{"name":"firebase_messaging_web","dependencies":["firebase_core","firebase_core_web"]},{"name":"flutter_callkit_incoming","dependencies":[]},{"name":"flutter_local_notifications","dependencies":[]},{"name":"flutter_webrtc","dependencies":["path_provider"]},{"name":"fluttertoast","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"permission_handler","dependencies":["permission_handler_android","permission_handler_apple","permission_handler_html","permission_handler_windows"]},{"name":"permission_handler_android","dependencies":[]},{"name":"permission_handler_apple","dependencies":[]},{"name":"permission_handler_html","dependencies":[]},{"name":"permission_handler_windows","dependencies":[]}],"date_created":"2024-02-07 11:10:31.535547","version":"3.16.1"} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"audioplayers_darwin","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/audioplayers_darwin-5.0.2/","native_build":true,"dependencies":[]},{"name":"firebase_core","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/firebase_core-2.25.4/","native_build":true,"dependencies":[]},{"name":"firebase_messaging","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/firebase_messaging-14.7.16/","native_build":true,"dependencies":["firebase_core"]},{"name":"flutter_callkit_incoming","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/flutter_callkit_incoming-2.0.1+2/","native_build":true,"dependencies":[]},{"name":"flutter_local_notifications","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/flutter_local_notifications-16.3.2/","native_build":true,"dependencies":[]},{"name":"flutter_webrtc","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/flutter_webrtc-0.9.48+hotfix.1/","native_build":true,"dependencies":[]},{"name":"fluttertoast","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/fluttertoast-8.2.4/","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/path_provider_foundation-2.3.2/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"permission_handler_apple","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.0/","native_build":true,"dependencies":[]}],"android":[{"name":"audioplayers_android","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/audioplayers_android-4.0.3/","native_build":true,"dependencies":[]},{"name":"firebase_core","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/firebase_core-2.25.4/","native_build":true,"dependencies":[]},{"name":"firebase_messaging","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/firebase_messaging-14.7.16/","native_build":true,"dependencies":["firebase_core"]},{"name":"flutter_callkit_incoming","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/flutter_callkit_incoming-2.0.1+2/","native_build":true,"dependencies":[]},{"name":"flutter_local_notifications","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/flutter_local_notifications-16.3.2/","native_build":true,"dependencies":[]},{"name":"flutter_webrtc","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/flutter_webrtc-0.9.48+hotfix.1/","native_build":true,"dependencies":[]},{"name":"fluttertoast","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/fluttertoast-8.2.4/","native_build":true,"dependencies":[]},{"name":"path_provider_android","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/path_provider_android-2.2.2/","native_build":true,"dependencies":[]},{"name":"permission_handler_android","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/permission_handler_android-12.0.5/","native_build":true,"dependencies":[]}],"macos":[{"name":"audioplayers_darwin","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/audioplayers_darwin-5.0.2/","native_build":true,"dependencies":[]},{"name":"firebase_core","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/firebase_core-2.25.4/","native_build":true,"dependencies":[]},{"name":"firebase_messaging","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/firebase_messaging-14.7.16/","native_build":true,"dependencies":["firebase_core"]},{"name":"flutter_local_notifications","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/flutter_local_notifications-16.3.2/","native_build":true,"dependencies":[]},{"name":"flutter_webrtc","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/flutter_webrtc-0.9.48+hotfix.1/","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/path_provider_foundation-2.3.2/","shared_darwin_source":true,"native_build":true,"dependencies":[]}],"linux":[{"name":"audioplayers_linux","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/audioplayers_linux-3.1.0/","native_build":true,"dependencies":[]},{"name":"flutter_webrtc","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/flutter_webrtc-0.9.48+hotfix.1/","native_build":true,"dependencies":[]},{"name":"path_provider_linux","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[]}],"windows":[{"name":"audioplayers_windows","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/audioplayers_windows-3.1.0/","native_build":true,"dependencies":[]},{"name":"firebase_core","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/firebase_core-2.25.4/","native_build":true,"dependencies":[]},{"name":"flutter_webrtc","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/flutter_webrtc-0.9.48+hotfix.1/","native_build":true,"dependencies":[]},{"name":"path_provider_windows","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/path_provider_windows-2.2.1/","native_build":false,"dependencies":[]},{"name":"permission_handler_windows","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/permission_handler_windows-0.2.1/","native_build":true,"dependencies":[]}],"web":[{"name":"audioplayers_web","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/audioplayers_web-4.1.0/","dependencies":[]},{"name":"firebase_core_web","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/firebase_core_web-2.11.4/","dependencies":[]},{"name":"firebase_messaging_web","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/firebase_messaging_web-3.6.5/","dependencies":["firebase_core_web"]},{"name":"fluttertoast","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/fluttertoast-8.2.4/","dependencies":[]},{"name":"permission_handler_html","path":"/Users/isaacakakpo/.pub-cache/hosted/pub.dev/permission_handler_html-0.1.1/","dependencies":[]}]},"dependencyGraph":[{"name":"audioplayers","dependencies":["audioplayers_android","audioplayers_darwin","audioplayers_linux","audioplayers_web","audioplayers_windows","path_provider"]},{"name":"audioplayers_android","dependencies":[]},{"name":"audioplayers_darwin","dependencies":[]},{"name":"audioplayers_linux","dependencies":[]},{"name":"audioplayers_web","dependencies":[]},{"name":"audioplayers_windows","dependencies":[]},{"name":"firebase_core","dependencies":["firebase_core_web"]},{"name":"firebase_core_web","dependencies":[]},{"name":"firebase_messaging","dependencies":["firebase_core","firebase_messaging_web"]},{"name":"firebase_messaging_web","dependencies":["firebase_core","firebase_core_web"]},{"name":"flutter_callkit_incoming","dependencies":[]},{"name":"flutter_local_notifications","dependencies":[]},{"name":"flutter_webrtc","dependencies":["path_provider"]},{"name":"fluttertoast","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"permission_handler","dependencies":["permission_handler_android","permission_handler_apple","permission_handler_html","permission_handler_windows"]},{"name":"permission_handler_android","dependencies":[]},{"name":"permission_handler_apple","dependencies":[]},{"name":"permission_handler_html","dependencies":[]},{"name":"permission_handler_windows","dependencies":[]}],"date_created":"2024-02-26 11:31:22.763612","version":"3.16.1"} \ No newline at end of file diff --git a/.idea/libraries/Flutter_Plugins.xml b/.idea/libraries/Flutter_Plugins.xml index 5d8f7a3..4f46563 100644 --- a/.idea/libraries/Flutter_Plugins.xml +++ b/.idea/libraries/Flutter_Plugins.xml @@ -2,20 +2,22 @@ - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/lib/view/screen/login_screen.dart b/lib/view/screen/login_screen.dart index e6b85c6..bd43c7e 100644 --- a/lib/view/screen/login_screen.dart +++ b/lib/view/screen/login_screen.dart @@ -34,6 +34,7 @@ class _LoginScreenState extends State { Future _checkPermissions() async { Map statuses = await [ + Permission.audio, Permission.microphone, Permission.bluetooth, Permission.bluetoothConnect @@ -58,7 +59,9 @@ class _LoginScreenState extends State { sipNameController.text, sipNumberController.text, token, - true); + true, + "", + ""); Provider.of(context, listen: false) .login(credentialConfig); }); @@ -142,12 +145,13 @@ class _LoginScreenState extends State { ); } - void _showToast(BuildContext context,String text) { + void _showToast(BuildContext context, String text) { final scaffold = ScaffoldMessenger.of(context); scaffold.showSnackBar( SnackBar( - content: Text('$text'), - action: SnackBarAction(label: 'OKAY', onPressed: scaffold.hideCurrentSnackBar), + content: Text('$text'), + action: SnackBarAction( + label: 'OKAY', onPressed: scaffold.hideCurrentSnackBar), ), ); } diff --git a/lib/view/widgets/dialpad_widget.dart b/lib/view/widgets/dialpad_widget.dart index d314a2f..41377c4 100644 --- a/lib/view/widgets/dialpad_widget.dart +++ b/lib/view/widgets/dialpad_widget.dart @@ -264,7 +264,8 @@ class _DialButtonState extends State child: Center( child: widget.icon == null ? widget.subtitle != null - ? Column( + ? SingleChildScrollView( + child: Column( mainAxisSize: MainAxisSize.min, children: [ const SizedBox( @@ -282,7 +283,7 @@ class _DialButtonState extends State color: widget.textColor ?? Colors.black)) ], - ) + )) : Padding( padding: EdgeInsets.only( top: widget.title == "*" ? 10 : 0), diff --git a/packages/telnyx_webrtc/lib/call.dart b/packages/telnyx_webrtc/lib/call.dart index 8a20ae0..a9578e1 100644 --- a/packages/telnyx_webrtc/lib/call.dart +++ b/packages/telnyx_webrtc/lib/call.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:flutter/foundation.dart'; import 'package:telnyx_webrtc/model/jsonrpc.dart'; import '/model/socket_method.dart'; @@ -8,18 +9,22 @@ import '/model/verto/send/send_bye_message_body.dart'; import '/model/verto/send/info_dtmf_message_body.dart'; import '/model/verto/send/invite_answer_message_body.dart'; import '/model/verto/send/modify_message_body.dart'; -import '/peer/peer.dart'; +import '/peer/peer.dart' if (dart.library.html) '/web/peer.dart'; import 'package:telnyx_webrtc/tx_socket.dart' if (dart.library.js) 'package:telnyx_webrtc/tx_socket_web.dart'; import 'package:uuid/uuid.dart'; +import 'package:audioplayers/audioplayers.dart'; /// The Call class which is used for call related methods such as hold/mute or /// creating invitations, declining calls, etc. class Call { - Call(this._txSocket, this._sessid); + Call(this._txSocket, this._sessid, this.ringToneFile, this.ringBackFile); + final audioService = AudioService(); final TxSocket _txSocket; final String _sessid; + final String ringBackFile; + final String ringToneFile; late String? callId; Peer? peerConnection; @@ -28,11 +33,13 @@ class Call { String sessionCallerNumber = ""; String sessionDestinationNumber = ""; String sessionClientState = ""; - Map customHeaders = {}; + Map customHeaders = {}; + /// Creates an invitation to send to a [destinationNumber] or SIP Destination /// using the provided [callerName], [callerNumber] and a [clientState] void newInvite(String callerName, String callerNumber, - String destinationNumber, String clientState,{Map customHeaders = const {}}) { + String destinationNumber, String clientState, + {Map customHeaders = const {}}) { sessionCallerName = callerName; sessionCallerNumber = callerNumber; sessionDestinationNumber = destinationNumber; @@ -43,7 +50,9 @@ class Call { peerConnection = Peer(_txSocket); peerConnection?.invite(callerName, callerNumber, destinationNumber, - base64State, callId!, _sessid,customHeaders); + base64State, callId!, _sessid, customHeaders); + //play ringback + playAudio(ringBackFile); } void onRemoteSessionReceived(String? sdp) { @@ -57,7 +66,8 @@ class Call { /// Accepts the incoming call specified via the [invite] parameter, sending /// your local specified [callerName], [callerNumber] and [clientState] void acceptCall(IncomingInviteParams invite, String callerName, - String callerNumber, String clientState,{Map customHeaders = const {}}) { + String callerNumber, String clientState, + {Map customHeaders = const {}}) { callId = invite.callID; sessionCallerName = callerName; @@ -69,7 +79,9 @@ class Call { peerConnection = Peer(_txSocket); peerConnection?.accept(callerName, callerNumber, destinationNum!, - clientState, callId!, invite,customHeaders); + clientState, callId!, invite, customHeaders); + + stopAudio(); } /// Attempts to end the call identified via the [callID] @@ -94,6 +106,7 @@ class Call { if (peerConnection != null) { peerConnection?.closeSession(_sessid); } + stopAudio(); } /// Sends a DTMF message with the chosen [tone] to the call @@ -176,4 +189,34 @@ class Call { String jsonModifyMessage = jsonEncode(modifyMessage); _txSocket.send(jsonModifyMessage); } + + // Example file path for 'web/assets/audio/sound.wav' + void playAudio(String filePath) { + if (kIsWeb && filePath.isNotEmpty) { + audioService.playLocalFile(filePath); + } + } + + void stopAudio() { + if (kIsWeb) { + audioService.stopAudio(); + } + } +} + +class AudioService { + final AudioPlayer _audioPlayer = AudioPlayer(); + + Future playLocalFile(String filePath) async { + // Ensure the file path is correct and accessible from the web directory + await _audioPlayer.play(DeviceFileSource(filePath)); + } + + + + Future stopAudio() async { + // Ensure the file path is correct and accessible from the web directory + _audioPlayer.stop(); + await _audioPlayer.release(); + } } diff --git a/packages/telnyx_webrtc/lib/config/telnyx_config.dart b/packages/telnyx_webrtc/lib/config/telnyx_config.dart index dd4d448..3e66ef8 100644 --- a/packages/telnyx_webrtc/lib/config/telnyx_config.dart +++ b/packages/telnyx_webrtc/lib/config/telnyx_config.dart @@ -7,7 +7,8 @@ /// legitimate credentials class CredentialConfig { CredentialConfig(this.sipUser, this.sipPassword, this.sipCallerIDName, - this.sipCallerIDNumber, this.notificationToken, this.autoReconnect); + this.sipCallerIDNumber, this.notificationToken, this.autoReconnect, + [this.ringTonePath, this.ringbackPath]); final String sipUser; final String sipPassword; @@ -15,6 +16,8 @@ class CredentialConfig { final String sipCallerIDNumber; final String? notificationToken; final bool? autoReconnect; + final String? ringTonePath; + final String? ringbackPath; } /// Creates an instance of TokenConfig which can be used to log in @@ -26,11 +29,14 @@ class CredentialConfig { /// a legitimate token class TokenConfig { TokenConfig(this.sipToken, this.sipCallerIDName, this.sipCallerIDNumber, - this.notificationToken, this.autoReconnect); + this.notificationToken, this.autoReconnect, + [this.ringTonePath, this.ringbackPath]); final String sipToken; final String sipCallerIDName; final String sipCallerIDNumber; final String? notificationToken; final bool? autoReconnect; + final String? ringTonePath; + final String? ringbackPath; } diff --git a/packages/telnyx_webrtc/lib/peer/peer.dart b/packages/telnyx_webrtc/lib/peer/peer.dart index 6d8123d..01fdfa7 100644 --- a/packages/telnyx_webrtc/lib/peer/peer.dart +++ b/packages/telnyx_webrtc/lib/peer/peer.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'dart:async'; import 'dart:math'; +import 'package:flutter/foundation.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart'; import 'package:telnyx_webrtc/config.dart'; import 'package:telnyx_webrtc/model/socket_method.dart'; diff --git a/packages/telnyx_webrtc/lib/telnyx_client.dart b/packages/telnyx_webrtc/lib/telnyx_client.dart index a522608..eb52c95 100644 --- a/packages/telnyx_webrtc/lib/telnyx_client.dart +++ b/packages/telnyx_webrtc/lib/telnyx_client.dart @@ -28,6 +28,10 @@ typedef OnSocketErrorReceived = void Function(TelnyxSocketError message); class TelnyxClient { late OnSocketMessageReceived onSocketMessageReceived; late OnSocketErrorReceived onSocketErrorReceived; + String ringtonePath = ""; + String ringBackpath = ""; + + TelnyxClient({this.ringtonePath = '', this.ringBackpath = ''}); TxSocket txSocket = TxSocket("wss://rtc.telnyx.com:443"); bool _closed = false; @@ -124,7 +128,7 @@ class TelnyxClient { /// yourself on hold/mute. Call createCall() { // Set global call parameter - call = Call(txSocket, sessid); + call = Call(txSocket, sessid, ringtonePath, ringBackpath); return call; } @@ -138,6 +142,8 @@ class TelnyxClient { var user = config.sipUser; var password = config.sipPassword; var fcmToken = config.notificationToken; + ringBackpath = config.ringbackPath ?? ""; + ringtonePath = config.ringTonePath ?? ""; UserVariables? notificationParams; _autoReconnectLogin = config.autoReconnect ?? true; @@ -174,6 +180,8 @@ class TelnyxClient { var uuid = const Uuid().v4(); var token = config.sipToken; var fcmToken = config.notificationToken; + ringBackpath = config.ringbackPath ?? ""; + ringtonePath = config.ringTonePath ?? ""; UserVariables? notificationParams; _autoReconnectLogin = config.autoReconnect ?? true; @@ -186,7 +194,10 @@ class TelnyxClient { } var loginParams = LoginParams( - loginToken: token, loginParams: [], userVariables: notificationParams, sessionId: sessid); + loginToken: token, + loginParams: [], + userVariables: notificationParams, + sessionId: sessid); var loginMessage = LoginMessage( id: uuid, method: SocketMethod.LOGIN, @@ -234,13 +245,13 @@ class TelnyxClient { if (data.toString().trim().isNotEmpty) { _logger.i('Received WebSocket message :: ${data.toString().trim()}'); - if(data.toString().trim().contains("error")){ + if (data.toString().trim().contains("error")) { var errorJson = jsonEncode(data.toString()); _logger .i('Received WebSocket message - Contains Error :: $errorJson'); try { ReceivedResult errorResult = - ReceivedResult.fromJson(jsonDecode(data.toString())); + ReceivedResult.fromJson(jsonDecode(data.toString())); onSocketErrorReceived.call(errorResult.error!); } on Exception catch (e) { _logger.e('Error parsing JSON: $e'); @@ -255,9 +266,12 @@ class TelnyxClient { try { ReceivedResult stateMessage = - ReceivedResult.fromJson(jsonDecode(data.toString())); + ReceivedResult.fromJson(jsonDecode(data.toString())); - var mainMessage = ReceivedMessage(jsonrpc: stateMessage.jsonrpc, method: SocketMethod.GATEWAY_STATE, stateParams: stateMessage.resultParams?.stateParams); + var mainMessage = ReceivedMessage( + jsonrpc: stateMessage.jsonrpc, + method: SocketMethod.GATEWAY_STATE, + stateParams: stateMessage.resultParams?.stateParams); if (stateMessage.resultParams != null) { switch (stateMessage.resultParams?.stateParams?.state) { @@ -314,15 +328,15 @@ class TelnyxClient { { _invalidateGatewayResponseTimer(); _resetGatewayCounters(); - _logger.i('GATEWAY REGISTRATION FAILED :: Unknown State'); + _logger.i( + 'GATEWAY REGISTRATION :: Unknown State ${stateMessage.toString()}'); } } } } on Exception catch (e) { _logger.e('Error parsing JSON: $e'); } - } - else if (data.toString().trim().contains("method")) { + } else if (data.toString().trim().contains("method")) { //Received Telnyx Method Message var messageJson = jsonDecode(data.toString()); _logger.i( @@ -381,6 +395,10 @@ class TelnyxClient { ReceivedMessage.fromJson(jsonDecode(data.toString())); var message = TelnyxMessage( socketMethod: SocketMethod.INVITE, message: invite); + + //create Call instance + createCall(); + call.playAudio(ringtonePath); onSocketMessageReceived.call(message); break; } @@ -415,6 +433,7 @@ class TelnyxClient { call.endCall(inviteAnswer.inviteParams?.callID); } earlySDP = false; + call.stopAudio(); break; } case SocketMethod.BYE: @@ -425,6 +444,7 @@ class TelnyxClient { var message = TelnyxMessage(socketMethod: SocketMethod.BYE, message: bye); onSocketMessageReceived(message); + call.stopAudio(); break; } case SocketMethod.RINGING: @@ -440,8 +460,7 @@ class TelnyxClient { break; } } - } - else { + } else { _logger.i('Received and ignored empty packet'); } } else { diff --git a/packages/telnyx_webrtc/lib/web/peer.dart b/packages/telnyx_webrtc/lib/web/peer.dart new file mode 100644 index 0000000..c7300f0 --- /dev/null +++ b/packages/telnyx_webrtc/lib/web/peer.dart @@ -0,0 +1,475 @@ +import 'dart:convert'; +import 'dart:async'; +import 'dart:math'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_webrtc/flutter_webrtc.dart'; +import 'package:telnyx_webrtc/config.dart'; +import 'package:telnyx_webrtc/model/socket_method.dart'; +import 'package:telnyx_webrtc/model/verto/send/invite_answer_message_body.dart'; +import 'package:telnyx_webrtc/tx_socket.dart' + if (dart.library.js) 'package:telnyx_webrtc/tx_socket_web.dart'; +import 'package:uuid/uuid.dart'; +import 'package:logger/logger.dart'; + +import 'package:telnyx_webrtc/model/verto/receive/received_message_body.dart'; + +import '../model/jsonrpc.dart'; + +enum SignalingState { + ConnectionOpen, + ConnectionClosed, + ConnectionError, +} + +enum CallState { + CallStateNew, + CallStateRinging, + CallStateInvite, + CallStateConnected, + CallStateBye, +} + +class Session { + Session({required this.sid, required this.pid}); + + String pid; + String sid; + RTCPeerConnection? peerConnection; + RTCDataChannel? dc; + List remoteCandidates = []; +} + +class Peer { + Peer(this._socket); + + final _logger = Logger(); + + final String _selfId = randomNumeric(6); + + final TxSocket _socket; + final Map _sessions = {}; + MediaStream? _localStream; + final List _remoteStreams = []; + + final RTCVideoRenderer _localRenderer = RTCVideoRenderer(); + final RTCVideoRenderer _remoteRenderer = RTCVideoRenderer(); + + Function(SignalingState state)? onSignalingStateChange; + Function(Session session, CallState state)? onCallStateChange; + Function(MediaStream stream)? onLocalStream; + Function(Session session, MediaStream stream)? onAddRemoteStream; + Function(Session session, MediaStream stream)? onRemoveRemoteStream; + Function(dynamic event)? onPeersUpdate; + Function(Session session, RTCDataChannel dc, RTCDataChannelMessage data)? + onDataChannelMessage; + Function(Session session, RTCDataChannel dc)? onDataChannel; + + String get sdpSemantics => + WebRTC.platformIsWindows ? 'plan-b' : 'unified-plan'; + + final Map _iceServers = { + 'iceServers': [ + { + 'url': DefaultConfig.defaultStun, + 'username': DefaultConfig.username, + 'credential': DefaultConfig.password + }, + { + 'url': DefaultConfig.defaultTurn, + 'username': DefaultConfig.username, + 'credential': DefaultConfig.password + }, + ] + }; + + final Map _dcConstraints = { + 'mandatory': { + 'OfferToReceiveAudio': true, + 'OfferToReceiveVideo': false, + }, + 'optional': [ + {'DtlsSrtpKeyAgreement': true}, + ], + }; + + close() async { + await _cleanSessions(); + _socket.close(); + } + + void muteUnmuteMic() { + if (_localStream != null) { + bool enabled = _localStream!.getAudioTracks()[0].enabled; + _localStream!.getAudioTracks()[0].enabled = !enabled; + } else { + _logger.d("Peer :: No local stream :: Unable to Mute / Unmute"); + } + } + + + void enableSpeakerPhone(bool enable) { + if (kIsWeb) { + _logger.d("Peer :: Speaker Enabled :: $enable"); + _localStream!.getAudioTracks().first.enableSpeakerphone(enable); + return; + } + if (_localStream != null) { + _localStream!.getAudioTracks()[0].enableSpeakerphone(enable); + _logger.d("Peer :: Speaker Enabled :: $enable"); + } else { + _logger.d("Peer :: No local stream :: Unable to toggle speaker mode"); + } + } + + void invite( + String callerName, + String callerNumber, + String destinationNumber, + String clientState, + String callId, + String telnyxSessionId, + Map customHeaders) async { + var sessionId = _selfId; + + Session session = await _createSession(null, + peerId: "0", sessionId: sessionId, media: "audio"); + + _sessions[sessionId] = session; + + _createOffer(session, "audio", callerName, callerNumber, destinationNumber, + clientState, callId, telnyxSessionId, customHeaders); + onCallStateChange?.call(session, CallState.CallStateInvite); + } + + Future _createOffer( + Session session, + String media, + String callerName, + String callerNumber, + String destinationNumber, + String clientState, + String callId, + String sessionId, + Map customHeaders) async { + try { + RTCSessionDescription s = + await session.peerConnection!.createOffer(_dcConstraints); + await session.peerConnection!.setLocalDescription(s); + + if (session.remoteCandidates.isNotEmpty) { + for (var candidate in session.remoteCandidates) { + if (candidate.candidate != null) { + _logger.i("adding $candidate"); + await session.peerConnection?.addCandidate(candidate); + } + } + session.remoteCandidates.clear(); + } + + await Future.delayed(const Duration(milliseconds: 500)); + + String? sdpUsed = ""; + session.peerConnection + ?.getLocalDescription() + .then((value) => sdpUsed = value?.sdp.toString()); + + Timer(const Duration(milliseconds: 500), () { + var dialogParams = DialogParams( + attach: false, + audio: true, + callID: callId, + callerIdName: callerName, + callerIdNumber: callerNumber, + clientState: clientState, + destinationNumber: destinationNumber, + remoteCallerIdName: "", + screenShare: false, + useStereo: false, + userVariables: [], + video: false, + customHeaders: customHeaders); + var inviteParams = InviteParams( + dialogParams: dialogParams, + sdp: sdpUsed, + sessid: sessionId, + userAgent: "Flutter-1.0"); + var inviteMessage = InviteAnswerMessage( + id: const Uuid().v4(), + jsonrpc: JsonRPCConstant.jsonrpc, + method: SocketMethod.INVITE, + params: inviteParams); + + String jsonInviteMessage = jsonEncode(inviteMessage); + + _send(jsonInviteMessage); + }); + } catch (e) { + _logger.e("Peer :: $e"); + } + } + + void remoteSessionReceived(String sdp) async { + await _sessions[_selfId] + ?.peerConnection + ?.setRemoteDescription(RTCSessionDescription(sdp, "answer")); + } + + void accept( + String callerName, + String callerNumber, + String destinationNumber, + String clientState, + String callId, + IncomingInviteParams invite, + Map customHeaders) async { + var sessionId = _selfId; + Session session = await _createSession(null, + peerId: "0", sessionId: sessionId, media: "audio"); + _sessions[sessionId] = session; + + await session.peerConnection + ?.setRemoteDescription(RTCSessionDescription(invite.sdp, "offer")); + + _createAnswer(session, "audio", callerName, callerNumber, destinationNumber, + clientState, callId, customHeaders); + + onCallStateChange?.call(session, CallState.CallStateNew); + } + + Future _createAnswer( + Session session, + String media, + String callerName, + String callerNumber, + String destinationNumber, + String clientState, + String callId, + Map customHeaders) async { + try { + session.peerConnection?.onIceCandidate = (candidate) async { + if (session.peerConnection != null) { + _logger.i("Peer :: Add Ice Candidate!"); + if (candidate.candidate != null) { + await session.peerConnection?.addCandidate(candidate); + } + } else { + session.remoteCandidates.add(candidate); + } + }; + + RTCSessionDescription s = + await session.peerConnection!.createAnswer(_dcConstraints); + await session.peerConnection!.setLocalDescription(s); + + await Future.delayed(const Duration(milliseconds: 500)); + + String? sdpUsed = ""; + session.peerConnection + ?.getLocalDescription() + .then((value) => sdpUsed = value?.sdp.toString()); + + Timer(const Duration(milliseconds: 500), () { + var dialogParams = DialogParams( + attach: false, + audio: true, + callID: callId, + callerIdName: callerNumber, + callerIdNumber: callerNumber, + clientState: clientState, + destinationNumber: destinationNumber, + remoteCallerIdName: "", + screenShare: false, + useStereo: false, + userVariables: [], + video: false, + customHeaders: customHeaders); + var inviteParams = InviteParams( + dialogParams: dialogParams, + sdp: sdpUsed, + sessid: session.sid, + userAgent: "Flutter-1.0"); + var answerMessage = InviteAnswerMessage( + id: const Uuid().v4(), + jsonrpc: JsonRPCConstant.jsonrpc, + method: SocketMethod.ANSWER, + params: inviteParams); + + String jsonAnswerMessage = jsonEncode(answerMessage); + _send(jsonAnswerMessage); + }); + } catch (e) { + _logger.e("Peer :: $e"); + } + } + + void closeSession(String sessionId) { + var sess = _sessions[sessionId]; + if (sess != null) { + _closeSession(sess); + } + } + + Future createStream(String media) async { + _logger.i("Peer :: Creating stream"); + final Map mediaConstraints = { + 'audio': true, + 'video': false + }; + + MediaStream stream = + await navigator.mediaDevices.getUserMedia(mediaConstraints); + onLocalStream?.call(stream); + return stream; + } + + Future initRenderers() async { + await _localRenderer.initialize(); + await _remoteRenderer.initialize(); + } + + Future _createSession(Session? session, + {required String peerId, + required String sessionId, + required String media}) async { + _logger.i('Web is running'); + + var newSession = session ?? Session(sid: sessionId, pid: peerId); + if (media != 'data') _localStream = await createStream(media); + _localRenderer.srcObject = _localStream; + initRenderers(); + RTCPeerConnection peerConnection = await createPeerConnection({ + ..._iceServers, + ...{'sdpSemantics': sdpSemantics} + }); + peerConnection.onTrack = (event) { + if (event.track.kind == 'video') { + _remoteRenderer.srcObject = event.streams[0]; + } else if (event.track.kind == 'audio') { + _logger.i("Peer :: onTrack: audio"); + _remoteRenderer.srcObject = event.streams[0]; + } + }; + + _localStream?.getTracks().forEach((track) async { + await peerConnection.addTrack(track, _localStream!); + _logger.i('track.settings ${track.getSettings()}'); + }); + + peerConnection.onIceCandidate = (candidate) async { + if (!candidate.candidate.toString().contains("127.0.0.1")) { + _logger.i("Peer :: Adding ICE candidate :: ${candidate.toString()}"); + peerConnection.addCandidate(candidate); + } else { + _logger.i("Peer :: Local candidate skipped!"); + } + if (candidate.candidate == null) { + _logger.i("Peer :: onIceCandidate: complete!"); + return; + } + }; + + peerConnection.onIceConnectionState = (state) { + _logger.i("Peer :: ICE Connection State change :: $state"); + switch (state) { + case RTCIceConnectionState.RTCIceConnectionStateFailed: + peerConnection.restartIce(); + return; + default: + return; + } + }; + + peerConnection.onRemoveStream = (stream) { + onRemoveRemoteStream?.call(newSession, stream); + _remoteStreams.removeWhere((it) { + return (it.id == stream.id); + }); + }; + + onAddRemoteStream = (newSession, stream) { + _remoteStreams.add(stream); + _logger.i("Peer :: Remote stream added"); + }; + + peerConnection.onDataChannel = (channel) { + _addDataChannel(newSession, channel); + }; + + newSession.peerConnection = peerConnection; + return newSession; + } + + void _addDataChannel(Session session, RTCDataChannel channel) { + channel.onDataChannelState = (e) {}; + channel.onMessage = (RTCDataChannelMessage data) { + onDataChannelMessage?.call(session, channel, data); + }; + session.dc = channel; + onDataChannel?.call(session, channel); + } + + /*Future _createDataChannel(Session session, + {label = 'fileTransfer'}) async { + RTCDataChannelInit dataChannelDict = RTCDataChannelInit() + ..maxRetransmits = 30; + RTCDataChannel channel = + await session.peerConnection!.createDataChannel(label, dataChannelDict); + _addDataChannel(session, channel); + }*/ + + _send(event) { + _socket.send(event); + } + + Future _cleanSessions() async { + if (_localStream != null) { + _localStream!.getTracks().forEach((element) async { + await element.stop(); + }); + await _localStream!.dispose(); + _localStream = null; + } + _sessions.forEach((key, sess) async { + await sess.peerConnection?.close(); + await sess.dc?.close(); + }); + _sessions.clear(); + } + + /*void _closeSessionByPeerId(String peerId) { + Session? session; + _sessions.removeWhere((String key, Session sess) { + var ids = key.split('-'); + session = sess; + return peerId == ids[0] || peerId == ids[1]; + }); + if (session != null) { + _closeSession(session!); + onCallStateChange?.call(session!, CallState.CallStateBye); + } + }*/ + + Future _closeSession(Session session) async { + _localStream?.getTracks().forEach((element) async { + await element.stop(); + }); + await _localStream?.dispose(); + _localStream = null; + + await session.peerConnection?.close(); + await session.dc?.close(); + } +} + +int randomBetween(int from, int to) { + if (from > to) throw Exception('$from cannot be > $to'); + var rand = Random(); + return ((to - from) * rand.nextDouble()).toInt() + from; +} + +String randomString(int length, {int from = 33, int to = 126}) { + return String.fromCharCodes( + List.generate(length, (index) => randomBetween(from, to))); +} + +String randomNumeric(int length) => randomString(length, from: 48, to: 57); diff --git a/packages/telnyx_webrtc/pubspec.yaml b/packages/telnyx_webrtc/pubspec.yaml index 5474350..ea7aca5 100644 --- a/packages/telnyx_webrtc/pubspec.yaml +++ b/packages/telnyx_webrtc/pubspec.yaml @@ -13,11 +13,12 @@ dependencies: flutter: sdk: flutter flutter_lints: ^2.0.1 - flutter_webrtc: ^0.9.5 + flutter_webrtc: ^0.9.48+hotfix.1 logger: ^1.1.0 uuid: ^3.0.6 mockito: ^5.2.0 build_runner: ^2.2.0 + audioplayers: ^5.2.1 dev_dependencies: flutter_test: