@pose-tracker/pose-estimation-web-react 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,10 @@
1
+ PoseTracker React Web SDK
2
+ Copyright (c) 2024–2026 Movelytics SAS. All rights reserved.
3
+
4
+ PROPRIETARY SOFTWARE LICENSE
5
+
6
+ This package published under the @pose-tracker scope (the "Software") is
7
+ proprietary to Movelytics SAS ("Licensor").
8
+
9
+ See the LICENSE file in `@pose-tracker/pose-estimation` for the full terms.
10
+ This React wrapper is governed by the same proprietary license.
package/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # `@pose-tracker/pose-estimation-web-react`
2
+
3
+ Thin React wrapper over [`@pose-tracker/pose-estimation-web`](../pose-estimation-web/).
4
+ Mirrors RN light naming: `PoseTrackerProvider`, `PoseCamera`, `usePoseTracker`.
5
+
6
+ ```bash
7
+ npm install @pose-tracker/pose-estimation-web-react @pose-tracker/pose-estimation-web @tensorflow/tfjs
8
+ ```
9
+
10
+ ```tsx
11
+ import {
12
+ PoseTrackerProvider,
13
+ PoseCamera,
14
+ usePoseTracker,
15
+ } from '@pose-tracker/pose-estimation-web-react';
16
+
17
+ function Screen() {
18
+ usePoseTracker({ onKeypoints: (e) => console.log(e.keypoints.length) });
19
+ return <PoseCamera style={{ height: 480 }} />;
20
+ }
21
+
22
+ export function App() {
23
+ return (
24
+ <PoseTrackerProvider options={{ model: 'movenet' }}>
25
+ <Screen />
26
+ </PoseTrackerProvider>
27
+ );
28
+ }
29
+ ```
@@ -0,0 +1,6 @@
1
+ # Third-party notices
2
+
3
+ This package wraps `@pose-tracker/pose-estimation`. See that package’s
4
+ `THIRD_PARTY_NOTICES.md` for TensorFlow.js / MoveNet notices.
5
+
6
+ React is a peer dependency (MIT) — not redistributed by this package.
package/dist/index.cjs ADDED
@@ -0,0 +1,336 @@
1
+ 'use strict';
2
+
3
+ var poseEstimationWeb = require('@pose-tracker/pose-estimation-web');
4
+ var react = require('react');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+
7
+ // src/PoseTrackerProvider.tsx
8
+ var PoseTrackerContext = react.createContext(null);
9
+ function PoseTrackerProvider({
10
+ apiToken,
11
+ options,
12
+ autoStart = true,
13
+ autoPreload = false,
14
+ children
15
+ }) {
16
+ const merged = react.useMemo(
17
+ () => ({ ...options, apiToken: apiToken ?? options?.apiToken }),
18
+ // eslint-disable-next-line react-hooks/exhaustive-deps
19
+ [
20
+ apiToken,
21
+ options?.model,
22
+ options?.modelUrl,
23
+ options?.facingMode,
24
+ options?.position,
25
+ options?.drawSkeleton,
26
+ options?.drawPlacementBox,
27
+ options?.loadingText,
28
+ options?.showWatermark,
29
+ options?.debugHud,
30
+ options?.baseUrl,
31
+ options?.features,
32
+ options?.skeletonUuid
33
+ ]
34
+ );
35
+ const clientRef = react.useRef(null);
36
+ if (!clientRef.current) {
37
+ clientRef.current = poseEstimationWeb.createPoseTracker(merged);
38
+ }
39
+ const client = clientRef.current;
40
+ const [status, setStatus] = react.useState(client.getStatus());
41
+ const [mode, setMode] = react.useState(client.getMode());
42
+ const [error, setError] = react.useState(null);
43
+ const [manifest, setManifest] = react.useState(client.getManifest());
44
+ const [exercises, setExercises] = react.useState(client.getAvailableExercises());
45
+ const hostRef = react.useRef(null);
46
+ react.useEffect(() => {
47
+ const offState = client.onStateChange(() => {
48
+ setStatus(client.getStatus());
49
+ setMode(client.getMode());
50
+ setError(client.getError());
51
+ setManifest(client.getManifest());
52
+ setExercises(client.getAvailableExercises());
53
+ });
54
+ if (autoPreload) {
55
+ client.preload({ coldStart: "basic" }).catch(() => {
56
+ });
57
+ }
58
+ return () => {
59
+ offState();
60
+ };
61
+ }, [client, autoPreload]);
62
+ react.useEffect(() => {
63
+ return () => {
64
+ void client.dispose();
65
+ clientRef.current = null;
66
+ };
67
+ }, [client]);
68
+ const preload = react.useCallback((opts) => client.preload(opts), [client]);
69
+ const configure = react.useCallback((token) => client.configure(token), [client]);
70
+ const startExercise = react.useCallback(
71
+ (id, exerciseOptions) => client.startExercise(id, exerciseOptions),
72
+ [client]
73
+ );
74
+ const stopExercise = react.useCallback(() => client.stopExercise(), [client]);
75
+ const setModel = react.useCallback(
76
+ (model, modelUrl) => client.setModel(model, modelUrl),
77
+ [client]
78
+ );
79
+ const getAvailableExercises = react.useCallback(() => client.getAvailableExercises(), [client]);
80
+ const getAvailableCustomExercises = react.useCallback(
81
+ () => client.getAvailableCustomExercises(),
82
+ [client]
83
+ );
84
+ const addEventListener = react.useCallback(
85
+ (listener) => client.addEventListener(listener),
86
+ [client]
87
+ );
88
+ const addMessageListener = react.useCallback(
89
+ (listener) => client.addMessageListener(listener),
90
+ [client]
91
+ );
92
+ const _registerMountHost = react.useCallback((el) => {
93
+ hostRef.current = el;
94
+ }, []);
95
+ const value = react.useMemo(
96
+ () => ({
97
+ client,
98
+ status,
99
+ mode,
100
+ error,
101
+ manifest,
102
+ exercises,
103
+ preload,
104
+ warmup: preload,
105
+ configure,
106
+ startExercise,
107
+ stopExercise,
108
+ setModel,
109
+ getAvailableExercises,
110
+ getAvailableCustomExercises,
111
+ addEventListener,
112
+ addMessageListener,
113
+ _registerMountHost,
114
+ options: merged,
115
+ autoStart,
116
+ autoPreload
117
+ }),
118
+ [
119
+ client,
120
+ status,
121
+ mode,
122
+ error,
123
+ manifest,
124
+ exercises,
125
+ preload,
126
+ configure,
127
+ startExercise,
128
+ stopExercise,
129
+ setModel,
130
+ getAvailableExercises,
131
+ getAvailableCustomExercises,
132
+ addEventListener,
133
+ addMessageListener,
134
+ _registerMountHost,
135
+ merged,
136
+ autoStart,
137
+ autoPreload
138
+ ]
139
+ );
140
+ return /* @__PURE__ */ jsxRuntime.jsx(PoseTrackerContext.Provider, { value, children });
141
+ }
142
+ function usePoseTrackerContext() {
143
+ const ctx = react.useContext(PoseTrackerContext);
144
+ if (!ctx) {
145
+ throw new Error("usePoseTracker must be used within PoseTrackerProvider");
146
+ }
147
+ return ctx;
148
+ }
149
+ function PoseCamera({
150
+ style,
151
+ className,
152
+ position,
153
+ drawSkeleton,
154
+ drawPlacementBox,
155
+ placementPaddingPercent,
156
+ coldStart,
157
+ loadingText,
158
+ showWatermark,
159
+ skeletonDef,
160
+ skeletonUuid,
161
+ debugHud,
162
+ autoStart: autoStartProp
163
+ }) {
164
+ const { client, autoStart: providerAutoStart, _registerMountHost } = usePoseTrackerContext();
165
+ const hostRef = react.useRef(null);
166
+ const startedRef = react.useRef(false);
167
+ react.useEffect(() => {
168
+ const el = hostRef.current;
169
+ _registerMountHost(el);
170
+ if (!el) return;
171
+ client.updateOptions({
172
+ ...position ? { position } : {},
173
+ ...typeof drawSkeleton === "boolean" ? { drawSkeleton } : {},
174
+ ...typeof drawPlacementBox === "boolean" ? { drawPlacementBox } : {},
175
+ ...typeof placementPaddingPercent === "number" ? { placementPaddingPercent } : {},
176
+ ...coldStart ? { coldStart } : {},
177
+ ...loadingText ? { loadingText } : {},
178
+ ...typeof showWatermark === "boolean" ? { showWatermark } : {},
179
+ ...skeletonDef !== void 0 ? { skeletonDef } : {},
180
+ ...skeletonUuid !== void 0 ? { skeletonUuid } : {},
181
+ ...typeof debugHud === "boolean" ? { debugHud } : {}
182
+ });
183
+ client.mount(el);
184
+ const shouldStart = autoStartProp ?? providerAutoStart;
185
+ if (shouldStart && !startedRef.current) {
186
+ startedRef.current = true;
187
+ client.start().catch(() => {
188
+ });
189
+ }
190
+ return () => {
191
+ client.stop();
192
+ startedRef.current = false;
193
+ _registerMountHost(null);
194
+ };
195
+ }, [client]);
196
+ return /* @__PURE__ */ jsxRuntime.jsx(
197
+ "div",
198
+ {
199
+ ref: hostRef,
200
+ className,
201
+ style: {
202
+ position: "relative",
203
+ width: "100%",
204
+ height: 480,
205
+ background: "#000",
206
+ ...style
207
+ }
208
+ }
209
+ );
210
+ }
211
+ function usePoseTracker(callbacks = {}) {
212
+ const context = usePoseTrackerContext();
213
+ const callbacksRef = react.useRef(callbacks);
214
+ callbacksRef.current = callbacks;
215
+ react.useEffect(() => {
216
+ const offEvents = context.addEventListener((event) => {
217
+ const cb = callbacksRef.current;
218
+ switch (event.type) {
219
+ case "initialization":
220
+ cb.onInitialization?.(event);
221
+ break;
222
+ case "error":
223
+ cb.onError?.(event);
224
+ break;
225
+ case "warning":
226
+ cb.onWarning?.(event);
227
+ break;
228
+ case "keypoints":
229
+ cb.onKeypoints?.(event);
230
+ break;
231
+ case "stats":
232
+ cb.onStats?.(event);
233
+ break;
234
+ case "angles":
235
+ cb.onAngles?.(event);
236
+ break;
237
+ case "counter":
238
+ cb.onCounter?.(event);
239
+ break;
240
+ case "posture":
241
+ cb.onPosture?.(event);
242
+ break;
243
+ case "progression":
244
+ cb.onProgression?.(event);
245
+ break;
246
+ case "recommendations":
247
+ cb.onRecommendations?.(event);
248
+ break;
249
+ case "form_score":
250
+ cb.onFormScore?.(event);
251
+ break;
252
+ case "exercise_summary":
253
+ cb.onExerciseSummary?.(event);
254
+ break;
255
+ case "jump_calibration":
256
+ cb.onJumpCalibration?.(event);
257
+ break;
258
+ case "jump_started":
259
+ cb.onJumpStarted?.(event);
260
+ break;
261
+ case "jump_height":
262
+ cb.onJumpHeight?.(event);
263
+ break;
264
+ case "jump_discarded":
265
+ cb.onJumpDiscarded?.(event);
266
+ break;
267
+ case "jump_result":
268
+ cb.onJumpResult?.(event);
269
+ break;
270
+ case "jump_summary":
271
+ cb.onJumpSummary?.(event);
272
+ break;
273
+ case "quality_changed":
274
+ cb.onQualityChanged?.(event);
275
+ break;
276
+ case "performance_warning":
277
+ cb.onPerformanceWarning?.(event);
278
+ break;
279
+ case "runtime_download_progress":
280
+ cb.onRuntimeDownloadProgress?.(event);
281
+ break;
282
+ }
283
+ });
284
+ const offMessages = context.addMessageListener((message) => {
285
+ callbacksRef.current.onMessage?.(message);
286
+ });
287
+ return () => {
288
+ offEvents();
289
+ offMessages();
290
+ };
291
+ }, [context]);
292
+ return context;
293
+ }
294
+
295
+ Object.defineProperty(exports, "COCO_KEYPOINT_NAMES", {
296
+ enumerable: true,
297
+ get: function () { return poseEstimationWeb.COCO_KEYPOINT_NAMES; }
298
+ });
299
+ Object.defineProperty(exports, "DEFAULT_LOADING_TEXT", {
300
+ enumerable: true,
301
+ get: function () { return poseEstimationWeb.DEFAULT_LOADING_TEXT; }
302
+ });
303
+ Object.defineProperty(exports, "DEFAULT_MOVENET_LIGHTNING_URL", {
304
+ enumerable: true,
305
+ get: function () { return poseEstimationWeb.DEFAULT_MOVENET_LIGHTNING_URL; }
306
+ });
307
+ Object.defineProperty(exports, "PoseTrackerClient", {
308
+ enumerable: true,
309
+ get: function () { return poseEstimationWeb.PoseTrackerClient; }
310
+ });
311
+ Object.defineProperty(exports, "SDK_NAME", {
312
+ enumerable: true,
313
+ get: function () { return poseEstimationWeb.SDK_NAME; }
314
+ });
315
+ Object.defineProperty(exports, "SDK_VERSION", {
316
+ enumerable: true,
317
+ get: function () { return poseEstimationWeb.SDK_VERSION; }
318
+ });
319
+ Object.defineProperty(exports, "createPoseTracker", {
320
+ enumerable: true,
321
+ get: function () { return poseEstimationWeb.createPoseTracker; }
322
+ });
323
+ Object.defineProperty(exports, "resolvePoseModel", {
324
+ enumerable: true,
325
+ get: function () { return poseEstimationWeb.resolvePoseModel; }
326
+ });
327
+ Object.defineProperty(exports, "shouldShowWatermark", {
328
+ enumerable: true,
329
+ get: function () { return poseEstimationWeb.shouldShowWatermark; }
330
+ });
331
+ exports.PoseCamera = PoseCamera;
332
+ exports.PoseTrackerProvider = PoseTrackerProvider;
333
+ exports.usePoseTracker = usePoseTracker;
334
+ exports.usePoseTrackerContext = usePoseTrackerContext;
335
+ //# sourceMappingURL=index.cjs.map
336
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1,88 @@
1
+ import { PoseTrackerClient, PoseTrackerStatus, PoseTrackerMode, ErrorEvent, SdkManifest, ExerciseConfig, PreloadOptions, StartExerciseOptions, PoseTrackerClientOptions, PoseTrackerEventListener, ClassicNativeMessage, SkeletonDefinition, PoseTrackerCallbacks } from '@pose-tracker/pose-estimation-web';
2
+ export { COCO_KEYPOINT_NAMES, ColdStartMode, CounterEvent, DEFAULT_LOADING_TEXT, DEFAULT_MOVENET_LIGHTNING_URL, ErrorEvent, ExerciseConfig, InitializationEvent, Keypoint, KeypointsEvent, Pose, PoseModelAlias, PoseTracker, PoseTrackerCallbacks, PoseTrackerClient, PoseTrackerClientOptions, PoseTrackerOptions, PostureEvent, PreloadOptions, SDK_NAME, SDK_VERSION, SdkManifest, StartExerciseOptions, createPoseTracker, resolvePoseModel, shouldShowWatermark } from '@pose-tracker/pose-estimation-web';
3
+ import React from 'react';
4
+
5
+ /**
6
+ * React layer mirroring RN light: Provider owns PoseTrackerClient;
7
+ * PoseCamera mounts DOM; usePoseTracker wires typed callbacks.
8
+ */
9
+
10
+ interface PoseTrackerContextValue {
11
+ client: PoseTrackerClient;
12
+ status: PoseTrackerStatus;
13
+ mode: PoseTrackerMode;
14
+ error: ErrorEvent | null;
15
+ manifest: SdkManifest | null;
16
+ exercises: ExerciseConfig[];
17
+ preload: (options?: PreloadOptions) => Promise<void>;
18
+ warmup: (options?: PreloadOptions) => Promise<void>;
19
+ configure: (apiToken?: string) => Promise<boolean>;
20
+ startExercise: (exerciseId: string, options?: StartExerciseOptions) => void;
21
+ stopExercise: () => void;
22
+ setModel: (model?: PoseTrackerClientOptions['model'], modelUrl?: string) => Promise<void>;
23
+ getAvailableExercises: () => ExerciseConfig[];
24
+ getAvailableCustomExercises: () => ReturnType<PoseTrackerClient['getAvailableCustomExercises']>;
25
+ addEventListener: (listener: PoseTrackerEventListener) => () => void;
26
+ addMessageListener: (listener: (message: ClassicNativeMessage) => void) => () => void;
27
+ /** Internal: used by PoseCamera to attach the DOM shell. */
28
+ _registerMountHost: (el: HTMLElement | null) => void;
29
+ options: PoseTrackerClientOptions;
30
+ autoStart: boolean;
31
+ autoPreload: boolean;
32
+ }
33
+ interface PoseTrackerProviderProps {
34
+ apiToken?: string;
35
+ options?: PoseTrackerClientOptions;
36
+ /**
37
+ * When true, PoseCamera calls `start()` after mount (full cold start).
38
+ * Default true for web camera screens.
39
+ */
40
+ autoStart?: boolean;
41
+ /** Warm model on provider mount without camera (coldStart basic). Default false. */
42
+ autoPreload?: boolean;
43
+ children: React.ReactNode;
44
+ }
45
+ declare function PoseTrackerProvider({ apiToken, options, autoStart, autoPreload, children, }: PoseTrackerProviderProps): React.JSX.Element;
46
+ declare function usePoseTrackerContext(): PoseTrackerContextValue;
47
+
48
+ interface PoseCameraProps {
49
+ /** CSS style for the host container (video/canvas fill 100%). */
50
+ style?: React.CSSProperties;
51
+ className?: string;
52
+ /** 'front' | 'back' — mapped to getUserMedia facingMode. Default front. */
53
+ position?: 'front' | 'back';
54
+ /** Draw navy/gold skeleton overlay. Default true. */
55
+ drawSkeleton?: boolean;
56
+ /** Placement guide while posture.ready === false. Default true. */
57
+ drawPlacementBox?: boolean;
58
+ placementPaddingPercent?: number;
59
+ /**
60
+ * Cold-start: `full` (default on camera screens) opens getUserMedia;
61
+ * `basic` warms the model only (use with autoStart=false + preload).
62
+ */
63
+ coldStart?: 'basic' | 'full';
64
+ /** Boot overlay copy. Default "AI Loading". */
65
+ loadingText?: string;
66
+ /** Force watermark; omit to derive from plan. */
67
+ showWatermark?: boolean;
68
+ skeletonDef?: SkeletonDefinition | null;
69
+ skeletonUuid?: string | null;
70
+ /** Show technical FPS HUD. Default false. */
71
+ debugHud?: boolean;
72
+ /**
73
+ * Start camera + inference when mounted. Default: provider `autoStart` (true).
74
+ */
75
+ autoStart?: boolean;
76
+ }
77
+ /**
78
+ * Browser camera + canvas overlay (web equivalent of RN `WebViewPoseView`).
79
+ * Must be rendered under {@link PoseTrackerProvider}.
80
+ */
81
+ declare function PoseCamera({ style, className, position, drawSkeleton, drawPlacementBox, placementPaddingPercent, coldStart, loadingText, showWatermark, skeletonDef, skeletonUuid, debugHud, autoStart: autoStartProp, }: PoseCameraProps): React.JSX.Element;
82
+
83
+ /**
84
+ * Access the PoseTracker pipeline and subscribe to typed events (RN light parity).
85
+ */
86
+ declare function usePoseTracker(callbacks?: PoseTrackerCallbacks): PoseTrackerContextValue;
87
+
88
+ export { PoseCamera, type PoseCameraProps, type PoseTrackerContextValue, PoseTrackerProvider, type PoseTrackerProviderProps, usePoseTracker, usePoseTrackerContext };
@@ -0,0 +1,88 @@
1
+ import { PoseTrackerClient, PoseTrackerStatus, PoseTrackerMode, ErrorEvent, SdkManifest, ExerciseConfig, PreloadOptions, StartExerciseOptions, PoseTrackerClientOptions, PoseTrackerEventListener, ClassicNativeMessage, SkeletonDefinition, PoseTrackerCallbacks } from '@pose-tracker/pose-estimation-web';
2
+ export { COCO_KEYPOINT_NAMES, ColdStartMode, CounterEvent, DEFAULT_LOADING_TEXT, DEFAULT_MOVENET_LIGHTNING_URL, ErrorEvent, ExerciseConfig, InitializationEvent, Keypoint, KeypointsEvent, Pose, PoseModelAlias, PoseTracker, PoseTrackerCallbacks, PoseTrackerClient, PoseTrackerClientOptions, PoseTrackerOptions, PostureEvent, PreloadOptions, SDK_NAME, SDK_VERSION, SdkManifest, StartExerciseOptions, createPoseTracker, resolvePoseModel, shouldShowWatermark } from '@pose-tracker/pose-estimation-web';
3
+ import React from 'react';
4
+
5
+ /**
6
+ * React layer mirroring RN light: Provider owns PoseTrackerClient;
7
+ * PoseCamera mounts DOM; usePoseTracker wires typed callbacks.
8
+ */
9
+
10
+ interface PoseTrackerContextValue {
11
+ client: PoseTrackerClient;
12
+ status: PoseTrackerStatus;
13
+ mode: PoseTrackerMode;
14
+ error: ErrorEvent | null;
15
+ manifest: SdkManifest | null;
16
+ exercises: ExerciseConfig[];
17
+ preload: (options?: PreloadOptions) => Promise<void>;
18
+ warmup: (options?: PreloadOptions) => Promise<void>;
19
+ configure: (apiToken?: string) => Promise<boolean>;
20
+ startExercise: (exerciseId: string, options?: StartExerciseOptions) => void;
21
+ stopExercise: () => void;
22
+ setModel: (model?: PoseTrackerClientOptions['model'], modelUrl?: string) => Promise<void>;
23
+ getAvailableExercises: () => ExerciseConfig[];
24
+ getAvailableCustomExercises: () => ReturnType<PoseTrackerClient['getAvailableCustomExercises']>;
25
+ addEventListener: (listener: PoseTrackerEventListener) => () => void;
26
+ addMessageListener: (listener: (message: ClassicNativeMessage) => void) => () => void;
27
+ /** Internal: used by PoseCamera to attach the DOM shell. */
28
+ _registerMountHost: (el: HTMLElement | null) => void;
29
+ options: PoseTrackerClientOptions;
30
+ autoStart: boolean;
31
+ autoPreload: boolean;
32
+ }
33
+ interface PoseTrackerProviderProps {
34
+ apiToken?: string;
35
+ options?: PoseTrackerClientOptions;
36
+ /**
37
+ * When true, PoseCamera calls `start()` after mount (full cold start).
38
+ * Default true for web camera screens.
39
+ */
40
+ autoStart?: boolean;
41
+ /** Warm model on provider mount without camera (coldStart basic). Default false. */
42
+ autoPreload?: boolean;
43
+ children: React.ReactNode;
44
+ }
45
+ declare function PoseTrackerProvider({ apiToken, options, autoStart, autoPreload, children, }: PoseTrackerProviderProps): React.JSX.Element;
46
+ declare function usePoseTrackerContext(): PoseTrackerContextValue;
47
+
48
+ interface PoseCameraProps {
49
+ /** CSS style for the host container (video/canvas fill 100%). */
50
+ style?: React.CSSProperties;
51
+ className?: string;
52
+ /** 'front' | 'back' — mapped to getUserMedia facingMode. Default front. */
53
+ position?: 'front' | 'back';
54
+ /** Draw navy/gold skeleton overlay. Default true. */
55
+ drawSkeleton?: boolean;
56
+ /** Placement guide while posture.ready === false. Default true. */
57
+ drawPlacementBox?: boolean;
58
+ placementPaddingPercent?: number;
59
+ /**
60
+ * Cold-start: `full` (default on camera screens) opens getUserMedia;
61
+ * `basic` warms the model only (use with autoStart=false + preload).
62
+ */
63
+ coldStart?: 'basic' | 'full';
64
+ /** Boot overlay copy. Default "AI Loading". */
65
+ loadingText?: string;
66
+ /** Force watermark; omit to derive from plan. */
67
+ showWatermark?: boolean;
68
+ skeletonDef?: SkeletonDefinition | null;
69
+ skeletonUuid?: string | null;
70
+ /** Show technical FPS HUD. Default false. */
71
+ debugHud?: boolean;
72
+ /**
73
+ * Start camera + inference when mounted. Default: provider `autoStart` (true).
74
+ */
75
+ autoStart?: boolean;
76
+ }
77
+ /**
78
+ * Browser camera + canvas overlay (web equivalent of RN `WebViewPoseView`).
79
+ * Must be rendered under {@link PoseTrackerProvider}.
80
+ */
81
+ declare function PoseCamera({ style, className, position, drawSkeleton, drawPlacementBox, placementPaddingPercent, coldStart, loadingText, showWatermark, skeletonDef, skeletonUuid, debugHud, autoStart: autoStartProp, }: PoseCameraProps): React.JSX.Element;
82
+
83
+ /**
84
+ * Access the PoseTracker pipeline and subscribe to typed events (RN light parity).
85
+ */
86
+ declare function usePoseTracker(callbacks?: PoseTrackerCallbacks): PoseTrackerContextValue;
87
+
88
+ export { PoseCamera, type PoseCameraProps, type PoseTrackerContextValue, PoseTrackerProvider, type PoseTrackerProviderProps, usePoseTracker, usePoseTrackerContext };
package/dist/index.js ADDED
@@ -0,0 +1,296 @@
1
+ import { createPoseTracker } from '@pose-tracker/pose-estimation-web';
2
+ export { COCO_KEYPOINT_NAMES, DEFAULT_LOADING_TEXT, DEFAULT_MOVENET_LIGHTNING_URL, PoseTrackerClient, SDK_NAME, SDK_VERSION, createPoseTracker, resolvePoseModel, shouldShowWatermark } from '@pose-tracker/pose-estimation-web';
3
+ import { createContext, useMemo, useRef, useState, useEffect, useCallback, useContext } from 'react';
4
+ import { jsx } from 'react/jsx-runtime';
5
+
6
+ // src/PoseTrackerProvider.tsx
7
+ var PoseTrackerContext = createContext(null);
8
+ function PoseTrackerProvider({
9
+ apiToken,
10
+ options,
11
+ autoStart = true,
12
+ autoPreload = false,
13
+ children
14
+ }) {
15
+ const merged = useMemo(
16
+ () => ({ ...options, apiToken: apiToken ?? options?.apiToken }),
17
+ // eslint-disable-next-line react-hooks/exhaustive-deps
18
+ [
19
+ apiToken,
20
+ options?.model,
21
+ options?.modelUrl,
22
+ options?.facingMode,
23
+ options?.position,
24
+ options?.drawSkeleton,
25
+ options?.drawPlacementBox,
26
+ options?.loadingText,
27
+ options?.showWatermark,
28
+ options?.debugHud,
29
+ options?.baseUrl,
30
+ options?.features,
31
+ options?.skeletonUuid
32
+ ]
33
+ );
34
+ const clientRef = useRef(null);
35
+ if (!clientRef.current) {
36
+ clientRef.current = createPoseTracker(merged);
37
+ }
38
+ const client = clientRef.current;
39
+ const [status, setStatus] = useState(client.getStatus());
40
+ const [mode, setMode] = useState(client.getMode());
41
+ const [error, setError] = useState(null);
42
+ const [manifest, setManifest] = useState(client.getManifest());
43
+ const [exercises, setExercises] = useState(client.getAvailableExercises());
44
+ const hostRef = useRef(null);
45
+ useEffect(() => {
46
+ const offState = client.onStateChange(() => {
47
+ setStatus(client.getStatus());
48
+ setMode(client.getMode());
49
+ setError(client.getError());
50
+ setManifest(client.getManifest());
51
+ setExercises(client.getAvailableExercises());
52
+ });
53
+ if (autoPreload) {
54
+ client.preload({ coldStart: "basic" }).catch(() => {
55
+ });
56
+ }
57
+ return () => {
58
+ offState();
59
+ };
60
+ }, [client, autoPreload]);
61
+ useEffect(() => {
62
+ return () => {
63
+ void client.dispose();
64
+ clientRef.current = null;
65
+ };
66
+ }, [client]);
67
+ const preload = useCallback((opts) => client.preload(opts), [client]);
68
+ const configure = useCallback((token) => client.configure(token), [client]);
69
+ const startExercise = useCallback(
70
+ (id, exerciseOptions) => client.startExercise(id, exerciseOptions),
71
+ [client]
72
+ );
73
+ const stopExercise = useCallback(() => client.stopExercise(), [client]);
74
+ const setModel = useCallback(
75
+ (model, modelUrl) => client.setModel(model, modelUrl),
76
+ [client]
77
+ );
78
+ const getAvailableExercises = useCallback(() => client.getAvailableExercises(), [client]);
79
+ const getAvailableCustomExercises = useCallback(
80
+ () => client.getAvailableCustomExercises(),
81
+ [client]
82
+ );
83
+ const addEventListener = useCallback(
84
+ (listener) => client.addEventListener(listener),
85
+ [client]
86
+ );
87
+ const addMessageListener = useCallback(
88
+ (listener) => client.addMessageListener(listener),
89
+ [client]
90
+ );
91
+ const _registerMountHost = useCallback((el) => {
92
+ hostRef.current = el;
93
+ }, []);
94
+ const value = useMemo(
95
+ () => ({
96
+ client,
97
+ status,
98
+ mode,
99
+ error,
100
+ manifest,
101
+ exercises,
102
+ preload,
103
+ warmup: preload,
104
+ configure,
105
+ startExercise,
106
+ stopExercise,
107
+ setModel,
108
+ getAvailableExercises,
109
+ getAvailableCustomExercises,
110
+ addEventListener,
111
+ addMessageListener,
112
+ _registerMountHost,
113
+ options: merged,
114
+ autoStart,
115
+ autoPreload
116
+ }),
117
+ [
118
+ client,
119
+ status,
120
+ mode,
121
+ error,
122
+ manifest,
123
+ exercises,
124
+ preload,
125
+ configure,
126
+ startExercise,
127
+ stopExercise,
128
+ setModel,
129
+ getAvailableExercises,
130
+ getAvailableCustomExercises,
131
+ addEventListener,
132
+ addMessageListener,
133
+ _registerMountHost,
134
+ merged,
135
+ autoStart,
136
+ autoPreload
137
+ ]
138
+ );
139
+ return /* @__PURE__ */ jsx(PoseTrackerContext.Provider, { value, children });
140
+ }
141
+ function usePoseTrackerContext() {
142
+ const ctx = useContext(PoseTrackerContext);
143
+ if (!ctx) {
144
+ throw new Error("usePoseTracker must be used within PoseTrackerProvider");
145
+ }
146
+ return ctx;
147
+ }
148
+ function PoseCamera({
149
+ style,
150
+ className,
151
+ position,
152
+ drawSkeleton,
153
+ drawPlacementBox,
154
+ placementPaddingPercent,
155
+ coldStart,
156
+ loadingText,
157
+ showWatermark,
158
+ skeletonDef,
159
+ skeletonUuid,
160
+ debugHud,
161
+ autoStart: autoStartProp
162
+ }) {
163
+ const { client, autoStart: providerAutoStart, _registerMountHost } = usePoseTrackerContext();
164
+ const hostRef = useRef(null);
165
+ const startedRef = useRef(false);
166
+ useEffect(() => {
167
+ const el = hostRef.current;
168
+ _registerMountHost(el);
169
+ if (!el) return;
170
+ client.updateOptions({
171
+ ...position ? { position } : {},
172
+ ...typeof drawSkeleton === "boolean" ? { drawSkeleton } : {},
173
+ ...typeof drawPlacementBox === "boolean" ? { drawPlacementBox } : {},
174
+ ...typeof placementPaddingPercent === "number" ? { placementPaddingPercent } : {},
175
+ ...coldStart ? { coldStart } : {},
176
+ ...loadingText ? { loadingText } : {},
177
+ ...typeof showWatermark === "boolean" ? { showWatermark } : {},
178
+ ...skeletonDef !== void 0 ? { skeletonDef } : {},
179
+ ...skeletonUuid !== void 0 ? { skeletonUuid } : {},
180
+ ...typeof debugHud === "boolean" ? { debugHud } : {}
181
+ });
182
+ client.mount(el);
183
+ const shouldStart = autoStartProp ?? providerAutoStart;
184
+ if (shouldStart && !startedRef.current) {
185
+ startedRef.current = true;
186
+ client.start().catch(() => {
187
+ });
188
+ }
189
+ return () => {
190
+ client.stop();
191
+ startedRef.current = false;
192
+ _registerMountHost(null);
193
+ };
194
+ }, [client]);
195
+ return /* @__PURE__ */ jsx(
196
+ "div",
197
+ {
198
+ ref: hostRef,
199
+ className,
200
+ style: {
201
+ position: "relative",
202
+ width: "100%",
203
+ height: 480,
204
+ background: "#000",
205
+ ...style
206
+ }
207
+ }
208
+ );
209
+ }
210
+ function usePoseTracker(callbacks = {}) {
211
+ const context = usePoseTrackerContext();
212
+ const callbacksRef = useRef(callbacks);
213
+ callbacksRef.current = callbacks;
214
+ useEffect(() => {
215
+ const offEvents = context.addEventListener((event) => {
216
+ const cb = callbacksRef.current;
217
+ switch (event.type) {
218
+ case "initialization":
219
+ cb.onInitialization?.(event);
220
+ break;
221
+ case "error":
222
+ cb.onError?.(event);
223
+ break;
224
+ case "warning":
225
+ cb.onWarning?.(event);
226
+ break;
227
+ case "keypoints":
228
+ cb.onKeypoints?.(event);
229
+ break;
230
+ case "stats":
231
+ cb.onStats?.(event);
232
+ break;
233
+ case "angles":
234
+ cb.onAngles?.(event);
235
+ break;
236
+ case "counter":
237
+ cb.onCounter?.(event);
238
+ break;
239
+ case "posture":
240
+ cb.onPosture?.(event);
241
+ break;
242
+ case "progression":
243
+ cb.onProgression?.(event);
244
+ break;
245
+ case "recommendations":
246
+ cb.onRecommendations?.(event);
247
+ break;
248
+ case "form_score":
249
+ cb.onFormScore?.(event);
250
+ break;
251
+ case "exercise_summary":
252
+ cb.onExerciseSummary?.(event);
253
+ break;
254
+ case "jump_calibration":
255
+ cb.onJumpCalibration?.(event);
256
+ break;
257
+ case "jump_started":
258
+ cb.onJumpStarted?.(event);
259
+ break;
260
+ case "jump_height":
261
+ cb.onJumpHeight?.(event);
262
+ break;
263
+ case "jump_discarded":
264
+ cb.onJumpDiscarded?.(event);
265
+ break;
266
+ case "jump_result":
267
+ cb.onJumpResult?.(event);
268
+ break;
269
+ case "jump_summary":
270
+ cb.onJumpSummary?.(event);
271
+ break;
272
+ case "quality_changed":
273
+ cb.onQualityChanged?.(event);
274
+ break;
275
+ case "performance_warning":
276
+ cb.onPerformanceWarning?.(event);
277
+ break;
278
+ case "runtime_download_progress":
279
+ cb.onRuntimeDownloadProgress?.(event);
280
+ break;
281
+ }
282
+ });
283
+ const offMessages = context.addMessageListener((message) => {
284
+ callbacksRef.current.onMessage?.(message);
285
+ });
286
+ return () => {
287
+ offEvents();
288
+ offMessages();
289
+ };
290
+ }, [context]);
291
+ return context;
292
+ }
293
+
294
+ export { PoseCamera, PoseTrackerProvider, usePoseTracker, usePoseTrackerContext };
295
+ //# sourceMappingURL=index.js.map
296
+ //# sourceMappingURL=index.js.map
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "@pose-tracker/pose-estimation-web-react",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "description": "PoseTracker React (web) pose estimation — thin wrapper over @pose-tracker/pose-estimation-web. Browser camera + canvas, not React Native.",
6
+ "type": "module",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "require": "./dist/index.cjs"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist/index.js",
19
+ "dist/index.cjs",
20
+ "dist/index.d.ts",
21
+ "dist/index.d.cts",
22
+ "README.md",
23
+ "LICENSE",
24
+ "THIRD_PARTY_NOTICES.md"
25
+ ],
26
+ "scripts": {
27
+ "build": "tsup",
28
+ "typecheck": "tsc -p tsconfig.json --noEmit",
29
+ "prepack": "npm run build",
30
+ "pack:dry": "npm pack --dry-run"
31
+ },
32
+ "keywords": [
33
+ "pose-estimation",
34
+ "react",
35
+ "movenet",
36
+ "tensorflow.js",
37
+ "posetracker",
38
+ "webcam",
39
+ "web",
40
+ "sdk"
41
+ ],
42
+ "author": {
43
+ "name": "Movelytics SAS",
44
+ "email": "hello@posetracker.com",
45
+ "url": "https://www.posetracker.com"
46
+ },
47
+ "license": "SEE LICENSE IN LICENSE",
48
+ "homepage": "https://www.posetracker.com",
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "git+https://github.com/Movelytics/pose-estimation-web-react.git"
52
+ },
53
+ "publishConfig": {
54
+ "access": "public"
55
+ },
56
+ "dependencies": {
57
+ "@pose-tracker/pose-estimation-web": "0.1.0"
58
+ },
59
+ "peerDependencies": {
60
+ "react": ">=18.0.0",
61
+ "react-dom": ">=18.0.0",
62
+ "@tensorflow/tfjs": "^4.22.0"
63
+ },
64
+ "devDependencies": {
65
+ "@types/react": "^18.2.0",
66
+ "@types/react-dom": "^18.2.0",
67
+ "react": "^18.2.0",
68
+ "react-dom": "^18.2.0",
69
+ "tsup": "^8.4.0",
70
+ "typescript": "^5.4.0"
71
+ }
72
+ }