@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
@@ -2,9 +2,10 @@
2
2
  * WebView HTML assembler (light / online).
3
3
  *
4
4
  * Loads TF.js from CDN script tags and MoveNet (or a custom graph model)
5
- * from {@link OnlineRuntimeParts.modelUrl}. The thin page runtime is still
6
- * inlined from the package. Same camera / adaptive quality / postMessage
7
- * events as the offline SDK.
5
+ * from {@link OnlineRuntimeParts.modelUrl}. For BlazePose, also loads
6
+ * `@tensorflow-models/pose-detection` (no graph URL). The thin page runtime
7
+ * is still inlined from the package. Same camera / adaptive quality /
8
+ * postMessage events as the offline SDK.
8
9
  */
9
10
 
10
11
  import { Platform } from 'react-native';
@@ -21,7 +22,7 @@ import { POSETRACKER_LOGO_DATA_URL } from './brandAssets';
21
22
  import type { OnlineRuntimeParts } from './onlineRuntime';
22
23
 
23
24
  /** Bumped on every assembler-path change — appears in WebView diag logs. */
24
- export const POSE_HTML_BUILD = '20260811-onlineLight-localhostBase';
25
+ export const POSE_HTML_BUILD = '20260812-onlineLight-mediaSources';
25
26
 
26
27
  /** Default boot overlay copy (WebView `loading_message` parity). */
27
28
  export const DEFAULT_LOADING_TEXT = 'AI Loading';
@@ -76,16 +77,23 @@ export interface PoseHtmlOptions {
76
77
  showWatermark?: boolean;
77
78
  /** Show the technical `#hud` overlay (FPS / backend). Default false. */
78
79
  debugHud?: boolean;
80
+ /**
81
+ * Input source mode. Default `camera`.
82
+ * For `video` / `image`, pass a URI the WebView can load (`sourceUrl`).
83
+ */
84
+ sourceType?: 'camera' | 'video' | 'image';
85
+ /** file://, content://, https://, or data: URL for video/image modes. */
86
+ sourceUrl?: string;
79
87
  }
80
88
 
81
89
  const PAGE_CSS = `
82
90
  html, body { margin:0; padding:0; width:100%; height:100%; background:#000; overflow:hidden; }
83
91
  #wrap { position:relative; width:100%; height:100%; background:#000; }
84
- video, canvas {
92
+ video, img#still, canvas {
85
93
  position:absolute; inset:0; width:100%; height:100%;
86
94
  object-fit:cover; opacity:0; transition:opacity 180ms ease-out;
87
95
  }
88
- video { z-index:1; background:#000; }
96
+ video, img#still { z-index:1; background:#000; }
89
97
  canvas { z-index:2; pointer-events:none; }
90
98
  #boot {
91
99
  position:absolute; inset:0; z-index:4;
@@ -204,11 +212,17 @@ export function buildPoseHtml(parts: OnlineRuntimeParts, options?: PoseHtmlOptio
204
212
  loadingText,
205
213
  showWatermark: watermarkOn,
206
214
  debugHud,
215
+ sourceType:
216
+ options?.sourceType === 'video' || options?.sourceType === 'image'
217
+ ? options.sourceType
218
+ : 'camera',
219
+ sourceUrl: typeof options?.sourceUrl === 'string' ? options.sourceUrl : null,
207
220
  captureConstraintMode: CAPTURE_CONSTRAINT_MODE,
208
221
  capturePriority,
209
222
  platform: Platform.OS,
210
223
  modelUrl: parts.modelUrl,
211
224
  modelId: parts.modelId,
225
+ modelKind: parts.modelKind,
212
226
  tfjsWasmPath: parts.tfjsWasmPath,
213
227
  inferFrameSkip:
214
228
  isAndroid && ANDROID_INFER_FRAME_SKIP > 0 ? ANDROID_INFER_FRAME_SKIP : 0,
@@ -228,6 +242,7 @@ export function buildPoseHtml(parts: OnlineRuntimeParts, options?: PoseHtmlOptio
228
242
  '<style>', PAGE_CSS, '</style></head><body>',
229
243
  '<div id="wrap">',
230
244
  '<video id="video" playsinline muted autoplay></video>',
245
+ '<img id="still" alt="" draggable="false" />',
231
246
  '<canvas id="overlay"></canvas>',
232
247
  '<div id="wm">',
233
248
  '<div class="wm-powered">powered by</div>',
@@ -247,6 +262,8 @@ export function buildPoseHtml(parts: OnlineRuntimeParts, options?: PoseHtmlOptio
247
262
  '<script>window.__PT_BUILD=', JSON.stringify(`${POSE_HTML_BUILD}/${parts.version}`),
248
263
  ';window.__PT_CONFIG=', config,
249
264
  ';window.__PT_MODEL_URL=', JSON.stringify(parts.modelUrl),
265
+ ';window.__PT_MODEL_ID=', JSON.stringify(parts.modelId),
266
+ ';window.__PT_MODEL_KIND=', JSON.stringify(parts.modelKind),
250
267
  ';window.__PT_WASM_PATH=', JSON.stringify(parts.tfjsWasmPath),
251
268
  ';window.__PT_MODEL_JSON=null',
252
269
  ';window.__PT_WEIGHTS_B64=null',
@@ -120,6 +120,18 @@ export interface WebViewPoseViewProps {
120
120
  * Fetched via `GET /api/sdk/skeleton`. Ignored if `skeletonDef` is set.
121
121
  */
122
122
  skeletonUuid?: string | null;
123
+ /**
124
+ * Input source. Default `camera` (getUserMedia).
125
+ * For `video` / `image`, the host app picks a file and passes `sourceUri`
126
+ * (file://, content://, https, data URL) or `sourceBase64`.
127
+ */
128
+ source?: 'camera' | 'video' | 'image';
129
+ /** URI injected into the WebView for video/image modes. */
130
+ sourceUri?: string;
131
+ /** Optional base64 payload (without data: prefix); paired with sourceMime. */
132
+ sourceBase64?: string;
133
+ /** MIME for sourceBase64 (default image/jpeg or video/mp4). */
134
+ sourceMime?: string;
123
135
  onPose?: (pose: Pose, stats: { inferenceTimeMs: number; timestampMs: number }) => void;
124
136
  children?: React.ReactNode;
125
137
  }
@@ -266,6 +278,8 @@ export function WebViewPoseView(props: WebViewPoseViewProps): React.ReactElement
266
278
  // injected via __PT_SET_SKELETON (avoids remounting on uuid fetch).
267
279
  skeletonDef: props.skeletonDef ?? null,
268
280
  capturePriority: client.getQualityState().capturePriority,
281
+ sourceType: props.source ?? 'camera',
282
+ sourceUrl: props.sourceUri,
269
283
  });
270
284
  }, [
271
285
  facingMode,
@@ -276,6 +290,8 @@ export function WebViewPoseView(props: WebViewPoseViewProps): React.ReactElement
276
290
  coldStart,
277
291
  loadingText,
278
292
  props.skeletonDef,
293
+ props.source,
294
+ props.sourceUri,
279
295
  ]);
280
296
 
281
297
  // Apply custom skeleton (uuid fetch or prop) without remounting the page.
@@ -307,6 +323,33 @@ export function WebViewPoseView(props: WebViewPoseViewProps): React.ReactElement
307
323
  );
308
324
  }, [isWebViewBackend, html, loadingText]);
309
325
 
326
+ // Live source switches after first HTML boot (CFG already has initial source).
327
+ const lastSourceHtmlRef = useRef<string | null>(null);
328
+ useEffect(() => {
329
+ if (!isWebViewBackend || !html) return;
330
+ if (lastSourceHtmlRef.current !== html) {
331
+ lastSourceHtmlRef.current = html;
332
+ return;
333
+ }
334
+ const type = props.source ?? 'camera';
335
+ const payload = JSON.stringify({
336
+ type,
337
+ url: props.sourceUri ?? null,
338
+ base64: props.sourceBase64 ?? null,
339
+ mime: props.sourceMime ?? null,
340
+ });
341
+ webRef.current?.injectJavaScript?.(
342
+ `window.__PT_SET_SOURCE && window.__PT_SET_SOURCE(${payload}); true;`,
343
+ );
344
+ }, [
345
+ isWebViewBackend,
346
+ html,
347
+ props.source,
348
+ props.sourceUri,
349
+ props.sourceBase64,
350
+ props.sourceMime,
351
+ ]);
352
+
310
353
  useEffect(() => {
311
354
  if (!(backend instanceof WebViewPoseBackend)) {
312
355
  return;
package/src/client.ts CHANGED
@@ -69,7 +69,6 @@ import {
69
69
  import {
70
70
  toClassicNativeMessage,
71
71
  type ClassicMessageListener,
72
- type ClassicNativeMessage,
73
72
  } from './events/classicMessage';
74
73
  import {
75
74
  FREE_PLAN_FEATURES_MESSAGE,
@@ -122,8 +121,8 @@ export interface PoseTrackerClientOptions extends ConfigureOptions {
122
121
  usageTracker?: UsageTracker;
123
122
  /**
124
123
  * Docs API `model` query parity (`movenet` default, `blazepose`, …).
125
- * BlazePose is not wired in the light WebView yet — use `modelUrl` for a
126
- * custom TF.js graph model. Ignored when {@link modelUrl} is set.
124
+ * BlazePose loads via CDN pose-detection in the WebView (heavier than
125
+ * MoveNet). Ignored when {@link modelUrl} is set.
127
126
  */
128
127
  model?: PoseModelAlias;
129
128
  /**
@@ -164,6 +163,12 @@ export interface PoseTrackerClientOptions extends ConfigureOptions {
164
163
  * triaging Android GPU issues in the field.
165
164
  */
166
165
  onDiagnostic?: (message: string) => void;
166
+ /**
167
+ * When true, exercise sessions request `engine_debug` snapshots from the
168
+ * remote engine (bundle ≥ 1.2.4). Overridable per call via
169
+ * `startExercise(id, { debug: true|false })`.
170
+ */
171
+ debugEngine?: boolean;
167
172
  }
168
173
 
169
174
  /**
@@ -181,6 +186,11 @@ export interface StartExerciseOptions {
181
186
  userHeightCm?: number;
182
187
  /** Device pitch in degrees — jump exercises compensate camera tilt. */
183
188
  devicePitchDeg?: number;
189
+ /**
190
+ * Request throttled `engine_debug` events for this session (movement FSM
191
+ * QA). Defaults to {@link PoseTrackerClientOptions.debugEngine}.
192
+ */
193
+ debug?: boolean;
184
194
  }
185
195
 
186
196
  export class PoseTrackerClient {
@@ -579,7 +589,7 @@ export class PoseTrackerClient {
579
589
  });
580
590
  this.options.onDiagnostic?.(
581
591
  `[posetracker-light] pose-runtime online version=${parts.version} ` +
582
- `modelId=${parts.modelId} modelUrl=${parts.modelUrl}`,
592
+ `modelId=${parts.modelId} modelKind=${parts.modelKind} modelUrl=${parts.modelUrl}`,
583
593
  );
584
594
  return parts;
585
595
  }
@@ -1040,12 +1050,14 @@ export class PoseTrackerClient {
1040
1050
  this.stopExercise();
1041
1051
  this.currentExerciseId = exerciseId;
1042
1052
  this.keypointsSuppressionLogged = false;
1053
+ const debug = options.debug ?? this.options.debugEngine === true;
1043
1054
  this.session = this.engine.createSession(
1044
1055
  {
1045
1056
  exercise,
1046
1057
  locale: this.options.locale ?? 'en',
1047
1058
  difficulty: options.difficulty,
1048
1059
  minGrade: this.features.minGrade ?? undefined,
1060
+ debug,
1049
1061
  features: {
1050
1062
  angles: this.features.angles,
1051
1063
  recommendations: this.features.recommendations,
@@ -1054,6 +1066,10 @@ export class PoseTrackerClient {
1054
1066
  },
1055
1067
  (event) => this.emitFromSession(event),
1056
1068
  );
1069
+ this.options.onDiagnostic?.(
1070
+ `[posetracker] startExercise id=${exercise.id} difficulty=${options.difficulty ?? 'medium'} ` +
1071
+ `debug=${debug} engine=${this.engine.version} minGrade=${this.features.minGrade ?? 'off'}`,
1072
+ );
1057
1073
  }
1058
1074
 
1059
1075
  /** Custom engine session (jump_analysis / air_time_jump). */
@@ -1280,7 +1296,11 @@ export class PoseTrackerClient {
1280
1296
  // Skip the classic-message conversion entirely (per-frame allocation).
1281
1297
  return;
1282
1298
  }
1283
- const classic: ClassicNativeMessage = toClassicNativeMessage(event);
1299
+ const classic = toClassicNativeMessage(event);
1300
+ if (classic == null) {
1301
+ // SDK-only events (e.g. engine_debug) have no classic equivalent.
1302
+ return;
1303
+ }
1284
1304
  this.messageListeners.forEach((l) => {
1285
1305
  try {
1286
1306
  l(classic);
@@ -39,6 +39,11 @@ export interface EngineSessionOptions {
39
39
  minGrade?: MinGrade;
40
40
  /** Emission flags — engines older than 1.1.0 ignore them (client re-filters). */
41
41
  features?: EngineSessionFeatures;
42
+ /**
43
+ * When true, the engine emits throttled `engine_debug` events (movement
44
+ * FSM snapshot). Requires engine bundle ≥ 1.2.4. Ignored by older engines.
45
+ */
46
+ debug?: boolean;
42
47
  }
43
48
 
44
49
  export interface EngineSession {
@@ -59,9 +59,11 @@ export function anglesToClassicTree(angles: AngleValue[]): {
59
59
  /**
60
60
  * Map a typed SDK event to a classic PoseTracker `sendDataToNative` payload.
61
61
  * Returns null for SDK-only events that have no classic equivalent and should
62
- * not be mirrored (none today — quality/performance map to error/warning).
62
+ * not be mirrored (e.g. `engine_debug` QA snapshots).
63
63
  */
64
- export function toClassicNativeMessage(event: PoseTrackerEvent): ClassicNativeMessage {
64
+ export function toClassicNativeMessage(
65
+ event: PoseTrackerEvent,
66
+ ): ClassicNativeMessage | null {
65
67
  switch (event.type) {
66
68
  case 'initialization':
67
69
  return {
@@ -247,6 +249,9 @@ export function toClassicNativeMessage(event: PoseTrackerEvent): ClassicNativeMe
247
249
  totalParts: event.totalParts,
248
250
  timestampMs: event.timestampMs,
249
251
  };
252
+ case 'engine_debug':
253
+ // SDK-only QA stream — no classic WebView equivalent; skip onMessage.
254
+ return null;
250
255
  default: {
251
256
  const _exhaustive: never = event;
252
257
  return { type: 'unknown', event: _exhaustive };
package/src/index.ts CHANGED
@@ -35,7 +35,9 @@ export {
35
35
  getOnlineRuntimeParts,
36
36
  getOnlineRuntimeVersion,
37
37
  defaultOnlineRuntimeUrls,
38
+ defaultPoseDetectionCdnUrl,
38
39
  ONLINE_TFJS_VERSION,
40
+ ONLINE_POSE_DETECTION_VERSION,
39
41
  } from './backends/webview/onlineRuntime';
40
42
  export type {
41
43
  GetOnlineRuntimeOptions,
@@ -49,6 +51,7 @@ export {
49
51
  } from './models/poseModels';
50
52
  export type {
51
53
  PoseModelAlias,
54
+ PoseModelKind,
52
55
  ResolvePoseModelOptions,
53
56
  ResolvedPoseModel,
54
57
  } from './models/poseModels';
@@ -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
 
13
13
  /** Production MoveNet SinglePose Lightning topology (Front default). */
@@ -32,17 +32,20 @@ export interface ResolvePoseModelOptions {
32
32
  modelUrl?: string;
33
33
  }
34
34
 
35
+ export type PoseModelKind = 'movenet-graph' | 'blazepose' | 'custom-graph' | 'unsupported';
36
+
35
37
  export interface ResolvedPoseModel {
36
38
  /** Stable id reported in diagnostics / track params. */
37
39
  modelId: string;
38
- /** TF.js `loadGraphModel` URL, or null when unsupported without override. */
40
+ kind: PoseModelKind;
41
+ /** TF.js `loadGraphModel` URL when kind is movenet-graph or custom-graph. */
39
42
  modelUrl: string | null;
40
- /** Human-readable reason when {@link modelUrl} is null. */
43
+ /** Human-readable reason when kind is unsupported. */
41
44
  unsupportedReason?: string;
42
45
  }
43
46
 
44
47
  /**
45
- * Resolve which TF.js graph model the light WebView should fetch.
48
+ * Resolve which online pose model the light WebView should run.
46
49
  * Default: MoveNet SinglePose Lightning (product URL).
47
50
  */
48
51
  export function resolvePoseModel(options: ResolvePoseModelOptions = {}): ResolvedPoseModel {
@@ -55,30 +58,30 @@ export function resolvePoseModel(options: ResolvePoseModelOptions = {}): Resolve
55
58
  typeof options.model === 'string' && options.model.trim().length > 0
56
59
  ? options.model.trim()
57
60
  : 'custom';
58
- return { modelId: alias, modelUrl: explicit };
61
+ return { modelId: alias, kind: 'custom-graph', modelUrl: explicit };
59
62
  }
60
63
 
61
64
  const key = (options.model ?? 'movenet').trim().toLowerCase();
62
65
  if (key === 'movenet' || key === 'movenet-singlepose-lightning' || key === 'lightning') {
63
66
  return {
64
67
  modelId: 'movenet-singlepose-lightning',
68
+ kind: 'movenet-graph',
65
69
  modelUrl: DEFAULT_MOVENET_LIGHTNING_URL,
66
70
  };
67
71
  }
68
72
  if (key === 'blazepose') {
69
73
  return {
70
74
  modelId: 'blazepose',
75
+ kind: 'blazepose',
71
76
  modelUrl: null,
72
- unsupportedReason:
73
- 'BlazePose is not wired in the light RN WebView yet (needs MediaPipe). ' +
74
- 'Use model="movenet" or pass a TF.js graph modelUrl.',
75
77
  };
76
78
  }
77
79
  return {
78
80
  modelId: key,
81
+ kind: 'unsupported',
79
82
  modelUrl: null,
80
83
  unsupportedReason:
81
- `Unknown model "${options.model}". Use "movenet" (default) or pass modelUrl.`,
84
+ `Unknown model "${options.model}". Use "movenet" (default), "blazepose", or pass modelUrl.`,
82
85
  };
83
86
  }
84
87
 
package/src/sdkVersion.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /** AUTO-GENERATED by scripts/sync-sdk-version.mjs — do not edit. */
2
- export const SDK_VERSION = '0.1.0';
2
+ export const SDK_VERSION = '0.2.1';
@@ -38,6 +38,7 @@ export type InitializationStep =
38
38
  | 'downloading'
39
39
  | 'warming'
40
40
  | 'accessing_webcam'
41
+ | 'loading_media'
41
42
  | 'loading_pose_model'
42
43
  | 'ready';
43
44
 
@@ -197,6 +198,38 @@ export interface ProgressionEvent {
197
198
  timestampMs: number;
198
199
  }
199
200
 
201
+ /**
202
+ * Throttled movement / rep-FSM snapshot for QA (engine ≥ 1.2.4).
203
+ * Enable via `PoseTrackerClientOptions.debugEngine` or `startExercise(..., { debug: true })`.
204
+ */
205
+ export interface EngineDebugEvent {
206
+ type: 'engine_debug';
207
+ exerciseId: string;
208
+ engineVersion: string;
209
+ ready: boolean;
210
+ postureHint: string;
211
+ direction?: string;
212
+ missingKeypoints: string[];
213
+ stepIndex: number;
214
+ stepCount: number;
215
+ stepPosition: number | null;
216
+ stepProgression: number;
217
+ progression: number;
218
+ /** Raw FSM rep count (before minGrade gate). */
219
+ rawCount: number;
220
+ /** Public counter after minGrade gate. */
221
+ publicCount: number;
222
+ difficulty: string;
223
+ minGrade: MinGrade | null;
224
+ pointOfView: 'low' | 'up';
225
+ computeError?: string;
226
+ lastSkipReason: string | null;
227
+ posesProcessed: number;
228
+ visibleKeypoints: number;
229
+ knees: { left: number | null; right: number | null };
230
+ timestampMs: number;
231
+ }
232
+
200
233
  export interface RecommendationsEvent {
201
234
  type: 'recommendations';
202
235
  /** Localized form advice strings. */
@@ -388,6 +421,7 @@ export type PoseTrackerEvent =
388
421
  | CounterEvent
389
422
  | PostureEvent
390
423
  | ProgressionEvent
424
+ | EngineDebugEvent
391
425
  | RecommendationsEvent
392
426
  | FormScoreEvent
393
427
  | ExerciseSummaryEvent
@@ -417,6 +451,7 @@ export interface PoseTrackerCallbacks {
417
451
  onCounter?: (event: CounterEvent) => void;
418
452
  onPosture?: (event: PostureEvent) => void;
419
453
  onProgression?: (event: ProgressionEvent) => void;
454
+ onEngineDebug?: (event: EngineDebugEvent) => void;
420
455
  onRecommendations?: (event: RecommendationsEvent) => void;
421
456
  onFormScore?: (event: FormScoreEvent) => void;
422
457
  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
 
18
18
  import type { MinGrade } from './events';
@@ -75,12 +75,12 @@ const KNOWN_FEATURE_KEYS = new Set<string>([
75
75
  ]);
76
76
 
77
77
  /**
78
- * WebView params that this SDK deliberately does NOT ship (one SDK, one
79
- * model: MoveNet Lightning). Passing them (possible from untyped JS hosts)
80
- * produces a clear `feature_not_supported` error.
78
+ * WebView params that are NOT feature flags in this SDK.
79
+ * Note: `options.model = 'blazepose'` is supported on light; passing
80
+ * `{ blazepose: true }` inside `features` is rejected (WebView query parity).
81
81
  */
82
82
  const UNSUPPORTED_FEATURE_HINTS: Record<string, string> = {
83
- blazepose: 'BlazePose',
83
+ blazepose: 'BlazePose as a features flag (use options.model = "blazepose" instead)',
84
84
  poseEngine: 'pose engine selection (MediaPipe/PoseLandmarker)',
85
85
  mediapipeModel: 'MediaPipe model selection',
86
86
  poseBackend: 'pose backend selection',
@@ -108,9 +108,8 @@ export const COMBINED_REFERENCE_EXERCISE_MESSAGE =
108
108
  export function featureNotSupportedMessage(key: string): string {
109
109
  const hint = UNSUPPORTED_FEATURE_HINTS[key] ?? `'${key}'`;
110
110
  return (
111
- `The '${key}' option (${hint}) is not available in this SDK. ` +
112
- 'This SDK ships MoveNet Lightning only — pose engine and model cannot be changed. ' +
113
- 'Remove the option, or use the PoseTracker WebView product if you need it. ' +
111
+ `The '${key}' option (${hint}) is not available as a features flag. ` +
112
+ 'Remove the option, or use options.model / modelUrl for model selection. ' +
114
113
  '(visit: https://posetracker.gitbook.io/posetracker-api/tracking-endpoint)'
115
114
  );
116
115
  }