mirror of https://github.com/flutter/pinball.git
commit
135bd31e29
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
import 'package:bloc_test/bloc_test.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('PlungerCubit', () {
|
||||||
|
test('can be instantiated', () {
|
||||||
|
expect(PlungerCubit(), isA<PlungerCubit>());
|
||||||
|
});
|
||||||
|
|
||||||
|
blocTest<PlungerCubit, PlungerState>(
|
||||||
|
'overrides previous pulling state',
|
||||||
|
build: PlungerCubit.new,
|
||||||
|
act: (cubit) => cubit
|
||||||
|
..pulled()
|
||||||
|
..autoPulled()
|
||||||
|
..pulled(),
|
||||||
|
expect: () => [
|
||||||
|
PlungerState.pulling,
|
||||||
|
PlungerState.autoPulling,
|
||||||
|
PlungerState.pulling,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in new issue