Skip to content

Commit

Permalink
Update tests
Browse files Browse the repository at this point in the history
  • Loading branch information
enebin committed Jun 30, 2023
1 parent 7438a97 commit ec26ed0
Show file tree
Hide file tree
Showing 11 changed files with 183 additions and 121 deletions.
18 changes: 8 additions & 10 deletions Demo/Aespa-iOS/VideoContentViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ import Aespa
class VideoContentViewModel: ObservableObject {
let aespaSession: AespaSession

var preview: InteractivePreview {
let option = InteractivePreviewOption(enableShowingCrosshair: false)
return aespaSession.interactivePreview(option: option)
var preview: some View {
return aespaSession.interactivePreview()

// Or you can give some options
// let option = InteractivePreviewOption(enableShowingCrosshair: false)
// return aespaSession.interactivePreview(option: option)
}

private var subscription = Set<AnyCancellable>()
Expand Down Expand Up @@ -93,13 +96,8 @@ class VideoContentViewModel: ObservableObject {

func fetchPhotoFiles() {
// File fetching task can cause low reponsiveness when called from main thread
DispatchQueue.global().async {
let fetchedFiles = self.aespaSession.fetchPhotoFiles()

DispatchQueue.main.async {
self.photoFiles = fetchedFiles
}
}
let fetchedFiles = self.aespaSession.fetchPhotoFiles()
self.photoFiles = fetchedFiles
}
}

Expand Down
23 changes: 20 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,8 @@ One of our main feature, `InteractivePreview` provides a comprehensive and intui

| Features | Description |
|------------------------|------------------------------------------------------------------------------------------------------------------|
| Tap-to-focus | Adjusts the focus of the camera based on the tapped area on the screen. |
| Double tap camera change | Switches between the front and back camera upon double tapping. |
| Tap to focus | Adjusts the focus of the camera based on the tapped area on the screen. |
| Double tap to change camera | Switches between the front and back camera upon double tapping. |
| Pinch zoom | Allows zooming in or out on the preview by using a pinch gesture. |


Expand Down Expand Up @@ -235,9 +235,26 @@ aespaSession.stopRecording()
// Capture photo
aespaSession.capturePhoto()
```
### Get result
``` Swift
aespaSession.stopRecording { result in
switch result {
case .success(let file):
//
case .failure(let error):
print(error)
}
}

// or
aespaSession.fetchVideoFiles(limit: 1)

// or you can use publisher
aespaSession.videoFilePublisher.sink { result in ... }
```

## SwiftUI Integration
Aespa also provides a super-easy way to integrate video capture functionality into SwiftUI applications. AespaSession includes a helper method to create a SwiftUI `UIViewRepresentable` that provides a preview of the video capture.
Aespa also provides a super-easy way to integrate video capture functionality into SwiftUI applications. `AespaSession` includes a helper method to create a SwiftUI `UIViewRepresentable` that provides a preview of the video capture.

### Example usage

Expand Down
62 changes: 47 additions & 15 deletions Sources/Aespa/AespaSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ open class AespaSession {
coreSession
}

/// This property indicates whether the current session is active or not.
public var isRunning: Bool {
coreSession.isRunning
}
Expand Down Expand Up @@ -137,6 +138,10 @@ open class AespaSession {
return device.position
}

/// This property indicates whether the camera device is set to monitor changes in the subject area.
///
/// Enabling subject area change monitoring allows the device to adjust focus and exposure settings automatically
/// when the subject within the specified area changes.
public var isSubjectAreaChangeMonitoringEnabled: Bool? {
guard let device = coreSession.videoDeviceInput?.device else { return nil }
return device.isSubjectAreaChangeMonitoringEnabled
Expand All @@ -155,7 +160,12 @@ open class AespaSession {
}

// MARK: - Utilities

/// Returns a publisher that emits a `Notification` when the subject area of the capture device changes.
///
/// This is useful when you want to react to changes in the capture device's subject area,
/// such as when the user changes the zoom factor, or when the device changes its autofocus area.
///
/// - Returns: An `AnyPublisher` instance that emits `Notification` values.
public func getSubjectAreaDidChangePublisher() -> AnyPublisher<Notification, Never> {
return NotificationCenter.default
.publisher(for: NSNotification.Name.AVCaptureDeviceSubjectAreaDidChange)
Expand Down Expand Up @@ -199,34 +209,46 @@ extension AespaSession: CommonContext {
}

@discardableResult
public func quality(to preset: AVCaptureSession.Preset, _ onComplete: @escaping CompletionHandler = { _ in }) -> AespaSession {
public func quality(
to preset: AVCaptureSession.Preset,
_ onComplete: @escaping CompletionHandler = { _ in }
) -> AespaSession {
let tuner = QualityTuner(videoQuality: preset)
coreSession.run(tuner, onComplete)
return self
}

@discardableResult
public func position(to position: AVCaptureDevice.Position, _ onComplete: @escaping CompletionHandler = { _ in }) -> AespaSession {
public func position(
to position: AVCaptureDevice.Position,
_ onComplete: @escaping CompletionHandler = { _ in }
) -> AespaSession {
let tuner = CameraPositionTuner(position: position,
devicePreference: option.session.cameraDevicePreference)
coreSession.run(tuner, onComplete)
return self
}

@discardableResult
public func orientation(to orientation: AVCaptureVideoOrientation, _ onComplete: @escaping CompletionHandler = { _ in }) -> AespaSession {
public func orientation(
to orientation: AVCaptureVideoOrientation,
_ onComplete: @escaping CompletionHandler = { _ in }
) -> AespaSession {
let tuner = VideoOrientationTuner(orientation: orientation)
coreSession.run(tuner, onComplete)
return self
}

@discardableResult
public func focus(mode: AVCaptureDevice.FocusMode, point: CGPoint? = nil, _ onComplete: @escaping CompletionHandler = { _ in }) -> AespaSession {
public func focus(
mode: AVCaptureDevice.FocusMode, point: CGPoint? = nil,
_ onComplete: @escaping CompletionHandler = { _ in }
) -> AespaSession {
let tuner = FocusTuner(mode: mode, point: point)
coreSession.run(tuner, onComplete)
return self
}

@discardableResult
public func zoom(factor: CGFloat, _ onComplete: @escaping CompletionHandler = { _ in }) -> AespaSession {
let tuner = ZoomTuner(zoomFactor: factor)
Expand All @@ -235,14 +257,17 @@ extension AespaSession: CommonContext {
}

@discardableResult
public func changeMonitoring(enabled: Bool, _ onComplete: @escaping CompletionHandler = { _ in }) -> AespaSession {
public func changeMonitoring(enabled: Bool, _ onComplete: @escaping CompletionHandler = { _ in }) -> AespaSession {
let tuner = ChangeMonitoringTuner(isSubjectAreaChangeMonitoringEnabled: enabled)
coreSession.run(tuner, onComplete)
return self
}

@discardableResult
public func custom<T: AespaSessionTuning>(_ tuner: T, _ onComplete: @escaping CompletionHandler = { _ in }) -> AespaSession {
public func custom<T: AespaSessionTuning>(
_ tuner: T,
_ onComplete: @escaping CompletionHandler = { _ in }
) -> AespaSession {
coreSession.run(tuner, onComplete)
return self
}
Expand Down Expand Up @@ -286,15 +311,22 @@ extension AespaSession: VideoContext {
}

@discardableResult
public func stabilization(mode: AVCaptureVideoStabilizationMode, _ onComplete: @escaping CompletionHandler = { _ in }) -> AespaVideoSessionContext {
public func stabilization(
mode: AVCaptureVideoStabilizationMode,
_ onComplete: @escaping CompletionHandler = { _ in }
) -> AespaVideoSessionContext {
videoContext.stabilization(mode: mode, onComplete)
}

@discardableResult
public func torch(mode: AVCaptureDevice.TorchMode, level: Float, _ onComplete: @escaping CompletionHandler = { _ in }) -> AespaVideoSessionContext {
public func torch(
mode: AVCaptureDevice.TorchMode,
level: Float,
_ onComplete: @escaping CompletionHandler = { _ in }
) -> AespaVideoSessionContext {
videoContext.torch(mode: mode, level: level, onComplete)
}

public func fetchVideoFiles(limit: Int = 0) -> [VideoFile] {
videoContext.fetchVideoFiles(limit: limit)
}
Expand Down
20 changes: 15 additions & 5 deletions Sources/Aespa/Core/Context/AespaVideoContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -131,27 +131,37 @@ extension AespaVideoContext: VideoContext {
}

@discardableResult
public func stabilization(mode: AVCaptureVideoStabilizationMode, _ onComplete: @escaping CompletionHandler = { _ in }) -> AespaVideoContext {
public func stabilization(
mode: AVCaptureVideoStabilizationMode,
_ onComplete: @escaping CompletionHandler = { _ in }
) -> AespaVideoContext {
let tuner = VideoStabilizationTuner(stabilzationMode: mode)
coreSession.run(tuner, onComplete)

return self
}

@discardableResult
public func torch(mode: AVCaptureDevice.TorchMode, level: Float, _ onComplete: @escaping CompletionHandler = { _ in }) -> AespaVideoContext {
public func torch(
mode: AVCaptureDevice.TorchMode,
level: Float,
_ onComplete: @escaping CompletionHandler = { _ in }
) -> AespaVideoContext {
let tuner = TorchTuner(level: level, torchMode: mode)
coreSession.run(tuner, onComplete)

return self
}

public func customize<T: AespaSessionTuning>(_ tuner: T, _ onComplete: @escaping CompletionHandler = { _ in }) -> AespaVideoContext {

public func customize<T: AespaSessionTuning>(
_ tuner: T,
_ onComplete: @escaping CompletionHandler = { _ in }
) -> AespaVideoContext {
coreSession.run(tuner, onComplete)

return self
}

public func fetchVideoFiles(limit: Int = 0) -> [VideoFile] {
return fileManager.fetchVideo(
albumName: option.asset.albumName,
Expand Down
64 changes: 52 additions & 12 deletions Sources/Aespa/Core/Context/Context.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,21 @@ import Combine
import Foundation
import AVFoundation

///
/// A type representing a closure that handles a completion event with potential errors.
public typealias CompletionHandler = (Result<Void, Error>) -> Void

/// A type representing a closure that handles a result of an operation
/// that produces a value of type `T`, with potential errors.
public typealias ResultHandler<T> = (Result<T, Error>) -> Void

/// A protocol that defines the common behaviors and properties that all context types must implement.
///
/// It includes methods to control the quality, position, orientation, and auto-focusing behavior
/// of the session. It also includes the ability to adjust the zoom level of the session.
public protocol CommonContext {
///
associatedtype CommonContextType: CommonContext & VideoContext & PhotoContext

///
var underlyingCommonContext: CommonContextType { get }

/// Sets the quality preset for the video recording session.
Expand All @@ -30,7 +34,10 @@ public protocol CommonContext {
/// - onComplete: A closure to be executed if the session fails to run the tuner.
///
/// - Returns: `AespaVideoContext`, for chaining calls.
@discardableResult func quality(to preset: AVCaptureSession.Preset, _ onComplete: @escaping CompletionHandler) -> CommonContextType
@discardableResult func quality(
to preset: AVCaptureSession.Preset,
_ onComplete: @escaping CompletionHandler
) -> CommonContextType

/// Sets the camera position for the video recording session.
///
Expand All @@ -41,7 +48,10 @@ public protocol CommonContext {
/// - onComplete: A closure to be executed if the session fails to run the tuner.
///
/// - Returns: `AespaVideoContext`, for chaining calls.
@discardableResult func position(to position: AVCaptureDevice.Position, _ onComplete: @escaping CompletionHandler) -> CommonContextType
@discardableResult func position(
to position: AVCaptureDevice.Position,
_ onComplete: @escaping CompletionHandler
) -> CommonContextType

/// Sets the orientation for the session.
///
Expand All @@ -51,8 +61,12 @@ public protocol CommonContext {
///
/// - Returns: `AespaVideoContext`, for chaining calls.
///
/// - Note: It sets the orientation of the video you are recording, not the orientation of the `AVCaptureVideoPreviewLayer`.
@discardableResult func orientation(to orientation: AVCaptureVideoOrientation, _ onComplete: @escaping CompletionHandler) -> CommonContextType
/// - Note: It sets the orientation of the video you are recording,
/// not the orientation of the `AVCaptureVideoPreviewLayer`.
@discardableResult func orientation(
to orientation: AVCaptureVideoOrientation,
_ onComplete: @escaping CompletionHandler
) -> CommonContextType

/// Sets the autofocusing mode for the video recording session.
///
Expand All @@ -62,7 +76,11 @@ public protocol CommonContext {
/// - onComplete: A closure to be executed if the session fails to run the tuner.
///
/// - Returns: `AespaVideoContext`, for chaining calls.
@discardableResult func focus(mode: AVCaptureDevice.FocusMode, point: CGPoint?, _ onComplete: @escaping CompletionHandler) -> CommonContextType
@discardableResult func focus(
mode: AVCaptureDevice.FocusMode,
point: CGPoint?,
_ onComplete: @escaping CompletionHandler
) -> CommonContextType

/// Sets the zoom factor for the video recording session.
///
Expand All @@ -80,7 +98,10 @@ public protocol CommonContext {
/// - onComplete: A closure to be executed if the session fails to run the tuner.
///
/// - Returns: `AespaVideoContext`, for chaining calls.
@discardableResult func changeMonitoring(enabled: Bool, _ onComplete: @escaping CompletionHandler) -> CommonContextType
@discardableResult func changeMonitoring(
enabled: Bool,
_ onComplete: @escaping CompletionHandler
) -> CommonContextType

/// This function provides a way to use a custom tuner to modify the current session.
/// The tuner must conform to `AespaSessionTuning`.
Expand All @@ -90,7 +111,10 @@ public protocol CommonContext {
/// - onComplete: A closure to be executed if the session fails to run the tuner.
///
/// - Returns: `AespaVideoContext`, for chaining calls.
@discardableResult func custom<T: AespaSessionTuning>(_ tuner: T, _ onComplete: @escaping CompletionHandler) -> CommonContextType
@discardableResult func custom<T: AespaSessionTuning>(
_ tuner: T,
_ onComplete: @escaping CompletionHandler
) -> CommonContextType
}

/// A protocol that defines the behaviors and properties specific to the video context.
Expand All @@ -99,7 +123,9 @@ public protocol CommonContext {
/// the session is currently recording or muted, and controlling video recording,
/// stabilization, torch mode, and fetching recorded video files.
public protocol VideoContext {
///
associatedtype VideoContextType: VideoContext
///
var underlyingVideoContext: VideoContextType { get }

/// A Boolean value that indicates whether the session is currently recording video.
Expand All @@ -126,7 +152,14 @@ public protocol VideoContext {
/// it sets the orientation according to the current device orientation.
func startRecording(_ onComplete: @escaping CompletionHandler)

func stopRecording(_ completionHandler: @escaping (Result<VideoFile, Error>) -> Void)
/// Stops the current recording session and saves the video file.
///
/// Once the recording session is successfully stopped and the video file is saved,
/// this function invokes a completion handler with the resulting `VideoFile` instance or an error.
///
/// - Parameter onComplete: A closure to be called after the recording has stopped
/// and the video file is saved or failed.
func stopRecording(_ onComplete: @escaping (Result<VideoFile, Error>) -> Void)

/// Mutes the audio input for the video recording session.
///
Expand All @@ -152,7 +185,10 @@ public protocol VideoContext {
///
/// - Returns: The modified `VideoContextType` for chaining calls.
@discardableResult
func stabilization(mode: AVCaptureVideoStabilizationMode, _ onComplete: @escaping CompletionHandler) -> VideoContextType
func stabilization(
mode: AVCaptureVideoStabilizationMode,
_ onComplete: @escaping CompletionHandler
) -> VideoContextType

/// Sets the torch mode and level for the video recording session.
///
Expand All @@ -165,7 +201,11 @@ public protocol VideoContext {
/// - Note: This function might throw an error if the torch mode is not supported,
/// or the specified level is not within the acceptable range.
@discardableResult
func torch(mode: AVCaptureDevice.TorchMode, level: Float, _ onComplete: @escaping CompletionHandler) -> VideoContextType
func torch(
mode: AVCaptureDevice.TorchMode,
level: Float,
_ onComplete: @escaping CompletionHandler
) -> VideoContextType

/// Fetches a list of recorded video files.
/// The number of files fetched is controlled by the limit parameter.
Expand Down
Loading

0 comments on commit ec26ed0

Please sign in to comment.