diff --git a/packages/audioplayers/lib/src/audio_pool.dart b/packages/audioplayers/lib/src/audio_pool.dart index 971548c44..0133e174c 100644 --- a/packages/audioplayers/lib/src/audio_pool.dart +++ b/packages/audioplayers/lib/src/audio_pool.dart @@ -41,6 +41,9 @@ class AudioPool { /// returned to the pool. final int maxPlayers; + @visibleForTesting + Duration? duration; + final Lock _lock = Lock(); AudioPool._({ @@ -94,10 +97,7 @@ class AudioPool { /// Starts playing the audio, returns a function that can stop the audio. Future 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(); @@ -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 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 _createNewAudioPlayer() async { final player = AudioPlayer()..audioCache = audioCache; if (audioContext != null) { @@ -135,6 +163,13 @@ class AudioPool { return player; } + Future _createNewOrReserveAnAvailablePlayer() async { + if (availablePlayers.isEmpty) { + return _createNewAudioPlayer(); + } + return availablePlayers.removeAt(0); + } + /// Disposes the audio pool. Then it cannot be used anymore. Future dispose() => Future.wait(availablePlayers.map((e) => e.dispose())); diff --git a/packages/audioplayers/test/audio_pool_test.dart b/packages/audioplayers/test/audio_pool_test.dart index f8ae4b29a..aa68f1d82 100644 --- a/packages/audioplayers/test/audio_pool_test.dart +++ b/packages/audioplayers/test/audio_pool_test.dart @@ -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()); + }); + + 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); + }); }); }