aws-amplify / amplify-ui-swift-liveness

This repository offers a UI component for Amazon Rekognition Face Liveness, enabling developers to ensure that only authentic users, and not bad actors using spoofs, can access their services.
https://ui.docs.amplify.aws/swift/connected-components/liveness
Apache License 2.0
9 stars 20 forks source link

Liveness camera page showing black screen #75

Closed finexusCriss closed 10 months ago

finexusCriss commented 10 months ago

I'm trying to bridge the Swift Liveness to React Native, the instruction screen is successfully shown, but at the camera page, a dot is shown, but the camera preview is completely not showing, and the cross button is showing up image

I have updated the IAM Role Permissions for the unauth role, but the result is still the same.

Below is my source code for the bridging from Swift to React Native AWSFaceLivenessView.swift

import SwiftUI
import FaceLiveness

struct AWSFaceLivenessView: View {
    @State private var isPresentingLiveness = true
    @State private var sessionID: SessionID?
    @State private var isSessionIDObtained = false
    @State private var isLivenessResultObtained = false

    var body: some View {
        VStack {
            if isSessionIDObtained {
                FaceLivenessDetectorView(
                    sessionID: sessionID?.sessionId ?? "",
                    region: "us-west-2",
                    disableStartView: true,
                    isPresented: $isPresentingLiveness,
                    onCompletion: { result in
                        switch result {
                        case .success:
                            print("Success")
                            isLivenessResultObtained = true
                        case .failure(let error):
                            print("Failure")
                        default:
                            print("Default")
                        }
                    }
                )
            } else {
                Text("Getting session ID for AWS Faceliveness")
            }

        }
        .task {
            do {
                sessionID = try await getSessionId()
                isSessionIDObtained = true
                print("sessionID: \(sessionID?.sessionId ?? "SessionID not found")")
            } catch sessionIDError.invalidURL {
                print ("invalid URL")
            } catch sessionIDError.invalidData {
                print ("invalid Data")
            } catch sessionIDError.invalidResponse {
                print ("invalid response")
            } catch {
                print("unexpected error")
            }
        }
    }

    func getSessionId() async throws -> SessionID {
        let endpoint = {myAPIOnLamdaFunctionToGetSessionID}

        guard let url = URL(string: endpoint) else { throw sessionIDError.invalidURL }

        let (data, response) = try await URLSession.shared.data(from: url)

        guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
            throw sessionIDError.invalidResponse
        }

        do {
            let decoder = JSONDecoder()
            decoder.keyDecodingStrategy = .convertFromSnakeCase
            return try decoder.decode(SessionID.self, from: data)
        } catch {
            throw sessionIDError.invalidData
        }
    }
}

struct SessionID: Codable {
    let sessionId: String
}

enum sessionIDError: Error {
    case invalidURL
    case invalidResponse
    case invalidData
}

Wrapping it in a view controller: AWSLivenessViewController.swift

import UIKit
import SwiftUI

class AWSLivenessViewController: UIViewController {

    let awsLivenessView = UIHostingController(rootView: AWSFaceLivenessView())

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        addChild(awsLivenessView)
        view.addSubview(awsLivenessView.view)
        setupConstraints()
    }

    fileprivate func setupConstraints() {
        awsLivenessView.view.translatesAutoresizingMaskIntoConstraints = false
        awsLivenessView.view.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        awsLivenessView.view.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
        awsLivenessView.view.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
        awsLivenessView.view.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
    }
}

The view manager: LivenessViewManager.swift

import Foundation
import UIKit

@objc(LivenessViewManager)
class LivenessViewManager: RCTViewManager {

  override var methodQueue: DispatchQueue! {
    return DispatchQueue.main
  }

  override static func requiresMainQueueSetup() -> Bool {
    return true
  }

  override func view() -> UIView! {
    return AWSLivenessViewController().view
  }

  @objc(presentViewController)
  func presentViewController() {
    DispatchQueue.main.async {
      let viewController = AWSLivenessViewController()
      let rootViewController = RCTPresentedViewController()

      rootViewController?.present(viewController, animated: true, completion: nil)
    }
  }}

LivenessViewManager.m

#import <React/RCTUtils.h>
#import "React/RCTViewManager.h"
@interface RCT_EXTERN_MODULE(LivenessViewManager, RCTViewManager)
@end

I have tested it in Swift, and it is working as expected, but when bridged to React Native, it is having the camera view black screen with only the cross button shown as in screenshot above. The error message received is: Error: Transpose unit is not supported. Socket SO_ERROR [54: Connection reset by peer] SocketStream read error [0x283544d20]: 1 54

Another thing I noticed is for the working one in Swift, the IAM Role has a last activity shown, while the not working on in React Native has no last activity shown.

Has been searching for solutions for days but still result in the same black screen of camera page, would be appreciate if any hint or sample code for how to bridge from Swift to React Native can be provided. Thank you!

phantumcode commented 10 months ago

@finexusCriss Thanks for submitting your question. Can you provide all of the sample code package into a sample app? We'll try to reproduce the issue and investigate further.

finexusCriss commented 10 months ago

Thank you for your reply, heres the sample app in React Native: https://github.com/finexusCriss/rnlivenesssampleapp

A standard yarn install npx pod-install ios

and opening the .xcworkspace file in XCode will do

finexusCriss commented 10 months ago

@phantumcode Would like to check if there's any update on this issue, thank you!

phantumcode commented 10 months ago

@finexusCriss Apologies for the slow response, I was able to reproduce the issue but haven't been able to determine root cause. It is likely an integration between the native camera view with react native. I will continue to investigate further and provide additional updates when they're available.

finexusCriss commented 10 months ago

Thank you so much for looking into the demo app, and glad that the issue is reproducible, looking forward to your update and solutions

phantumcode commented 10 months ago

@finexusCriss It doesn't appear to be a bug or issue with Liveness library. I was able to reproduce it by attempting to integrate with a basic camera view controller written from scratch.

I was able to resolve the issue by updating the LivenessViewManager to the folllowing code snippet:

@objc(LivenessViewManager)
class LivenessViewManager: RCTViewManager {
  let awsLivenessViewVC = UIHostingController(rootView: AWSFaceLivenessView())
  let rootViewController = RCTPresentedViewController()

  override var methodQueue: DispatchQueue! {
    return DispatchQueue(label: "com.amazonaws.faceliveness.cameracapturequeue")
  }

  override static func requiresMainQueueSetup() -> Bool {
    return true
  }

  override func view() -> UIView! {
    return awsLivenessViewVC.view
  }

  @objc(presentViewController)
  func presentViewController() {
    self.rootViewController?.present(self.awsLivenessViewVC, animated: true, completion: nil)
  }
}
finexusCriss commented 10 months ago

Thank you so much for helping to look into the issue! I am able to use resolve the issue by using your updated code for LivenessViewManager , never thought of putting the UIHostingController in presentViewController will cause such an issue, thanks again!