From 08f4c376709be0370663278888b055bef1582d88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?th=E1=BB=8Bnh?= Date: Fri, 27 Sep 2024 03:53:43 +0700 Subject: [PATCH] Clean up socket service --- app/lib/pages/capture/_page.dart | 18 - app/lib/pages/capture/location_service.dart | 36 -- .../logic/_mic_background_service.dart | 69 ---- app/lib/providers/websocket_provider.dart | 326 ------------------ app/lib/utils/websockets.dart | 124 ------- 5 files changed, 573 deletions(-) delete mode 100644 app/lib/pages/capture/_page.dart delete mode 100644 app/lib/pages/capture/location_service.dart delete mode 100644 app/lib/pages/capture/logic/_mic_background_service.dart delete mode 100644 app/lib/providers/websocket_provider.dart delete mode 100644 app/lib/utils/websockets.dart diff --git a/app/lib/pages/capture/_page.dart b/app/lib/pages/capture/_page.dart deleted file mode 100644 index 75d10b227..000000000 --- a/app/lib/pages/capture/_page.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:flutter/material.dart'; - -@Deprecated("Capture page is deprecated, use @pages > memories > widgets > capture instead.") -class CapturePage extends StatefulWidget { - const CapturePage({ - super.key, - }); - - @override - State createState() => CapturePageState(); -} - -class CapturePageState extends State { - @override - Widget build(BuildContext context) { - return const Text("Depreacted"); - } -} diff --git a/app/lib/pages/capture/location_service.dart b/app/lib/pages/capture/location_service.dart deleted file mode 100644 index f5f9fbc54..000000000 --- a/app/lib/pages/capture/location_service.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'dart:async'; -import 'package:friend_private/backend/preferences.dart'; -import 'package:geolocator/geolocator.dart'; -import 'package:location/location.dart'; - -class LocationService { - Location location = Location(); - LocationData? locationData; - Future enableService() async { - bool serviceEnabled = await location.serviceEnabled(); - if (serviceEnabled) { - return true; - } else { - return await location.requestService(); - } - } - - Future displayPermissionsDialog() async { - if (SharedPreferencesUtil().locationPermissionRequested) return false; - if (await isServiceEnabled()) { - SharedPreferencesUtil().locationPermissionRequested = true; - } - var status = await permissionStatus(); - return await isServiceEnabled() == false || (status != LocationPermission.always); - } - - Future isServiceEnabled() => location.serviceEnabled(); - - Future permissionStatus() => Geolocator.checkPermission(); - - Future hasPermission() async => (await Geolocator.checkPermission()) == LocationPermission.always; - - Future getDeviceLocation() async { - locationData = await location.getLocation(); - } -} diff --git a/app/lib/pages/capture/logic/_mic_background_service.dart b/app/lib/pages/capture/logic/_mic_background_service.dart deleted file mode 100644 index 421b76046..000000000 --- a/app/lib/pages/capture/logic/_mic_background_service.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'dart:async'; -import 'dart:typed_data'; - -import 'package:flutter/widgets.dart'; -import 'package:flutter_background_service/flutter_background_service.dart'; -import 'package:flutter_sound/flutter_sound.dart'; - -void startBackgroundService() { - final service = FlutterBackgroundService(); - service.startService(); -} - -void stopBackgroundService() { - final service = FlutterBackgroundService(); - service.invoke("stop"); -} - -Future initializeMicBackgroundService() async { - final service = FlutterBackgroundService(); - - await service.configure( - iosConfiguration: IosConfiguration( - autoStart: false, - onForeground: onStart, - onBackground: onIosBackground, - ), - androidConfiguration: AndroidConfiguration( - autoStart: true, - onStart: onStart, - isForegroundMode: true, - autoStartOnBoot: true, - foregroundServiceType: AndroidForegroundType.microphone, - ), - ); -} - -@pragma('vm:entry-point') -Future onIosBackground(ServiceInstance service) async { - WidgetsFlutterBinding.ensureInitialized(); - - return true; -} - -@pragma('vm:entry-point') -Future onStart(ServiceInstance service) async { - FlutterSoundRecorder? _mRecorder = FlutterSoundRecorder(); - await _mRecorder.openRecorder(isBGService: true); - var recordingDataController = StreamController(); - - await _mRecorder.startRecorder( - toStream: recordingDataController.sink, - codec: Codec.pcm16, - numChannels: 1, - sampleRate: 16000, - bufferSize: 8192, - ); - service.invoke("stateUpdate", {"state": 'recording'}); - recordingDataController.stream.listen((buffer) { - Uint8List audioBytes = buffer; - List audioBytesList = audioBytes.toList(); - service.invoke("audioBytes", {"data": audioBytesList}); - }); - - service.on('stop').listen((event) { - _mRecorder.stopRecorder(); - service.invoke("stateUpdate", {"state": 'stopped'}); - service.stopSelf(); - }); -} diff --git a/app/lib/providers/websocket_provider.dart b/app/lib/providers/websocket_provider.dart deleted file mode 100644 index b316956ed..000000000 --- a/app/lib/providers/websocket_provider.dart +++ /dev/null @@ -1,326 +0,0 @@ -import 'dart:async'; -import 'dart:math'; - -import 'package:flutter/material.dart'; -import 'package:friend_private/backend/schema/message_event.dart'; -import 'package:friend_private/backend/schema/transcript_segment.dart'; -import 'package:friend_private/backend/schema/bt_device.dart'; -import 'package:friend_private/services/notification_service.dart'; -import 'package:friend_private/utils/websockets.dart'; -import 'package:internet_connection_checker_plus/internet_connection_checker_plus.dart'; -import 'package:web_socket_channel/io.dart'; - -@Deprecated("Use the socket service") -class WebSocketProvider with ChangeNotifier { - WebsocketConnectionStatus wsConnectionState = WebsocketConnectionStatus.notConnected; - bool websocketReconnecting = false; - IOWebSocketChannel? websocketChannel; - int _reconnectionAttempts = 0; - Timer? _reconnectionTimer; - StreamSubscription? _internetListener; - InternetStatus _internetStatus = InternetStatus.connected; - - final int _initialReconnectDelay = 1; - final int _maxReconnectDelay = 60; - bool _isConnecting = false; - - bool _hasNotifiedUser = false; - bool _internetListenerSetup = false; - Timer? internetLostNotificationDelay; - - bool shouldReconnect = true; - - get isConnecting => _isConnecting; - - Future initWebSocket({ - required Function onConnectionSuccess, - required Function(dynamic) onConnectionFailed, - required Function(int?, String?) onConnectionClosed, - required Function(dynamic) onConnectionError, - required Function(List) onMessageReceived, - Function(ServerMessageEvent)? onMessageEventReceived, - required BleAudioCodec codec, - required int sampleRate, - required bool includeSpeechProfile, - required bool newMemoryWatch, - }) async { - print('isConnecting before even the func begins: $_isConnecting'); - if (_isConnecting) return; - _isConnecting = true; - - debugPrint('initWebSocket with ${codec} ${sampleRate}'); - if (!_internetListenerSetup) { - _setupInternetListener( - onConnectionSuccess: onConnectionSuccess, - onConnectionFailed: onConnectionFailed, - onConnectionClosed: onConnectionClosed, - onConnectionError: onConnectionError, - onMessageReceived: onMessageReceived, - onMessageEventReceived: onMessageEventReceived, - codec: codec, - sampleRate: sampleRate, - includeSpeechProfile: includeSpeechProfile, - newMemoryWatch: newMemoryWatch, - ); - _internetListenerSetup = true; - } - - if (_internetStatus == InternetStatus.disconnected) { - debugPrint('No internet connection. Waiting for connection to be restored.'); - _isConnecting = false; - return; - } - - try { - websocketChannel = await streamingTranscript( - onWebsocketConnectionSuccess: () { - debugPrint('WebSocket connected successfully'); - wsConnectionState = WebsocketConnectionStatus.connected; - websocketReconnecting = false; - _reconnectionAttempts = 0; - _isConnecting = false; - shouldReconnect = true; - onConnectionSuccess(); - NotificationService.instance.clearNotification(2); - notifyListeners(); - }, - onWebsocketConnectionFailed: (err) { - debugPrint('WebSocket connection failed: $err'); - wsConnectionState = WebsocketConnectionStatus.failed; - websocketReconnecting = false; - _isConnecting = false; - onConnectionFailed(err); - _scheduleReconnection( - onConnectionSuccess: onConnectionSuccess, - onConnectionFailed: onConnectionFailed, - onConnectionClosed: onConnectionClosed, - onConnectionError: onConnectionError, - onMessageReceived: onMessageReceived, - onMessageEventReceived: onMessageEventReceived, - codec: codec, - sampleRate: sampleRate, - includeSpeechProfile: includeSpeechProfile, - newMemoryWatch: newMemoryWatch, - ); - notifyListeners(); - }, - onWebsocketConnectionClosed: (int? closeCode, String? closeReason) { - debugPrint('WebSocket connection closed2: code ~ $closeCode, reason ~ $closeReason'); - wsConnectionState = WebsocketConnectionStatus.closed; - _isConnecting = false; - onConnectionClosed(closeCode, closeReason); - if (shouldReconnect) { - if (closeCode != 1000 && !websocketReconnecting) { - _scheduleReconnection( - onConnectionSuccess: onConnectionSuccess, - onConnectionFailed: onConnectionFailed, - onConnectionClosed: onConnectionClosed, - onConnectionError: onConnectionError, - onMessageReceived: onMessageReceived, - onMessageEventReceived: onMessageEventReceived, - codec: codec, - sampleRate: sampleRate, - includeSpeechProfile: includeSpeechProfile, - newMemoryWatch: newMemoryWatch, - ); - } - } - - notifyListeners(); - }, - onWebsocketConnectionError: (err) { - debugPrint('WebSocket connection error: $err'); - wsConnectionState = WebsocketConnectionStatus.error; - websocketReconnecting = false; - _isConnecting = false; - onConnectionError(err); - _scheduleReconnection( - onConnectionSuccess: onConnectionSuccess, - onConnectionFailed: onConnectionFailed, - onConnectionClosed: onConnectionClosed, - onConnectionError: onConnectionError, - onMessageReceived: onMessageReceived, - onMessageEventReceived: onMessageEventReceived, - codec: codec, - sampleRate: sampleRate, - includeSpeechProfile: includeSpeechProfile, - newMemoryWatch: newMemoryWatch, - ); - notifyListeners(); - }, - onMessageReceived: onMessageReceived, - onMessageEventReceived: onMessageEventReceived, - codec: codec, - sampleRate: sampleRate, - includeSpeechProfile: includeSpeechProfile, - newMemoryWatch: newMemoryWatch, - ); - } catch (e) { - debugPrint('Error in initWebSocket: $e'); - _isConnecting = false; - onConnectionFailed(e); - notifyListeners(); - } - } - - void _setupInternetListener({ - required Function onConnectionSuccess, - required Function(dynamic) onConnectionFailed, - required Function(int?, String?) onConnectionClosed, - required Function(dynamic) onConnectionError, - required Function(List) onMessageReceived, - Function(ServerMessageEvent)? onMessageEventReceived, - required BleAudioCodec codec, - required int sampleRate, - required bool includeSpeechProfile, - required bool newMemoryWatch, - }) { - _internetListener?.cancel(); - _internetListener = InternetConnection().onStatusChange.listen((InternetStatus status) { - _internetStatus = status; - switch (status) { - case InternetStatus.connected: - if (wsConnectionState != WebsocketConnectionStatus.connected && !_isConnecting) { - debugPrint('Internet connection restored. Attempting to reconnect WebSocket.'); - internetLostNotificationDelay?.cancel(); - _reconnectionTimer?.cancel(); - _reconnectionAttempts = 0; - _attemptReconnection( - onConnectionSuccess: onConnectionSuccess, - onConnectionFailed: onConnectionFailed, - onConnectionClosed: onConnectionClosed, - onConnectionError: onConnectionError, - onMessageReceived: onMessageReceived, - onMessageEventReceived: onMessageEventReceived, - codec: codec, - sampleRate: sampleRate, - includeSpeechProfile: includeSpeechProfile, - newMemoryWatch: newMemoryWatch, - ); - } - break; - case InternetStatus.disconnected: - debugPrint('Internet connection lost. Disconnecting WebSocket.'); - internetLostNotificationDelay?.cancel(); - internetLostNotificationDelay = Timer(const Duration(seconds: 60), () => _notifyInternetLost()); - websocketChannel?.sink.close(1000, 'Internet connection lost'); - _reconnectionTimer?.cancel(); - wsConnectionState = WebsocketConnectionStatus.notConnected; - onConnectionClosed(1000, 'Internet connection lost'); - notifyListeners(); - break; - } - }); - } - - void _scheduleReconnection({ - required Function onConnectionSuccess, - required Function(dynamic) onConnectionFailed, - required Function(int?, String?) onConnectionClosed, - required Function(dynamic) onConnectionError, - required Function(List) onMessageReceived, - Function(ServerMessageEvent)? onMessageEventReceived, - required BleAudioCodec codec, - required int sampleRate, - required bool includeSpeechProfile, - required bool newMemoryWatch, - }) { - if (websocketReconnecting || _internetStatus == InternetStatus.disconnected || _isConnecting) return; - - websocketReconnecting = true; - _reconnectionAttempts++; - - int delaySeconds = _calculateReconnectDelay(); - debugPrint('Scheduling reconnection attempt $_reconnectionAttempts in $delaySeconds seconds'); - - _reconnectionTimer?.cancel(); - _reconnectionTimer = Timer(Duration(seconds: delaySeconds), () { - _attemptReconnection( - onConnectionSuccess: onConnectionSuccess, - onConnectionFailed: onConnectionFailed, - onConnectionClosed: onConnectionClosed, - onConnectionError: onConnectionError, - onMessageReceived: onMessageReceived, - onMessageEventReceived: onMessageEventReceived, - codec: codec, - sampleRate: sampleRate, - includeSpeechProfile: includeSpeechProfile, - newMemoryWatch: newMemoryWatch, - ); - }); - if (_reconnectionAttempts == 6 && !_hasNotifiedUser) { - _notifyReconnectionFailure(); - _hasNotifiedUser = true; - } - } - - int _calculateReconnectDelay() { - int delay = _initialReconnectDelay * pow(2, _reconnectionAttempts - 1).toInt(); - return min(delay, _maxReconnectDelay); - } - - Future _attemptReconnection({ - required Function onConnectionSuccess, - required Function(dynamic) onConnectionFailed, - required Function(int?, String?) onConnectionClosed, - required Function(dynamic) onConnectionError, - required Function(List) onMessageReceived, - Function(ServerMessageEvent)? onMessageEventReceived, - required BleAudioCodec codec, - required int sampleRate, - required bool includeSpeechProfile, - required bool newMemoryWatch, - }) async { - if (_internetStatus == InternetStatus.disconnected) { - debugPrint('Cannot attempt reconnection: No internet connection'); - return; - } - - debugPrint('Attempting reconnection'); - websocketChannel?.sink.close(1000); - await initWebSocket( - onConnectionSuccess: onConnectionSuccess, - onConnectionFailed: onConnectionFailed, - onConnectionClosed: onConnectionClosed, - onConnectionError: onConnectionError, - onMessageReceived: onMessageReceived, - onMessageEventReceived: onMessageEventReceived, - codec: codec, - sampleRate: sampleRate, - includeSpeechProfile: includeSpeechProfile, - newMemoryWatch: newMemoryWatch, - ); - } - - void _notifyReconnectionFailure() { - NotificationService.instance.clearNotification(2); - NotificationService.instance.createNotification( - notificationId: 2, - title: 'Connection Issue 🚨', - body: 'Unable to connect to the transcript service.' - ' Please restart the app or contact support if the problem persists.', - ); - } - - void _notifyInternetLost() { - NotificationService.instance.clearNotification(3); - NotificationService.instance.createNotification( - notificationId: 3, - title: 'Internet Connection Lost', - body: 'Your device is offline. Transcription is paused until connection is restored.', - ); - } - - Future closeWebSocketWithoutReconnect(String from) async { - print('Closing WebSocket from $from'); - _isConnecting = false; - _internetListenerSetup = false; - _reconnectionTimer?.cancel(); - _internetListener?.cancel(); - internetLostNotificationDelay?.cancel(); - shouldReconnect = false; - print('wschannel is null: ${websocketChannel == null}'); - await websocketChannel?.sink.close(1000, 'User closed WebSocket'); - notifyListeners(); - } -} diff --git a/app/lib/utils/websockets.dart b/app/lib/utils/websockets.dart deleted file mode 100644 index bfabb56c1..000000000 --- a/app/lib/utils/websockets.dart +++ /dev/null @@ -1,124 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; - -import 'package:flutter/material.dart'; -import 'package:friend_private/backend/preferences.dart'; -import 'package:friend_private/backend/schema/bt_device.dart'; -import 'package:friend_private/backend/schema/message_event.dart'; -import 'package:friend_private/backend/schema/transcript_segment.dart'; -import 'package:friend_private/env/env.dart'; -import 'package:instabug_flutter/instabug_flutter.dart'; -import 'package:web_socket_channel/io.dart'; - -enum WebsocketConnectionStatus { notConnected, connected, failed, closed, error } - -Future _initWebsocketStream( - void Function(List) onMessageReceived, - void Function(ServerMessageEvent)? onMessageEventReceived, - VoidCallback onWebsocketConnectionSuccess, - void Function(dynamic) onWebsocketConnectionFailed, - void Function(int?, String?) onWebsocketConnectionClosed, - void Function(dynamic) onWebsocketConnectionError, - int sampleRate, - String codec, - bool includeSpeechProfile, - bool newMemoryWatch, -) async { - debugPrint('Websocket Opening'); - final recordingsLanguage = SharedPreferencesUtil().recordingsLanguage; - var params = '?language=$recordingsLanguage&sample_rate=$sampleRate&codec=$codec&uid=${SharedPreferencesUtil().uid}' - '&include_speech_profile=$includeSpeechProfile&new_memory_watch=$newMemoryWatch&stt_service=${SharedPreferencesUtil().transcriptionModel}'; - - IOWebSocketChannel channel = IOWebSocketChannel.connect( - Uri.parse('${Env.apiBaseUrl!.replaceAll('https', 'wss')}listen$params'), - // headers: {'Authorization': await getAuthHeader()}, - ); - - await channel.ready.then((v) { - channel.stream.listen( - (event) { - if (event == 'ping') return; - - final jsonEvent = jsonDecode(event); - - // segment - if (jsonEvent is List) { - var segments = jsonEvent; - if (segments.isEmpty) return; - onMessageReceived(segments.map((e) => TranscriptSegment.fromJson(e)).toList()); - return; - } - - debugPrint(event); - - // object message event - if (jsonEvent.containsKey("type")) { - var messageEvent = ServerMessageEvent.fromJson(jsonEvent); - if (onMessageEventReceived != null) { - onMessageEventReceived(messageEvent); - return; - } - } - - debugPrint(event.toString()); - }, - onError: (err, stackTrace) { - onWebsocketConnectionError(err); // error during connection - CrashReporting.reportHandledCrash(err!, stackTrace, level: NonFatalExceptionLevel.warning); - }, - onDone: (() { - // debugPrint('Websocket connection onDone ${channel}'); // FIXME - onWebsocketConnectionClosed(channel.closeCode, channel.closeReason); - }), - cancelOnError: true, // TODO: is this correct? - ); - }).onError((err, stackTrace) { - // no closing reason or code - print(err); - CrashReporting.reportHandledCrash(err!, stackTrace, level: NonFatalExceptionLevel.warning); - onWebsocketConnectionFailed(err); // initial connection failed - }); - - try { - await channel.ready; - debugPrint('Websocket Opened'); - onWebsocketConnectionSuccess(); - } catch (err) { - print(err); - } - return channel; -} - -Future streamingTranscript({ - required VoidCallback onWebsocketConnectionSuccess, - required void Function(dynamic) onWebsocketConnectionFailed, - required void Function(int?, String?) onWebsocketConnectionClosed, - required void Function(dynamic) onWebsocketConnectionError, - required void Function(List) onMessageReceived, - Function(ServerMessageEvent)? onMessageEventReceived, - required BleAudioCodec codec, - required int sampleRate, - required bool includeSpeechProfile, - required bool newMemoryWatch, -}) async { - try { - IOWebSocketChannel? channel = await _initWebsocketStream( - onMessageReceived, - onMessageEventReceived, - onWebsocketConnectionSuccess, - onWebsocketConnectionFailed, - onWebsocketConnectionClosed, - onWebsocketConnectionError, - sampleRate, - mapCodecToName(codec), - includeSpeechProfile, - newMemoryWatch, - ); - - return channel; - } catch (e) { - debugPrint('Error receiving data: $e'); - } finally {} - - return null; -}