Flutter: How to configure audio_session to duck others

1.9k views Asked by At

I need to play audio without interrupting an existing playlist. My goal is to play an audio over the current audio service that is going on the phone, ideally I would like to duck the audio and play my stream over it. Pretty much like GPS services do while driving (Waze, Google maps).

2

There are 2 answers

1
Paulo Briceño Gonzalez On BEST ANSWER

Steps:

  1. Install dependencies
  2. Create initAudioService() function
  3. Create an AudioSession instance
  4. Configure your AudioSession instance as in the example
  5. Initialize your AudioService as in the example
  6. Call initAudioService() from main before runApp

Dependencies:

  • audio_service: ^0.18.0;
  • audio_session: ^0.1.6+1

Example:


    import 'package:audio_service/audio_service.dart';
    import 'package:audio_session/audio_session.dart';
    import 'audio_player_service.dart';
    
    Future initAudioService() async {
      //audio_session INSTANCE
      final session = await AudioSession.instance;
      //audio_session DUCK OTHERS CONFIGURATION
      await session.configure(const AudioSessionConfiguration(
        avAudioSessionCategory: AVAudioSessionCategory.playback,
        avAudioSessionCategoryOptions: AVAudioSessionCategoryOptions.duckOthers,
        avAudioSessionMode: AVAudioSessionMode.defaultMode,
        avAudioSessionRouteSharingPolicy:
            AVAudioSessionRouteSharingPolicy.defaultPolicy,
        avAudioSessionSetActiveOptions: AVAudioSessionSetActiveOptions.none,
        androidAudioAttributes: AndroidAudioAttributes(
          contentType: AndroidAudioContentType.music,
          flags: AndroidAudioFlags.none,
          usage: AndroidAudioUsage.media,
        ),
        androidAudioFocusGainType: AndroidAudioFocusGainType.gainTransientMayDuck,
        androidWillPauseWhenDucked: true,
      ));
    //INITIALIZE audio_service
      return await AudioService.init(
        builder: () => AudioPlayerService(),
        config: const AudioServiceConfig(
          androidNotificationChannelId: 'com.YOUR_COMPANY.YOUR_APP_NAME.audio',
          androidNotificationChannelName: 'Audio Service Demo',
          androidNotificationOngoing: true,
          androidStopForegroundOnPause: true,
        ),
      );
    }
    
    //MAIN
    void main() async {
      await initAudioService();
      runApp(const MaterialApp(home: MyApp()));
    }

0
Shahan Ahmed On

Here is what I did:

packages:

  1. audio_session
  2. just_audio
  3. audio_service

In main.dart:

Define this method anywhere, please keep an eye on the

Future initAudioService() async {
  //INITIALIZE audio_service
  return

    audioHandler = await audio_service.AudioService.init(
   ///This will be the custom Audio Service we use
    builder: () => MyCustomAudioService(), 
    config: const audio_service.AudioServiceConfig(
      androidNotificationChannelId: 'com.optimized.strength.audio',
      androidNotificationChannelName: 'Audio Service',
      androidNotificationOngoing: true,
      androidStopForegroundOnPause: true,
    ),
  );
}

Then for the Custom Audio Service We defined it below:

NOTE: I didn't need to use the broadcasting or any other function except the play sound, so you can directly copy paste the below part and tweak/adjust it as you please.

class MyCustomAudioService extends BaseAudioHandler with SeekHandler {
  final AudioPlayer _player = AudioPlayer();

  MyCustomAudioService() {
    _init();
  }

  Future<void> _init() async {
    try {
      // Load the audio from assets
      await _player.setAsset('assets/sounds/tick_sound.mp3');
      // Listen to changes in player state and update the UI accordingly
      _player.playerStateStream.listen((playerState) {
        final playing = playerState.playing;
        final processingState = playerState.processingState;
        if (processingState == ProcessingState.completed) {
          // Handle completion of audio playback
          stop(); // Or any other logic you want to implement on completion
        } else if (playing) {
          _broadcastState(playing: true);
        } else {
          _broadcastState(playing: false);
        }
      }, onError: (Object e, StackTrace stackTrace) {
        print("Error listening to playerStateStream: $e");
        _broadcastState(playing: false, error: e.toString());
      });
    } catch (e) {
      print("Error initializing player: $e");
      _broadcastState(playing: false, error: e.toString());
    }
  }

  void _broadcastState({bool playing = false, String? error}) {
    final state = AudioProcessingState.idle;
    playbackState.add(playbackState.value.copyWith(
      playing: playing,
      processingState: error == null ? state : AudioProcessingState.error,
      // error: error,
    ));
  }
  
  @override
  Future<void> play() async {
    try {
      final _session = await audio_session.AudioSession.instance;
      //audio_session DUCK OTHERS CONFIGURATION
      await _session.configure(const audio_session.AudioSessionConfiguration(
        avAudioSessionCategory: audio_session.AVAudioSessionCategory.playback,
        avAudioSessionCategoryOptions:
        audio_session.AVAudioSessionCategoryOptions.duckOthers,
        avAudioSessionMode: audio_session.AVAudioSessionMode.defaultMode,
        avAudioSessionRouteSharingPolicy:
        audio_session.AVAudioSessionRouteSharingPolicy.defaultPolicy,
        avAudioSessionSetActiveOptions:
        audio_session.AVAudioSessionSetActiveOptions.none,
        androidAudioAttributes: audio_session.AndroidAudioAttributes(
          contentType: audio_session.AndroidAudioContentType.music,
          flags: audio_session.AndroidAudioFlags.none,
          usage: audio_session.AndroidAudioUsage.media,
        ),
        androidAudioFocusGainType:
        audio_session.AndroidAudioFocusGainType.gainTransientMayDuck,
        androidWillPauseWhenDucked: true,
      ));
      _session.setActive(true);
      print('played inside the service');
      // Set the source every time play is called to ensure it can be replayed
      await _player.setAsset('assets/sounds/tick_sound.mp3');
      await _player.play();
      _session.setActive(false);
    } catch (e) {
      print("Error playing audio: $e");
    }
  }

  @override
  Future<void> pause() async {
    _player.pause();
  }

  @override
  Future<void> seek(Duration position) async {
    _player.seek(position);
  }

  @override
  Future<void> stop() async {
    await _player.stop();
    await _player.dispose();
    super.stop();
  }
}

Finally in the UI

InkWell(
      onTap: ()  {
 MyCustomAudioService().play();

  }