damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

commit 88b3c6fe8d70624f6484044ad7fd5e1e672cdec4
parent 476f52562a70c2615ad084640dd1a0ba5c4c12e3
Author: Suhail Saqan <suhail.saqan@gmail.com>
Date:   Tue, 19 Sep 2023 13:29:40 -0700

camera: add PhotoCaptureProcessor and VideoCaptureProcessor

Diffstat:
Mdamus.xcodeproj/project.pbxproj | 8++++++++
Adamus/Models/Camera/PhotoCaptureProcessor.swift | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adamus/Models/Camera/VideoCaptureProcessor.swift | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 176 insertions(+), 0 deletions(-)

diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj @@ -416,6 +416,8 @@ 9C83F89329A937B900136C08 /* TextViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C83F89229A937B900136C08 /* TextViewWrapper.swift */; }; 9CA876E229A00CEA0003B9A3 /* AttachMediaUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CA876E129A00CE90003B9A3 /* AttachMediaUtility.swift */; }; BA37598A2ABCCDE40018D73B /* ImageResizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA3759892ABCCDE30018D73B /* ImageResizer.swift */; }; + BA37598D2ABCCE500018D73B /* PhotoCaptureProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA37598B2ABCCE500018D73B /* PhotoCaptureProcessor.swift */; }; + BA37598E2ABCCE500018D73B /* VideoCaptureProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA37598C2ABCCE500018D73B /* VideoCaptureProcessor.swift */; }; BA4AB0AE2A63B9270070A32A /* AddEmojiView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA4AB0AD2A63B9270070A32A /* AddEmojiView.swift */; }; BA4AB0B02A63B94D0070A32A /* EmojiListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA4AB0AF2A63B94D0070A32A /* EmojiListItemView.swift */; }; BA693074295D649800ADDB87 /* UserSettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA693073295D649800ADDB87 /* UserSettingsStore.swift */; }; @@ -1096,6 +1098,8 @@ 9C83F89229A937B900136C08 /* TextViewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextViewWrapper.swift; sourceTree = "<group>"; }; 9CA876E129A00CE90003B9A3 /* AttachMediaUtility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachMediaUtility.swift; sourceTree = "<group>"; }; BA3759892ABCCDE30018D73B /* ImageResizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageResizer.swift; sourceTree = "<group>"; }; + BA37598B2ABCCE500018D73B /* PhotoCaptureProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoCaptureProcessor.swift; sourceTree = "<group>"; }; + BA37598C2ABCCE500018D73B /* VideoCaptureProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoCaptureProcessor.swift; sourceTree = "<group>"; }; BA4AB0AD2A63B9270070A32A /* AddEmojiView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddEmojiView.swift; sourceTree = "<group>"; }; BA4AB0AF2A63B94D0070A32A /* EmojiListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiListItemView.swift; sourceTree = "<group>"; }; BA693073295D649800ADDB87 /* UserSettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettingsStore.swift; sourceTree = "<group>"; }; @@ -2258,6 +2262,8 @@ isa = PBXGroup; children = ( BA3759892ABCCDE30018D73B /* ImageResizer.swift */, + BA37598B2ABCCE500018D73B /* PhotoCaptureProcessor.swift */, + BA37598C2ABCCE500018D73B /* VideoCaptureProcessor.swift */, ); path = Camera; sourceTree = "<group>"; @@ -2713,6 +2719,7 @@ 4C2859602A12A2BE004746F7 /* SupporterBadge.swift in Sources */, 4C1A9A2A29DDF54400516EAC /* DamusVideoPlayer.swift in Sources */, 4CA352A22A76AEC5003BB08B /* LikedNotify.swift in Sources */, + BA37598D2ABCCE500018D73B /* PhotoCaptureProcessor.swift in Sources */, 4C9146FD2A2A87C200DDEA40 /* wasm.c in Sources */, 4C75EFAF28049D350006080F /* NostrFilter.swift in Sources */, 4C3EA64C28FF59AC00C48A62 /* bech32_util.c in Sources */, @@ -2877,6 +2884,7 @@ 4C9AA14A2A4587A6003F49FD /* NotificationStatusModel.swift in Sources */, 4CB9D4A72992D02B00A9A7E4 /* ProfileNameView.swift in Sources */, 4CE4F0F429D779B5005914DB /* PostBox.swift in Sources */, + BA37598E2ABCCE500018D73B /* VideoCaptureProcessor.swift in Sources */, 4C9B0DF32A65C46800CBDA21 /* ProfileEditButton.swift in Sources */, 4C32B95F2A9AD44700DC3548 /* Enum.swift in Sources */, 4C2859622A12A7F0004746F7 /* GoldSupportGradient.swift in Sources */, diff --git a/damus/Models/Camera/PhotoCaptureProcessor.swift b/damus/Models/Camera/PhotoCaptureProcessor.swift @@ -0,0 +1,91 @@ +// +// PhotoCaptureProcessor.swift +// damus +// +// Created by Suhail Saqan on 8/5/23. +// + +import Foundation +import Photos + +class PhotoCaptureProcessor: NSObject { + private(set) var requestedPhotoSettings: AVCapturePhotoSettings + private(set) var photoOutput: AVCapturePhotoOutput? + + lazy var context = CIContext() + var photoData: Data? + private var maxPhotoProcessingTime: CMTime? + + private let willCapturePhotoAnimation: () -> Void + private let completionHandler: (PhotoCaptureProcessor) -> Void + private let photoProcessingHandler: (Bool) -> Void + + init(with requestedPhotoSettings: AVCapturePhotoSettings, + photoOutput: AVCapturePhotoOutput?, + willCapturePhotoAnimation: @escaping () -> Void, + completionHandler: @escaping (PhotoCaptureProcessor) -> Void, + photoProcessingHandler: @escaping (Bool) -> Void) { + self.requestedPhotoSettings = requestedPhotoSettings + self.willCapturePhotoAnimation = willCapturePhotoAnimation + self.completionHandler = completionHandler + self.photoProcessingHandler = photoProcessingHandler + self.photoOutput = photoOutput + } + + func capturePhoto(settings: AVCapturePhotoSettings) { + if let photoOutput = self.photoOutput { + photoOutput.capturePhoto(with: settings, delegate: self) + } + } +} + +extension PhotoCaptureProcessor: AVCapturePhotoCaptureDelegate { + func photoOutput(_ output: AVCapturePhotoOutput, willBeginCaptureFor resolvedSettings: AVCaptureResolvedPhotoSettings) { + maxPhotoProcessingTime = resolvedSettings.photoProcessingTimeRange.start + resolvedSettings.photoProcessingTimeRange.duration + } + + func photoOutput(_ output: AVCapturePhotoOutput, willCapturePhotoFor resolvedSettings: AVCaptureResolvedPhotoSettings) { + DispatchQueue.main.async { + self.willCapturePhotoAnimation() + } + + guard let maxPhotoProcessingTime = maxPhotoProcessingTime else { + return + } + + DispatchQueue.main.async { + self.photoProcessingHandler(true) + } + + let oneSecond = CMTime(seconds: 2, preferredTimescale: 1) + if maxPhotoProcessingTime > oneSecond { + DispatchQueue.main.async { + self.photoProcessingHandler(true) + } + } + } + + func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { + DispatchQueue.main.async { + self.photoProcessingHandler(false) + } + + if let error = error { + print("Error capturing photo: \(error)") + } else { + photoData = photo.fileDataRepresentation() + + } + } + + func photoOutput(_ output: AVCapturePhotoOutput, didFinishCaptureFor resolvedSettings: AVCaptureResolvedPhotoSettings, error: Error?) { + if let error = error { + print("Error capturing photo: \(error)") + return + } + + DispatchQueue.main.async { + self.completionHandler(self) + } + } +} diff --git a/damus/Models/Camera/VideoCaptureProcessor.swift b/damus/Models/Camera/VideoCaptureProcessor.swift @@ -0,0 +1,77 @@ +// +// VideoCaptureProcessor.swift +// damus +// +// Created by Suhail Saqan on 8/5/23. +// + +import Foundation +import AVFoundation +import Photos + +class VideoCaptureProcessor: NSObject { + private(set) var movieOutput: AVCaptureMovieFileOutput? + + private let beginHandler: () -> Void + private let completionHandler: (VideoCaptureProcessor, URL) -> Void + private let videoProcessingHandler: (Bool) -> Void + private var session: AVCaptureSession? + + init(movieOutput: AVCaptureMovieFileOutput?, + beginHandler: @escaping () -> Void, + completionHandler: @escaping (VideoCaptureProcessor, URL) -> Void, + videoProcessingHandler: @escaping (Bool) -> Void) { + self.beginHandler = beginHandler + self.completionHandler = completionHandler + self.videoProcessingHandler = videoProcessingHandler + self.movieOutput = movieOutput + } + + func startCapture(session: AVCaptureSession) { + if let movieOutput = self.movieOutput, session.isRunning { + let outputFileURL = uniqueOutputFileURL() + movieOutput.startRecording(to: outputFileURL, recordingDelegate: self) + } + } + + func stopCapture() { + if let movieOutput = self.movieOutput { + if movieOutput.isRecording { + movieOutput.stopRecording() + } + } + } + + private func uniqueOutputFileURL() -> URL { + let tempDirectory = FileManager.default.temporaryDirectory + let fileName = UUID().uuidString + ".mov" + return tempDirectory.appendingPathComponent(fileName) + } +} + +extension VideoCaptureProcessor: AVCaptureFileOutputRecordingDelegate { + + func fileOutput(_ output: AVCaptureFileOutput, didStartRecordingTo fileURL: URL, from connections: [AVCaptureConnection]) { + DispatchQueue.main.async { + self.beginHandler() + } + } + + func fileOutput(_ output: AVCaptureFileOutput, willFinishRecordingTo fileURL: URL, from connections: [AVCaptureConnection]) { + DispatchQueue.main.async { + self.videoProcessingHandler(true) + } + } + + func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) { + if let error = error { + print("Error capturing video: \(error)") + return + } + + DispatchQueue.main.async { + self.completionHandler(self, outputFileURL) + self.videoProcessingHandler(false) + } + } +}