@pose-tracker/react-native-pose-estimation-light 0.1.0 → 0.2.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.
@@ -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
  /**
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
  /**
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.0";
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.0';
@@ -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
  /**
@@ -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.0",
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",
@@ -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
  };
@@ -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
@@ -122,8 +122,8 @@ export interface PoseTrackerClientOptions extends ConfigureOptions {
122
122
  usageTracker?: UsageTracker;
123
123
  /**
124
124
  * 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.
125
+ * BlazePose loads via CDN pose-detection in the WebView (heavier than
126
+ * MoveNet). Ignored when {@link modelUrl} is set.
127
127
  */
128
128
  model?: PoseModelAlias;
129
129
  /**
@@ -579,7 +579,7 @@ export class PoseTrackerClient {
579
579
  });
580
580
  this.options.onDiagnostic?.(
581
581
  `[posetracker-light] pose-runtime online version=${parts.version} ` +
582
- `modelId=${parts.modelId} modelUrl=${parts.modelUrl}`,
582
+ `modelId=${parts.modelId} modelKind=${parts.modelKind} modelUrl=${parts.modelUrl}`,
583
583
  );
584
584
  return parts;
585
585
  }
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';