Skip to content

Commit 0528d69

Browse files
committed
Update ChallengeOptions and use camera position based on challenge type received
1 parent db4e7e0 commit 0528d69

File tree

7 files changed

+100
-76
lines changed

7 files changed

+100
-76
lines changed

HostApp/HostApp/Views/ExampleLivenessView.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ struct ExampleLivenessView: View {
2323
FaceLivenessDetectorView(
2424
sessionID: viewModel.sessionID,
2525
region: "us-east-1",
26-
challengeOption: .faceMovementAndLightChallenge,
26+
challengeOptions: .init(faceMovementChallengeOption: FaceMovementChallengeOption(camera: .front),
27+
faceMovementAndLightChallengeOption: FaceMovementAndLightChallengeOption()),
2728
isPresented: Binding(
2829
get: { viewModel.presentationState == .liveness },
2930
set: { _ in }
@@ -47,6 +48,10 @@ struct ExampleLivenessView: View {
4748
viewModel.presentationState = .error(.countdownFaceTooClose)
4849
case .failure(.invalidSignature):
4950
viewModel.presentationState = .error(.invalidSignature)
51+
case .failure(.faceInOvalMatchExceededTimeLimitError):
52+
viewModel.presentationState = .error(.faceInOvalMatchExceededTimeLimitError)
53+
case .failure(.internalServer):
54+
viewModel.presentationState = .error(.internalServer)
5055
case .failure(.cameraNotAvailable):
5156
viewModel.presentationState = .error(.cameraNotAvailable)
5257
case .failure(.validation):

Sources/FaceLiveness/Views/Instruction/InstructionContainerView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ struct InstructionContainerView: View {
110110
)
111111
}
112112
case .faceMatched:
113-
if let challenge = viewModel.challenge,
113+
if let challenge = viewModel.challengeReceived,
114114
case .faceMovementAndLightChallenge = challenge.type {
115115
InstructionView(
116116
text: LocalizedStrings.challenge_instruction_hold_still,

Sources/FaceLiveness/Views/Liveness/FaceLivenessDetectionView.swift

Lines changed: 41 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public struct FaceLivenessDetectorView: View {
2020
@State var displayingCameraPermissionsNeededAlert = false
2121

2222
let disableStartView: Bool
23-
let cameraPosition: LivenessCamera
23+
let challengeOptions: ChallengeOptions
2424
let onCompletion: (Result<Void, FaceLivenessDetectionError>) -> Void
2525

2626
let sessionTask: Task<FaceLivenessSession, Error>
@@ -30,14 +30,14 @@ public struct FaceLivenessDetectorView: View {
3030
credentialsProvider: AWSCredentialsProvider? = nil,
3131
region: String,
3232
disableStartView: Bool = false,
33-
challengeOption: ChallengeOption,
33+
challengeOptions: ChallengeOptions,
3434
isPresented: Binding<Bool>,
3535
onCompletion: @escaping (Result<Void, FaceLivenessDetectionError>) -> Void
3636
) {
3737
self.disableStartView = disableStartView
3838
self._isPresented = isPresented
39-
self.cameraPosition = challengeOption.camera
4039
self.onCompletion = onCompletion
40+
self.challengeOptions = challengeOptions
4141

4242
self.sessionTask = Task {
4343
let session = try await AWSPredictionsPlugin.startFaceLivenessSession(
@@ -59,31 +59,16 @@ public struct FaceLivenessDetectorView: View {
5959
assetWriterDelegate: VideoChunker.AssetWriterDelegate(),
6060
assetWriterInput: LivenessAVAssetWriterInput()
6161
)
62-
63-
let avCaptureDevice = AVCaptureDevice.default(
64-
.builtInWideAngleCamera,
65-
for: .video,
66-
position: cameraPosition == .front ? .front : .back)
67-
68-
let captureSession = LivenessCaptureSession(
69-
captureDevice: .init(avCaptureDevice: avCaptureDevice),
70-
outputDelegate: OutputSampleBufferCapturer(
71-
faceDetector: faceDetector,
72-
videoChunker: videoChunker
73-
)
74-
)
7562

7663
self._viewModel = StateObject(
7764
wrappedValue: .init(
7865
faceDetector: faceDetector,
7966
faceInOvalMatching: faceInOvalStateMatching,
80-
captureSession: captureSession,
8167
videoChunker: videoChunker,
8268
closeButtonAction: { onCompletion(.failure(.userCancelled)) },
8369
sessionID: sessionID,
8470
isPreviewScreenEnabled: !disableStartView,
85-
cameraPosition: challengeOption.camera,
86-
challengeOption: challengeOption
71+
challengeOptions: challengeOptions
8772
)
8873
)
8974
}
@@ -93,15 +78,15 @@ public struct FaceLivenessDetectorView: View {
9378
credentialsProvider: AWSCredentialsProvider? = nil,
9479
region: String,
9580
disableStartView: Bool = false,
96-
challengeOption: ChallengeOption,
81+
challengeOptions: ChallengeOptions,
9782
isPresented: Binding<Bool>,
9883
onCompletion: @escaping (Result<Void, FaceLivenessDetectionError>) -> Void,
9984
captureSession: LivenessCaptureSession
10085
) {
10186
self.disableStartView = disableStartView
10287
self._isPresented = isPresented
10388
self.onCompletion = onCompletion
104-
self.cameraPosition = challengeOption.camera
89+
self.challengeOptions = challengeOptions
10590

10691
self.sessionTask = Task {
10792
let session = try await AWSPredictionsPlugin.startFaceLivenessSession(
@@ -121,13 +106,11 @@ public struct FaceLivenessDetectorView: View {
121106
wrappedValue: .init(
122107
faceDetector: captureSession.outputSampleBufferCapturer!.faceDetector,
123108
faceInOvalMatching: faceInOvalStateMatching,
124-
captureSession: captureSession,
125109
videoChunker: captureSession.outputSampleBufferCapturer!.videoChunker,
126110
closeButtonAction: { onCompletion(.failure(.userCancelled)) },
127111
sessionID: sessionID,
128112
isPreviewScreenEnabled: !disableStartView,
129-
cameraPosition: challengeOption.camera,
130-
challengeOption: challengeOption
113+
challengeOptions: challengeOptions
131114
)
132115
)
133116
}
@@ -172,6 +155,14 @@ public struct FaceLivenessDetectorView: View {
172155
.onAppear {
173156
Task {
174157
do {
158+
let cameraPosition: LivenessCamera
159+
switch challenge.type {
160+
case .faceMovementAndLightChallenge:
161+
cameraPosition = challengeOptions.faceMovementAndLightChallengeOption.camera
162+
case .faceMovementChallenge:
163+
cameraPosition = challengeOptions.faceMovementChallengeOption.camera
164+
}
165+
175166
let newState = disableStartView
176167
? DisplayState.displayingLiveness
177168
: DisplayState.displayingGetReadyView(challenge, cameraPosition)
@@ -255,7 +246,7 @@ public struct FaceLivenessDetectorView: View {
255246
for: .video,
256247
completionHandler: { accessGranted in
257248
guard accessGranted == true else { return }
258-
guard let challenge = viewModel.challenge else { return }
249+
guard let challenge = viewModel.challengeReceived else { return }
259250
displayState = .awaitingLivenessSession(challenge)
260251
}
261252
)
@@ -274,7 +265,7 @@ public struct FaceLivenessDetectorView: View {
274265
case .restricted, .denied:
275266
alertCameraAccessNeeded()
276267
case .authorized:
277-
guard let challenge = viewModel.challenge else { return }
268+
guard let challenge = viewModel.challengeReceived else { return }
278269
displayState = .awaitingLivenessSession(challenge)
279270
@unknown default:
280271
break
@@ -341,32 +332,38 @@ private func map(detectionCompletion: @escaping (Result<Void, FaceLivenessDetect
341332
}
342333
}
343334

344-
enum LivenessCamera {
335+
public enum LivenessCamera {
345336
case front
346337
case back
347338
}
348339

349-
public struct ChallengeOption {
340+
public struct ChallengeOptions {
341+
let faceMovementChallengeOption: FaceMovementChallengeOption
342+
let faceMovementAndLightChallengeOption: FaceMovementAndLightChallengeOption
343+
344+
public init(faceMovementChallengeOption: FaceMovementChallengeOption,
345+
faceMovementAndLightChallengeOption: FaceMovementAndLightChallengeOption) {
346+
self.faceMovementChallengeOption = faceMovementChallengeOption
347+
self.faceMovementAndLightChallengeOption = faceMovementAndLightChallengeOption
348+
}
349+
}
350+
351+
public struct FaceMovementChallengeOption {
350352
let challenge: Challenge
351353
let camera: LivenessCamera
352354

353-
init(challenge: Challenge, camera: LivenessCamera) {
354-
self.challenge = challenge
355+
public init(camera: LivenessCamera) {
356+
self.challenge = .init(version: "1.0.0", type: .faceMovementChallenge)
355357
self.camera = camera
356358
}
359+
}
360+
361+
public struct FaceMovementAndLightChallengeOption {
362+
let challenge: Challenge
363+
let camera: LivenessCamera
357364

358-
public static let faceMovementAndLightChallenge = Self.init(
359-
challenge: .init(version: "2.0.0",
360-
type: .faceMovementAndLightChallenge),
361-
camera: .front)
362-
363-
public static let faceMovementChallengeWithFrontCamera = Self.init(
364-
challenge: .init(version: "1.0.0",
365-
type: .faceMovementAndLightChallenge),
366-
camera: .front)
367-
368-
public static let faceMovementChallengeWithBackCamera = Self.init(
369-
challenge: .init(version: "1.0.0",
370-
type: .faceMovementAndLightChallenge),
371-
camera: .back)
365+
public init() {
366+
self.challenge = .init(version: "2.0.0", type: .faceMovementAndLightChallenge)
367+
self.camera = .front
368+
}
372369
}

Sources/FaceLiveness/Views/Liveness/FaceLivenessDetectionViewModel+FaceDetectionResultHandler.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ extension FaceLivenessDetectionViewModel: FaceDetectionResultHandler {
113113
self.faceMatchedTimestamp = Date().timestampMilliseconds
114114

115115
// next step after face match
116-
switch self.challenge?.type {
116+
switch self.challengeReceived?.type {
117117
case .faceMovementAndLightChallenge:
118118
if let colorSequences = colorSequences {
119119
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
@@ -146,7 +146,7 @@ extension FaceLivenessDetectionViewModel: FaceDetectionResultHandler {
146146
DispatchQueue.main.async {
147147
self.livenessState
148148
.unrecoverableStateEncountered(.timedOut)
149-
self.captureSession.stopRunning()
149+
self.captureSession?.stopRunning()
150150
}
151151
}
152152
}

Sources/FaceLiveness/Views/Liveness/FaceLivenessDetectionViewModel.swift

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class FaceLivenessDetectionViewModel: ObservableObject {
2121
@Published var livenessState: LivenessStateMachine
2222

2323
weak var livenessViewControllerDelegate: FaceLivenessViewControllerPresenter?
24-
let captureSession: LivenessCaptureSession
24+
var captureSession: LivenessCaptureSession?
2525
var closeButtonAction: () -> Void
2626
let videoChunker: VideoChunker
2727
let sessionID: String
@@ -35,7 +35,7 @@ class FaceLivenessDetectionViewModel: ObservableObject {
3535
var hasSentFirstVideo = false
3636
var layerRectConverted: (CGRect) -> CGRect = { $0 }
3737
var sessionConfiguration: FaceLivenessSession.SessionConfiguration?
38-
var challenge: Challenge?
38+
var challengeReceived: Challenge?
3939
var normalizeFace: (DetectedFace) -> DetectedFace = { $0 }
4040
var provideSingleFrame: ((UIImage) -> Void)?
4141
var cameraViewRect = CGRect.zero
@@ -44,8 +44,7 @@ class FaceLivenessDetectionViewModel: ObservableObject {
4444
var initialClientEvent: InitialClientEvent?
4545
var faceMatchedTimestamp: UInt64?
4646
var noFitStartTime: Date?
47-
let cameraPosition: LivenessCamera
48-
let challengeOption: ChallengeOption
47+
let challengeOptions: ChallengeOptions
4948

5049
static var attemptCount: Int = 0
5150
static var attemptIdTimeStamp: Date = Date()
@@ -61,25 +60,21 @@ class FaceLivenessDetectionViewModel: ObservableObject {
6160
init(
6261
faceDetector: FaceDetector,
6362
faceInOvalMatching: FaceInOvalMatching,
64-
captureSession: LivenessCaptureSession,
6563
videoChunker: VideoChunker,
6664
stateMachine: LivenessStateMachine = .init(state: .initial),
6765
closeButtonAction: @escaping () -> Void,
6866
sessionID: String,
6967
isPreviewScreenEnabled: Bool,
70-
cameraPosition: LivenessCamera,
71-
challengeOption: ChallengeOption
68+
challengeOptions: ChallengeOptions
7269
) {
7370
self.closeButtonAction = closeButtonAction
7471
self.videoChunker = videoChunker
7572
self.livenessState = stateMachine
7673
self.sessionID = sessionID
77-
self.captureSession = captureSession
7874
self.faceDetector = faceDetector
7975
self.faceInOvalMatching = faceInOvalMatching
8076
self.isPreviewScreenEnabled = isPreviewScreenEnabled
81-
self.cameraPosition = cameraPosition
82-
self.challengeOption = challengeOption
77+
self.challengeOptions = challengeOptions
8378

8479
self.closeButtonAction = { [weak self] in
8580
guard let self else { return }
@@ -129,7 +124,8 @@ class FaceLivenessDetectionViewModel: ObservableObject {
129124

130125
livenessService?.register(
131126
listener: { [weak self] _challenge in
132-
self?.challenge = _challenge
127+
self?.challengeReceived = _challenge
128+
self?.configureCaptureSession(challenge: _challenge)
133129
onChallengeTypeReceived(_challenge)
134130
},
135131
on: .challenge)
@@ -144,16 +140,16 @@ class FaceLivenessDetectionViewModel: ObservableObject {
144140
}
145141

146142
func startSession() {
147-
captureSession.startSession()
143+
captureSession?.startSession()
148144
}
149145

150146
func stopRecording() {
151-
captureSession.stopRunning()
147+
captureSession?.stopRunning()
152148
}
153149

154150
func configureCamera(withinFrame frame: CGRect) -> CALayer? {
155151
do {
156-
let avLayer = try captureSession.configureCamera(frame: frame)
152+
let avLayer = try captureSession?.configureCamera(frame: frame)
157153
DispatchQueue.main.async {
158154
self.livenessState.checkIsFacePrepared()
159155
}
@@ -209,7 +205,8 @@ class FaceLivenessDetectionViewModel: ObservableObject {
209205
try livenessService?.initializeLivenessStream(
210206
withSessionID: sessionID,
211207
userAgent: UserAgentValues.standard().userAgentString,
212-
challenges: [challengeOption.challenge],
208+
challenges: [challengeOptions.faceMovementChallengeOption.challenge,
209+
challengeOptions.faceMovementAndLightChallengeOption.challenge],
213210
options: .init(
214211
attemptCount: Self.attemptCount,
215212
preCheckViewEnabled: isPreviewScreenEnabled)
@@ -258,7 +255,7 @@ class FaceLivenessDetectionViewModel: ObservableObject {
258255
videoStartTime: UInt64
259256
) {
260257
guard initialClientEvent == nil else { return }
261-
guard let challenge else { return }
258+
guard let challengeReceived else { return }
262259

263260
videoChunker.start()
264261

@@ -278,8 +275,8 @@ class FaceLivenessDetectionViewModel: ObservableObject {
278275
do {
279276
try livenessService?.send(
280277
.initialFaceDetected(event: _initialClientEvent,
281-
challenge: .init(version: challenge.version,
282-
type: challenge.type)),
278+
challenge: .init(version: challengeReceived.version,
279+
type: challengeReceived.type)),
283280
eventDate: { .init() }
284281
)
285282
} catch {
@@ -298,7 +295,7 @@ class FaceLivenessDetectionViewModel: ObservableObject {
298295
let sessionConfiguration,
299296
let initialClientEvent,
300297
let faceMatchedTimestamp,
301-
let challenge
298+
let challengeReceived
302299
else { return }
303300

304301
let finalClientEvent = FinalClientEvent(
@@ -313,8 +310,8 @@ class FaceLivenessDetectionViewModel: ObservableObject {
313310
do {
314311
try livenessService?.send(
315312
.final(event: finalClientEvent,
316-
challenge: .init(version: challenge.version,
317-
type: challenge.type)),
313+
challenge: .init(version: challengeReceived.version,
314+
type: challengeReceived.type)),
318315
eventDate: { .init() }
319316
)
320317

@@ -407,6 +404,29 @@ class FaceLivenessDetectionViewModel: ObservableObject {
407404
}
408405
return data
409406
}
407+
408+
func configureCaptureSession(challenge: Challenge) {
409+
let cameraPosition: LivenessCamera
410+
switch challenge.type {
411+
case .faceMovementChallenge:
412+
cameraPosition = challengeOptions.faceMovementChallengeOption.camera
413+
case .faceMovementAndLightChallenge:
414+
cameraPosition = challengeOptions.faceMovementAndLightChallengeOption.camera
415+
}
416+
417+
let avCaptureDevice = AVCaptureDevice.default(
418+
.builtInWideAngleCamera,
419+
for: .video,
420+
position: cameraPosition == .front ? .front : .back)
421+
422+
self.captureSession = LivenessCaptureSession(
423+
captureDevice: .init(avCaptureDevice: avCaptureDevice),
424+
outputDelegate: OutputSampleBufferCapturer(
425+
faceDetector: self.faceDetector,
426+
videoChunker: self.videoChunker
427+
)
428+
)
429+
}
410430
}
411431

412432
extension FaceLivenessDetectionViewModel: FaceDetectionSessionConfigurationWrapper { }

0 commit comments

Comments
 (0)