@limrun/ui 0.15.0 → 0.15.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 CHANGED
@@ -6,6 +6,23 @@ types and helpers for building inspect, search, and agent interfaces.
6
6
 
7
7
  See [examples](../../examples/) to see how it can be used.
8
8
 
9
+ ## Simulated cameras
10
+
11
+ `RemoteControl` responds automatically when an app in the instance opens,
12
+ closes, or switches a virtual camera. Camera facing is a preference: the
13
+ component requests the browser's `user` or `environment` camera with an
14
+ `ideal` constraint and falls back to an available input. Repeated requests for
15
+ the same facing reuse the live track. Facing changes restart capture so mobile
16
+ browsers can release one camera before opening the other.
17
+
18
+ WebRTC and the browser choose the capture resolution. The simulated camera's
19
+ host output follows the decoded frame dimensions, including width/height swaps
20
+ when captured video rotates. `onCameraDemandChange` receives fresh
21
+ `MediaStreamTrack.getSettings()` status metadata after track changes.
22
+
23
+ Camera API types (`CameraRequest`, `CameraResult`, `CameraFacingMode`,
24
+ `CameraCaptureInfo`, and `CameraStreamStats`) are exported from `@limrun/ui`.
25
+
9
26
  Related browser workflow packages are published separately:
10
27
 
11
28
  - `@limrun/apple-auth` handles Apple ID login, signing credentials, and App
@@ -1,12 +1,14 @@
1
1
  import { default as React } from 'react';
2
- import { AxStatus } from '../core/ax-fetcher';
2
+ import { AxStatus, AndroidElementTreeOptions } from '../core/ax-fetcher';
3
3
  import { AxElement, AxSnapshot } from '../core/ax-tree';
4
+ import { CameraCaptureInfo } from '../core/camera';
5
+ export type { CameraCaptureInfo, CameraFacingMode, CameraRequest, CameraResult } from '../core/camera';
4
6
  declare global {
5
7
  interface Window {
6
8
  debugRemoteControl?: boolean;
7
9
  }
8
10
  }
9
- interface RemoteControlProps {
11
+ export interface RemoteControlProps {
10
12
  url: string;
11
13
  token: string;
12
14
  className?: string;
@@ -105,6 +107,7 @@ interface RemoteControlProps {
105
107
  * @default 2000
106
108
  */
107
109
  axMaxBackoffMs?: number;
110
+ androidElementTreeOptions?: AndroidElementTreeOptions;
108
111
  /**
109
112
  * Fires whenever the iOS simulator's camera demand state changes —
110
113
  * i.e. an app inside the sim called
@@ -114,12 +117,10 @@ interface RemoteControlProps {
114
117
  * UI can render a status indicator ("simulator is using your
115
118
  * camera", etc.).
116
119
  *
117
- * `active` reflects whether the sim is currently asking for
118
- * frames. `granted` is set only on the call that follows a
119
- * `getUserMedia` attempt: `true` when the user accepted the
120
- * browser prompt, `false` when they denied or the call failed
121
- * (in which case the limulator side switches to a black-frame
122
- * fallback so the app keeps ticking).
120
+ * `active` reflects whether the sim is currently asking for frames.
121
+ * `granted` is set on capture acknowledgements: `true` after a new
122
+ * track, reused track, or constraint update; `false` when capture
123
+ * fails. The host switches to black frames after a failure.
123
124
  *
124
125
  * `camera` (optional, only meaningful when `granted === true`)
125
126
  * carries what `MediaStreamTrack.getSettings()` reported for the
@@ -149,40 +150,6 @@ interface RemoteControlProps {
149
150
  * itself — this is the canonical place.
150
151
  */
151
152
  onCameraStats?: (stats: CameraStreamStats | null) => void;
152
- /**
153
- * Optional resolution cap for outbound camera capture.
154
- *
155
- * - `'auto'` (default): no extra constraint. The browser captures
156
- * at its webcam's native max; WebRTC's quality scaler may step
157
- * down resolution on the encoder side under bandwidth pressure
158
- * while still feeding the simulator at the pool's native size.
159
- * - `'1080p'` / `'720p'` / `'480p'`: hard cap applied via
160
- * `getUserMedia` constraints (for new captures) and
161
- * `track.applyConstraints` (for the currently-active track),
162
- * matching the way Meet/Zoom expose a "Send resolution" picker.
163
- *
164
- * Bumping or lowering the cap mid-stream is supported; the
165
- * change takes effect within a frame or two as the webcam
166
- * re-negotiates.
167
- */
168
- cameraResolutionCap?: CameraResolutionCap;
169
- /**
170
- * Aspect ratio the simulator's virtual camera should report to apps.
171
- * Picking a value here triggers a `cameraAspect` WS message to the
172
- * host, which rebuilds its IOSurface ring at the matching dimensions
173
- * (16:9 → 1920×1080, 4:3 → 1440×1080, 1:1 → 1080×1080, 9:16 →
174
- * 1080×1920) and signals the in-sim dylib to re-handshake. iOS apps
175
- * see CMSampleBuffers at the new dimensions within a frame or two.
176
- *
177
- * The browser still captures whatever the webcam offers; the host
178
- * aspect-fills (cover, center-crop) into the new pool. Switching
179
- * aspect at runtime is intentionally cheap so users can A/B preview
180
- * styles without restarting the simulator.
181
- *
182
- * `undefined` leaves the pool untouched (the host's boot default —
183
- * 16:9 / 1920×1080 — applies).
184
- */
185
- cameraAspect?: CameraAspect;
186
153
  /**
187
154
  * When true, capture the user's microphone via
188
155
  * `getUserMedia({ audio: true })` and stream it live into the
@@ -218,34 +185,6 @@ export interface MicrophoneState {
218
185
  /** Device label of the captured microphone, when available. */
219
186
  label?: string;
220
187
  }
221
- /**
222
- * Resolution caps a host app can request on the outbound camera.
223
- * `'auto'` is "let the browser decide" (no constraints beyond the
224
- * 30 fps ceiling); the other options clamp width/height to the
225
- * named target. Aspect ratio is preserved.
226
- */
227
- export type CameraResolutionCap = 'auto' | '1080p' | '720p' | '480p';
228
- /**
229
- * Aspect ratios exposed to the operator for the simulated camera.
230
- * The host maps each label to concrete IOSurface dimensions; values
231
- * the host doesn't recognise are silently ignored.
232
- */
233
- export type CameraAspect = '16:9' | '4:3' | '1:1' | '9:16';
234
- /**
235
- * Snapshot of the browser's webcam capture, mirrored from
236
- * `MediaStreamTrack.getSettings()`. Forwarded to the host alongside
237
- * `cameraResult` and exposed to the host app via
238
- * `onCameraDemandChange` so it can render a status indicator without
239
- * having to call `getStats()` itself.
240
- */
241
- export interface CameraCaptureInfo {
242
- width?: number;
243
- height?: number;
244
- frameRate?: number;
245
- deviceId?: string;
246
- label?: string;
247
- facingMode?: string;
248
- }
249
188
  /**
250
189
  * Live outbound-camera quality snapshot. Derived from
251
190
  * `RTCPeerConnection.getStats()` and rate-derived deltas, sampled
@@ -310,4 +249,3 @@ export interface RemoteControlHandle {
310
249
  }
311
250
  export declare const withAuthenticationToken: (url: string, token: string) => string;
312
251
  export declare const RemoteControl: React.ForwardRefExoticComponent<RemoteControlProps & React.RefAttributes<RemoteControlHandle>>;
313
- export {};
@@ -1,10 +1,14 @@
1
1
  import { AxPlatform, AxSnapshot } from './ax-tree';
2
2
  export type AxStatus = 'idle' | 'starting' | 'ready' | 'unavailable' | 'error';
3
3
  export type AxFetcherSendFn = (payload: Record<string, unknown>) => boolean;
4
+ export interface AndroidElementTreeOptions {
5
+ waitForIdleTimeoutMs?: number;
6
+ }
4
7
  export interface AxFetcherOptions {
5
8
  platform: AxPlatform;
6
9
  send: AxFetcherSendFn;
7
10
  onSnapshot: (snapshot: AxSnapshot | null) => void;
11
+ androidElementTreeOptions?: AndroidElementTreeOptions;
8
12
  onStatusChange?: (status: AxStatus, error?: string) => void;
9
13
  baseIntervalMs?: number;
10
14
  maxBackoffMs?: number;
@@ -16,6 +20,7 @@ export declare class AxFetcher {
16
20
  private readonly onStatusChange?;
17
21
  private readonly baseIntervalMs;
18
22
  private readonly maxBackoffMs;
23
+ private readonly androidElementTreeOptions?;
19
24
  private readonly pending;
20
25
  private running;
21
26
  private timer;
@@ -0,0 +1,58 @@
1
+ export type CameraFacingMode = 'user' | 'environment';
2
+ /** Camera selection requested by the instance host. */
3
+ export interface CameraRequest {
4
+ active: boolean;
5
+ facingMode?: CameraFacingMode;
6
+ }
7
+ /** Browser capture metadata returned to the instance host. */
8
+ export interface CameraCaptureInfo {
9
+ width?: number;
10
+ height?: number;
11
+ frameRate?: number;
12
+ deviceId?: string;
13
+ label?: string;
14
+ facingMode?: string;
15
+ }
16
+ /** Acknowledgement sent after camera capture changes or is reused. */
17
+ export interface CameraResult {
18
+ type: 'cameraResult';
19
+ granted: boolean;
20
+ camera?: CameraCaptureInfo;
21
+ }
22
+ export interface CameraOperationContext {
23
+ generation: number;
24
+ isCurrent: () => boolean;
25
+ }
26
+ /**
27
+ * Serializes camera mutations while invalidating queued or suspended work.
28
+ *
29
+ * `enqueue` increments the generation before returning, so an operation
30
+ * waiting on browser permission becomes stale as soon as a newer request
31
+ * arrives, even though the newer operation has not started yet.
32
+ */
33
+ export declare class CameraOperationCoordinator {
34
+ private generation;
35
+ private tail;
36
+ get currentGeneration(): number;
37
+ enqueue(operation: (context: CameraOperationContext) => Promise<void>): Promise<void>;
38
+ /**
39
+ * Queues related work without superseding an already queued request.
40
+ *
41
+ * The operation retains the generation current at enqueue time and becomes
42
+ * stale if a later camera request increments it before this work starts.
43
+ */
44
+ enqueueCurrent(operation: (context: CameraOperationContext) => Promise<void>): Promise<void>;
45
+ private append;
46
+ invalidate(): void;
47
+ }
48
+ export declare function parseCameraRequest(message: unknown): CameraRequest | null;
49
+ /**
50
+ * Builds best-effort capture constraints.
51
+ *
52
+ * Facing is a preference so browsers can fall back when a phone-facing label
53
+ * or exact mode is unavailable. The browser chooses capture dimensions.
54
+ */
55
+ export declare function cameraCaptureConstraints(request: CameraRequest): MediaTrackConstraints;
56
+ export declare function shouldReacquireCamera(current: Pick<CameraRequest, 'facingMode'> | undefined, requested: Pick<CameraRequest, 'facingMode'>): boolean;
57
+ export declare function cameraCaptureInfoFromTrack(track: MediaStreamTrack): CameraCaptureInfo;
58
+ export declare function createGrantedCameraResult(track: MediaStreamTrack): CameraResult;