Skip to content

Commit 471ac56

Browse files
authored
feat(ux): update get ready page with new preview screen (#78)
* add preview/hair check screen * chore(ux): add error log message * chore(ux): update handling of camera permissions * chore: reset integration test * fix build error * fix build error * chore: misc ux updates * chore: update sample and integration app results screen * chore: update UX * chore: minor UX tweak on oval dimension * chore: remove camera permission title * chore: update UX * chore: update per review feedback * chore: update localization strings * update readme
1 parent 615ccd8 commit 471ac56

34 files changed

+477
-410
lines changed

HostApp/HostApp/Views/LivenessResultContentView+Result.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,19 @@ extension LivenessResultContentView {
1414
let valueTextColor: Color
1515
let valueBackgroundColor: Color
1616
let auditImage: Data?
17-
17+
let isLive: Bool
18+
1819
init(livenessResult: LivenessResult) {
1920
guard livenessResult.confidenceScore > 0 else {
2021
text = ""
2122
value = ""
2223
valueTextColor = .clear
2324
valueBackgroundColor = .clear
2425
auditImage = nil
26+
isLive = false
2527
return
2628
}
27-
29+
isLive = livenessResult.isLive
2830
let truncated = String(format: "%.4f", livenessResult.confidenceScore)
2931
value = truncated
3032
if livenessResult.isLive {

HostApp/HostApp/Views/LivenessResultContentView.swift

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ struct LivenessResultContentView: View {
1717
Text("Result:")
1818
Text(result.text)
1919
.fontWeight(.semibold)
20-
20+
.foregroundColor(result.valueTextColor)
21+
.padding(6)
22+
.background(result.valueBackgroundColor)
23+
.cornerRadius(8)
2124
}
2225
.padding(.bottom, 12)
2326

@@ -42,6 +45,20 @@ struct LivenessResultContentView: View {
4245
.frame(maxWidth: .infinity, idealHeight: 268)
4346
.background(Color.secondary.opacity(0.1))
4447
}
48+
49+
if !result.isLive {
50+
steps()
51+
.padding()
52+
.background(
53+
Rectangle()
54+
.foregroundColor(
55+
.dynamicColors(
56+
light: .hex("#ECECEC"),
57+
dark: .darkGray
58+
)
59+
)
60+
.cornerRadius(6))
61+
}
4562
}
4663
.padding(.bottom, 16)
4764
.onAppear {
@@ -54,6 +71,31 @@ struct LivenessResultContentView: View {
5471
}
5572
}
5673
}
74+
75+
private func steps() -> some View {
76+
func step(number: Int, text: String) -> some View {
77+
HStack(alignment: .top) {
78+
Text("\(number).")
79+
Text(text)
80+
}
81+
}
82+
83+
return VStack(
84+
alignment: .leading,
85+
spacing: 8
86+
) {
87+
Text("Tips to pass the video check:")
88+
.fontWeight(.semibold)
89+
step(number: 1, text: "Maximize your screen's brightness.")
90+
.accessibilityElement(children: .combine)
91+
92+
step(number: 2, text: "Avoid very bright lighting conditions, such as direct sunlight.")
93+
.accessibilityElement(children: .combine)
94+
95+
step(number: 3, text: "Remove sunglasses, mask, hat, or anything blocking your face.")
96+
.accessibilityElement(children: .combine)
97+
}
98+
}
5799
}
58100

59101

HostApp/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ cd amplify-ui-swift-livenes/HostApp
2929

3030
7. Once signed in and authenticated, the "Create Liveness Session" is enabled. Click the button to generate and get a session id from your backend.
3131

32-
8. Once a session id is created, the Liveness Check screen is displayed. Follow the instructions and click on Begin Check button to begin liveness verification.
32+
8. Once a session id is created, the Liveness Check screen is displayed. Follow the instructions and click on Start video check button to begin liveness verification.
3333

3434
## Provision AWS Backend Resources
3535

Sources/FaceLiveness/AV/CMSampleBuffer+Rotate.swift

Lines changed: 0 additions & 42 deletions
This file was deleted.

Sources/FaceLiveness/AV/LivenessCaptureSession.swift

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,34 @@ import AVFoundation
1111
class LivenessCaptureSession {
1212
let captureDevice: LivenessCaptureDevice
1313
private let captureQueue = DispatchQueue(label: "com.amazonaws.faceliveness.cameracapturequeue")
14-
let outputDelegate: OutputSampleBufferCapturer
14+
let outputDelegate: AVCaptureVideoDataOutputSampleBufferDelegate
1515
var captureSession: AVCaptureSession?
16+
17+
var outputSampleBufferCapturer: OutputSampleBufferCapturer? {
18+
return outputDelegate as? OutputSampleBufferCapturer
19+
}
1620

17-
init(captureDevice: LivenessCaptureDevice, outputDelegate: OutputSampleBufferCapturer) {
21+
init(captureDevice: LivenessCaptureDevice, outputDelegate: AVCaptureVideoDataOutputSampleBufferDelegate) {
1822
self.captureDevice = captureDevice
1923
self.outputDelegate = outputDelegate
2024
}
2125

2226
func startSession(frame: CGRect) throws -> CALayer {
27+
try startSession()
28+
29+
guard let captureSession = captureSession else {
30+
throw LivenessCaptureSessionError.captureSessionUnavailable
31+
}
32+
33+
let previewLayer = previewLayer(
34+
frame: frame,
35+
for: captureSession
36+
)
37+
38+
return previewLayer
39+
}
40+
41+
func startSession() throws {
2342
guard let camera = captureDevice.avCaptureDevice
2443
else { throw LivenessCaptureSessionError.cameraUnavailable }
2544

@@ -44,17 +63,10 @@ class LivenessCaptureSession {
4463
captureSession.startRunning()
4564
}
4665

47-
let previewLayer = previewLayer(
48-
frame: frame,
49-
for: captureSession
50-
)
51-
5266
videoOutput.setSampleBufferDelegate(
5367
outputDelegate,
5468
queue: captureQueue
5569
)
56-
57-
return previewLayer
5870
}
5971

6072
func stopRunning() {
@@ -83,6 +95,11 @@ class LivenessCaptureSession {
8395
_ output: AVCaptureVideoDataOutput,
8496
for captureSession: AVCaptureSession
8597
) throws {
98+
if captureSession.canAddOutput(output) {
99+
captureSession.addOutput(output)
100+
} else {
101+
throw LivenessCaptureSessionError.captureSessionOutputUnavailable
102+
}
86103
output.videoSettings = [
87104
kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA
88105
]
@@ -92,12 +109,6 @@ class LivenessCaptureSession {
92109
.forEach {
93110
$0.videoOrientation = .portrait
94111
}
95-
96-
if captureSession.canAddOutput(output) {
97-
captureSession.addOutput(output)
98-
} else {
99-
throw LivenessCaptureSessionError.captureSessionOutputUnavailable
100-
}
101112
}
102113

103114
private func previewLayer(

Sources/FaceLiveness/AV/OutputSampleBufferCapturer.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class OutputSampleBufferCapturer: NSObject, AVCaptureVideoDataOutputSampleBuffer
2424
) {
2525
videoChunker.consume(sampleBuffer)
2626

27-
guard let imageBuffer = sampleBuffer.rotateRightUpMirrored()
27+
guard let imageBuffer = sampleBuffer.imageBuffer
2828
else { return }
2929

3030
faceDetector.detectFaces(from: imageBuffer)

Sources/FaceLiveness/AV/VideoChunker.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ final class VideoChunker {
3434

3535
func start() {
3636
guard state == .pending else { return }
37-
state = .writing
3837
assetWriter.startWriting()
3938
assetWriter.startSession(atSourceTime: .zero)
39+
state = .writing
4040
}
4141

4242
func finish(singleFrame: @escaping (UIImage) -> Void) {
@@ -49,8 +49,8 @@ final class VideoChunker {
4949

5050
func consume(_ buffer: CMSampleBuffer) {
5151
if state == .awaitingSingleFrame {
52-
guard let rotated = buffer.rotateRightUpMirrored() else { return }
53-
let singleFrame = singleFrame(from: rotated)
52+
guard let imageBuffer = buffer.imageBuffer else { return }
53+
let singleFrame = singleFrame(from: imageBuffer)
5454
provideSingleFrame?(singleFrame)
5555
state = .complete
5656
}
@@ -66,10 +66,10 @@ final class VideoChunker {
6666
if assetWriterInput.isReadyForMoreMediaData {
6767
let timestamp = CMSampleBufferGetPresentationTimeStamp(buffer).seconds
6868
let presentationTime = CMTime(seconds: timestamp - startTimeSeconds, preferredTimescale: 600)
69-
guard let rotated = buffer.rotateRightUpMirrored() else { return }
69+
guard let imageBuffer = buffer.imageBuffer else { return }
7070

7171
pixelBufferAdaptor.append(
72-
rotated,
72+
imageBuffer,
7373
withPresentationTime: presentationTime
7474
)
7575
}

Sources/FaceLiveness/Resources/Assets.xcassets/illustration_face_good_fit.imageset/Contents.json

Lines changed: 0 additions & 21 deletions
This file was deleted.

Sources/FaceLiveness/Resources/Assets.xcassets/illustration_face_too_close.imageset/Contents.json

Lines changed: 0 additions & 21 deletions
This file was deleted.

0 commit comments

Comments
 (0)