Platform player ad2b050d-a01b-49f3-a3d7-c7be8cd4f197 already exists
Platforms exhibiting the bug
- [x] Android
- [ ] iOS
- [ ] web
Devices exhibiting the bug
There is no issue during normal usage, but when I try to play media files quickly one after another, I encounter this problem. Even though I dispose of the currently defined player, a new one is not created properly. The duration of the media I want to play remains at 0.0, and the issue does not resolve unless I completely restart the app.
Minimal reproduction project
Minimal reproduction project
class AudioPlayerHandler extends BaseAudioHandler with QueueHandler, SeekHandler {
AudioPlayer _player = AudioPlayer();
// Stream subscriptions
StreamSubscription<PlayerState>? _playerStateSubscription;
StreamSubscription<Duration?>? _durationSubscription;
StreamSubscription<Duration>? _positionSubscription;
AudioPlayerHandler() {
_init();
}
void _init() {
_playerStateSubscription = _player.playerStateStream.listen(_handlePlayerStateChange);
_durationSubscription = _player.durationStream.listen((duration) {
if (duration != null) {
mediaItem.add(mediaItem.value?.copyWith(duration: duration));
}
});
_positionSubscription = _player.positionStream.listen((position) {
playbackState.add(playbackState.value.copyWith(
updatePosition: position,
));
});
}
void _handlePlayerStateChange(PlayerState state) {
final playing = state.playing;
final processingState = state.processingState;
final hasNext = locator.call<PlaylistProvider>().nextSong != null;
final hasPrevious = locator.call<PlaylistProvider>().previousSong != null;
AudioProcessingState audioProcessingState;
switch (processingState) {
case ProcessingState.idle:
audioProcessingState = AudioProcessingState.idle;
break;
case ProcessingState.loading:
case ProcessingState.buffering:
audioProcessingState = AudioProcessingState.loading;
break;
case ProcessingState.ready:
audioProcessingState = AudioProcessingState.ready;
break;
case ProcessingState.completed:
audioProcessingState = AudioProcessingState.completed;
break;
}
playbackState.add(PlaybackState(
controls: [
if (hasPrevious) MediaControl.skipToPrevious,
if (playing) MediaControl.pause else MediaControl.play,
if (hasNext) MediaControl.skipToNext,
],
systemActions: const {
MediaAction.seek,
MediaAction.seekForward,
MediaAction.seekBackward,
MediaAction.rewind,
MediaAction.fastForward,
MediaAction.skipToPrevious,
MediaAction.skipToNext,
},
androidCompactActionIndices: const [0, 1, 2],
processingState: audioProcessingState,
playing: playing,
updatePosition: _player.position,
bufferedPosition: _player.bufferedPosition,
speed: _player.speed,
queueIndex: 0,
));
}
@override
Future<void> play() async {
try {
await _player.play();
} catch (e) {
locator<Logger>().error('Play error: $e');
rethrow;
}
}
@override
Future<void> pause() async {
try {
await _player.pause();
} catch (e) {
locator<Logger>().error('Pause error: $e');
rethrow;
}
}
@override
Future<void> stop() async {
try {
await _player.stop();
playbackState.add(PlaybackState(
processingState: AudioProcessingState.idle,
playing: false,
));
} catch (e) {
locator<Logger>().error('Stop error: $e');
rethrow;
}
}
@override
Future<void> seek(Duration position) async {
try {
await _player.seek(position);
} catch (e) {
locator<Logger>().error('Seek error: $e');
rethrow;
}
}
// Custom method to set audio source
Future<void> setAudioSource(AudioSource source, {Duration? initialPosition}) async {
try {
await _player.setAudioSource(source, initialPosition: initialPosition);
} catch (e) {
locator<Logger>().error('Set audio source error: $e');
rethrow;
}
}
// Custom method to set media item
void setMediaItem(MediaItem item) {
mediaItem.add(item);
}
// Getters for accessing player state
bool get playing => _player.playing;
ProcessingState get processingState => _player.processingState;
Duration get position => _player.position;
Duration get bufferedPosition => _player.bufferedPosition;
Duration? get duration => _player.duration;
// Stream getters
Stream<PlayerState> get playerStateStream => _player.playerStateStream;
Stream<Duration> get positionStream => _player.positionStream;
Stream<Duration?> get durationStream => _player.durationStream;
Stream<Duration> get bufferedPositionStream => _player.bufferedPositionStream;
@override
Future<void> onTaskRemoved() async {
await stop();
}
Future<void> recreatePlayer() async {
try {
await _onDestroy();
// _init();
} catch (e) {
locator<Logger>().error('Recreate player error: $e');
} finally {
_player = AudioPlayer(handleInterruptions: false);
_init();
}
}
Future<void> resetPlayerSafely() async {
locator.call<Logger>.call().error('CustomCrashlytics resetPlayerSafely called!');
try {
await _player.stop();
await _player.dispose();
} catch (e) {
locator.call<Logger>.call().error('Error resetting player: $e');
} finally {
locator.call<Logger>.call().error('CustomCrashlytics resetPlayerSafely finally called!');
_player = AudioPlayer(handleInterruptions: false);
}
}
@override
Future<void> click([MediaButton button = MediaButton.media]) async {
switch (button) {
case MediaButton.media:
if (_player.playing) {
await pause();
} else {
await play();
}
break;
case MediaButton.next:
await skipToNext();
break;
case MediaButton.previous:
await skipToPrevious();
break;
}
}
Future<void> _onDestroy() async {
await _playerStateSubscription?.cancel();
await _durationSubscription?.cancel();
await _positionSubscription?.cancel();
await _player.stop();
await _player.dispose();
}
@override
Future<void> skipToNext() async {
try {
final playlistProvider = locator.call<PlaylistProvider>();
final nextSong = playlistProvider.nextSong;
if (nextSong != null) {
final mp3Provider = locator.call<MP3Provider>();
mp3Provider.playMP3(nextSong, ActionType.change);
}
} catch (e) {
locator<Logger>().error('Skip to next error: $e');
}
}
@override
Future<void> skipToPrevious() async {
try {
final playlistProvider = locator.call<PlaylistProvider>();
final previousSong = playlistProvider.previousSong;
if (previousSong != null) {
final mp3Provider = locator.call<MP3Provider>();
mp3Provider.playMP3(previousSong, ActionType.change);
}
} catch (e) {
locator<Logger>().error('Skip to previous error: $e');
}
}
}
initialize:
await AudioService.init(
builder: () => locator.call<AudioPlayerHandler>.call(),
config: const AudioServiceConfig(
androidNotificationChannelId: 'com..background_player',
androidNotificationChannelName: 'customPlayer',
androidNotificationOngoing: true,
androidStopForegroundOnPause: true,
androidNotificationIcon: 'drawable/splash',
),
);
Steps to reproduce
try to play media files quickly one after another (or call play-stop quickly)
Expected results
There is no issue during normal usage, but when I try to play media files quickly one after another, I encounter this problem. Even though I dispose of the currently defined player, a new one is not created properly. The duration of the media I want to play remains at 0.0, and the issue does not resolve unless I completely restart the app.
Actual results
Getting error: Platform player already exists PlatformException(Platform player ad2b050d-a01b-49f3-a3d7-c7be8cd4f197 already exists, null, null, null)
Screenshots or Video
Screenshots / Video demonstration
[Upload media here]
Logs
Logs
[Paste here]
Flutter Doctor output
Doctor output
[√] Flutter (Channel stable, 3.22.0, on Microsoft Windows [Version 10.0.26100.4770], locale tr-TR)
[√] Windows Version (Installed version of Windows is version 10 or higher)
[!] Android toolchain - develop for Android devices (Android SDK version 35.0.1)
! Some Android licenses not accepted. To resolve this, run: flutter doctor --android-licenses
[√] Chrome - develop for the web
[√] Visual Studio - develop Windows apps (Visual Studio Community 2022 17.12.4)
[√] Android Studio (version 2024.2)
[√] VS Code (version 1.102.1)
[√] Connected device (4 available)
[√] Network resources
! Doctor found issues in 1 category.
This is from just_audio, not audio_service. The issue is concurrent calls to player.dispose() and player.stop().
You should reuse the same AudioPlayer instance. If you are done with it, only call dispose(), which implicitly calls stop(). If you must recreate the instance for whatever reason, try this:
final previousPlayer = _player;
_player = AudioPlayer();
await previousPlayer.dispose();