tauri-plugin-camera-kessdev 0.1.1-beta.3 → 0.1.1-beta.4

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,7 +128,10 @@ import {
128
128
  stopRecording,
129
129
  flipCamera,
130
130
  setFlash,
131
+ isFlashAvailable,
131
132
  setZoom,
133
+ getZoom,
134
+ getZoomRange,
132
135
  saveToGallery,
133
136
  checkPermissions,
134
137
  requestPermissions,
@@ -168,8 +171,23 @@ await startRecording()
168
171
  // ... user records ...
169
172
  const video = await stopRecording()
170
173
  // video: { path, duration } (mobile) | { duration, blob, url } (desktop)
174
+
175
+ // Optionally request a base64 JPEG thumbnail of the recording:
176
+ const withThumbnail = await stopRecording({ thumbnail: true })
177
+ // withThumbnail.thumbnail: string | undefined
171
178
  ```
172
179
 
180
+ ### Video thumbnail
181
+
182
+ Pass `{ thumbnail: true }` to `stopRecording()` to include a base64-encoded JPEG
183
+ poster frame in the `Video` result. It is opt-in because it costs a frame decode
184
+ and encode. When it cannot be generated the field is omitted (the recording still
185
+ succeeds).
186
+
187
+ - **Android**: extracted with `MediaMetadataRetriever` on the recording executor.
188
+ - **iOS**: extracted with `AVAssetImageGenerator` at ~0.1 s (avoids a black first frame).
189
+ - **Desktop**: captured from the live preview stream at stop time.
190
+
173
191
  ### Recording progress (telemetry)
174
192
 
175
193
  Pass an `onProgress` channel to `startRecording()` to receive live telemetry while
@@ -214,14 +232,27 @@ ends. Telemetry is never emitted after `stopRecording()` resolves.
214
232
  await flipCamera()
215
233
  await setFlash('on') // 'on' | 'off' | 'auto'
216
234
  await setZoom(2.0)
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)
217
240
  ```
218
241
 
242
+ On desktop `getZoom()` and `getZoomRange()` reject with a descriptive error
243
+ (consistent with `setZoom`), while `isFlashAvailable()` resolves to `false`.
244
+
219
245
  ### Gallery
220
246
 
221
247
  ```ts
222
- await saveToGallery(photo.path) // mobile only
248
+ await saveToGallery(photo.path) // image
249
+ await saveToGallery(video.path) // video
250
+ await saveToGallery(media.path, { type: 'video' }) // forced type override
223
251
  ```
224
252
 
253
+ Mobile only. The media type is inferred from the file extension (mp4, mov, m4v,
254
+ webm, 3gp, mkv are treated as video); pass `{ type }` to force it.
255
+
225
256
  ### Permissions
226
257
 
227
258
  ```ts
@@ -360,11 +391,15 @@ destination from the generated Xcode project (`src-tauri/gen/apple`).
360
391
  | `capture(options?)` | ✓ `ImageCapture` / `AVCapturePhotoOutput` | ✓ canvas |
361
392
  | `flipCamera()` | ✓ | ✗ (throws) |
362
393
  | `setFlash(mode)` | ✓ | ✗ (throws) |
394
+ | `isFlashAvailable()` | ✓ | `false` |
363
395
  | `setZoom(factor)` | ✓ | ✗ (throws) |
396
+ | `getZoom()` | ✓ | ✗ (throws) |
397
+ | `getZoomRange()` | ✓ | ✗ (throws) |
364
398
  | `startRecording(options?)` | ✓ `VideoCapture` / `AVCaptureMovieFileOutput` | ✓ `MediaRecorder` |
365
- | `stopRecording()` | ✓ | ✓ |
399
+ | `stopRecording(options?)` | ✓ | ✓ |
366
400
  | `RecordingProgress` telemetry (`onProgress` channel) | ✓ | ✓ |
367
- | `saveToGallery(path)` | ✓ | ✗ (throws) |
401
+ | `Video.thumbnail` (`stopRecording({ thumbnail: true })`) | ✓ | ✓ |
402
+ | `saveToGallery(path, options?)` | ✓ image & video | ✗ (throws) |
368
403
  | `checkPermissions()` | ✓ | ✓ |
369
404
  | `requestPermissions()` | ✓ | ✓ |
370
405
  | `openAppSettings()` | ✓ | ✗ (throws) |
package/dist-js/index.cjs CHANGED
@@ -181,6 +181,31 @@ async function setZoom(factor) {
181
181
  }
182
182
  throw new Error('setZoom is not supported on desktop.');
183
183
  }
184
+ /** Gets the current camera zoom factor (mobile). */
185
+ async function getZoom() {
186
+ if (isMobile()) {
187
+ return await core.invoke('plugin:camera-kessdev|get_zoom');
188
+ }
189
+ throw new Error('getZoom is not supported on desktop.');
190
+ }
191
+ /** Gets the supported zoom range of the active camera (mobile). */
192
+ async function getZoomRange() {
193
+ if (isMobile()) {
194
+ return await core.invoke('plugin:camera-kessdev|get_zoom_range');
195
+ }
196
+ throw new Error('getZoomRange is not supported on desktop.');
197
+ }
198
+ /**
199
+ * Returns whether the active camera has a flash unit.
200
+ *
201
+ * On desktop this always resolves to `false` (no flash control).
202
+ */
203
+ async function isFlashAvailable() {
204
+ if (isMobile()) {
205
+ return await core.invoke('plugin:camera-kessdev|is_flash_available');
206
+ }
207
+ return false;
208
+ }
184
209
  // ---------------------------------------------------------------------------
185
210
  // Video recording
186
211
  // ---------------------------------------------------------------------------
@@ -228,14 +253,15 @@ async function startRecording(options) {
228
253
  }
229
254
  }
230
255
  /** Stops video recording and returns the recorded media. */
231
- async function stopRecording() {
256
+ async function stopRecording(options) {
232
257
  if (isMobile()) {
233
- return await core.invoke('plugin:camera-kessdev|stop_recording');
258
+ return await core.invoke('plugin:camera-kessdev|stop_recording', { ...options });
234
259
  }
235
260
  if (!mediaRecorder) {
236
261
  throw new Error('Recording is not active. Call startRecording() first.');
237
262
  }
238
263
  const duration = (performance.now() - recordingStartedAt) / 1000;
264
+ const thumbnail = options?.thumbnail ? captureDesktopThumbnail() : undefined;
239
265
  stopProgressTimer();
240
266
  return await new Promise((resolve, reject) => {
241
267
  mediaRecorder.onstop = () => {
@@ -245,23 +271,48 @@ async function stopRecording() {
245
271
  recordedChunks = [];
246
272
  recordedBytes = 0;
247
273
  mediaRecorder = null;
248
- resolve({ duration, blob, url });
274
+ const result = { duration, blob, url };
275
+ if (thumbnail)
276
+ result.thumbnail = thumbnail;
277
+ resolve(result);
249
278
  };
250
279
  mediaRecorder.onerror = () => reject(new Error('Video recording failed.'));
251
280
  mediaRecorder.stop();
252
281
  });
253
282
  }
283
+ /** Captures the current desktop preview frame as a base64 JPEG thumbnail. */
284
+ function captureDesktopThumbnail() {
285
+ if (!activeVideo || !activeVideo.videoWidth || !activeVideo.videoHeight) {
286
+ return undefined;
287
+ }
288
+ try {
289
+ const canvas = document.createElement('canvas');
290
+ canvas.width = activeVideo.videoWidth;
291
+ canvas.height = activeVideo.videoHeight;
292
+ const ctx = canvas.getContext('2d');
293
+ if (!ctx)
294
+ return undefined;
295
+ ctx.drawImage(activeVideo, 0, 0, canvas.width, canvas.height);
296
+ return canvas.toDataURL('image/jpeg');
297
+ }
298
+ catch {
299
+ return undefined;
300
+ }
301
+ }
254
302
  // ---------------------------------------------------------------------------
255
303
  // Gallery
256
304
  // ---------------------------------------------------------------------------
257
305
  /**
258
- * Saves a captured image to the device photo gallery (mobile).
306
+ * Saves a captured photo or video to the device gallery (mobile).
307
+ *
308
+ * The media type is inferred from the file extension; pass `options.type` to
309
+ * force it. On desktop this is not supported.
259
310
  *
260
- * @param path Absolute path to the image file.
311
+ * @param path Absolute path to the media file.
261
312
  */
262
- async function saveToGallery(path) {
313
+ async function saveToGallery(path, options) {
263
314
  if (isMobile()) {
264
- await core.invoke('plugin:camera-kessdev|save_to_gallery', { path });
315
+ await core.invoke('plugin:camera-kessdev|save_to_gallery', { path, ...options });
265
316
  return;
266
317
  }
267
318
  throw new Error('saveToGallery is not supported on desktop.');
@@ -313,6 +364,9 @@ exports.capture = capture;
313
364
  exports.checkPermissions = checkPermissions;
314
365
  exports.flipCamera = flipCamera;
315
366
  exports.getPreviewStream = getPreviewStream;
367
+ exports.getZoom = getZoom;
368
+ exports.getZoomRange = getZoomRange;
369
+ exports.isFlashAvailable = isFlashAvailable;
316
370
  exports.openAppSettings = openAppSettings;
317
371
  exports.requestPermissions = requestPermissions;
318
372
  exports.saveToGallery = saveToGallery;
@@ -34,6 +34,14 @@ export interface Video {
34
34
  blob?: Blob;
35
35
  /** Object URL for the recorded video (desktop). */
36
36
  url?: string;
37
+ /** Optional base64-encoded thumbnail, when requested via `stopRecording`. */
38
+ thumbnail?: string;
39
+ }
40
+ /** Supported zoom range of the active camera. */
41
+ export interface ZoomRange {
42
+ min: number;
43
+ max: number;
44
+ current: number;
37
45
  }
38
46
  /**
39
47
  * Live telemetry emitted while a video recording is in progress.
@@ -54,6 +62,14 @@ export interface StartRecordingOptions {
54
62
  /** Receives live recording telemetry while the recording is active. */
55
63
  onProgress?: Channel<RecordingProgress>;
56
64
  }
65
+ export interface StopRecordingOptions {
66
+ /** When true, the result includes a base64-encoded `thumbnail`. */
67
+ thumbnail?: boolean;
68
+ }
69
+ export interface SaveToGalleryOptions {
70
+ /** Force the media type instead of inferring it from the file. */
71
+ type?: 'image' | 'video';
72
+ }
57
73
  /**
58
74
  * Starts the camera preview.
59
75
  *
@@ -85,16 +101,29 @@ export declare function flipCamera(): Promise<void>;
85
101
  export declare function setFlash(mode: FlashMode): Promise<void>;
86
102
  /** Sets the camera zoom factor (mobile). */
87
103
  export declare function setZoom(factor: number): Promise<void>;
104
+ /** Gets the current camera zoom factor (mobile). */
105
+ 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
+ /**
109
+ * Returns whether the active camera has a flash unit.
110
+ *
111
+ * On desktop this always resolves to `false` (no flash control).
112
+ */
113
+ export declare function isFlashAvailable(): Promise<boolean>;
88
114
  /** Starts video recording (with audio). */
89
115
  export declare function startRecording(options?: StartRecordingOptions): Promise<void>;
90
116
  /** Stops video recording and returns the recorded media. */
91
- export declare function stopRecording(): Promise<Video>;
117
+ export declare function stopRecording(options?: StopRecordingOptions): Promise<Video>;
92
118
  /**
93
- * Saves a captured image to the device photo gallery (mobile).
119
+ * Saves a captured photo or video to the device gallery (mobile).
120
+ *
121
+ * The media type is inferred from the file extension; pass `options.type` to
122
+ * force it. On desktop this is not supported.
94
123
  *
95
- * @param path Absolute path to the image file.
124
+ * @param path Absolute path to the media file.
96
125
  */
97
- export declare function saveToGallery(path: string): Promise<void>;
126
+ export declare function saveToGallery(path: string, options?: SaveToGalleryOptions): Promise<void>;
98
127
  /** Gets the current camera permission state. */
99
128
  export declare function checkPermissions(): Promise<PermissionState>;
100
129
  /** Requests camera permission. */
package/dist-js/index.js CHANGED
@@ -180,6 +180,31 @@ async function setZoom(factor) {
180
180
  }
181
181
  throw new Error('setZoom is not supported on desktop.');
182
182
  }
183
+ /** Gets the current camera zoom factor (mobile). */
184
+ async function getZoom() {
185
+ if (isMobile()) {
186
+ return await invoke('plugin:camera-kessdev|get_zoom');
187
+ }
188
+ throw new Error('getZoom is not supported on desktop.');
189
+ }
190
+ /** Gets the supported zoom range of the active camera (mobile). */
191
+ async function getZoomRange() {
192
+ if (isMobile()) {
193
+ return await invoke('plugin:camera-kessdev|get_zoom_range');
194
+ }
195
+ throw new Error('getZoomRange is not supported on desktop.');
196
+ }
197
+ /**
198
+ * Returns whether the active camera has a flash unit.
199
+ *
200
+ * On desktop this always resolves to `false` (no flash control).
201
+ */
202
+ async function isFlashAvailable() {
203
+ if (isMobile()) {
204
+ return await invoke('plugin:camera-kessdev|is_flash_available');
205
+ }
206
+ return false;
207
+ }
183
208
  // ---------------------------------------------------------------------------
184
209
  // Video recording
185
210
  // ---------------------------------------------------------------------------
@@ -227,14 +252,15 @@ async function startRecording(options) {
227
252
  }
228
253
  }
229
254
  /** Stops video recording and returns the recorded media. */
230
- async function stopRecording() {
255
+ async function stopRecording(options) {
231
256
  if (isMobile()) {
232
- return await invoke('plugin:camera-kessdev|stop_recording');
257
+ return await invoke('plugin:camera-kessdev|stop_recording', { ...options });
233
258
  }
234
259
  if (!mediaRecorder) {
235
260
  throw new Error('Recording is not active. Call startRecording() first.');
236
261
  }
237
262
  const duration = (performance.now() - recordingStartedAt) / 1000;
263
+ const thumbnail = options?.thumbnail ? captureDesktopThumbnail() : undefined;
238
264
  stopProgressTimer();
239
265
  return await new Promise((resolve, reject) => {
240
266
  mediaRecorder.onstop = () => {
@@ -244,23 +270,48 @@ async function stopRecording() {
244
270
  recordedChunks = [];
245
271
  recordedBytes = 0;
246
272
  mediaRecorder = null;
247
- resolve({ duration, blob, url });
273
+ const result = { duration, blob, url };
274
+ if (thumbnail)
275
+ result.thumbnail = thumbnail;
276
+ resolve(result);
248
277
  };
249
278
  mediaRecorder.onerror = () => reject(new Error('Video recording failed.'));
250
279
  mediaRecorder.stop();
251
280
  });
252
281
  }
282
+ /** Captures the current desktop preview frame as a base64 JPEG thumbnail. */
283
+ function captureDesktopThumbnail() {
284
+ if (!activeVideo || !activeVideo.videoWidth || !activeVideo.videoHeight) {
285
+ return undefined;
286
+ }
287
+ try {
288
+ const canvas = document.createElement('canvas');
289
+ canvas.width = activeVideo.videoWidth;
290
+ canvas.height = activeVideo.videoHeight;
291
+ const ctx = canvas.getContext('2d');
292
+ if (!ctx)
293
+ return undefined;
294
+ ctx.drawImage(activeVideo, 0, 0, canvas.width, canvas.height);
295
+ return canvas.toDataURL('image/jpeg');
296
+ }
297
+ catch {
298
+ return undefined;
299
+ }
300
+ }
253
301
  // ---------------------------------------------------------------------------
254
302
  // Gallery
255
303
  // ---------------------------------------------------------------------------
256
304
  /**
257
- * Saves a captured image to the device photo gallery (mobile).
305
+ * Saves a captured photo or video to the device gallery (mobile).
306
+ *
307
+ * The media type is inferred from the file extension; pass `options.type` to
308
+ * force it. On desktop this is not supported.
258
309
  *
259
- * @param path Absolute path to the image file.
310
+ * @param path Absolute path to the media file.
260
311
  */
261
- async function saveToGallery(path) {
312
+ async function saveToGallery(path, options) {
262
313
  if (isMobile()) {
263
- await invoke('plugin:camera-kessdev|save_to_gallery', { path });
314
+ await invoke('plugin:camera-kessdev|save_to_gallery', { path, ...options });
264
315
  return;
265
316
  }
266
317
  throw new Error('saveToGallery is not supported on desktop.');
@@ -304,4 +355,4 @@ async function openAppSettings() {
304
355
  throw new Error('openAppSettings is not supported on desktop.');
305
356
  }
306
357
 
307
- export { capture, checkPermissions, flipCamera, getPreviewStream, openAppSettings, requestPermissions, saveToGallery, setFlash, setZoom, startPreview, startRecording, stopPreview, stopRecording };
358
+ export { capture, checkPermissions, flipCamera, getPreviewStream, getZoom, getZoomRange, isFlashAvailable, 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.3",
3
+ "version": "0.1.1-beta.4",
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",