@pose-tracker/react-native-pose-estimation-light 0.1.0 → 0.2.1

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.
Files changed (38) hide show
  1. package/README.md +14 -2
  2. package/lib/PoseTrackerProvider.js +3 -0
  3. package/lib/backends/webview/WebViewPoseBackend.d.ts +1 -1
  4. package/lib/backends/webview/onlineRuntime.d.ts +14 -4
  5. package/lib/backends/webview/onlineRuntime.js +22 -4
  6. package/lib/backends/webview/poseHtml.d.ts +12 -4
  7. package/lib/backends/webview/poseHtml.js +15 -6
  8. package/lib/backends/webview/poseRuntimeSource.js +2 -2
  9. package/lib/camera/WebViewPoseView.d.ts +12 -0
  10. package/lib/camera/WebViewPoseView.js +29 -0
  11. package/lib/client.d.ts +13 -2
  12. package/lib/client.js +9 -1
  13. package/lib/engine/types.d.ts +5 -0
  14. package/lib/events/classicMessage.d.ts +2 -2
  15. package/lib/events/classicMessage.js +4 -1
  16. package/lib/index.d.ts +2 -2
  17. package/lib/index.js +4 -2
  18. package/lib/models/poseModels.d.ts +8 -6
  19. package/lib/models/poseModels.js +9 -8
  20. package/lib/sdkVersion.d.ts +1 -1
  21. package/lib/sdkVersion.js +1 -1
  22. package/lib/types/events.d.ts +37 -2
  23. package/lib/types/features.d.ts +2 -2
  24. package/lib/types/features.js +8 -9
  25. package/package.json +1 -1
  26. package/src/PoseTrackerProvider.tsx +3 -0
  27. package/src/backends/webview/WebViewPoseBackend.ts +1 -1
  28. package/src/backends/webview/onlineRuntime.ts +36 -5
  29. package/src/backends/webview/poseHtml.ts +23 -6
  30. package/src/camera/WebViewPoseView.tsx +43 -0
  31. package/src/client.ts +25 -5
  32. package/src/engine/types.ts +5 -0
  33. package/src/events/classicMessage.ts +7 -2
  34. package/src/index.ts +3 -0
  35. package/src/models/poseModels.ts +14 -11
  36. package/src/sdkVersion.ts +1 -1
  37. package/src/types/events.ts +35 -0
  38. package/src/types/features.ts +8 -9
@@ -178,6 +178,8 @@ function WebViewPoseView(props) {
178
178
  // injected via __PT_SET_SKELETON (avoids remounting on uuid fetch).
179
179
  skeletonDef: props.skeletonDef ?? null,
180
180
  capturePriority: client.getQualityState().capturePriority,
181
+ sourceType: props.source ?? 'camera',
182
+ sourceUrl: props.sourceUri,
181
183
  });
182
184
  }, [
183
185
  facingMode,
@@ -188,6 +190,8 @@ function WebViewPoseView(props) {
188
190
  coldStart,
189
191
  loadingText,
190
192
  props.skeletonDef,
193
+ props.source,
194
+ props.sourceUri,
191
195
  ]);
192
196
  // Apply custom skeleton (uuid fetch or prop) without remounting the page.
193
197
  (0, react_1.useEffect)(() => {
@@ -210,6 +214,31 @@ function WebViewPoseView(props) {
210
214
  const payload = JSON.stringify(loadingText);
211
215
  webRef.current?.injectJavaScript?.(`window.__PT_SET_LOADING_TEXT && window.__PT_SET_LOADING_TEXT(${payload}); true;`);
212
216
  }, [isWebViewBackend, html, loadingText]);
217
+ // Live source switches after first HTML boot (CFG already has initial source).
218
+ const lastSourceHtmlRef = (0, react_1.useRef)(null);
219
+ (0, react_1.useEffect)(() => {
220
+ if (!isWebViewBackend || !html)
221
+ return;
222
+ if (lastSourceHtmlRef.current !== html) {
223
+ lastSourceHtmlRef.current = html;
224
+ return;
225
+ }
226
+ const type = props.source ?? 'camera';
227
+ const payload = JSON.stringify({
228
+ type,
229
+ url: props.sourceUri ?? null,
230
+ base64: props.sourceBase64 ?? null,
231
+ mime: props.sourceMime ?? null,
232
+ });
233
+ webRef.current?.injectJavaScript?.(`window.__PT_SET_SOURCE && window.__PT_SET_SOURCE(${payload}); true;`);
234
+ }, [
235
+ isWebViewBackend,
236
+ html,
237
+ props.source,
238
+ props.sourceUri,
239
+ props.sourceBase64,
240
+ props.sourceMime,
241
+ ]);
213
242
  (0, react_1.useEffect)(() => {
214
243
  if (!(backend instanceof WebViewPoseBackend_1.WebViewPoseBackend)) {
215
244
  return;
package/lib/client.d.ts CHANGED
@@ -60,8 +60,8 @@ export interface PoseTrackerClientOptions extends ConfigureOptions {
60
60
  usageTracker?: UsageTracker;
61
61
  /**
62
62
  * Docs API `model` query parity (`movenet` default, `blazepose`, …).
63
- * BlazePose is not wired in the light WebView yet — use `modelUrl` for a
64
- * custom TF.js graph model. Ignored when {@link modelUrl} is set.
63
+ * BlazePose loads via CDN pose-detection in the WebView (heavier than
64
+ * MoveNet). Ignored when {@link modelUrl} is set.
65
65
  */
66
66
  model?: PoseModelAlias;
67
67
  /**
@@ -102,6 +102,12 @@ export interface PoseTrackerClientOptions extends ConfigureOptions {
102
102
  * triaging Android GPU issues in the field.
103
103
  */
104
104
  onDiagnostic?: (message: string) => void;
105
+ /**
106
+ * When true, exercise sessions request `engine_debug` snapshots from the
107
+ * remote engine (bundle ≥ 1.2.4). Overridable per call via
108
+ * `startExercise(id, { debug: true|false })`.
109
+ */
110
+ debugEngine?: boolean;
105
111
  }
106
112
  /**
107
113
  * Per-session options for {@link PoseTrackerClient.startExercise} — the SDK
@@ -118,6 +124,11 @@ export interface StartExerciseOptions {
118
124
  userHeightCm?: number;
119
125
  /** Device pitch in degrees — jump exercises compensate camera tilt. */
120
126
  devicePitchDeg?: number;
127
+ /**
128
+ * Request throttled `engine_debug` events for this session (movement FSM
129
+ * QA). Defaults to {@link PoseTrackerClientOptions.debugEngine}.
130
+ */
131
+ debug?: boolean;
121
132
  }
122
133
  export declare class PoseTrackerClient {
123
134
  private status;
package/lib/client.js CHANGED
@@ -398,7 +398,7 @@ class PoseTrackerClient {
398
398
  tfjsVersion: this.options.tfjsVersion,
399
399
  });
400
400
  this.options.onDiagnostic?.(`[posetracker-light] pose-runtime online version=${parts.version} ` +
401
- `modelId=${parts.modelId} modelUrl=${parts.modelUrl}`);
401
+ `modelId=${parts.modelId} modelKind=${parts.modelKind} modelUrl=${parts.modelUrl}`);
402
402
  return parts;
403
403
  }
404
404
  /**
@@ -818,17 +818,21 @@ class PoseTrackerClient {
818
818
  this.stopExercise();
819
819
  this.currentExerciseId = exerciseId;
820
820
  this.keypointsSuppressionLogged = false;
821
+ const debug = options.debug ?? this.options.debugEngine === true;
821
822
  this.session = this.engine.createSession({
822
823
  exercise,
823
824
  locale: this.options.locale ?? 'en',
824
825
  difficulty: options.difficulty,
825
826
  minGrade: this.features.minGrade ?? undefined,
827
+ debug,
826
828
  features: {
827
829
  angles: this.features.angles,
828
830
  recommendations: this.features.recommendations,
829
831
  progression: this.features.progression,
830
832
  },
831
833
  }, (event) => this.emitFromSession(event));
834
+ this.options.onDiagnostic?.(`[posetracker] startExercise id=${exercise.id} difficulty=${options.difficulty ?? 'medium'} ` +
835
+ `debug=${debug} engine=${this.engine.version} minGrade=${this.features.minGrade ?? 'off'}`);
832
836
  }
833
837
  /** Custom engine session (jump_analysis / air_time_jump). */
834
838
  startCustomExercise(custom, options) {
@@ -1032,6 +1036,10 @@ class PoseTrackerClient {
1032
1036
  return;
1033
1037
  }
1034
1038
  const classic = (0, classicMessage_1.toClassicNativeMessage)(event);
1039
+ if (classic == null) {
1040
+ // SDK-only events (e.g. engine_debug) have no classic equivalent.
1041
+ return;
1042
+ }
1035
1043
  this.messageListeners.forEach((l) => {
1036
1044
  try {
1037
1045
  l(classic);
@@ -36,6 +36,11 @@ export interface EngineSessionOptions {
36
36
  minGrade?: MinGrade;
37
37
  /** Emission flags — engines older than 1.1.0 ignore them (client re-filters). */
38
38
  features?: EngineSessionFeatures;
39
+ /**
40
+ * When true, the engine emits throttled `engine_debug` events (movement
41
+ * FSM snapshot). Requires engine bundle ≥ 1.2.4. Ignored by older engines.
42
+ */
43
+ debug?: boolean;
39
44
  }
40
45
  export interface EngineSession {
41
46
  /** Feed one estimated pose; the session emits events through the sink. */
@@ -26,6 +26,6 @@ export declare function anglesToClassicTree(angles: AngleValue[]): {
26
26
  /**
27
27
  * Map a typed SDK event to a classic PoseTracker `sendDataToNative` payload.
28
28
  * Returns null for SDK-only events that have no classic equivalent and should
29
- * not be mirrored (none today — quality/performance map to error/warning).
29
+ * not be mirrored (e.g. `engine_debug` QA snapshots).
30
30
  */
31
- export declare function toClassicNativeMessage(event: PoseTrackerEvent): ClassicNativeMessage;
31
+ export declare function toClassicNativeMessage(event: PoseTrackerEvent): ClassicNativeMessage | null;
@@ -49,7 +49,7 @@ function anglesToClassicTree(angles) {
49
49
  /**
50
50
  * Map a typed SDK event to a classic PoseTracker `sendDataToNative` payload.
51
51
  * Returns null for SDK-only events that have no classic equivalent and should
52
- * not be mirrored (none today — quality/performance map to error/warning).
52
+ * not be mirrored (e.g. `engine_debug` QA snapshots).
53
53
  */
54
54
  function toClassicNativeMessage(event) {
55
55
  switch (event.type) {
@@ -236,6 +236,9 @@ function toClassicNativeMessage(event) {
236
236
  totalParts: event.totalParts,
237
237
  timestampMs: event.timestampMs,
238
238
  };
239
+ case 'engine_debug':
240
+ // SDK-only QA stream — no classic WebView equivalent; skip onMessage.
241
+ return null;
239
242
  default: {
240
243
  const _exhaustive = event;
241
244
  return { type: 'unknown', event: _exhaustive };
package/lib/index.d.ts CHANGED
@@ -19,10 +19,10 @@ export { UsageTracker, TrackError } from './api/track';
19
19
  export type { TrackRequest, TrackResponse, UsageTrackerOptions } from './api/track';
20
20
  export { RuntimeCache } from './runtime/RuntimeCache';
21
21
  export type { PoseRuntimeParts, RuntimeCacheOptions, RuntimeCacheProgress, } from './runtime/RuntimeCache';
22
- export { getOnlineRuntimeParts, getOnlineRuntimeVersion, defaultOnlineRuntimeUrls, ONLINE_TFJS_VERSION, } from './backends/webview/onlineRuntime';
22
+ export { getOnlineRuntimeParts, getOnlineRuntimeVersion, defaultOnlineRuntimeUrls, defaultPoseDetectionCdnUrl, ONLINE_TFJS_VERSION, ONLINE_POSE_DETECTION_VERSION, } from './backends/webview/onlineRuntime';
23
23
  export type { GetOnlineRuntimeOptions, OnlineRuntimeParts, OnlineRuntimeUrls, } from './backends/webview/onlineRuntime';
24
24
  export { DEFAULT_MOVENET_LIGHTNING_URL, originFromModelUrl, resolvePoseModel, } from './models/poseModels';
25
- export type { PoseModelAlias, ResolvePoseModelOptions, ResolvedPoseModel, } from './models/poseModels';
25
+ export type { PoseModelAlias, PoseModelKind, ResolvePoseModelOptions, ResolvedPoseModel, } from './models/poseModels';
26
26
  export { PoseTrackerClient } from './client';
27
27
  export type { PoseTrackerClientOptions, PreferredBackend, StartExerciseOptions } from './client';
28
28
  export type { ColdStartMode, PreloadOptions } from './types/preload';
package/lib/index.js CHANGED
@@ -28,8 +28,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
28
28
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
29
29
  };
30
30
  Object.defineProperty(exports, "__esModule", { value: true });
31
- exports.logPlatformBanner = exports.logFrameStats = exports.logAccelerationReport = exports.defaultDiagnosticLogger = exports.createNativeFileStore = exports.createMemoryFileStore = exports.createBlobUtilFileStore = exports.createRnfsFileStore = exports.createExpoFileStore = exports.EngineLoader = exports.isExpoGo = exports.PoseCameraView = exports.VISION_JOINT_TO_SDK = exports.poseFromVisionPluginResult = exports.VISION_BODY_POSE_PLUGIN_NAME = exports.isVisionPoseCameraAvailable = exports.isVisionBackendAvailable = exports.VisionUnavailableError = exports.VisionPoseBackend = exports.POSETRACKER_LOGO_DATA_URL = exports.POSE_HTML_BUILD = exports.DEFAULT_LOADING_TEXT = exports.buildPoseHtml = exports.WebViewPoseView = exports.isWebViewPoseBackend = exports.WebViewPoseBackend = exports.usePoseTracker = exports.PoseTrackerProvider = exports.SkeletonFetchError = exports.fetchSkeletonDefinition = exports.DEFAULT_SKELETON_DEFINITION = exports.PoseTrackerClient = exports.resolvePoseModel = exports.originFromModelUrl = exports.DEFAULT_MOVENET_LIGHTNING_URL = exports.ONLINE_TFJS_VERSION = exports.defaultOnlineRuntimeUrls = exports.getOnlineRuntimeVersion = exports.getOnlineRuntimeParts = exports.RuntimeCache = exports.TrackError = exports.UsageTracker = exports.resolveExerciseName = exports.findExerciseByIdOrAlias = exports.EXERCISE_ALIASES = exports.SDK_VERSION = exports.SDK_NAME = exports.DEFAULT_BASE_URL = exports.ConfigureError = exports.configure = void 0;
32
- exports.resolveFeatures = exports.shouldShowWatermark = exports.isPaidPlan = exports.freeBlockedFeatures = exports.featureNotSupportedMessage = exports.COMBINED_REFERENCE_EXERCISE_MESSAGE = exports.INVALID_TOKEN_MESSAGE = exports.FREE_PLAN_FEATURES_MESSAGE = exports.DEFAULT_FEATURES = exports.RuntimeGuard = exports.scoreDeviceCapability = exports.profileFromScore = exports.isMaliRenderer = exports.ENABLE_MALI_HARD_CAP = exports.CAPTURE_CONSTRAINT_MODE = exports.ANDROID_SOFT_CAP_PROFILE = exports.ANDROID_PREPROCESS_PATH = exports.ANDROID_PERF_DEBUG = exports.ANDROID_MIN_TARGET_FPS = exports.ANDROID_INFER_FRAME_SKIP = exports.profileFromWarmupMedianMs = exports.nextLowerQualityProfile = exports.minMedianMsForMinTarget = exports.lowerQualityProfile = exports.isQualityProfileId = exports.getQualityProfile = exports.getMinTargetFps = exports.getIdealFpsRange = exports.getCriticalFpsThreshold = exports.estimatedFpsFromMedianMs = exports.currentQualityPlatform = exports.TARGET_MEDIAN_MS = exports.TARGET_FPS = exports.QUALITY_PROFILES = exports.QUALITY_LADDER = exports.MIN_TARGET_FPS_IOS = exports.MIN_TARGET_FPS_DEFAULT = exports.MIN_TARGET_FPS_ANDROID = exports.QUALITY_SETTLE_MS = exports.LOW_FPS_STREAK_BEFORE_DOWNGRADE = exports.IDEAL_FPS_RANGE_IOS = exports.IDEAL_FPS_RANGE_ANDROID = exports.CRITICAL_FPS_THRESHOLD = exports.AdaptiveQualityController = exports.toClassicNativeMessage = void 0;
31
+ exports.logAccelerationReport = exports.defaultDiagnosticLogger = exports.createNativeFileStore = exports.createMemoryFileStore = exports.createBlobUtilFileStore = exports.createRnfsFileStore = exports.createExpoFileStore = exports.EngineLoader = exports.isExpoGo = exports.PoseCameraView = exports.VISION_JOINT_TO_SDK = exports.poseFromVisionPluginResult = exports.VISION_BODY_POSE_PLUGIN_NAME = exports.isVisionPoseCameraAvailable = exports.isVisionBackendAvailable = exports.VisionUnavailableError = exports.VisionPoseBackend = exports.POSETRACKER_LOGO_DATA_URL = exports.POSE_HTML_BUILD = exports.DEFAULT_LOADING_TEXT = exports.buildPoseHtml = exports.WebViewPoseView = exports.isWebViewPoseBackend = exports.WebViewPoseBackend = exports.usePoseTracker = exports.PoseTrackerProvider = exports.SkeletonFetchError = exports.fetchSkeletonDefinition = exports.DEFAULT_SKELETON_DEFINITION = exports.PoseTrackerClient = exports.resolvePoseModel = exports.originFromModelUrl = exports.DEFAULT_MOVENET_LIGHTNING_URL = exports.ONLINE_POSE_DETECTION_VERSION = exports.ONLINE_TFJS_VERSION = exports.defaultPoseDetectionCdnUrl = exports.defaultOnlineRuntimeUrls = exports.getOnlineRuntimeVersion = exports.getOnlineRuntimeParts = exports.RuntimeCache = exports.TrackError = exports.UsageTracker = exports.resolveExerciseName = exports.findExerciseByIdOrAlias = exports.EXERCISE_ALIASES = exports.SDK_VERSION = exports.SDK_NAME = exports.DEFAULT_BASE_URL = exports.ConfigureError = exports.configure = void 0;
32
+ exports.resolveFeatures = exports.shouldShowWatermark = exports.isPaidPlan = exports.freeBlockedFeatures = exports.featureNotSupportedMessage = exports.COMBINED_REFERENCE_EXERCISE_MESSAGE = exports.INVALID_TOKEN_MESSAGE = exports.FREE_PLAN_FEATURES_MESSAGE = exports.DEFAULT_FEATURES = exports.RuntimeGuard = exports.scoreDeviceCapability = exports.profileFromScore = exports.isMaliRenderer = exports.ENABLE_MALI_HARD_CAP = exports.CAPTURE_CONSTRAINT_MODE = exports.ANDROID_SOFT_CAP_PROFILE = exports.ANDROID_PREPROCESS_PATH = exports.ANDROID_PERF_DEBUG = exports.ANDROID_MIN_TARGET_FPS = exports.ANDROID_INFER_FRAME_SKIP = exports.profileFromWarmupMedianMs = exports.nextLowerQualityProfile = exports.minMedianMsForMinTarget = exports.lowerQualityProfile = exports.isQualityProfileId = exports.getQualityProfile = exports.getMinTargetFps = exports.getIdealFpsRange = exports.getCriticalFpsThreshold = exports.estimatedFpsFromMedianMs = exports.currentQualityPlatform = exports.TARGET_MEDIAN_MS = exports.TARGET_FPS = exports.QUALITY_PROFILES = exports.QUALITY_LADDER = exports.MIN_TARGET_FPS_IOS = exports.MIN_TARGET_FPS_DEFAULT = exports.MIN_TARGET_FPS_ANDROID = exports.QUALITY_SETTLE_MS = exports.LOW_FPS_STREAK_BEFORE_DOWNGRADE = exports.IDEAL_FPS_RANGE_IOS = exports.IDEAL_FPS_RANGE_ANDROID = exports.CRITICAL_FPS_THRESHOLD = exports.AdaptiveQualityController = exports.toClassicNativeMessage = exports.logPlatformBanner = exports.logFrameStats = void 0;
33
33
  // Handshake + usage tracking
34
34
  var configure_1 = require("./api/configure");
35
35
  Object.defineProperty(exports, "configure", { enumerable: true, get: function () { return configure_1.configure; } });
@@ -51,7 +51,9 @@ var onlineRuntime_1 = require("./backends/webview/onlineRuntime");
51
51
  Object.defineProperty(exports, "getOnlineRuntimeParts", { enumerable: true, get: function () { return onlineRuntime_1.getOnlineRuntimeParts; } });
52
52
  Object.defineProperty(exports, "getOnlineRuntimeVersion", { enumerable: true, get: function () { return onlineRuntime_1.getOnlineRuntimeVersion; } });
53
53
  Object.defineProperty(exports, "defaultOnlineRuntimeUrls", { enumerable: true, get: function () { return onlineRuntime_1.defaultOnlineRuntimeUrls; } });
54
+ Object.defineProperty(exports, "defaultPoseDetectionCdnUrl", { enumerable: true, get: function () { return onlineRuntime_1.defaultPoseDetectionCdnUrl; } });
54
55
  Object.defineProperty(exports, "ONLINE_TFJS_VERSION", { enumerable: true, get: function () { return onlineRuntime_1.ONLINE_TFJS_VERSION; } });
56
+ Object.defineProperty(exports, "ONLINE_POSE_DETECTION_VERSION", { enumerable: true, get: function () { return onlineRuntime_1.ONLINE_POSE_DETECTION_VERSION; } });
55
57
  var poseModels_1 = require("./models/poseModels");
56
58
  Object.defineProperty(exports, "DEFAULT_MOVENET_LIGHTNING_URL", { enumerable: true, get: function () { return poseModels_1.DEFAULT_MOVENET_LIGHTNING_URL; } });
57
59
  Object.defineProperty(exports, "originFromModelUrl", { enumerable: true, get: function () { return poseModels_1.originFromModelUrl; } });
@@ -5,9 +5,9 @@
5
5
  * MoveNet SinglePose Lightning hosted at app.posetracker.com (same URL as
6
6
  * `usePoseDetection` / tracking WebView `modelUrl`).
7
7
  *
8
- * BlazePose is accepted as a `model` alias for API parity but is not wired
9
- * in the RN WebView yet (needs MediaPipe). Pass a TF.js `modelUrl` for any
10
- * custom graph model.
8
+ * BlazePose loads via CDN `@tensorflow-models/pose-detection` inside the
9
+ * WebView (no graph `modelUrl`). Pass a TF.js `modelUrl` for any custom
10
+ * graph model.
11
11
  */
12
12
  /** Production MoveNet SinglePose Lightning topology (Front default). */
13
13
  export declare const DEFAULT_MOVENET_LIGHTNING_URL = "https://app.posetracker.com/scripts/tmp_model_to_remove.json";
@@ -22,16 +22,18 @@ export interface ResolvePoseModelOptions {
22
22
  /** Explicit TF.js graph-model topology URL (weights resolve as siblings). */
23
23
  modelUrl?: string;
24
24
  }
25
+ export type PoseModelKind = 'movenet-graph' | 'blazepose' | 'custom-graph' | 'unsupported';
25
26
  export interface ResolvedPoseModel {
26
27
  /** Stable id reported in diagnostics / track params. */
27
28
  modelId: string;
28
- /** TF.js `loadGraphModel` URL, or null when unsupported without override. */
29
+ kind: PoseModelKind;
30
+ /** TF.js `loadGraphModel` URL when kind is movenet-graph or custom-graph. */
29
31
  modelUrl: string | null;
30
- /** Human-readable reason when {@link modelUrl} is null. */
32
+ /** Human-readable reason when kind is unsupported. */
31
33
  unsupportedReason?: string;
32
34
  }
33
35
  /**
34
- * Resolve which TF.js graph model the light WebView should fetch.
36
+ * Resolve which online pose model the light WebView should run.
35
37
  * Default: MoveNet SinglePose Lightning (product URL).
36
38
  */
37
39
  export declare function resolvePoseModel(options?: ResolvePoseModelOptions): ResolvedPoseModel;
@@ -6,9 +6,9 @@
6
6
  * MoveNet SinglePose Lightning hosted at app.posetracker.com (same URL as
7
7
  * `usePoseDetection` / tracking WebView `modelUrl`).
8
8
  *
9
- * BlazePose is accepted as a `model` alias for API parity but is not wired
10
- * in the RN WebView yet (needs MediaPipe). Pass a TF.js `modelUrl` for any
11
- * custom graph model.
9
+ * BlazePose loads via CDN `@tensorflow-models/pose-detection` inside the
10
+ * WebView (no graph `modelUrl`). Pass a TF.js `modelUrl` for any custom
11
+ * graph model.
12
12
  */
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
14
  exports.DEFAULT_MOVENET_LIGHTNING_URL = void 0;
@@ -17,7 +17,7 @@ exports.originFromModelUrl = originFromModelUrl;
17
17
  /** Production MoveNet SinglePose Lightning topology (Front default). */
18
18
  exports.DEFAULT_MOVENET_LIGHTNING_URL = 'https://app.posetracker.com/scripts/tmp_model_to_remove.json';
19
19
  /**
20
- * Resolve which TF.js graph model the light WebView should fetch.
20
+ * Resolve which online pose model the light WebView should run.
21
21
  * Default: MoveNet SinglePose Lightning (product URL).
22
22
  */
23
23
  function resolvePoseModel(options = {}) {
@@ -28,27 +28,28 @@ function resolvePoseModel(options = {}) {
28
28
  const alias = typeof options.model === 'string' && options.model.trim().length > 0
29
29
  ? options.model.trim()
30
30
  : 'custom';
31
- return { modelId: alias, modelUrl: explicit };
31
+ return { modelId: alias, kind: 'custom-graph', modelUrl: explicit };
32
32
  }
33
33
  const key = (options.model ?? 'movenet').trim().toLowerCase();
34
34
  if (key === 'movenet' || key === 'movenet-singlepose-lightning' || key === 'lightning') {
35
35
  return {
36
36
  modelId: 'movenet-singlepose-lightning',
37
+ kind: 'movenet-graph',
37
38
  modelUrl: exports.DEFAULT_MOVENET_LIGHTNING_URL,
38
39
  };
39
40
  }
40
41
  if (key === 'blazepose') {
41
42
  return {
42
43
  modelId: 'blazepose',
44
+ kind: 'blazepose',
43
45
  modelUrl: null,
44
- unsupportedReason: 'BlazePose is not wired in the light RN WebView yet (needs MediaPipe). ' +
45
- 'Use model="movenet" or pass a TF.js graph modelUrl.',
46
46
  };
47
47
  }
48
48
  return {
49
49
  modelId: key,
50
+ kind: 'unsupported',
50
51
  modelUrl: null,
51
- unsupportedReason: `Unknown model "${options.model}". Use "movenet" (default) or pass modelUrl.`,
52
+ unsupportedReason: `Unknown model "${options.model}". Use "movenet" (default), "blazepose", or pass modelUrl.`,
52
53
  };
53
54
  }
54
55
  /** Origin used as WebView `baseUrl` so model fetches are same-origin when possible. */
@@ -1,2 +1,2 @@
1
1
  /** AUTO-GENERATED by scripts/sync-sdk-version.mjs — do not edit. */
2
- export declare const SDK_VERSION = "0.1.0";
2
+ export declare const SDK_VERSION = "0.2.1";
package/lib/sdkVersion.js CHANGED
@@ -2,4 +2,4 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SDK_VERSION = void 0;
4
4
  /** AUTO-GENERATED by scripts/sync-sdk-version.mjs — do not edit. */
5
- exports.SDK_VERSION = '0.1.0';
5
+ exports.SDK_VERSION = '0.2.1';
@@ -23,7 +23,7 @@ export type PoseTrackerStatus = 'idle' | 'configuring' | 'downloading' | 'warmin
23
23
  * restarting the camera pipeline, via `configure()` or a preload retry.
24
24
  */
25
25
  export type PoseTrackerMode = 'keypoints-only' | 'full-engine';
26
- export type InitializationStep = 'configuring' | 'downloading' | 'warming' | 'accessing_webcam' | 'loading_pose_model' | 'ready';
26
+ export type InitializationStep = 'configuring' | 'downloading' | 'warming' | 'accessing_webcam' | 'loading_media' | 'loading_pose_model' | 'ready';
27
27
  export interface InitializationEvent {
28
28
  type: 'initialization';
29
29
  /**
@@ -154,6 +154,40 @@ export interface ProgressionEvent {
154
154
  value: number;
155
155
  timestampMs: number;
156
156
  }
157
+ /**
158
+ * Throttled movement / rep-FSM snapshot for QA (engine ≥ 1.2.4).
159
+ * Enable via `PoseTrackerClientOptions.debugEngine` or `startExercise(..., { debug: true })`.
160
+ */
161
+ export interface EngineDebugEvent {
162
+ type: 'engine_debug';
163
+ exerciseId: string;
164
+ engineVersion: string;
165
+ ready: boolean;
166
+ postureHint: string;
167
+ direction?: string;
168
+ missingKeypoints: string[];
169
+ stepIndex: number;
170
+ stepCount: number;
171
+ stepPosition: number | null;
172
+ stepProgression: number;
173
+ progression: number;
174
+ /** Raw FSM rep count (before minGrade gate). */
175
+ rawCount: number;
176
+ /** Public counter after minGrade gate. */
177
+ publicCount: number;
178
+ difficulty: string;
179
+ minGrade: MinGrade | null;
180
+ pointOfView: 'low' | 'up';
181
+ computeError?: string;
182
+ lastSkipReason: string | null;
183
+ posesProcessed: number;
184
+ visibleKeypoints: number;
185
+ knees: {
186
+ left: number | null;
187
+ right: number | null;
188
+ };
189
+ timestampMs: number;
190
+ }
157
191
  export interface RecommendationsEvent {
158
192
  type: 'recommendations';
159
193
  /** Localized form advice strings. */
@@ -309,7 +343,7 @@ export interface RuntimeDownloadProgressEvent {
309
343
  partBytes: number;
310
344
  timestampMs: number;
311
345
  }
312
- export type PoseTrackerEvent = InitializationEvent | ErrorEvent | WarningEvent | RuntimeDownloadProgressEvent | KeypointsEvent | AnglesEvent | CounterEvent | PostureEvent | ProgressionEvent | RecommendationsEvent | FormScoreEvent | ExerciseSummaryEvent | JumpCalibrationEvent | JumpStartedEvent | JumpHeightEvent | JumpDiscardedEvent | JumpResultEvent | JumpSummaryEvent | QualityChangedEvent | PerformanceWarningEvent;
346
+ export type PoseTrackerEvent = InitializationEvent | ErrorEvent | WarningEvent | RuntimeDownloadProgressEvent | KeypointsEvent | AnglesEvent | CounterEvent | PostureEvent | ProgressionEvent | EngineDebugEvent | RecommendationsEvent | FormScoreEvent | ExerciseSummaryEvent | JumpCalibrationEvent | JumpStartedEvent | JumpHeightEvent | JumpDiscardedEvent | JumpResultEvent | JumpSummaryEvent | QualityChangedEvent | PerformanceWarningEvent;
313
347
  export type PoseTrackerEventType = PoseTrackerEvent['type'];
314
348
  export type PoseTrackerEventListener = (event: PoseTrackerEvent) => void;
315
349
  /**
@@ -325,6 +359,7 @@ export interface PoseTrackerCallbacks {
325
359
  onCounter?: (event: CounterEvent) => void;
326
360
  onPosture?: (event: PostureEvent) => void;
327
361
  onProgression?: (event: ProgressionEvent) => void;
362
+ onEngineDebug?: (event: EngineDebugEvent) => void;
328
363
  onRecommendations?: (event: RecommendationsEvent) => void;
329
364
  onFormScore?: (event: FormScoreEvent) => void;
330
365
  onExerciseSummary?: (event: ExerciseSummaryEvent) => void;
@@ -11,8 +11,8 @@
11
11
  * - `keypoints` DURING an exercise session requires a paid plan too
12
12
  * (free may stream keypoints only in pose-only mode, i.e. no exercise).
13
13
  * - `blazepose` / `poseEngine` / `mediapipeModel` / `poseBackend` /
14
- * `runInWorker` are NOT part of this SDK (MoveNet Lightning only) and are
15
- * rejected with a clear error.
14
+ * `runInWorker` are NOT feature flags here — select BlazePose with
15
+ * `options.model = 'blazepose'` instead of `{ features: { blazepose: true } }`.
16
16
  */
17
17
  import type { MinGrade } from './events';
18
18
  export interface PoseTrackerFeatures {
@@ -12,8 +12,8 @@
12
12
  * - `keypoints` DURING an exercise session requires a paid plan too
13
13
  * (free may stream keypoints only in pose-only mode, i.e. no exercise).
14
14
  * - `blazepose` / `poseEngine` / `mediapipeModel` / `poseBackend` /
15
- * `runInWorker` are NOT part of this SDK (MoveNet Lightning only) and are
16
- * rejected with a clear error.
15
+ * `runInWorker` are NOT feature flags here — select BlazePose with
16
+ * `options.model = 'blazepose'` instead of `{ features: { blazepose: true } }`.
17
17
  */
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
19
  exports.COMBINED_REFERENCE_EXERCISE_MESSAGE = exports.INVALID_TOKEN_MESSAGE = exports.FREE_PLAN_FEATURES_MESSAGE = exports.DEFAULT_FEATURES = void 0;
@@ -38,12 +38,12 @@ const KNOWN_FEATURE_KEYS = new Set([
38
38
  'minGrade',
39
39
  ]);
40
40
  /**
41
- * WebView params that this SDK deliberately does NOT ship (one SDK, one
42
- * model: MoveNet Lightning). Passing them (possible from untyped JS hosts)
43
- * produces a clear `feature_not_supported` error.
41
+ * WebView params that are NOT feature flags in this SDK.
42
+ * Note: `options.model = 'blazepose'` is supported on light; passing
43
+ * `{ blazepose: true }` inside `features` is rejected (WebView query parity).
44
44
  */
45
45
  const UNSUPPORTED_FEATURE_HINTS = {
46
- blazepose: 'BlazePose',
46
+ blazepose: 'BlazePose as a features flag (use options.model = "blazepose" instead)',
47
47
  poseEngine: 'pose engine selection (MediaPipe/PoseLandmarker)',
48
48
  mediapipeModel: 'MediaPipe model selection',
49
49
  poseBackend: 'pose backend selection',
@@ -62,9 +62,8 @@ exports.COMBINED_REFERENCE_EXERCISE_MESSAGE = 'You cannot combine reference & ex
62
62
  /** SDK-specific: the requested WebView feature does not exist in this SDK. */
63
63
  function featureNotSupportedMessage(key) {
64
64
  const hint = UNSUPPORTED_FEATURE_HINTS[key] ?? `'${key}'`;
65
- return (`The '${key}' option (${hint}) is not available in this SDK. ` +
66
- 'This SDK ships MoveNet Lightning only — pose engine and model cannot be changed. ' +
67
- 'Remove the option, or use the PoseTracker WebView product if you need it. ' +
65
+ return (`The '${key}' option (${hint}) is not available as a features flag. ` +
66
+ 'Remove the option, or use options.model / modelUrl for model selection. ' +
68
67
  '(visit: https://posetracker.gitbook.io/posetracker-api/tracking-endpoint)');
69
68
  }
70
69
  // ---------------------------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pose-tracker/react-native-pose-estimation-light",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Human pose estimation SDK for React Native (light / online) — MoveNet Lightning via CDN + model URL each session, no bundled weights. iOS & Android (Expo Go). Optional API-key exercise engine.",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -281,6 +281,9 @@ export function usePoseTracker(callbacks: PoseTrackerCallbacks = {}): PoseTracke
281
281
  case 'progression':
282
282
  cb.onProgression?.(event);
283
283
  break;
284
+ case 'engine_debug':
285
+ cb.onEngineDebug?.(event);
286
+ break;
284
287
  case 'recommendations':
285
288
  cb.onRecommendations?.(event);
286
289
  break;
@@ -86,7 +86,7 @@ export type WebViewPoseMessage =
86
86
  type: 'initialization';
87
87
  message: string;
88
88
  ready?: boolean;
89
- step?: 'accessing_webcam' | 'loading_pose_model' | 'ready';
89
+ step?: 'accessing_webcam' | 'loading_media' | 'loading_pose_model' | 'ready';
90
90
  }
91
91
  | { type: 'warning'; message: string; code?: string }
92
92
  | { type: 'diag'; message: string }
@@ -5,22 +5,29 @@
5
5
  * TF.js + WASM backends load from a CDN; MoveNet (or a custom graph model)
6
6
  * loads from a URL on every WebView boot — same idea as the historical
7
7
  * PoseTracker Front tracking page.
8
+ *
9
+ * BlazePose: appends `@tensorflow-models/pose-detection` CDN after TF.js
10
+ * (no graph `modelUrl`); the page runtime calls `createDetector`.
8
11
  */
9
12
 
10
13
  import { POSE_RUNTIME_JS, POSE_RUNTIME_VERSION } from './poseRuntimeSource';
11
14
  import {
12
15
  resolvePoseModel,
13
16
  type PoseModelAlias,
17
+ type PoseModelKind,
14
18
  type ResolvedPoseModel,
15
19
  } from '../../models/poseModels';
16
20
 
17
21
  /** TF.js version pinned for CDN scripts (matches offline SDK build). */
18
22
  export const ONLINE_TFJS_VERSION = '4.22.0';
19
23
 
24
+ /** pose-detection UMD pin (BlazePose). */
25
+ export const ONLINE_POSE_DETECTION_VERSION = '2.1.3';
26
+
20
27
  const JSDELIVR = 'https://cdn.jsdelivr.net/npm';
21
28
 
22
29
  export interface OnlineRuntimeUrls {
23
- /** Ordered <script src> list (core → converter → webgl → wasm). */
30
+ /** Ordered <script src> list (core → converter → webgl → wasm [→ pose-detection]). */
24
31
  tfjsScriptUrls: string[];
25
32
  /** Directory passed to `tf.wasm.setWasmPaths`. */
26
33
  tfjsWasmPath: string;
@@ -30,7 +37,9 @@ export interface OnlineRuntimeParts {
30
37
  version: string;
31
38
  delivery: 'online';
32
39
  modelId: string;
33
- modelUrl: string;
40
+ /** Graph kinds only; null for BlazePose. */
41
+ modelUrl: string | null;
42
+ modelKind: Exclude<PoseModelKind, 'unsupported'>;
34
43
  tfjsScriptUrls: string[];
35
44
  tfjsWasmPath: string;
36
45
  /** Thin page runtime shipped in the package (~50 KB). */
@@ -47,6 +56,8 @@ export interface GetOnlineRuntimeOptions {
47
56
  tfjsCdnBase?: string;
48
57
  /** Override TF.js version segment in CDN URLs. */
49
58
  tfjsVersion?: string;
59
+ /** Override pose-detection version (BlazePose CDN). */
60
+ poseDetectionVersion?: string;
50
61
  }
51
62
 
52
63
  export function defaultOnlineRuntimeUrls(
@@ -65,25 +76,45 @@ export function defaultOnlineRuntimeUrls(
65
76
  };
66
77
  }
67
78
 
79
+ export function defaultPoseDetectionCdnUrl(
80
+ cdnBase: string = JSDELIVR,
81
+ version: string = ONLINE_POSE_DETECTION_VERSION,
82
+ ): string {
83
+ const base = cdnBase.replace(/\/$/, '');
84
+ return `${base}/@tensorflow-models/pose-detection@${version}/dist/pose-detection.min.js`;
85
+ }
86
+
68
87
  /**
69
88
  * Build the online runtime descriptor used by {@link buildPoseHtml}.
70
- * Throws when the selected model cannot be loaded (e.g. blazepose without URL).
89
+ * Throws when the selected model cannot be loaded.
71
90
  */
72
91
  export function getOnlineRuntimeParts(options: GetOnlineRuntimeOptions = {}): OnlineRuntimeParts {
73
92
  const resolved: ResolvedPoseModel = resolvePoseModel({
74
93
  model: options.model,
75
94
  modelUrl: options.modelUrl,
76
95
  });
77
- if (!resolved.modelUrl) {
96
+ if (resolved.kind === 'unsupported') {
78
97
  throw new Error(resolved.unsupportedReason ?? `Model "${resolved.modelId}" is unavailable.`);
79
98
  }
99
+ if (resolved.kind !== 'blazepose' && !resolved.modelUrl) {
100
+ throw new Error(resolved.unsupportedReason ?? `Model "${resolved.modelId}" is unavailable.`);
101
+ }
102
+
80
103
  const urls = defaultOnlineRuntimeUrls(options.tfjsCdnBase, options.tfjsVersion);
104
+ const scriptUrls = [...urls.tfjsScriptUrls];
105
+ if (resolved.kind === 'blazepose') {
106
+ scriptUrls.push(
107
+ defaultPoseDetectionCdnUrl(options.tfjsCdnBase, options.poseDetectionVersion),
108
+ );
109
+ }
110
+
81
111
  return {
82
112
  version: POSE_RUNTIME_VERSION,
83
113
  delivery: 'online',
84
114
  modelId: resolved.modelId,
85
115
  modelUrl: resolved.modelUrl,
86
- tfjsScriptUrls: urls.tfjsScriptUrls,
116
+ modelKind: resolved.kind,
117
+ tfjsScriptUrls: scriptUrls,
87
118
  tfjsWasmPath: urls.tfjsWasmPath,
88
119
  runtimeJs: POSE_RUNTIME_JS,
89
120
  };