Skip to content

Release texture players when the iOS scene disconnects - #409

Open
JulienDev wants to merge 1 commit into
wang-bin:masterfrom
JulienDev:fix/ios-scene-disconnect
Open

JulienDev wants to merge 1 commit into
wang-bin:masterfrom
JulienDev:fix/ios-scene-disconnect

Conversation

@JulienDev

Copy link
Copy Markdown
Contributor

Summary

Fixes the remaining case of #181 for apps using the UIScene lifecycle.

  • Register FvpPlugin with addSceneDelegate: when the Flutter SDK provides it.
  • Clear the texture players in sceneDidDisconnect:, as applicationWillTerminate: already does since fix crash when killing app on iOS #221.

With UIScene, FlutterViewController calls destroyContext on UISceneDidDisconnectNotification, so the engine shell is reset before applicationWillTerminate: reaches plugins. If a player is still rendering, typically with background audio when the app is killed from the app switcher, mdk.vdec calls textureFrameAvailable: on a null shell. FlutterSceneDelegate notifies scene delegates first, so the players are released while the engine is still alive. The registration is guarded by __has_include(<Flutter/FlutterSceneLifeCycle.h>), @available(iOS 13.0, *) and respondsToSelector:; older Flutter SDKs and macOS are unchanged.

The crash came back in my app when I adopted the UIScene lifecycle (FlutterSceneDelegate, Flutter 3.41.9). From 16 September to 10 October 2026, Crashlytics recorded 1,827 fatal events affecting 1,279 users on iPhone and 601 events affecting 399 users on iPad, all on mdk.vdec right after the app moves to the background. An earlier release without scenes has no event for 13,363 users over 90 days.

Thread: mdk.vdec0 (crashed) EXC_BAD_ACCESS KERN_INVALID_ADDRESS at 0x6c8
flutter::Shell::GetPlatformView()
-[FlutterEngine textureFrameAvailable:]
-[FlutterTextureRegistryRelay textureFrameAvailable:]
TexturePlayer::TexturePlayer(...)::'lambda'(void*)::operator()  FvpPlugin.mm:105
mdk::Player::setRenderCallback(...)::'lambda'(void*, void*)::__invoke

Reproduction

fvp-scene-disconnect-repro.zip contains a flutter create project (Flutter 3.41.9, fvp v0.39.0, MDK 0.39.0) with background audio and allowBackgroundPlayback: true, synthetic HLS media and a loop script. With REPRO_DESTROY_SCENE=1, the app destroys its scene session 10 s after playback starts; SpringBoard handles it as "killed from app switcher". The iPhone simulator refuses requestSceneSessionDestruction, so the steps use an iPad simulator.

scripts/make_media.sh
python3 -m http.server 8899 --bind 127.0.0.1 --directory media &
flutter pub get && flutter build ios --simulator --debug
xcrun simctl install <ipad-udid> build/ios/iphonesimulator/Runner.app
scripts/loop_repro.sh <ipad-udid> 12

The script prints the exit status reported by launchd: (2, 11, 11) is the SIGSEGV above, (0, 0, 0) a clean exit. README.md has the full steps and the Xcode 27 adjustments to the template.

Testing

  • iPad Pro 13-inch (M5) simulator, iOS 27.0, Xcode 27.0: v0.39.0 crashed in 11/12 runs; this branch exited cleanly in 24/24 runs. I ran the README from a clean copy of the archive for both refs.

I haven't tested a real app-switcher kill on a physical device yet. Apps whose scene delegate doesn't forward to FlutterSceneDelegate, and add-to-app hosts that dismiss their FlutterViewController, still destroy the engine without notifying plugins; a null-shell check in -[FlutterEngine textureFrameAvailable:] would cover those in Flutter.

With the UIScene lifecycle, FlutterViewController destroys the engine on
UISceneDidDisconnectNotification, before applicationWillTerminate: reaches
plugins. A player that keeps rendering (e.g. background audio when the app is
killed from the app switcher) then calls textureFrameAvailable on a null shell.

Register as a scene delegate and clear the players in sceneDidDisconnect:,
which Flutter forwards before the engine is torn down.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant