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:
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)
+ }
+ }
+}