flutter_sound icon indicating copy to clipboard operation
flutter_sound copied to clipboard

[BUG]: Audio does not record when screen locks

Open akshay-kapase opened this issue 1 year ago • 14 comments

Version of flutter_sound ^9.10.4

flutter doctor [√] Flutter (Channel stable, 3.22.2, on Microsoft Windows [Version 10.0.19045.4780], locale en-IN) [√] Windows Version (Installed version of Windows is version 10 or higher) [√] Chrome - develop for the web [√] Android Studio (version 2022.2) [√] Connected device (4 available) [√] Network resources

Platforms you faced the error (IOS or Android or both?) Android

Expected behavior Keep recording even when the screen times out.

Actual behavior Recording continues, but mic audio cuts off when Android times out . For example, if you record an hour long presentation, you'll find that the output file is one hour long, but there is only 1 minute of usable audio, and 59 minutes of total silence.

Tested environment (Emulator? Real Device?) Real Device. Redmi 11i OS : Android 13

Steps to reproduce the behavior Start recording, let Android time out while you are recording. After a few minutes, stop the recording, and listen to the output.

Flutter version Flutter 3.22.2 • channel stable • https://github.com/flutter/flutter.git Framework • revision 761747bfc5 (2 months ago) • 2024-06-05 22:15:13 +0200 Engine • revision edd8546116 Tools • Dart 3.4.3 • DevTools 2.34.3

akshay-kapase avatar Aug 15 '24 12:08 akshay-kapase

Thank you @akshay-kapase for your problem report. We already have several issue reported around this area, so we need to address this issue. Someone should investigate to understand what is the problem and what could be a solution. I added this issue in the column todo in 10.0 of the Kanban table to be sure that we will do something.

10.0 is not for today, nor for tomorrow. So it would be good if someone does something in 9.x. Perhaps the solution is in the new isBGService that we added recently on openRecorder(). Or perhaps this is something that can be fixed with some good parameters when opening the session...

Someone expert on Android should explain the problem and the solution.

Larpoux avatar Aug 15 '24 13:08 Larpoux

Hi @Larpoux thanks for the feedback. I just tried using isBgService but did not got any success using it. May i know what does isBgService actually does ?

akshay-kapase avatar Aug 15 '24 13:08 akshay-kapase

Perhaps Mohsin or @together87 could help you. We definitely need an Android expert to tell us what we need to do inside Flutter Sound. I am sure that this issue could be very easily fixed as soon as we understand what we must do.

Larpoux avatar Aug 15 '24 13:08 Larpoux

ok, after going through a lot of documentation & trial and errors, it seems the recording should start by starting a foreground service with a notification. I have tried with flutter_sound + flutter_foreground_task which did not work because i was getting this error from flutter_sound package

Error starting recorder: MissingPluginException(No implementation found for method resetPlugin on channel xyz.canardoux.flutter_sound_recorder)

so then i tried with record plugin + flutter_foreground_task in which case the recording works even when the phone is locked.

akshay-kapase avatar Aug 15 '24 19:08 akshay-kapase

Interesting. When you had success, it was not with Flutter Sound, was it ? I am not sure to have completely understood

Larpoux avatar Aug 15 '24 19:08 Larpoux

yes with flutter sound i got this resetPlugin error. i tried to run it in release mode but still got the same error. so i have changed this small poc code from flutter_sound to record plugin

akshay-kapase avatar Aug 15 '24 19:08 akshay-kapase

@Larpoux can you please guide me where you are running the background service inside the library

@Override
	public void onAttachedToEngine ( FlutterPlugin.FlutterPluginBinding binding )
	{
		this.pluginBinding = binding;

		new MethodChannel(binding.getBinaryMessenger(), "xyz.canardoux.flutter_sound_bgservice").setMethodCallHandler(new MethodCallHandler() {
			@Override
			public void onMethodCall ( final MethodCall call, final Result result )
			{
				if (call.method.equals("setBGService")) {
					attachFlauto();
				}
				result.success(0);
			}
		});
	}

akshay-kapase avatar Aug 17 '24 19:08 akshay-kapase

Hi @akshay-kapase @Larpoux I am also getting the same error and I am trying to find the solution, have you got any solution for this issue?

kaushik072 avatar Aug 23 '24 11:08 kaushik072

use combination of record plugin + foreground service task plugin + audioplayers plugin it should work

akshay-kapase avatar Aug 23 '24 18:08 akshay-kapase

use combination of record plugin + foreground service task plugin + audioplayers plugin it should work

@akshay-kapase would you share your code, we are also struggling with this problem.

truongdo avatar Aug 30 '24 10:08 truongdo

use combination of record plugin + foreground service task plugin + audioplayers plugin it should work

Any chance you can share your code? Thanks a lot

bloemy7 avatar Sep 20 '24 05:09 bloemy7

use combination of record plugin + foreground service task plugin + audioplayers plugin it should work

is there any fix yet? or could you share the code? Thanksss

yagoquesadafloriach avatar Jan 07 '25 11:01 yagoquesadafloriach

Subject: Solution for Background Recording after Screen Lock using Foreground Service

Hello everyone,

Addressing the issue of background recording after the screen locks, as highlighted by Android documentation Android Foreground Services, apps need to utilize a foreground service for recording to continue in the background after Android 14.

The following code implementation successfully enabled background recording for me. It leverages a combination of a foreground service and necessary permissions.

Code Implementation:

1. AndroidManifest.xml:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />

<service
    android:name=".ForegroundNotificationService"
    android:foregroundServiceType="microphone"
    android:enabled="true"
    android:exported="false"
    android:label="Record Service"
    android:permission="android.permission.FOREGROUND_SERVICE_MICROPHONE">
</service>

2. ForegroundNotificationService.kt:

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.IBinder
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat

class ForegroundNotificationService : Service() {

    companion object {
        private const val CHANNEL_ID = "ForegroundNotificationChannel"
        private const val NOTIFICATION_ID = 1
        private const val ACTION_START_FOREGROUND_SERVICE = "ACTION_START_FOREGROUND_SERVICE"
        private const val ACTION_STOP_FOREGROUND_SERVICE = "ACTION_STOP_FOREGROUND_SERVICE"

        fun startForegroundService(context: Context, message: String) {
            val intent = Intent(context, ForegroundNotificationService::class.java).apply {
                action = ACTION_START_FOREGROUND_SERVICE
                putExtra("message", message)
            }
            ContextCompat.startForegroundService(context, intent)
        }

        fun stopForegroundService(context: Context) {
            val intent = Intent(context, ForegroundNotificationService::class.java).apply {
                action = ACTION_STOP_FOREGROUND_SERVICE
            }
            context.startService(intent)
        }
    }

    override fun onCreate() {
        super.onCreate()
        createNotificationChannel()
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        intent?.action?.let { action ->
            when (action) {
                ACTION_START_FOREGROUND_SERVICE -> {
                    val message = intent.getStringExtra("message") ?: "Foreground Service is running"
                    startForegroundNotification(message)
                }
                ACTION_STOP_FOREGROUND_SERVICE -> {
                    stopForegroundNotification()
                    stopSelf()
                }
            }
        }
        return START_STICKY
    }

    override fun onDestroy() {
        super.onDestroy()
    }

    override fun onBind(intent: Intent?): IBinder? {
        return null
    }

    private fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val serviceChannel = NotificationChannel(
                CHANNEL_ID,
                "Foreground Service Channel",
                NotificationManager.IMPORTANCE_DEFAULT
            ).apply {
                description = "Notification channel for foreground service"
            }
            val manager = getSystemService(NotificationManager::class.java)
            manager.createNotificationChannel(serviceChannel)
        }
    }

    private fun startForegroundNotification(message: String) {
        val notification: Notification = NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle("Foreground Service")
            .setContentText(message)
            .setSmallIcon(R.drawable.ic_notification) // Replace with your notification icon
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            .setOngoing(true)
            .build()

        startForeground(NOTIFICATION_ID, notification)
    }

    private fun stopForegroundNotification() {
        stopForeground(STOP_FOREGROUND_REMOVE)
    }
}

3. ForegroundNotificationServicePlugin.kt:

package com.example.foreground_notification_service_plugin

import android.content.Context
import androidx.annotation.NonNull
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result


class ForegroundNotificationServicePlugin: MethodCallHandler {

    private lateinit var channel : MethodChannel
    private lateinit var context: Context

    fun registerWith(flutterEngine: FlutterEngine, context: Context) {
        channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "foreground_notification_service_plugin")
        channel.setMethodCallHandler(this)
        this.context = context.applicationContext
    }


    override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
        when (call.method) {
            "startService" -> {
                val message = call.argument<String>("message") as String? ?: "Foreground Service is running"
                startForegroundNotificationService(message)
                result.success(null)
            }
            "stopService" -> {
                stopForegroundNotificationService()
                result.success(null)
            }
            else -> {
                result.notImplemented()
            }
        }
    }

    private fun startForegroundNotificationService(message: String) {
        ForegroundNotificationService.startForegroundService(context, message)
    }

    private fun stopForegroundNotificationService() {
        ForegroundNotificationService.stopForegroundService(context)
    }
}

4. Plugin Registration in MainActivity (MainActivity.kt):

ForegroundNotificationServicePlugin().registerWith(flutterEngine, this)

5. Flutter Plugin Dart Code (foreground_notification_service_plugin.dart):

import 'dart:async';
import 'package:flutter/services.dart';

class ForegroundNotificationServicePlugin {
  static const MethodChannel _channel =
      const MethodChannel('foreground_notification_service_plugin');

  static Future<void> startService({String message = "Foreground Service is running"}) async {
    await _channel.invokeMethod('startService', <String, String>{
      'message': message, // Message parameter for Android side
    });
  }

  static Future<void> stopService() async {
    await _channel.invokeMethod('stopService');
  }
}

Usage in Flutter:

import 'package:foreground_notification_service_plugin/foreground_notification_service_plugin.dart';

// ... inside your Flutter code ...

ForegroundNotificationServicePlugin.startService(message: "Recording in progress..."); // Start service before recording
ForegroundNotificationServicePlugin.stopService(); // Stop service after recording is finished

slang-barn avatar Feb 20 '25 04:02 slang-barn

Subject: Solution for Background Recording after Screen Lock using Foreground Service

Hello everyone,

Addressing the issue of background recording after the screen locks, as highlighted by Android documentation Android Foreground Services, apps need to utilize a foreground service for recording to continue in the background after Android 14.

The following code implementation successfully enabled background recording for me. It leverages a combination of a foreground service and necessary permissions.

Code Implementation:

1. AndroidManifest.xml:

<service android:name=".ForegroundNotificationService" android:foregroundServiceType="microphone" android:enabled="true" android:exported="false" android:label="Record Service" android:permission="android.permission.FOREGROUND_SERVICE_MICROPHONE"> 2. ForegroundNotificationService.kt:

import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager import android.app.Service import android.content.Context import android.content.Intent import android.os.Build import android.os.IBinder import androidx.core.app.NotificationCompat import androidx.core.content.ContextCompat

class ForegroundNotificationService : Service() {

companion object {
    private const val CHANNEL_ID = "ForegroundNotificationChannel"
    private const val NOTIFICATION_ID = 1
    private const val ACTION_START_FOREGROUND_SERVICE = "ACTION_START_FOREGROUND_SERVICE"
    private const val ACTION_STOP_FOREGROUND_SERVICE = "ACTION_STOP_FOREGROUND_SERVICE"

    fun startForegroundService(context: Context, message: String) {
        val intent = Intent(context, ForegroundNotificationService::class.java).apply {
            action = ACTION_START_FOREGROUND_SERVICE
            putExtra("message", message)
        }
        ContextCompat.startForegroundService(context, intent)
    }

    fun stopForegroundService(context: Context) {
        val intent = Intent(context, ForegroundNotificationService::class.java).apply {
            action = ACTION_STOP_FOREGROUND_SERVICE
        }
        context.startService(intent)
    }
}

override fun onCreate() {
    super.onCreate()
    createNotificationChannel()
}

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    intent?.action?.let { action ->
        when (action) {
            ACTION_START_FOREGROUND_SERVICE -> {
                val message = intent.getStringExtra("message") ?: "Foreground Service is running"
                startForegroundNotification(message)
            }
            ACTION_STOP_FOREGROUND_SERVICE -> {
                stopForegroundNotification()
                stopSelf()
            }
        }
    }
    return START_STICKY
}

override fun onDestroy() {
    super.onDestroy()
}

override fun onBind(intent: Intent?): IBinder? {
    return null
}

private fun createNotificationChannel() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val serviceChannel = NotificationChannel(
            CHANNEL_ID,
            "Foreground Service Channel",
            NotificationManager.IMPORTANCE_DEFAULT
        ).apply {
            description = "Notification channel for foreground service"
        }
        val manager = getSystemService(NotificationManager::class.java)
        manager.createNotificationChannel(serviceChannel)
    }
}

private fun startForegroundNotification(message: String) {
    val notification: Notification = NotificationCompat.Builder(this, CHANNEL_ID)
        .setContentTitle("Foreground Service")
        .setContentText(message)
        .setSmallIcon(R.drawable.ic_notification) // Replace with your notification icon
        .setPriority(NotificationCompat.PRIORITY_DEFAULT)
        .setOngoing(true)
        .build()

    startForeground(NOTIFICATION_ID, notification)
}

private fun stopForegroundNotification() {
    stopForeground(STOP_FOREGROUND_REMOVE)
}

} 3. ForegroundNotificationServicePlugin.kt:

package com.example.foreground_notification_service_plugin

import android.content.Context import androidx.annotation.NonNull import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result

class ForegroundNotificationServicePlugin: MethodCallHandler {

private lateinit var channel : MethodChannel
private lateinit var context: Context

fun registerWith(flutterEngine: FlutterEngine, context: Context) {
    channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "foreground_notification_service_plugin")
    channel.setMethodCallHandler(this)
    this.context = context.applicationContext
}


override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
    when (call.method) {
        "startService" -> {
            val message = call.argument<String>("message") as String? ?: "Foreground Service is running"
            startForegroundNotificationService(message)
            result.success(null)
        }
        "stopService" -> {
            stopForegroundNotificationService()
            result.success(null)
        }
        else -> {
            result.notImplemented()
        }
    }
}

private fun startForegroundNotificationService(message: String) {
    ForegroundNotificationService.startForegroundService(context, message)
}

private fun stopForegroundNotificationService() {
    ForegroundNotificationService.stopForegroundService(context)
}

} 4. Plugin Registration in MainActivity (MainActivity.kt):

ForegroundNotificationServicePlugin().registerWith(flutterEngine, this) 5. Flutter Plugin Dart Code (foreground_notification_service_plugin.dart):

import 'dart:async'; import 'package:flutter/services.dart';

class ForegroundNotificationServicePlugin { static const MethodChannel _channel = const MethodChannel('foreground_notification_service_plugin');

static Future startService({String message = "Foreground Service is running"}) async { await _channel.invokeMethod('startService', <String, String>{ 'message': message, // Message parameter for Android side }); }

static Future stopService() async { await _channel.invokeMethod('stopService'); } } Usage in Flutter:

import 'package:foreground_notification_service_plugin/foreground_notification_service_plugin.dart';

// ... inside your Flutter code ...

ForegroundNotificationServicePlugin.startService(message: "Recording in progress..."); // Start service before recording ForegroundNotificationServicePlugin.stopService(); // Stop service after recording is finished

What about IOS ?

beshoo avatar Sep 10 '25 23:09 beshoo