Immanuel Dsouza
Immanuel Dsouza

Reputation: 1

How to play audio, when my flutter app is terminated (Alarm application)

I am working on an alarm app which is created in flutter. I am using android_alarm_manager_plus, flutter_foreground_task, flutter_local_notifications as the major packages to implement core functionality of alarm. I am basically building alarm app to trigger alarm for the selected days on specific time.

The app is able to fire alarms by showing notification & play alarm-audio ,when the app is alive in foreground. But when i terminate my app, after setting alarm; it would show the notification. But no alarm-audio is played.

here is my pubspec.yaml packages

enter image description here

When the alarm is being fired, the "alarmCallback" is executed. Inside it I have tried to start the foreground service that plays the audio in loop. Whenever the user taps on "stop alarm" notification button, i am stopping the audio and closing the foreground service.

I tried to google about this problem, not much help exists there. So please help me to resolve this issue. Here is the code of "alarmCallback", "foregroundservice" and "foregroundServiceHandler".

AlarmCallback & foregroundStartServiceCallback code (top level function)


@pragma('vm:entry-point')
void startForegroundTaskCallback() {
  FlutterForegroundTask.setTaskHandler(ForegroundServiceHandler());
}

@pragma('vm:entry-point')
void alarmCallback(int id, Map<String, dynamic> params) async {
  debugPrint("The alarm callback is running !");
  bool isRunningService = await FlutterForegroundTask.isRunningService;

  debugPrint("The foreground service is running : $isRunningService");

  if (!isRunningService) {
    debugPrint("Init foreground task");
    ForegroundTaskService.init();
  } else {
    debugPrint("restart foreground task");
    await FlutterForegroundTask.restartService();
  }

  try {
    var map = params['params'] as Map<String, dynamic>;
    var alarmModel = Alarmsmodel.fromMap(map);

    debugPrint(alarmModel.toString());

    assert(alarmModel.audioPath != null,
        " Cannot play audio because ,audio path is null !");

    
    ForegroundTaskService.saveData("audioPath", alarmModel.audioPath);
    ServiceRequestResult serviceResult =
        await ForegroundTaskService.startService();

    if (serviceResult is ServiceRequestSuccess) {
      debugPrint("service result is success !");
    } else if (serviceResult is ServiceRequestFailure) {
      debugPrint("service result is faliure !");
    }

    var weekDay = DateTime.now().weekday;

    // logic to reschedule alarm in repetition pattern.
    // await LocalAlarmService.rescheduleAlarm(alarmModel, weekDay);
  } catch (e) {
    debugPrint("Error : $e");
    throw Exception("Something went wrong, after triggering alarmCallback");
  }
}

This is ForegroundService class that manages the foreground task in flutter


@pragma('vm:entry-point')
void startForegroundTaskCallback() {
  FlutterForegroundTask.setTaskHandler(ForegroundServiceHandler());
}

@pragma('vm:entry-point')
void alarmCallback(int id, Map<String, dynamic> params) async {
  debugPrint("The alarm callback is running !");
  bool isRunningService = await FlutterForegroundTask.isRunningService;

  debugPrint("The foreground service is running : $isRunningService");

  if (!isRunningService) {
    debugPrint("Init foreground task");
    ForegroundTaskService.init();
  } else {
    debugPrint("restart foreground task");
    await FlutterForegroundTask.restartService();
  }

  try {
    var map = params['params'] as Map<String, dynamic>;
    var alarmModel = Alarmsmodel.fromMap(map);

    debugPrint(alarmModel.toString());

    assert(alarmModel.audioPath != null,
        " Cannot play audio because ,audio path is null !");

    
    ForegroundTaskService.saveData("audioPath", alarmModel.audioPath);
    ServiceRequestResult serviceResult =
        await ForegroundTaskService.startService();

    if (serviceResult is ServiceRequestSuccess) {
      debugPrint("service result is success !");
    } else if (serviceResult is ServiceRequestFailure) {
      debugPrint("service result is faliure !");
    }

    var weekDay = DateTime.now().weekday;

    // logic to reschedule alarm in repetition pattern.
    // await LocalAlarmService.rescheduleAlarm(alarmModel, weekDay);
  } catch (e) {
    debugPrint("Error : $e");
    throw Exception("Something went wrong, after triggering alarmCallback");
  }
}

Here is the code of foregroundServiceHandler


class ForegroundServiceHandler extends TaskHandler {
  @override
  Future<void> onStart(DateTime timestamp, TaskStarter starter) async {
    debugPrint("Fetching audio path.");

    String? audioPath =
        await FlutterForegroundTask.getData<String>(key: 'audioPath');
    debugPrint("The audio path is $audioPath");

    if (audioPath != null && audioPath.isNotEmpty) {
      debugPrint(
          "The audio path is not null neither its empty . Audiopath is $audioPath");
      AudioService.instance.playAudioInForeground(audioPath);
    } else {
      debugPrint(
          "Error : audioPath is missing. (ForegroundServiceHandler)(onStart)");
    }
  }

  @override
  void onNotificationButtonPressed(String id) {
    super.onNotificationButtonPressed(id);
    debugPrint("onNotificationButtonPressed");

    if (id == "stop_audio") {
      AudioService.instance.stopAudio();
    }
    FlutterForegroundTask.stopService();

  }

  @override
  void onNotificationPressed() {
    super.onNotificationPressed();
    debugPrint("onNotificationPressed");
  }

  @override
  void onNotificationDismissed() {
    super.onNotificationDismissed();
    debugPrint("onNotificationDismissed");
    FlutterForegroundTask.stopService();
  }

  @override
  void onReceiveData(Object data) {
    super.onReceiveData(data);
    debugPrint("onReceiveData");
  }

  @override
  void onRepeatEvent(DateTime timestamp) {}

  @override
  Future<void> onDestroy(DateTime timestamp) async {
    debugPrint("onDestroy invoked. Stopping service");
  }
}

This is my code Audio Service


class AudioService {
  AudioPlayer? _audioPlayer;
  bool isAudioPlaying = false;

  static final AudioService instance = AudioService._internal();

  factory AudioService() {
    return instance;
  }

  AudioService._internal() {
    _audioPlayer = AudioPlayer();
    _audioPlayer?.setReleaseMode(ReleaseMode.loop);
    _audioPlayer?.onPlayerStateChanged.listen((PlayerState state) {
      isAudioPlaying = (state == PlayerState.playing);
    });
  }

  void disposeAudio() {
    _audioPlayer?.dispose();
    _audioPlayer = null;
  }

  void playAudio(String path) async {
    if (_audioPlayer == null) {
      debugPrint("Audio player instance is null !");
    } else {
      debugPrint("Audio Player instance is not null !");
    }

    _audioPlayer ??= AudioPlayer(); //ensure its initialized.

    if (isAudioPlaying) {
      await _audioPlayer?.stop();
    }

    debugPrint("Playing audio for audiopath : $path");
    await _audioPlayer?.play(
      AssetSource(path),
      volume: 1,
    );
  }

  void stopAudio() async {
    _audioPlayer ??= AudioPlayer();
    debugPrint("Stopping audio instance hashcode : ${_audioPlayer.hashCode}");

    await _audioPlayer!.stop();
  }

  void playAudioInForeground(String path) async {
    String localPath = await getLocalFilePath(path);

    debugPrint("Setting audio source url");

    debugPrint("playing deviceFilePath .");
    await _audioPlayer?.play(DeviceFileSource(localPath), volume: 1);
  }

  Future<String> getLocalFilePath(String assetPath) async {
    // Get the app's document directory.
    final directory = await getApplicationDocumentsDirectory();
    final filePath = '${directory.path}/$assetPath';
    debugPrint("File path is $filePath");

    final file = File(filePath);
    bool isFilePresent = await file.exists();
    debugPrint("Does file exists $isFilePresent");

    if (!file.existsSync()) {
      // load assets as bytes.
      debugPrint("loading bytes from rootbundle");
      ByteData data = await rootBundle.load("assets/$assetPath");

      debugPrint("loading bytes from ByteData");
      List<int> bytes = data.buffer.asUint8List();

      debugPrint("The bytes length is ${bytes.length}");
      await file.create(recursive: true);
      await file.writeAsBytes(bytes);

      debugPrint("written to file");
    }

    debugPrint("returning device file path - $filePath");
    return filePath;
  }
}

Upvotes: 0

Views: 43

Answers (0)

Related Questions