diff --git a/liveness/src/main/java/com/amplifyframework/ui/liveness/camera/LivenessCoordinator.kt b/liveness/src/main/java/com/amplifyframework/ui/liveness/camera/LivenessCoordinator.kt index 1febd66d..3a91eb28 100644 --- a/liveness/src/main/java/com/amplifyframework/ui/liveness/camera/LivenessCoordinator.kt +++ b/liveness/src/main/java/com/amplifyframework/ui/liveness/camera/LivenessCoordinator.kt @@ -54,9 +54,13 @@ import java.util.Date import java.util.concurrent.Executors import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine +import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.MainScope +import kotlinx.coroutines.NonCancellable +import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import kotlinx.coroutines.plus internal typealias OnMuxedSegment = (bytes: ByteArray, timestamp: Long) -> Unit internal typealias OnChallengeComplete = () -> Unit @@ -82,6 +86,7 @@ internal class LivenessCoordinator( private val attemptCounter = AttemptCounter() private val analysisExecutor = Executors.newSingleThreadExecutor() + private val coordinatorScope = MainScope() + CoroutineName("LivenessCoordinator") val livenessState = LivenessState( sessionId = sessionId, @@ -153,7 +158,7 @@ internal class LivenessCoordinator( } private fun launchCamera(camera: Camera) { - MainScope().launch { + coordinatorScope.launch { delay(5_000) if (!previewTextureView.hasReceivedUpdate) { val faceLivenessException = FaceLivenessDetectionException( @@ -163,7 +168,7 @@ internal class LivenessCoordinator( processSessionError(faceLivenessException, true) } } - MainScope().launch { + coordinatorScope.launch { getCameraProvider(context).apply { if (lifecycleOwner.lifecycle.currentState != Lifecycle.State.DESTROYED) { unbindAll() @@ -241,7 +246,8 @@ internal class LivenessCoordinator( FaceLivenessDetectionException.UnsupportedChallengeTypeException(throwable = error) to true else -> FaceLivenessDetectionException( error.message ?: "Unknown error.", - error.recoverySuggestion, error + error.recoverySuggestion, + error ) to false } processSessionError(faceLivenessException, shouldStopLivenessSession) @@ -250,10 +256,8 @@ internal class LivenessCoordinator( } private fun unbindCamera(context: Context) { - MainScope().launch { - getCameraProvider(context).apply { - unbindAll() - } + coordinatorScope.launch(NonCancellable) { + getCameraProvider(context).unbindAll() } } @@ -294,44 +298,40 @@ internal class LivenessCoordinator( fun processLivenessCheckComplete() { livenessState.onLivenessChallengeComplete() - stopEncoder { livenessState.onFullChallengeComplete() } + coordinatorScope.launch { + encoder.stop() + livenessState.onFullChallengeComplete() + } } private fun processFinalEventsSent() { unbindCamera(context) } - private fun stopEncoder(onComplete: () -> Unit) { - encoder.stop { - MainScope().launch { - onComplete() - } - } - } - /** * This is only called when onDispose is triggered from FaceLivenessDetector view. * If we begin calling destroy in other places, we should ensure we are still tracking the proper error code. */ fun destroy(context: Context) { // Destroy all resources so a new coordinator can safely be created - encoder.stop { + coordinatorScope.launch(NonCancellable) { + encoder.stop() encoder.destroy() } val webSocketCloseCode = if (!disconnectEventReceived) WebSocketCloseCode.DISPOSED else null livenessState.onDestroy(true, webSocketCloseCode) unbindCamera(context) analysisExecutor.shutdown() + coordinatorScope.cancel() } - private suspend fun getCameraProvider(context: Context): ProcessCameraProvider = - suspendCoroutine { continuation -> - ProcessCameraProvider.getInstance(context).also { cameraProvider -> - cameraProvider.addListener({ - continuation.resume(cameraProvider.get()) - }, ContextCompat.getMainExecutor(context)) - } + private suspend fun getCameraProvider(context: Context): ProcessCameraProvider = suspendCoroutine { continuation -> + ProcessCameraProvider.getInstance(context).also { cameraProvider -> + cameraProvider.addListener({ + continuation.resume(cameraProvider.get()) + }, ContextCompat.getMainExecutor(context)) } + } companion object { const val TARGET_FPS_MIN = 24 diff --git a/liveness/src/main/java/com/amplifyframework/ui/liveness/camera/LivenessVideoEncoder.kt b/liveness/src/main/java/com/amplifyframework/ui/liveness/camera/LivenessVideoEncoder.kt index 138af1d8..000321ce 100644 --- a/liveness/src/main/java/com/amplifyframework/ui/liveness/camera/LivenessVideoEncoder.kt +++ b/liveness/src/main/java/com/amplifyframework/ui/liveness/camera/LivenessVideoEncoder.kt @@ -27,6 +27,8 @@ import androidx.annotation.WorkerThread import com.amplifyframework.core.Amplify import com.amplifyframework.ui.liveness.util.isKeyFrame import java.io.File +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine internal class LivenessVideoEncoder private constructor( width: Int, @@ -212,7 +214,7 @@ internal class LivenessVideoEncoder private constructor( } } - fun stop(onComplete: () -> Unit) { + suspend fun stop() = suspendCoroutine { continuation -> encoderHandler.post { encoding = false livenessMuxer?.stop() @@ -220,11 +222,11 @@ internal class LivenessVideoEncoder private constructor( if (LOGGING_ENABLED) { Log.i(TAG, "Stopping encoder") } - onComplete() + continuation.resume(Unit) } } - fun destroy() { + suspend fun destroy() = suspendCoroutine { continuation -> encoderHandler.post { if (LOGGING_ENABLED) { Log.i(TAG, "Destroying encoder") @@ -241,6 +243,8 @@ internal class LivenessVideoEncoder private constructor( // may already be stopped } encoder.release() + + continuation.resume(Unit) } } } diff --git a/samples/liveness/app/.gitignore b/samples/liveness/app/.gitignore index 7915f57c..f103701d 100644 --- a/samples/liveness/app/.gitignore +++ b/samples/liveness/app/.gitignore @@ -1,4 +1,4 @@ /build /buildNative **/awsconfiguration.json -**/amplifyconfiguration.json \ No newline at end of file +**/amplifyconfiguration**.json \ No newline at end of file