Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: Update flame_audio to AP 1.0.0 #1724

Merged
merged 11 commits into from
Jun 14, 2022
Merged
1 change: 1 addition & 0 deletions packages/flame/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class MyGame extends FlameGame with HasTappables {
@override
void onTapUp(int id, TapUpInfo info) {
super.onTapUp(id, info);

luanpotter marked this conversation as resolved.
Show resolved Hide resolved
if (!info.handled) {
final touchPoint = info.eventPosition.game;
add(Square(touchPoint));
Expand Down
8 changes: 6 additions & 2 deletions packages/flame_audio/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ class AudioGame extends FlameGame with TapDetector {

@override
Future<void> onLoad() async {
pool = await AudioPool.create('fire_2.mp3', minPlayers: 3, maxPlayers: 4);
pool = await FlameAudio.createPool(
'sfx/fire_2.mp3',
luanpotter marked this conversation as resolved.
Show resolved Hide resolved
minPlayers: 3,
maxPlayers: 4,
);
startBgmMusic();
}

Expand All @@ -41,7 +45,7 @@ class AudioGame extends FlameGame with TapDetector {
}

void fireOne() {
FlameAudio.audioCache.play('sfx/fire_1.mp3');
FlameAudio.play('sfx/fire_1.mp3');
}

void fireTwo() {
Expand Down
48 changes: 17 additions & 31 deletions packages/flame_audio/lib/audio_pool.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,54 +6,47 @@ import 'package:synchronized/synchronized.dart';
/// Represents a function that can stop an audio playing.
typedef Stoppable = void Function();

/// An AudioPool is a provider of AudioPlayers that leaves them pre-loaded to
/// minimize delays.
/// An AudioPool is a provider of AudioPlayers that leaves them pre-loaded with
/// local assets to minimize delays.
luanpotter marked this conversation as resolved.
Show resolved Hide resolved
///
/// All AudioPlayers loaded are for the same [sound]. If you want multiple
/// sounds use multiple [AudioPool].
/// Use this class if you'd like have extremely quick firing, repetitive and
/// simultaneous sounds, like shooting a laser in a fast-paced spaceship game.
class AudioPool {
final AudioCache _cache;
final Map<String, AudioPlayer> _currentPlayers = {};
final List<AudioPlayer> _availablePlayers = [];

/// Instance of [AudioCache] to be used by all players.
final AudioCache audioCache;

/// The path of the sound of this pool.
final String sound;

/// If the pool is repeating.
final bool repeating;

/// Max and min numbers of players.
final int minPlayers, maxPlayers;

final Lock _lock = Lock();

AudioPool._(
this.sound, {
bool? repeating,
int? maxPlayers,
int? minPlayers = 1,
String? prefix,
}) : _cache = AudioCache(prefix: prefix ?? 'assets/audio/sfx/'),
repeating = repeating ?? false,
maxPlayers = maxPlayers ?? 1,
minPlayers = minPlayers ?? 1;
required this.minPlayers,
required this.maxPlayers,
AudioCache? audioCache,
}) : audioCache = audioCache ?? AudioCache.instance;

/// Creates an [AudioPool] instance with the given parameters.
static Future<AudioPool> create(
String sound, {
bool? repeating,
int? maxPlayers,
int? minPlayers = 1,
String? prefix,
AudioCache? audioCache,
int minPlayers = 1,
required int maxPlayers,
}) async {
final instance = AudioPool._(
sound,
repeating: repeating,
audioCache: audioCache,
maxPlayers: maxPlayers,
minPlayers: minPlayers,
prefix: prefix,
);
for (var i = 0; i < instance.minPlayers; i++) {
instance._availablePlayers.add(await instance._createNewAudioPlayer());
Expand Down Expand Up @@ -90,23 +83,16 @@ class AudioPool {
});
}

subscription = player.onPlayerCompletion.listen((_) {
if (repeating) {
player.resume();
} else {
stop();
}
});
subscription = player.onPlayerComplete.listen((_) => stop());

return stop;
});
}

Future<AudioPlayer> _createNewAudioPlayer() async {
final player = AudioPlayer();
final url = (await _cache.load(sound)).path;
await player.setUrl(url);
await player.setReleaseMode(ReleaseMode.STOP);
final player = AudioPlayer()..audioCache = audioCache;
await player.setSource(AssetSource(sound));
await player.setReleaseMode(ReleaseMode.stop);
return player;
}
}
72 changes: 20 additions & 52 deletions packages/flame_audio/lib/bgm.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,16 @@ import 'package:flutter/widgets.dart';
class Bgm extends WidgetsBindingObserver {
bool _isRegistered = false;

/// [AudioCache] instance of the [Bgm].
late AudioCache audioCache;

/// The [AudioPlayer] instance that is currently playing the audio.
AudioPlayer? audioPlayer;
/// The [AudioPlayer] instance that is used to play the audio.
AudioPlayer audioPlayer;

/// Whether [Bgm] is playing or not.
bool isPlaying = false;

/// {@macro _bgm}
Bgm({AudioCache? audioCache}) : audioCache = audioCache ?? AudioCache();
Bgm({AudioCache? audioCache})
: audioPlayer = AudioPlayer()
..audioCache = audioCache ?? AudioCache.instance;

/// Registers a [WidgetsBinding] observer.
///
Expand All @@ -42,6 +41,7 @@ class Bgm extends WidgetsBindingObserver {

/// Dispose the [WidgetsBinding] observer.
void dispose() {
audioPlayer.dispose();
if (!_isRegistered) {
return;
}
Expand All @@ -56,66 +56,34 @@ class Bgm extends WidgetsBindingObserver {
///
/// It is safe to call this function even when a current BGM track is
/// playing.
Future<void> play(String filename, {double volume = 1}) async {
final currentPlayer = audioPlayer;
if (currentPlayer != null && currentPlayer.state != PlayerState.STOPPED) {
currentPlayer.stop();
}

Future<void> play(String fileName, {double volume = 1}) async {
await audioPlayer.dispose();
await audioPlayer.setReleaseMode(ReleaseMode.loop);
await audioPlayer.setVolume(volume);
await audioPlayer.setSource(AssetSource(fileName));
await audioPlayer.resume();
isPlaying = true;
audioPlayer = await audioCache.loop(filename, volume: volume);
}

/// Stops the currently playing background music track (if any).
Future<void> stop() async {
isPlaying = false;
if (audioPlayer != null) {
await audioPlayer!.stop();
}
await audioPlayer.stop();
}

/// Resumes the currently played (but resumed) background music.
Future<void> resume() async {
if (audioPlayer != null) {
isPlaying = true;
await audioPlayer!.resume();
}
isPlaying = true;
await audioPlayer.resume();
}

/// Pauses the background music without unloading or resetting the audio
/// player.
Future<void> pause() async {
if (audioPlayer != null) {
isPlaying = false;
await audioPlayer!.pause();
}
isPlaying = false;
await audioPlayer.pause();
}

/// Pre-fetch an audio and store it in the cache.
///
/// Alias of `audioCache.load();`.
Future<Uri> load(String file) => audioCache.load(file);

/// Pre-fetch an audio and store it in the cache.
///
/// Alias of `audioCache.loadAsFile();`.
Future<File> loadAsFile(String file) => audioCache.loadAsFile(file);

/// Pre-fetch a list of audios and store them in the cache.
///
/// Alias of `audioCache.loadAll();`.
Future<List<Uri>> loadAll(List<String> files) => audioCache.loadAll(files);

/// Clears the file in the cache.
///
/// Alias of `audioCache.clear();`.
void clear(Uri file) => audioCache.clear(file);

/// Clears all the audios in the cache.
///
/// Alias of `audioCache.clearAll();`.
void clearAll() => audioCache.clearAll();

/// Handler for AppLifecycleState changes.
///
/// This function handles the automatic pause and resume when the app
Expand All @@ -124,11 +92,11 @@ class Bgm extends WidgetsBindingObserver {
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
if (isPlaying && audioPlayer?.state == PlayerState.PAUSED) {
audioPlayer?.resume();
if (isPlaying && audioPlayer.state == PlayerState.paused) {
audioPlayer.resume();
}
} else {
audioPlayer?.pause();
audioPlayer.pause();
}
}
}
Expand Down
63 changes: 57 additions & 6 deletions packages/flame_audio/lib/flame_audio.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:audioplayers/audioplayers.dart';
import 'package:flame_audio/audio_pool.dart';

import 'package:flame_audio/bgm.dart';

Expand All @@ -10,20 +11,51 @@ class FlameAudio {
/// Access a shared instance of the [AudioCache] class.
static AudioCache audioCache = AudioCache(prefix: 'assets/audio/');

static Future<AudioPlayer> _preparePlayer(
String file,
double volume,
ReleaseMode releaseMode,
PlayerMode playerMode,
) async {
final player = AudioPlayer()..audioCache = audioCache;
await player.setReleaseMode(releaseMode);
await player.play(
AssetSource(file),
volume: volume,
mode: playerMode,
);
return player;
}

/// Plays a single run of the given [file], with a given [volume].
static Future<AudioPlayer> play(String file, {double volume = 1.0}) {
return audioCache.play(file, volume: volume, mode: PlayerMode.LOW_LATENCY);
static Future<AudioPlayer> play(String file, {double volume = 1.0}) async {
return _preparePlayer(
file,
volume,
ReleaseMode.release,
PlayerMode.lowLatency,
);
}

/// Plays, and keeps looping the given [file].
static Future<AudioPlayer> loop(String file, {double volume = 1.0}) {
return audioCache.loop(file, volume: volume, mode: PlayerMode.LOW_LATENCY);
static Future<AudioPlayer> loop(String file, {double volume = 1.0}) async {
return _preparePlayer(
file,
volume,
ReleaseMode.loop,
PlayerMode.lowLatency,
);
}

/// Plays a single run of the given file [file]
/// This method supports long audio files
static Future<AudioPlayer> playLongAudio(String file, {double volume = 1.0}) {
return audioCache.play(file, volume: volume);
return _preparePlayer(
file,
volume,
ReleaseMode.release,
PlayerMode.mediaPlayer,
);
}

/// Plays, and keep looping the given [file]
Expand All @@ -33,11 +65,30 @@ class FlameAudio {
/// iterations, this happens due to limitations on Android's native media
/// player features. If you need a gapless loop, prefer the loop method.
static Future<AudioPlayer> loopLongAudio(String file, {double volume = 1.0}) {
return audioCache.loop(file, volume: volume);
return _preparePlayer(
file,
volume,
ReleaseMode.loop,
PlayerMode.mediaPlayer,
);
}

/// Access a shared instance of the [Bgm] class.
///
/// This will use the same global audio cache from [FlameAudio].
static late final Bgm bgm = Bgm(audioCache: audioCache);

/// Creates a new Audio Pool using Flame's global Audio Cache.
static Future<AudioPool> createPool(
String sound, {
int minPlayers = 1,
required int maxPlayers,
}) {
return AudioPool.create(
sound,
audioCache: audioCache,
minPlayers: minPlayers,
maxPlayers: maxPlayers,
);
}
}
2 changes: 1 addition & 1 deletion packages/flame_audio/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ environment:
flutter: ^3.0.0

dependencies:
audioplayers: ^0.20.1
audioplayers: ^1.0.0
flame: ^1.2.0
flutter:
sdk: flutter
Expand Down