Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 39 additions & 4 deletions packages/audioplayers/lib/src/audio_pool.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ class AudioPool {
/// returned to the pool.
final int maxPlayers;

@visibleForTesting
Duration? duration;

final Lock _lock = Lock();

AudioPool._({
Expand Down Expand Up @@ -94,10 +97,7 @@ class AudioPool {
/// Starts playing the audio, returns a function that can stop the audio.
Future<StopFunction> start({double volume = 1.0}) async {
return _lock.synchronized(() async {
if (availablePlayers.isEmpty) {
availablePlayers.add(await _createNewAudioPlayer());
}
final player = availablePlayers.removeAt(0);
final player = await _createNewOrReserveAnAvailablePlayer();
currentPlayers[player.playerId] = player;
await player.setVolume(volume);
await player.resume();
Expand Down Expand Up @@ -125,6 +125,34 @@ class AudioPool {
});
}

/// Returns the duration of the audio.
///
/// If the duration is requested for the first time it will use the pool to
/// get or create a player and get the duration from it. Subsequent calls will
/// return the duration immediately.
Future<Duration?> getDuration() async {
if (duration != null) {
return duration;
}

return _lock.synchronized(() async {
final player = await _createNewOrReserveAnAvailablePlayer();
currentPlayers[player.playerId] = player;
duration = await player.getDuration();

final removedPlayer = currentPlayers.remove(player.playerId);
if (removedPlayer != null) {
if (availablePlayers.length >= maxPlayers) {
await removedPlayer.dispose();
} else {
availablePlayers.add(removedPlayer);
}
}

return duration;
});
}

Future<AudioPlayer> _createNewAudioPlayer() async {
final player = AudioPlayer()..audioCache = audioCache;
if (audioContext != null) {
Expand All @@ -135,6 +163,13 @@ class AudioPool {
return player;
}

Future<AudioPlayer> _createNewOrReserveAnAvailablePlayer() async {
if (availablePlayers.isEmpty) {
return _createNewAudioPlayer();
}
return availablePlayers.removeAt(0);
}

/// Disposes the audio pool. Then it cannot be used anymore.
Future<void> dispose() =>
Future.wait(availablePlayers.map((e) => e.dispose()));
Expand Down
70 changes: 70 additions & 0 deletions packages/audioplayers/test/audio_pool_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,5 +79,75 @@ void main() {
expect(pool.availablePlayers.length, 3);
expect(pool.currentPlayers.isEmpty, isTrue);
});

test('gets duration', () async {
final pool = await AudioPool.createFromAsset(
path: 'audio.mp3',
maxPlayers: 3,
audioCache: FakeAudioCache(),
);
final duration = await pool.getDuration();
expect(duration, isA<Duration>());
});

test('getDuration adds an available player to the pool', () async {
final pool = await AudioPool.createFromAsset(
path: 'audio.mp3',
maxPlayers: 3,
audioCache: FakeAudioCache(),
);

final stop = await pool.start();
await Future.wait([
pool.getDuration(),
stop(),
]);

expect(pool.availablePlayers.length, 2);
expect(pool.currentPlayers.isEmpty, isTrue);
});

test(
'Consecutive getDuration returns from cache and does not create player',
() async {
final pool = await AudioPool.createFromAsset(
path: 'audio.mp3',
maxPlayers: 3,
audioCache: FakeAudioCache(),
);

expect(pool.duration, isNull);

final durations = await Future.wait([
pool.getDuration(),
pool.getDuration(),
]);

final durationFromNewPlayer = durations[0];
final durationFromCache = durations[1];

expect(pool.duration, isNotNull);
expect(durationFromNewPlayer, durationFromCache);

expect(pool.availablePlayers.length, 1);
expect(pool.currentPlayers.isEmpty, isTrue);
});

test('getDuration keeps the minPlayers/maxPlayers contract', () async {
final pool = await AudioPool.createFromAsset(
path: 'audio.mp3',
maxPlayers: 3,
audioCache: FakeAudioCache(),
);

final stopFunctions =
await Future.wait(List.generate(3, (_) => pool.start()));

await pool.getDuration();
await Future.wait(stopFunctions.map((f) => f()));

expect(pool.availablePlayers.length, 3);
expect(pool.currentPlayers.isEmpty, isTrue);
});
});
}
Loading