fix: prevent iOS/macOS thumbnail decoder wedge that froze the editor#175
Merged
Conversation
getThumbnails used a single unsynchronized 'completed' counter as both the write index and the completion count. AVAssetImageGenerator's generateCGImagesAsynchronously(forTimes:) does not guarantee serial or in-order callbacks (observed concurrent on iOS 26.5), so the counter could miss == totalCount and never resume the continuation, leaving the Dart Future hung forever and wedging thumbnail generation for the whole session; it also mapped frames to the wrong timestamps and compacted out failures. Frames are now placed by their requested timestamp (matched to the position in the requested times), all shared state is mutated under a lock, and the continuation resumes exactly once even on failure/cancel. Failed frames are returned as an empty, positionally-stable entry instead of being dropped. iOS 16+/macOS 13+ use the ordered AVAssetImageGenerator.images(for:) async sequence; older OSes keep the hardened callback path. Adds RunnerTests (iOS + macOS) that verify timestamp->frame alignment under shuffled/concurrent delivery and an integration test covering large shuffled batches and repeated batches with no wedge. Bumps to 2.5.1.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Loading many clips (40+) into the editor could freeze it "step by step". The native iOS/macOS thumbnail generator's
getThumbnailscompletion handler used a single unsynchronizedcompletedcounter as both the write index into the results buffer and the completion count.AVAssetImageGenerator.generateCGImagesAsynchronously(forTimes:)does not guarantee serial or in-order callbacks — on modern iOS (observed on iOS 26.5) they can fire concurrently. That caused:completed += 1updates could miss== totalCount, socontinuation.resumenever fired. The DartgetThumbnailscall has no timeout, so its Future hung forever. The consuming app runs every thumbnail batch through one global serial queue whose release is in afinally, so a single wedged call blocked thumbnail generation for every clip for the rest of the session.compactMap { $0 }dropped failed frames, shifting all later frames.Fix
requestedTime(matched to its position in the requested times), never from a running counter.Data()instead of being compacted out, soresult[i]maps index-for-index to the requestedtimestamps[i].AVAssetImageGenerator.images(for:)async sequence; older OSes keep the hardened callback path. Also guards against an empty-timeshang.Public method signature and the
onProgress/onComplete/onErrorcontract are unchanged. Applied to both the UIKit and AppKit paths.Behavior note
Individual frame failures now surface as empty entries via
onCompleterather than firingonErrorper frame (which previously also double-delivered the method-channel result alongsideonComplete).onErrorstill covers operation-level failures (e.g. file not found).Tests
video_thumbnail_test.dart): a large shuffled batch must stay index-aligned to its timestamps (sorted-vs-shuffled byte comparison), and many sequential batches must never wedge.Verified on macOS
RunnerTests: all 10 pass (incl. 3 new).flutter test integration_test/video_thumbnail_test.dart -d macos: the new wedge-regression group passes.macOS 26.5 exercises the ordered path — the same path iOS 26.5 uses in production.
Release
Bumps package to 2.5.1 with a changelog entry.