@smileid/usesmileid_huawei_face 12.0.2 → 12.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/android/build.gradle +18 -18
- package/android/src/main/java/com/usesmileid/huawei/face/UseSmileIDHuaweiFaceKameraAnalysisTick.kt +42 -0
- package/android/src/main/java/com/usesmileid/huawei/face/UseSmileIDHuaweiFaceModule.kt +6 -28
- package/android/src/main/java/com/usesmileid/huawei/face/UseSmileIDHuaweiFaceSessionHolder.kt +2 -3
- package/android/src/main/java/com/usesmileid/rn/analyzer/huaweiface/UseSmileIDAnalyzerExecutor.kt +28 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1 -37
- package/package.json +10 -9
- package/android/src/main/java/com/usesmileid/huawei/face/UseSmileIDHuaweiFaceFrameProcessorPlugin.kt +0 -79
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/src/__tests__/use_smile_id_huawei_face.test.ts +0 -79
- package/src/index.ts +0 -42
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
Full release notes: [Smile ID docs](https://docs.usesmileid.com/developer-resources/sdks/mobile/release-notes/react-native-expo).
|
|
2
|
+
|
|
3
|
+
## 12.1.1
|
|
4
|
+
|
|
5
|
+
See the repository CHANGELOG for the 12.1.1 release notes.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
## 12.1.0
|
|
9
|
+
|
|
10
|
+
See the repository CHANGELOG for the 12.1.0 release notes.
|
|
11
|
+
|
|
12
|
+
|
|
1
13
|
## 12.0.2
|
|
2
14
|
|
|
3
15
|
See the repository CHANGELOG for the 12.0.2 release notes.
|
package/android/build.gradle
CHANGED
|
@@ -17,7 +17,7 @@ plugins {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
group = 'com.usesmileid.huawei.face'
|
|
20
|
-
version = '12.
|
|
20
|
+
version = '12.1.1'
|
|
21
21
|
|
|
22
22
|
// HMS ML is published only to Huawei's own Maven repository — none of the
|
|
23
23
|
// com.huawei.hms:ml-computer-vision-* artifacts exist on Maven Central. Expo
|
|
@@ -56,7 +56,7 @@ android {
|
|
|
56
56
|
// android/build.gradle and the rest of the @expo/* modules.
|
|
57
57
|
defaultConfig {
|
|
58
58
|
versionCode 1
|
|
59
|
-
versionName '12.
|
|
59
|
+
versionName '12.1.1'
|
|
60
60
|
// R8 keep-rules to bundle into partner apps that depend on this
|
|
61
61
|
// module. Without these, R8 may rename the Expo module entry
|
|
62
62
|
// point and the JS-bridged member functions, breaking autolinking.
|
|
@@ -66,27 +66,27 @@ android {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
dependencies {
|
|
69
|
+
// kamera native core: the analyzer executor takes analysis frames from
|
|
70
|
+
// kamera's KameraFrameHub so pixels never cross the JS bridge. Resolves from
|
|
71
|
+
// Maven Central; a local publishToMavenLocal build wins (see kamera.md).
|
|
72
|
+
implementation 'com.usesmileid:kamera:1.0.6'
|
|
73
|
+
|
|
69
74
|
// Existing Smile ID native AAR — the entire ML pipeline lives here.
|
|
70
75
|
// Targets Huawei AppGallery / no-GMS devices via HMS ML kit. The
|
|
71
76
|
// Expo Module is a thin Kotlin shim that constructs the session
|
|
72
77
|
// and bridges its four methods to JS via Expo Modules.
|
|
73
|
-
implementation 'com.usesmileid:huawei-face:12.
|
|
78
|
+
implementation 'com.usesmileid:huawei-face:12.1.1'
|
|
74
79
|
|
|
75
|
-
// VisionCamera frame-processor plugin classes. The host app provides
|
|
76
|
-
// this dependency via RN autolinking — react-native-vision-camera >= 4.7.3
|
|
77
|
-
// must be present in the partner app's package.json.
|
|
78
|
-
implementation project(':react-native-vision-camera')
|
|
79
80
|
|
|
80
|
-
|
|
81
|
-
// frame processor plugin. VisionCamera declares camera-core as
|
|
82
|
-
// `implementation`, so it is NOT on this module's compile classpath
|
|
83
|
-
// transitively — compileOnly pins the compile-time API while the host
|
|
84
|
-
// app's VisionCamera provides the runtime classes (its camera-core is
|
|
85
|
-
// 1.5.0-alpha03; the `ImageProxy` surface used here is stable since 1.0).
|
|
86
|
-
compileOnly 'androidx.camera:camera-core:1.5.0-alpha03'
|
|
81
|
+
}
|
|
87
82
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
83
|
+
// kamera resolves from Maven Central. mavenLocal stays injected — content-filtered to
|
|
84
|
+
// com.usesmileid — the same way kamera's own glue does it, so a locally published kamera
|
|
85
|
+
// build takes precedence during the kamera dev loop without host apps editing repos.
|
|
86
|
+
rootProject.allprojects {
|
|
87
|
+
repositories {
|
|
88
|
+
mavenLocal {
|
|
89
|
+
content { includeGroup 'com.usesmileid' }
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
92
|
}
|
package/android/src/main/java/com/usesmileid/huawei/face/UseSmileIDHuaweiFaceKameraAnalysisTick.kt
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
package com.usesmileid.huawei.face
|
|
2
|
+
|
|
3
|
+
import com.usesmileid.bridge.model.YuvFrame as NativeYuvFrame
|
|
4
|
+
import com.usesmileid.kamera.integration.KameraFrameHub
|
|
5
|
+
import com.usesmileid.rn.analyzer.huaweiface.toNativeYuvFrame
|
|
6
|
+
import kotlin.coroutines.cancellation.CancellationException
|
|
7
|
+
import kotlinx.coroutines.runBlocking
|
|
8
|
+
import kotlinx.coroutines.withContext
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Scans the latest [KameraFrameHub] frame natively; no pending frame returns the `no_frame` wire error.
|
|
12
|
+
* The frame's rotationDegrees already uprights the buffer to the locked interface axis.
|
|
13
|
+
*/
|
|
14
|
+
internal fun analyzeKameraFrameTick(arguments: Map<String, Any?>?): Any {
|
|
15
|
+
val frame = KameraFrameHub.take() ?: return mapOf("error" to "no_frame")
|
|
16
|
+
val yuvFrame = try {
|
|
17
|
+
frame.toNativeYuvFrame()
|
|
18
|
+
} finally {
|
|
19
|
+
// Closing releases the frame to the pipeline; the converted copy owns its own bytes.
|
|
20
|
+
frame.close()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Device-tilt context (0 = upright portrait) for the validators.
|
|
24
|
+
val jsRotation = (arguments?.get("rotation") as? Number)?.toInt() ?: 0
|
|
25
|
+
|
|
26
|
+
return runBlocking {
|
|
27
|
+
withContext(UseSmileIDHuaweiFaceSessionHolder.sessionDispatcher) {
|
|
28
|
+
val activeSession = UseSmileIDHuaweiFaceSessionHolder.session
|
|
29
|
+
?: return@withContext mapOf("error" to "session_not_configured")
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
val scanResult = activeSession.scan(yuvFrame, jsRotation)
|
|
33
|
+
val events = activeSession.drainCaptureReadyEvents()
|
|
34
|
+
UseSmileIDHuaweiFaceModule.buildWireResult(scanResult, events)
|
|
35
|
+
} catch (cancellation: CancellationException) {
|
|
36
|
+
throw cancellation
|
|
37
|
+
} catch (e: Exception) {
|
|
38
|
+
mapOf("error" to ("scan_failed: " + (e.message ?: e.javaClass.simpleName)))
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
package com.usesmileid.huawei.face
|
|
2
2
|
|
|
3
|
-
import com.mrousavy.camera.frameprocessors.FrameProcessorPluginRegistry
|
|
4
3
|
import com.usesmileid.bridge.dsl.builder.FaceDetectorMode
|
|
5
4
|
import com.usesmileid.bridge.huawei.face.HuaweiFaceDetectorAnalyzer
|
|
6
5
|
import com.usesmileid.bridge.model.BoundingBox as NativeBoundingBox
|
|
@@ -27,10 +26,7 @@ import expo.modules.kotlin.modules.ModuleDefinition
|
|
|
27
26
|
* variant targets devices without Google Mobile Services and uses
|
|
28
27
|
* Huawei HMS ML kit for face detection.
|
|
29
28
|
*
|
|
30
|
-
*
|
|
31
|
-
* which is registered with VisionCamera's frame-processor runtime in
|
|
32
|
-
* [OnCreate]. Partners call `VisionCameraProxy.initFrameProcessorPlugin`
|
|
33
|
-
* from their JS worklet instead of the old `AsyncFunction("analyze")`.
|
|
29
|
+
* Frames reach `analyzeKameraFrame` through kamera's native hub; pixels never cross the JS bridge.
|
|
34
30
|
*/
|
|
35
31
|
class UseSmileIDHuaweiFaceModule : Module() {
|
|
36
32
|
|
|
@@ -105,20 +101,17 @@ class UseSmileIDHuaweiFaceModule : Module() {
|
|
|
105
101
|
?: throw IllegalStateException("Module not attached to a React context")
|
|
106
102
|
executor.onAttach(ctx)
|
|
107
103
|
|
|
108
|
-
// Guard against duplicate registration on dev-client hot reloads
|
|
109
|
-
// where OnCreate can fire more than once in the same process.
|
|
110
|
-
if (!pluginRegistered) {
|
|
111
|
-
pluginRegistered = true
|
|
112
|
-
FrameProcessorPluginRegistry.addFrameProcessorPlugin(
|
|
113
|
-
FRAME_PROCESSOR_PLUGIN_NAME,
|
|
114
|
-
) { _, _ -> UseSmileIDHuaweiFaceFrameProcessorPlugin() }
|
|
115
|
-
}
|
|
116
104
|
}
|
|
117
105
|
|
|
118
106
|
AsyncFunction("configure") { config: Map<String, Any?> ->
|
|
119
107
|
executor.handleConfigure(config)
|
|
120
108
|
}
|
|
121
109
|
|
|
110
|
+
AsyncFunction("analyzeKameraFrame") { arguments: Map<String, Any?>? ->
|
|
111
|
+
// Blocks this module's async queue so only one scan is in flight at a time.
|
|
112
|
+
analyzeKameraFrameTick(arguments)
|
|
113
|
+
}
|
|
114
|
+
|
|
122
115
|
AsyncFunction("reset") {
|
|
123
116
|
executor.handleReset()
|
|
124
117
|
}
|
|
@@ -145,21 +138,6 @@ class UseSmileIDHuaweiFaceModule : Module() {
|
|
|
145
138
|
/** Wire-format key partners pass via `FaceConfig.analyzerFactoryKey`. */
|
|
146
139
|
const val KEY: String = "huawei-face"
|
|
147
140
|
|
|
148
|
-
/**
|
|
149
|
-
* Must match the JS worklet's `VisionCameraProxy.initFrameProcessorPlugin(...)`
|
|
150
|
-
* call exactly — drift is a runtime "plugin not found" error.
|
|
151
|
-
*/
|
|
152
|
-
const val FRAME_PROCESSOR_PLUGIN_NAME: String = "UseSmileIDHuaweiFaceFrameProcessor"
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* [FrameProcessorPluginRegistry.addFrameProcessorPlugin] asserts uniqueness
|
|
156
|
-
* and crashes on duplicate registration; this guard prevents that on dev-client
|
|
157
|
-
* hot reloads where [OnCreate] fires more than once per process.
|
|
158
|
-
*/
|
|
159
|
-
@Volatile
|
|
160
|
-
private var pluginRegistered: Boolean = false
|
|
161
|
-
|
|
162
|
-
/** Wire-result builder shared with [UseSmileIDHuaweiFaceFrameProcessorPlugin]. */
|
|
163
141
|
internal fun buildWireResult(
|
|
164
142
|
scanResult: UseSmileIDFaceScanResult,
|
|
165
143
|
events: List<NativeCaptureReadyEvent>,
|
package/android/src/main/java/com/usesmileid/huawei/face/UseSmileIDHuaweiFaceSessionHolder.kt
CHANGED
|
@@ -10,9 +10,8 @@ import kotlinx.coroutines.asCoroutineDispatcher
|
|
|
10
10
|
*
|
|
11
11
|
* The session's state machine is not thread-safe; all operations —
|
|
12
12
|
* scan+drain as one atomic unit, flush, reset, close — are serialized
|
|
13
|
-
* through [sessionDispatcher] (a single-thread executor). The
|
|
14
|
-
*
|
|
15
|
-
* arrive while a scan is in flight rather than queuing them.
|
|
13
|
+
* through [sessionDispatcher] (a single-thread executor). The analysis tick blocks its
|
|
14
|
+
* caller via [runBlocking]; kamera's hub keeps only the latest frame, so frames never queue.
|
|
16
15
|
*/
|
|
17
16
|
internal object UseSmileIDHuaweiFaceSessionHolder {
|
|
18
17
|
|
package/android/src/main/java/com/usesmileid/rn/analyzer/huaweiface/UseSmileIDAnalyzerExecutor.kt
CHANGED
|
@@ -12,6 +12,8 @@ import com.usesmileid.bridge.model.CaptureType
|
|
|
12
12
|
import com.usesmileid.bridge.model.YuvFrame as NativeYuvFrame
|
|
13
13
|
import com.usesmileid.bridge.storage.DocumentSide
|
|
14
14
|
import com.usesmileid.bridge.storage.FrameStorage
|
|
15
|
+
import com.usesmileid.kamera.frame.KameraFrame
|
|
16
|
+
import com.usesmileid.kamera.integration.KameraFrameHub
|
|
15
17
|
import java.io.File
|
|
16
18
|
import kotlinx.coroutines.CoroutineDispatcher
|
|
17
19
|
import kotlinx.coroutines.CoroutineScope
|
|
@@ -171,6 +173,7 @@ abstract class UseSmileIDAnalyzerExecutor<
|
|
|
171
173
|
|
|
172
174
|
// Shared implementation behind the Expo Module's async functions -----
|
|
173
175
|
|
|
176
|
+
|
|
174
177
|
/**
|
|
175
178
|
* Implementation behind `configure`. Closes any prior session,
|
|
176
179
|
* allocates a fresh on-disk session directory, and delegates session
|
|
@@ -282,3 +285,28 @@ abstract class UseSmileIDAnalyzerExecutor<
|
|
|
282
285
|
return dir
|
|
283
286
|
}
|
|
284
287
|
}
|
|
288
|
+
|
|
289
|
+
/** Copies the YUV planes so the frame can close at once; rotation stays metadata for the detector. */
|
|
290
|
+
internal fun KameraFrame.toNativeYuvFrame(): NativeYuvFrame {
|
|
291
|
+
val yPlane = planes[0]
|
|
292
|
+
val uPlane = planes[1]
|
|
293
|
+
val vPlane = planes[2]
|
|
294
|
+
fun copyBytes(buffer: java.nio.ByteBuffer): ByteArray {
|
|
295
|
+
val source = buffer.duplicate()
|
|
296
|
+
source.rewind()
|
|
297
|
+
return ByteArray(source.remaining()).also { source.get(it) }
|
|
298
|
+
}
|
|
299
|
+
return NativeYuvFrame(
|
|
300
|
+
planeY = copyBytes(yPlane.buffer),
|
|
301
|
+
planeU = copyBytes(uPlane.buffer),
|
|
302
|
+
planeV = copyBytes(vPlane.buffer),
|
|
303
|
+
yRowStride = yPlane.rowStride,
|
|
304
|
+
uRowStride = uPlane.rowStride,
|
|
305
|
+
vRowStride = vPlane.rowStride,
|
|
306
|
+
uPixelStride = uPlane.pixelStride,
|
|
307
|
+
vPixelStride = vPlane.pixelStride,
|
|
308
|
+
width = width,
|
|
309
|
+
height = height,
|
|
310
|
+
rotation = rotationDegrees,
|
|
311
|
+
)
|
|
312
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -19,9 +19,10 @@ import type { FaceConfig, FaceScanResult } from "@smileid/usesmileid_bridge";
|
|
|
19
19
|
* contract.
|
|
20
20
|
*/
|
|
21
21
|
/**
|
|
22
|
-
* Must match the Kotlin Expo Module's `Name("UseSmileIDHuaweiFace")`
|
|
23
|
-
*
|
|
24
|
-
*
|
|
22
|
+
* Must match the Kotlin Expo Module's `Name("UseSmileIDHuaweiFace")` — drift is a runtime
|
|
23
|
+
* "module not found" error, not a compile error.
|
|
24
|
+
*
|
|
25
|
+
* `frameProcessorPluginName` is inert; kept only for API compatibility.
|
|
25
26
|
*/
|
|
26
27
|
export declare const UseSmileIDHuaweiFaceIdentifiers: {
|
|
27
28
|
readonly nativeModuleName: "UseSmileIDHuaweiFace";
|
|
@@ -29,4 +30,3 @@ export declare const UseSmileIDHuaweiFaceIdentifiers: {
|
|
|
29
30
|
};
|
|
30
31
|
export declare const useSmileIDHuaweiFace: import("@smileid/usesmileid_platform_interface").FaceAnalyzer;
|
|
31
32
|
export type { FaceConfig, FaceScanResult };
|
|
32
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,37 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { useSmileIDAnalyzerExecutor } from "@smileid/usesmileid_platform_interface";
|
|
3
|
-
/**
|
|
4
|
-
* Huawei HMS face analyzer (Android-only, for devices without
|
|
5
|
-
* Google Mobile Services).
|
|
6
|
-
*
|
|
7
|
-
* Thin shim around the existing native session
|
|
8
|
-
* `com.usesmileid.bridge.session.UseSmileIDFaceSession(HuaweiFaceDetectorAnalyzer())`
|
|
9
|
-
* shipped in the `com.smileidentity:huawei-face` AAR. The native side
|
|
10
|
-
* runs the entire pipeline (analyzer + validator + transitioner +
|
|
11
|
-
* state machine); this TS layer routes calls and validates wire
|
|
12
|
-
* payloads at the channel boundary.
|
|
13
|
-
*
|
|
14
|
-
* The canonical wiring lives in [useSmileIDAnalyzerExecutor] in
|
|
15
|
-
* `usesmileid_platform_interface`.
|
|
16
|
-
*
|
|
17
|
-
* Lifecycle: `configure()` → per-frame analysis via the native frame
|
|
18
|
-
* processor plugin → `reset()`? → `close()`. See
|
|
19
|
-
* `usesmileid_platform_interface` `FaceAnalyzer` for the canonical
|
|
20
|
-
* contract.
|
|
21
|
-
*/
|
|
22
|
-
/**
|
|
23
|
-
* Must match the Kotlin Expo Module's `Name("UseSmileIDHuaweiFace")` and its
|
|
24
|
-
* VisionCamera frame-processor-plugin registration — drift is a runtime
|
|
25
|
-
* "module/plugin not found" error, not a compile error.
|
|
26
|
-
*/
|
|
27
|
-
export const UseSmileIDHuaweiFaceIdentifiers = {
|
|
28
|
-
nativeModuleName: "UseSmileIDHuaweiFace",
|
|
29
|
-
frameProcessorPluginName: "UseSmileIDHuaweiFaceFrameProcessor",
|
|
30
|
-
};
|
|
31
|
-
export const useSmileIDHuaweiFace = useSmileIDAnalyzerExecutor({
|
|
32
|
-
nativeModuleName: UseSmileIDHuaweiFaceIdentifiers.nativeModuleName,
|
|
33
|
-
frameProcessorPluginName: UseSmileIDHuaweiFaceIdentifiers.frameProcessorPluginName,
|
|
34
|
-
kind: "face",
|
|
35
|
-
key: AnalyzerFactoryKey.HuaweiFace,
|
|
36
|
-
});
|
|
37
|
-
//# sourceMappingURL=index.js.map
|
|
1
|
+
"use strict";import{AnalyzerFactoryKey as e}from"@smileid/usesmileid_bridge";import{useSmileIDAnalyzerExecutor as a}from"@smileid/usesmileid_platform_interface";export const UseSmileIDHuaweiFaceIdentifiers={nativeModuleName:"UseSmileIDHuaweiFace",frameProcessorPluginName:"UseSmileIDHuaweiFaceFrameProcessor"},useSmileIDHuaweiFace=a({nativeModuleName:UseSmileIDHuaweiFaceIdentifiers.nativeModuleName,frameProcessorPluginName:UseSmileIDHuaweiFaceIdentifiers.frameProcessorPluginName,kind:"face",key:e.HuaweiFace});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smileid/usesmileid_huawei_face",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.1.1",
|
|
4
4
|
"description": "Huawei HMS face detection implementation for the UseSmileID React Native (Expo) SDK.",
|
|
5
5
|
"homepage": "https://usesmileid.com",
|
|
6
6
|
"repository": {
|
|
@@ -35,16 +35,17 @@
|
|
|
35
35
|
},
|
|
36
36
|
"files": [
|
|
37
37
|
"dist",
|
|
38
|
-
"
|
|
39
|
-
"android",
|
|
38
|
+
"android/build.gradle",
|
|
39
|
+
"android/consumer-rules.pro",
|
|
40
|
+
"android/src/main",
|
|
40
41
|
"expo-module.config.json",
|
|
41
42
|
"README.md",
|
|
42
43
|
"LICENSE",
|
|
43
44
|
"CHANGELOG.md"
|
|
44
45
|
],
|
|
45
46
|
"dependencies": {
|
|
46
|
-
"@smileid/
|
|
47
|
-
"@smileid/
|
|
47
|
+
"@smileid/usesmileid_bridge": "12.1.1",
|
|
48
|
+
"@smileid/usesmileid_platform_interface": "12.1.1"
|
|
48
49
|
},
|
|
49
50
|
"peerDependencies": {
|
|
50
51
|
"expo": ">=56.0.0",
|
|
@@ -52,18 +53,18 @@
|
|
|
52
53
|
"react-native": ">=0.83.0"
|
|
53
54
|
},
|
|
54
55
|
"devDependencies": {
|
|
55
|
-
"expo-modules-core": "^
|
|
56
|
-
"react-native": "^0.
|
|
56
|
+
"expo-modules-core": "^57.0.16",
|
|
57
|
+
"react-native": "^0.86.3",
|
|
57
58
|
"typescript": "^6.0.3"
|
|
58
59
|
},
|
|
59
60
|
"publishConfig": {
|
|
60
61
|
"access": "public"
|
|
61
62
|
},
|
|
62
63
|
"scripts": {
|
|
63
|
-
"build": "tsc -p tsconfig.json",
|
|
64
|
+
"build": "tsc -p tsconfig.json && node ../Scripts/copy-assets.mjs",
|
|
64
65
|
"typecheck": "tsc -b",
|
|
65
66
|
"test": "vitest run",
|
|
66
67
|
"test:watch": "vitest",
|
|
67
|
-
"clean": "rm -rf dist .turbo coverage"
|
|
68
|
+
"clean": "rm -rf dist .turbo coverage tsconfig.tsbuildinfo"
|
|
68
69
|
}
|
|
69
70
|
}
|
package/android/src/main/java/com/usesmileid/huawei/face/UseSmileIDHuaweiFaceFrameProcessorPlugin.kt
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
package com.usesmileid.huawei.face
|
|
2
|
-
|
|
3
|
-
import androidx.camera.core.ExperimentalGetImage
|
|
4
|
-
import com.mrousavy.camera.core.FrameInvalidError
|
|
5
|
-
import com.mrousavy.camera.frameprocessors.Frame
|
|
6
|
-
import com.mrousavy.camera.frameprocessors.FrameProcessorPlugin
|
|
7
|
-
import com.usesmileid.bridge.model.YuvFrame as NativeYuvFrame
|
|
8
|
-
import kotlin.coroutines.cancellation.CancellationException
|
|
9
|
-
import kotlinx.coroutines.runBlocking
|
|
10
|
-
import kotlinx.coroutines.withContext
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* VisionCamera Frame Processor Plugin for the Huawei HMS ML Kit face backend.
|
|
14
|
-
* Registered under [UseSmileIDHuaweiFaceModule.FRAME_PROCESSOR_PLUGIN_NAME].
|
|
15
|
-
* Reads raw [ImageProxy] planes directly (no JPEG encoding) and runs the
|
|
16
|
-
* native session pipeline. Blocks the FP thread via [runBlocking] —
|
|
17
|
-
* VisionCamera drops frames that arrive while a scan is in flight.
|
|
18
|
-
*/
|
|
19
|
-
@OptIn(ExperimentalGetImage::class)
|
|
20
|
-
internal class UseSmileIDHuaweiFaceFrameProcessorPlugin : FrameProcessorPlugin() {
|
|
21
|
-
|
|
22
|
-
override fun callback(frame: Frame, arguments: Map<String, Any>?): Any? {
|
|
23
|
-
val imageProxy = try {
|
|
24
|
-
frame.imageProxy
|
|
25
|
-
} catch (e: FrameInvalidError) {
|
|
26
|
-
return mapOf("error" to "frame_conversion_failed")
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
val image = imageProxy.image
|
|
30
|
-
?: return mapOf("error" to "frame_conversion_failed")
|
|
31
|
-
|
|
32
|
-
// Pixel-orientation for ML Kit — always from ImageProxy metadata, never from JS.
|
|
33
|
-
val sensorRotation = imageProxy.imageInfo.rotationDegrees
|
|
34
|
-
|
|
35
|
-
// Device-tilt context (0 = upright portrait) for head-pose math.
|
|
36
|
-
// Must not fall back to sensorRotation — sensor angle != device tilt.
|
|
37
|
-
val jsRotation = (arguments?.get("rotation") as? Number)?.toInt() ?: 0
|
|
38
|
-
|
|
39
|
-
val planes = image.planes
|
|
40
|
-
if (planes.size < 3) return mapOf("error" to "frame_conversion_failed")
|
|
41
|
-
|
|
42
|
-
fun extractPlane(index: Int): ByteArray {
|
|
43
|
-
val buffer = planes[index].buffer
|
|
44
|
-
buffer.rewind()
|
|
45
|
-
return ByteArray(buffer.remaining()).also { buffer.get(it) }
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
val yuvFrame = NativeYuvFrame(
|
|
49
|
-
planeY = extractPlane(0),
|
|
50
|
-
planeU = extractPlane(1),
|
|
51
|
-
planeV = extractPlane(2),
|
|
52
|
-
yRowStride = planes[0].rowStride,
|
|
53
|
-
uRowStride = planes[1].rowStride,
|
|
54
|
-
vRowStride = planes[2].rowStride,
|
|
55
|
-
uPixelStride = planes[1].pixelStride,
|
|
56
|
-
vPixelStride = planes[2].pixelStride,
|
|
57
|
-
width = image.width,
|
|
58
|
-
height = image.height,
|
|
59
|
-
rotation = sensorRotation,
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
return runBlocking {
|
|
63
|
-
withContext(UseSmileIDHuaweiFaceSessionHolder.sessionDispatcher) {
|
|
64
|
-
val activeSession = UseSmileIDHuaweiFaceSessionHolder.session
|
|
65
|
-
?: return@withContext mapOf("error" to "session_not_configured")
|
|
66
|
-
|
|
67
|
-
try {
|
|
68
|
-
val scanResult = activeSession.scan(yuvFrame, jsRotation)
|
|
69
|
-
val events = activeSession.drainCaptureReadyEvents()
|
|
70
|
-
UseSmileIDHuaweiFaceModule.buildWireResult(scanResult, events)
|
|
71
|
-
} catch (cancellation: CancellationException) {
|
|
72
|
-
throw cancellation
|
|
73
|
-
} catch (e: Exception) {
|
|
74
|
-
mapOf("error" to ("scan_failed: " + (e.message ?: e.javaClass.simpleName)))
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAG7E;;;;;;;;;;;;;;;;;;GAkBG;AACH;;;;GAIG;AACH,eAAO,MAAM,+BAA+B;;;CAGlC,CAAC;AAEX,eAAO,MAAM,oBAAoB,+DAM/B,CAAC;AAEH,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,CAAC"}
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAEhE,OAAO,EAAE,0BAA0B,EAAE,MAAM,wCAAwC,CAAC;AAEpF;;;;;;;;;;;;;;;;;;GAkBG;AACH;;;;GAIG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG;IAC7C,gBAAgB,EAAE,sBAAsB;IACxC,wBAAwB,EAAE,oCAAoC;CACtD,CAAC;AAEX,MAAM,CAAC,MAAM,oBAAoB,GAAG,0BAA0B,CAAC;IAC7D,gBAAgB,EAAE,+BAA+B,CAAC,gBAAgB;IAClE,wBAAwB,EACtB,+BAA+B,CAAC,wBAAwB;IAC1D,IAAI,EAAE,MAAM;IACZ,GAAG,EAAE,kBAAkB,CAAC,UAAU;CACnC,CAAC,CAAC"}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi, beforeEach, type Mock } from "vitest";
|
|
2
|
-
|
|
3
|
-
const native = vi.hoisted(() => ({
|
|
4
|
-
configure: vi.fn(),
|
|
5
|
-
reset: vi.fn(),
|
|
6
|
-
close: vi.fn(),
|
|
7
|
-
}));
|
|
8
|
-
|
|
9
|
-
vi.mock("expo-modules-core", () => ({
|
|
10
|
-
requireNativeModule: vi.fn(() => native),
|
|
11
|
-
}));
|
|
12
|
-
|
|
13
|
-
import { requireNativeModule } from "expo-modules-core";
|
|
14
|
-
import {
|
|
15
|
-
useSmileIDHuaweiFace,
|
|
16
|
-
UseSmileIDHuaweiFaceIdentifiers,
|
|
17
|
-
} from "../index";
|
|
18
|
-
|
|
19
|
-
describe("useSmileIDHuaweiFace — TS wrapper", () => {
|
|
20
|
-
beforeEach(() => {
|
|
21
|
-
Object.values(native).forEach((m) => (m as Mock).mockReset());
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it("looks up the native module under the contract-stable name", () => {
|
|
25
|
-
expect(requireNativeModule).toHaveBeenCalledWith("UseSmileIDHuaweiFace");
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it("exposes the lifecycle surface — no per-frame analyze method", () => {
|
|
29
|
-
expect(typeof useSmileIDHuaweiFace.configure).toBe("function");
|
|
30
|
-
expect(typeof useSmileIDHuaweiFace.reset).toBe("function");
|
|
31
|
-
expect(typeof useSmileIDHuaweiFace.close).toBe("function");
|
|
32
|
-
// Per-frame analysis lives in the native VisionCamera frame
|
|
33
|
-
// processor plugin — raw pixels never cross the JS bridge.
|
|
34
|
-
expect("analyze" in useSmileIDHuaweiFace).toBe(false);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it("exposes the kind discriminator and frame processor plugin name", () => {
|
|
38
|
-
expect(useSmileIDHuaweiFace.kind).toBe("face");
|
|
39
|
-
expect(useSmileIDHuaweiFace.frameProcessorPluginName).toBe(
|
|
40
|
-
UseSmileIDHuaweiFaceIdentifiers.frameProcessorPluginName,
|
|
41
|
-
);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
describe("configure", () => {
|
|
45
|
-
it("passes the validated config through to native", async () => {
|
|
46
|
-
native.configure.mockResolvedValueOnce(undefined);
|
|
47
|
-
await useSmileIDHuaweiFace.configure({ selfieCaptureTimeout: 60_000 });
|
|
48
|
-
expect(native.configure).toHaveBeenCalledTimes(1);
|
|
49
|
-
expect(native.configure).toHaveBeenCalledWith({
|
|
50
|
-
selfieCaptureTimeout: 60_000,
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it("rejects malformed config at the JS boundary before crossing the bridge", async () => {
|
|
55
|
-
await expect(
|
|
56
|
-
useSmileIDHuaweiFace.configure({
|
|
57
|
-
// @ts-expect-error — int field with float value
|
|
58
|
-
selfieCaptureTimeout: 60_000.5,
|
|
59
|
-
}),
|
|
60
|
-
).rejects.toThrow();
|
|
61
|
-
expect(native.configure).not.toHaveBeenCalled();
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
describe("reset / close", () => {
|
|
66
|
-
it("routes reset through to native", async () => {
|
|
67
|
-
native.reset.mockResolvedValueOnce(undefined);
|
|
68
|
-
await useSmileIDHuaweiFace.reset();
|
|
69
|
-
expect(native.reset).toHaveBeenCalledTimes(1);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it("routes close through to native and is idempotent", async () => {
|
|
73
|
-
native.close.mockResolvedValue(undefined);
|
|
74
|
-
await useSmileIDHuaweiFace.close();
|
|
75
|
-
await useSmileIDHuaweiFace.close();
|
|
76
|
-
expect(native.close).toHaveBeenCalledTimes(2);
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
});
|
package/src/index.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { AnalyzerFactoryKey } from "@smileid/usesmileid_bridge";
|
|
2
|
-
import type { FaceConfig, FaceScanResult } from "@smileid/usesmileid_bridge";
|
|
3
|
-
import { useSmileIDAnalyzerExecutor } from "@smileid/usesmileid_platform_interface";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Huawei HMS face analyzer (Android-only, for devices without
|
|
7
|
-
* Google Mobile Services).
|
|
8
|
-
*
|
|
9
|
-
* Thin shim around the existing native session
|
|
10
|
-
* `com.usesmileid.bridge.session.UseSmileIDFaceSession(HuaweiFaceDetectorAnalyzer())`
|
|
11
|
-
* shipped in the `com.smileidentity:huawei-face` AAR. The native side
|
|
12
|
-
* runs the entire pipeline (analyzer + validator + transitioner +
|
|
13
|
-
* state machine); this TS layer routes calls and validates wire
|
|
14
|
-
* payloads at the channel boundary.
|
|
15
|
-
*
|
|
16
|
-
* The canonical wiring lives in [useSmileIDAnalyzerExecutor] in
|
|
17
|
-
* `usesmileid_platform_interface`.
|
|
18
|
-
*
|
|
19
|
-
* Lifecycle: `configure()` → per-frame analysis via the native frame
|
|
20
|
-
* processor plugin → `reset()`? → `close()`. See
|
|
21
|
-
* `usesmileid_platform_interface` `FaceAnalyzer` for the canonical
|
|
22
|
-
* contract.
|
|
23
|
-
*/
|
|
24
|
-
/**
|
|
25
|
-
* Must match the Kotlin Expo Module's `Name("UseSmileIDHuaweiFace")` and its
|
|
26
|
-
* VisionCamera frame-processor-plugin registration — drift is a runtime
|
|
27
|
-
* "module/plugin not found" error, not a compile error.
|
|
28
|
-
*/
|
|
29
|
-
export const UseSmileIDHuaweiFaceIdentifiers = {
|
|
30
|
-
nativeModuleName: "UseSmileIDHuaweiFace",
|
|
31
|
-
frameProcessorPluginName: "UseSmileIDHuaweiFaceFrameProcessor",
|
|
32
|
-
} as const;
|
|
33
|
-
|
|
34
|
-
export const useSmileIDHuaweiFace = useSmileIDAnalyzerExecutor({
|
|
35
|
-
nativeModuleName: UseSmileIDHuaweiFaceIdentifiers.nativeModuleName,
|
|
36
|
-
frameProcessorPluginName:
|
|
37
|
-
UseSmileIDHuaweiFaceIdentifiers.frameProcessorPluginName,
|
|
38
|
-
kind: "face",
|
|
39
|
-
key: AnalyzerFactoryKey.HuaweiFace,
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
export type { FaceConfig, FaceScanResult };
|