Crash bij verlaten videodia: setState tijdens gelockte widgettree (VideoPlayheadBus.clearFor) #1114

Closed
opened 2026-08-02 08:33:47 +00:00 by brenno · 0 comments
Owner

Symptoom

Bij het weg-navigeren van een dia met (afspelende) video gooit Flutter tijdens het tekenen van het frame:

setState() or markNeedsBuild() called when widget tree was locked.
This ValueListenableBuilder<VideoPlayhead?> widget cannot be marked as needing to build
because the framework is locked.

De crash komt uit VideoPlayheadBus.clearFor_VideoPreviewState.dispose tijdens BuildOwner.finalizeTree.

Oorzaak

VideoPlayheadBus.clearFor (lib/widgets/slides/video_playhead_bus.dart) zet current.value = null. Dat gebeurt vanuit dispose() van _VideoPreviewState én _VideoEmbedPreviewState (lib/widgets/slides/previews/media_previews_video.dart), die draait tijdens finalizeTree — de element-tree is dan gelockt. De video-editor luistert live mee via een ValueListenableBuilder<VideoPlayhead?> (de "Knip hier"-knop in lib/widgets/editors/video_slide_editor.dart); diens setState valt onder de lock en gooit de assertie. Beide dispose-paden raken het, dus de fix hoort in de bus.

Aanpak

Centraal in de bus: draait er een frame (schedulerPhase == persistentCallbacks), stel de clear dan uit tot ná het frame (addPostFrameCallback); anders synchroon wissen. De slideId-guard maakt de uitgestelde clear een no-op als een andere dia intussen zelf een afspeelpunt publiceert, zodat er geen vers afspeelpunt wordt weggegooid.

Regressietest: test/video_playhead_bus_test.dart (reproduceert de exacte assertie zonder de fix, groen met de fix).

## Symptoom Bij het weg-navigeren van een dia met (afspelende) video gooit Flutter tijdens het tekenen van het frame: ``` setState() or markNeedsBuild() called when widget tree was locked. This ValueListenableBuilder<VideoPlayhead?> widget cannot be marked as needing to build because the framework is locked. ``` De crash komt uit `VideoPlayheadBus.clearFor` → `_VideoPreviewState.dispose` tijdens `BuildOwner.finalizeTree`. ## Oorzaak `VideoPlayheadBus.clearFor` (`lib/widgets/slides/video_playhead_bus.dart`) zet `current.value = null`. Dat gebeurt vanuit `dispose()` van `_VideoPreviewState` én `_VideoEmbedPreviewState` (`lib/widgets/slides/previews/media_previews_video.dart`), die draait tijdens `finalizeTree` — de element-tree is dan gelockt. De video-editor luistert live mee via een `ValueListenableBuilder<VideoPlayhead?>` (de "Knip hier"-knop in `lib/widgets/editors/video_slide_editor.dart`); diens `setState` valt onder de lock en gooit de assertie. Beide dispose-paden raken het, dus de fix hoort in de bus. ## Aanpak Centraal in de bus: draait er een frame (`schedulerPhase == persistentCallbacks`), stel de clear dan uit tot ná het frame (`addPostFrameCallback`); anders synchroon wissen. De `slideId`-guard maakt de uitgestelde clear een no-op als een andere dia intussen zelf een afspeelpunt publiceert, zodat er geen vers afspeelpunt wordt weggegooid. Regressietest: `test/video_playhead_bus_test.dart` (reproduceert de exacte assertie zonder de fix, groen met de fix).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
LibreKAT/Ocideck#1114
No description provided.