audio_waveforms icon indicating copy to clipboard operation
audio_waveforms copied to clipboard

Is there any way to pause the current audio while start playing another one?

Open mohammadsaif19 opened this issue 2 years ago • 6 comments

Similar to the WhatsApp voice note, when we play any new voice note the current gets paused or stopped.

mohammadsaif19 avatar Sep 21 '22 08:09 mohammadsaif19

same issues

nawafAlkhadidi avatar Sep 21 '22 12:09 nawafAlkhadidi

You can do this by calling pausePlayer when another audio is played for every other PlayerController but that would be cumbersome to do so putting it under enhancement.

ujas-m-simformsolutions avatar Sep 21 '22 17:09 ujas-m-simformsolutions

I have solved this problem by applying small hacks using the provider, I checked all the available player controllers and the file path of those controllers, and if the path isn't the same one I'm playing currently then the provider will notify the listeners to pause the other voice note. There are might be better approaches to solve it but this how I solved it for now, you may give it a shot.

mohammadsaif19 avatar Sep 22 '22 08:09 mohammadsaif19

I have a chat app - that lazy loads and caches players for each item and keeps track of the currently playing player and ensures only player is playing at any one time.

An abridged version of the code I wrote is here:

Each voice message just calls the voiceController.startOrStopPlayer(path) on tap gesture. The VoiceMessageController is insantiated at the top level widget which has children for the messages but also message bar.

Note - canCancel is based on whether the user has dragged their finger to the cancel area - which is why it's reset to false when the recording starts - as obviously they're not at the cancel zone.

It seems to work well - not done any profiling on it though


import 'package:audio_waveforms/audio_waveforms.dart';
import 'package:flutter/foundation.dart';

class VoiceMessageController {
  late final RecorderController recorderController;
  final ValueNotifier<bool> isRecordingNotifier = ValueNotifier(false);
  final ValueNotifier<bool> canCancel = ValueNotifier(false);
  Map<String, PlayerController> playerControllers = {};
  PlayerController? _currentPlayer;

  VoiceMessageController() {
    recorderController = RecorderController()
      ..iosEncoder = IosEncoder.kAudioFormatMPEG4AAC
      ..androidEncoder = AndroidEncoder.aac
      ..androidOutputFormat = AndroidOutputFormat.mpeg4
      ..normalizationFactor = Platform.isAndroid ? 60 : 25;
  }

  void startOrStopPlayer(String path) async {
    _currentPlayer?.pausePlayer();

    _currentPlayer = await getPlayerController(path);
    _currentPlayer!.startPlayer(finishMode: FinishMode.pause);
  }

  Future<PlayerController> getPlayerController(String path) async {
    if (playerControllers.containsKey(path)) {
      return playerControllers[path]!;
    }

    final playerController = PlayerController();
    await playerController.preparePlayer(path, 1);
    playerControllers[path] = playerController;
    return playerController;
  }

  void startRecording(String path) async {
    canCancel.value = false;
    await recorderController.record(path);
    isRecordingNotifier.value = true;
  }

  Future stopRecording({bool cancel = false}) async {
    final path = await recorderController.stop(true);
    isRecordingNotifier.value = false;
    if (cancel) {
      if (path != null) {
        File(path).delete();
      }
      return null;
    }
    return await _onVoidRecordComplete(path!);
  }

  Future _onVoidRecordComplete(String filePath) async {
	// do what you want with the file here - I create a player controller to find duration and return a custom object
	
  }

  dispose() {
    recorderController.dispose();
    playerControllers.forEach((key, value) {
      value.dispose();
    });
  }
}

codecoded avatar Sep 22 '22 10:09 codecoded

@codecoded Thank you for the code snippet. And also you can use playerKey available in PlayerController instead of the path for the key of the playerControllers map. A playerKey will be unique for each player.

Ujas-Majithiya avatar Sep 22 '22 12:09 Ujas-Majithiya

I actually pass in a custom object which has a unique asset id which I use as the key, rather than the path - just this is an abridged snippet.

I guess you could pass in the player key or null if new and also the path - so a quick pseduo revised version for those interested

  Future<PlayerController> getPlayerController({ required String path, String playerKey?, double? volume=1}) async {
    if (playerKey != null && playerControllers.containsKey(playerKey)) {
      return playerControllers[playerKey]!;
    }

    final playerController = PlayerController();
    await playerController.preparePlayer(path, volume);
    playerControllers[playerController.playerKey] = playerController;
    return playerController;
  }

codecoded avatar Sep 22 '22 13:09 codecoded

I have a chat app - that lazy loads and caches players for each item and keeps track of the currently playing player and ensures only player is playing at any one time.

An abridged version of the code I wrote is here:

Each voice message just calls the voiceController.startOrStopPlayer(path) on tap gesture. The VoiceMessageController is insantiated at the top level widget which has children for the messages but also message bar.

Note - canCancel is based on whether the user has dragged their finger to the cancel area - which is why it's reset to false when the recording starts - as obviously they're not at the cancel zone.

It seems to work well - not done any profiling on it though


import 'package:audio_waveforms/audio_waveforms.dart';
import 'package:flutter/foundation.dart';

class VoiceMessageController {
  late final RecorderController recorderController;
  final ValueNotifier<bool> isRecordingNotifier = ValueNotifier(false);
  final ValueNotifier<bool> canCancel = ValueNotifier(false);
  Map<String, PlayerController> playerControllers = {};
  PlayerController? _currentPlayer;

  VoiceMessageController() {
    recorderController = RecorderController()
      ..iosEncoder = IosEncoder.kAudioFormatMPEG4AAC
      ..androidEncoder = AndroidEncoder.aac
      ..androidOutputFormat = AndroidOutputFormat.mpeg4
      ..normalizationFactor = Platform.isAndroid ? 60 : 25;
  }

  void startOrStopPlayer(String path) async {
    _currentPlayer?.pausePlayer();

    _currentPlayer = await getPlayerController(path);
    _currentPlayer!.startPlayer(finishMode: FinishMode.pause);
  }

  Future<PlayerController> getPlayerController(String path) async {
    if (playerControllers.containsKey(path)) {
      return playerControllers[path]!;
    }

    final playerController = PlayerController();
    await playerController.preparePlayer(path, 1);
    playerControllers[path] = playerController;
    return playerController;
  }

  void startRecording(String path) async {
    canCancel.value = false;
    await recorderController.record(path);
    isRecordingNotifier.value = true;
  }

  Future stopRecording({bool cancel = false}) async {
    final path = await recorderController.stop(true);
    isRecordingNotifier.value = false;
    if (cancel) {
      if (path != null) {
        File(path).delete();
      }
      return null;
    }
    return await _onVoidRecordComplete(path!);
  }

  Future _onVoidRecordComplete(String filePath) async {
	// do what you want with the file here - I create a player controller to find duration and return a custom object
	
  }

  dispose() {
    recorderController.dispose();
    playerControllers.forEach((key, value) {
      value.dispose();
    });
  }
}

I think from this reference it can be easily implemented. We can use controller.playerKey to distinguish between players and implement function similar to play this and pause other.

ujas-m-simformsolutions avatar Jan 13 '23 08:01 ujas-m-simformsolutions

Closing this as not planned.

ujas-m-simformsolutions avatar Jan 13 '23 08:01 ujas-m-simformsolutions