react-native-nitro-pose-exercises 1.1.5 → 1.1.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/README.md +192 -159
- package/android/src/main/java/com/margelo/nitro/nitroposeexercises/NitroPoseExercises.kt +121 -4
- package/ios/NitroPoseExercises.swift +128 -11
- package/lib/module/NitroPoseExercises.nitro.js.map +1 -1
- package/lib/module/config/bicep-curl.js +3 -0
- package/lib/module/config/bicep-curl.js.map +1 -1
- package/lib/module/config/boat-pose.js +44 -0
- package/lib/module/config/boat-pose.js.map +1 -0
- package/lib/module/config/bow-pose.js +37 -0
- package/lib/module/config/bow-pose.js.map +1 -0
- package/lib/module/config/bridge-pose.js +37 -0
- package/lib/module/config/bridge-pose.js.map +1 -0
- package/lib/module/config/calf-raise.js +47 -0
- package/lib/module/config/calf-raise.js.map +1 -0
- package/lib/module/config/camel-pose.js +37 -0
- package/lib/module/config/camel-pose.js.map +1 -0
- package/lib/module/config/chair-pose.js +3 -0
- package/lib/module/config/chair-pose.js.map +1 -1
- package/lib/module/config/childs-pose.js +42 -0
- package/lib/module/config/childs-pose.js.map +1 -0
- package/lib/module/config/cobra-pose.js +3 -0
- package/lib/module/config/cobra-pose.js.map +1 -1
- package/lib/module/config/cobra-wings.js +47 -0
- package/lib/module/config/cobra-wings.js.map +1 -0
- package/lib/module/config/dead-lift.js +54 -0
- package/lib/module/config/dead-lift.js.map +1 -0
- package/lib/module/config/downward-dog.js +3 -0
- package/lib/module/config/downward-dog.js.map +1 -1
- package/lib/module/config/extended-side-angle.js +49 -0
- package/lib/module/config/extended-side-angle.js.map +1 -0
- package/lib/module/config/fish-pose.js +37 -0
- package/lib/module/config/fish-pose.js.map +1 -0
- package/lib/module/config/front-raise.js +54 -0
- package/lib/module/config/front-raise.js.map +1 -0
- package/lib/module/config/glute-bridge.js +49 -0
- package/lib/module/config/glute-bridge.js.map +1 -0
- package/lib/module/config/hip-abduction.js +42 -0
- package/lib/module/config/hip-abduction.js.map +1 -0
- package/lib/module/config/knee-raise.js +47 -0
- package/lib/module/config/knee-raise.js.map +1 -0
- package/lib/module/config/lateral-raise.js +54 -0
- package/lib/module/config/lateral-raise.js.map +1 -0
- package/lib/module/config/leg-raise.js +49 -0
- package/lib/module/config/leg-raise.js.map +1 -0
- package/lib/module/config/lunge.js +3 -0
- package/lib/module/config/lunge.js.map +1 -1
- package/lib/module/config/mountain-pose.js +49 -0
- package/lib/module/config/mountain-pose.js.map +1 -0
- package/lib/module/config/overarm-reach.js +47 -0
- package/lib/module/config/overarm-reach.js.map +1 -0
- package/lib/module/config/plank.js +3 -0
- package/lib/module/config/plank.js.map +1 -1
- package/lib/module/config/pull-up.js +47 -0
- package/lib/module/config/pull-up.js.map +1 -0
- package/lib/module/config/pushup.js +4 -0
- package/lib/module/config/pushup.js.map +1 -1
- package/lib/module/config/reverse-warrior.js +49 -0
- package/lib/module/config/reverse-warrior.js.map +1 -0
- package/lib/module/config/shoulder-press.js +3 -0
- package/lib/module/config/shoulder-press.js.map +1 -1
- package/lib/module/config/side-lung.js +54 -0
- package/lib/module/config/side-lung.js.map +1 -0
- package/lib/module/config/side-plank.js +37 -0
- package/lib/module/config/side-plank.js.map +1 -0
- package/lib/module/config/situp.js +3 -0
- package/lib/module/config/situp.js.map +1 -1
- package/lib/module/config/squat.js +3 -0
- package/lib/module/config/squat.js.map +1 -1
- package/lib/module/config/sumo-squat.js +54 -0
- package/lib/module/config/sumo-squat.js.map +1 -0
- package/lib/module/config/tree-pose.js +3 -0
- package/lib/module/config/tree-pose.js.map +1 -1
- package/lib/module/config/triangle-pose.js +49 -0
- package/lib/module/config/triangle-pose.js.map +1 -0
- package/lib/module/config/tricep-dip.js +4 -0
- package/lib/module/config/tricep-dip.js.map +1 -1
- package/lib/module/config/v-up.js +42 -0
- package/lib/module/config/v-up.js.map +1 -0
- package/lib/module/config/wall-sit.js +3 -0
- package/lib/module/config/wall-sit.js.map +1 -1
- package/lib/module/config/warrior-i.js +3 -0
- package/lib/module/config/warrior-i.js.map +1 -1
- package/lib/module/config/warrior-ii.js +3 -0
- package/lib/module/config/warrior-ii.js.map +1 -1
- package/lib/module/config/warrior-iii.js +61 -0
- package/lib/module/config/warrior-iii.js.map +1 -0
- package/lib/module/index.js +31 -3
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/NitroPoseExercises.nitro.d.ts +9 -1
- package/lib/typescript/src/NitroPoseExercises.nitro.d.ts.map +1 -1
- package/lib/typescript/src/config/bicep-curl.d.ts.map +1 -1
- package/lib/typescript/src/config/boat-pose.d.ts +3 -0
- package/lib/typescript/src/config/boat-pose.d.ts.map +1 -0
- package/lib/typescript/src/config/bow-pose.d.ts +3 -0
- package/lib/typescript/src/config/bow-pose.d.ts.map +1 -0
- package/lib/typescript/src/config/bridge-pose.d.ts +3 -0
- package/lib/typescript/src/config/bridge-pose.d.ts.map +1 -0
- package/lib/typescript/src/config/calf-raise.d.ts +3 -0
- package/lib/typescript/src/config/calf-raise.d.ts.map +1 -0
- package/lib/typescript/src/config/camel-pose.d.ts +3 -0
- package/lib/typescript/src/config/camel-pose.d.ts.map +1 -0
- package/lib/typescript/src/config/chair-pose.d.ts.map +1 -1
- package/lib/typescript/src/config/childs-pose.d.ts +3 -0
- package/lib/typescript/src/config/childs-pose.d.ts.map +1 -0
- package/lib/typescript/src/config/cobra-pose.d.ts.map +1 -1
- package/lib/typescript/src/config/cobra-wings.d.ts +3 -0
- package/lib/typescript/src/config/cobra-wings.d.ts.map +1 -0
- package/lib/typescript/src/config/dead-lift.d.ts +3 -0
- package/lib/typescript/src/config/dead-lift.d.ts.map +1 -0
- package/lib/typescript/src/config/downward-dog.d.ts.map +1 -1
- package/lib/typescript/src/config/extended-side-angle.d.ts +3 -0
- package/lib/typescript/src/config/extended-side-angle.d.ts.map +1 -0
- package/lib/typescript/src/config/fish-pose.d.ts +3 -0
- package/lib/typescript/src/config/fish-pose.d.ts.map +1 -0
- package/lib/typescript/src/config/front-raise.d.ts +3 -0
- package/lib/typescript/src/config/front-raise.d.ts.map +1 -0
- package/lib/typescript/src/config/glute-bridge.d.ts +3 -0
- package/lib/typescript/src/config/glute-bridge.d.ts.map +1 -0
- package/lib/typescript/src/config/hip-abduction.d.ts +3 -0
- package/lib/typescript/src/config/hip-abduction.d.ts.map +1 -0
- package/lib/typescript/src/config/knee-raise.d.ts +3 -0
- package/lib/typescript/src/config/knee-raise.d.ts.map +1 -0
- package/lib/typescript/src/config/lateral-raise.d.ts +3 -0
- package/lib/typescript/src/config/lateral-raise.d.ts.map +1 -0
- package/lib/typescript/src/config/leg-raise.d.ts +3 -0
- package/lib/typescript/src/config/leg-raise.d.ts.map +1 -0
- package/lib/typescript/src/config/lunge.d.ts.map +1 -1
- package/lib/typescript/src/config/mountain-pose.d.ts +3 -0
- package/lib/typescript/src/config/mountain-pose.d.ts.map +1 -0
- package/lib/typescript/src/config/overarm-reach.d.ts +3 -0
- package/lib/typescript/src/config/overarm-reach.d.ts.map +1 -0
- package/lib/typescript/src/config/plank.d.ts.map +1 -1
- package/lib/typescript/src/config/pull-up.d.ts +3 -0
- package/lib/typescript/src/config/pull-up.d.ts.map +1 -0
- package/lib/typescript/src/config/pushup.d.ts.map +1 -1
- package/lib/typescript/src/config/reverse-warrior.d.ts +3 -0
- package/lib/typescript/src/config/reverse-warrior.d.ts.map +1 -0
- package/lib/typescript/src/config/shoulder-press.d.ts.map +1 -1
- package/lib/typescript/src/config/side-lung.d.ts +3 -0
- package/lib/typescript/src/config/side-lung.d.ts.map +1 -0
- package/lib/typescript/src/config/side-plank.d.ts +3 -0
- package/lib/typescript/src/config/side-plank.d.ts.map +1 -0
- package/lib/typescript/src/config/situp.d.ts.map +1 -1
- package/lib/typescript/src/config/squat.d.ts.map +1 -1
- package/lib/typescript/src/config/sumo-squat.d.ts +3 -0
- package/lib/typescript/src/config/sumo-squat.d.ts.map +1 -0
- package/lib/typescript/src/config/tree-pose.d.ts.map +1 -1
- package/lib/typescript/src/config/triangle-pose.d.ts +3 -0
- package/lib/typescript/src/config/triangle-pose.d.ts.map +1 -0
- package/lib/typescript/src/config/tricep-dip.d.ts.map +1 -1
- package/lib/typescript/src/config/v-up.d.ts +3 -0
- package/lib/typescript/src/config/v-up.d.ts.map +1 -0
- package/lib/typescript/src/config/wall-sit.d.ts.map +1 -1
- package/lib/typescript/src/config/warrior-i.d.ts.map +1 -1
- package/lib/typescript/src/config/warrior-ii.d.ts.map +1 -1
- package/lib/typescript/src/config/warrior-iii.d.ts +3 -0
- package/lib/typescript/src/config/warrior-iii.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +26 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JCameraAngleType.hpp +58 -0
- package/nitrogen/generated/android/c++/JExerciseConfig.hpp +19 -3
- package/nitrogen/generated/android/c++/JHybridNitroPoseExercisesSpec.cpp +47 -0
- package/nitrogen/generated/android/c++/JHybridNitroPoseExercisesSpec.hpp +5 -0
- package/nitrogen/generated/android/c++/JPostureFamily.hpp +73 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroposeexercises/CameraAngleType.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroposeexercises/ExerciseConfig.kt +19 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroposeexercises/HybridNitroPoseExercisesSpec.kt +32 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroposeexercises/PostureFamily.kt +28 -0
- package/nitrogen/generated/ios/NitroPoseExercises-Swift-Cxx-Bridge.hpp +9 -0
- package/nitrogen/generated/ios/NitroPoseExercises-Swift-Cxx-Umbrella.hpp +6 -0
- package/nitrogen/generated/ios/c++/HybridNitroPoseExercisesSpecSwift.hpp +28 -0
- package/nitrogen/generated/ios/swift/CameraAngleType.swift +40 -0
- package/nitrogen/generated/ios/swift/ExerciseConfig.swift +17 -2
- package/nitrogen/generated/ios/swift/HybridNitroPoseExercisesSpec.swift +3 -0
- package/nitrogen/generated/ios/swift/HybridNitroPoseExercisesSpec_cxx.swift +76 -0
- package/nitrogen/generated/ios/swift/PostureFamily.swift +60 -0
- package/nitrogen/generated/shared/c++/CameraAngleType.hpp +76 -0
- package/nitrogen/generated/shared/c++/ExerciseConfig.hpp +20 -2
- package/nitrogen/generated/shared/c++/HybridNitroPoseExercisesSpec.cpp +5 -0
- package/nitrogen/generated/shared/c++/HybridNitroPoseExercisesSpec.hpp +5 -0
- package/nitrogen/generated/shared/c++/PostureFamily.hpp +96 -0
- package/package.json +1 -1
- package/src/NitroPoseExercises.nitro.ts +19 -0
- package/src/config/bicep-curl.ts +3 -0
- package/src/config/boat-pose.ts +36 -0
- package/src/config/bow-pose.ts +27 -0
- package/src/config/bridge-pose.ts +28 -0
- package/src/config/calf-raise.ts +30 -0
- package/src/config/camel-pose.ts +27 -0
- package/src/config/chair-pose.ts +3 -0
- package/src/config/childs-pose.ts +26 -0
- package/src/config/cobra-pose.ts +3 -0
- package/src/config/cobra-wings.ts +29 -0
- package/src/config/dead-lift.ts +38 -0
- package/src/config/downward-dog.ts +3 -0
- package/src/config/extended-side-angle.ts +36 -0
- package/src/config/fish-pose.ts +28 -0
- package/src/config/front-raise.ts +58 -0
- package/src/config/glute-bridge.ts +36 -0
- package/src/config/hip-abduction.ts +28 -0
- package/src/config/knee-raise.ts +29 -0
- package/src/config/lateral-raise.ts +58 -0
- package/src/config/leg-raise.ts +36 -0
- package/src/config/lunge.ts +3 -0
- package/src/config/mountain-pose.ts +36 -0
- package/src/config/overarm-reach.ts +49 -0
- package/src/config/plank.ts +3 -0
- package/src/config/pull-up.ts +30 -0
- package/src/config/pushup.ts +3 -0
- package/src/config/reverse-warrior.ts +36 -0
- package/src/config/shoulder-press.ts +3 -0
- package/src/config/side-lung.ts +37 -0
- package/src/config/side-plank.ts +28 -0
- package/src/config/situp.ts +3 -0
- package/src/config/squat.ts +3 -0
- package/src/config/sumo-squat.ts +38 -0
- package/src/config/tree-pose.ts +3 -0
- package/src/config/triangle-pose.ts +37 -0
- package/src/config/tricep-dip.ts +3 -0
- package/src/config/v-up.ts +29 -0
- package/src/config/wall-sit.ts +3 -0
- package/src/config/warrior-i.ts +3 -0
- package/src/config/warrior-ii.ts +3 -0
- package/src/config/warrior-iii.ts +45 -0
- package/src/index.tsx +31 -3
package/README.md
CHANGED
|
@@ -8,12 +8,15 @@
|
|
|
8
8
|
|
|
9
9
|
A **React Native Nitro Module** for real-time, on-device exercise tracking using pose estimation. Uses **OS-native pose detection** — Apple Vision on iOS and Google ML Kit on Android — with **VisionCamera v5**.
|
|
10
10
|
|
|
11
|
-
* 🏋️ **
|
|
11
|
+
* 🏋️ **38 Built-In Exercises** — Push-ups, squats, deadlifts, yoga poses, and more
|
|
12
|
+
* 🔄 **Rep Counting** — Automatic rep detection with configurable state machines
|
|
12
13
|
* 🧘 **Hold Tracking** — Duration and stability tracking for planks, yoga poses, and isometric holds
|
|
13
|
-
* 📐 **Form Validation** — Real-time form feedback with
|
|
14
|
-
*
|
|
14
|
+
* 📐 **Form Validation** — Real-time form feedback with angle-based rules
|
|
15
|
+
* 🚦 **Posture Gating** — Refuses to count reps unless the user is in valid posture; "Get in position" feedback before sessions start
|
|
16
|
+
* 💀 **Skeleton Overlay** — Skia-powered skeleton with glow effects and live angle badges
|
|
15
17
|
* ⚡ **Fully Native** — OS-level pose detection via Nitro Modules, zero JS bridge overhead
|
|
16
18
|
* 📦 **Zero Model Bundling** — No ML model files to download or ship with your app
|
|
19
|
+
* 🪶 **~200 KB** — Virtually zero app size impact
|
|
17
20
|
|
|
18
21
|
---
|
|
19
22
|
|
|
@@ -56,15 +59,15 @@ cd ios && pod install
|
|
|
56
59
|
|
|
57
60
|
<table>
|
|
58
61
|
<tr>
|
|
59
|
-
<th align="center"
|
|
60
|
-
<th align="center"
|
|
62
|
+
<th align="center">📸 Normal Mode</th>
|
|
63
|
+
<th align="center">💀 Skeleton + Angle Overlay</th>
|
|
61
64
|
</tr>
|
|
62
65
|
<tr>
|
|
63
66
|
<td align="center">
|
|
64
|
-
|
|
67
|
+
<img alt="normal-mode" src="./docs/img/normal.png" height="650" width="300"/>
|
|
65
68
|
</td>
|
|
66
69
|
<td align="center">
|
|
67
|
-
|
|
70
|
+
<img alt="skeleton-mode" src="./docs/img/skeleton.png" height="650" width="300"/>
|
|
68
71
|
</td>
|
|
69
72
|
</tr>
|
|
70
73
|
</table>
|
|
@@ -75,12 +78,13 @@ cd ios && pod install
|
|
|
75
78
|
|
|
76
79
|
| Feature | Description |
|
|
77
80
|
| --- | --- |
|
|
78
|
-
| **Rep-Based Exercises** | Cyclic state machine (UP → DOWN → UP = 1 rep). Push-ups, squats, curls. |
|
|
79
|
-
| **Hold-Based Exercises** | Single target pose with duration tracking. Planks, wall sits, yoga poses. |
|
|
80
|
-
| **
|
|
81
|
-
| **Form Feedback** | Angle-based rules with throttled real-time callbacks. |
|
|
82
|
-
| **Skeleton Overlay** |
|
|
81
|
+
| **Rep-Based Exercises** | Cyclic state machine (UP → DOWN → UP = 1 rep). Push-ups, squats, curls, and more. |
|
|
82
|
+
| **Hold-Based Exercises** | Single target pose with duration + stability tracking. Planks, wall sits, yoga poses. |
|
|
83
|
+
| **Posture Gate** | Family-based posture validation. Refuses to start or count reps until user is in correct position (e.g. horizontal for pushups, upright for squats). |
|
|
84
|
+
| **Form Feedback** | Angle-based rules with throttled real-time callbacks. Bad form blocks rep counting. |
|
|
85
|
+
| **Skeleton Overlay** | Glow-effect bones, color-coded joints, and live angle badges drawn over camera via Skia. |
|
|
83
86
|
| **Bilateral Tracking** | Left and right side angles tracked independently. |
|
|
87
|
+
| **Fatigue Guard** | Minimum 800ms per rep prevents false counts. Form score gate rejects bad reps. |
|
|
84
88
|
|
|
85
89
|
---
|
|
86
90
|
|
|
@@ -90,8 +94,8 @@ cd ios && pod install
|
|
|
90
94
|
|
|
91
95
|
Unlike MediaPipe-based solutions, this library uses OS-native APIs. There is **no model file to download or bundle**.
|
|
92
96
|
|
|
93
|
-
* **iOS:** Apple Vision is a system framework —
|
|
94
|
-
* **Android:** ML Kit manages its own model via Google Play Services —
|
|
97
|
+
* **iOS:** Apple Vision is a system framework — already on every iPhone running iOS 14+.
|
|
98
|
+
* **Android:** ML Kit manages its own model via Google Play Services — downloads and updates automatically.
|
|
95
99
|
|
|
96
100
|
### Permissions
|
|
97
101
|
|
|
@@ -124,18 +128,14 @@ module.exports = {
|
|
|
124
128
|
|
|
125
129
|
### Podspec (for library authors)
|
|
126
130
|
|
|
127
|
-
The iOS podspec needs the Vision and AVFoundation system frameworks:
|
|
128
|
-
|
|
129
131
|
```ruby
|
|
130
|
-
|
|
132
|
+
s.frameworks = ['Vision', 'AVFoundation']
|
|
131
133
|
```
|
|
132
134
|
|
|
133
135
|
No CocoaPods dependencies required — Vision is built into iOS.
|
|
134
136
|
|
|
135
137
|
### Android Gradle (for library authors)
|
|
136
138
|
|
|
137
|
-
Add ML Kit Pose Detection to `android/build.gradle`:
|
|
138
|
-
|
|
139
139
|
```groovy
|
|
140
140
|
dependencies {
|
|
141
141
|
implementation 'com.google.mlkit:pose-detection:18.0.0-beta5'
|
|
@@ -149,8 +149,8 @@ dependencies {
|
|
|
149
149
|
### Basic — Normal Camera (No Skeleton)
|
|
150
150
|
|
|
151
151
|
```tsx
|
|
152
|
-
import { useEffect,
|
|
153
|
-
import { StyleSheet, View, Text
|
|
152
|
+
import { useEffect, useState } from 'react';
|
|
153
|
+
import { StyleSheet, View, Text } from 'react-native';
|
|
154
154
|
import {
|
|
155
155
|
Camera,
|
|
156
156
|
useCameraDevice,
|
|
@@ -162,7 +162,6 @@ import {
|
|
|
162
162
|
nitroPoseExercises,
|
|
163
163
|
PUSHUP_CONFIG,
|
|
164
164
|
type RepData,
|
|
165
|
-
type FormFeedback,
|
|
166
165
|
type SessionResult,
|
|
167
166
|
} from 'react-native-nitro-pose-exercises';
|
|
168
167
|
|
|
@@ -176,7 +175,6 @@ export default function App() {
|
|
|
176
175
|
if (!hasPermission) requestPermission();
|
|
177
176
|
}, [hasPermission]);
|
|
178
177
|
|
|
179
|
-
// Initialize pose engine — modelPath is ignored (OS-native, no model file)
|
|
180
178
|
useEffect(() => {
|
|
181
179
|
async function init() {
|
|
182
180
|
await nitroPoseExercises.initialize('');
|
|
@@ -184,30 +182,19 @@ export default function App() {
|
|
|
184
182
|
|
|
185
183
|
nitroPoseExercises.onRepComplete = (data: RepData) => {
|
|
186
184
|
setRepCount(data.repNumber);
|
|
187
|
-
console.log(`Rep ${data.repNumber} — form: ${data.formScore}`);
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
nitroPoseExercises.onFormFeedback = (feedback: FormFeedback) => {
|
|
191
|
-
console.log(`Form: ${feedback.message}`);
|
|
192
185
|
};
|
|
193
186
|
|
|
194
187
|
nitroPoseExercises.onSessionComplete = (result: SessionResult) => {
|
|
195
|
-
console.log(
|
|
196
|
-
`Done! ${result.totalReps} reps, avg form: ${result.averageFormScore}`
|
|
197
|
-
);
|
|
188
|
+
console.log(`Done! ${result.totalReps} reps, form: ${result.averageFormScore}`);
|
|
198
189
|
};
|
|
199
190
|
|
|
200
|
-
// Start: 10 target reps, 3 second countdown
|
|
201
191
|
nitroPoseExercises.startSession(10, 3);
|
|
202
192
|
}
|
|
203
193
|
|
|
204
194
|
init();
|
|
205
|
-
return () => {
|
|
206
|
-
nitroPoseExercises.release();
|
|
207
|
-
};
|
|
195
|
+
return () => { nitroPoseExercises.release(); };
|
|
208
196
|
}, []);
|
|
209
197
|
|
|
210
|
-
// Frame processor
|
|
211
198
|
const frameOutput = useFrameOutput({
|
|
212
199
|
pixelFormat: 'rgb',
|
|
213
200
|
onFrame(frame) {
|
|
@@ -251,75 +238,6 @@ const styles = StyleSheet.create({
|
|
|
251
238
|
});
|
|
252
239
|
```
|
|
253
240
|
|
|
254
|
-
### Skeleton Overlay — SkiaCamera
|
|
255
|
-
|
|
256
|
-
```tsx
|
|
257
|
-
import { SkiaCamera } from 'react-native-vision-camera-skia'
|
|
258
|
-
import { Skia } from '@shopify/react-native-skia'
|
|
259
|
-
import { nitroPoseExercises } from 'react-native-nitro-pose-exercises'
|
|
260
|
-
|
|
261
|
-
const SKELETON_CONNECTIONS: [number, number][] = [
|
|
262
|
-
[11, 12], [11, 23], [12, 24], [23, 24], // Torso
|
|
263
|
-
[11, 13], [13, 15], // Left arm
|
|
264
|
-
[12, 14], [14, 16], // Right arm
|
|
265
|
-
[23, 25], [25, 27], // Left leg
|
|
266
|
-
[24, 26], [26, 28], // Right leg
|
|
267
|
-
]
|
|
268
|
-
|
|
269
|
-
<SkiaCamera
|
|
270
|
-
style={StyleSheet.absoluteFill}
|
|
271
|
-
isActive={true}
|
|
272
|
-
device="back"
|
|
273
|
-
pixelFormat="rgb"
|
|
274
|
-
onFrame={(frame, render) => {
|
|
275
|
-
'worklet'
|
|
276
|
-
try {
|
|
277
|
-
nitroPoseExercises.processFrame(frame)
|
|
278
|
-
const landmarks = nitroPoseExercises.landmarks
|
|
279
|
-
|
|
280
|
-
render(({ frameTexture, canvas }) => {
|
|
281
|
-
canvas.drawImage(frameTexture, 0, 0)
|
|
282
|
-
|
|
283
|
-
if (landmarks && landmarks.length > 0) {
|
|
284
|
-
const w = frame.width
|
|
285
|
-
const h = frame.height
|
|
286
|
-
|
|
287
|
-
// Draw bones
|
|
288
|
-
const linePaint = Skia.Paint()
|
|
289
|
-
linePaint.setColor(Skia.Color('#00FF00'))
|
|
290
|
-
linePaint.setStrokeWidth(4)
|
|
291
|
-
linePaint.setStyle(1)
|
|
292
|
-
|
|
293
|
-
for (const [i, j] of SKELETON_CONNECTIONS) {
|
|
294
|
-
if (i < landmarks.length && j < landmarks.length) {
|
|
295
|
-
const a = landmarks[i]
|
|
296
|
-
const b = landmarks[j]
|
|
297
|
-
if (a.visibility > 0.5 && b.visibility > 0.5) {
|
|
298
|
-
canvas.drawLine(a.x * w, a.y * h, b.x * w, b.y * h, linePaint)
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// Draw joints
|
|
304
|
-
const jointPaint = Skia.Paint()
|
|
305
|
-
jointPaint.setColor(Skia.Color('#00FFFF'))
|
|
306
|
-
jointPaint.setStyle(0)
|
|
307
|
-
|
|
308
|
-
for (let idx = 0; idx < landmarks.length; idx++) {
|
|
309
|
-
const lm = landmarks[idx]
|
|
310
|
-
if (lm.visibility > 0.5) {
|
|
311
|
-
canvas.drawCircle(lm.x * w, lm.y * h, 6, jointPaint)
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
})
|
|
316
|
-
} finally {
|
|
317
|
-
frame.dispose()
|
|
318
|
-
}
|
|
319
|
-
}}
|
|
320
|
-
/>
|
|
321
|
-
```
|
|
322
|
-
|
|
323
241
|
---
|
|
324
242
|
|
|
325
243
|
## 🧩 API Reference
|
|
@@ -337,7 +255,6 @@ release(): void
|
|
|
337
255
|
### Exercise Setup
|
|
338
256
|
|
|
339
257
|
```ts
|
|
340
|
-
// Load an exercise config (built-in or custom)
|
|
341
258
|
loadExercise(config: ExerciseConfig): void
|
|
342
259
|
```
|
|
343
260
|
|
|
@@ -348,12 +265,15 @@ startSession(targetReps: number, countdownSeconds: number): void
|
|
|
348
265
|
pauseSession(): void
|
|
349
266
|
resumeSession(): void
|
|
350
267
|
stopSession(): void
|
|
268
|
+
// Returns true if the user is currently in valid posture for the loaded exercise.
|
|
269
|
+
// Poll this before starting a session, e.g. show "Get in position" until ready.
|
|
270
|
+
isReady(): boolean
|
|
351
271
|
```
|
|
352
272
|
|
|
353
273
|
### Frame Processing
|
|
354
274
|
|
|
355
275
|
```ts
|
|
356
|
-
//
|
|
276
|
+
// Call from VisionCamera frame processor worklet
|
|
357
277
|
processFrame(frame: Frame): void
|
|
358
278
|
```
|
|
359
279
|
|
|
@@ -363,7 +283,7 @@ processFrame(frame: Frame): void
|
|
|
363
283
|
readonly status: SessionStatus // 'idle' | 'countdown' | 'active' | 'paused' | 'completed'
|
|
364
284
|
readonly currentPhase: ExercisePhase // 'up' | 'down' | 'hold' | 'transition' | 'unknown'
|
|
365
285
|
readonly repCount: number
|
|
366
|
-
readonly landmarks: Landmark[] // Body landmarks
|
|
286
|
+
readonly landmarks: Landmark[] // Body landmarks mapped to MediaPipe indices
|
|
367
287
|
```
|
|
368
288
|
|
|
369
289
|
### Callbacks
|
|
@@ -375,21 +295,21 @@ onFormFeedback: ((feedback: FormFeedback) => void) | undefined
|
|
|
375
295
|
onHoldProgress: ((progress: HoldProgress) => void) | undefined
|
|
376
296
|
onPoseLost: (() => void) | undefined
|
|
377
297
|
onPoseRegained: (() => void) | undefined
|
|
298
|
+
onPostureLost: (() => void) | undefined
|
|
299
|
+
onPostureRegained: (() => void) | undefined
|
|
378
300
|
onSessionComplete: ((result: SessionResult) => void) | undefined
|
|
379
301
|
```
|
|
380
302
|
|
|
381
|
-
---
|
|
382
|
-
|
|
383
303
|
### Callback Payloads
|
|
384
304
|
|
|
385
305
|
#### RepData
|
|
386
306
|
|
|
387
307
|
```ts
|
|
388
308
|
{
|
|
389
|
-
repNumber: number
|
|
390
|
-
durationMs: number
|
|
391
|
-
formScore: number
|
|
392
|
-
angles: AngleSnapshot[]
|
|
309
|
+
repNumber: number
|
|
310
|
+
durationMs: number
|
|
311
|
+
formScore: number // 0-100
|
|
312
|
+
angles: AngleSnapshot[] // all tracked angles at rep completion
|
|
393
313
|
}
|
|
394
314
|
```
|
|
395
315
|
|
|
@@ -397,9 +317,9 @@ onSessionComplete: ((result: SessionResult) => void) | undefined
|
|
|
397
317
|
|
|
398
318
|
```ts
|
|
399
319
|
{
|
|
400
|
-
ruleName: string
|
|
401
|
-
message: string
|
|
402
|
-
severity: FormSeverity
|
|
320
|
+
ruleName: string
|
|
321
|
+
message: string
|
|
322
|
+
severity: FormSeverity // 'info' | 'warning' | 'error'
|
|
403
323
|
}
|
|
404
324
|
```
|
|
405
325
|
|
|
@@ -415,40 +335,139 @@ onSessionComplete: ((result: SessionResult) => void) | undefined
|
|
|
415
335
|
angleHistory: AngleSnapshot[]
|
|
416
336
|
}
|
|
417
337
|
```
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## 🚦 Posture Gating
|
|
341
|
+
|
|
342
|
+
Each exercise config declares a **posture family** that defines what body position is required before reps are counted. This prevents false counts — e.g. waving your arm while standing won't count as a push-up.
|
|
343
|
+
|
|
344
|
+
### Posture Families
|
|
345
|
+
|
|
346
|
+
| Family | Description | Used For |
|
|
347
|
+
| --- | --- | --- |
|
|
348
|
+
| `horizontalProne` | Body horizontal, face down. Shoulders, hips, ankles in a horizontal band. | Push-ups, planks, cobra, mountain climbers |
|
|
349
|
+
| `standingUpright` | Standing, shoulders above hips above knees. | Squats, lunges, curls, presses, most yoga poses |
|
|
350
|
+
| `seated` | Hips near knees, shoulders above hips. | Boat pose, seated yoga, child's pose |
|
|
351
|
+
| `supine` | Body horizontal, face up. | Sit-ups, glute bridge, leg raises |
|
|
352
|
+
| `sidePlank` | Body horizontal, rotated to one side. | Side plank, side leg raises |
|
|
353
|
+
| `inverted` | Hips higher than shoulders and ankles. | Downward dog, handstand |
|
|
354
|
+
| `none` | No posture gating. | Custom or unconstrained exercises |
|
|
355
|
+
|
|
356
|
+
### Flow
|
|
357
|
+
|
|
358
|
+
loadExercise(config) → poll isReady() → user gets in position →
|
|
359
|
+
isReady() returns true → startSession() → reps counted normally
|
|
360
|
+
↓
|
|
361
|
+
if posture breaks mid-session
|
|
362
|
+
→ onPostureLost fires
|
|
363
|
+
→ phase detection pauses
|
|
364
|
+
→ in-progress rep discarded
|
|
365
|
+
↓
|
|
366
|
+
user re-enters position
|
|
367
|
+
→ onPostureRegained fires
|
|
368
|
+
→ counting resumes
|
|
369
|
+
|
|
370
|
+
### Example: Wait for Position Before Starting
|
|
371
|
+
|
|
372
|
+
```tsx
|
|
373
|
+
const [isInPosition, setIsInPosition] = useState(false);
|
|
374
|
+
|
|
375
|
+
useEffect(() => {
|
|
376
|
+
// Wait for the user to get into position before starting
|
|
377
|
+
const checkInterval = setInterval(() => {
|
|
378
|
+
if (nitroPoseExercises.isReady()) {
|
|
379
|
+
clearInterval(checkInterval);
|
|
380
|
+
nitroPoseExercises.startSession(10, 3);
|
|
381
|
+
}
|
|
382
|
+
}, 300);
|
|
383
|
+
return () => clearInterval(interval);
|
|
384
|
+
}, []);
|
|
385
|
+
|
|
386
|
+
useEffect(() => {
|
|
387
|
+
nitroPoseExercises.onPostureLost = () => {
|
|
388
|
+
setMessage('Get back into position');
|
|
389
|
+
};
|
|
390
|
+
nitroPoseExercises.onPostureRegained = () => {
|
|
391
|
+
setMessage('');
|
|
392
|
+
};
|
|
393
|
+
}, []);
|
|
394
|
+
|
|
395
|
+
return (
|
|
396
|
+
<>
|
|
397
|
+
{!isInPosition && <Text>Get into push-up position</Text>}
|
|
398
|
+
{isInPosition && <Text>Hold still — starting...</Text>}
|
|
399
|
+
</>
|
|
400
|
+
);
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Tuning
|
|
404
|
+
|
|
405
|
+
Posture gates use a **10-frame hysteresis** (about 1 second at 30fps with frame throttling) — single-frame failures don't pause the session. This prevents flicker from momentary occlusion or visibility drops.
|
|
418
406
|
|
|
419
407
|
---
|
|
420
408
|
|
|
421
|
-
## 🏋️ Built-In Exercise Configs
|
|
409
|
+
## 🏋️ All 38 Built-In Exercise Configs
|
|
422
410
|
|
|
423
|
-
### Rep-Based
|
|
411
|
+
### Rep-Based: Strength (15 exercises)
|
|
424
412
|
|
|
425
|
-
| Config | Exercise | Primary Angle |
|
|
413
|
+
| Config | Exercise | Primary Angle | Camera View |
|
|
414
|
+
| --- | --- | --- | --- |
|
|
415
|
+
| `PUSHUP_CONFIG` | Push-Up | Elbow 140°–180° / 30°–110° | Side |
|
|
416
|
+
| `PULL_UP_CONFIG` | Pull-Up | Elbow 150°–180° / 40°–90° | Side |
|
|
417
|
+
| `SQUAT_CONFIG` | Squat | Knee 155°–180° / 50°–105° | Side |
|
|
418
|
+
| `SUMO_SQUAT_CONFIG` | Sumo Squat | Knee 155°–180° / 60°–110° | Front |
|
|
419
|
+
| `BICEP_CURL_CONFIG` | Bicep Curl | Elbow 150°–180° / 25°–70° | Side |
|
|
420
|
+
| `SHOULDER_PRESS_CONFIG` | Shoulder Press | Elbow 155°–180° / 60°–100° | Side |
|
|
421
|
+
| `LUNGE_CONFIG` | Lunge | Front knee 155°–180° / 70°–110° | Side |
|
|
422
|
+
| `SIDE_LUNGE_CONFIG` | Side Lunge | Bent knee 155°–180° / 70°–110° | Front |
|
|
423
|
+
| `TRICEP_DIP_CONFIG` | Tricep Dip | Elbow 150°–180° / 60°–100° | Side |
|
|
424
|
+
| `DEADLIFT_CONFIG` | Deadlift | Hip 160°–180° / 60°–120° | Side |
|
|
425
|
+
| `LATERAL_RAISE_CONFIG` | Lateral Raise | Shoulder abduction 5°–30° / 75°–110° | Front |
|
|
426
|
+
| `FRONT_RAISE_CONFIG` | Front Raise | Shoulder flexion 0°–25° / 75°–110° | Side |
|
|
427
|
+
| `CALF_RAISE_CONFIG` | Calf Raise | Ankle 70°–95° / 110°–150° | Side |
|
|
428
|
+
| `OVERARM_REACH_CONFIG` | Overarm Reach | Shoulder abduction 0°–30° / 155°–180° | Front |
|
|
429
|
+
| `HIP_ABDUCTION_CONFIG` | Hip Abduction | Leg spread 0°–15° / 30°–60° | Front |
|
|
430
|
+
|
|
431
|
+
### Rep-Based: Core (6 exercises)
|
|
432
|
+
|
|
433
|
+
| Config | Exercise | Primary Angle | Camera View |
|
|
426
434
|
| --- | --- | --- | --- |
|
|
427
|
-
| `
|
|
428
|
-
| `
|
|
429
|
-
| `
|
|
430
|
-
| `
|
|
431
|
-
| `
|
|
432
|
-
| `
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
|
438
|
-
|
|
|
439
|
-
| `
|
|
440
|
-
| `WALL_SIT_CONFIG` | Wall Sit | Knee 80°–110° | 45s |
|
|
441
|
-
|
|
442
|
-
### Yoga
|
|
443
|
-
|
|
444
|
-
| Config | Exercise | Hold Angle | Duration |
|
|
445
|
-
| --- | --- | --- | --- |
|
|
446
|
-
| `
|
|
447
|
-
| `
|
|
448
|
-
| `
|
|
449
|
-
| `
|
|
450
|
-
| `
|
|
451
|
-
| `
|
|
435
|
+
| `SITUP_CONFIG` | Sit-Up | Hip 130°–180° / 40°–90° | Side |
|
|
436
|
+
| `LEG_RAISE_CONFIG` | Leg Raise | Hip 150°–180° / 60°–110° | Side |
|
|
437
|
+
| `V_UP_CONFIG` | V-Up | Hip fold 150°–180° / 30°–80° | Side |
|
|
438
|
+
| `GLUTE_BRIDGE_CONFIG` | Glute Bridge | Hip extension 80°–120° / 155°–180° | Side |
|
|
439
|
+
| `COBRA_WINGS_CONFIG` | Cobra Wings | Hip extension 160°–180° / 120°–155° | Side |
|
|
440
|
+
| `KNEE_RAISE_CONFIG` | Knee Raise | Hip 155°–180° / 60°–110° | Side |
|
|
441
|
+
|
|
442
|
+
### Hold-Based: Strength (3 exercises)
|
|
443
|
+
|
|
444
|
+
| Config | Exercise | Hold Angle | Default Duration |
|
|
445
|
+
| --- | --- | --- | --- |
|
|
446
|
+
| `PLANK_CONFIG` | Plank | Hip 155°–180° | 60s |
|
|
447
|
+
| `SIDE_PLANK_CONFIG` | Side Plank | Hip lateral 155°–180° | 30s |
|
|
448
|
+
| `WALL_SIT_CONFIG` | Wall Sit | Knee 80°–110° | 45s |
|
|
449
|
+
|
|
450
|
+
### Hold-Based: Yoga (14 exercises)
|
|
451
|
+
|
|
452
|
+
| Config | Exercise | Hold Angle | Default Duration |
|
|
453
|
+
| --- | --- | --- | --- |
|
|
454
|
+
| `MOUNTAIN_POSE_CONFIG` | Mountain Pose (Tadasana) | Knee 170°–180° | 30s |
|
|
455
|
+
| `TREE_POSE_CONFIG` | Tree Pose (Vrksasana) | Standing leg 165°–180° | 30s |
|
|
456
|
+
| `CHAIR_POSE_CONFIG` | Chair Pose (Utkatasana) | Knee 90°–130° | 30s |
|
|
457
|
+
| `WARRIOR_I_CONFIG` | Warrior I (Virabhadrasana I) | Front knee 80°–110° | 30s |
|
|
458
|
+
| `WARRIOR_II_CONFIG` | Warrior II (Virabhadrasana II) | Front knee 80°–110° | 30s |
|
|
459
|
+
| `WARRIOR_III_CONFIG` | Warrior III (Virabhadrasana III) | Hip hinge 70°–110° | 30s |
|
|
460
|
+
| `REVERSE_WARRIOR_CONFIG` | Reverse Warrior | Front knee 80°–110° | 30s |
|
|
461
|
+
| `DOWNWARD_DOG_CONFIG` | Downward Dog (Adho Mukha Svanasana) | Hip 55°–100° | 30s |
|
|
462
|
+
| `COBRA_POSE_CONFIG` | Cobra Pose (Bhujangasana) | Hip extension 120°–170° | 30s |
|
|
463
|
+
| `TRIANGLE_POSE_CONFIG` | Triangle Pose (Trikonasana) | Front leg 160°–180° | 30s |
|
|
464
|
+
| `EXTENDED_SIDE_ANGLE_CONFIG` | Extended Side Angle (Utthita Parsvakonasana) | Front knee 80°–110° | 30s |
|
|
465
|
+
| `BRIDGE_POSE_CONFIG` | Bridge Pose (Setu Bandhasana) | Knee 80°–110° | 30s |
|
|
466
|
+
| `BOAT_POSE_CONFIG` | Boat Pose (Navasana) | Hip flexion 60°–110° | 30s |
|
|
467
|
+
| `CAMEL_POSE_CONFIG` | Camel Pose (Ustrasana) | Hip extension 120°–165° | 30s |
|
|
468
|
+
| `CHILDS_POSE_CONFIG` | Child's Pose (Balasana) | Hip fold 30°–80° | 60s |
|
|
469
|
+
| `BOW_POSE_CONFIG` | Bow Pose (Dhanurasana) | Knee 50°–100° | 30s |
|
|
470
|
+
| `FISH_POSE_CONFIG` | Fish Pose (Matsyasana) | Chest open 130°–170° | 30s |
|
|
452
471
|
|
|
453
472
|
### Custom Exercise Config
|
|
454
473
|
|
|
@@ -458,6 +477,7 @@ import type { ExerciseConfig } from 'react-native-nitro-pose-exercises';
|
|
|
458
477
|
const MY_EXERCISE: ExerciseConfig = {
|
|
459
478
|
name: 'Custom Exercise',
|
|
460
479
|
type: 'rep', // 'rep' | 'hold'
|
|
480
|
+
postureFamily: 'standingUpright', // ← required: see table above
|
|
461
481
|
angles: [
|
|
462
482
|
{ name: 'myAngle', landmarkA: 11, landmarkB: 13, landmarkC: 15 },
|
|
463
483
|
],
|
|
@@ -486,12 +506,24 @@ Landmarks are mapped to MediaPipe-compatible indices on both platforms. iOS Visi
|
|
|
486
506
|
| 14 | Right elbow | 26 | Right knee |
|
|
487
507
|
| 15 | Left wrist | 27 | Left ankle |
|
|
488
508
|
|
|
489
|
-
**iOS note:** Vision provides 19 joints. Indices not available
|
|
509
|
+
**iOS note:** Vision provides 19 joints. Indices not available (face 1-10, hands 17-22, feet 29-32) are filled with `visibility: 0`.
|
|
490
510
|
|
|
491
511
|
**Android note:** ML Kit provides all 33 landmarks matching MediaPipe indices exactly.
|
|
492
512
|
|
|
493
513
|
---
|
|
494
514
|
|
|
515
|
+
## 📏 Camera Angle Guide
|
|
516
|
+
|
|
517
|
+
| ✅ Good | ❌ Bad |
|
|
518
|
+
| --- | --- |
|
|
519
|
+
| Side view, full body visible | Front-facing view |
|
|
520
|
+
| Phone at waist height, 6-8 ft away | Ground-level angle |
|
|
521
|
+
| Well-lit environment | Heavy glare or backlight |
|
|
522
|
+
|
|
523
|
+
Each exercise config includes a `cameraAngle` recommendation (`'side'` or `'front'`). Side view works for most exercises. Front view is needed for lateral raises, sumo squats, warrior II, and hip abductions.
|
|
524
|
+
|
|
525
|
+
---
|
|
526
|
+
|
|
495
527
|
## 🏗️ Architecture — OS-Native vs MediaPipe
|
|
496
528
|
|
|
497
529
|
| | OS-Native (current) | MediaPipe (previous) |
|
|
@@ -499,21 +531,24 @@ Landmarks are mapped to MediaPipe-compatible indices on both platforms. iOS Visi
|
|
|
499
531
|
| **iOS** | Apple Vision framework (built-in) | MediaPipeTasksVision (CocoaPod) |
|
|
500
532
|
| **Android** | Google ML Kit (Play Services) | com.google.mediapipe:tasks-vision |
|
|
501
533
|
| **Model file** | None needed | ~3 MB bundled `.task` file |
|
|
502
|
-
| **Color conversion** | None — takes CVPixelBuffer/
|
|
534
|
+
| **Color conversion** | None — takes CVPixelBuffer/InputImage directly | BGRA required (iOS), NV21→RGB (Android) |
|
|
503
535
|
| **App size impact** | ~200 KB (Nitro module code only) | ~11-15 MB (SDK + model) |
|
|
504
536
|
| **Updates** | OS/Play Services updates | Manual model file replacement |
|
|
505
537
|
|
|
506
538
|
---
|
|
507
539
|
|
|
508
|
-
##
|
|
509
|
-
|
|
510
|
-
For best results, the camera should see the exerciser from a **side profile**:
|
|
540
|
+
## 🛡️ Safety Features
|
|
511
541
|
|
|
512
|
-
|
|
|
542
|
+
| Feature | Description |
|
|
513
543
|
| --- | --- |
|
|
514
|
-
|
|
|
515
|
-
|
|
|
516
|
-
|
|
|
544
|
+
| **Min rep duration** | 800ms minimum per rep — prevents false counts from sensor noise |
|
|
545
|
+
| **Form score gate** | Reps with form score below 30/100 are rejected and not counted |
|
|
546
|
+
| **Feedback throttle** | Same form warning fires max once every 5 seconds to avoid UI spam |
|
|
547
|
+
| **Pose lost detection** | `onPoseLost` / `onPoseRegained` callbacks when user exits/enters frame |
|
|
548
|
+
| **Frame throttle** | Processes every 3rd frame to reduce CPU load without losing accuracy |
|
|
549
|
+
| **Visibility filter** | Landmarks with confidence below 0.3 are excluded from angle calculations |
|
|
550
|
+
| **Posture entry gate** | Sessions don't start counting until `isReady()` returns true |
|
|
551
|
+
| **Posture hysteresis** | 10 consecutive failed frames required to fire `onPostureLost` — prevents flicker |
|
|
517
552
|
|
|
518
553
|
---
|
|
519
554
|
|
|
@@ -521,8 +556,8 @@ For best results, the camera should see the exerciser from a **side profile**:
|
|
|
521
556
|
|
|
522
557
|
| Platform | Status | Notes |
|
|
523
558
|
| --- | --- | --- |
|
|
524
|
-
| **iOS** | ✅ Supported |
|
|
525
|
-
| **Android** | ✅ Supported | API 23
|
|
559
|
+
| **iOS** | ✅ Supported | Physical device, iOS 14+ (Vision body pose) |
|
|
560
|
+
| **Android** | ✅ Supported | API 23+, Google Play Services required |
|
|
526
561
|
| **iOS Simulator** | ❌ Not supported | No camera access |
|
|
527
562
|
| **Android Emulator** | ❌ Not supported | No real camera feed |
|
|
528
563
|
|
|
@@ -537,13 +572,11 @@ For best results, the camera should see the exerciser from a **side profile**:
|
|
|
537
572
|
| Vision framework (iOS, built-in) | ~0 KB (system framework) |
|
|
538
573
|
| **Total new addition** | **~200 KB** |
|
|
539
574
|
|
|
540
|
-
Compared to the MediaPipe approach (~11-15 MB), the OS-native approach adds virtually zero app size.
|
|
541
|
-
|
|
542
575
|
---
|
|
543
576
|
|
|
544
577
|
## 🤝 Contributing
|
|
545
578
|
|
|
546
|
-
PRs welcome!
|
|
579
|
+
PRs welcome! Adding a new exercise is as simple as creating a config file — no native code changes needed.
|
|
547
580
|
|
|
548
581
|
* [Development Workflow](CONTRIBUTING.md#development-workflow)
|
|
549
582
|
* [Sending a PR](CONTRIBUTING.md#sending-a-pull-request)
|