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.
@@ -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
- implementation project(':react-native-vision-camera')
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
- override fun processFrame(frame: HybridFrameSpec) {
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
- // Convert ImageProxy to Bitmap
172
- val bitmap = imageProxyToBitmap(imageProxy)
173
- val mpImage = BitmapImageBuilder(bitmap).build()
174
-
175
- val result = poseLandmarker!!.detect(mpImage)
176
-
177
- if (result.landmarks().isNotEmpty()) {
178
- val poseLandmarks = result.landmarks()[0]
179
-
180
- if (poseWasLost) {
181
- poseWasLost = false
182
- onPoseRegained?.invoke()
183
- }
184
-
185
- _landmarks = poseLandmarks.map { lm ->
186
- Landmark(
187
- x = lm.x().toDouble(),
188
- y = lm.y().toDouble(),
189
- z = lm.z().toDouble(),
190
- visibility = (lm.visibility().orElse(0f)).toDouble()
191
- )
192
- }.toTypedArray()
193
-
194
- if (_status == SessionStatus.ACTIVE) {
195
- processExerciseLogic()
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
- } else {
199
- if (!poseWasLost) {
200
- poseWasLost = true
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
- // MediaPipe detection failed — skip this frame
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.5",
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",