#5 fix camera video preview after shooting

- Claim the picker-owned temp movie synchronously inside the picker
  delegate with FileManager.moveItem before returning, so the picker's
  post-dismissal cleanup can no longer delete the source while a
  background copy is reading it.
- Deliver the captured movie to the staging flow immediately; heavy
  staging and thumbnail work still runs off the main thread.
- Add a verify.sh invariant for the synchronous movie claim.
This commit is contained in:
wt committed 2026-08-29 04:06:33 +07:00
1 parent 5790f9b2f6
commit 75fbcc5346
2 files changed
+25 -21

No files matched your search

+4
View File
@@ -344,6 +344,10 @@ grep -q 'onDismissRequest' Sources/Shared/UI/ChatView.swift || {
echo "The camera cover must dismiss through the SwiftUI binding"
exit 1
}
grep -q 'moveItem(at: sourceURL' Sources/Shared/UI/SystemPhotoCameraView.swift || {
echo "The picker movie must be claimed synchronously before dismissal cleanup"
exit 1
}
grep -q 'ArchiveSyncCursorPolicy.requestPosition' Sources/Shared/XMPP/XMPPService.swift || {
echo "MAM reconnects must prefer the durable archive UID over timestamps"
exit 1
+21 -21
View File
@@ -115,29 +115,29 @@ struct SystemPhotoCameraView: UIViewControllerRepresentable {
}
}
/// Claims the picker-owned temporary movie synchronously, while the
/// picker is still alive: UIImagePickerController can delete its
/// temporary file as soon as it is dismissed, so a background copy
/// races that cleanup and fails with an unreadable source. The move
/// is a same-volume rename and does not block the main thread.
private func prepareMovie(from sourceURL: URL) {
let completion = onMedia
DispatchQueue.global(qos: .userInitiated).async {
let result: Result<CapturedCameraMedia, Error>
do {
let fileExtension = sourceURL.pathExtension.isEmpty
? "mov"
: sourceURL.pathExtension
let destination = try Self.destinationURL(extension: fileExtension)
try FileManager.default.copyItem(at: sourceURL, to: destination)
let values = try destination.resourceValues(
forKeys: [.fileSizeKey, .isRegularFileKey]
)
guard values.isRegularFile == true,
(values.fileSize ?? 0) > 0 else {
try? FileManager.default.removeItem(at: destination)
throw SystemCameraError.emptyMovie
}
result = .success(CapturedCameraMedia(url: destination, kind: .video))
} catch {
result = .failure(error)
do {
let fileExtension = sourceURL.pathExtension.isEmpty
? "mov"
: sourceURL.pathExtension
let destination = try Self.destinationURL(extension: fileExtension)
try FileManager.default.moveItem(at: sourceURL, to: destination)
let values = try destination.resourceValues(
forKeys: [.fileSizeKey, .isRegularFileKey]
)
guard values.isRegularFile == true,
(values.fileSize ?? 0) > 0 else {
try? FileManager.default.removeItem(at: destination)
throw SystemCameraError.emptyMovie
}
DispatchQueue.main.async { completion(result) }
onMedia(.success(CapturedCameraMedia(url: destination, kind: .video)))
} catch {
onMedia(.failure(error))
}
}