@pose-tracker/react-native-pose-estimation-light 0.1.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.
Files changed (125) hide show
  1. package/LICENSE +45 -0
  2. package/PoseTrackerVision.podspec +28 -0
  3. package/README.md +161 -0
  4. package/THIRD_PARTY_NOTICES.md +19 -0
  5. package/ios/PoseTrackerVision/PoseTrackerBodyPosePlugin.m +18 -0
  6. package/ios/PoseTrackerVision/PoseTrackerBodyPosePlugin.swift +108 -0
  7. package/lib/PoseTrackerProvider.d.ts +102 -0
  8. package/lib/PoseTrackerProvider.js +204 -0
  9. package/lib/api/configure.d.ts +29 -0
  10. package/lib/api/configure.js +64 -0
  11. package/lib/api/skeleton.d.ts +17 -0
  12. package/lib/api/skeleton.js +53 -0
  13. package/lib/api/track.d.ts +61 -0
  14. package/lib/api/track.js +146 -0
  15. package/lib/backends/PoseBackend.d.ts +50 -0
  16. package/lib/backends/PoseBackend.js +11 -0
  17. package/lib/backends/vision/VisionPoseBackend.d.ts +72 -0
  18. package/lib/backends/vision/VisionPoseBackend.js +177 -0
  19. package/lib/backends/vision/mapVisionJoints.d.ts +35 -0
  20. package/lib/backends/vision/mapVisionJoints.js +162 -0
  21. package/lib/backends/vision/optionalVision.d.ts +32 -0
  22. package/lib/backends/vision/optionalVision.js +76 -0
  23. package/lib/backends/webview/WebViewPoseBackend.d.ts +223 -0
  24. package/lib/backends/webview/WebViewPoseBackend.js +382 -0
  25. package/lib/backends/webview/brandAssets.d.ts +5 -0
  26. package/lib/backends/webview/brandAssets.js +8 -0
  27. package/lib/backends/webview/onlineRuntime.d.ts +45 -0
  28. package/lib/backends/webview/onlineRuntime.js +57 -0
  29. package/lib/backends/webview/poseHtml.d.ts +71 -0
  30. package/lib/backends/webview/poseHtml.js +188 -0
  31. package/lib/backends/webview/poseRuntimeSource.d.ts +3 -0
  32. package/lib/backends/webview/poseRuntimeSource.js +4 -0
  33. package/lib/cache/obfuscate.d.ts +18 -0
  34. package/lib/cache/obfuscate.js +90 -0
  35. package/lib/camera/PoseCameraView.d.ts +51 -0
  36. package/lib/camera/PoseCameraView.js +99 -0
  37. package/lib/camera/WebViewPoseView.d.ts +81 -0
  38. package/lib/camera/WebViewPoseView.js +312 -0
  39. package/lib/client.d.ts +361 -0
  40. package/lib/client.js +1046 -0
  41. package/lib/diagnostics/logReport.d.ts +23 -0
  42. package/lib/diagnostics/logReport.js +102 -0
  43. package/lib/engine/EngineLoader.d.ts +95 -0
  44. package/lib/engine/EngineLoader.js +347 -0
  45. package/lib/engine/types.d.ts +83 -0
  46. package/lib/engine/types.js +11 -0
  47. package/lib/events/classicMessage.d.ts +31 -0
  48. package/lib/events/classicMessage.js +259 -0
  49. package/lib/exercises/aliases.d.ts +18 -0
  50. package/lib/exercises/aliases.js +48 -0
  51. package/lib/index.d.ts +72 -0
  52. package/lib/index.js +170 -0
  53. package/lib/models/poseModels.d.ts +39 -0
  54. package/lib/models/poseModels.js +63 -0
  55. package/lib/quality/AdaptiveQualityController.d.ts +120 -0
  56. package/lib/quality/AdaptiveQualityController.js +423 -0
  57. package/lib/quality/RuntimeGuard.d.ts +34 -0
  58. package/lib/quality/RuntimeGuard.js +105 -0
  59. package/lib/quality/captureMode.d.ts +82 -0
  60. package/lib/quality/captureMode.js +75 -0
  61. package/lib/quality/deviceCapability.d.ts +37 -0
  62. package/lib/quality/deviceCapability.js +177 -0
  63. package/lib/quality/profiles.d.ts +125 -0
  64. package/lib/quality/profiles.js +202 -0
  65. package/lib/runtime/RuntimeCache.d.ts +75 -0
  66. package/lib/runtime/RuntimeCache.js +230 -0
  67. package/lib/sdkVersion.d.ts +2 -0
  68. package/lib/sdkVersion.js +5 -0
  69. package/lib/support/optionalModules.d.ts +46 -0
  70. package/lib/support/optionalModules.js +82 -0
  71. package/lib/types/acceleration.d.ts +86 -0
  72. package/lib/types/acceleration.js +15 -0
  73. package/lib/types/events.d.ts +345 -0
  74. package/lib/types/events.js +10 -0
  75. package/lib/types/features.d.ts +90 -0
  76. package/lib/types/features.js +122 -0
  77. package/lib/types/manifest.d.ts +197 -0
  78. package/lib/types/manifest.js +10 -0
  79. package/lib/types/pose.d.ts +36 -0
  80. package/lib/types/pose.js +41 -0
  81. package/lib/types/preload.d.ts +17 -0
  82. package/lib/types/preload.js +10 -0
  83. package/lib/types/skeleton.d.ts +29 -0
  84. package/lib/types/skeleton.js +52 -0
  85. package/package.json +92 -0
  86. package/react-native.config.js +12 -0
  87. package/src/PoseTrackerProvider.tsx +332 -0
  88. package/src/api/configure.ts +82 -0
  89. package/src/api/skeleton.ts +60 -0
  90. package/src/api/track.ts +182 -0
  91. package/src/backends/PoseBackend.ts +59 -0
  92. package/src/backends/vision/VisionPoseBackend.ts +238 -0
  93. package/src/backends/vision/mapVisionJoints.ts +185 -0
  94. package/src/backends/vision/optionalVision.ts +94 -0
  95. package/src/backends/webview/WebViewPoseBackend.ts +582 -0
  96. package/src/backends/webview/brandAssets.ts +5 -0
  97. package/src/backends/webview/onlineRuntime.ts +94 -0
  98. package/src/backends/webview/poseHtml.ts +259 -0
  99. package/src/backends/webview/poseRuntimeSource.d.ts +3 -0
  100. package/src/cache/obfuscate.ts +94 -0
  101. package/src/camera/PoseCameraView.tsx +223 -0
  102. package/src/camera/WebViewPoseView.tsx +466 -0
  103. package/src/client.ts +1295 -0
  104. package/src/diagnostics/logReport.ts +131 -0
  105. package/src/engine/EngineLoader.ts +428 -0
  106. package/src/engine/types.ts +93 -0
  107. package/src/events/classicMessage.ts +271 -0
  108. package/src/exercises/aliases.ts +50 -0
  109. package/src/index.ts +243 -0
  110. package/src/models/poseModels.ts +93 -0
  111. package/src/quality/AdaptiveQualityController.ts +579 -0
  112. package/src/quality/RuntimeGuard.ts +119 -0
  113. package/src/quality/captureMode.ts +97 -0
  114. package/src/quality/deviceCapability.ts +187 -0
  115. package/src/quality/profiles.ts +253 -0
  116. package/src/runtime/RuntimeCache.ts +300 -0
  117. package/src/sdkVersion.ts +2 -0
  118. package/src/support/optionalModules.ts +114 -0
  119. package/src/types/acceleration.ts +94 -0
  120. package/src/types/events.ts +437 -0
  121. package/src/types/features.ts +176 -0
  122. package/src/types/manifest.ts +230 -0
  123. package/src/types/pose.ts +85 -0
  124. package/src/types/preload.ts +19 -0
  125. package/src/types/skeleton.ts +77 -0
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ /**
3
+ * Crash-loop guard for quality / runtime profiles.
4
+ *
5
+ * Analog of Sency's `TfliteRuntimeGuard` (PROBING → PASSED | FAILED):
6
+ * 1. Before activating a profile, `markProbing(key)`.
7
+ * 2. After a healthy ready signal, `markPassed(key)`.
8
+ * 3. If the app is killed/crashes while still PROBING, the next launch
9
+ * sees PROBING → treats it as FAILED → caller must pick a lower profile.
10
+ *
11
+ * Persisted via {@link KeyValueStore} (AsyncStorage when available). Without
12
+ * a store the guard is in-memory only (still useful within one process for
13
+ * the 30s same-process timeout).
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.RuntimeGuard = void 0;
17
+ const KEY_PREFIX = 'posetracker.quality.guard.';
18
+ /** Same-process: if still probing after this, treat as failed. */
19
+ const PROBING_TIMEOUT_MS = 30000;
20
+ class RuntimeGuard {
21
+ constructor(kv, sdkVersion) {
22
+ this.kv = kv;
23
+ this.sdkVersion = sdkVersion;
24
+ this.memory = new Map();
25
+ }
26
+ storageKey(profileKey) {
27
+ return `${KEY_PREFIX}${this.sdkVersion}.${profileKey}`;
28
+ }
29
+ async getState(profileKey) {
30
+ const key = this.storageKey(profileKey);
31
+ let record = this.memory.get(key) ?? null;
32
+ if (!record && this.kv) {
33
+ try {
34
+ const raw = await this.kv.getItem(key);
35
+ if (raw)
36
+ record = JSON.parse(raw);
37
+ }
38
+ catch {
39
+ record = null;
40
+ }
41
+ }
42
+ if (!record)
43
+ return 'unknown';
44
+ if (record.state === 'probing' &&
45
+ Date.now() - record.updatedAt > PROBING_TIMEOUT_MS) {
46
+ await this.write(profileKey, 'failed');
47
+ return 'failed';
48
+ }
49
+ return record.state;
50
+ }
51
+ /**
52
+ * If previous launch died while probing this key, mark failed and return true.
53
+ */
54
+ async consumeCrashIfProbing(profileKey) {
55
+ const state = await this.getState(profileKey);
56
+ if (state === 'probing') {
57
+ await this.write(profileKey, 'failed');
58
+ return true;
59
+ }
60
+ return false;
61
+ }
62
+ async markProbing(profileKey) {
63
+ await this.write(profileKey, 'probing');
64
+ }
65
+ async markPassed(profileKey) {
66
+ await this.write(profileKey, 'passed');
67
+ }
68
+ async markFailed(profileKey) {
69
+ await this.write(profileKey, 'failed');
70
+ }
71
+ async isFailed(profileKey) {
72
+ return (await this.getState(profileKey)) === 'failed';
73
+ }
74
+ /** Clear persisted/memory state for a profile (recover from stale FAILED). */
75
+ async clear(profileKey) {
76
+ const key = this.storageKey(profileKey);
77
+ this.memory.delete(key);
78
+ if (!this.kv)
79
+ return;
80
+ try {
81
+ await this.kv.removeItem(key);
82
+ }
83
+ catch {
84
+ /* best-effort */
85
+ }
86
+ }
87
+ async write(profileKey, state) {
88
+ const key = this.storageKey(profileKey);
89
+ const record = {
90
+ state,
91
+ updatedAt: Date.now(),
92
+ sdkVersion: this.sdkVersion,
93
+ };
94
+ this.memory.set(key, record);
95
+ if (!this.kv)
96
+ return;
97
+ try {
98
+ await this.kv.setItem(key, JSON.stringify(record));
99
+ }
100
+ catch {
101
+ /* persistence best-effort */
102
+ }
103
+ }
104
+ }
105
+ exports.RuntimeGuard = RuntimeGuard;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Camera capture + Android perf experiment knobs (WebView getUserMedia / MoveNet).
3
+ *
4
+ * iOS is never mutated by the Android-only flags below — {@link buildPoseHtml}
5
+ * forces frame-skip / soft-cap / preprocess overrides off on iOS.
6
+ *
7
+ * ## Experiment stack 2026-08-06
8
+ * 1. `device-native` cold open (Front-aligned) + optional Android soft-cap
9
+ * 2. Android frame-skip between inferences
10
+ * 3. Android lighter preprocess (`canvas-direct`)
11
+ * 4. Android min-target floor 10 FPS (downgrade only when truly needed)
12
+ * 5. Verbose perf debug in WebView stats / HUD
13
+ *
14
+ * ## REVERT to previous stable behaviour
15
+ * 1. `CAPTURE_CONSTRAINT_MODE = 'profile-constrained'`
16
+ * 2. `ENABLE_MALI_HARD_CAP = true`
17
+ * 3. `ANDROID_INFER_FRAME_SKIP = 0`
18
+ * 4. `ANDROID_PREPROCESS_PATH = 'imagebitmap'`
19
+ * 5. `ANDROID_SOFT_CAP_PROFILE = null` (experiment currently pins `'basic'`)
20
+ * 6. `ANDROID_MIN_TARGET_FPS = 15`
21
+ * 7. `ANDROID_PERF_DEBUG = false`
22
+ * 8. Rebuild: `npm run build:runtime-payload && npm run build`
23
+ * 9. Refresh testapp link + `npx expo start -c`
24
+ *
25
+ * You also have a zip archive of the previous SDK as a full rollback.
26
+ */
27
+ /** How getUserMedia chooses resolution on cold open. */
28
+ export type CaptureConstraintMode = 'device-native' | 'profile-constrained';
29
+ /**
30
+ * Letterbox preprocess path inside the WebView (Android experiment only).
31
+ * - `imagebitmap` — draw 192² canvas → createImageBitmap → fromPixels (legacy)
32
+ * - `canvas-direct` — draw 192² canvas → fromPixels(canvas) (skips ImageBitmap)
33
+ */
34
+ export type AndroidPreprocessPath = 'imagebitmap' | 'canvas-direct';
35
+ /** Soft-cap ladder ids (mirrors QualityProfileId; kept local to avoid import cycles). */
36
+ export type AndroidSoftCapProfile = 'prime' | 'pro' | 'lite' | 'ultralite' | 'basic';
37
+ /**
38
+ * - `device-native` — facingMode only (like PoseTrackerFront). Preview = device default (often HD).
39
+ * - `profile-constrained` — ideal width/height/frameRate from the AdaptiveChoice ladder.
40
+ */
41
+ export declare const CAPTURE_CONSTRAINT_MODE: CaptureConstraintMode;
42
+ /**
43
+ * When true, Mali GL renderers are hard-capped to UltraLite at capability score
44
+ * time (previous SDK default). Disable for the Front-aligned experiment so RN
45
+ * does not immediately setQuality() back down to 480×360 after a native HD open.
46
+ */
47
+ export declare const ENABLE_MALI_HARD_CAP = false;
48
+ /**
49
+ * Android-only: ready rAF ticks to skip between MoveNet inferences.
50
+ * Preview stays at full capture quality.
51
+ *
52
+ * - `0` — off
53
+ * - `1` — every other ready tick
54
+ * - `2` — every 3rd, etc.
55
+ */
56
+ export declare const ANDROID_INFER_FRAME_SKIP = 1;
57
+ /**
58
+ * Android-only preprocess path. iOS always uses `imagebitmap` (unchanged).
59
+ */
60
+ export declare const ANDROID_PREPROCESS_PATH: AndroidPreprocessPath;
61
+ /**
62
+ * Android-only cold-open soft cap. When set, getUserMedia uses this profile’s
63
+ * width/height as ideal+max even in `device-native` mode (so the device cannot
64
+ * open unrestricted 1080p). Set `null` for fully unconstrained facingMode-only.
65
+ *
66
+ * Mali-G52 evidence (2026-08-06): `basic` 240×320 ≈ 10 FPS median; `ultralite`
67
+ * 360×480 ≈ 8 FPS — pin soft-cap to `basic` to hold the 10 FPS floor.
68
+ * Live `setQuality` cannot go lower than basic.
69
+ */
70
+ export declare const ANDROID_SOFT_CAP_PROFILE: AndroidSoftCapProfile | null;
71
+ /**
72
+ * Android AdaptiveChoice floor (min target FPS). Downgrade when median
73
+ * inference ms stays above ~1.2 × (1000/floor). iOS stays on
74
+ * {@link MIN_TARGET_FPS_IOS} (30).
75
+ */
76
+ export declare const ANDROID_MIN_TARGET_FPS = 10;
77
+ /**
78
+ * When true (Android), WebView posts rich `experiments` + `leverHints` on each
79
+ * 1 Hz stats tick and mirrors a compact line on the HUD. RN logs the same via
80
+ * the existing diagnostic listener. iOS ignores this flag.
81
+ */
82
+ export declare const ANDROID_PERF_DEBUG = true;
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ /**
3
+ * Camera capture + Android perf experiment knobs (WebView getUserMedia / MoveNet).
4
+ *
5
+ * iOS is never mutated by the Android-only flags below — {@link buildPoseHtml}
6
+ * forces frame-skip / soft-cap / preprocess overrides off on iOS.
7
+ *
8
+ * ## Experiment stack 2026-08-06
9
+ * 1. `device-native` cold open (Front-aligned) + optional Android soft-cap
10
+ * 2. Android frame-skip between inferences
11
+ * 3. Android lighter preprocess (`canvas-direct`)
12
+ * 4. Android min-target floor 10 FPS (downgrade only when truly needed)
13
+ * 5. Verbose perf debug in WebView stats / HUD
14
+ *
15
+ * ## REVERT to previous stable behaviour
16
+ * 1. `CAPTURE_CONSTRAINT_MODE = 'profile-constrained'`
17
+ * 2. `ENABLE_MALI_HARD_CAP = true`
18
+ * 3. `ANDROID_INFER_FRAME_SKIP = 0`
19
+ * 4. `ANDROID_PREPROCESS_PATH = 'imagebitmap'`
20
+ * 5. `ANDROID_SOFT_CAP_PROFILE = null` (experiment currently pins `'basic'`)
21
+ * 6. `ANDROID_MIN_TARGET_FPS = 15`
22
+ * 7. `ANDROID_PERF_DEBUG = false`
23
+ * 8. Rebuild: `npm run build:runtime-payload && npm run build`
24
+ * 9. Refresh testapp link + `npx expo start -c`
25
+ *
26
+ * You also have a zip archive of the previous SDK as a full rollback.
27
+ */
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.ANDROID_PERF_DEBUG = exports.ANDROID_MIN_TARGET_FPS = exports.ANDROID_SOFT_CAP_PROFILE = exports.ANDROID_PREPROCESS_PATH = exports.ANDROID_INFER_FRAME_SKIP = exports.ENABLE_MALI_HARD_CAP = exports.CAPTURE_CONSTRAINT_MODE = void 0;
30
+ /**
31
+ * - `device-native` — facingMode only (like PoseTrackerFront). Preview = device default (often HD).
32
+ * - `profile-constrained` — ideal width/height/frameRate from the AdaptiveChoice ladder.
33
+ */
34
+ exports.CAPTURE_CONSTRAINT_MODE = 'device-native';
35
+ /**
36
+ * When true, Mali GL renderers are hard-capped to UltraLite at capability score
37
+ * time (previous SDK default). Disable for the Front-aligned experiment so RN
38
+ * does not immediately setQuality() back down to 480×360 after a native HD open.
39
+ */
40
+ exports.ENABLE_MALI_HARD_CAP = false;
41
+ /**
42
+ * Android-only: ready rAF ticks to skip between MoveNet inferences.
43
+ * Preview stays at full capture quality.
44
+ *
45
+ * - `0` — off
46
+ * - `1` — every other ready tick
47
+ * - `2` — every 3rd, etc.
48
+ */
49
+ exports.ANDROID_INFER_FRAME_SKIP = 1;
50
+ /**
51
+ * Android-only preprocess path. iOS always uses `imagebitmap` (unchanged).
52
+ */
53
+ exports.ANDROID_PREPROCESS_PATH = 'canvas-direct';
54
+ /**
55
+ * Android-only cold-open soft cap. When set, getUserMedia uses this profile’s
56
+ * width/height as ideal+max even in `device-native` mode (so the device cannot
57
+ * open unrestricted 1080p). Set `null` for fully unconstrained facingMode-only.
58
+ *
59
+ * Mali-G52 evidence (2026-08-06): `basic` 240×320 ≈ 10 FPS median; `ultralite`
60
+ * 360×480 ≈ 8 FPS — pin soft-cap to `basic` to hold the 10 FPS floor.
61
+ * Live `setQuality` cannot go lower than basic.
62
+ */
63
+ exports.ANDROID_SOFT_CAP_PROFILE = 'basic';
64
+ /**
65
+ * Android AdaptiveChoice floor (min target FPS). Downgrade when median
66
+ * inference ms stays above ~1.2 × (1000/floor). iOS stays on
67
+ * {@link MIN_TARGET_FPS_IOS} (30).
68
+ */
69
+ exports.ANDROID_MIN_TARGET_FPS = 10;
70
+ /**
71
+ * When true (Android), WebView posts rich `experiments` + `leverHints` on each
72
+ * 1 Hz stats tick and mirrors a compact line on the HUD. RN logs the same via
73
+ * the existing diagnostic listener. iOS ignores this flag.
74
+ */
75
+ exports.ANDROID_PERF_DEBUG = true;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Lightweight device capability score → initial quality profile.
3
+ *
4
+ * Sency's AdaptiveChoice uses RAM / CPU freq / first API level. In Expo Go we
5
+ * only have a subset of signals without extra native deps; we combine:
6
+ * - Platform (iOS is typically 3–5× faster on WebGL MoveNet)
7
+ * - Android API level
8
+ * - Optional RAM via expo-device / react-native DeviceInfo when present
9
+ * - GL renderer hint (Mali mid-range) once known from the WebView
10
+ *
11
+ * Score is 0–100; mapped to a QualityProfileId. Brand/year caps from Sency
12
+ * are approximated via API level + Mali detection.
13
+ */
14
+ import type { QualityProfileId } from './profiles';
15
+ export interface DeviceCapabilitySnapshot {
16
+ platform: 'ios' | 'android' | 'other';
17
+ /** Android API level or iOS major version when available. */
18
+ osVersion: number | null;
19
+ /** Total device RAM in GiB when a probe is available. */
20
+ totalMemoryGiB: number | null;
21
+ /** WebGL UNMASKED_RENDERER when known (after first WebView ready). */
22
+ glRenderer: string | null;
23
+ /** 0–100 composite score. */
24
+ score: number;
25
+ /** Profile suggested by the score (before crash-guard / caps). */
26
+ suggestedProfile: QualityProfileId;
27
+ reasons: string[];
28
+ }
29
+ export declare function isMaliRenderer(renderer: string | null | undefined): boolean;
30
+ /**
31
+ * Map capability score → profile (before Mali / crash-guard caps).
32
+ * Thresholds calibrated so mid-range Android (Mali-G52 era) lands on UltraLite.
33
+ */
34
+ export declare function profileFromScore(score: number): QualityProfileId;
35
+ export declare function scoreDeviceCapability(options?: {
36
+ glRenderer?: string | null;
37
+ }): DeviceCapabilitySnapshot;
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ /**
3
+ * Lightweight device capability score → initial quality profile.
4
+ *
5
+ * Sency's AdaptiveChoice uses RAM / CPU freq / first API level. In Expo Go we
6
+ * only have a subset of signals without extra native deps; we combine:
7
+ * - Platform (iOS is typically 3–5× faster on WebGL MoveNet)
8
+ * - Android API level
9
+ * - Optional RAM via expo-device / react-native DeviceInfo when present
10
+ * - GL renderer hint (Mali mid-range) once known from the WebView
11
+ *
12
+ * Score is 0–100; mapped to a QualityProfileId. Brand/year caps from Sency
13
+ * are approximated via API level + Mali detection.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.isMaliRenderer = isMaliRenderer;
17
+ exports.profileFromScore = profileFromScore;
18
+ exports.scoreDeviceCapability = scoreDeviceCapability;
19
+ const react_native_1 = require("react-native");
20
+ const captureMode_1 = require("./captureMode");
21
+ function probeTotalMemoryGiB() {
22
+ // Optional peers — never required.
23
+ try {
24
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
25
+ const Device = require('expo-device');
26
+ const bytes = Device.totalMemory;
27
+ if (typeof bytes === 'number' && bytes > 0)
28
+ return bytes / (1024 * 1024 * 1024);
29
+ }
30
+ catch {
31
+ /* ignore */
32
+ }
33
+ try {
34
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
35
+ const DeviceInfo = require('react-native-device-info');
36
+ // sync API may exist
37
+ if (typeof DeviceInfo.getTotalMemorySync === 'function') {
38
+ const bytes = DeviceInfo.getTotalMemorySync();
39
+ if (typeof bytes === 'number' && bytes > 0)
40
+ return bytes / (1024 * 1024 * 1024);
41
+ }
42
+ }
43
+ catch {
44
+ /* ignore */
45
+ }
46
+ return null;
47
+ }
48
+ function parseOsVersion() {
49
+ const v = react_native_1.Platform.Version;
50
+ if (typeof v === 'number')
51
+ return v;
52
+ if (typeof v === 'string') {
53
+ const major = parseInt(v.split('.')[0] ?? '', 10);
54
+ return Number.isFinite(major) ? major : null;
55
+ }
56
+ return null;
57
+ }
58
+ function isMaliRenderer(renderer) {
59
+ return !!(renderer && /mali/i.test(renderer));
60
+ }
61
+ /**
62
+ * Map capability score → profile (before Mali / crash-guard caps).
63
+ * Thresholds calibrated so mid-range Android (Mali-G52 era) lands on UltraLite.
64
+ */
65
+ function profileFromScore(score) {
66
+ if (score >= 78)
67
+ return 'prime';
68
+ if (score >= 62)
69
+ return 'pro';
70
+ if (score >= 48)
71
+ return 'lite';
72
+ if (score >= 32)
73
+ return 'ultralite';
74
+ return 'basic';
75
+ }
76
+ function scoreDeviceCapability(options) {
77
+ const reasons = [];
78
+ const platform = react_native_1.Platform.OS === 'ios' ? 'ios' : react_native_1.Platform.OS === 'android' ? 'android' : 'other';
79
+ const osVersion = parseOsVersion();
80
+ const totalMemoryGiB = probeTotalMemoryGiB();
81
+ const glRenderer = options?.glRenderer ?? null;
82
+ let score = 0;
83
+ if (platform === 'ios') {
84
+ // Apple GPU WebGL MoveNet is typically <20 ms — start high.
85
+ score += 55;
86
+ reasons.push('platform=ios (+55)');
87
+ if (osVersion != null) {
88
+ if (osVersion >= 17) {
89
+ score += 15;
90
+ reasons.push(`ios>=17 (+15)`);
91
+ }
92
+ else if (osVersion >= 15) {
93
+ score += 10;
94
+ reasons.push(`ios>=15 (+10)`);
95
+ }
96
+ else {
97
+ score += 5;
98
+ reasons.push(`ios legacy (+5)`);
99
+ }
100
+ }
101
+ }
102
+ else if (platform === 'android') {
103
+ score += 18;
104
+ reasons.push('platform=android (+18)');
105
+ if (osVersion != null) {
106
+ if (osVersion >= 34) {
107
+ score += 14;
108
+ reasons.push(`api>=34 (+14)`);
109
+ }
110
+ else if (osVersion >= 31) {
111
+ score += 10;
112
+ reasons.push(`api>=31 (+10)`);
113
+ }
114
+ else if (osVersion >= 28) {
115
+ score += 6;
116
+ reasons.push(`api>=28 (+6)`);
117
+ }
118
+ else {
119
+ reasons.push(`api=${osVersion} (+0)`);
120
+ }
121
+ }
122
+ }
123
+ else {
124
+ score += 20;
125
+ reasons.push('platform=other (+20)');
126
+ }
127
+ if (totalMemoryGiB != null) {
128
+ if (totalMemoryGiB >= 8) {
129
+ score += 20;
130
+ reasons.push(`ram>=8GiB (+20)`);
131
+ }
132
+ else if (totalMemoryGiB >= 6) {
133
+ score += 14;
134
+ reasons.push(`ram>=6GiB (+14)`);
135
+ }
136
+ else if (totalMemoryGiB >= 4) {
137
+ score += 8;
138
+ reasons.push(`ram>=4GiB (+8)`);
139
+ }
140
+ else {
141
+ reasons.push(`ram=${totalMemoryGiB.toFixed(1)}GiB (+0)`);
142
+ }
143
+ }
144
+ else {
145
+ // Neutral default when RAM unknown: don't assume high-end.
146
+ score += 6;
147
+ reasons.push('ram=unknown (+6)');
148
+ }
149
+ if (isMaliRenderer(glRenderer)) {
150
+ // Mali mid-range: HD camera preprocess alone can cost ~100 ms/frame.
151
+ score = Math.min(score, 40);
152
+ reasons.push('gl=Mali cap score≤40');
153
+ }
154
+ score = Math.max(0, Math.min(100, score));
155
+ let suggested = profileFromScore(score);
156
+ // Hard caps (Sency-style).
157
+ if (platform === 'android' && suggested === 'prime') {
158
+ suggested = 'pro';
159
+ reasons.push('android cap: prime→pro');
160
+ }
161
+ // Mali → ultralite hard cap: gated by captureMode (see captureMode.ts REVERT).
162
+ if (captureMode_1.ENABLE_MALI_HARD_CAP &&
163
+ isMaliRenderer(glRenderer) &&
164
+ (suggested === 'prime' || suggested === 'pro')) {
165
+ suggested = 'ultralite';
166
+ reasons.push('mali cap: →ultralite');
167
+ }
168
+ return {
169
+ platform,
170
+ osVersion,
171
+ totalMemoryGiB,
172
+ glRenderer,
173
+ score,
174
+ suggestedProfile: suggested,
175
+ reasons,
176
+ };
177
+ }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Camera / preprocess quality tiers for the WebView MoveNet runtime.
3
+ *
4
+ * Inspired by Sency's PoseModelChoice ladder (Prime → … → Basic), adapted to
5
+ * our stack: MoveNet Lightning always runs at 192×192; what changes is the
6
+ * **camera capture resolution**.
7
+ *
8
+ * FPS policy uses a **minimum target** (floor), not a single setpoint:
9
+ * - iOS: min 30 — acceptable/ideal band roughly 30–50+ FPS
10
+ * - Android: min from {@link ANDROID_MIN_TARGET_FPS} (experiment default 10)
11
+ *
12
+ * Warm-up benches estimate execute cost; AdaptiveChoice picks a capture
13
+ * profile before getUserMedia when the estimate is below the platform floor.
14
+ */
15
+ export type QualityProfileId = 'prime' | 'pro' | 'lite' | 'ultralite' | 'basic';
16
+ /** Host-facing choice, mirrors Sency's AdaptiveChoice + explicit override. */
17
+ export type QualityChoice = 'AdaptiveChoice' | QualityProfileId;
18
+ /**
19
+ * Host trade-off between pose FPS and camera preview sharpness.
20
+ *
21
+ * - `performance` (default) — AdaptiveChoice may lower capture (esp. Android
22
+ * soft-cap / warm-up / live downgrade) so inference stays near the platform
23
+ * FPS floor. Preview can look soft on mid-range Android.
24
+ * - `quality` — keep high capture / skip FPS-driven capture downgrades.
25
+ * Preview stays sharp; pose FPS may drop well below the floor. The SDK still
26
+ * emits `performance_warning` when slow.
27
+ *
28
+ * Explicit {@link QualityChoice} profile ids still pin the starting tier;
29
+ * `quality` only disables automatic FPS-driven drops.
30
+ */
31
+ export type CapturePriority = 'performance' | 'quality';
32
+ export type QualityPlatform = 'ios' | 'android' | 'other';
33
+ export interface FpsRange {
34
+ /** Soft lower bound of the “good experience” band (same as min target on that OS). */
35
+ min: number;
36
+ /**
37
+ * Soft upper reference for docs / UI (“we’re happy around here”).
38
+ * Not a hard cap — exceeding it is fine.
39
+ */
40
+ idealMax: number;
41
+ }
42
+ export interface QualityProfile {
43
+ id: QualityProfileId;
44
+ /** Human label for diagnostics / docs. */
45
+ label: string;
46
+ /** getUserMedia ideal width (CSS pixels / sensor). */
47
+ idealWidth: number;
48
+ /** getUserMedia ideal height. */
49
+ idealHeight: number;
50
+ /** getUserMedia ideal frameRate. */
51
+ idealFrameRate: number;
52
+ /**
53
+ * Per-profile floor used when AdaptiveChoice has not overridden via platform.
54
+ * Live controller uses {@link getMinTargetFps} instead.
55
+ */
56
+ minStableFps: number;
57
+ }
58
+ /**
59
+ * Ordered highest → lowest. `nextLowerQualityProfile` walks this ladder.
60
+ */
61
+ export declare const QUALITY_LADDER: readonly QualityProfileId[];
62
+ /**
63
+ * Android minimum acceptable inference FPS (floor).
64
+ * Sourced from {@link ANDROID_MIN_TARGET_FPS} so the experiment flag is the
65
+ * single knob (see captureMode.ts).
66
+ */
67
+ export declare const MIN_TARGET_FPS_ANDROID = 10;
68
+ /**
69
+ * iOS minimum acceptable inference FPS (floor).
70
+ * Acceptable/ideal experience is typically 30–50+ on Apple GPU.
71
+ */
72
+ export declare const MIN_TARGET_FPS_IOS = 30;
73
+ /** Fallback floor for unknown platforms. */
74
+ export declare const MIN_TARGET_FPS_DEFAULT = 10;
75
+ /**
76
+ * Documented “good experience” bands (not hard caps).
77
+ * Downgrades / warnings use the **min** only.
78
+ */
79
+ export declare const IDEAL_FPS_RANGE_IOS: FpsRange;
80
+ export declare const IDEAL_FPS_RANGE_ANDROID: FpsRange;
81
+ /**
82
+ * @deprecated Use {@link getMinTargetFps} / {@link MIN_TARGET_FPS_ANDROID}.
83
+ * Kept as the Android floor so older imports keep a sensible number.
84
+ */
85
+ export declare const TARGET_FPS = 10;
86
+ /** @deprecated Use {@link minMedianMsForMinTarget}. */
87
+ export declare const TARGET_MEDIAN_MS: number;
88
+ /**
89
+ * Critical alert when FPS stays well below the platform minimum target
90
+ * (even on `basic`). Android: &lt;10; iOS: &lt;20.
91
+ */
92
+ export declare function getCriticalFpsThreshold(platform?: QualityPlatform): number;
93
+ /** @deprecated Prefer {@link getCriticalFpsThreshold}. */
94
+ export declare const CRITICAL_FPS_THRESHOLD = 10;
95
+ /** Consecutive samples below min-target before auto-downgrade. */
96
+ /** Sustained samples below the inference budget before a live downgrade. */
97
+ export declare const LOW_FPS_STREAK_BEFORE_DOWNGRADE = 4;
98
+ /**
99
+ * Ignore live downgrade decisions for this long after ready / a quality swap
100
+ * (camera restart tanks the first 1 Hz stats samples).
101
+ */
102
+ export declare const QUALITY_SETTLE_MS = 5000;
103
+ export declare const QUALITY_PROFILES: Record<QualityProfileId, QualityProfile>;
104
+ export declare function currentQualityPlatform(): QualityPlatform;
105
+ /** Platform minimum target FPS (floor — not a setpoint). */
106
+ export declare function getMinTargetFps(platform?: QualityPlatform): number;
107
+ export declare function getIdealFpsRange(platform?: QualityPlatform): FpsRange;
108
+ /** Max warm-up median ms that still meets a given min-target FPS. */
109
+ export declare function minMedianMsForMinTarget(minTargetFps: number): number;
110
+ export declare function getQualityProfile(id: QualityProfileId): QualityProfile;
111
+ export declare function nextLowerQualityProfile(id: QualityProfileId): QualityProfileId | null;
112
+ export declare function isQualityProfileId(value: string): value is QualityProfileId;
113
+ export declare function qualityLadderIndex(id: QualityProfileId): number;
114
+ /** Lower or equal quality (higher index) wins. */
115
+ export declare function lowerQualityProfile(a: QualityProfileId, b: QualityProfileId): QualityProfileId;
116
+ /**
117
+ * Map warm-up execute median (zeros, no camera) → max safe capture profile.
118
+ *
119
+ * Thresholds scale with the platform **minimum target** FPS. Warm-up ignores
120
+ * bitmap cost, so anything over the floor budget must stay on `basic` —
121
+ * the old `≤ 1.3× budget → ultralite` band upgraded Mali devices that were
122
+ * already below the floor (e.g. 120 ms zeros → ultralite → live ~8 FPS).
123
+ */
124
+ export declare function profileFromWarmupMedianMs(medianMs: number | null, minTargetFps?: number): QualityProfileId;
125
+ export declare function estimatedFpsFromMedianMs(medianMs: number | null): number | null;