flame
flame copied to clipboard
audio_pool calls stop() when player is not in currentPlayers
Current bug behaviour
Error is triggered when stop() is called on a player that is no longer in memory.
Expected behaviour
stop() is only called on players in memory.
Steps to reproduce
Create a static class method which returns an audio_pool and call that instance many times over.
Flutter doctor output
[√] Flutter (Channel beta, 2.5.0-5.1.pre, on Microsoft Windows [Version 10.0.19043.1165], locale en-GB)
• Flutter version 2.5.0-5.1.pre at C:\src\flutter
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision 65cf7de5f4 (8 days ago), 2021-08-11 15:48:01 -0700
• Engine revision fbeb7e22bd
• Dart version 2.14.0 (build 2.14.0-377.4.beta)
[√] Android toolchain - develop for Android devices (Android SDK version 31.0.0)
• Android SDK at C:\Users\Pjotr\AppData\Local\Android\Sdk
• Platform android-31, build-tools 31.0.0
• ANDROID_HOME = C:\Users\Pjotr\AppData\Local\Android\Sdk\
• ANDROID_SDK_ROOT = C:\Users\Pjotr\AppData\Local\Android\Sdk\
• Java binary at: C:\src\Android Studio\jre\bin\java
• Java version OpenJDK Runtime Environment (build 11.0.10+0-b96-7249189)
• All Android licenses accepted.
[√] Chrome - develop for the web
• Chrome at C:\Program Files (x86)\Google\Chrome\Application\chrome.exe
[√] Visual Studio - develop for Windows (Visual Studio Community 2019 16.9.0)
• Visual Studio at C:\Program Files (x86)\Microsoft Visual Studio\2019\Community
• Visual Studio Community 2019 version 16.9.31025.194
• Windows 10 SDK version 10.0.19041.0
[√] Android Studio (version 2020.3)
• Android Studio at C:\src\Android Studio
• Flutter plugin can be installed from:
https://plugins.jetbrains.com/plugin/9212-flutter
• Dart plugin can be installed from:
https://plugins.jetbrains.com/plugin/6351-dart
• Java version OpenJDK Runtime Environment (build 11.0.10+0-b96-7249189)
[√] Connected device (3 available)
• Windows (desktop) • windows • windows-x64 • Microsoft Windows [Version 10.0.19043.1165]
• Chrome (web) • chrome • web-javascript • Google Chrome 92.0.4515.159
• Edge (web) • edge • web-javascript • Microsoft Edge 92.0.902.73
• No issues found!
More environment information
- Flame version: 0.29.3
- Platform affected: android emulator
Log information
E/flutter ( 8365): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: NoSuchMethodError: The method 'stop' was called on null.
E/flutter ( 8365): Receiver: null
E/flutter ( 8365): Tried calling: stop()
E/flutter ( 8365): #0 Object.noSuchMethod (dart:core-patch/object_patch.dart:63:5)
E/flutter ( 8365): #1 AudioPool.start.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:flame/audio_pool.dart:54:19)
E/flutter ( 8365): #2 AudioPool.start.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:flame/audio_pool.dart:51:28)
E/flutter ( 8365): #3 BasicLock.synchronized (package:synchronized/src/basic_lock.dart:32:26)
More information
In my project I solved this issue by forking audio_pool and rewriting it as follows. I'm not sure if my solution has performance issues, but if you are happy with it I'd be happy to create a PR based on it:
//@dart=2.13
import 'dart:async';
import 'package:audioplayers/audio_cache.dart';
import 'package:audioplayers/audioplayers.dart';
import 'package:synchronized/synchronized.dart';
// typedef void Stoppable();
/// An AudioPool is a provider of AudioPlayers that leaves them pre-loaded to minimize delays.
///
/// All AudioPlayers loaded are for the same [sound]. If you want multiple sounds use multiple [AudioPool].
/// Use this class if you'd like have extremely quick firing, repetitive and simultaneous sounds, like shooting a laser in a fast-paced spaceship game.
class ToAudioPool {
late AudioCache cache;
// Map<String, AudioPlayer> currentPlayers = {};
Set<AudioPlayer> currentPlayers = {};
Map<AudioPlayer, StreamSubscription<void>> subscriptions = {};
Set<AudioPlayer> availablePlayers = {};
String sound;
bool repeating;
int minPlayers, maxPlayers;
final Lock _lock = Lock();
ToAudioPool(this.sound,
{this.repeating = false,
this.maxPlayers = 1,
this.minPlayers = 1,
String prefix = 'assets/audio/sfx/'}) {
cache = AudioCache(prefix: prefix);
}
Future init() async {
for (int i = 0; i < minPlayers; i++) {
availablePlayers.add(await _createNewAudioPlayer());
}
}
Future<void> start({double volume = 1.0}) async {
return _lock.synchronized(() async {
if (availablePlayers.isEmpty) {
availablePlayers.add(await _createNewAudioPlayer());
}
final AudioPlayer player = availablePlayers.elementAt(0);
availablePlayers.remove(player);
currentPlayers.add(player);
await player.setVolume(volume);
await player.resume();
// subscription
subscriptions[player]= player.onPlayerCompletion.listen((_) {
if (repeating) {
player.resume();
} else {
stopPlayer(player);
}
});
});
}
void stopPlayer(AudioPlayer player){
_lock.synchronized(() async {
currentPlayers.remove(player);
subscriptions[player]?.cancel();
await player.stop();
if (availablePlayers.length >= maxPlayers) {
await player.release();
} else {
availablePlayers.add(player);
}
});
}
Future<AudioPlayer> _createNewAudioPlayer() async {
final AudioPlayer player = AudioPlayer();
final String url = (await cache.load(sound)).path;
await player.setUrl(url);
await player.setReleaseMode(ReleaseMode.STOP);
return player;
}
}
A PR would be awesome! Then it would be easier to compare both versions to see what was the issue and the changes proposed.
I'm closing this as inactive, it's most likely solved in the new version of AudioPlayers anyways.