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 +38 -3
- package/dist-js/index.cjs +61 -7
- package/dist-js/index.d.ts +33 -4
- package/dist-js/index.js +59 -8
- package/package.json +1 -1
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)
|
|
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
|
-
| `
|
|
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
|
-
|
|
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
|
|
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
|
|
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;
|
package/dist-js/index.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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