flame icon indicating copy to clipboard operation
flame copied to clipboard

Audio issue

Open RawadZogheib opened this issue 1 year ago • 12 comments

What happened?

When I remove the audio the game a fast and all is functioning well, but when I add the audio I have 2 issue:

  1. If I'm in a WhatsApp call the game have a bad functionality (this issue is not accruing if I remove the audio plugin)
  2. I we play 5 round or more the game start lagging and having low FPS (this issue is not accruing if I remove the audio plugin)

I think there is an issue with the dispose in this plugin after checking the code!

What do you expect?

a solution for the bug? :')

How can we reproduce this?

I guess if there is a good dispose for the audio after it's played the issue should be solved because this issue isn't available on AudioPlayer!

What steps should take to fix this?

.

Do have an example of where the bug occurs?

no

Relevant log output

There is no exception!

Execute in a terminal and put output into the code block below

flutter doctor -v [✓] Flutter (Channel stable, 3.24.4, on macOS 13.7.1 22H221 darwin-x64, locale en-LB) • Flutter version 3.24.4 on channel stable at /Users/rawadzogheib/Documents/flutter • Upstream repository https://github.com/flutter/flutter.git • Framework revision 603104015d (3 weeks ago), 2024-10-24 08:01:25 -0700 • Engine revision db49896cf2 • Dart version 3.5.4 • DevTools version 2.37.3

[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0) • Android SDK at /Users/rawadzogheib/Library/Android/sdk • Platform android-34, build-tools 34.0.0 • Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 21.0.3+-79915915-b509.11) • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 15.2) • Xcode at /Applications/Xcode.app/Contents/Developer • Build 15C500b • CocoaPods version 1.15.2

[✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2024.2) • Android Studio at /Applications/Android Studio.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart • Java version OpenJDK Runtime Environment (build 21.0.3+-79915915-b509.11)

[✓] VS Code (version 1.94.2) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.98.0

[✓] Connected device (2 available)
• macOS (desktop) • macos • darwin-x64 • macOS 13.7.1 22H221 darwin-x64 • Chrome (web) • chrome • web-javascript • Google Chrome 131.0.6778.69

[✓] Network resources • All expected network resources are available.

• No issues found!

Affected platforms

Android, iOS

Other information

import 'dart:math';

import 'package:flame_audio/flame_audio.dart';
import 'package:kung_fu_cat/locale/get_storage_helper.dart';
import 'package:kung_fu_cat/remote/firebase/crud_firestore.dart';

class AudioHelper {
  static final AudioHelper _singleton = AudioHelper._internal();

  factory AudioHelper() {
    return _singleton;
  }

  AudioHelper._internal() {
    _musicVolume = GetStorageHelper().getMusicVolume();
    _gameVolume = GetStorageHelper().getGameVolume();
  }

  double _musicVolume = 1.0;
  double _gameVolume = 1.0;

  double get musicVolume => _musicVolume;

  double get gameVolume => _gameVolume;

  set musicVolume(double value) {
    _musicVolume = value;
    GetStorageHelper().setMusicVolume(musicVolume: _musicVolume);
  }

  set gameVolume(double value) {
    _gameVolume = value;
    GetStorageHelper().setGameVolume(gameVolume: _gameVolume);
  }

  AudioPool? clickSound;
  AudioPool? slicingSound0;
  AudioPool? slicingSound1;
  AudioPool? slicingSound2;
  AudioPool? slicingSound3;
  AudioPool? coinThrowSoundSound;
  AudioPool? bombThrowSoundSound;
  AudioPool? gameOverBombSound;
  AudioPool? chooseGameSwipe;

  Future<void> initAudio() async {
    try {
      FlameAudio.bgm.initialize();
      await FlameAudio.audioCache.loadAll([
        'background_music/background_song.mp3',
        'button_sound/button_click_sound.mp3',
        'slicing_sound/slicing_sound0.mp3',
        'slicing_sound/slicing_sound1.mp3',
        'slicing_sound/slicing_sound2.mp3',
        'slicing_sound/slicing_sound3.mp3',
        'coin_sound/coin_throw_sound.mp3',
        'bomb_sound/bomb_throw_sound.mp3',
        'loading_game/countdown_one.mp3',
        'loading_game/countdown_two.mp3',
        'loading_game/countdown_three.mp3',
        'loading_game/countdown_go.mp3',
        'loading_game/countdown_end_sound.mp3',
        'golden_cat_sound/golden_cat_throw_sound.mp3',
        'golden_cat_sound/thunder_sound.mp3',
        'background_music/golden_cat_song.mp3',
        'game_view/game_over_via_bomb_sound.mp3',
        'game_view/game_over_via_fruit_sound.mp3',
        'game_view/next_level_sound.mp3',
        'choose_game_view/choose_game_swipe.mp3',
        'waiting_room/opponent_found_sound.mp3',
        'waiting_room/prize_pool_sound.mp3',
        'waiting_room/start_multiplayer_game_sound.mp3',
        'waiting_room/waiting_opponent_sound.mp3',
      ]);
      await iniAudioPool();
    } catch (e) {
      _exception(
          exception: e.toString(), errorDescription: "On Audio First Init");
    }
  }

  Future<void> playBackgroundMusic() async {
    if (FlameAudio.bgm.isPlaying) {
      try {
        await FlameAudio.bgm.stop();
      } catch (e) {
        _exception(
            exception: e.toString(),
            errorDescription: "On Audio Stop Background Music");
      }
    }
    try {
      await FlameAudio.bgm
          .play('background_music/background_song.mp3', volume: _musicVolume);
    } catch (e) {
      _exception(
          exception: e.toString(),
          errorDescription: "On Audio Play Background Music");
    }
  }

  Future<void> playWaitingOpponentBackgroundMusic() async {
    if (FlameAudio.bgm.isPlaying) {
      try {
        await FlameAudio.bgm.stop();
      } catch (e) {
        _exception(
            exception: e.toString(),
            errorDescription: "On Audio Stop WaitingRoom Background Music");
      }
    }
    try {
      await FlameAudio.bgm
          .play('waiting_room/waiting_opponent_sound.mp3', volume: _gameVolume);
    } catch (e) {
      _exception(
          exception: e.toString(),
          errorDescription: "On Audio Play WaitingRoom Background Music");
    }
  }

  Future<void> playGoldenCatThrowMusic() async {
    if (FlameAudio.bgm.isPlaying) {
      try {
        await FlameAudio.bgm.stop();
      } catch (e) {
        _exception(
            exception: e.toString(),
            errorDescription: "On Audio Stop Golden Cat Background Music1");
      }
    }
    try {
      await FlameAudio.play('golden_cat_sound/golden_cat_throw_sound.mp3',
          volume: _gameVolume);
      await FlameAudio.bgm
          .play('background_music/golden_cat_song.mp3', volume: _musicVolume);
    } catch (e) {
      _exception(
          exception: e.toString(),
          errorDescription: "On Audio Play Golden Cat Background Music1");
    }
  }

  Future<void> playGoldenCatBackgroundMusic() async {
    if (FlameAudio.bgm.isPlaying) {
      try {
        await FlameAudio.bgm.stop();
      } catch (e) {
        _exception(
            exception: e.toString(),
            errorDescription: "On Audio Stop Golden Cat Background Music2");
      }
    }
    try {
      await FlameAudio.bgm
          .play('background_music/golden_cat_song.mp3', volume: _musicVolume);
    } catch (e) {
      _exception(
          exception: e.toString(),
          errorDescription: "On Audio Play Golden Cat Background Music2");
    }
  }

  Future<void> pauseBackgroundMusic() async {
    try {
      if (FlameAudio.bgm.isPlaying) {
        await FlameAudio.bgm.pause();
      }
    } catch (e) {
      _exception(
          exception: e.toString(),
          errorDescription: "On Audio Pause Background Music");
    }
  }

  Future<void> stopBackgroundMusic() async {
    try {
      await FlameAudio.bgm.stop();
    } catch (e) {
      _exception(
          exception: e.toString(),
          errorDescription: "On Audio Stop Background Music");
    }
  }

  Future<void> resumeBackgroundMusic() async {
    try {
      await FlameAudio.bgm.resume();
    } catch (e) {
      _exception(
          exception: e.toString(),
          errorDescription: "On Audio Resume Background Music");
    }
  }

  Future<void> playChooseGameSwipeSound() async {
    try {
      await chooseGameSwipe!.start(volume: _gameVolume);
    } catch (e) {
      _exception(
          exception: e.toString(),
          errorDescription: "On Audio Game Choose Swipe");
    }
  }

  Future<void> playOpponentFoundSound() async {
    try {
      await FlameAudio.play('waiting_room/opponent_found_sound.mp3',
          volume: _gameVolume);
    } catch (e) {
      _exception(
          exception: e.toString(),
          errorDescription: "On Audio Play Opponent Found");
    }
  }

  Future<void> playPrizePoolSound() async {
    try {
      await FlameAudio.play('waiting_room/prize_pool_sound.mp3',
          volume: _gameVolume);
    } catch (e) {
      _exception(
          exception: e.toString(),
          errorDescription: "On Audio Play Prize Pool");
    }
  }

  Future<void> playStartMultiplayerGameSound() async {
    try {
      await FlameAudio.play('waiting_room/start_multiplayer_game_sound.mp3',
          volume: _gameVolume);
    } catch (e) {
      _exception(
          exception: e.toString(),
          errorDescription: "On Audio Play Game Multiplayer");
    }
  }

  Future<void> playCoinThrowSound() async {
    try {
      await coinThrowSoundSound!.start(volume: _gameVolume);
    } catch (e) {
      _exception(
          exception: e.toString(), errorDescription: "On Audio Play Coin");
    }
  }

  Future<void> playBombSound() async {
    try {
      await bombThrowSoundSound!.start(volume: _gameVolume);
    } catch (e) {
      _exception(
          exception: e.toString(), errorDescription: "On Audio Play Bomb");
    }
  }

  Future<void> playSelectSwordSound() async {
    try {
      await FlameAudio.play('locker_view/select_sword_sound.mp3',
          volume: _gameVolume);
    } catch (e) {
      _exception(
          exception: e.toString(),
          errorDescription: "On Audio Play Select Sword");
    }
  }

  Future<void> playSelectCatSound() async {
    try {
      await FlameAudio.play('locker_view/select_cat_sound.mp3',
          volume: _gameVolume);
    } catch (e) {
      _exception(
          exception: e.toString(),
          errorDescription: "On Audio Play Select Cat");
    }
  }

  Future<void> playGameOverBombSound() async {
    try {
      await gameOverBombSound!.start(volume: _gameVolume);
    } catch (e) {
      _exception(
          exception: e.toString(),
          errorDescription: "On Audio Play GameOver Bomb");
    }
  }

  Future<void> playGameOverFruitSound() async {
    try {
      await FlameAudio.play('game_view/game_over_via_fruit_sound.mp3',
          volume: _gameVolume);
    } catch (e) {
      _exception(
          exception: e.toString(),
          errorDescription: "On Audio Play GameOver Fruit");
    }
  }

  Future<void> playNextLevelSound() async {
    try {
      await FlameAudio.play('game_view/next_level_sound.mp3',
          volume: _gameVolume);
    } catch (e) {
      _exception(
          exception: e.toString(),
          errorDescription: "On Audio Play Next Level");
    }
  }

  Future<void> playClickSound() async {
    try {
      await clickSound!.start(volume: _gameVolume);
    } catch (e) {
      _exception(
          exception: e.toString(), errorDescription: "On Audio Play Click");
    }
  }

  /// Loading Game
  Future<void> playCountDownOneSound() async {
    try {
      await FlameAudio.play('loading_game/countdown_one.mp3',
          volume: _gameVolume);
    } catch (e) {
      _exception(
          exception: e.toString(),
          errorDescription: "On Audio Play Count Down One");
    }
  }

  Future<void> playCountDownTwoSound() async {
    try {
      await FlameAudio.play('loading_game/countdown_two.mp3',
          volume: _gameVolume);
    } catch (e) {
      _exception(
          exception: e.toString(),
          errorDescription: "On Audio Play Count Down Two");
    }
  }

  Future<void> playCountDownThreeSound() async {
    try {
      await FlameAudio.play('loading_game/countdown_three.mp3',
          volume: _gameVolume);
    } catch (e) {
      _exception(
          exception: e.toString(),
          errorDescription: "On Audio Play Count Down Thre");
    }
  }

  Future<void> playCountDownGoSound() async {
    try {
      await FlameAudio.play('loading_game/countdown_go.mp3',
          volume: _gameVolume);
    } catch (e) {
      _exception(
          exception: e.toString(),
          errorDescription: "On Audio Play Count Down Go");
    }
  }

  Future<void> playCountDownEndSoundSound() async {
    try {
      await FlameAudio.play('loading_game/countdown_end_sound.mp3',
          volume: _gameVolume);
    } catch (e) {
      _exception(
          exception: e.toString(),
          errorDescription: "On Audio Play Count Down End");
    }
  }

  Future<void> playThunderSound() async {
    try {
      await FlameAudio.play('golden_cat_sound/thunder_sound.mp3',
          volume: _gameVolume);
    } catch (e) {
      _exception(
          exception: e.toString(), errorDescription: "On Audio Play Thunder");
    }
  }

  Future<void> playRandomSlicingSound() async {
    try {
      final int rand = Random().nextInt(3);
      if (rand == 0) await slicingSound0!.start(volume: _gameVolume);
      if (rand == 1) await slicingSound1!.start(volume: _gameVolume);
      if (rand == 2) await slicingSound2!.start(volume: _gameVolume);
      if (rand == 3) await slicingSound3!.start(volume: _gameVolume);
    } catch (e) {
      _exception(
          exception: e.toString(),
          errorDescription: "On Audio Play Random Slicing");
    }
  }

  Future<void> iniAudioPool() async {
    await disposeAudioPool();
    try {
      clickSound = await FlameAudio.createPool(
          'button_sound/button_click_sound.mp3',
          maxPlayers: 5);
      chooseGameSwipe = await FlameAudio.createPool(
          'choose_game_view/choose_game_swipe.mp3',
          maxPlayers: 10);
    } catch (e) {
      _exception(exception: e.toString(), errorDescription: "On Audio Init");
    }
    await iniGameAudioPool();
  }

  Future<void> iniGameAudioPool() async {
    await disposeGameAudioPool();
    try {
      slicingSound0 = await FlameAudio.createPool(
          'slicing_sound/slicing_sound0.mp3',
          maxPlayers: 5);
      slicingSound1 = await FlameAudio.createPool(
          'slicing_sound/slicing_sound1.mp3',
          maxPlayers: 5);
      slicingSound2 = await FlameAudio.createPool(
          'slicing_sound/slicing_sound2.mp3',
          maxPlayers: 5);
      slicingSound3 = await FlameAudio.createPool(
          'slicing_sound/slicing_sound3.mp3',
          maxPlayers: 5);
      coinThrowSoundSound = await FlameAudio.createPool(
          'coin_sound/coin_throw_sound.mp3',
          maxPlayers: 5);
      bombThrowSoundSound = await FlameAudio.createPool(
          'bomb_sound/bomb_throw_sound.mp3',
          maxPlayers: 5);
      gameOverBombSound = await FlameAudio.createPool(
          'game_view/game_over_via_bomb_sound.mp3',
          maxPlayers: 5);
    } catch (e) {
      _exception(
          exception: e.toString(), errorDescription: "On Audio Game Init");
    }
  }

  Future<void> disposeAudioPool() async {
    try {
      if (clickSound != null) await clickSound!.dispose();
      if (chooseGameSwipe != null) await chooseGameSwipe!.dispose();
      clickSound = null;
      chooseGameSwipe = null;
    } catch (e) {
      _exception(exception: e.toString(), errorDescription: "On Audio Dispose");
    }
  }

  Future<void> disposeGameAudioPool() async {
    try {
      if (slicingSound0 != null) await slicingSound0!.dispose();
      if (slicingSound1 != null) await slicingSound1!.dispose();
      if (slicingSound2 != null) await slicingSound2!.dispose();
      if (slicingSound3 != null) await slicingSound3!.dispose();
      if (coinThrowSoundSound != null) await coinThrowSoundSound!.dispose();
      if (bombThrowSoundSound != null) await bombThrowSoundSound!.dispose();
      if (gameOverBombSound != null) await gameOverBombSound!.dispose();
      slicingSound0 = null;
      slicingSound1 = null;
      slicingSound2 = null;
      slicingSound3 = null;
      coinThrowSoundSound = null;
      bombThrowSoundSound = null;
      gameOverBombSound = null;
    } catch (e) {
      _exception(
          exception: e.toString(), errorDescription: "On Audio Game Dispose");
    }
  }

  void _exception(
      {required String exception, required String errorDescription}) {
    final CRUDFirestore crudFirestore = CRUDFirestore();
    crudFirestore.exception(
        errorCode: "44974",
        exception: exception,
        showSelectedGameId: true,
        errorDescription: errorDescription);
  }
}

Are you interested in working on a PR for this?

  • [ ] I want to work on this

RawadZogheib avatar Nov 16 '24 09:11 RawadZogheib

Can you check in the Flutter DevTools if you're having more audioplayers instances than you expect? Other than that I would try to only play background sounds to see if it is the pools that are the problem, or the other way around.

spydon avatar Nov 16 '24 10:11 spydon

Hello, @spydon, thank you for responding,

DevTools I can see all the flame Component but I can't see the AudioPool.

But I think "FlameAudio.bgm.initialize();" is called only once on the begin of the game and never disposed, because I always have background music in the game, and it's initialized in the First App load after signing.

I only use bgm.play(); and bam.stop() to switch background music?

It can be the issue?

RawadZogheib avatar Nov 16 '24 11:11 RawadZogheib

I also experience a similar issue. This might work as a reproducible code but there is not going a lot here:

import 'package:flame/game.dart';
import 'package:flutter/material.dart';
import 'dart:async';

import 'package:flame/components.dart';
import 'package:flame_audio/flame_audio.dart';

void main() {
  runApp(GameWidget(game: MyGame()));
}

class MyGame extends FlameGame {
  @override
  FutureOr<void> onLoad() {
    world.add(FpsComponent());
    world.add(FpsTextComponent());
    add(
      TimerComponent(
        period: .05,
        onTick: () {
          FlameAudio.play('audio.mp3');
        },
        repeat: true,
      ),
    );
  }
}

After an audio is played, I'm expecting that instance to be disposed but it doesn't seem like they are getting disposed and just piling up. The audio that is being played is only around .2 second long. Why do I have these many instances?

output

Also, I had another project where I had some components. While playing the audio over time, the fps was dropping to 5-6 fps, eventually crashing the game. I'm not sure how is this supposed to work so I don't know if this is even a bug. Are we supposed to dispose manually?

dipanshparmar avatar Dec 06 '24 12:12 dipanshparmar

Use the AudioPool class for such cases. Or the flutter_soloud package.

spydon avatar Dec 06 '24 14:12 spydon

So this is the correct behavior while playing the audio with FlameAudio directly?

dipanshparmar avatar Dec 06 '24 15:12 dipanshparmar

So this is the correct behavior while playing the audio with FlameAudio directly?

The play method gives back a reusable AudioPlayer, so the user has to call dispose on that one when they don't want to use it anymore.

spydon avatar Dec 06 '24 15:12 spydon

The play method gives back a reusable AudioPlayer, so the user has to call dispose on that one when they don't want to use it anymore.

That makes sense! Thank you.

dipanshparmar avatar Dec 06 '24 15:12 dipanshparmar

I'm running into the same issue. Let's say there are about 10 different sounds used in the game—do we need to create a separate AudioPool for each one?

It makes sense to use an AudioPool for sounds that are triggered frequently and require low latency. But with the current version of flame_audio, it seems even sounds that are played infrequently can lead to memory issues, because the underlying AudioPlayer instance is never disposed.

I'm also a bit unclear on how disposal is supposed to work. I tried manually disposing the player after the sound finishes, but it seems that none of the usual callbacks—like onPlayerComplete, onPlayerStateChanged, or eventStream—ever fire. So I can't find a reliable way to know when the sound is done playing in order to dispose the player.

Future<void> playSound(Sound sound) async {
    try {
     log("a ${sound.assetPath}");
      final player = await FlameAudio.play(sound.assetPath,);

     player.onPlayerStateChanged.listen((state) {
       log('Player state changed: $state');
     });

     player.eventStream.listen((event) {
       log('Player event: ${event.eventType}');
     });

     player.onPlayerComplete.listen((_){
        log("c ${sound.assetPath}");
        player.dispose();
      });
    } catch (e) {
      log("Failed to play sound: $e");
    }
`

Im using GALAXY A53 5G - SM-A5360

MaxmaxB avatar May 26 '25 10:05 MaxmaxB

@MaxmaxB I think the problem is that android doesn't support events in low latency mode (it should work on the other platforms). I'm not sure how to be able to know when it is safe to dispose the player on android actually.

spydon avatar May 26 '25 12:05 spydon

@spydon Thanks, yeah I'm kinda of stuck here not sure how to solve this problem. And yes as you mention it works perfectly fine on iOS. Do you know how game made with flame engine usually handle the audio on Android?

MaxmaxB avatar May 27 '25 00:05 MaxmaxB

@MaxmaxB if you don't need audio mixing with other sounds (like the users own music), I would recommend flutter_soloud, it is extremely efficient.

spydon avatar May 27 '25 06:05 spydon

Thanks @spydon will try it out

MaxmaxB avatar May 27 '25 11:05 MaxmaxB