Skip to content

fix(ios): two device-blocking crashes, and main compiles again - #153

Merged
KyleKun merged 3 commits into
KyleKun:mainfrom
alexis-morain:fix/ios-device-crashes
Aug 7, 2026
Merged

KyleKun merged 3 commits into
KyleKun:mainfrom
alexis-morain:fix/ios-device-crashes

Conversation

@alexis-morain

Copy link
Copy Markdown
Contributor

Follow-up to #152, from the first real device pass (iPhone 16e, iOS 26.6). Three fixes, each verified on the phone:

1. Launch crash: duplicate plugin registration

The app aborted at startup with Duplicate plugin key: VideoThumbnailPlugin. The project depends on both video_thumbnail (direct git fork) and get_thumbnail_video (transitively through video_trimmer), which is a maintained fork of the same plugin. On Android their registrant keys are distinct FQCNs so the duplication is harmless; on iOS both register the same ObjC class and the engine refuses the second one. Fixed by dropping the direct video_thumbnail dependency and pointing the two call sites at get_thumbnail_video, which video_trimmer forces into the build anyway. Same VideoThumbnail.thumbnailData API, imports only.

2. Save crash: ffmpeg-kit's setFontDirectory and NSNull

Tapping save aborted with -[NSNull allKeys]: unrecognized selector. setFontDirectory(fontPath) without the optional font map sends null across the method channel, which lands in ObjC as NSNull, and ffmpeg-kit iOS calls allKeys on it unchecked. Android tolerates the null, which is why this never surfaced. Fixed by passing an explicit empty map. Worth an upstream report to ffmpeg-kit eventually.

With these two fixes the core loop works on a real iPhone: record (camera outputs HEVC), save with the burned-in date, and VideoToolbox transcodes to H.264 exactly as the runtime encoder selection intends. Reinstalling over an existing install kept every video visible — the container UUID did change between installs, so that path fix is doing its job.

3. main compiles again, and the photo flow gets the same treatment

#150 and #152 crossed in flight: save_photo_button.dart still called StorageUtils.deleteFileWithMediaStore, which #152 had replaced, so flutter analyze failed on main. Since the photo flow is a pre-refactor clone of SaveButton, it now goes through the same seams: AppPaths for output paths (with the missing-profile bounds check), VideoEncoder instead of hardcoded libx264, the empty font map, and the unused MediaStore instance removed.

Still known-broken on iOS, next PR

Subtitles editing and movie generation fail on device (ffmpeg session errors, logs captured). I'm on it — flagging so you know the current state before tagging anything.

flutter analyze clean, 19/19 tests, release build runs on device.

🤖 Generated with Claude Code

alexis-morain and others added 3 commits August 7, 2026 16:57
Verified on an iPhone 16e running iOS 26.6: the app aborted during plugin
registration with 'Duplicate plugin key: VideoThumbnailPlugin'. The project
depended on both video_thumbnail (direct git fork) and get_thumbnail_video
(transitively through video_trimmer), which is a maintained fork of the same
plugin. On Android their registrant keys are distinct fully-qualified class
names, so the duplication went unnoticed; on iOS both register the ObjC class
VideoThumbnailPlugin and the engine refuses the second one at startup.

Keep get_thumbnail_video, which video_trimmer already forces into the build,
and drop the direct video_thumbnail dependency. Both packages expose the same
VideoThumbnail.thumbnailData API, so the two call sites only change imports.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Verified on an iPhone 16e running iOS 26.6: tapping save aborted the app
with '-[NSNull allKeys]: unrecognized selector'. SaveButton called
FFmpegKitConfig.setFontDirectory(fontPath) without the optional font name
map; the null map crosses the method channel as NSNull, and the iOS side of
ffmpeg-kit calls allKeys on it without checking. The Android implementation
tolerates the null, which is why this never showed up before.

Pass an empty map instead. With this fix the whole save pipeline works on
device: the camera records HEVC and VideoToolbox transcodes to H.264 exactly
as the runtime encoder selection intends.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… again

The KyleKun#150 photo feature and the KyleKun#152 iOS refactor crossed in flight:
save_photo_button still called StorageUtils.deleteFileWithMediaStore, which
KyleKun#152 replaced with StorageUtils.deleteVideo, so main no longer passed
flutter analyze. Beyond the compile fix, the photo flow now goes through the
same seams as the video flow, which it had been cloned from pre-refactor:

- output paths come from AppPaths instead of a persisted appPath, with the
  same missing-profile bounds check SaveButton got
- the encoder comes from VideoEncoder instead of a hardcoded libx264, so a
  photo saved on iOS encodes with VideoToolbox
- setFontDirectory gets an explicit empty font map, the same iOS NSNull
  crash fixed in SaveButton
- the unused MediaStore instance and the now-redundant direct ffmpeg-kit
  imports are gone

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@KyleKun
KyleKun merged commit c628591 into KyleKun:main Aug 7, 2026
2 checks passed
alexis-morain added a commit to alexis-morain/one_second_diary that referenced this pull request Aug 7, 2026
The doc claimed nothing had been tested on hardware. Everything in KyleKun#153 was
verified on an iPhone 16e (iOS 26.6), so say what passed and, more usefully,
name the two features that still fail there. Also flag the missing Runner
privacy manifest, which blocks an App Store submission.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@alexis-morain

Copy link
Copy Markdown
Contributor Author

Closing the loop on the ffmpeg-kit note in the description: reported upstream as sk3llo/ffmpeg_kit_flutter#167.

Traced the full chain while writing it up. The Dart optional argument defaults to null, the platform channel sends it as a map value, and Flutter's standard codec substitutes kCFNull for null map values (FlutterStandardCodecHelper.cc), so it lands in ObjC as NSNull rather than nil. The plugin's dispatch guards the font directory but never the map, and FFmpegKitConfig then enumerates it with allKeys. A genuine nil would have been harmless there, since a for-in over nil is a no-op, so NSNull is the only fatal case. setFontDirectoryList has the same defect and funnels into the same line. Still present on their main (4.6.2).

Our empty-map workaround is correct and not a papering-over, but this is worth knowing if the dependency ever moves.

KyleKun pushed a commit that referenced this pull request Aug 7, 2026
…tatus (#155)

* docs: clarify the MediaGallery.save destinationPath contract

Review feedback on #152: the doc said the file is published as
destinationPath, but AndroidMediaGallery ignores that parameter entirely,
MediaStore derives the destination from the current album and the temp
file name. Spell out both platforms and the implicit contract that
callers must name the temp file like the destination.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(ios): record the device pass, and what still fails on it

The doc claimed nothing had been tested on hardware. Everything in #153 was
verified on an iPhone 16e (iOS 26.6), so say what passed and, more usefully,
name the two features that still fail there. Also flag the missing Runner
privacy manifest, which blocks an App Store submission.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 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.

2 participants