feat: better audio pool (#461)

* feat: better audio pool

* cspell

* lint

* typo

* coverage

* Apply suggestions from code review

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* suggestions

* suggestions

* pr

* lint

* lint

* lint

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>
Co-authored-by: Tom Arra <tarra3@gmail.com>
pull/463/head
Erick 3 years ago committed by GitHub
parent a73f464afe
commit 5492cfaef2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -42,7 +42,8 @@
"unawaited",
"unfocus",
"unlayered",
"vsync"
"vsync",
"microtask"
],
"ignorePaths": [
".github/workflows/**"

@ -2,10 +2,10 @@ import 'dart:math';
import 'package:audioplayers/audioplayers.dart';
import 'package:clock/clock.dart';
import 'package:flame_audio/audio_pool.dart';
import 'package:flame_audio/flame_audio.dart';
import 'package:flutter/material.dart';
import 'package:pinball_audio/gen/assets.gen.dart';
import 'package:pinball_audio/src/pinball_audio_pool.dart';
/// Sounds available to play.
enum PinballAudio {
@ -52,17 +52,8 @@ enum PinballAudio {
flipper,
}
/// Defines the contract of the creation of an [AudioPool].
typedef CreateAudioPool = Future<AudioPool> Function(
String sound, {
bool? repeating,
int? maxPlayers,
int? minPlayers,
String? prefix,
});
/// Defines the contract for playing a single audio.
typedef PlaySingleAudio = Future<void> Function(String, {double volume});
typedef PlaySingleAudio = Future<AudioPlayer> Function(String, {double volume});
/// Defines the contract for looping a single audio.
typedef LoopSingleAudio = Future<void> Function(String, {double volume});
@ -94,13 +85,20 @@ class _SimplePlayAudio extends _Audio {
final PlaySingleAudio playSingleAudio;
final String path;
final double? volume;
AudioPlayer? _player;
@override
Future<void> load() => preCacheSingleAudio(prefixFile(path));
@override
void play() {
playSingleAudio(prefixFile(path), volume: volume ?? 1);
Future<void> play() async {
final url = prefixFile(path);
final volume = this.volume ?? 1;
if (_player == null) {
_player = await playSingleAudio(url, volume: volume);
} else {
await _player!.play(url, volume: volume);
}
}
}
@ -153,81 +151,94 @@ class _SingleLoopAudio extends _LoopAudio {
class _SingleAudioPool extends _Audio {
_SingleAudioPool({
required this.path,
required this.createAudioPool,
required this.duration,
required this.maxPlayers,
required this.preCacheSingleAudio,
required this.playSingleAudio,
});
final String path;
final CreateAudioPool createAudioPool;
final int maxPlayers;
late AudioPool pool;
final Duration duration;
final PreCacheSingleAudio preCacheSingleAudio;
final PlaySingleAudio playSingleAudio;
late PinballAudioPool pool;
@override
Future<void> load() async {
pool = await createAudioPool(
prefixFile(path),
maxPlayers: maxPlayers,
prefix: '',
pool = PinballAudioPool(
path: prefixFile(path),
poolSize: maxPlayers,
preCacheSingleAudio: preCacheSingleAudio,
playSingleAudio: playSingleAudio,
duration: duration,
);
await pool.load();
}
@override
void play() => pool.start();
void play() => pool.play();
}
class _RandomABAudio extends _Audio {
_RandomABAudio({
required this.createAudioPool,
required this.preCacheSingleAudio,
required this.playSingleAudio,
required this.seed,
required this.audioAssetA,
required this.audioAssetB,
required this.duration,
this.volume,
});
final CreateAudioPool createAudioPool;
final PreCacheSingleAudio preCacheSingleAudio;
final PlaySingleAudio playSingleAudio;
final Random seed;
final String audioAssetA;
final String audioAssetB;
final Duration duration;
final double? volume;
late AudioPool audioA;
late AudioPool audioB;
late PinballAudioPool audioA;
late PinballAudioPool audioB;
@override
Future<void> load() async {
await Future.wait(
[
createAudioPool(
prefixFile(audioAssetA),
maxPlayers: 4,
prefix: '',
).then((pool) => audioA = pool),
createAudioPool(
prefixFile(audioAssetB),
maxPlayers: 4,
prefix: '',
).then((pool) => audioB = pool),
],
audioA = PinballAudioPool(
path: prefixFile(audioAssetA),
poolSize: 4,
preCacheSingleAudio: preCacheSingleAudio,
playSingleAudio: playSingleAudio,
duration: duration,
);
audioB = PinballAudioPool(
path: prefixFile(audioAssetB),
poolSize: 4,
preCacheSingleAudio: preCacheSingleAudio,
playSingleAudio: playSingleAudio,
duration: duration,
);
await Future.wait([audioA.load(), audioB.load()]);
}
@override
void play() {
(seed.nextBool() ? audioA : audioB).start(volume: volume ?? 1);
(seed.nextBool() ? audioA : audioB).play(volume: volume ?? 1);
}
}
class _ThrottledAudio extends _Audio {
class _ThrottledAudio extends _SimplePlayAudio {
_ThrottledAudio({
required this.preCacheSingleAudio,
required this.playSingleAudio,
required this.path,
required PreCacheSingleAudio preCacheSingleAudio,
required PlaySingleAudio playSingleAudio,
required String path,
required this.duration,
});
}) : super(
preCacheSingleAudio: preCacheSingleAudio,
playSingleAudio: playSingleAudio,
path: path,
);
final PreCacheSingleAudio preCacheSingleAudio;
final PlaySingleAudio playSingleAudio;
final String path;
final Duration duration;
DateTime? _lastPlayed;
@ -236,12 +247,12 @@ class _ThrottledAudio extends _Audio {
Future<void> load() => preCacheSingleAudio(prefixFile(path));
@override
void play() {
Future<void> play() async {
final now = clock.now();
if (_lastPlayed == null ||
(_lastPlayed != null && now.difference(_lastPlayed!) > duration)) {
_lastPlayed = now;
playSingleAudio(prefixFile(path));
await super.play();
}
}
}
@ -252,14 +263,12 @@ class _ThrottledAudio extends _Audio {
class PinballAudioPlayer {
/// {@macro pinball_audio_player}
PinballAudioPlayer({
CreateAudioPool? createAudioPool,
PlaySingleAudio? playSingleAudio,
LoopSingleAudio? loopSingleAudio,
PreCacheSingleAudio? preCacheSingleAudio,
ConfigureAudioCache? configureAudioCache,
Random? seed,
}) : _createAudioPool = createAudioPool ?? AudioPool.create,
_playSingleAudio = playSingleAudio ?? FlameAudio.audioCache.play,
}) : _playSingleAudio = playSingleAudio ?? FlameAudio.audioCache.play,
_loopSingleAudio = loopSingleAudio ?? FlameAudio.audioCache.loop,
_preCacheSingleAudio =
preCacheSingleAudio ?? FlameAudio.audioCache.load,
@ -308,8 +317,10 @@ class PinballAudioPlayer {
),
PinballAudio.flipper: _SingleAudioPool(
path: Assets.sfx.flipper,
createAudioPool: _createAudioPool,
maxPlayers: 2,
maxPlayers: 4,
preCacheSingleAudio: _preCacheSingleAudio,
playSingleAudio: _playSingleAudio,
duration: const Duration(milliseconds: 200),
),
PinballAudio.ioPinballVoiceOver: _SimplePlayAudio(
preCacheSingleAudio: _preCacheSingleAudio,
@ -322,17 +333,21 @@ class PinballAudioPlayer {
path: Assets.sfx.gameOverVoiceOver,
),
PinballAudio.bumper: _RandomABAudio(
createAudioPool: _createAudioPool,
preCacheSingleAudio: _preCacheSingleAudio,
playSingleAudio: _playSingleAudio,
seed: _seed,
audioAssetA: Assets.sfx.bumperA,
audioAssetB: Assets.sfx.bumperB,
duration: const Duration(seconds: 1),
volume: 0.6,
),
PinballAudio.kicker: _RandomABAudio(
createAudioPool: _createAudioPool,
preCacheSingleAudio: _preCacheSingleAudio,
playSingleAudio: _playSingleAudio,
seed: _seed,
audioAssetA: Assets.sfx.kickerA,
audioAssetB: Assets.sfx.kickerB,
duration: const Duration(seconds: 1),
volume: 0.6,
),
PinballAudio.cowMoo: _ThrottledAudio(
@ -350,8 +365,6 @@ class PinballAudioPlayer {
};
}
final CreateAudioPool _createAudioPool;
final PlaySingleAudio _playSingleAudio;
final LoopSingleAudio _loopSingleAudio;

@ -0,0 +1,87 @@
import 'dart:async';
import 'package:audioplayers/audioplayers.dart';
import 'package:pinball_audio/pinball_audio.dart';
class _PlayerEntry {
_PlayerEntry({
required this.available,
required this.player,
});
bool available;
final AudioPlayer player;
}
/// {@template pinball_audio_pool}
/// Creates an audio player pool used to trigger many sounds at the same time.
/// {@endtemplate}
class PinballAudioPool {
/// {@macro pinball_audio_pool}
PinballAudioPool({
required this.path,
required this.poolSize,
required this.preCacheSingleAudio,
required this.playSingleAudio,
required this.duration,
});
/// Sounds path.
final String path;
/// Max size of this pool.
final int poolSize;
/// Function to cache audios.
final PreCacheSingleAudio preCacheSingleAudio;
/// Function to play audios.
final PlaySingleAudio playSingleAudio;
/// How long the sound lasts.
final Duration duration;
final List<_PlayerEntry> _players = [];
/// Loads the pool.
Future<void> load() async {
await preCacheSingleAudio(path);
}
/// Plays the pool.
Future<void> play({double volume = 1}) async {
AudioPlayer? player;
if (_players.length < poolSize) {
_players.add(
_PlayerEntry(
available: false,
player: player = await playSingleAudio(path, volume: volume),
),
);
} else {
final entries = _players.where((entry) => entry.available);
if (entries.isNotEmpty) {
final entry = entries.first..available = false;
player = entry.player;
unawaited(entry.player.play(path, volume: volume));
}
}
if (player != null) {
unawaited(
Future<void>.delayed(duration).then(
(_) {
_returnEntryAvailability(player!);
},
),
);
} else {}
}
void _returnEntryAvailability(
AudioPlayer player,
) {
_players.where((entry) => entry.player == player).single.available = true;
}
}

@ -0,0 +1,74 @@
// ignore_for_file: one_member_abstracts
import 'package:audioplayers/audioplayers.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball_audio/src/pinball_audio_pool.dart';
class _MockAudioPlayer extends Mock implements AudioPlayer {}
class _MockPlaySingleAudio extends Mock {
Future<AudioPlayer> onCall(String path, {double volume});
}
abstract class _PreCacheSingleAudio {
Future<void> onCall(String path);
}
class _MockPreCacheSingleAudio extends Mock implements _PreCacheSingleAudio {}
void main() {
group('PinballAudioPool', () {
late _PreCacheSingleAudio preCacheSingleAudio;
late _MockPlaySingleAudio playSingleAudio;
late PinballAudioPool pool;
late AudioPlayer audioPlayer;
setUp(() {
preCacheSingleAudio = _MockPreCacheSingleAudio();
when(() => preCacheSingleAudio.onCall(any())).thenAnswer((_) async {});
audioPlayer = _MockAudioPlayer();
when(() => audioPlayer.play(any(), volume: any(named: 'volume')))
.thenAnswer((_) async => 1);
playSingleAudio = _MockPlaySingleAudio();
when(() => playSingleAudio.onCall(any(), volume: any(named: 'volume')))
.thenAnswer((_) async => audioPlayer);
pool = PinballAudioPool(
path: 'path',
poolSize: 1,
preCacheSingleAudio: preCacheSingleAudio.onCall,
playSingleAudio: playSingleAudio.onCall,
duration: const Duration(milliseconds: 10),
);
});
test('pre cache the sound', () async {
await pool.load();
verify(() => preCacheSingleAudio.onCall('path')).called(1);
});
test('plays a fresh sound', () async {
await pool.load();
await pool.play();
verify(
() => playSingleAudio.onCall(
'path',
volume: any(named: 'volume'),
),
).called(1);
});
test('plays from the pool after it returned', () async {
await pool.load();
await pool.play();
await Future<void>.delayed(const Duration(milliseconds: 12));
await pool.play();
verify(() => audioPlayer.play('path')).called(1);
});
});
}

@ -3,33 +3,22 @@ import 'dart:math';
import 'package:audioplayers/audioplayers.dart';
import 'package:clock/clock.dart';
import 'package:flame_audio/audio_pool.dart';
import 'package:flame_audio/flame_audio.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball_audio/gen/assets.gen.dart';
import 'package:pinball_audio/pinball_audio.dart';
class _MockAudioPool extends Mock implements AudioPool {}
class _MockAudioCache extends Mock implements AudioCache {}
class _MockCreateAudioPool extends Mock {
Future<AudioPool> onCall(
String sound, {
bool? repeating,
int? maxPlayers,
int? minPlayers,
String? prefix,
});
}
class _MockAudioPlayer extends Mock implements AudioPlayer {}
class _MockConfigureAudioCache extends Mock {
void onCall(AudioCache cache);
}
class _MockPlaySingleAudio extends Mock {
Future<void> onCall(String path, {double volume});
Future<AudioPlayer> onCall(String path, {double volume});
}
class _MockLoopSingleAudio extends Mock {
@ -48,7 +37,6 @@ class _MockClock extends Mock implements Clock {}
void main() {
group('PinballAudio', () {
late _MockCreateAudioPool createAudioPool;
late _MockConfigureAudioCache configureAudioCache;
late _MockPlaySingleAudio playSingleAudio;
late _MockLoopSingleAudio loopSingleAudio;
@ -61,21 +49,12 @@ void main() {
});
setUp(() {
createAudioPool = _MockCreateAudioPool();
when(
() => createAudioPool.onCall(
any(),
maxPlayers: any(named: 'maxPlayers'),
prefix: any(named: 'prefix'),
),
).thenAnswer((_) async => _MockAudioPool());
configureAudioCache = _MockConfigureAudioCache();
when(() => configureAudioCache.onCall(any())).thenAnswer((_) {});
playSingleAudio = _MockPlaySingleAudio();
when(() => playSingleAudio.onCall(any(), volume: any(named: 'volume')))
.thenAnswer((_) async {});
.thenAnswer((_) async => _MockAudioPlayer());
loopSingleAudio = _MockLoopSingleAudio();
when(() => loopSingleAudio.onCall(any(), volume: any(named: 'volume')))
@ -88,7 +67,6 @@ void main() {
audioPlayer = PinballAudioPlayer(
configureAudioCache: configureAudioCache.onCall,
createAudioPool: createAudioPool.onCall,
playSingleAudio: playSingleAudio.onCall,
loopSingleAudio: loopSingleAudio.onCall,
preCacheSingleAudio: preCacheSingleAudio.onCall,
@ -101,64 +79,6 @@ void main() {
});
group('load', () {
test('creates the bumpers pools', () async {
await Future.wait(
audioPlayer.load().map((loadableBuilder) => loadableBuilder()),
);
verify(
() => createAudioPool.onCall(
'packages/pinball_audio/${Assets.sfx.bumperA}',
maxPlayers: 4,
prefix: '',
),
).called(1);
verify(
() => createAudioPool.onCall(
'packages/pinball_audio/${Assets.sfx.bumperB}',
maxPlayers: 4,
prefix: '',
),
).called(1);
});
test('creates the kicker pools', () async {
await Future.wait(
audioPlayer.load().map((loadableBuilder) => loadableBuilder()),
);
verify(
() => createAudioPool.onCall(
'packages/pinball_audio/${Assets.sfx.kickerA}',
maxPlayers: 4,
prefix: '',
),
).called(1);
verify(
() => createAudioPool.onCall(
'packages/pinball_audio/${Assets.sfx.kickerB}',
maxPlayers: 4,
prefix: '',
),
).called(1);
});
test('creates the flipper pool', () async {
await Future.wait(
audioPlayer.load().map((loadableBuilder) => loadableBuilder()),
);
verify(
() => createAudioPool.onCall(
'packages/pinball_audio/${Assets.sfx.flipper}',
maxPlayers: 2,
prefix: '',
),
).called(1);
});
test('configures the audio cache instance', () async {
await Future.wait(
audioPlayer.load().map((loadableBuilder) => loadableBuilder()),
@ -170,7 +90,6 @@ void main() {
test('sets the correct prefix', () async {
audioPlayer = PinballAudioPlayer(
createAudioPool: createAudioPool.onCall,
playSingleAudio: playSingleAudio.onCall,
preCacheSingleAudio: preCacheSingleAudio.onCall,
);
@ -186,6 +105,26 @@ void main() {
audioPlayer.load().map((loadableBuilder) => loadableBuilder()),
);
verify(
() => preCacheSingleAudio
.onCall('packages/pinball_audio/assets/sfx/bumper_a.mp3'),
).called(1);
verify(
() => preCacheSingleAudio
.onCall('packages/pinball_audio/assets/sfx/bumper_b.mp3'),
).called(1);
verify(
() => preCacheSingleAudio
.onCall('packages/pinball_audio/assets/sfx/kicker_a.mp3'),
).called(1);
verify(
() => preCacheSingleAudio
.onCall('packages/pinball_audio/assets/sfx/kicker_b.mp3'),
).called(1);
verify(
() => preCacheSingleAudio
.onCall('packages/pinball_audio/assets/sfx/flipper.mp3'),
).called(1);
verify(
() => preCacheSingleAudio
.onCall('packages/pinball_audio/assets/sfx/google.mp3'),
@ -236,33 +175,6 @@ void main() {
});
group('bumper', () {
late AudioPool bumperAPool;
late AudioPool bumperBPool;
setUp(() {
bumperAPool = _MockAudioPool();
when(() => bumperAPool.start(volume: any(named: 'volume')))
.thenAnswer((_) async => () {});
when(
() => createAudioPool.onCall(
'packages/pinball_audio/${Assets.sfx.bumperA}',
maxPlayers: any(named: 'maxPlayers'),
prefix: any(named: 'prefix'),
),
).thenAnswer((_) async => bumperAPool);
bumperBPool = _MockAudioPool();
when(() => bumperBPool.start(volume: any(named: 'volume')))
.thenAnswer((_) async => () {});
when(
() => createAudioPool.onCall(
'packages/pinball_audio/${Assets.sfx.bumperB}',
maxPlayers: any(named: 'maxPlayers'),
prefix: any(named: 'prefix'),
),
).thenAnswer((_) async => bumperBPool);
});
group('when seed is true', () {
test('plays the bumper A sound pool', () async {
when(seed.nextBool).thenReturn(true);
@ -271,7 +183,12 @@ void main() {
);
audioPlayer.play(PinballAudio.bumper);
verify(() => bumperAPool.start(volume: 0.6)).called(1);
verify(
() => playSingleAudio.onCall(
'packages/pinball_audio/${Assets.sfx.bumperA}',
volume: 0.6,
),
).called(1);
});
});
@ -283,39 +200,17 @@ void main() {
);
audioPlayer.play(PinballAudio.bumper);
verify(() => bumperBPool.start(volume: 0.6)).called(1);
verify(
() => playSingleAudio.onCall(
'packages/pinball_audio/${Assets.sfx.bumperB}',
volume: 0.6,
),
).called(1);
});
});
});
group('kicker', () {
late AudioPool kickerAPool;
late AudioPool kickerBPool;
setUp(() {
kickerAPool = _MockAudioPool();
when(() => kickerAPool.start(volume: any(named: 'volume')))
.thenAnswer((_) async => () {});
when(
() => createAudioPool.onCall(
'packages/pinball_audio/${Assets.sfx.kickerA}',
maxPlayers: any(named: 'maxPlayers'),
prefix: any(named: 'prefix'),
),
).thenAnswer((_) async => kickerAPool);
kickerBPool = _MockAudioPool();
when(() => kickerBPool.start(volume: any(named: 'volume')))
.thenAnswer((_) async => () {});
when(
() => createAudioPool.onCall(
'packages/pinball_audio/${Assets.sfx.kickerB}',
maxPlayers: any(named: 'maxPlayers'),
prefix: any(named: 'prefix'),
),
).thenAnswer((_) async => kickerBPool);
});
group('when seed is true', () {
test('plays the kicker A sound pool', () async {
when(seed.nextBool).thenReturn(true);
@ -324,7 +219,12 @@ void main() {
);
audioPlayer.play(PinballAudio.kicker);
verify(() => kickerAPool.start(volume: 0.6)).called(1);
verify(
() => playSingleAudio.onCall(
'packages/pinball_audio/${Assets.sfx.kickerA}',
volume: 0.6,
),
).called(1);
});
});
@ -336,27 +236,17 @@ void main() {
);
audioPlayer.play(PinballAudio.kicker);
verify(() => kickerBPool.start(volume: 0.6)).called(1);
verify(
() => playSingleAudio.onCall(
'packages/pinball_audio/${Assets.sfx.kickerB}',
volume: 0.6,
),
).called(1);
});
});
});
group('flipper', () {
late AudioPool pool;
setUp(() {
pool = _MockAudioPool();
when(() => pool.start(volume: any(named: 'volume')))
.thenAnswer((_) async => () {});
when(
() => createAudioPool.onCall(
'packages/pinball_audio/${Assets.sfx.flipper}',
maxPlayers: any(named: 'maxPlayers'),
prefix: any(named: 'prefix'),
),
).thenAnswer((_) async => pool);
});
test('plays the flipper sound pool', () async {
when(seed.nextBool).thenReturn(true);
await Future.wait(
@ -364,7 +254,12 @@ void main() {
);
audioPlayer.play(PinballAudio.flipper);
verify(() => pool.start()).called(1);
verify(
() => playSingleAudio.onCall(
'packages/pinball_audio/${Assets.sfx.flipper}',
volume: any(named: 'volume'),
),
).called(1);
});
});
@ -376,14 +271,21 @@ void main() {
audioPlayer.play(PinballAudio.cowMoo);
verify(
() => playSingleAudio
.onCall('packages/pinball_audio/${Assets.sfx.cowMoo}'),
() => playSingleAudio.onCall(
'packages/pinball_audio/${Assets.sfx.cowMoo}',
volume: any(named: 'volume'),
),
).called(1);
});
test('only plays the sound again after 2 seconds', () async {
final clock = _MockClock();
await withClock(clock, () async {
final audioPlayerInstance = _MockAudioPlayer();
when(
() => playSingleAudio.onCall(any(), volume: any(named: 'volume')),
).thenAnswer((_) async => audioPlayerInstance);
when(clock.now).thenReturn(DateTime(2022));
await Future.wait(
audioPlayer.load().map((loadableBuilder) => loadableBuilder()),
@ -393,16 +295,20 @@ void main() {
..play(PinballAudio.cowMoo);
verify(
() => playSingleAudio
.onCall('packages/pinball_audio/${Assets.sfx.cowMoo}'),
() => playSingleAudio.onCall(
'packages/pinball_audio/${Assets.sfx.cowMoo}',
volume: any(named: 'volume'),
),
).called(1);
when(clock.now).thenReturn(DateTime(2022, 1, 1, 1, 2));
audioPlayer.play(PinballAudio.cowMoo);
verify(
() => playSingleAudio
.onCall('packages/pinball_audio/${Assets.sfx.cowMoo}'),
() => playSingleAudio.onCall(
'packages/pinball_audio/${Assets.sfx.cowMoo}',
volume: any(named: 'volume'),
),
).called(1);
});
});
@ -422,6 +328,44 @@ void main() {
),
).called(1);
});
test('uses the cached player on the second time', () async {
final audioPlayerCache = _MockAudioPlayer();
when(() => audioPlayerCache.play(any(), volume: any(named: 'volume')))
.thenAnswer((_) async => 0);
when(() => playSingleAudio.onCall(any(), volume: any(named: 'volume')))
.thenAnswer((_) async => audioPlayerCache);
audioPlayer = PinballAudioPlayer(
configureAudioCache: configureAudioCache.onCall,
playSingleAudio: playSingleAudio.onCall,
loopSingleAudio: loopSingleAudio.onCall,
preCacheSingleAudio: preCacheSingleAudio.onCall,
seed: seed,
);
await Future.wait(
audioPlayer.load().map((loadableBuilder) => loadableBuilder()),
);
audioPlayer.play(PinballAudio.google);
verify(
() => playSingleAudio.onCall(
'packages/pinball_audio/${Assets.sfx.google}',
volume: any(named: 'volume'),
),
).called(1);
await Future.microtask(() {});
audioPlayer.play(PinballAudio.google);
verify(
() => audioPlayerCache.play(
'packages/pinball_audio/${Assets.sfx.google}',
volume: any(named: 'volume'),
),
).called(1);
});
});
group('sparky', () {
@ -467,16 +411,20 @@ void main() {
..play(PinballAudio.dino);
verify(
() => playSingleAudio
.onCall('packages/pinball_audio/${Assets.sfx.dino}'),
() => playSingleAudio.onCall(
'packages/pinball_audio/${Assets.sfx.dino}',
volume: any(named: 'volume'),
),
).called(1);
when(clock.now).thenReturn(DateTime(2022, 1, 1, 1, 6));
audioPlayer.play(PinballAudio.dino);
verify(
() => playSingleAudio
.onCall('packages/pinball_audio/${Assets.sfx.dino}'),
() => playSingleAudio.onCall(
'packages/pinball_audio/${Assets.sfx.dino}',
volume: any(named: 'volume'),
),
).called(1);
});
});

Loading…
Cancel
Save