Skip to content
Closed
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#### 4.0.13 (20 Jul 2026)
- fix: Android hot restart now clears stale Dart callback registrations. Hot restart replaces the isolate without detaching plugins, so the registered `NativeCallable`s silently went stale
- Android: the plugin now does no native work at app startup. Plugin registration and `onAttachedToEngine` are pure Java bookkeeping; the native library is loaded lazily, only when an engine-lifecycle hook actually has to call into it. Previously a static initializer pulled the whole library onto the main thread during app launch even for apps that never played a sound, and a load failure there crashed plugin registration
- fix: Waveform audio sources do not match engine sample rate #501. Thanks to @Colton127
- Android now stops the audio device when idle (no active voices) like every other platform, releasing the audioserver `AudioMix` partial wakelock #250; use `setAudioDeviceIdleTimeout()` to keep it running
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package flutter.soloud.flutter_soloud;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.plugins.FlutterPlugin;

public final class FlutterSoloudPlugin implements FlutterPlugin {
Expand All @@ -24,7 +26,9 @@ public final class FlutterSoloudPlugin implements FlutterPlugin {
private static native boolean
nativeClearDartCallbackRegistrationsForEngine(long engineId);

private Long engineId;
@Nullable private FlutterEngine flutterEngine;
@Nullable private Long engineId;
@Nullable private FlutterEngine.EngineLifecycleListener lifecycleListener;

private static synchronized boolean ensureNativeLibraryLoaded() {
if (!nativeLibraryLoadAttempted) {
Expand All @@ -49,20 +53,56 @@ public void onAttachedToEngine(
) {
// Deliberately does no native work. This runs during app launch for
// every app that depends on the plugin, whether or not it ever uses
// SoLoud, so it must stay pure Java bookkeeping.
engineId = binding.getFlutterEngine().getEngineId();
// SoLoud, so it must stay pure Java bookkeeping: read the engine id and
// register a listener.
final FlutterEngine engine = binding.getFlutterEngine();
flutterEngine = engine;
engineId = engine.getEngineId();

lifecycleListener = new FlutterEngine.EngineLifecycleListener() {
@Override
public void onPreEngineRestart() {
// Hot restart replaces the Dart isolate but does not detach
// plugins, and the engine id is unchanged -- so without this the
// registered NativeCallables silently go stale. Only the bridges
// are cleared: the new isolate's init() finds the native engine
// still initialized and deinits it itself.
clearDartCallbackRegistrations();
}

@Override
public void onEngineWillDestroy() {
// Fires just before the plugin registry is destroyed, while the
// engine is still valid.
clearDartCallbackRegistrations();
}
};
engine.addEngineLifecycleListener(lifecycleListener);
}

@Override
public void onDetachedFromEngine(
@NonNull FlutterPluginBinding binding
) {
final Long detachedEngineId = engineId;
final FlutterEngine engine = flutterEngine;
final FlutterEngine.EngineLifecycleListener listener = lifecycleListener;

if (engine != null && listener != null) {
engine.removeEngineLifecycleListener(listener);
}

clearDartCallbackRegistrations();

flutterEngine = null;
engineId = null;
lifecycleListener = null;
}

if (detachedEngineId == null || !ensureNativeLibraryLoaded()) {
private void clearDartCallbackRegistrations() {
final Long id = engineId;
if (id == null || !ensureNativeLibraryLoaded()) {
return;
}
nativeClearDartCallbackRegistrationsForEngine(detachedEngineId);
nativeClearDartCallbackRegistrationsForEngine(id);
}
}