@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.
- package/README.md +14 -2
- package/lib/PoseTrackerProvider.js +3 -0
- package/lib/backends/webview/WebViewPoseBackend.d.ts +1 -1
- package/lib/backends/webview/onlineRuntime.d.ts +14 -4
- package/lib/backends/webview/onlineRuntime.js +22 -4
- package/lib/backends/webview/poseHtml.d.ts +12 -4
- package/lib/backends/webview/poseHtml.js +15 -6
- package/lib/backends/webview/poseRuntimeSource.js +2 -2
- package/lib/camera/WebViewPoseView.d.ts +12 -0
- package/lib/camera/WebViewPoseView.js +29 -0
- package/lib/client.d.ts +13 -2
- package/lib/client.js +9 -1
- package/lib/engine/types.d.ts +5 -0
- package/lib/events/classicMessage.d.ts +2 -2
- package/lib/events/classicMessage.js +4 -1
- package/lib/index.d.ts +2 -2
- package/lib/index.js +4 -2
- package/lib/models/poseModels.d.ts +8 -6
- package/lib/models/poseModels.js +9 -8
- package/lib/sdkVersion.d.ts +1 -1
- package/lib/sdkVersion.js +1 -1
- package/lib/types/events.d.ts +37 -2
- package/lib/types/features.d.ts +2 -2
- package/lib/types/features.js +8 -9
- package/package.json +1 -1
- package/src/PoseTrackerProvider.tsx +3 -0
- package/src/backends/webview/WebViewPoseBackend.ts +1 -1
- package/src/backends/webview/onlineRuntime.ts +36 -5
- package/src/backends/webview/poseHtml.ts +23 -6
- package/src/camera/WebViewPoseView.tsx +43 -0
- package/src/client.ts +25 -5
- package/src/engine/types.ts +5 -0
- package/src/events/classicMessage.ts +7 -2
- package/src/index.ts +3 -0
- package/src/models/poseModels.ts +14 -11
- package/src/sdkVersion.ts +1 -1
- package/src/types/events.ts +35 -0
- 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}.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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 = '
|
|
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
|
|
126
|
-
*
|
|
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
|
|
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);
|
package/src/engine/types.ts
CHANGED
|
@@ -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 (
|
|
62
|
+
* not be mirrored (e.g. `engine_debug` QA snapshots).
|
|
63
63
|
*/
|
|
64
|
-
export function toClassicNativeMessage(
|
|
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';
|
package/src/models/poseModels.ts
CHANGED
|
@@ -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
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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
|
-
|
|
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
|
|
43
|
+
/** Human-readable reason when kind is unsupported. */
|
|
41
44
|
unsupportedReason?: string;
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
/**
|
|
45
|
-
* Resolve which
|
|
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
|
|
2
|
+
export const SDK_VERSION = '0.2.1';
|
package/src/types/events.ts
CHANGED
|
@@ -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;
|
package/src/types/features.ts
CHANGED
|
@@ -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
|
|
15
|
-
*
|
|
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
|
|
79
|
-
*
|
|
80
|
-
*
|
|
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
|
|
112
|
-
'
|
|
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
|
}
|