damus

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

CameraService.swift (27639B)


      1 //
      2 //  CameraService.swift
      3 //  Campus
      4 //
      5 //  Created by Suhail Saqan on 8/5/23.
      6 //
      7 
      8 import Foundation
      9 import Combine
     10 import AVFoundation
     11 import Photos
     12 import UIKit
     13 
     14 public struct Thumbnail: Identifiable, Equatable {
     15     public var id: String
     16     public var type: CameraMediaType
     17     public var url: URL
     18 
     19     public init(id: String = UUID().uuidString, type: CameraMediaType, url: URL) {
     20         self.id = id
     21         self.type = type
     22         self.url = url
     23     }
     24 
     25     public var thumbnailImage: UIImage? {
     26         switch type {
     27         case .image:
     28             return ImageResizer(targetWidth: 100).resize(at: url)
     29         case .video:
     30             return generateVideoThumbnail(for: url)
     31         }
     32     }
     33 }
     34 
     35 public struct AlertError {
     36     public var title: String = ""
     37     public var message: String = ""
     38     public var primaryButtonTitle = "Accept"
     39     public var secondaryButtonTitle: String?
     40     public var primaryAction: (() -> ())?
     41     public var secondaryAction: (() -> ())?
     42 
     43     public init(title: String = "", message: String = "", primaryButtonTitle: String = "Accept", secondaryButtonTitle: String? = nil, primaryAction: (() -> ())? = nil, secondaryAction: (() -> ())? = nil) {
     44         self.title = title
     45         self.message = message
     46         self.primaryAction = primaryAction
     47         self.primaryButtonTitle = primaryButtonTitle
     48         self.secondaryAction = secondaryAction
     49     }
     50 }
     51 
     52 func generateVideoThumbnail(for videoURL: URL) -> UIImage? {
     53     let asset = AVAsset(url: videoURL)
     54     let imageGenerator = AVAssetImageGenerator(asset: asset)
     55     imageGenerator.appliesPreferredTrackTransform = true
     56 
     57     do {
     58         let cgImage = try imageGenerator.copyCGImage(at: .zero, actualTime: nil)
     59         return UIImage(cgImage: cgImage)
     60     } catch {
     61         print("Error generating thumbnail: \(error)")
     62         return nil
     63     }
     64 }
     65 
     66 public enum CameraMediaType {
     67     case image
     68     case video
     69 }
     70 
     71 public struct MediaItem {
     72     let url: URL
     73     let type: CameraMediaType
     74 }
     75 
     76 public class CameraService: NSObject, Identifiable {
     77     public let session = AVCaptureSession()
     78 
     79     public var isSessionRunning = false
     80     public var isConfigured = false
     81     var setupResult: SessionSetupResult = .success
     82 
     83     public var alertError: AlertError = AlertError()
     84 
     85     @Published public var flashMode: AVCaptureDevice.FlashMode = .off
     86     @Published public var shouldShowAlertView = false
     87     @Published public var isPhotoProcessing = false
     88     @Published public var captureMode: CameraMediaType = .image
     89     @Published public var isRecording: Bool = false
     90 
     91     @Published public var willCapturePhoto = false
     92     @Published public var isCameraButtonDisabled = false
     93     @Published public var isCameraUnavailable = false
     94     @Published public var thumbnail: Thumbnail?
     95     @Published public var mediaItems: [MediaItem] = []
     96 
     97     public let sessionQueue = DispatchQueue(label: "io.damus.camera")
     98 
     99     @objc dynamic public var videoDeviceInput: AVCaptureDeviceInput!
    100     @objc dynamic public var audioDeviceInput: AVCaptureDeviceInput!
    101 
    102     public let videoDeviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera, .builtInDualCamera, .builtInTrueDepthCamera], mediaType: .video, position: .unspecified)
    103 
    104     public let photoOutput = AVCapturePhotoOutput()
    105 
    106     public let movieOutput = AVCaptureMovieFileOutput()
    107 
    108     var videoCaptureProcessor: VideoCaptureProcessor?
    109     var photoCaptureProcessor: PhotoCaptureProcessor?
    110 
    111     public var keyValueObservations = [NSKeyValueObservation]()
    112 
    113     override public init() {
    114         super.init()
    115 
    116         DispatchQueue.main.async {
    117             self.isCameraButtonDisabled = true
    118             self.isCameraUnavailable = true
    119         }
    120     }
    121 
    122     enum SessionSetupResult {
    123         case success
    124         case notAuthorized
    125         case configurationFailed
    126     }
    127 
    128     public func configure() {
    129         if !self.isSessionRunning && !self.isConfigured {
    130             sessionQueue.async {
    131                 self.configureSession()
    132             }
    133         }
    134     }
    135 
    136     public func checkForPermissions() {
    137         switch AVCaptureDevice.authorizationStatus(for: .video) {
    138         case .authorized:
    139             break
    140         case .notDetermined:
    141             sessionQueue.suspend()
    142             AVCaptureDevice.requestAccess(for: .video, completionHandler: { granted in
    143                 if !granted {
    144                     self.setupResult = .notAuthorized
    145                 }
    146                 self.sessionQueue.resume()
    147             })
    148 
    149         default:
    150             setupResult = .notAuthorized
    151 
    152             DispatchQueue.main.async {
    153                 self.alertError = AlertError(title: "Camera Access", message: "Damus needs camera and microphone access. Enable in settings.", primaryButtonTitle: "Go to settings", secondaryButtonTitle: nil, primaryAction: {
    154                         UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!,
    155                                                   options: [:], completionHandler: nil)
    156 
    157                 }, secondaryAction: nil)
    158                 self.shouldShowAlertView = true
    159                 self.isCameraUnavailable = true
    160                 self.isCameraButtonDisabled = true
    161             }
    162         }
    163     }
    164 
    165     private func configureSession() {
    166         if setupResult != .success {
    167             return
    168         }
    169 
    170         session.beginConfiguration()
    171 
    172         session.sessionPreset = .high
    173 
    174         // Add video input.
    175         do {
    176             var defaultVideoDevice: AVCaptureDevice?
    177 
    178             if let backCameraDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) {
    179                 // If a rear dual camera is not available, default to the rear wide angle camera.
    180                 defaultVideoDevice = backCameraDevice
    181             } else if let frontCameraDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front) {
    182                 // If the rear wide angle camera isn't available, default to the front wide angle camera.
    183                 defaultVideoDevice = frontCameraDevice
    184             }
    185 
    186             guard let videoDevice = defaultVideoDevice else {
    187                 print("Default video device is unavailable.")
    188                 setupResult = .configurationFailed
    189                 session.commitConfiguration()
    190                 return
    191             }
    192 
    193             let videoDeviceInput = try AVCaptureDeviceInput(device: videoDevice)
    194 
    195             if session.canAddInput(videoDeviceInput) {
    196                 session.addInput(videoDeviceInput)
    197                 self.videoDeviceInput = videoDeviceInput
    198             } else {
    199                 print("Couldn't add video device input to the session.")
    200                 setupResult = .configurationFailed
    201                 session.commitConfiguration()
    202                 return
    203             }
    204 
    205             let audioDevice = AVCaptureDevice.default(for: .audio)
    206             let audioDeviceInput = try AVCaptureDeviceInput(device: audioDevice!)
    207 
    208             if session.canAddInput(audioDeviceInput) {
    209                 session.addInput(audioDeviceInput)
    210                 self.audioDeviceInput = audioDeviceInput
    211             } else {
    212                 print("Couldn't add audio device input to the session.")
    213                 setupResult = .configurationFailed
    214                 session.commitConfiguration()
    215                 return
    216             }
    217 
    218             // Add video output
    219             if session.canAddOutput(movieOutput) {
    220                 session.addOutput(movieOutput)
    221             } else {
    222                 print("Could not add movie output to the session")
    223                 setupResult = .configurationFailed
    224                 session.commitConfiguration()
    225                 return
    226             }
    227         } catch {
    228             print("Couldn't create video device input: \(error)")
    229             setupResult = .configurationFailed
    230             session.commitConfiguration()
    231             return
    232         }
    233 
    234         // Add the photo output.
    235         if session.canAddOutput(photoOutput) {
    236             session.addOutput(photoOutput)
    237 
    238             photoOutput.maxPhotoQualityPrioritization = .quality
    239 
    240         } else {
    241             print("Could not add photo output to the session")
    242             setupResult = .configurationFailed
    243             session.commitConfiguration()
    244             return
    245         }
    246 
    247         session.commitConfiguration()
    248         self.isConfigured = true
    249 
    250         self.start()
    251     }
    252 
    253     private func resumeInterruptedSession() {
    254         sessionQueue.async {
    255             self.session.startRunning()
    256             self.isSessionRunning = self.session.isRunning
    257             if !self.session.isRunning {
    258                 DispatchQueue.main.async {
    259                     self.alertError = AlertError(title: "Camera Error", message: "Unable to resume camera", primaryButtonTitle: "Accept", secondaryButtonTitle: nil, primaryAction: nil, secondaryAction: nil)
    260                     self.shouldShowAlertView = true
    261                     self.isCameraUnavailable = true
    262                     self.isCameraButtonDisabled = true
    263                 }
    264             } else {
    265                 DispatchQueue.main.async {
    266                     self.isCameraUnavailable = false
    267                     self.isCameraButtonDisabled = false
    268                 }
    269             }
    270         }
    271     }
    272 
    273     public func changeCamera() {
    274         DispatchQueue.main.async {
    275             self.isCameraButtonDisabled = true
    276         }
    277 
    278         sessionQueue.async {
    279             let currentVideoDevice = self.videoDeviceInput.device
    280             let currentPosition = currentVideoDevice.position
    281 
    282             let preferredPosition: AVCaptureDevice.Position
    283             let preferredDeviceType: AVCaptureDevice.DeviceType
    284 
    285             switch currentPosition {
    286             case .unspecified, .front:
    287                 preferredPosition = .back
    288                 preferredDeviceType = .builtInWideAngleCamera
    289 
    290             case .back:
    291                 preferredPosition = .front
    292                 preferredDeviceType = .builtInWideAngleCamera
    293 
    294             @unknown default:
    295                 print("Unknown capture position. Defaulting to back, dual-camera.")
    296                 preferredPosition = .back
    297                 preferredDeviceType = .builtInWideAngleCamera
    298             }
    299             let devices = self.videoDeviceDiscoverySession.devices
    300             var newVideoDevice: AVCaptureDevice? = nil
    301 
    302             if let device = devices.first(where: { $0.position == preferredPosition && $0.deviceType == preferredDeviceType }) {
    303                 newVideoDevice = device
    304             } else if let device = devices.first(where: { $0.position == preferredPosition }) {
    305                 newVideoDevice = device
    306             }
    307 
    308             if let videoDevice = newVideoDevice {
    309                 do {
    310                     let videoDeviceInput = try AVCaptureDeviceInput(device: videoDevice)
    311 
    312                     self.session.beginConfiguration()
    313 
    314                     self.session.removeInput(self.videoDeviceInput)
    315 
    316                     if self.session.canAddInput(videoDeviceInput) {
    317                         NotificationCenter.default.removeObserver(self, name: .AVCaptureDeviceSubjectAreaDidChange, object: currentVideoDevice)
    318                         NotificationCenter.default.addObserver(self, selector: #selector(self.subjectAreaDidChange), name: .AVCaptureDeviceSubjectAreaDidChange, object: videoDeviceInput.device)
    319 
    320                         self.session.addInput(videoDeviceInput)
    321                         self.videoDeviceInput = videoDeviceInput
    322                     } else {
    323                         self.session.addInput(self.videoDeviceInput)
    324                     }
    325 
    326                     if let connection = self.photoOutput.connection(with: .video) {
    327                         if connection.isVideoStabilizationSupported {
    328                             connection.preferredVideoStabilizationMode = .auto
    329                         }
    330                     }
    331 
    332                     self.photoOutput.maxPhotoQualityPrioritization = .quality
    333 
    334                     self.session.commitConfiguration()
    335                 } catch {
    336                     print("Error occurred while creating video device input: \(error)")
    337                 }
    338             }
    339 
    340             DispatchQueue.main.async {
    341                 self.isCameraButtonDisabled = false
    342             }
    343         }
    344     }
    345 
    346     public func focus(with focusMode: AVCaptureDevice.FocusMode, exposureMode: AVCaptureDevice.ExposureMode, at devicePoint: CGPoint, monitorSubjectAreaChange: Bool) {
    347         sessionQueue.async {
    348             guard let device = self.videoDeviceInput?.device else { return }
    349             do {
    350                 try device.lockForConfiguration()
    351 
    352                 if device.isFocusPointOfInterestSupported && device.isFocusModeSupported(focusMode) {
    353                     device.focusPointOfInterest = devicePoint
    354                     device.focusMode = focusMode
    355                 }
    356 
    357                 if device.isExposurePointOfInterestSupported && device.isExposureModeSupported(exposureMode) {
    358                     device.exposurePointOfInterest = devicePoint
    359                     device.exposureMode = exposureMode
    360                 }
    361 
    362                 device.isSubjectAreaChangeMonitoringEnabled = monitorSubjectAreaChange
    363                 device.unlockForConfiguration()
    364             } catch {
    365                 print("Could not lock device for configuration: \(error)")
    366             }
    367         }
    368     }
    369 
    370 
    371     public func focus(at focusPoint: CGPoint) {
    372         let device = self.videoDeviceInput.device
    373         do {
    374             try device.lockForConfiguration()
    375             if device.isFocusPointOfInterestSupported {
    376                 device.focusPointOfInterest = focusPoint
    377                 device.exposurePointOfInterest = focusPoint
    378                 device.exposureMode = .continuousAutoExposure
    379                 device.focusMode = .continuousAutoFocus
    380                 device.unlockForConfiguration()
    381             }
    382         }
    383         catch {
    384             print(error.localizedDescription)
    385         }
    386     }
    387 
    388     @objc public func stop(completion: (() -> ())? = nil) {
    389         sessionQueue.async {
    390             if self.isSessionRunning {
    391                 if self.setupResult == .success {
    392                     self.session.stopRunning()
    393                     self.isSessionRunning = self.session.isRunning
    394                     print("CAMERA STOPPED")
    395                     self.removeObservers()
    396 
    397                     if !self.session.isRunning {
    398                         DispatchQueue.main.async {
    399                             self.isCameraButtonDisabled = true
    400                             self.isCameraUnavailable = true
    401                             completion?()
    402                         }
    403                     }
    404                 }
    405             }
    406         }
    407     }
    408 
    409     @objc public func start() {
    410         sessionQueue.async {
    411             if !self.isSessionRunning && self.isConfigured {
    412                 switch self.setupResult {
    413                 case .success:
    414                     self.addObservers()
    415                     self.session.startRunning()
    416                     print("CAMERA RUNNING")
    417                     self.isSessionRunning = self.session.isRunning
    418 
    419                     if self.session.isRunning {
    420                         DispatchQueue.main.async {
    421                             self.isCameraButtonDisabled = false
    422                             self.isCameraUnavailable = false
    423                         }
    424                     }
    425 
    426                 case .notAuthorized:
    427                     print("Application not authorized to use camera")
    428                     DispatchQueue.main.async {
    429                         self.isCameraButtonDisabled = true
    430                         self.isCameraUnavailable = true
    431                     }
    432 
    433                 case .configurationFailed:
    434                     DispatchQueue.main.async {
    435                         self.alertError = AlertError(title: "Camera Error", message: "Camera configuration failed. Either your device camera is not available or other application is using it", primaryButtonTitle: "Accept", secondaryButtonTitle: nil, primaryAction: nil, secondaryAction: nil)
    436                         self.shouldShowAlertView = true
    437                         self.isCameraButtonDisabled = true
    438                         self.isCameraUnavailable = true
    439                     }
    440                 }
    441             }
    442         }
    443     }
    444 
    445     public func set(zoom: CGFloat) {
    446         let factor = zoom < 1 ? 1 : zoom
    447         let device = self.videoDeviceInput.device
    448 
    449         do {
    450             try device.lockForConfiguration()
    451             device.videoZoomFactor = factor
    452             device.unlockForConfiguration()
    453         }
    454         catch {
    455             print(error.localizedDescription)
    456         }
    457     }
    458 
    459     public func capturePhoto() {
    460         if self.setupResult != .configurationFailed {
    461             let videoPreviewLayerOrientation: AVCaptureVideoOrientation = .portrait
    462             self.isCameraButtonDisabled = true
    463 
    464             sessionQueue.async {
    465                 if let photoOutputConnection = self.photoOutput.connection(with: .video) {
    466                     photoOutputConnection.videoOrientation = videoPreviewLayerOrientation
    467                 }
    468                 var photoSettings = AVCapturePhotoSettings()
    469 
    470                 // Capture HEIF photos when supported. Enable according to user settings and high-resolution photos.
    471                 if (self.photoOutput.availablePhotoCodecTypes.contains(.hevc)) {
    472                     photoSettings = AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.hevc])
    473                 }
    474 
    475                 if self.videoDeviceInput.device.isFlashAvailable {
    476                     photoSettings.flashMode = self.flashMode
    477                 }
    478 
    479                 if !photoSettings.__availablePreviewPhotoPixelFormatTypes.isEmpty {
    480                     photoSettings.previewPhotoFormat = [kCVPixelBufferPixelFormatTypeKey as String: photoSettings.__availablePreviewPhotoPixelFormatTypes.first!]
    481                 }
    482 
    483                 photoSettings.photoQualityPrioritization = .speed
    484 
    485                 if self.photoCaptureProcessor == nil {
    486                     self.photoCaptureProcessor = PhotoCaptureProcessor(with: photoSettings, photoOutput: self.photoOutput, willCapturePhotoAnimation: {
    487                         DispatchQueue.main.async {
    488                             self.willCapturePhoto.toggle()
    489                             self.willCapturePhoto.toggle()
    490                         }
    491                     }, completionHandler: { (photoCaptureProcessor) in
    492                         if let data = photoCaptureProcessor.photoData {
    493                             let url = self.savePhoto(data: data)
    494                             if let unwrappedURL = url {
    495                                 self.thumbnail = Thumbnail(type: .image, url: unwrappedURL)
    496                             }
    497                         } else {
    498                             print("Data for photo not found")
    499                         }
    500 
    501                         self.isCameraButtonDisabled = false
    502                     }, photoProcessingHandler: { animate in
    503                         self.isPhotoProcessing = animate
    504                     })
    505                 }
    506 
    507                 self.photoCaptureProcessor?.capturePhoto(settings: photoSettings)
    508             }
    509         }
    510     }
    511 
    512     public func startRecording() {
    513         if self.setupResult != .configurationFailed {
    514             let videoPreviewLayerOrientation: AVCaptureVideoOrientation = .portrait
    515             self.isCameraButtonDisabled = true
    516 
    517             sessionQueue.async {
    518                 if let videoOutputConnection = self.movieOutput.connection(with: .video) {
    519                     videoOutputConnection.videoOrientation = videoPreviewLayerOrientation
    520 
    521                     var videoSettings = [String: Any]()
    522 
    523                     if self.movieOutput.availableVideoCodecTypes.contains(.hevc) == true {
    524                         videoSettings[AVVideoCodecKey] = AVVideoCodecType.hevc
    525                         self.movieOutput.setOutputSettings(videoSettings, for: videoOutputConnection)
    526                     }
    527                 }
    528 
    529                 if self.videoCaptureProcessor == nil {
    530                     self.videoCaptureProcessor = VideoCaptureProcessor(movieOutput: self.movieOutput, beginHandler: {
    531                         self.isRecording = true
    532                     }, completionHandler: { (videoCaptureProcessor, outputFileURL) in
    533                         self.isCameraButtonDisabled = false
    534                         self.captureMode = .image
    535 
    536                         self.mediaItems.append(MediaItem(url: outputFileURL, type: .video))
    537                         self.thumbnail = Thumbnail(type: .video, url: outputFileURL)
    538                     }, videoProcessingHandler: { animate in
    539                         self.isPhotoProcessing = animate
    540                     })
    541                 }
    542 
    543                 self.videoCaptureProcessor?.startCapture(session: self.session)
    544             }
    545         }
    546     }
    547 
    548     func stopRecording() {
    549         if let videoCaptureProcessor = self.videoCaptureProcessor {
    550             isRecording = false
    551             videoCaptureProcessor.stopCapture()
    552         }
    553     }
    554 
    555     func savePhoto(imageType: String = "jpeg", data: Data) -> URL?  {
    556         guard let uiImage = UIImage(data: data) else {
    557             print("Error converting media data to UIImage")
    558             return nil
    559         }
    560 
    561         guard let compressedData = uiImage.jpegData(compressionQuality: 0.8) else {
    562             print("Error converting UIImage to JPEG data")
    563             return nil
    564         }
    565 
    566         let temporaryDirectory = NSTemporaryDirectory()
    567         let tempFileName = "\(UUID().uuidString).\(imageType)"
    568         let tempFileURL = URL(fileURLWithPath: temporaryDirectory).appendingPathComponent(tempFileName)
    569 
    570         do {
    571             try compressedData.write(to: tempFileURL)
    572             self.mediaItems.append(MediaItem(url: tempFileURL, type: .image))
    573             return tempFileURL
    574         } catch {
    575             print("Error saving image data to temporary URL: \(error.localizedDescription)")
    576         }
    577         return nil
    578     }
    579 
    580     private func addObservers() {
    581         let systemPressureStateObservation = observe(\.videoDeviceInput.device.systemPressureState, options: .new) { _, change in
    582             guard let systemPressureState = change.newValue else { return }
    583             self.setRecommendedFrameRateRangeForPressureState(systemPressureState: systemPressureState)
    584         }
    585         keyValueObservations.append(systemPressureStateObservation)
    586 
    587 //        NotificationCenter.default.addObserver(self, selector: #selector(self.onOrientationChange), name: UIDevice.orientationDidChangeNotification, object: nil)
    588 
    589         NotificationCenter.default.addObserver(self,
    590                                                selector: #selector(subjectAreaDidChange),
    591                                                name: .AVCaptureDeviceSubjectAreaDidChange,
    592                                                object: videoDeviceInput.device)
    593 
    594         NotificationCenter.default.addObserver(self, selector: #selector(uiRequestedNewFocusArea), name: .init(rawValue: "UserDidRequestNewFocusPoint"), object: nil)
    595 
    596         NotificationCenter.default.addObserver(self,
    597                                                selector: #selector(sessionRuntimeError),
    598                                                name: .AVCaptureSessionRuntimeError,
    599                                                object: session)
    600 
    601         NotificationCenter.default.addObserver(self,
    602                                                selector: #selector(sessionWasInterrupted),
    603                                                name: .AVCaptureSessionWasInterrupted,
    604                                                object: session)
    605 
    606         NotificationCenter.default.addObserver(self,
    607                                                selector: #selector(sessionInterruptionEnded),
    608                                                name: .AVCaptureSessionInterruptionEnded,
    609                                                object: session)
    610     }
    611 
    612     private func removeObservers() {
    613         NotificationCenter.default.removeObserver(self)
    614 
    615         for keyValueObservation in keyValueObservations {
    616             keyValueObservation.invalidate()
    617         }
    618         keyValueObservations.removeAll()
    619     }
    620 
    621     @objc private func uiRequestedNewFocusArea(notification: NSNotification) {
    622         guard let userInfo = notification.userInfo as? [String: Any], let devicePoint = userInfo["devicePoint"] as? CGPoint else { return }
    623         self.focus(at: devicePoint)
    624     }
    625 
    626     @objc
    627     private func subjectAreaDidChange(notification: NSNotification) {
    628         let devicePoint = CGPoint(x: 0.5, y: 0.5)
    629         focus(with: .continuousAutoFocus, exposureMode: .continuousAutoExposure, at: devicePoint, monitorSubjectAreaChange: false)
    630     }
    631 
    632     @objc
    633     private func sessionRuntimeError(notification: NSNotification) {
    634         guard let error = notification.userInfo?[AVCaptureSessionErrorKey] as? AVError else { return }
    635 
    636         print("Capture session runtime error: \(error)")
    637 
    638         if error.code == .mediaServicesWereReset {
    639             sessionQueue.async {
    640                 if self.isSessionRunning {
    641                     self.session.startRunning()
    642                     self.isSessionRunning = self.session.isRunning
    643                 }
    644             }
    645         }
    646     }
    647 
    648     private func setRecommendedFrameRateRangeForPressureState(systemPressureState: AVCaptureDevice.SystemPressureState) {
    649         let pressureLevel = systemPressureState.level
    650         if pressureLevel == .serious || pressureLevel == .critical {
    651             do {
    652                 try self.videoDeviceInput.device.lockForConfiguration()
    653                 print("WARNING: Reached elevated system pressure level: \(pressureLevel). Throttling frame rate.")
    654                 self.videoDeviceInput.device.activeVideoMinFrameDuration = CMTime(value: 1, timescale: 20)
    655                 self.videoDeviceInput.device.activeVideoMaxFrameDuration = CMTime(value: 1, timescale: 15)
    656                 self.videoDeviceInput.device.unlockForConfiguration()
    657             } catch {
    658                 print("Could not lock device for configuration: \(error)")
    659             }
    660         } else if pressureLevel == .shutdown {
    661             print("Session stopped running due to shutdown system pressure level.")
    662         }
    663     }
    664 
    665     @objc
    666     private func sessionWasInterrupted(notification: NSNotification) {
    667         DispatchQueue.main.async {
    668             self.isCameraUnavailable = true
    669         }
    670 
    671         if let userInfoValue = notification.userInfo?[AVCaptureSessionInterruptionReasonKey] as AnyObject?,
    672             let reasonIntegerValue = userInfoValue.integerValue,
    673             let reason = AVCaptureSession.InterruptionReason(rawValue: reasonIntegerValue) {
    674             print("Capture session was interrupted with reason \(reason)")
    675 
    676             if reason == .audioDeviceInUseByAnotherClient || reason == .videoDeviceInUseByAnotherClient {
    677                 print("Session stopped running due to video devies in use by another client.")
    678             } else if reason == .videoDeviceNotAvailableWithMultipleForegroundApps {
    679                 print("Session stopped running due to video devies is not available with multiple foreground apps.")
    680             } else if reason == .videoDeviceNotAvailableDueToSystemPressure {
    681                 print("Session stopped running due to shutdown system pressure level.")
    682             }
    683         }
    684     }
    685 
    686     @objc
    687     private func sessionInterruptionEnded(notification: NSNotification) {
    688         print("Capture session interruption ended")
    689         DispatchQueue.main.async {
    690             self.isCameraUnavailable = false
    691         }
    692     }
    693 }