@pose-tracker/react-native-pose-estimation-light 0.2.2 → 0.3.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 +5 -14
- package/lib/PoseTrackerProvider.d.ts +7 -1
- package/lib/PoseTrackerProvider.js +5 -2
- package/lib/api/configure.d.ts +7 -0
- package/lib/api/configure.js +1 -0
- package/lib/client.d.ts +4 -0
- package/lib/client.js +83 -2
- package/lib/engine/types.d.ts +12 -0
- package/lib/engineChannel.d.ts +28 -0
- package/lib/engineChannel.js +48 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +12 -2
- package/lib/sdkVersion.d.ts +1 -1
- package/lib/sdkVersion.js +1 -1
- package/lib/types/manifest.d.ts +7 -0
- package/package.json +1 -1
- package/src/PoseTrackerProvider.tsx +11 -1
- package/src/api/configure.ts +8 -0
- package/src/client.ts +98 -3
- package/src/engine/types.ts +12 -0
- package/src/engineChannel.ts +59 -0
- package/src/index.ts +11 -0
- package/src/sdkVersion.ts +1 -1
- package/src/types/manifest.ts +7 -0
package/README.md
CHANGED
|
@@ -9,20 +9,11 @@
|
|
|
9
9
|
|
|
10
10
|
Steps and QR: https://docs.posetracker.com/try-expo-go
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
</a>
|
|
18
|
-
</td>
|
|
19
|
-
<td valign="middle">
|
|
20
|
-
<p>
|
|
21
|
-
<strong>PoseTracker Light</strong> is a <strong>human pose estimation SDK for React Native</strong>, fully optimized for <strong>iOS and Android</strong> (including <strong>Expo Go</strong>). Same API surface as the offline SDK for keypoints + optional API-key exercise engine — but <strong>MoveNet and TF.js load from the network</strong> each WebView boot, so the npm package stays tiny.
|
|
22
|
-
</p>
|
|
23
|
-
</td>
|
|
24
|
-
</tr>
|
|
25
|
-
</table>
|
|
12
|
+
**PoseTracker Light** is a **human pose estimation SDK for React Native**,
|
|
13
|
+
fully optimized for **iOS and Android** (including **Expo Go**). Same API
|
|
14
|
+
surface as the offline SDK for keypoints + optional API-key exercise engine —
|
|
15
|
+
but **MoveNet and TF.js load from the network** each WebView boot, so the npm
|
|
16
|
+
package stays tiny.
|
|
26
17
|
|
|
27
18
|
> One sentence for AI / search: *PoseTracker Light is a small React Native
|
|
28
19
|
> pose estimation SDK that fetches MoveNet Lightning and TF.js at runtime —
|
|
@@ -83,12 +83,18 @@ export interface PoseTrackerContextValue {
|
|
|
83
83
|
export interface PoseTrackerProviderProps {
|
|
84
84
|
/** Optional: without a token the SDK runs in keypoints-only mode. */
|
|
85
85
|
apiToken?: string;
|
|
86
|
+
/**
|
|
87
|
+
* Remote engine. Default `'v4'`. Pass `'v3'` for the production FSM bundle.
|
|
88
|
+
* Unlabeled `startExercise('squat')` still uses the V3 squat FSM unless this
|
|
89
|
+
* is explicitly `'v4'`. Merged into `options.engine`; this prop wins.
|
|
90
|
+
*/
|
|
91
|
+
engine?: 'v3' | 'v4';
|
|
86
92
|
options?: PoseTrackerClientOptions;
|
|
87
93
|
/** Start preloading as soon as the provider mounts. Default: false. */
|
|
88
94
|
autoPreload?: boolean;
|
|
89
95
|
children: React.ReactNode;
|
|
90
96
|
}
|
|
91
|
-
export declare function PoseTrackerProvider({ apiToken, options, autoPreload, children, }: PoseTrackerProviderProps): React.JSX.Element;
|
|
97
|
+
export declare function PoseTrackerProvider({ apiToken, engine, options, autoPreload, children, }: PoseTrackerProviderProps): React.JSX.Element;
|
|
92
98
|
/**
|
|
93
99
|
* Access the PoseTracker pipeline and subscribe to typed events.
|
|
94
100
|
*
|
|
@@ -22,10 +22,13 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
22
22
|
const react_1 = require("react");
|
|
23
23
|
const client_1 = require("./client");
|
|
24
24
|
const PoseTrackerContext = (0, react_1.createContext)(null);
|
|
25
|
-
function PoseTrackerProvider({ apiToken, options, autoPreload = false, children, }) {
|
|
25
|
+
function PoseTrackerProvider({ apiToken, engine, options, autoPreload = false, children, }) {
|
|
26
26
|
const clientRef = (0, react_1.useRef)(null);
|
|
27
27
|
if (!clientRef.current) {
|
|
28
|
-
clientRef.current = new client_1.PoseTrackerClient(apiToken,
|
|
28
|
+
clientRef.current = new client_1.PoseTrackerClient(apiToken, {
|
|
29
|
+
...options,
|
|
30
|
+
engine: engine ?? options?.engine,
|
|
31
|
+
});
|
|
29
32
|
}
|
|
30
33
|
const client = clientRef.current;
|
|
31
34
|
const [status, setStatus] = (0, react_1.useState)(client.getStatus());
|
package/lib/api/configure.d.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { SDK_VERSION } from '../sdkVersion';
|
|
13
13
|
import type { ConfigureRequest, PoseModelProfile, SdkManifest } from '../types/manifest';
|
|
14
|
+
import type { EngineChannel } from '../engineChannel';
|
|
14
15
|
export declare const SDK_NAME: "posetracker-rn-light";
|
|
15
16
|
export { SDK_VERSION };
|
|
16
17
|
/** Overridable for staging/self-hosted backends. */
|
|
@@ -21,6 +22,12 @@ export interface ConfigureOptions {
|
|
|
21
22
|
locale?: string;
|
|
22
23
|
/** Local cache versions, so the backend can answer `upToDate` per module. */
|
|
23
24
|
localVersions?: ConfigureRequest['localVersions'];
|
|
25
|
+
/**
|
|
26
|
+
* Remote engine channel. Default `'v4'`. Pass `'v3'` for the production FSM
|
|
27
|
+
* bundle. Unlabeled `startExercise('squat')` stays on the V3 squat FSM even
|
|
28
|
+
* on the V4 channel unless `engine` is explicitly `'v4'`.
|
|
29
|
+
*/
|
|
30
|
+
engine?: EngineChannel;
|
|
24
31
|
}
|
|
25
32
|
export declare class ConfigureError extends Error {
|
|
26
33
|
readonly code: 'invalid_token' | 'quota_exceeded' | 'network' | 'internal';
|
package/lib/api/configure.js
CHANGED
|
@@ -37,6 +37,7 @@ async function configure(apiToken, options = {}) {
|
|
|
37
37
|
poseModelProfile: options.poseModelProfile ?? 'AdaptiveChoice',
|
|
38
38
|
locale: options.locale,
|
|
39
39
|
localVersions: options.localVersions,
|
|
40
|
+
engineChannel: options.engine === 'v3' ? 'v3' : 'v4',
|
|
40
41
|
};
|
|
41
42
|
let response;
|
|
42
43
|
try {
|
package/lib/client.d.ts
CHANGED
|
@@ -28,6 +28,7 @@ import { EngineLoader, type EngineLoadResult, type FileStore } from './engine/En
|
|
|
28
28
|
import { type OnlineRuntimeParts } from './backends/webview/onlineRuntime';
|
|
29
29
|
import type { CustomExerciseDescriptor } from './engine/types';
|
|
30
30
|
import type { PoseBackend, PoseInputFrame } from './backends/PoseBackend';
|
|
31
|
+
import { type EngineChannel } from './engineChannel';
|
|
31
32
|
import type { ExerciseConfig, SdkManifest } from './types/manifest';
|
|
32
33
|
import type { ColdStartMode, PreloadOptions } from './types/preload';
|
|
33
34
|
import type { SkeletonDefinition } from './types/skeleton';
|
|
@@ -326,6 +327,8 @@ export declare class PoseTrackerClient {
|
|
|
326
327
|
private validateRequestedFeatures;
|
|
327
328
|
private resolveModel;
|
|
328
329
|
getAvailableExercises(): ExerciseConfig[];
|
|
330
|
+
getEngineChannel(): EngineChannel;
|
|
331
|
+
private v4ExerciseConfig;
|
|
329
332
|
/**
|
|
330
333
|
* Custom exercises shipped inside the engine bundle (jump_analysis,
|
|
331
334
|
* air_time_jump — WebView `customHandlers.js` parity). Empty in
|
|
@@ -333,6 +336,7 @@ export declare class PoseTrackerClient {
|
|
|
333
336
|
*/
|
|
334
337
|
getAvailableCustomExercises(): CustomExerciseDescriptor[];
|
|
335
338
|
startExercise(exerciseId: string, options?: StartExerciseOptions): void;
|
|
339
|
+
private beginEngineSession;
|
|
336
340
|
/** Custom engine session (jump_analysis / air_time_jump). */
|
|
337
341
|
private startCustomExercise;
|
|
338
342
|
/**
|
package/lib/client.js
CHANGED
|
@@ -34,6 +34,7 @@ const obfuscate_1 = require("./cache/obfuscate");
|
|
|
34
34
|
const VisionPoseBackend_1 = require("./backends/vision/VisionPoseBackend");
|
|
35
35
|
const WebViewPoseBackend_1 = require("./backends/webview/WebViewPoseBackend");
|
|
36
36
|
const aliases_1 = require("./exercises/aliases");
|
|
37
|
+
const engineChannel_1 = require("./engineChannel");
|
|
37
38
|
const skeleton_1 = require("./api/skeleton");
|
|
38
39
|
const logReport_1 = require("./diagnostics/logReport");
|
|
39
40
|
const AdaptiveQualityController_1 = require("./quality/AdaptiveQualityController");
|
|
@@ -755,7 +756,32 @@ class PoseTrackerClient {
|
|
|
755
756
|
// Exercise sessions (full-engine mode only)
|
|
756
757
|
// -------------------------------------------------------------------------
|
|
757
758
|
getAvailableExercises() {
|
|
758
|
-
|
|
759
|
+
if (this.mode !== 'full-engine')
|
|
760
|
+
return [];
|
|
761
|
+
if ((0, engineChannel_1.normalizeEngineChannel)(this.options.engine) === 'v4') {
|
|
762
|
+
const listed = this.engine?.listExercises?.() ?? [];
|
|
763
|
+
if (listed.length)
|
|
764
|
+
return listed.map((e) => this.v4ExerciseConfig(e));
|
|
765
|
+
}
|
|
766
|
+
return this.manifest?.exercises ?? [];
|
|
767
|
+
}
|
|
768
|
+
getEngineChannel() {
|
|
769
|
+
return (0, engineChannel_1.normalizeEngineChannel)(this.options.engine);
|
|
770
|
+
}
|
|
771
|
+
v4ExerciseConfig(entry) {
|
|
772
|
+
const type = entry.type === 'static' ? 'static' : 'dynamic';
|
|
773
|
+
return {
|
|
774
|
+
id: entry.id,
|
|
775
|
+
name: entry.displayName || entry.id,
|
|
776
|
+
type,
|
|
777
|
+
movement: {
|
|
778
|
+
name: entry.id,
|
|
779
|
+
type,
|
|
780
|
+
scale_acceptance: {},
|
|
781
|
+
movement_steps: [],
|
|
782
|
+
movement_initial_posture: null,
|
|
783
|
+
},
|
|
784
|
+
};
|
|
759
785
|
}
|
|
760
786
|
/**
|
|
761
787
|
* Custom exercises shipped inside the engine bundle (jump_analysis,
|
|
@@ -798,6 +824,55 @@ class PoseTrackerClient {
|
|
|
798
824
|
throw new Error(features_1.FREE_PLAN_FEATURES_MESSAGE);
|
|
799
825
|
}
|
|
800
826
|
}
|
|
827
|
+
if ((0, engineChannel_1.requiresEngineV4)(exerciseId) && (0, engineChannel_1.normalizeEngineChannel)(this.options.engine) !== 'v4') {
|
|
828
|
+
const message = `Exercise '${exerciseId}' requires engine: 'v4'`;
|
|
829
|
+
this.reportError({ type: 'error', code: 'invalid_exercise', message });
|
|
830
|
+
throw new Error(message);
|
|
831
|
+
}
|
|
832
|
+
const jumpId = (0, aliases_1.resolveExerciseName)(exerciseId);
|
|
833
|
+
if ((0, engineChannel_1.isJumpExercise)(jumpId)) {
|
|
834
|
+
const customs = this.getAvailableCustomExercises();
|
|
835
|
+
const custom = customs.find((e) => e.id === jumpId) ??
|
|
836
|
+
(0, aliases_1.findExerciseByIdOrAlias)(exerciseId, customs) ??
|
|
837
|
+
{
|
|
838
|
+
id: jumpId,
|
|
839
|
+
displayName: jumpId === 'jump_analysis' ? 'Jump Analysis' : 'Air Time Jump',
|
|
840
|
+
type: 'custom',
|
|
841
|
+
requiredParams: jumpId === 'jump_analysis' ? ['userHeightCm'] : [],
|
|
842
|
+
optionalParams: ['devicePitchDeg'],
|
|
843
|
+
};
|
|
844
|
+
this.startCustomExercise(custom, options);
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
// Production squat stays on the V3 FSM unless engine is explicitly 'v4'.
|
|
848
|
+
const sessionEngine = (0, engineChannel_1.resolveMovementEngine)({
|
|
849
|
+
engine: this.options.engine,
|
|
850
|
+
exercise: jumpId || exerciseId,
|
|
851
|
+
});
|
|
852
|
+
if (sessionEngine === 'v4') {
|
|
853
|
+
const listed = this.engine.listExercises?.() ?? [];
|
|
854
|
+
const v4Hit = listed.find((e) => e.id === exerciseId) ?? listed.find((e) => e.id === jumpId);
|
|
855
|
+
if (v4Hit) {
|
|
856
|
+
this.beginEngineSession(this.v4ExerciseConfig(v4Hit), options);
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
const customs = this.getAvailableCustomExercises();
|
|
860
|
+
const custom = customs.find((e) => e.id === exerciseId) ??
|
|
861
|
+
(0, aliases_1.findExerciseByIdOrAlias)(exerciseId, customs);
|
|
862
|
+
if (custom) {
|
|
863
|
+
this.startCustomExercise(custom, options);
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
const message = `Exercise '${exerciseId}' is not available in V4 engine`;
|
|
867
|
+
this.reportError({ type: 'error', code: 'invalid_exercise', message });
|
|
868
|
+
throw new Error(message);
|
|
869
|
+
}
|
|
870
|
+
// V4 bundle still runs the production V3 squat FSM unless engine is 'v4'.
|
|
871
|
+
if ((0, engineChannel_1.isProductionSquatId)(jumpId || exerciseId) &&
|
|
872
|
+
(0, engineChannel_1.normalizeEngineChannel)(this.options.engine) === 'v4') {
|
|
873
|
+
this.beginEngineSession(this.v4ExerciseConfig({ id: jumpId || 'squat', displayName: 'Squat', type: 'dynamic' }), options);
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
801
876
|
const available = this.getAvailableExercises();
|
|
802
877
|
const exercise = (0, aliases_1.findExerciseByIdOrAlias)(exerciseId, available);
|
|
803
878
|
if (!exercise) {
|
|
@@ -815,8 +890,13 @@ class PoseTrackerClient {
|
|
|
815
890
|
this.reportError({ type: 'error', code: 'invalid_exercise', message });
|
|
816
891
|
throw new Error(message);
|
|
817
892
|
}
|
|
893
|
+
this.beginEngineSession(exercise, options);
|
|
894
|
+
}
|
|
895
|
+
beginEngineSession(exercise, options) {
|
|
896
|
+
if (!this.engine)
|
|
897
|
+
return;
|
|
818
898
|
this.stopExercise();
|
|
819
|
-
this.currentExerciseId =
|
|
899
|
+
this.currentExerciseId = exercise.id;
|
|
820
900
|
this.keypointsSuppressionLogged = false;
|
|
821
901
|
const debug = options.debug ?? this.options.debugEngine === true;
|
|
822
902
|
this.session = this.engine.createSession({
|
|
@@ -825,6 +905,7 @@ class PoseTrackerClient {
|
|
|
825
905
|
difficulty: options.difficulty,
|
|
826
906
|
minGrade: this.features.minGrade ?? undefined,
|
|
827
907
|
debug,
|
|
908
|
+
v4CatalogSquat: this.options.engine === 'v4',
|
|
828
909
|
features: {
|
|
829
910
|
angles: this.features.angles,
|
|
830
911
|
recommendations: this.features.recommendations,
|
package/lib/engine/types.d.ts
CHANGED
|
@@ -41,6 +41,11 @@ export interface EngineSessionOptions {
|
|
|
41
41
|
* Ignored by older engines.
|
|
42
42
|
*/
|
|
43
43
|
debug?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Run the V4 catalog squat instead of the production V3 FSM.
|
|
46
|
+
* Set when the host passed `engine: 'v4'` explicitly.
|
|
47
|
+
*/
|
|
48
|
+
v4CatalogSquat?: boolean;
|
|
44
49
|
}
|
|
45
50
|
export interface EngineSession {
|
|
46
51
|
/** Feed one estimated pose; the session emits events through the sink. */
|
|
@@ -78,6 +83,13 @@ export interface PoseTrackerEngine {
|
|
|
78
83
|
createSession(options: EngineSessionOptions, emit: EventSink): EngineSession;
|
|
79
84
|
/** Engine >= 1.2.0: custom exercises shipped in the bundle (jump analysis…). */
|
|
80
85
|
listCustomExercises?(): CustomExerciseDescriptor[];
|
|
86
|
+
/** Engine V4 catalog (opt-in channel). Absent on V3 bundles. */
|
|
87
|
+
listExercises?(): Array<{
|
|
88
|
+
id: string;
|
|
89
|
+
displayName: string;
|
|
90
|
+
type: string;
|
|
91
|
+
description?: string;
|
|
92
|
+
}>;
|
|
81
93
|
/** Engine >= 1.2.0: start a custom exercise session (placement + jump_* events). */
|
|
82
94
|
createCustomSession?(options: CustomSessionOptions, emit: EventSink): EngineSession;
|
|
83
95
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Engine channel + production squat lock.
|
|
3
|
+
*
|
|
4
|
+
* Default handshake is V4 except unlabeled squat / face_squat (V3 FSM).
|
|
5
|
+
* Explicit `engine: 'v4'` is the only way to run the V4 catalog squat.
|
|
6
|
+
* Explicit `engine: 'v3'` keeps the full V3 bundle.
|
|
7
|
+
* See engine/v4/catalog/SQUAT_PRODUCTION.md
|
|
8
|
+
*/
|
|
9
|
+
export type EngineChannel = 'v3' | 'v4';
|
|
10
|
+
/** Wellness ids with no V3 FSM. */
|
|
11
|
+
export declare const V4_ONLY_EXERCISE_IDS: readonly ["shoulder_roll", "shoulder_deep_breath", "chair_forward_fold", "chair_side_stretch"];
|
|
12
|
+
/** Existing jump handlers — not V4 JSON. */
|
|
13
|
+
export declare const JUMP_EXERCISE_IDS: readonly ["jump_analysis", "air_time_jump"];
|
|
14
|
+
export declare const PRODUCTION_SQUAT_IDS: readonly ["squat", "face_squat"];
|
|
15
|
+
export declare function isProductionSquatId(exerciseId?: string | null): boolean;
|
|
16
|
+
/** Handshake default is V4. Pass `'v3'` to force the V3 bundle. */
|
|
17
|
+
export declare function normalizeEngineChannel(raw?: string | null): EngineChannel;
|
|
18
|
+
/**
|
|
19
|
+
* Session interpreter for this id. Production squat stays V3 unless `engine`
|
|
20
|
+
* is explicitly `'v4'`. Other ids follow the handshake (default V4).
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveMovementEngine(opts?: {
|
|
23
|
+
engine?: string | null;
|
|
24
|
+
exercise?: string | null;
|
|
25
|
+
userExercise?: boolean;
|
|
26
|
+
}): EngineChannel;
|
|
27
|
+
export declare function requiresEngineV4(exerciseId: string): boolean;
|
|
28
|
+
export declare function isJumpExercise(exerciseId: string): boolean;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PRODUCTION_SQUAT_IDS = exports.JUMP_EXERCISE_IDS = exports.V4_ONLY_EXERCISE_IDS = void 0;
|
|
4
|
+
exports.isProductionSquatId = isProductionSquatId;
|
|
5
|
+
exports.normalizeEngineChannel = normalizeEngineChannel;
|
|
6
|
+
exports.resolveMovementEngine = resolveMovementEngine;
|
|
7
|
+
exports.requiresEngineV4 = requiresEngineV4;
|
|
8
|
+
exports.isJumpExercise = isJumpExercise;
|
|
9
|
+
/** Wellness ids with no V3 FSM. */
|
|
10
|
+
exports.V4_ONLY_EXERCISE_IDS = [
|
|
11
|
+
'shoulder_roll',
|
|
12
|
+
'shoulder_deep_breath',
|
|
13
|
+
'chair_forward_fold',
|
|
14
|
+
'chair_side_stretch',
|
|
15
|
+
];
|
|
16
|
+
/** Existing jump handlers — not V4 JSON. */
|
|
17
|
+
exports.JUMP_EXERCISE_IDS = ['jump_analysis', 'air_time_jump'];
|
|
18
|
+
exports.PRODUCTION_SQUAT_IDS = ['squat', 'face_squat'];
|
|
19
|
+
function isProductionSquatId(exerciseId) {
|
|
20
|
+
const id = String(exerciseId || '')
|
|
21
|
+
.trim()
|
|
22
|
+
.toLowerCase();
|
|
23
|
+
return id === 'squat' || id === 'face_squat';
|
|
24
|
+
}
|
|
25
|
+
/** Handshake default is V4. Pass `'v3'` to force the V3 bundle. */
|
|
26
|
+
function normalizeEngineChannel(raw) {
|
|
27
|
+
return raw === 'v3' ? 'v3' : 'v4';
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Session interpreter for this id. Production squat stays V3 unless `engine`
|
|
31
|
+
* is explicitly `'v4'`. Other ids follow the handshake (default V4).
|
|
32
|
+
*/
|
|
33
|
+
function resolveMovementEngine(opts = {}) {
|
|
34
|
+
if (opts.userExercise)
|
|
35
|
+
return 'v4';
|
|
36
|
+
const requested = String(opts.engine || '')
|
|
37
|
+
.trim()
|
|
38
|
+
.toLowerCase();
|
|
39
|
+
if (isProductionSquatId(opts.exercise) && requested !== 'v4')
|
|
40
|
+
return 'v3';
|
|
41
|
+
return normalizeEngineChannel(opts.engine);
|
|
42
|
+
}
|
|
43
|
+
function requiresEngineV4(exerciseId) {
|
|
44
|
+
return exports.V4_ONLY_EXERCISE_IDS.includes(exerciseId);
|
|
45
|
+
}
|
|
46
|
+
function isJumpExercise(exerciseId) {
|
|
47
|
+
return exports.JUMP_EXERCISE_IDS.includes(exerciseId);
|
|
48
|
+
}
|
package/lib/index.d.ts
CHANGED
|
@@ -25,6 +25,8 @@ export { DEFAULT_MOVENET_LIGHTNING_URL, originFromModelUrl, resolvePoseModel, }
|
|
|
25
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
|
+
export type { EngineChannel } from './engineChannel';
|
|
29
|
+
export { JUMP_EXERCISE_IDS, PRODUCTION_SQUAT_IDS, V4_ONLY_EXERCISE_IDS, isJumpExercise, isProductionSquatId, normalizeEngineChannel, requiresEngineV4, resolveMovementEngine, } from './engineChannel';
|
|
28
30
|
export type { ColdStartMode, PreloadOptions } from './types/preload';
|
|
29
31
|
export type { SkeletonAnglesStyle, SkeletonCirclesStyle, SkeletonDefinition, SkeletonLinesStyle, } from './types/skeleton';
|
|
30
32
|
export { DEFAULT_SKELETON_DEFINITION } from './types/skeleton';
|
package/lib/index.js
CHANGED
|
@@ -28,8 +28,9 @@ 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.
|
|
32
|
-
exports.
|
|
31
|
+
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.resolveMovementEngine = exports.requiresEngineV4 = exports.normalizeEngineChannel = exports.isProductionSquatId = exports.isJumpExercise = exports.V4_ONLY_EXERCISE_IDS = exports.PRODUCTION_SQUAT_IDS = exports.JUMP_EXERCISE_IDS = 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.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 = exports.logAccelerationReport = exports.defaultDiagnosticLogger = exports.createNativeFileStore = exports.createMemoryFileStore = exports.createBlobUtilFileStore = exports.createRnfsFileStore = exports.createExpoFileStore = exports.EngineLoader = void 0;
|
|
33
|
+
exports.resolveFeatures = exports.shouldShowWatermark = exports.isPaidPlan = exports.freeBlockedFeatures = exports.featureNotSupportedMessage = void 0;
|
|
33
34
|
// Handshake + usage tracking
|
|
34
35
|
var configure_1 = require("./api/configure");
|
|
35
36
|
Object.defineProperty(exports, "configure", { enumerable: true, get: function () { return configure_1.configure; } });
|
|
@@ -61,6 +62,15 @@ Object.defineProperty(exports, "resolvePoseModel", { enumerable: true, get: func
|
|
|
61
62
|
// Orchestrator + React layer
|
|
62
63
|
var client_1 = require("./client");
|
|
63
64
|
Object.defineProperty(exports, "PoseTrackerClient", { enumerable: true, get: function () { return client_1.PoseTrackerClient; } });
|
|
65
|
+
var engineChannel_1 = require("./engineChannel");
|
|
66
|
+
Object.defineProperty(exports, "JUMP_EXERCISE_IDS", { enumerable: true, get: function () { return engineChannel_1.JUMP_EXERCISE_IDS; } });
|
|
67
|
+
Object.defineProperty(exports, "PRODUCTION_SQUAT_IDS", { enumerable: true, get: function () { return engineChannel_1.PRODUCTION_SQUAT_IDS; } });
|
|
68
|
+
Object.defineProperty(exports, "V4_ONLY_EXERCISE_IDS", { enumerable: true, get: function () { return engineChannel_1.V4_ONLY_EXERCISE_IDS; } });
|
|
69
|
+
Object.defineProperty(exports, "isJumpExercise", { enumerable: true, get: function () { return engineChannel_1.isJumpExercise; } });
|
|
70
|
+
Object.defineProperty(exports, "isProductionSquatId", { enumerable: true, get: function () { return engineChannel_1.isProductionSquatId; } });
|
|
71
|
+
Object.defineProperty(exports, "normalizeEngineChannel", { enumerable: true, get: function () { return engineChannel_1.normalizeEngineChannel; } });
|
|
72
|
+
Object.defineProperty(exports, "requiresEngineV4", { enumerable: true, get: function () { return engineChannel_1.requiresEngineV4; } });
|
|
73
|
+
Object.defineProperty(exports, "resolveMovementEngine", { enumerable: true, get: function () { return engineChannel_1.resolveMovementEngine; } });
|
|
64
74
|
var skeleton_1 = require("./types/skeleton");
|
|
65
75
|
Object.defineProperty(exports, "DEFAULT_SKELETON_DEFINITION", { enumerable: true, get: function () { return skeleton_1.DEFAULT_SKELETON_DEFINITION; } });
|
|
66
76
|
var skeleton_2 = require("./api/skeleton");
|
package/lib/sdkVersion.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** AUTO-GENERATED by scripts/sync-sdk-version.mjs — do not edit. */
|
|
2
|
-
export declare const SDK_VERSION = "0.
|
|
2
|
+
export declare const SDK_VERSION = "0.3.1";
|
package/lib/sdkVersion.js
CHANGED
package/lib/types/manifest.d.ts
CHANGED
|
@@ -126,6 +126,8 @@ export interface EngineBundleDescriptor {
|
|
|
126
126
|
minSdkVersion: string;
|
|
127
127
|
/** True when the version sent in `localVersions.engine` is already current. */
|
|
128
128
|
upToDate?: boolean;
|
|
129
|
+
/** Which interpreter this descriptor is. Absent on older backends = v3. */
|
|
130
|
+
channel?: 'v3' | 'v4';
|
|
129
131
|
}
|
|
130
132
|
export interface PoseRuntimePartDescriptor {
|
|
131
133
|
/** Download URL of this part (`GET /api/sdk/pose-runtime?part=...&v=...`). */
|
|
@@ -166,6 +168,11 @@ export interface ConfigureRequest {
|
|
|
166
168
|
poseRuntime?: string | null;
|
|
167
169
|
engine?: string | null;
|
|
168
170
|
};
|
|
171
|
+
/**
|
|
172
|
+
* Which remote engine to download. New SDKs send `'v4'` by default.
|
|
173
|
+
* Omitted (0.2.x) still means V3 on the server. `'v3'` forces the FSM bundle.
|
|
174
|
+
*/
|
|
175
|
+
engineChannel?: 'v3' | 'v4';
|
|
169
176
|
}
|
|
170
177
|
export interface PlanInfo {
|
|
171
178
|
plan: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pose-tracker/react-native-pose-estimation-light",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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",
|
|
@@ -106,6 +106,12 @@ const PoseTrackerContext = createContext<PoseTrackerContextValue | null>(null);
|
|
|
106
106
|
export interface PoseTrackerProviderProps {
|
|
107
107
|
/** Optional: without a token the SDK runs in keypoints-only mode. */
|
|
108
108
|
apiToken?: string;
|
|
109
|
+
/**
|
|
110
|
+
* Remote engine. Default `'v4'`. Pass `'v3'` for the production FSM bundle.
|
|
111
|
+
* Unlabeled `startExercise('squat')` still uses the V3 squat FSM unless this
|
|
112
|
+
* is explicitly `'v4'`. Merged into `options.engine`; this prop wins.
|
|
113
|
+
*/
|
|
114
|
+
engine?: 'v3' | 'v4';
|
|
109
115
|
options?: PoseTrackerClientOptions;
|
|
110
116
|
/** Start preloading as soon as the provider mounts. Default: false. */
|
|
111
117
|
autoPreload?: boolean;
|
|
@@ -114,13 +120,17 @@ export interface PoseTrackerProviderProps {
|
|
|
114
120
|
|
|
115
121
|
export function PoseTrackerProvider({
|
|
116
122
|
apiToken,
|
|
123
|
+
engine,
|
|
117
124
|
options,
|
|
118
125
|
autoPreload = false,
|
|
119
126
|
children,
|
|
120
127
|
}: PoseTrackerProviderProps): React.JSX.Element {
|
|
121
128
|
const clientRef = useRef<PoseTrackerClient | null>(null);
|
|
122
129
|
if (!clientRef.current) {
|
|
123
|
-
clientRef.current = new PoseTrackerClient(apiToken,
|
|
130
|
+
clientRef.current = new PoseTrackerClient(apiToken, {
|
|
131
|
+
...options,
|
|
132
|
+
engine: engine ?? options?.engine,
|
|
133
|
+
});
|
|
124
134
|
}
|
|
125
135
|
const client = clientRef.current;
|
|
126
136
|
|
package/src/api/configure.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { Platform } from 'react-native';
|
|
|
14
14
|
|
|
15
15
|
import { SDK_VERSION } from '../sdkVersion';
|
|
16
16
|
import type { ConfigureRequest, PoseModelProfile, SdkManifest } from '../types/manifest';
|
|
17
|
+
import type { EngineChannel } from '../engineChannel';
|
|
17
18
|
|
|
18
19
|
export const SDK_NAME = 'posetracker-rn-light' as const;
|
|
19
20
|
export { SDK_VERSION };
|
|
@@ -28,6 +29,12 @@ export interface ConfigureOptions {
|
|
|
28
29
|
locale?: string;
|
|
29
30
|
/** Local cache versions, so the backend can answer `upToDate` per module. */
|
|
30
31
|
localVersions?: ConfigureRequest['localVersions'];
|
|
32
|
+
/**
|
|
33
|
+
* Remote engine channel. Default `'v4'`. Pass `'v3'` for the production FSM
|
|
34
|
+
* bundle. Unlabeled `startExercise('squat')` stays on the V3 squat FSM even
|
|
35
|
+
* on the V4 channel unless `engine` is explicitly `'v4'`.
|
|
36
|
+
*/
|
|
37
|
+
engine?: EngineChannel;
|
|
31
38
|
}
|
|
32
39
|
|
|
33
40
|
export class ConfigureError extends Error {
|
|
@@ -53,6 +60,7 @@ export async function configure(
|
|
|
53
60
|
poseModelProfile: options.poseModelProfile ?? 'AdaptiveChoice',
|
|
54
61
|
locale: options.locale,
|
|
55
62
|
localVersions: options.localVersions,
|
|
63
|
+
engineChannel: options.engine === 'v3' ? 'v3' : 'v4',
|
|
56
64
|
};
|
|
57
65
|
|
|
58
66
|
let response: Response;
|
package/src/client.ts
CHANGED
|
@@ -37,7 +37,15 @@ import type { CustomExerciseDescriptor, EngineSession, PoseTrackerEngine } from
|
|
|
37
37
|
import type { PoseBackend, PoseInputFrame } from './backends/PoseBackend';
|
|
38
38
|
import { VisionPoseBackend } from './backends/vision/VisionPoseBackend';
|
|
39
39
|
import { WebViewPoseBackend } from './backends/webview/WebViewPoseBackend';
|
|
40
|
-
import { findExerciseByIdOrAlias } from './exercises/aliases';
|
|
40
|
+
import { findExerciseByIdOrAlias, resolveExerciseName } from './exercises/aliases';
|
|
41
|
+
import {
|
|
42
|
+
isJumpExercise,
|
|
43
|
+
isProductionSquatId,
|
|
44
|
+
normalizeEngineChannel,
|
|
45
|
+
requiresEngineV4,
|
|
46
|
+
resolveMovementEngine,
|
|
47
|
+
type EngineChannel,
|
|
48
|
+
} from './engineChannel';
|
|
41
49
|
import { fetchSkeletonDefinition } from './api/skeleton';
|
|
42
50
|
import type { ExerciseConfig, ModelDescriptor, SdkManifest } from './types/manifest';
|
|
43
51
|
import type { ColdStartMode, PreloadOptions } from './types/preload';
|
|
@@ -979,7 +987,32 @@ export class PoseTrackerClient {
|
|
|
979
987
|
// -------------------------------------------------------------------------
|
|
980
988
|
|
|
981
989
|
getAvailableExercises(): ExerciseConfig[] {
|
|
982
|
-
|
|
990
|
+
if (this.mode !== 'full-engine') return [];
|
|
991
|
+
if (normalizeEngineChannel(this.options.engine) === 'v4') {
|
|
992
|
+
const listed = this.engine?.listExercises?.() ?? [];
|
|
993
|
+
if (listed.length) return listed.map((e) => this.v4ExerciseConfig(e));
|
|
994
|
+
}
|
|
995
|
+
return this.manifest?.exercises ?? [];
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
getEngineChannel(): EngineChannel {
|
|
999
|
+
return normalizeEngineChannel(this.options.engine);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
private v4ExerciseConfig(entry: { id: string; displayName?: string; type?: string }): ExerciseConfig {
|
|
1003
|
+
const type = entry.type === 'static' ? 'static' : 'dynamic';
|
|
1004
|
+
return {
|
|
1005
|
+
id: entry.id,
|
|
1006
|
+
name: entry.displayName || entry.id,
|
|
1007
|
+
type,
|
|
1008
|
+
movement: {
|
|
1009
|
+
name: entry.id,
|
|
1010
|
+
type,
|
|
1011
|
+
scale_acceptance: {},
|
|
1012
|
+
movement_steps: [],
|
|
1013
|
+
movement_initial_posture: null,
|
|
1014
|
+
},
|
|
1015
|
+
};
|
|
983
1016
|
}
|
|
984
1017
|
|
|
985
1018
|
/**
|
|
@@ -1028,6 +1061,62 @@ export class PoseTrackerClient {
|
|
|
1028
1061
|
throw new Error(FREE_PLAN_FEATURES_MESSAGE);
|
|
1029
1062
|
}
|
|
1030
1063
|
}
|
|
1064
|
+
if (requiresEngineV4(exerciseId) && normalizeEngineChannel(this.options.engine) !== 'v4') {
|
|
1065
|
+
const message = `Exercise '${exerciseId}' requires engine: 'v4'`;
|
|
1066
|
+
this.reportError({ type: 'error', code: 'invalid_exercise', message });
|
|
1067
|
+
throw new Error(message);
|
|
1068
|
+
}
|
|
1069
|
+
const jumpId = resolveExerciseName(exerciseId);
|
|
1070
|
+
if (isJumpExercise(jumpId)) {
|
|
1071
|
+
const customs = this.getAvailableCustomExercises();
|
|
1072
|
+
const custom =
|
|
1073
|
+
customs.find((e) => e.id === jumpId) ??
|
|
1074
|
+
findExerciseByIdOrAlias(exerciseId, customs) ??
|
|
1075
|
+
{
|
|
1076
|
+
id: jumpId,
|
|
1077
|
+
displayName: jumpId === 'jump_analysis' ? 'Jump Analysis' : 'Air Time Jump',
|
|
1078
|
+
type: 'custom' as const,
|
|
1079
|
+
requiredParams: jumpId === 'jump_analysis' ? ['userHeightCm'] : [],
|
|
1080
|
+
optionalParams: ['devicePitchDeg'],
|
|
1081
|
+
};
|
|
1082
|
+
this.startCustomExercise(custom, options);
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
// Production squat stays on the V3 FSM unless engine is explicitly 'v4'.
|
|
1086
|
+
const sessionEngine = resolveMovementEngine({
|
|
1087
|
+
engine: this.options.engine,
|
|
1088
|
+
exercise: jumpId || exerciseId,
|
|
1089
|
+
});
|
|
1090
|
+
if (sessionEngine === 'v4') {
|
|
1091
|
+
const listed = this.engine.listExercises?.() ?? [];
|
|
1092
|
+
const v4Hit = listed.find((e) => e.id === exerciseId) ?? listed.find((e) => e.id === jumpId);
|
|
1093
|
+
if (v4Hit) {
|
|
1094
|
+
this.beginEngineSession(this.v4ExerciseConfig(v4Hit), options);
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
const customs = this.getAvailableCustomExercises();
|
|
1098
|
+
const custom =
|
|
1099
|
+
customs.find((e) => e.id === exerciseId) ??
|
|
1100
|
+
findExerciseByIdOrAlias(exerciseId, customs);
|
|
1101
|
+
if (custom) {
|
|
1102
|
+
this.startCustomExercise(custom, options);
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
const message = `Exercise '${exerciseId}' is not available in V4 engine`;
|
|
1106
|
+
this.reportError({ type: 'error', code: 'invalid_exercise', message });
|
|
1107
|
+
throw new Error(message);
|
|
1108
|
+
}
|
|
1109
|
+
// V4 bundle still runs the production V3 squat FSM unless engine is 'v4'.
|
|
1110
|
+
if (
|
|
1111
|
+
isProductionSquatId(jumpId || exerciseId) &&
|
|
1112
|
+
normalizeEngineChannel(this.options.engine) === 'v4'
|
|
1113
|
+
) {
|
|
1114
|
+
this.beginEngineSession(
|
|
1115
|
+
this.v4ExerciseConfig({ id: jumpId || 'squat', displayName: 'Squat', type: 'dynamic' }),
|
|
1116
|
+
options,
|
|
1117
|
+
);
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1031
1120
|
const available = this.getAvailableExercises();
|
|
1032
1121
|
const exercise = findExerciseByIdOrAlias(exerciseId, available);
|
|
1033
1122
|
if (!exercise) {
|
|
@@ -1046,8 +1135,13 @@ export class PoseTrackerClient {
|
|
|
1046
1135
|
this.reportError({ type: 'error', code: 'invalid_exercise', message });
|
|
1047
1136
|
throw new Error(message);
|
|
1048
1137
|
}
|
|
1138
|
+
this.beginEngineSession(exercise, options);
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
private beginEngineSession(exercise: ExerciseConfig, options: StartExerciseOptions): void {
|
|
1142
|
+
if (!this.engine) return;
|
|
1049
1143
|
this.stopExercise();
|
|
1050
|
-
this.currentExerciseId =
|
|
1144
|
+
this.currentExerciseId = exercise.id;
|
|
1051
1145
|
this.keypointsSuppressionLogged = false;
|
|
1052
1146
|
const debug = options.debug ?? this.options.debugEngine === true;
|
|
1053
1147
|
this.session = this.engine.createSession(
|
|
@@ -1057,6 +1151,7 @@ export class PoseTrackerClient {
|
|
|
1057
1151
|
difficulty: options.difficulty,
|
|
1058
1152
|
minGrade: this.features.minGrade ?? undefined,
|
|
1059
1153
|
debug,
|
|
1154
|
+
v4CatalogSquat: this.options.engine === 'v4',
|
|
1060
1155
|
features: {
|
|
1061
1156
|
angles: this.features.angles,
|
|
1062
1157
|
recommendations: this.features.recommendations,
|
package/src/engine/types.ts
CHANGED
|
@@ -44,6 +44,11 @@ export interface EngineSessionOptions {
|
|
|
44
44
|
* Ignored by older engines.
|
|
45
45
|
*/
|
|
46
46
|
debug?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Run the V4 catalog squat instead of the production V3 FSM.
|
|
49
|
+
* Set when the host passed `engine: 'v4'` explicitly.
|
|
50
|
+
*/
|
|
51
|
+
v4CatalogSquat?: boolean;
|
|
47
52
|
}
|
|
48
53
|
|
|
49
54
|
export interface EngineSession {
|
|
@@ -86,6 +91,13 @@ export interface PoseTrackerEngine {
|
|
|
86
91
|
createSession(options: EngineSessionOptions, emit: EventSink): EngineSession;
|
|
87
92
|
/** Engine >= 1.2.0: custom exercises shipped in the bundle (jump analysis…). */
|
|
88
93
|
listCustomExercises?(): CustomExerciseDescriptor[];
|
|
94
|
+
/** Engine V4 catalog (opt-in channel). Absent on V3 bundles. */
|
|
95
|
+
listExercises?(): Array<{
|
|
96
|
+
id: string;
|
|
97
|
+
displayName: string;
|
|
98
|
+
type: string;
|
|
99
|
+
description?: string;
|
|
100
|
+
}>;
|
|
89
101
|
/** Engine >= 1.2.0: start a custom exercise session (placement + jump_* events). */
|
|
90
102
|
createCustomSession?(options: CustomSessionOptions, emit: EventSink): EngineSession;
|
|
91
103
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Engine channel + production squat lock.
|
|
3
|
+
*
|
|
4
|
+
* Default handshake is V4 except unlabeled squat / face_squat (V3 FSM).
|
|
5
|
+
* Explicit `engine: 'v4'` is the only way to run the V4 catalog squat.
|
|
6
|
+
* Explicit `engine: 'v3'` keeps the full V3 bundle.
|
|
7
|
+
* See engine/v4/catalog/SQUAT_PRODUCTION.md
|
|
8
|
+
*/
|
|
9
|
+
export type EngineChannel = 'v3' | 'v4';
|
|
10
|
+
|
|
11
|
+
/** Wellness ids with no V3 FSM. */
|
|
12
|
+
export const V4_ONLY_EXERCISE_IDS = [
|
|
13
|
+
'shoulder_roll',
|
|
14
|
+
'shoulder_deep_breath',
|
|
15
|
+
'chair_forward_fold',
|
|
16
|
+
'chair_side_stretch',
|
|
17
|
+
] as const;
|
|
18
|
+
|
|
19
|
+
/** Existing jump handlers — not V4 JSON. */
|
|
20
|
+
export const JUMP_EXERCISE_IDS = ['jump_analysis', 'air_time_jump'] as const;
|
|
21
|
+
|
|
22
|
+
export const PRODUCTION_SQUAT_IDS = ['squat', 'face_squat'] as const;
|
|
23
|
+
|
|
24
|
+
export function isProductionSquatId(exerciseId?: string | null): boolean {
|
|
25
|
+
const id = String(exerciseId || '')
|
|
26
|
+
.trim()
|
|
27
|
+
.toLowerCase();
|
|
28
|
+
return id === 'squat' || id === 'face_squat';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Handshake default is V4. Pass `'v3'` to force the V3 bundle. */
|
|
32
|
+
export function normalizeEngineChannel(raw?: string | null): EngineChannel {
|
|
33
|
+
return raw === 'v3' ? 'v3' : 'v4';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Session interpreter for this id. Production squat stays V3 unless `engine`
|
|
38
|
+
* is explicitly `'v4'`. Other ids follow the handshake (default V4).
|
|
39
|
+
*/
|
|
40
|
+
export function resolveMovementEngine(opts: {
|
|
41
|
+
engine?: string | null;
|
|
42
|
+
exercise?: string | null;
|
|
43
|
+
userExercise?: boolean;
|
|
44
|
+
} = {}): EngineChannel {
|
|
45
|
+
if (opts.userExercise) return 'v4';
|
|
46
|
+
const requested = String(opts.engine || '')
|
|
47
|
+
.trim()
|
|
48
|
+
.toLowerCase();
|
|
49
|
+
if (isProductionSquatId(opts.exercise) && requested !== 'v4') return 'v3';
|
|
50
|
+
return normalizeEngineChannel(opts.engine);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function requiresEngineV4(exerciseId: string): boolean {
|
|
54
|
+
return (V4_ONLY_EXERCISE_IDS as readonly string[]).includes(exerciseId);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function isJumpExercise(exerciseId: string): boolean {
|
|
58
|
+
return (JUMP_EXERCISE_IDS as readonly string[]).includes(exerciseId);
|
|
59
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -59,6 +59,17 @@ export type {
|
|
|
59
59
|
// Orchestrator + React layer
|
|
60
60
|
export { PoseTrackerClient } from './client';
|
|
61
61
|
export type { PoseTrackerClientOptions, PreferredBackend, StartExerciseOptions } from './client';
|
|
62
|
+
export type { EngineChannel } from './engineChannel';
|
|
63
|
+
export {
|
|
64
|
+
JUMP_EXERCISE_IDS,
|
|
65
|
+
PRODUCTION_SQUAT_IDS,
|
|
66
|
+
V4_ONLY_EXERCISE_IDS,
|
|
67
|
+
isJumpExercise,
|
|
68
|
+
isProductionSquatId,
|
|
69
|
+
normalizeEngineChannel,
|
|
70
|
+
requiresEngineV4,
|
|
71
|
+
resolveMovementEngine,
|
|
72
|
+
} from './engineChannel';
|
|
62
73
|
export type { ColdStartMode, PreloadOptions } from './types/preload';
|
|
63
74
|
export type {
|
|
64
75
|
SkeletonAnglesStyle,
|
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.
|
|
2
|
+
export const SDK_VERSION = '0.3.1';
|
package/src/types/manifest.ts
CHANGED
|
@@ -146,6 +146,8 @@ export interface EngineBundleDescriptor {
|
|
|
146
146
|
minSdkVersion: string;
|
|
147
147
|
/** True when the version sent in `localVersions.engine` is already current. */
|
|
148
148
|
upToDate?: boolean;
|
|
149
|
+
/** Which interpreter this descriptor is. Absent on older backends = v3. */
|
|
150
|
+
channel?: 'v3' | 'v4';
|
|
149
151
|
}
|
|
150
152
|
|
|
151
153
|
// ---------------------------------------------------------------------------
|
|
@@ -197,6 +199,11 @@ export interface ConfigureRequest {
|
|
|
197
199
|
poseRuntime?: string | null;
|
|
198
200
|
engine?: string | null;
|
|
199
201
|
};
|
|
202
|
+
/**
|
|
203
|
+
* Which remote engine to download. New SDKs send `'v4'` by default.
|
|
204
|
+
* Omitted (0.2.x) still means V3 on the server. `'v3'` forces the FSM bundle.
|
|
205
|
+
*/
|
|
206
|
+
engineChannel?: 'v3' | 'v4';
|
|
200
207
|
}
|
|
201
208
|
|
|
202
209
|
export interface PlanInfo {
|