react-native-nitro-pose-exercises 1.0.5 → 1.0.7
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/android/build.gradle
CHANGED
|
@@ -116,6 +116,12 @@ dependencies {
|
|
|
116
116
|
implementation "com.facebook.react:react-android"
|
|
117
117
|
implementation project(":react-native-nitro-modules")
|
|
118
118
|
|
|
119
|
+
// MediaPipe — bundled with your library
|
|
119
120
|
implementation 'com.google.mediapipe:tasks-vision:0.10.21'
|
|
120
|
-
|
|
121
|
+
|
|
122
|
+
// CameraX for ImageProxy access
|
|
123
|
+
implementation 'androidx.camera:camera-core:1.4.1'
|
|
124
|
+
|
|
125
|
+
// VisionCamera — provided by the consuming app
|
|
126
|
+
compileOnly project(':react-native-vision-camera')
|
|
121
127
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
package com.margelo.nitro.nitroposeexercises
|
|
2
2
|
|
|
3
3
|
import android.graphics.Bitmap
|
|
4
|
-
import android.graphics.Matrix
|
|
5
4
|
import androidx.annotation.Keep
|
|
6
5
|
import com.facebook.proguard.annotations.DoNotStrip
|
|
7
6
|
import com.google.mediapipe.framework.image.BitmapImageBuilder
|
|
@@ -12,7 +11,6 @@ import com.google.mediapipe.tasks.vision.poselandmarker.PoseLandmarkerOptions
|
|
|
12
11
|
import com.margelo.nitro.NitroModules
|
|
13
12
|
import com.margelo.nitro.core.Promise
|
|
14
13
|
import com.margelo.nitro.camera.HybridFrameSpec
|
|
15
|
-
import com.margelo.nitro.camera.NativeFrame
|
|
16
14
|
import kotlin.math.acos
|
|
17
15
|
import kotlin.math.max
|
|
18
16
|
import kotlin.math.min
|
|
@@ -156,59 +154,66 @@ class HybridPoseExercise : HybridNitroPoseExercisesSpec() {
|
|
|
156
154
|
// Frame Processing
|
|
157
155
|
// ═══════════════════════════════════════════════════════════
|
|
158
156
|
|
|
159
|
-
|
|
157
|
+
override fun processFrame(frame: HybridFrameSpec) {
|
|
160
158
|
if (_status != SessionStatus.ACTIVE && _status != SessionStatus.COUNTDOWN) return
|
|
161
159
|
if (!isInitialized || poseLandmarker == null) return
|
|
162
160
|
|
|
163
|
-
// Cast to NativeFrame to get the ImageProxy
|
|
164
|
-
val nativeFrame = frame as? NativeFrame ?: return
|
|
165
|
-
val imageProxy = nativeFrame.image ?: return
|
|
166
|
-
|
|
167
161
|
frameCount++
|
|
168
162
|
if (frameCount % processEveryNFrames != 0) return
|
|
169
163
|
|
|
170
164
|
try {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
val
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
165
|
+
val nativeBuffer = frame.getNativeBuffer()
|
|
166
|
+
val width = frame.getWidth().toInt()
|
|
167
|
+
val height = frame.getHeight().toInt()
|
|
168
|
+
|
|
169
|
+
// On Android, nativeBuffer.pointer is an AHardwareBuffer*
|
|
170
|
+
// Convert to Bitmap via Android's HardwareBuffer API
|
|
171
|
+
val hardwareBuffer = android.hardware.HardwareBuffer.wrap(nativeBuffer.pointer)
|
|
172
|
+
val bitmap = Bitmap.wrapHardwareBuffer(hardwareBuffer, null)
|
|
173
|
+
?: throw Exception("Failed to wrap HardwareBuffer to Bitmap")
|
|
174
|
+
|
|
175
|
+
// MediaPipe needs ARGB_8888, not hardware bitmap
|
|
176
|
+
val softBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false)
|
|
177
|
+
|
|
178
|
+
val mpImage = com.google.mediapipe.framework.image.BitmapImageBuilder(softBitmap).build()
|
|
179
|
+
val result = poseLandmarker!!.detect(mpImage)
|
|
180
|
+
|
|
181
|
+
if (result.landmarks().isNotEmpty()) {
|
|
182
|
+
val poseLandmarks = result.landmarks()[0]
|
|
183
|
+
|
|
184
|
+
if (poseWasLost) {
|
|
185
|
+
poseWasLost = false
|
|
186
|
+
onPoseRegained?.invoke()
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
_landmarks = poseLandmarks.map { lm ->
|
|
190
|
+
Landmark(
|
|
191
|
+
x = lm.x().toDouble(),
|
|
192
|
+
y = lm.y().toDouble(),
|
|
193
|
+
z = lm.z().toDouble(),
|
|
194
|
+
visibility = (lm.visibility().orElse(0f)).toDouble()
|
|
195
|
+
)
|
|
196
|
+
}.toTypedArray()
|
|
197
|
+
|
|
198
|
+
if (_status == SessionStatus.ACTIVE) {
|
|
199
|
+
processExerciseLogic()
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
if (!poseWasLost) {
|
|
203
|
+
poseWasLost = true
|
|
204
|
+
onPoseLost?.invoke()
|
|
205
|
+
}
|
|
206
|
+
_landmarks = emptyArray()
|
|
196
207
|
}
|
|
197
208
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
onPoseLost?.invoke()
|
|
202
|
-
}
|
|
203
|
-
_landmarks = emptyArray()
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
bitmap.recycle()
|
|
209
|
+
softBitmap.recycle()
|
|
210
|
+
bitmap.recycle()
|
|
211
|
+
nativeBuffer.release()
|
|
207
212
|
|
|
208
213
|
} catch (e: Exception) {
|
|
209
|
-
|
|
214
|
+
// MediaPipe detection failed — skip this frame
|
|
210
215
|
}
|
|
211
|
-
|
|
216
|
+
}
|
|
212
217
|
|
|
213
218
|
// ═══════════════════════════════════════════════════════════
|
|
214
219
|
// ImageProxy to Bitmap conversion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-nitro-pose-exercises",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "Real-time on-device exercise tracking for React Native. Rep counting, form validation, and skeleton overlay powered by MediaPipe Pose Landmarker and VisionCamera v5 via Nitro Modules.",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|