flutterdartfirebase-cloud-messagingflutter-audioplayers

How to play notification sound in background (Flutter)


The alarm sound only plays once when received an FCM push notification in background.

Instead of using the default sound, I play the custom alarm sound using audioplayer. I want the alarm keeps playing and stops only when the app is opened / user taps the notification if an FCM push message is received in background. If it is a foreground message, I want it plays once or the user can stop it immediately if they tap the notification.

Right now if it is a foreground message, the user is able to stop the notification sound by tapping the notification or it stops on completed. For a background message, I play the sound in background and it's supposed to loop if the user doesn't respond to it, however it only plays once and doesn't stop immediately when the app is brought to foreground.

I created a class for the audio player, and call the play() and stop() function in the FCM background/foreground message handler. The audio player object is created and initialised in main.dart initState.

AlarmPlayer.dart

import 'package:audioplayers/audioplayers.dart';

enum notiState { background, foreground, unknown }

class AlarmPlayer {
  late bool isplaying;
  late bool isResponded;
  late AudioPlayer player;
  late notiState state;

  AlarmPlayer() {
    isplaying = false;
    isResponded = true;
    player = AudioPlayer();
    state = notiState.unknown;
  }

  void init() {
    player.onPlayerStateChanged.listen((event) {
      switch (event) {
        case PlayerState.playing:
          print("Player event: playing");
          isplaying = true;
          break;
        case PlayerState.stopped:
          print("Player event: stopped");
          isplaying = false;
          break;
        case PlayerState.completed:
          print("Player event: completed");
          isplaying = false;
          if (!isResponded && state == notiState.background) {
            print("Play again");
            playAlarm();
          }
          break;
        default:
          break;
      }
    });
  }

  Future<void> playAlarm() async {
    if (!isplaying) {
      print("Alarm started");
      await player.play(AssetSource('audio/emergency.mp3'));
    }
  }

  Future<void> stopAlarm() async {
    print("Alarm stopped");
    await player.stop();
  }
}

FCM background message handler

Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  // If you're going to use other Firebase services in the background, such as Firestore,
  // make sure you call `initializeApp` before using other Firebase services.
  await Firebase.initializeApp();

  final String? msgTitle = message.notification!.title;
  final String? msgBody = message.notification!.body;
  if (msgTitle != null && msgBody != null) {
    print("Handling a background message: ${message.messageId}");
    print("onBackgroundMessage $message");

    alarmPlayer.state = notiState.background;
    alarmPlayer.isResponded = false;
    alarmPlayer.playAlarm();
  }
}

On app opened

    FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) async {
      print("*****Opened notification*****");
      alarmPlayer.isResponded = true;
      alarmPlayer.stopAlarm();
    });

It seems that the onPlayerStateChanged listener is never triggered in background. Any clue what I'm missing? Please let me know if my question or information is unclear. Thanks.


Solution

  • Just found a simple way to do that. I remove the "notification" from the FCM message so the the flutter app doesn't consider it as a notification but a normal remote message, it won't trigger any popup notification or sound. In the background message handler, just simply implement the flutter local notification function with a unique channel id and custom sound. If you want it an insistent notification, set the additional flag to 4. The sound and vibration stop at the moment you interact with the notification.

    Find more about flutter local notification below: https://pub.dev/packages/flutter_local_notifications/example