Refactor audio player to manage subscriptions more effectively and ensure proper cleanup

This commit is contained in:
Leon Bösche
2026-01-17 03:40:56 +01:00
parent 014b77a27e
commit 898922efd1

View File

@@ -12,43 +12,74 @@ class AudioPlayer {
final StreamController<String> _errorController = final StreamController<String> _errorController =
StreamController<String>.broadcast(); StreamController<String>.broadcast();
// Store subscriptions for cleanup
StreamSubscription? _durationSubscription;
StreamSubscription? _positionSubscription;
StreamSubscription? _playSubscription;
StreamSubscription? _pauseSubscription;
StreamSubscription? _endedSubscription;
StreamSubscription? _errorSubscription;
Stream<Duration> get positionStream => _positionController.stream; Stream<Duration> get positionStream => _positionController.stream;
Stream<Duration> get durationStream => _durationController.stream; Stream<Duration> get durationStream => _durationController.stream;
Stream<bool> get playingStream => _playingController.stream; Stream<bool> get playingStream => _playingController.stream;
Stream<String> get errorStream => _errorController.stream; Stream<String> get errorStream => _errorController.stream;
void _disposeSubscriptions() {
_durationSubscription?.cancel();
_positionSubscription?.cancel();
_playSubscription?.cancel();
_pauseSubscription?.cancel();
_endedSubscription?.cancel();
_errorSubscription?.cancel();
_durationSubscription = null;
_positionSubscription = null;
_playSubscription = null;
_pauseSubscription = null;
_endedSubscription = null;
_errorSubscription = null;
}
Future<void> setUrl(String url) async { Future<void> setUrl(String url) async {
// Clean up any existing subscriptions
_disposeSubscriptions();
try { try {
_audioElement = web.HTMLAudioElement(); _audioElement = web.HTMLAudioElement();
_audioElement!.src = url; _audioElement!.src = url;
_audioElement!.crossOrigin = 'anonymous'; // Handle CORS _audioElement!.crossOrigin = 'anonymous'; // Handle CORS
// Set up event listeners // Set up event listeners and store subscriptions
_audioElement!.onLoadedMetadata.listen((_) { _durationSubscription = _audioElement!.onLoadedMetadata.listen((_) {
if (_audioElement != null) {
_durationController.add( _durationController.add(
Duration(milliseconds: (_audioElement!.duration * 1000).toInt()), Duration(milliseconds: (_audioElement!.duration * 1000).toInt()),
); );
}
}); });
_audioElement!.onTimeUpdate.listen((_) { _positionSubscription = _audioElement!.onTimeUpdate.listen((_) {
if (_audioElement != null) {
_positionController.add( _positionController.add(
Duration(milliseconds: (_audioElement!.currentTime * 1000).toInt()), Duration(milliseconds: (_audioElement!.currentTime * 1000).toInt()),
); );
}
}); });
_audioElement!.onPlay.listen((_) { _playSubscription = _audioElement!.onPlay.listen((_) {
_playingController.add(true); _playingController.add(true);
}); });
_audioElement!.onPause.listen((_) { _pauseSubscription = _audioElement!.onPause.listen((_) {
_playingController.add(false); _playingController.add(false);
}); });
_audioElement!.onEnded.listen((_) { _endedSubscription = _audioElement!.onEnded.listen((_) {
_playingController.add(false); _playingController.add(false);
}); });
_audioElement!.onError.listen((_) { _errorSubscription = _audioElement!.onError.listen((_) {
_errorController.add('Failed to load audio'); _errorController.add('Failed to load audio');
}); });
@@ -82,6 +113,7 @@ class AudioPlayer {
} }
void dispose() { void dispose() {
_disposeSubscriptions();
_audioElement?.pause(); _audioElement?.pause();
_audioElement = null; _audioElement = null;
_positionController.close(); _positionController.close();