@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,223 @@
1
+ /**
2
+ * WebView inference backend — the SDK's base runtime on BOTH platforms.
3
+ *
4
+ * Runs MoveNet SinglePose Lightning (17 keypoints, 192×192) inside a real
5
+ * browser WebGL context (Chromium ANGLE on Android, WKWebView on iOS) — the
6
+ * same approach the PoseTracker WebView product uses in production, and the
7
+ * only one real-time across the whole Android park in Expo Go. Fully
8
+ * offline: TF.js and the model ship inside the npm package (poseHtml.ts).
9
+ *
10
+ * The WebView owns the camera (`getUserMedia`). Frames are NOT pushed from
11
+ * React Native — `estimatePose()` returns the latest pose posted by the page.
12
+ * Mount {@link WebViewPoseView} (or a 1×1 warmer) to attach the runtime.
13
+ */
14
+ import type { PoseBackend, PoseBackendInitOptions, PoseInputFrame } from '../PoseBackend';
15
+ import type { Pose } from '../../types/pose';
16
+ import type { AccelerationDiagnostics, AccelerationState, DiagnosticListener } from '../../types/acceleration';
17
+ import type { PoseTrackerEvent } from '../../types/events';
18
+ export type WebViewPoseMessage = {
19
+ type: 'ready';
20
+ backend: string;
21
+ medianInferenceMs: number | null;
22
+ warmUpRunsMs: number[];
23
+ estimatedFps?: number | null;
24
+ profileId?: string;
25
+ /** false after basic (model-only) cold-start; true once getUserMedia ran. */
26
+ cameraOpened?: boolean;
27
+ coldStart?: 'basic' | 'full';
28
+ gl: {
29
+ renderer: string | null;
30
+ vendor: string | null;
31
+ version: string | null;
32
+ } | null;
33
+ note?: string | null;
34
+ } | {
35
+ type: 'warmup_estimate';
36
+ medianInferenceMs: number | null;
37
+ estimatedFps: number | null;
38
+ /** Platform floor (iOS 30 / Android 15). */
39
+ minTargetFps: number;
40
+ /** @deprecated Use {@link minTargetFps}. */
41
+ targetFps?: number;
42
+ profileId: string;
43
+ previousProfileId?: string;
44
+ idealWidth?: number;
45
+ idealHeight?: number;
46
+ gl?: {
47
+ renderer: string | null;
48
+ vendor: string | null;
49
+ version: string | null;
50
+ } | null;
51
+ } | {
52
+ type: 'pose';
53
+ keypoints: Array<{
54
+ name: string;
55
+ x: number;
56
+ y: number;
57
+ score: number;
58
+ }>;
59
+ score: number;
60
+ inferenceTimeMs: number;
61
+ timestampMs: number;
62
+ } | {
63
+ type: 'stats';
64
+ fps: number;
65
+ medianInferenceMs: number | null;
66
+ estimatedFpsFromMedian?: number | null;
67
+ frames: number;
68
+ backend: string;
69
+ breakdown?: {
70
+ drawMs: number | null;
71
+ fromPixelsMs: number | null;
72
+ executeMs: number | null;
73
+ totalPipelineMs?: number | null;
74
+ };
75
+ windowCounts?: {
76
+ inferred: number;
77
+ skipped: number;
78
+ };
79
+ experiments?: Record<string, unknown>;
80
+ leverHints?: {
81
+ dominantStage?: string;
82
+ dominantMs?: number;
83
+ bitmapShare?: number;
84
+ fromPixelsShare?: number;
85
+ executeShare?: number;
86
+ hints?: string[];
87
+ } | null;
88
+ videoSize?: string;
89
+ mode?: string;
90
+ } | {
91
+ type: 'initialization';
92
+ message: string;
93
+ ready?: boolean;
94
+ step?: 'accessing_webcam' | 'loading_pose_model' | 'ready';
95
+ } | {
96
+ type: 'warning';
97
+ message: string;
98
+ code?: string;
99
+ } | {
100
+ type: 'diag';
101
+ message: string;
102
+ } | {
103
+ type: 'error';
104
+ message: string;
105
+ code?: string;
106
+ };
107
+ export interface WebViewPoseStats {
108
+ fps: number;
109
+ medianInferenceMs: number | null;
110
+ estimatedFpsFromMedian?: number | null;
111
+ frames: number;
112
+ backend: string;
113
+ videoSize?: string;
114
+ mode?: string;
115
+ breakdown?: {
116
+ drawMs: number | null;
117
+ fromPixelsMs: number | null;
118
+ executeMs: number | null;
119
+ totalPipelineMs?: number | null;
120
+ };
121
+ windowCounts?: {
122
+ inferred: number;
123
+ skipped: number;
124
+ };
125
+ experiments?: Record<string, unknown>;
126
+ leverHints?: {
127
+ dominantStage?: string;
128
+ dominantMs?: number;
129
+ bitmapShare?: number;
130
+ fromPixelsShare?: number;
131
+ executeShare?: number;
132
+ hints?: string[];
133
+ } | null;
134
+ }
135
+ export interface WebViewPoseBackendOptions {
136
+ onDiagnostic?: DiagnosticListener;
137
+ onAccelerationChange?: (state: AccelerationState) => void;
138
+ /** Fired on every pose from the WebView (used by WebViewPoseView / ingest). */
139
+ onPose?: (pose: Pose, inferenceTimeMs: number) => void;
140
+ /** Fired when the Chromium page reports ready (model + backend warmed). */
141
+ onReady?: (info: {
142
+ backend: string;
143
+ medianInferenceMs: number | null;
144
+ estimatedFps?: number | null;
145
+ profileId?: string | null;
146
+ glRenderer: string | null;
147
+ }) => void;
148
+ /**
149
+ * Fired after zeros warm-up, typically *before* getUserMedia — used to pick
150
+ * a capture profile targeting ≥15 FPS.
151
+ */
152
+ onWarmupEstimate?: (info: {
153
+ medianInferenceMs: number | null;
154
+ estimatedFps: number | null;
155
+ profileId: string;
156
+ glRenderer: string | null;
157
+ }) => void;
158
+ /** Fired ~1 Hz with inference FPS / breakdown (adaptive quality input). */
159
+ onStats?: (stats: WebViewPoseStats) => void;
160
+ /**
161
+ * Promotes page-level init / warning / error into typed {@link PoseTrackerEvent}s
162
+ * so the host can listen the same way as the classic PoseTracker WebView.
163
+ */
164
+ onTrackerEvent?: (event: PoseTrackerEvent) => void;
165
+ }
166
+ export declare class WebViewPoseBackend implements PoseBackend {
167
+ readonly name = "webview-movenet";
168
+ private acceleration;
169
+ private attached;
170
+ private ready;
171
+ private warm;
172
+ /** True after the page successfully opened getUserMedia. */
173
+ private cameraOpened;
174
+ private openCameraHandler;
175
+ private lastPose;
176
+ private inferenceTimesMs;
177
+ private medianInferenceMs;
178
+ private glRenderer;
179
+ private glVendor;
180
+ private glVersion;
181
+ private tfjsBackend;
182
+ private reasons;
183
+ private readyWaiters;
184
+ private cameraWaiters;
185
+ private readonly onDiagnostic;
186
+ private readonly onAccelerationChange;
187
+ private onPose;
188
+ private onReady;
189
+ private onWarmupEstimate;
190
+ private onStats;
191
+ private onTrackerEvent;
192
+ constructor(options?: WebViewPoseBackendOptions);
193
+ /** Host view calls this when the WebView mounts / unmounts. */
194
+ setAttached(attached: boolean): void;
195
+ setOnPose(handler: ((pose: Pose, inferenceTimeMs: number) => void) | undefined): void;
196
+ setOnReady(handler: WebViewPoseBackendOptions['onReady']): void;
197
+ setOnStats(handler: WebViewPoseBackendOptions['onStats']): void;
198
+ /** Feed a message from the WebView `onMessage` handler. */
199
+ handleMessage(raw: string): void;
200
+ init(_options: PoseBackendInitOptions): Promise<void>;
201
+ warmup(): Promise<void>;
202
+ /** Whether getUserMedia has already run in the attached page. */
203
+ isCameraOpened(): boolean;
204
+ /**
205
+ * Host injects `__PT_OPEN_CAMERA` via {@link setOpenCameraHandler}.
206
+ * Used when upgrading basic → full cold-start without remounting the page.
207
+ */
208
+ setOpenCameraHandler(handler: (() => void) | undefined): void;
209
+ /**
210
+ * Open getUserMedia after a basic (model-only) ready. No-op if the page
211
+ * already opened the camera (coldStart=full boot).
212
+ */
213
+ openCamera(): Promise<void>;
214
+ estimatePose(_frame: PoseInputFrame): Promise<Pose | null>;
215
+ getAcceleration(): AccelerationDiagnostics | null;
216
+ dispose(): Promise<void>;
217
+ private waitUntilReady;
218
+ private waitUntilCameraOpened;
219
+ private flushReadyWaiters;
220
+ private flushCameraWaiters;
221
+ private setAcceleration;
222
+ }
223
+ export declare function isWebViewPoseBackend(backend: PoseBackend): backend is WebViewPoseBackend;
@@ -0,0 +1,382 @@
1
+ "use strict";
2
+ /**
3
+ * WebView inference backend — the SDK's base runtime on BOTH platforms.
4
+ *
5
+ * Runs MoveNet SinglePose Lightning (17 keypoints, 192×192) inside a real
6
+ * browser WebGL context (Chromium ANGLE on Android, WKWebView on iOS) — the
7
+ * same approach the PoseTracker WebView product uses in production, and the
8
+ * only one real-time across the whole Android park in Expo Go. Fully
9
+ * offline: TF.js and the model ship inside the npm package (poseHtml.ts).
10
+ *
11
+ * The WebView owns the camera (`getUserMedia`). Frames are NOT pushed from
12
+ * React Native — `estimatePose()` returns the latest pose posted by the page.
13
+ * Mount {@link WebViewPoseView} (or a 1×1 warmer) to attach the runtime.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.WebViewPoseBackend = void 0;
17
+ exports.isWebViewPoseBackend = isWebViewPoseBackend;
18
+ class WebViewPoseBackend {
19
+ constructor(options = {}) {
20
+ this.name = 'webview-movenet';
21
+ this.acceleration = 'unknown';
22
+ this.attached = false;
23
+ this.ready = false;
24
+ this.warm = false;
25
+ /** True after the page successfully opened getUserMedia. */
26
+ this.cameraOpened = false;
27
+ this.lastPose = null;
28
+ this.inferenceTimesMs = [];
29
+ this.medianInferenceMs = null;
30
+ this.glRenderer = null;
31
+ this.glVendor = null;
32
+ this.glVersion = null;
33
+ this.tfjsBackend = null;
34
+ this.reasons = [];
35
+ this.readyWaiters = [];
36
+ this.cameraWaiters = [];
37
+ this.onDiagnostic = options.onDiagnostic;
38
+ this.onAccelerationChange = options.onAccelerationChange;
39
+ this.onPose = options.onPose;
40
+ this.onReady = options.onReady;
41
+ this.onWarmupEstimate = options.onWarmupEstimate;
42
+ this.onStats = options.onStats;
43
+ this.onTrackerEvent = options.onTrackerEvent;
44
+ }
45
+ /** Host view calls this when the WebView mounts / unmounts. */
46
+ setAttached(attached) {
47
+ this.attached = attached;
48
+ this.onDiagnostic?.(`[posetracker] WebViewPoseBackend attached=${attached} ready=${this.ready}`);
49
+ if (!attached) {
50
+ this.ready = false;
51
+ this.warm = false;
52
+ this.cameraOpened = false;
53
+ this.openCameraHandler = undefined;
54
+ // Drop stale GPU verdict from a previous warmer/page so the host does
55
+ // not show acceleration=gpu while the next WebView is still booting.
56
+ this.setAcceleration('unknown');
57
+ }
58
+ }
59
+ setOnPose(handler) {
60
+ this.onPose = handler;
61
+ }
62
+ setOnReady(handler) {
63
+ this.onReady = handler;
64
+ }
65
+ setOnStats(handler) {
66
+ this.onStats = handler;
67
+ }
68
+ /** Feed a message from the WebView `onMessage` handler. */
69
+ handleMessage(raw) {
70
+ let msg;
71
+ try {
72
+ msg = JSON.parse(raw);
73
+ }
74
+ catch {
75
+ return;
76
+ }
77
+ if (msg.type === 'warmup_estimate') {
78
+ this.glRenderer = msg.gl?.renderer ?? this.glRenderer;
79
+ this.medianInferenceMs = msg.medianInferenceMs;
80
+ const minTarget = msg.minTargetFps ?? msg.targetFps;
81
+ this.onDiagnostic?.(`[posetracker] WebView warmup_estimate fps=${msg.estimatedFps != null ? msg.estimatedFps.toFixed(1) : '?'} medianMs=${msg.medianInferenceMs != null ? msg.medianInferenceMs.toFixed(1) : '?'} ` +
82
+ `profile=${msg.profileId} minTarget=${minTarget}`);
83
+ this.onWarmupEstimate?.({
84
+ medianInferenceMs: msg.medianInferenceMs,
85
+ estimatedFps: msg.estimatedFps,
86
+ profileId: msg.profileId,
87
+ glRenderer: this.glRenderer,
88
+ });
89
+ return;
90
+ }
91
+ if (msg.type === 'ready') {
92
+ this.ready = true;
93
+ this.warm = true;
94
+ if (msg.cameraOpened === true) {
95
+ this.cameraOpened = true;
96
+ this.flushCameraWaiters();
97
+ }
98
+ this.tfjsBackend = msg.backend;
99
+ this.medianInferenceMs = msg.medianInferenceMs;
100
+ this.inferenceTimesMs = msg.warmUpRunsMs ?? [];
101
+ this.glRenderer = msg.gl?.renderer ?? null;
102
+ this.glVendor = msg.gl?.vendor ?? null;
103
+ this.glVersion = msg.gl?.version ?? null;
104
+ const med = msg.medianInferenceMs;
105
+ // Chromium WebGL on Android mid-range is typically 15–80 ms — treat as GPU.
106
+ this.setAcceleration(med != null && med > 200 ? 'cpu-fallback' : 'gpu');
107
+ this.onDiagnostic?.(`[posetracker] WebView Chromium ready backend=${msg.backend} ` +
108
+ `medianMs=${med != null ? med.toFixed(1) : 'n/a'} ` +
109
+ `estFps=${msg.estimatedFps != null ? msg.estimatedFps.toFixed(1) : 'n/a'} ` +
110
+ `profile=${msg.profileId ?? 'n/a'} coldStart=${msg.coldStart ?? '?'} ` +
111
+ `camera=${msg.cameraOpened === true ? 'on' : 'deferred'} ` +
112
+ `renderer=${this.glRenderer ?? 'n/a'}` +
113
+ (msg.note ? ` note=${msg.note}` : ''));
114
+ this.onReady?.({
115
+ backend: msg.backend,
116
+ medianInferenceMs: med,
117
+ estimatedFps: msg.estimatedFps,
118
+ profileId: msg.profileId,
119
+ glRenderer: this.glRenderer,
120
+ });
121
+ this.onTrackerEvent?.({
122
+ type: 'initialization',
123
+ step: 'ready',
124
+ message: 'running',
125
+ ready: true,
126
+ });
127
+ this.flushReadyWaiters();
128
+ return;
129
+ }
130
+ if (msg.type === 'initialization') {
131
+ const step = msg.step ?? (msg.ready ? 'ready' : 'loading_pose_model');
132
+ this.onDiagnostic?.(`[posetracker] WebView init: ${msg.message} ready=${Boolean(msg.ready)}`);
133
+ this.onTrackerEvent?.({
134
+ type: 'initialization',
135
+ step,
136
+ message: msg.message,
137
+ ready: Boolean(msg.ready),
138
+ });
139
+ return;
140
+ }
141
+ if (msg.type === 'warning') {
142
+ this.onDiagnostic?.(`[posetracker] WebView warning: ${msg.message}`);
143
+ this.onTrackerEvent?.({
144
+ type: 'warning',
145
+ code: 'webview',
146
+ message: msg.message,
147
+ timestampMs: Date.now(),
148
+ });
149
+ return;
150
+ }
151
+ if (msg.type === 'error') {
152
+ // Mid-boot quality swaps abort getUserMedia; ignore so warmup can finish.
153
+ if (/abort/i.test(msg.message)) {
154
+ this.onDiagnostic?.(`[posetracker] WebView pose error ignored (abort during quality swap): ${msg.message}`);
155
+ return;
156
+ }
157
+ this.reasons.push(msg.message);
158
+ this.onDiagnostic?.(`[posetracker] WebView pose error: ${msg.message}`);
159
+ this.onTrackerEvent?.({
160
+ type: 'error',
161
+ code: 'webview_error',
162
+ message: msg.message,
163
+ });
164
+ const err = new Error(msg.message);
165
+ for (const w of this.readyWaiters)
166
+ w.reject(err);
167
+ this.readyWaiters = [];
168
+ return;
169
+ }
170
+ if (msg.type === 'diag') {
171
+ this.onDiagnostic?.(`[posetracker] WebView diag: ${msg.message}`);
172
+ return;
173
+ }
174
+ if (msg.type === 'stats') {
175
+ this.medianInferenceMs = msg.medianInferenceMs;
176
+ this.tfjsBackend = msg.backend;
177
+ const b = msg.breakdown;
178
+ const fmt = (v) => (v != null ? v.toFixed(1) : 'n/a');
179
+ const wc = msg.windowCounts;
180
+ const lh = msg.leverHints;
181
+ this.onDiagnostic?.(`[posetracker] WebView stats fps=${msg.fps} medianMs=${msg.medianInferenceMs != null ? Math.round(msg.medianInferenceMs) : 'n/a'}` +
182
+ (msg.estimatedFpsFromMedian != null
183
+ ? ` estFps=${msg.estimatedFpsFromMedian.toFixed(1)}`
184
+ : '') +
185
+ ` backend=${msg.backend}` +
186
+ (msg.mode ? ` mode=${msg.mode}` : '') +
187
+ (msg.videoSize ? ` video=${msg.videoSize}` : '') +
188
+ (b
189
+ ? ` bitmap=${fmt(b.drawMs)} fromPixels=${fmt(b.fromPixelsMs)} execute=${fmt(b.executeMs)}` +
190
+ (b.totalPipelineMs != null ? ` total=${fmt(b.totalPipelineMs)}` : '')
191
+ : '') +
192
+ (wc ? ` inferred/skipped=${wc.inferred}/${wc.skipped}` : ''));
193
+ if (lh?.hints?.length) {
194
+ this.onDiagnostic?.(`[posetracker] WebView leverHints dominant=${lh.dominantStage ?? '?'} ` +
195
+ `shares bmp=${lh.bitmapShare ?? '?'} px=${lh.fromPixelsShare ?? '?'} ex=${lh.executeShare ?? '?'} ` +
196
+ `→ ${lh.hints.join(' | ')}`);
197
+ }
198
+ this.onStats?.({
199
+ fps: msg.fps,
200
+ medianInferenceMs: msg.medianInferenceMs,
201
+ estimatedFpsFromMedian: msg.estimatedFpsFromMedian,
202
+ frames: msg.frames,
203
+ backend: msg.backend,
204
+ videoSize: msg.videoSize,
205
+ mode: msg.mode,
206
+ breakdown: msg.breakdown,
207
+ windowCounts: msg.windowCounts,
208
+ experiments: msg.experiments,
209
+ leverHints: msg.leverHints,
210
+ });
211
+ return;
212
+ }
213
+ if (msg.type === 'pose') {
214
+ const pose = {
215
+ keypoints: msg.keypoints.map((k) => ({
216
+ name: k.name,
217
+ x: k.x,
218
+ y: k.y,
219
+ score: k.score,
220
+ })),
221
+ score: msg.score,
222
+ timestampMs: msg.timestampMs,
223
+ };
224
+ this.lastPose = pose;
225
+ this.onPose?.(pose, msg.inferenceTimeMs);
226
+ }
227
+ }
228
+ async init(_options) {
229
+ this.onDiagnostic?.('[posetracker] WebViewPoseBackend.init — waiting for Chromium WebView to mount (WebViewPoseView)');
230
+ // Actual model load happens inside the HTML page once the view attaches.
231
+ }
232
+ async warmup() {
233
+ if (this.warm)
234
+ return;
235
+ this.onDiagnostic?.('[posetracker] WebViewPoseBackend.warmup — awaiting page ready (model cold-start)…');
236
+ await this.waitUntilReady(60000);
237
+ }
238
+ /** Whether getUserMedia has already run in the attached page. */
239
+ isCameraOpened() {
240
+ return this.cameraOpened;
241
+ }
242
+ /**
243
+ * Host injects `__PT_OPEN_CAMERA` via {@link setOpenCameraHandler}.
244
+ * Used when upgrading basic → full cold-start without remounting the page.
245
+ */
246
+ setOpenCameraHandler(handler) {
247
+ this.openCameraHandler = handler;
248
+ }
249
+ /**
250
+ * Open getUserMedia after a basic (model-only) ready. No-op if the page
251
+ * already opened the camera (coldStart=full boot).
252
+ */
253
+ async openCamera() {
254
+ if (this.cameraOpened)
255
+ return;
256
+ if (!this.ready) {
257
+ await this.waitUntilReady(60000);
258
+ }
259
+ if (this.cameraOpened)
260
+ return;
261
+ if (!this.openCameraHandler) {
262
+ throw new Error('WebViewPoseBackend.openCamera: no WebViewPoseView attached to inject __PT_OPEN_CAMERA.');
263
+ }
264
+ this.onDiagnostic?.('[posetracker] WebViewPoseBackend.openCamera — requesting getUserMedia…');
265
+ this.openCameraHandler();
266
+ await this.waitUntilCameraOpened(30000);
267
+ }
268
+ async estimatePose(_frame) {
269
+ // Camera lives inside the WebView; return last pose for API compatibility.
270
+ return this.lastPose;
271
+ }
272
+ getAcceleration() {
273
+ if (this.acceleration === 'unknown' && !this.ready) {
274
+ return null;
275
+ }
276
+ return {
277
+ state: this.acceleration,
278
+ tfjsBackend: this.tfjsBackend,
279
+ runtime: 'webview',
280
+ medianInferenceMs: this.medianInferenceMs,
281
+ inferenceTimesMs: [...this.inferenceTimesMs],
282
+ maxAcceptableInferenceMs: 200,
283
+ capabilities: this.glRenderer
284
+ ? {
285
+ glVersion: this.glVersion,
286
+ renderer: this.glRenderer,
287
+ vendor: this.glVendor,
288
+ maxTextureSize: null,
289
+ colorBufferFloat: true,
290
+ colorBufferHalfFloat: true,
291
+ textureFloat: true,
292
+ textureHalfFloat: true,
293
+ extensionQueriesShimmed: false,
294
+ }
295
+ : null,
296
+ flags: {
297
+ WEBVIEW_CHROMIUM: true,
298
+ note: this.tfjsBackend === 'wasm'
299
+ ? 'Inference runs in WebView WASM (slow fallback)'
300
+ : 'Inference runs in WebView main-thread WebGL MoveNet (camera capped, createImageBitmap 192)',
301
+ },
302
+ contextLossCount: 0,
303
+ reasons: [
304
+ ...this.reasons,
305
+ 'backend=webview-movenet (Chromium getUserMedia + TF.js WebGL MoveNet Lightning)',
306
+ ],
307
+ };
308
+ }
309
+ async dispose() {
310
+ this.ready = false;
311
+ this.warm = false;
312
+ this.cameraOpened = false;
313
+ this.attached = false;
314
+ this.openCameraHandler = undefined;
315
+ this.lastPose = null;
316
+ this.acceleration = 'unknown';
317
+ this.readyWaiters = [];
318
+ this.cameraWaiters = [];
319
+ }
320
+ waitUntilReady(timeoutMs) {
321
+ if (this.ready)
322
+ return Promise.resolve();
323
+ return new Promise((resolve, reject) => {
324
+ const timer = setTimeout(() => {
325
+ reject(new Error('WebViewPoseBackend: timed out waiting for Chromium page ready. ' +
326
+ 'Mount <WebViewPoseView coldStart="basic" /> (or full) so MoveNet can warm up.'));
327
+ }, timeoutMs);
328
+ this.readyWaiters.push({
329
+ resolve: () => {
330
+ clearTimeout(timer);
331
+ resolve();
332
+ },
333
+ reject: (err) => {
334
+ clearTimeout(timer);
335
+ reject(err);
336
+ },
337
+ });
338
+ });
339
+ }
340
+ waitUntilCameraOpened(timeoutMs) {
341
+ if (this.cameraOpened)
342
+ return Promise.resolve();
343
+ return new Promise((resolve, reject) => {
344
+ const timer = setTimeout(() => {
345
+ reject(new Error('WebViewPoseBackend: timed out waiting for getUserMedia. ' +
346
+ 'Ensure the user grants camera permission when prompted.'));
347
+ }, timeoutMs);
348
+ this.cameraWaiters.push({
349
+ resolve: () => {
350
+ clearTimeout(timer);
351
+ resolve();
352
+ },
353
+ reject: (err) => {
354
+ clearTimeout(timer);
355
+ reject(err);
356
+ },
357
+ });
358
+ });
359
+ }
360
+ flushReadyWaiters() {
361
+ const waiters = this.readyWaiters;
362
+ this.readyWaiters = [];
363
+ for (const w of waiters)
364
+ w.resolve();
365
+ }
366
+ flushCameraWaiters() {
367
+ const waiters = this.cameraWaiters;
368
+ this.cameraWaiters = [];
369
+ for (const w of waiters)
370
+ w.resolve();
371
+ }
372
+ setAcceleration(state) {
373
+ if (this.acceleration !== state) {
374
+ this.acceleration = state;
375
+ this.onAccelerationChange?.(state);
376
+ }
377
+ }
378
+ }
379
+ exports.WebViewPoseBackend = WebViewPoseBackend;
380
+ function isWebViewPoseBackend(backend) {
381
+ return backend.name === 'webview-movenet';
382
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * PoseTracker join logo (icon + wordmark) for WebView overlays.
3
+ * Generated from assets/PoseTracker_logo.png — do not edit by hand.
4
+ */
5
+ export declare const POSETRACKER_LOGO_DATA_URL = "data:image/png;base64,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";
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.POSETRACKER_LOGO_DATA_URL = void 0;
4
+ /**
5
+ * PoseTracker join logo (icon + wordmark) for WebView overlays.
6
+ * Generated from assets/PoseTracker_logo.png — do not edit by hand.
7
+ */
8
+ exports.POSETRACKER_LOGO_DATA_URL = "data:image/png;base64,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";