#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:
1 parent
5790f9b2f6
commit
75fbcc5346
2 files changed
+25
-21
No files matched your search
@@ -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
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user