tauri-plugin-camera-kessdev 0.1.1-beta.4 → 0.2.0-beta.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.
package/README.md CHANGED
@@ -128,10 +128,10 @@ import {
128
128
  stopRecording,
129
129
  flipCamera,
130
130
  setFlash,
131
- isFlashAvailable,
132
131
  setZoom,
133
132
  getZoom,
134
- getZoomRange,
133
+ getAvailableCameras,
134
+ getCapabilities,
135
135
  saveToGallery,
136
136
  checkPermissions,
137
137
  requestPermissions,
@@ -233,14 +233,38 @@ await flipCamera()
233
233
  await setFlash('on') // 'on' | 'off' | 'auto'
234
234
  await setZoom(2.0)
235
235
 
236
- // Query the active camera (mobile). Desktop has no zoom/flash control:
237
- const zoom = await getZoom() // number
238
- const range = await getZoomRange() // { min, max, current }
239
- const hasFlash = await isFlashAvailable() // boolean (false on desktop)
236
+ // Live state (mobile):
237
+ const zoom = await getZoom() // number
240
238
  ```
241
239
 
242
- On desktop `getZoom()` and `getZoomRange()` reject with a descriptive error
243
- (consistent with `setZoom`), while `isFlashAvailable()` resolves to `false`.
240
+ On desktop `getZoom()` rejects with a descriptive error (consistent with
241
+ `setZoom`).
242
+
243
+ ### Camera introspection
244
+
245
+ Use `getAvailableCameras()` to list the cameras (physical/logical lenses) and
246
+ `getCapabilities()` to know what a camera *can* do, before trying:
247
+
248
+ ```ts
249
+ const cameras = await getAvailableCameras()
250
+ // [{ id: '0', facing: 'back', lens: 'wide' }, { id: '1', facing: 'front' }, ...]
251
+
252
+ // No argument: the active camera, or the default (back) camera without a preview.
253
+ const caps = await getCapabilities(cameras[0].id)
254
+ // { cameraId, hasFlash, hasTorch, zoomRange?, supportsFocus, focusModes?,
255
+ // photoFormats?, supportsPause }
256
+ ```
257
+
258
+ The plugin follows one rule to avoid ambiguity:
259
+
260
+ - **What the hardware can do** -> `getCapabilities()` (declarative).
261
+ - **What a value is right now** -> a dedicated live getter (e.g. `getZoom()`).
262
+
263
+ Both work without an active preview on mobile. On desktop `getAvailableCameras()`
264
+ works cold (labels may stay empty until camera permission has been granted once),
265
+ while the hardware capabilities `zoomRange`, `hasTorch` and `focusModes` are
266
+ omitted until a preview/recording stream is active; `hasFlash` is always `false`
267
+ and `supportsPause` is `true`.
244
268
 
245
269
  ### Gallery
246
270
 
@@ -391,10 +415,10 @@ destination from the generated Xcode project (`src-tauri/gen/apple`).
391
415
  | `capture(options?)` | ✓ `ImageCapture` / `AVCapturePhotoOutput` | ✓ canvas |
392
416
  | `flipCamera()` | ✓ | ✗ (throws) |
393
417
  | `setFlash(mode)` | ✓ | ✗ (throws) |
394
- | `isFlashAvailable()` | ✓ | `false` |
395
418
  | `setZoom(factor)` | ✓ | ✗ (throws) |
396
419
  | `getZoom()` | ✓ | ✗ (throws) |
397
- | `getZoomRange()` | ✓ | ✗ (throws) |
420
+ | `getAvailableCameras()` | ✓ | ✓ |
421
+ | `getCapabilities(cameraId?)` | ✓ | ✓ (partial) |
398
422
  | `startRecording(options?)` | ✓ `VideoCapture` / `AVCaptureMovieFileOutput` | ✓ `MediaRecorder` |
399
423
  | `stopRecording(options?)` | ✓ | ✓ |
400
424
  | `RecordingProgress` telemetry (`onProgress` channel) | ✓ | ✓ |
package/dist-js/index.cjs CHANGED
@@ -188,23 +188,89 @@ async function getZoom() {
188
188
  }
189
189
  throw new Error('getZoom is not supported on desktop.');
190
190
  }
191
- /** Gets the supported zoom range of the active camera (mobile). */
192
- async function getZoomRange() {
191
+ // ---------------------------------------------------------------------------
192
+ // Introspection (available cameras + declarative capabilities)
193
+ // ---------------------------------------------------------------------------
194
+ /** Best-effort facing detection from a device label (desktop). */
195
+ function facingFromLabel(label) {
196
+ const value = label.toLowerCase();
197
+ if (value.includes('front') || value.includes('user'))
198
+ return 'front';
199
+ if (value.includes('back') || value.includes('rear') || value.includes('environment'))
200
+ return 'back';
201
+ return undefined;
202
+ }
203
+ /** Enumerates video input devices on desktop via `enumerateDevices()`. */
204
+ async function enumerateDesktopCameras() {
205
+ const devices = await navigator.mediaDevices.enumerateDevices();
206
+ return devices
207
+ .filter((device) => device.kind === 'videoinput')
208
+ .map((device) => {
209
+ const info = { id: device.deviceId };
210
+ const facing = facingFromLabel(device.label);
211
+ if (facing)
212
+ info.facing = facing;
213
+ if (device.label)
214
+ info.label = device.label;
215
+ return info;
216
+ });
217
+ }
218
+ /**
219
+ * Declarative capabilities on desktop. Hardware fields (`zoomRange`, `hasTorch`,
220
+ * `focusModes`) are only known while a preview/recording stream is active; they
221
+ * are omitted otherwise.
222
+ */
223
+ function desktopCapabilities(cameraId) {
224
+ const caps = {
225
+ hasFlash: false,
226
+ hasTorch: false,
227
+ supportsFocus: false,
228
+ supportsPause: true,
229
+ photoFormats: ['jpeg', 'png', 'webp']
230
+ };
231
+ if (cameraId)
232
+ caps.cameraId = cameraId;
233
+ const track = activeStream?.getVideoTracks?.()[0];
234
+ const trackCaps = track?.getCapabilities?.();
235
+ if (trackCaps) {
236
+ if (trackCaps.zoom) {
237
+ caps.zoomRange = { min: trackCaps.zoom.min, max: trackCaps.zoom.max };
238
+ }
239
+ if (typeof trackCaps.torch === 'boolean') {
240
+ caps.hasTorch = trackCaps.torch;
241
+ }
242
+ if (trackCaps.focusMode && trackCaps.focusMode.length > 0) {
243
+ caps.supportsFocus = true;
244
+ caps.focusModes = trackCaps.focusMode;
245
+ }
246
+ }
247
+ return caps;
248
+ }
249
+ /**
250
+ * Lists the cameras (physical or logical lenses) available on the device.
251
+ *
252
+ * Works without an active preview on every platform. On desktop the labels may
253
+ * be empty until camera permission has been granted at least once.
254
+ */
255
+ async function getAvailableCameras() {
193
256
  if (isMobile()) {
194
- return await core.invoke('plugin:camera-kessdev|get_zoom_range');
257
+ return await core.invoke('plugin:camera-kessdev|get_available_cameras');
195
258
  }
196
- throw new Error('getZoomRange is not supported on desktop.');
259
+ return enumerateDesktopCameras();
197
260
  }
198
261
  /**
199
- * Returns whether the active camera has a flash unit.
262
+ * Returns the declarative capabilities of a camera.
200
263
  *
201
- * On desktop this always resolves to `false` (no flash control).
264
+ * Without `cameraId` it describes the active camera, or the default (back)
265
+ * camera when no preview is active. On mobile it works without an active
266
+ * preview; on desktop hardware fields are omitted until a stream is active.
267
+ * Live values are exposed by dedicated getters (e.g. {@link getZoom}).
202
268
  */
203
- async function isFlashAvailable() {
269
+ async function getCapabilities(cameraId) {
204
270
  if (isMobile()) {
205
- return await core.invoke('plugin:camera-kessdev|is_flash_available');
271
+ return await core.invoke('plugin:camera-kessdev|get_capabilities', { cameraId });
206
272
  }
207
- return false;
273
+ return desktopCapabilities(cameraId);
208
274
  }
209
275
  // ---------------------------------------------------------------------------
210
276
  // Video recording
@@ -363,10 +429,10 @@ Object.defineProperty(exports, "convertFileSrc", {
363
429
  exports.capture = capture;
364
430
  exports.checkPermissions = checkPermissions;
365
431
  exports.flipCamera = flipCamera;
432
+ exports.getAvailableCameras = getAvailableCameras;
433
+ exports.getCapabilities = getCapabilities;
366
434
  exports.getPreviewStream = getPreviewStream;
367
435
  exports.getZoom = getZoom;
368
- exports.getZoomRange = getZoomRange;
369
- exports.isFlashAvailable = isFlashAvailable;
370
436
  exports.openAppSettings = openAppSettings;
371
437
  exports.requestPermissions = requestPermissions;
372
438
  exports.saveToGallery = saveToGallery;
@@ -37,11 +37,46 @@ export interface Video {
37
37
  /** Optional base64-encoded thumbnail, when requested via `stopRecording`. */
38
38
  thumbnail?: string;
39
39
  }
40
- /** Supported zoom range of the active camera. */
40
+ /** Supported zoom range of a camera (declarative, without the live value). */
41
41
  export interface ZoomRange {
42
42
  min: number;
43
43
  max: number;
44
- current: number;
44
+ }
45
+ /** Orientation of a camera. */
46
+ export type CameraFacing = 'front' | 'back' | 'external';
47
+ /**
48
+ * Best-effort hint of which lens a camera maps to. Platforms that cannot
49
+ * determine it (notably desktop) omit the field.
50
+ */
51
+ export type CameraLens = 'wide' | 'ultraWide' | 'telephoto' | (string & {});
52
+ /** A camera (physical or logical lens) available on the device. */
53
+ export interface CameraInfo {
54
+ /** Native, session-stable identifier (usable to reference this camera). */
55
+ id: string;
56
+ /** Orientation, when the platform can determine it. */
57
+ facing?: CameraFacing;
58
+ /** Best-effort lens hint, when the platform can determine it. */
59
+ lens?: CameraLens;
60
+ /** Human-readable label, when available. */
61
+ label?: string;
62
+ }
63
+ /**
64
+ * Declarative capabilities of a camera: what the hardware/platform *can* do.
65
+ *
66
+ * Optional fields are omitted when the platform cannot determine them. Live
67
+ * values (e.g. the current zoom factor) are exposed by dedicated getters such
68
+ * as {@link getZoom}, never here.
69
+ */
70
+ export interface CameraCapabilities {
71
+ /** Identifier of the camera these capabilities describe, when known. */
72
+ cameraId?: string;
73
+ hasFlash: boolean;
74
+ hasTorch: boolean;
75
+ zoomRange?: ZoomRange;
76
+ supportsFocus: boolean;
77
+ focusModes?: string[];
78
+ photoFormats?: string[];
79
+ supportsPause: boolean;
45
80
  }
46
81
  /**
47
82
  * Live telemetry emitted while a video recording is in progress.
@@ -103,14 +138,22 @@ export declare function setFlash(mode: FlashMode): Promise<void>;
103
138
  export declare function setZoom(factor: number): Promise<void>;
104
139
  /** Gets the current camera zoom factor (mobile). */
105
140
  export declare function getZoom(): Promise<number>;
106
- /** Gets the supported zoom range of the active camera (mobile). */
107
- export declare function getZoomRange(): Promise<ZoomRange>;
108
141
  /**
109
- * Returns whether the active camera has a flash unit.
142
+ * Lists the cameras (physical or logical lenses) available on the device.
143
+ *
144
+ * Works without an active preview on every platform. On desktop the labels may
145
+ * be empty until camera permission has been granted at least once.
146
+ */
147
+ export declare function getAvailableCameras(): Promise<CameraInfo[]>;
148
+ /**
149
+ * Returns the declarative capabilities of a camera.
110
150
  *
111
- * On desktop this always resolves to `false` (no flash control).
151
+ * Without `cameraId` it describes the active camera, or the default (back)
152
+ * camera when no preview is active. On mobile it works without an active
153
+ * preview; on desktop hardware fields are omitted until a stream is active.
154
+ * Live values are exposed by dedicated getters (e.g. {@link getZoom}).
112
155
  */
113
- export declare function isFlashAvailable(): Promise<boolean>;
156
+ export declare function getCapabilities(cameraId?: string): Promise<CameraCapabilities>;
114
157
  /** Starts video recording (with audio). */
115
158
  export declare function startRecording(options?: StartRecordingOptions): Promise<void>;
116
159
  /** Stops video recording and returns the recorded media. */
package/dist-js/index.js CHANGED
@@ -187,23 +187,89 @@ async function getZoom() {
187
187
  }
188
188
  throw new Error('getZoom is not supported on desktop.');
189
189
  }
190
- /** Gets the supported zoom range of the active camera (mobile). */
191
- async function getZoomRange() {
190
+ // ---------------------------------------------------------------------------
191
+ // Introspection (available cameras + declarative capabilities)
192
+ // ---------------------------------------------------------------------------
193
+ /** Best-effort facing detection from a device label (desktop). */
194
+ function facingFromLabel(label) {
195
+ const value = label.toLowerCase();
196
+ if (value.includes('front') || value.includes('user'))
197
+ return 'front';
198
+ if (value.includes('back') || value.includes('rear') || value.includes('environment'))
199
+ return 'back';
200
+ return undefined;
201
+ }
202
+ /** Enumerates video input devices on desktop via `enumerateDevices()`. */
203
+ async function enumerateDesktopCameras() {
204
+ const devices = await navigator.mediaDevices.enumerateDevices();
205
+ return devices
206
+ .filter((device) => device.kind === 'videoinput')
207
+ .map((device) => {
208
+ const info = { id: device.deviceId };
209
+ const facing = facingFromLabel(device.label);
210
+ if (facing)
211
+ info.facing = facing;
212
+ if (device.label)
213
+ info.label = device.label;
214
+ return info;
215
+ });
216
+ }
217
+ /**
218
+ * Declarative capabilities on desktop. Hardware fields (`zoomRange`, `hasTorch`,
219
+ * `focusModes`) are only known while a preview/recording stream is active; they
220
+ * are omitted otherwise.
221
+ */
222
+ function desktopCapabilities(cameraId) {
223
+ const caps = {
224
+ hasFlash: false,
225
+ hasTorch: false,
226
+ supportsFocus: false,
227
+ supportsPause: true,
228
+ photoFormats: ['jpeg', 'png', 'webp']
229
+ };
230
+ if (cameraId)
231
+ caps.cameraId = cameraId;
232
+ const track = activeStream?.getVideoTracks?.()[0];
233
+ const trackCaps = track?.getCapabilities?.();
234
+ if (trackCaps) {
235
+ if (trackCaps.zoom) {
236
+ caps.zoomRange = { min: trackCaps.zoom.min, max: trackCaps.zoom.max };
237
+ }
238
+ if (typeof trackCaps.torch === 'boolean') {
239
+ caps.hasTorch = trackCaps.torch;
240
+ }
241
+ if (trackCaps.focusMode && trackCaps.focusMode.length > 0) {
242
+ caps.supportsFocus = true;
243
+ caps.focusModes = trackCaps.focusMode;
244
+ }
245
+ }
246
+ return caps;
247
+ }
248
+ /**
249
+ * Lists the cameras (physical or logical lenses) available on the device.
250
+ *
251
+ * Works without an active preview on every platform. On desktop the labels may
252
+ * be empty until camera permission has been granted at least once.
253
+ */
254
+ async function getAvailableCameras() {
192
255
  if (isMobile()) {
193
- return await invoke('plugin:camera-kessdev|get_zoom_range');
256
+ return await invoke('plugin:camera-kessdev|get_available_cameras');
194
257
  }
195
- throw new Error('getZoomRange is not supported on desktop.');
258
+ return enumerateDesktopCameras();
196
259
  }
197
260
  /**
198
- * Returns whether the active camera has a flash unit.
261
+ * Returns the declarative capabilities of a camera.
199
262
  *
200
- * On desktop this always resolves to `false` (no flash control).
263
+ * Without `cameraId` it describes the active camera, or the default (back)
264
+ * camera when no preview is active. On mobile it works without an active
265
+ * preview; on desktop hardware fields are omitted until a stream is active.
266
+ * Live values are exposed by dedicated getters (e.g. {@link getZoom}).
201
267
  */
202
- async function isFlashAvailable() {
268
+ async function getCapabilities(cameraId) {
203
269
  if (isMobile()) {
204
- return await invoke('plugin:camera-kessdev|is_flash_available');
270
+ return await invoke('plugin:camera-kessdev|get_capabilities', { cameraId });
205
271
  }
206
- return false;
272
+ return desktopCapabilities(cameraId);
207
273
  }
208
274
  // ---------------------------------------------------------------------------
209
275
  // Video recording
@@ -355,4 +421,4 @@ async function openAppSettings() {
355
421
  throw new Error('openAppSettings is not supported on desktop.');
356
422
  }
357
423
 
358
- export { capture, checkPermissions, flipCamera, getPreviewStream, getZoom, getZoomRange, isFlashAvailable, openAppSettings, requestPermissions, saveToGallery, setFlash, setZoom, startPreview, startRecording, stopPreview, stopRecording };
424
+ export { capture, checkPermissions, flipCamera, getAvailableCameras, getCapabilities, getPreviewStream, getZoom, openAppSettings, requestPermissions, saveToGallery, setFlash, setZoom, startPreview, startRecording, stopPreview, stopRecording };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tauri-plugin-camera-kessdev",
3
- "version": "0.1.1-beta.4",
3
+ "version": "0.2.0-beta.0",
4
4
  "author": "kess",
5
5
  "description": "Camera plugin for Tauri v2: photo capture, video recording and preview on Android, iOS and desktop.",
6
6
  "license": "MIT",