Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix background microphone recording on Android #564

Merged
merged 5 commits into from
Aug 10, 2024

Conversation

mdmohsin7
Copy link
Collaborator

@mdmohsin7 mdmohsin7 commented Aug 9, 2024

I've forked flutter_sound and made some changes in it so that it can work in background as well. I will open a PR to the flutter_sound for those changes to be merged. Once those changes are merged, we can use the package directly.

Summary by Entelligence.AI

  • New Feature: Introduced background microphone recording functionality for Android devices using flutter_sound. This allows the app to record and stream audio data even when it's running in the background.
  • Bug Fix: Added conditional logic to handle starting and stopping of stream recording based on the platform, ensuring smooth operation on both Android and iOS.
  • Chore: Updated Android manifest file and added new ProGuard rule to maintain compatibility with com.pravera.flutter_foreground_task.service, enhancing overall app stability.

Copy link

github-actions bot commented Aug 9, 2024

Image description Entelligence.AI

Walkthrough

This update introduces background microphone recording functionality for Android using flutter_sound. It includes changes in permissions, service management, and audio data handling. The code now accommodates different platforms and has enhanced event handling for background services. A new ProGuard rule is also added to protect specific classes.

Changes

File Summary
app/android/app/src/main/AndroidManifest.xml
app/lib/pages/capture/logic/mic_background_service.dart
app/lib/pages/capture/logic/phone_recorder_mixin.dart
app/lib/pages/capture/page.dart
These files have been updated to support background microphone recording on Android. This includes permission changes, service initialization, start and stop functions, and audio data handling. Platform-specific logic has been introduced in the CapturePageState class.
app/android/app/proguard-rules.pro A new ProGuard rule has been added to keep classes related to com.pravera.flutter_foreground_task.service.

"In the realm of sound, we've made a leap, 🎵

With background recording, while you sleep. 😴

On Android now, our app does peep, 📱

Capturing whispers, secrets you keep. 🤫"


Uplevel your code reviews with Entelligence.AI Pro

Entelligence.AI Pro

If you like this project, please support us by purchasing the Pro version. The Pro version has advanced context, superior noise reduction and several proprietary improvements compared to the open source version. Moreover, Entelligence.AI Pro is free for open source projects.

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description Entelligence.AI

Commits Files that changed from the base of the PR and between ed23442 and 46c22d9 commits.
Files selected (4)
  • app/android/app/src/main/AndroidManifest.xml (2)
  • app/lib/pages/capture/logic/mic_background_service.dart (1)
  • app/lib/pages/capture/logic/phone_recorder_mixin.dart (2)
  • app/lib/pages/capture/page.dart (2)
Files ignored due to filter (1)
  • app/pubspec.yaml
Review comments generated (13)
  • Review: 8
  • LGTM: 5

Tips

Chat with Image description Entelligence.AI Bot (@Entelligence.AI)

  • Reply on review comments left by this bot to ask follow-up questions. A review comment is a comment on a diff or a file.
  • Invite the bot into a review comment chain by tagging @Entelligence.AI in a reply.

Code suggestions

  • The bot may make code suggestions, but please review them carefully before committing since the line number ranges may be misaligned.
  • You can edit the comment made by the bot and manually tweak the suggestion if it is slightly off.

Pausing incremental reviews

  • Add @Entelligence.AI: ignore anywhere in the PR description to pause further reviews from the bot.

Comment on lines +83 to +86
<service
android:name="id.flutter.flutter_background_service.BackgroundService"
android:foregroundServiceType="microphone"/> -->
android:foregroundServiceType="microphone"
tools:node="merge" />
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description Entelligence.AI

The service declaration for the BackgroundService is missing some important attributes. It's recommended to add android:enabled="true" to ensure that the service can be instantiated by the system, and android:exported="false" to prevent other applications from starting the service. Also, consider adding android:stopWithTask="true" if you want the service to automatically stop when the user removes a task rooted in an activity owned by your application.

<service
    android:name="id.flutter.flutter_background_service.BackgroundService"
    android:foregroundServiceType="microphone"
+   android:enabled="true"
+   android:exported="false"
+   android:stopWithTask="true"
    tools:node="merge" />

Comment on lines +410 to +414
if (Platform.isAndroid) {
stopStreamRecordingOnAndroid();
} else {
await stopStreamRecording(wsConnectionState, websocketChannel);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description Entelligence.AI

The stopStreamRecordingOnAndroid() and stopStreamRecording() functions are called based on the platform, but there's no error handling in case these functions fail. It would be better to wrap these calls in a try-catch block to handle any potential exceptions.

-      if (Platform.isAndroid) {
-        stopStreamRecordingOnAndroid();
-      } else {
-        await stopStreamRecording(wsConnectionState, websocketChannel);
-      }
+      try {
+        if (Platform.isAndroid) {
+          stopStreamRecordingOnAndroid();
+        } else {
+          await stopStreamRecording(wsConnectionState, websocketChannel);
+        }
+      } catch (e) {
+        print('Error stopping stream recording: $e');
+      }

Comment on lines +431 to +435
if (Platform.isAndroid) {
await streamRecordingOnAndroid(wsConnectionState, websocketChannel);
} else {
await startStreamRecording(wsConnectionState, websocketChannel);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description Entelligence.AI

Similar to the previous comment, the streamRecordingOnAndroid() and startStreamRecording() functions are called based on the platform, but there's no error handling in case these functions fail. It would be better to wrap these calls in a try-catch block to handle any potential exceptions.

-            if (Platform.isAndroid) {
-              await streamRecordingOnAndroid(wsConnectionState, websocketChannel);
-            } else {
-              await startStreamRecording(wsConnectionState, websocketChannel);
-            }
+            try {
+              if (Platform.isAndroid) {
+                await streamRecordingOnAndroid(wsConnectionState, websocketChannel);
+              } else {
+                await startStreamRecording(wsConnectionState, websocketChannel);
+              }
+            } catch (e) {
+              print('Error starting stream recording: $e');
+            }

Comment on lines +31 to +58
WebsocketConnectionStatus? wsConnectionState2;
IOWebSocketChannel? websocketChannel2;

@override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
await listenToBackgroundService();
});
super.initState();
}

listenToBackgroundService() async {
if (await FlutterBackgroundService().isRunning()) {
FlutterBackgroundService().on('audioBytes').listen((event) {
Uint8List convertedList = Uint8List.fromList(event!['data'].cast<int>());
if (wsConnectionState2 == WebsocketConnectionStatus.connected) websocketChannel2?.sink.add(convertedList);
});
FlutterBackgroundService().on('stateUpdate').listen((event) {
if (event!['state'] == 'recording') {
setState(() => recordingState = RecordingState.record);
} else if (event['state'] == 'initializing') {
setState(() => recordingState = RecordingState.initialising);
} else if (event['state'] == 'stopped') {
setState(() => recordingState = RecordingState.stop);
}
});
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description Entelligence.AI

The listenToBackgroundService method is missing error handling. If the background service fails or if there's an issue with the event listeners, it could lead to unhandled exceptions. Consider wrapping the contents of this method in a try-catch block to handle potential errors.

  listenToBackgroundService() async {
+    try {
    if (await FlutterBackgroundService().isRunning()) {
      FlutterBackgroundService().on('audioBytes').listen((event) {
        Uint8List convertedList = Uint8List.fromList(event!['data'].cast<int>());
        if (wsConnectionState2 == WebsocketConnectionStatus.connected) websocketChannel2?.sink.add(convertedList);
      });
      FlutterBackgroundService().on('stateUpdate').listen((event) {
        if (event!['state'] == 'recording') {
          setState(() => recordingState = RecordingState.record);
        } else if (event['state'] == 'initializing') {
          setState(() => recordingState = RecordingState.initialising);
        } else if (event['state'] == 'stopped') {
          setState(() => recordingState = RecordingState.stop);
        }
      });
    }
+    } catch (e) {
+      // Handle or log the error here
+    }
  }

Comment on lines +60 to +75
streamRecordingOnAndroid(WebsocketConnectionStatus wsConnectionState, IOWebSocketChannel? websocketChannel) async {
setState(() {
wsConnectionState2 = wsConnectionState;
websocketChannel2 = websocketChannel;
});
await Permission.microphone.request();
setState(() => recordingState = RecordingState.initialising);
await initializeMicBackgroundService();
startBackgroundService();
await listenToBackgroundService();
}

stopStreamRecordingOnAndroid() {
stopBackgroundService();
if (wsConnectionState2 == WebsocketConnectionStatus.connected) websocketChannel2?.sink.close();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description Entelligence.AI

In the streamRecordingOnAndroid and stopStreamRecordingOnAndroid methods, you're directly manipulating state variables (wsConnectionState2, websocketChannel2). This can lead to unexpected behavior and make the code harder to debug. Instead, consider creating setter methods for these variables that encapsulate the state changes.

Comment on lines +8 to +11
void startBackgroundService() {
final service = FlutterBackgroundService();
service.startService();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description Entelligence.AI

The startBackgroundService and stopBackgroundService methods are creating a new instance of FlutterBackgroundService each time they're called. This could lead to multiple instances of the service running concurrently, which might not be what you want. Consider using a singleton pattern for managing the background service instance.

+ FlutterBackgroundService _serviceInstance = FlutterBackgroundService();

- void startBackgroundService() {
-   final service = FlutterBackgroundService();
-   service.startService();
- }

+ void startBackgroundService() {
+   _serviceInstance.startService();
+ }

- void stopBackgroundService() {
-   final service = FlutterBackgroundService();
-   service.invoke("stop");
- }

+ void stopBackgroundService() {
+   _serviceInstance.invoke("stop");
+ }

Comment on lines +18 to +35
Future<void> 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,
),
);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description Entelligence.AI

In the initializeMicBackgroundService method, you're configuring the service to auto-start on Android but not on iOS (autoStart: true vs autoStart: false). If this is intentional, make sure it's documented somewhere so that future developers understand why there's a difference in behavior between the two platforms.

Comment on lines +44 to +69
@pragma('vm:entry-point')
Future onStart(ServiceInstance service) async {
FlutterSoundRecorder? _mRecorder = FlutterSoundRecorder();
await _mRecorder.openRecorder(isBGService: true);
var recordingDataController = StreamController<Uint8List>();

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<dynamic> audioBytesList = audioBytes.toList();
service.invoke("audioBytes", {"data": audioBytesList});
});

service.on('stop').listen((event) {
_mRecorder.stopRecorder();
service.invoke("stateUpdate", {"state": 'stopped'});
service.stopSelf();
});
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description Entelligence.AI

In the onStart method, you're starting the recorder and setting up a stream listener, but there's no error handling. If an error occurs while opening the recorder or during recording, it could cause the app to crash. Consider adding try-catch blocks around these operations to handle potential errors gracefully.

  @pragma('vm:entry-point')
  Future onStart(ServiceInstance service) async {
    FlutterSoundRecorder? _mRecorder = FlutterSoundRecorder();
+   try {
      await _mRecorder.openRecorder(isBGService: true);
      var recordingDataController = StreamController<Uint8List>();

      await _mRecorder.startRecorder(
        toStream: recordingDataController.sink,
        codec: Codec.pcm16,
        numChannels: 1,
        sampleRate: 16000,
        bufferSize: 8192,
      );
+   } catch (e) {
+     // Handle the error appropriately
+     print('Error starting recorder: $e');
+   }
    service.invoke("stateUpdate", {"state": 'recording'});
    recordingDataController.stream.listen((buffer) {
      Uint8List audioBytes = buffer;
      List<dynamic> audioBytesList = audioBytes.toList();
      service.invoke("audioBytes", {"data": audioBytesList});
    });

    service.on('stop').listen((event) {
+     try {
        _mRecorder.stopRecorder();
+     } catch (e) {
+       // Handle the error appropriately
+       print('Error stopping recorder: $e');
+     }
      service.invoke("stateUpdate", {"state": 'stopped'});
      service.stopSelf();
    });
  }

Copy link
Contributor

@josancamon19 josancamon19 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested on discord DM

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description Entelligence.AI

Commits Files that changed from the base of the PR and between 46c22d9 and 9f6aa95 commits.
Files selected (1)
  • app/android/app/proguard-rules.pro (1)
Files ignored due to filter (1)
  • app/pubspec.yaml
Review comments generated (2)
  • Review: 1
  • LGTM: 1

Tips

Chat with Image description Entelligence.AI Bot (@Entelligence.AI)

  • Reply on review comments left by this bot to ask follow-up questions. A review comment is a comment on a diff or a file.
  • Invite the bot into a review comment chain by tagging @Entelligence.AI in a reply.

Code suggestions

  • The bot may make code suggestions, but please review them carefully before committing since the line number ranges may be misaligned.
  • You can edit the comment made by the bot and manually tweak the suggestion if it is slightly off.

Pausing incremental reviews

  • Add @Entelligence.AI: ignore anywhere in the PR description to pause further reviews from the bot.

Comment on lines 7 to 13

# You might not be using firebase
-keep class com.google.firebase.** { *; }
-keep class com.builttoroam.devicecalendar.** { *; } No newline at end of file
-keep class com.builttoroam.devicecalendar.** { *; }

-keep class com.pravera.flutter_foreground_task.service.** { *; }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description Entelligence.AI

Note: This review was outside of the patch, but no patch was found that overlapped with it. Original lines [14-14]

It's good that you've added the ProGuard rule to prevent obfuscation of the com.pravera.flutter_foreground_task.service classes, as they are likely used in reflection or native calls. However, it's a best practice to add a newline at the end of files. This is because some Unix-based tools don't handle the lack of a newline character at the end of files very well.

- \ No newline at end of file
+ -keep class com.pravera.flutter_foreground_task.service.** { *; }
+ 

@josancamon19 josancamon19 merged commit 1bd685b into main Aug 10, 2024
1 check passed
@josancamon19 josancamon19 deleted the fix/mic-background-android branch August 10, 2024 21:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants