Is there a way to update audio source of AudioPlayer so it can play different songs (Flutter)?

84 views Asked by At

I have an application with 100 numbered buttons in GridView. I would like to play corresponding song (named f. e. '87.mp3') after pressing one of the buttons (in this case button 87). The beginning of my AudioPlayer code looks like this:

class AudioPlayer extends StatefulWidget {
  final String audioPath;

  const AudioPlayer({super.key, required this.audioPath});

  @override
  _AudioPlayerState createState() => _AudioPlayerState();
}

class _NewCustomWidgetState extends State<AudioPlayer>
    with WidgetsBindingObserver {
  final _player = AudioPlayer();

  @override
  void initState() {
    super.initState();
    ambiguate(WidgetsBinding.instance)!.addObserver(this);
    _init();
  }

  Future<void> _init() async {
    final session = await AudioSession.instance;
    await session.configure(const AudioSessionConfiguration.speech());
    _player.playbackEventStream.listen((event) {},
        onError: (Object e, StackTrace stackTrace) {
      print('A stream error occurred: $e');
    });
    try {
      await _player.setAsset(widget.audioPath);
      print("Audio asset is set.");
    } catch (e) {
      print("Error loading audio source: $e");
    }
  }

  @override
  void dispose() {
    ambiguate(WidgetsBinding.instance)!.removeObserver(this);
    _player.dispose();
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.paused) {
      _player.stop();
    }
  }

  Stream<PositionData> get _positionDataStream =>
      Rx.combineLatest3<Duration, Duration, Duration?, PositionData>(
          _player.positionStream,
          _player.bufferedPositionStream,
          _player.durationStream,
          (position, bufferedPosition, duration) => PositionData(
              position, bufferedPosition, duration ?? Duration.zero));

  @override
  Widget build(BuildContext context) {...}}

AudioPlayer takes the audioPath as an argument. It notices that audioPath changes, but doesn't update itself, so it plays the same song what was set on the initialization.

2

There are 2 answers

0
korchix On BEST ANSWER

let's assume your GridView looks like this:

GridView.builder(
        itemCount: 100, // numbers of buttons
        ...
        itemBuilder: (_, int index) {
          return InkWell(
            onTap: () => AudioPlayer(audioPath:pathToAudio), // here you call your AudioPlayer
            child: GridTile(
                child: Container(
                ...
                )
              ),
          );
        });

what you have to do, is that you have to add all your audioPath to an array. and when a button is pressed on the grid, you access the corresponding audioPath using the index of the Grid button. for instance, you taped button 87, the index of that button is provided by the Grid, which it's 86 in the array. so what you have to do is, instead of :

onTap: () => AudioPlayer(audioPath: pathToAudio)

do this:

onTap: () => AudioPlayer(audioPath: arrayOfAudioAssets[index]) // you received the index here: itemBuilder: (_, int index)

2
Mosab Alrasheed On

Try this after updating the state, and don't worry since your code deals with audioplayer as a singlton, you are set to go

@override
void didUpdateWidget(covariant AudioPlayer oldWidget) {
  super.didUpdateWidget(oldWidget);
  
  // Check if the audioPath has changed
  if (oldWidget.audioPath != widget.audioPath) {
    // The audioPath has changed. We need to update the audio player.
    _init();
  }
}