npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-native-nitro-pose-exercises

v1.1.18

Published

Real-time on-device exercise tracking for React Native. Rep counting, form validation, and skeleton overlay powered by Apple Vision (iOS) and Google ML Kit (Android) with VisionCamera v5 via Nitro Modules.

Readme

react-native-nitro-pose-exercises

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.

  • 🏋️ 38 Built-In Exercises — Push-ups, squats, deadlifts, yoga poses, and more
  • 🔄 Rep Counting — Automatic rep detection with configurable state machines
  • 🧘 Hold Tracking — Duration and stability tracking for planks, yoga poses, and isometric holds
  • 📐 Form Validation — Real-time form feedback with angle-based rules
  • 🚦 Posture Gating — Refuses to count reps unless the user is in valid posture; "Get in position" feedback before sessions start
  • 💀 Skeleton Overlay — Skia-powered skeleton with glow effects and live angle badges
  • Fully Native — OS-level pose detection via Nitro Modules, zero JS bridge overhead
  • 📦 Zero Model Bundling — No ML model files to download or ship with your app
  • 🪶 ~200 KB — Virtually zero app size impact

[!IMPORTANT]

  • Requires React Native 0.76+ with Nitro Modules and VisionCamera v5.
  • Must be tested on a physical device — camera + ML inference don't work on simulators.
  • iOS requires iOS 14+ (Vision body pose API). Android requires API 23+ (ML Kit).

📦 Installation

npm install react-native-nitro-pose-exercises react-native-nitro-modules
npm install react-native-vision-camera react-native-nitro-image
npm install react-native-vision-camera-worklets react-native-worklets
npm install react-native-reanimated

For Skia skeleton overlay (optional):

npm install @shopify/react-native-skia react-native-vision-camera-skia
cd ios && pod install

[!NOTE] This package uses OS-native pose detection on both platforms. iOS uses Apple's Vision framework (VNDetectHumanBodyPoseRequest) — built into iOS, no extra dependencies. Android uses Google ML Kit Pose Detection (com.google.mlkit:pose-detection:18.0.0-beta5) — model auto-managed via Play Services. No model files to bundle, no extra downloads, no color format conversions.


Demo


🧠 Overview

| Feature | Description | | --- | --- | | Rep-Based Exercises | Cyclic state machine (UP → DOWN → UP = 1 rep). Push-ups, squats, curls, and more. | | Hold-Based Exercises | Single target pose with duration + stability tracking. Planks, wall sits, yoga poses. | | 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). | | Form Feedback | Angle-based rules with throttled real-time callbacks. Bad form blocks rep counting. | | Skeleton Overlay | Glow-effect bones, color-coded joints, and live angle badges drawn over camera via Skia. | | Bilateral Tracking | Left and right side angles tracked independently. | | Fatigue Guard | Minimum 800ms per rep prevents false counts. Form score gate rejects bad reps. |


🔧 Setup

No Model File Needed

Unlike MediaPipe-based solutions, this library uses OS-native APIs. There is no model file to download or bundle.

  • iOS: Apple Vision is a system framework — already on every iPhone running iOS 14+.
  • Android: ML Kit manages its own model via Google Play Services — downloads and updates automatically.

Permissions

iOS — Info.plist:

<key>NSCameraUsageDescription</key>
<string>Camera is needed for pose detection during exercises</string>
<key>NSMicrophoneUsageDescription</key>
<string>Microphone access for audio during exercise sessions</string>

Android — AndroidManifest.xml:

<uses-permission android:name="android.permission.CAMERA" />

Babel Config

module.exports = {
  presets: ['module:@react-native/babel-preset'],
  plugins: [
    'react-native-worklets/plugin',
    'react-native-reanimated/plugin', // must be last
  ],
};

Podspec (for library authors)

s.frameworks = ['Vision', 'AVFoundation']

No CocoaPods dependencies required — Vision is built into iOS.

Android Gradle (for library authors)

dependencies {
    implementation 'com.google.mlkit:pose-detection:18.0.0-beta5'
}

⚙️ Usage

Basic — Normal Camera (No Skeleton)

import { useEffect, useState } from 'react';
import { StyleSheet, View, Text } from 'react-native';
import {
  Camera,
  useCameraDevice,
  useCameraPermission,
  useFrameOutput,
  useAsyncRunner,
} from 'react-native-vision-camera';
import {
  nitroPoseExercises,
  PUSHUP_CONFIG,
  type RepData,
  type SessionResult,
} from 'react-native-nitro-pose-exercises';

export default function App() {
  const { hasPermission, requestPermission } = useCameraPermission();
  const device = useCameraDevice('back');
  const asyncRunner = useAsyncRunner();
  const [repCount, setRepCount] = useState(0);

  useEffect(() => {
    if (!hasPermission) requestPermission();
  }, [hasPermission]);

  useEffect(() => {
    async function init() {
      await nitroPoseExercises.initialize('');
      nitroPoseExercises.loadExercise(PUSHUP_CONFIG);

      nitroPoseExercises.onRepComplete = (data: RepData) => {
        setRepCount(data.repNumber);
      };

      nitroPoseExercises.onSessionComplete = (result: SessionResult) => {
        console.log(`Done! ${result.totalReps} reps, form: ${result.averageFormScore}`);
      };

      nitroPoseExercises.startSession(10, 3);
    }

    init();
    return () => { nitroPoseExercises.release(); };
  }, []);

  const frameOutput = useFrameOutput({
    pixelFormat: 'rgb',
    onFrame(frame) {
      'worklet';
      const accepted = asyncRunner.runAsync(() => {
        'worklet';
        try {
          nitroPoseExercises.processFrame(frame);
        } finally {
          frame.dispose();
        }
      });
      if (!accepted) frame.dispose();
    },
  });

  if (!hasPermission || !device) return null;

  return (
    <View style={StyleSheet.absoluteFill}>
      <Camera
        style={StyleSheet.absoluteFill}
        device={device}
        isActive={true}
        outputs={[frameOutput]}
      />
      <Text style={styles.repText}>{repCount} REPS</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  repText: {
    position: 'absolute',
    top: 100,
    alignSelf: 'center',
    fontSize: 48,
    fontFamily: 'System',
    color: '#4CAF50',
  },
});

🧩 API Reference

Lifecycle

// Initialize the pose engine (modelPath is ignored — OS-native, no model file needed)
initialize(modelPath: string): Promise<void>

// Clean up resources
release(): void

Exercise Setup

loadExercise(config: ExerciseConfig): void

Session Control

startSession(targetReps: number, countdownSeconds: number): void
pauseSession(): void
resumeSession(): void
stopSession(): void
// Returns true if the user is currently in valid posture for the loaded exercise.
// Poll this before starting a session, e.g. show "Get in position" until ready.
isReady(): boolean

Frame Processing

// Call from VisionCamera frame processor worklet
processFrame(frame: Frame): void

State (Readable)

readonly status: SessionStatus        // 'idle' | 'countdown' | 'active' | 'paused' | 'completed'
readonly currentPhase: ExercisePhase   // 'up' | 'down' | 'hold' | 'transition' | 'unknown'
readonly repCount: number
readonly landmarks: Landmark[]         // Body landmarks mapped to MediaPipe indices

Callbacks

onRepComplete: ((data: RepData) => void) | undefined
onPhaseChange: ((phase: ExercisePhase) => void) | undefined
onFormFeedback: ((feedback: FormFeedback) => void) | undefined
onHoldProgress: ((progress: HoldProgress) => void) | undefined
onPoseLost: (() => void) | undefined
onPoseRegained: (() => void) | undefined
onPostureLost: (() => void) | undefined        
onPostureRegained: (() => void) | undefined
onSessionComplete: ((result: SessionResult) => void) | undefined

Callback Payloads

RepData

{
  repNumber: number
  durationMs: number
  formScore: number        // 0-100
  angles: AngleSnapshot[]  // all tracked angles at rep completion
}

FormFeedback

{
  ruleName: string
  message: string
  severity: FormSeverity   // 'info' | 'warning' | 'error'
}

SessionResult

{
  totalReps: number
  totalDurationMs: number
  averageRepDurationMs: number
  averageFormScore: number
  formViolations: FormFeedback[]
  angleHistory: AngleSnapshot[]
}

🚦 Posture Gating

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.

Posture Families

| Family | Description | Used For | | --- | --- | --- | | horizontalProne | Body horizontal, face down. Shoulders, hips, ankles in a horizontal band. | Push-ups, planks, cobra, mountain climbers | | standingUpright | Standing, shoulders above hips above knees. | Squats, lunges, curls, presses, most yoga poses | | seated | Hips near knees, shoulders above hips. | Boat pose, seated yoga, child's pose | | supine | Body horizontal, face up. | Sit-ups, glute bridge, leg raises | | sidePlank | Body horizontal, rotated to one side. | Side plank, side leg raises | | inverted | Hips higher than shoulders and ankles. | Downward dog, handstand | | none | No posture gating. | Custom or unconstrained exercises |

Flow

loadExercise(config) → poll isReady() → user gets in position → isReady() returns true → startSession() → reps counted normally ↓ if posture breaks mid-session → onPostureLost fires → phase detection pauses → in-progress rep discarded ↓ user re-enters position → onPostureRegained fires → counting resumes

Example: Wait for Position Before Starting

const [isInPosition, setIsInPosition] = useState(false);

useEffect(() => {
// Wait for the user to get into position before starting
const checkInterval = setInterval(() => {
  if (nitroPoseExercises.isReady()) {
    clearInterval(checkInterval);
    nitroPoseExercises.startSession(10, 3);
  }
}, 300);
  return () => clearInterval(interval);
}, []);

useEffect(() => {
  nitroPoseExercises.onPostureLost = () => {
    setMessage('Get back into position');
  };
  nitroPoseExercises.onPostureRegained = () => {
    setMessage('');
  };
}, []);

return (
  <>
    {!isInPosition && <Text>Get into push-up position</Text>}
    {isInPosition && <Text>Hold still — starting...</Text>}
  </>
);

Tuning

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.


🏋️ All 38 Built-In Exercise Configs

Rep-Based: Strength (15 exercises)

| Config | Exercise | Primary Angle | Camera View | | --- | --- | --- | --- | | PUSHUP_CONFIG | Push-Up | Elbow 140°–180° / 30°–110° | Side | | PULL_UP_CONFIG | Pull-Up | Elbow 150°–180° / 40°–90° | Side | | SQUAT_CONFIG | Squat | Knee 155°–180° / 50°–105° | Side | | SUMO_SQUAT_CONFIG | Sumo Squat | Knee 155°–180° / 60°–110° | Front | | BICEP_CURL_CONFIG | Bicep Curl | Elbow 150°–180° / 25°–70° | Side | | SHOULDER_PRESS_CONFIG | Shoulder Press | Elbow 155°–180° / 60°–100° | Side | | LUNGE_CONFIG | Lunge | Front knee 155°–180° / 70°–110° | Side | | SIDE_LUNGE_CONFIG | Side Lunge | Bent knee 155°–180° / 70°–110° | Front | | TRICEP_DIP_CONFIG | Tricep Dip | Elbow 150°–180° / 60°–100° | Side | | DEADLIFT_CONFIG | Deadlift | Hip 160°–180° / 60°–120° | Side | | LATERAL_RAISE_CONFIG | Lateral Raise | Shoulder abduction 5°–30° / 75°–110° | Front | | FRONT_RAISE_CONFIG | Front Raise | Shoulder flexion 0°–25° / 75°–110° | Side | | CALF_RAISE_CONFIG | Calf Raise | Ankle 70°–95° / 110°–150° | Side | | OVERARM_REACH_CONFIG | Overarm Reach | Shoulder abduction 0°–30° / 155°–180° | Front | | HIP_ABDUCTION_CONFIG | Hip Abduction | Leg spread 0°–15° / 30°–60° | Front |

Rep-Based: Core (6 exercises)

| Config | Exercise | Primary Angle | Camera View | | --- | --- | --- | --- | | SITUP_CONFIG | Sit-Up | Hip 130°–180° / 40°–90° | Side | | LEG_RAISE_CONFIG | Leg Raise | Hip 150°–180° / 60°–110° | Side | | V_UP_CONFIG | V-Up | Hip fold 150°–180° / 30°–80° | Side | | GLUTE_BRIDGE_CONFIG | Glute Bridge | Hip extension 80°–120° / 155°–180° | Side | | COBRA_WINGS_CONFIG | Cobra Wings | Hip extension 160°–180° / 120°–155° | Side | | KNEE_RAISE_CONFIG | Knee Raise | Hip 155°–180° / 60°–110° | Side |

Hold-Based: Strength (3 exercises)

| Config | Exercise | Hold Angle | Default Duration | | --- | --- | --- | --- | | PLANK_CONFIG | Plank | Hip 155°–180° | 60s | | SIDE_PLANK_CONFIG | Side Plank | Hip lateral 155°–180° | 30s | | WALL_SIT_CONFIG | Wall Sit | Knee 80°–110° | 45s |

Hold-Based: Yoga (14 exercises)

| Config | Exercise | Hold Angle | Default Duration | | --- | --- | --- | --- | | MOUNTAIN_POSE_CONFIG | Mountain Pose (Tadasana) | Knee 170°–180° | 30s | | TREE_POSE_CONFIG | Tree Pose (Vrksasana) | Standing leg 165°–180° | 30s | | CHAIR_POSE_CONFIG | Chair Pose (Utkatasana) | Knee 90°–130° | 30s | | WARRIOR_I_CONFIG | Warrior I (Virabhadrasana I) | Front knee 80°–110° | 30s | | WARRIOR_II_CONFIG | Warrior II (Virabhadrasana II) | Front knee 80°–110° | 30s | | WARRIOR_III_CONFIG | Warrior III (Virabhadrasana III) | Hip hinge 70°–110° | 30s | | REVERSE_WARRIOR_CONFIG | Reverse Warrior | Front knee 80°–110° | 30s | | DOWNWARD_DOG_CONFIG | Downward Dog (Adho Mukha Svanasana) | Hip 55°–100° | 30s | | COBRA_POSE_CONFIG | Cobra Pose (Bhujangasana) | Hip extension 120°–170° | 30s | | TRIANGLE_POSE_CONFIG | Triangle Pose (Trikonasana) | Front leg 160°–180° | 30s | | EXTENDED_SIDE_ANGLE_CONFIG | Extended Side Angle (Utthita Parsvakonasana) | Front knee 80°–110° | 30s | | BRIDGE_POSE_CONFIG | Bridge Pose (Setu Bandhasana) | Knee 80°–110° | 30s | | BOAT_POSE_CONFIG | Boat Pose (Navasana) | Hip flexion 60°–110° | 30s | | CAMEL_POSE_CONFIG | Camel Pose (Ustrasana) | Hip extension 120°–165° | 30s | | CHILDS_POSE_CONFIG | Child's Pose (Balasana) | Hip fold 30°–80° | 60s | | BOW_POSE_CONFIG | Bow Pose (Dhanurasana) | Knee 50°–100° | 30s | | FISH_POSE_CONFIG | Fish Pose (Matsyasana) | Chest open 130°–170° | 30s |

Custom Exercise Config

import type { ExerciseConfig } from 'react-native-nitro-pose-exercises';

const MY_EXERCISE: ExerciseConfig = {
  name: 'Custom Exercise',
  type: 'rep',  // 'rep' | 'hold'
  postureFamily: 'standingUpright',  // ← required: see table above
  angles: [
    { name: 'myAngle', landmarkA: 11, landmarkB: 13, landmarkC: 15 },
  ],
  phases: [
    { phase: 'up', angleName: 'myAngle', minAngle: 150, maxAngle: 180 },
    { phase: 'down', angleName: 'myAngle', minAngle: 30, maxAngle: 100 },
  ],
  repSequence: ['up', 'down', 'up'],
  formRules: [],
  holdDurationMs: 0,
};

📐 Landmark Index Reference

Landmarks are mapped to MediaPipe-compatible indices on both platforms. iOS Vision provides 19 joints (all exercise-critical joints covered), Android ML Kit provides the full 33.

| Index | Landmark | Index | Landmark | | --- | --- | --- | --- | | 0 | Nose | 16 | Right wrist | | 11 | Left shoulder | 23 | Left hip | | 12 | Right shoulder | 24 | Right hip | | 13 | Left elbow | 25 | Left knee | | 14 | Right elbow | 26 | Right knee | | 15 | Left wrist | 27 | Left ankle |

iOS note: Vision provides 19 joints. Indices not available (face 1-10, hands 17-22, feet 29-32) are filled with visibility: 0.

Android note: ML Kit provides all 33 landmarks matching MediaPipe indices exactly.


📏 Camera Angle Guide

| ✅ Good | ❌ Bad | | --- | --- | | Side view, full body visible | Front-facing view | | Phone at waist height, 6-8 ft away | Ground-level angle | | Well-lit environment | Heavy glare or backlight |

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.


🏗️ Architecture — OS-Native vs MediaPipe

| | OS-Native (current) | MediaPipe (previous) | | --- | --- | --- | | iOS | Apple Vision framework (built-in) | MediaPipeTasksVision (CocoaPod) | | Android | Google ML Kit (Play Services) | com.google.mediapipe:tasks-vision | | Model file | None needed | ~3 MB bundled .task file | | Color conversion | None — takes CVPixelBuffer/InputImage directly | BGRA required (iOS), NV21→RGB (Android) | | App size impact | ~200 KB (Nitro module code only) | ~11-15 MB (SDK + model) | | Updates | OS/Play Services updates | Manual model file replacement |


🛡️ Safety Features

| Feature | Description | | --- | --- | | Min rep duration | 800ms minimum per rep — prevents false counts from sensor noise | | Form score gate | Reps with form score below 30/100 are rejected and not counted | | Feedback throttle | Same form warning fires max once every 5 seconds to avoid UI spam | | Pose lost detection | onPoseLost / onPoseRegained callbacks when user exits/enters frame | | Frame throttle | Processes every 3rd frame to reduce CPU load without losing accuracy | | Visibility filter | Landmarks with confidence below 0.3 are excluded from angle calculations | | Posture entry gate | Sessions don't start counting until isReady() returns true | | Posture hysteresis | 10 consecutive failed frames required to fire onPostureLost — prevents flicker |


🧩 Supported Platforms

| Platform | Status | Notes | | --- | --- | --- | | iOS | ✅ Supported | Physical device, iOS 14+ (Vision body pose) | | Android | ✅ Supported | API 23+, Google Play Services required | | iOS Simulator | ❌ Not supported | No camera access | | Android Emulator | ❌ Not supported | No real camera feed |


📊 App Size Impact

| Component | Size | | --- | --- | | Nitro module code (Swift + Kotlin) | ~200 KB | | ML Kit (Android, via Play Services) | ~0 KB (managed externally) | | Vision framework (iOS, built-in) | ~0 KB (system framework) | | Total new addition | ~200 KB |


🤝 Contributing

PRs welcome! Adding a new exercise is as simple as creating a config file — no native code changes needed.


🪪 License

MIT © Gautham Vijayan


Made with ❤️ and Nitro Modules + VisionCamera + Apple Vision + ML Kit