@capgo/camera-preview 7.4.0-beta.9 → 7.4.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 (43) hide show
  1. package/README.md +242 -50
  2. package/android/gradle/wrapper/gradle-wrapper.properties +1 -1
  3. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +1249 -143
  4. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +3400 -1432
  5. package/android/src/main/java/com/ahm/capacitor/camera/preview/GridOverlayView.java +95 -58
  6. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraDevice.java +55 -46
  7. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraLens.java +61 -52
  8. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraSessionConfiguration.java +160 -72
  9. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/LensInfo.java +29 -23
  10. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/ZoomFactors.java +24 -23
  11. package/dist/docs.json +441 -40
  12. package/dist/esm/definitions.d.ts +167 -25
  13. package/dist/esm/definitions.js.map +1 -1
  14. package/dist/esm/index.d.ts +2 -0
  15. package/dist/esm/index.js +24 -1
  16. package/dist/esm/index.js.map +1 -1
  17. package/dist/esm/web.d.ts +23 -3
  18. package/dist/esm/web.js +463 -65
  19. package/dist/esm/web.js.map +1 -1
  20. package/dist/plugin.cjs.js +485 -64
  21. package/dist/plugin.cjs.js.map +1 -1
  22. package/dist/plugin.js +485 -64
  23. package/dist/plugin.js.map +1 -1
  24. package/ios/Sources/{CapgoCameraPreview → CapgoCameraPreviewPlugin}/CameraController.swift +731 -315
  25. package/ios/Sources/CapgoCameraPreviewPlugin/Plugin.swift +1902 -0
  26. package/package.json +11 -3
  27. package/android/.gradle/8.14.2/checksums/checksums.lock +0 -0
  28. package/android/.gradle/8.14.2/checksums/md5-checksums.bin +0 -0
  29. package/android/.gradle/8.14.2/checksums/sha1-checksums.bin +0 -0
  30. package/android/.gradle/8.14.2/executionHistory/executionHistory.bin +0 -0
  31. package/android/.gradle/8.14.2/executionHistory/executionHistory.lock +0 -0
  32. package/android/.gradle/8.14.2/fileChanges/last-build.bin +0 -0
  33. package/android/.gradle/8.14.2/fileHashes/fileHashes.bin +0 -0
  34. package/android/.gradle/8.14.2/fileHashes/fileHashes.lock +0 -0
  35. package/android/.gradle/8.14.2/fileHashes/resourceHashesCache.bin +0 -0
  36. package/android/.gradle/8.14.2/gc.properties +0 -0
  37. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  38. package/android/.gradle/buildOutputCleanup/cache.properties +0 -2
  39. package/android/.gradle/buildOutputCleanup/outputFiles.bin +0 -0
  40. package/android/.gradle/file-system.probe +0 -0
  41. package/android/.gradle/vcs-1/gc.properties +0 -0
  42. package/ios/Sources/CapgoCameraPreview/Plugin.swift +0 -1369
  43. /package/ios/Sources/{CapgoCameraPreview → CapgoCameraPreviewPlugin}/GridOverlayView.swift +0 -0
@@ -16,6 +16,29 @@ exports.DeviceType = void 0;
16
16
  const CameraPreview = core.registerPlugin("CameraPreview", {
17
17
  web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.CameraPreviewWeb()),
18
18
  });
19
+ async function getBase64FromFilePath(filePath) {
20
+ const url = core.Capacitor.convertFileSrc(filePath);
21
+ const response = await fetch(url);
22
+ if (!response.ok) {
23
+ throw new Error(`Failed to read file at path: ${filePath} (status ${response.status})`);
24
+ }
25
+ const blob = await response.blob();
26
+ return await new Promise((resolve, reject) => {
27
+ const reader = new FileReader();
28
+ reader.onloadend = () => {
29
+ const dataUrl = reader.result;
30
+ const commaIndex = dataUrl.indexOf(",");
31
+ resolve(commaIndex >= 0 ? dataUrl.substring(commaIndex + 1) : dataUrl);
32
+ };
33
+ reader.onerror = () => reject(reader.error);
34
+ reader.readAsDataURL(blob);
35
+ });
36
+ }
37
+ async function deleteFile(path) {
38
+ // Use native bridge to delete file to handle platform-specific permissions/URIs
39
+ const { success } = await CameraPreview.deleteFile({ path });
40
+ return !!success;
41
+ }
19
42
 
20
43
  const DEFAULT_VIDEO_ID = "capgo_video";
21
44
  class CameraPreviewWeb extends core.WebPlugin {
@@ -29,6 +52,72 @@ class CameraPreviewWeb extends core.WebPlugin {
29
52
  this.currentDeviceId = null;
30
53
  this.videoElement = null;
31
54
  this.isStarted = false;
55
+ this.orientationListenerBound = false;
56
+ }
57
+ getCurrentOrientation() {
58
+ var _a, _b;
59
+ try {
60
+ const so = screen.orientation;
61
+ const type = (so === null || so === void 0 ? void 0 : so.type) || (so === null || so === void 0 ? void 0 : so.mozOrientation) || (so === null || so === void 0 ? void 0 : so.msOrientation);
62
+ if (typeof type === "string") {
63
+ if (type.includes("portrait-primary"))
64
+ return "portrait";
65
+ if (type.includes("portrait-secondary"))
66
+ return "portrait-upside-down";
67
+ if (type.includes("landscape-primary"))
68
+ return "landscape-left";
69
+ if (type.includes("landscape-secondary"))
70
+ return "landscape-right";
71
+ if (type.includes("landscape"))
72
+ return "landscape";
73
+ if (type.includes("portrait"))
74
+ return "portrait";
75
+ }
76
+ const angle = window.orientation;
77
+ if (typeof angle === "number") {
78
+ if (angle === 0)
79
+ return "portrait";
80
+ if (angle === 180)
81
+ return "portrait-upside-down";
82
+ if (angle === 90)
83
+ return "landscape-right";
84
+ if (angle === -90)
85
+ return "landscape-left";
86
+ if (angle === 270)
87
+ return "landscape-left";
88
+ }
89
+ if ((_a = window.matchMedia("(orientation: portrait)")) === null || _a === void 0 ? void 0 : _a.matches) {
90
+ return "portrait";
91
+ }
92
+ if ((_b = window.matchMedia("(orientation: landscape)")) === null || _b === void 0 ? void 0 : _b.matches) {
93
+ return "landscape";
94
+ }
95
+ }
96
+ catch (e) {
97
+ console.error(e);
98
+ }
99
+ return "unknown";
100
+ }
101
+ ensureOrientationListener() {
102
+ if (this.orientationListenerBound)
103
+ return;
104
+ const emit = () => {
105
+ this.notifyListeners("orientationChange", {
106
+ orientation: this.getCurrentOrientation(),
107
+ });
108
+ };
109
+ window.addEventListener("orientationchange", emit);
110
+ window.addEventListener("resize", emit);
111
+ this.orientationListenerBound = true;
112
+ }
113
+ async getOrientation() {
114
+ return { orientation: this.getCurrentOrientation() };
115
+ }
116
+ getSafeAreaInsets() {
117
+ throw new Error("Method not implemented.");
118
+ }
119
+ async getZoomButtonValues() {
120
+ throw new Error("getZoomButtonValues not supported under the web platform");
32
121
  }
33
122
  async getSupportedPictureSizes() {
34
123
  throw new Error("getSupportedPictureSizes not supported under the web platform");
@@ -44,6 +133,7 @@ class CameraPreviewWeb extends core.WebPlugin {
44
133
  this.isStarted = false;
45
134
  const parent = document.getElementById((options === null || options === void 0 ? void 0 : options.parent) || "");
46
135
  const gridMode = (options === null || options === void 0 ? void 0 : options.gridMode) || "none";
136
+ const positioning = (options === null || options === void 0 ? void 0 : options.positioning) || "top";
47
137
  if (options.position) {
48
138
  this.isBackCamera = options.position === "rear";
49
139
  }
@@ -51,7 +141,9 @@ class CameraPreviewWeb extends core.WebPlugin {
51
141
  if (video) {
52
142
  video.remove();
53
143
  }
54
- const container = options.parent ? document.getElementById(options.parent) : document.body;
144
+ const container = options.parent
145
+ ? document.getElementById(options.parent)
146
+ : document.body;
55
147
  if (!container) {
56
148
  throw new Error("container not found");
57
149
  }
@@ -61,45 +153,57 @@ class CameraPreviewWeb extends core.WebPlugin {
61
153
  this.videoElement.playsInline = true;
62
154
  this.videoElement.muted = true;
63
155
  this.videoElement.autoplay = true;
156
+ // Remove objectFit as we'll match camera's native aspect ratio
157
+ this.videoElement.style.backgroundColor = "transparent";
158
+ // Reset any default margins that might interfere
159
+ this.videoElement.style.margin = "0";
160
+ this.videoElement.style.padding = "0";
64
161
  container.appendChild(this.videoElement);
65
162
  if (options.toBack) {
66
163
  this.videoElement.style.zIndex = "-1";
67
164
  }
165
+ // Default to 4:3 if no aspect ratio or size specified
166
+ const useDefaultAspectRatio = !options.aspectRatio && !options.width && !options.height;
167
+ const effectiveAspectRatio = options.aspectRatio || (useDefaultAspectRatio ? "4:3" : null);
68
168
  if (options.width) {
69
169
  this.videoElement.width = options.width;
170
+ this.videoElement.style.width = `${options.width}px`;
70
171
  }
71
172
  if (options.height) {
72
173
  this.videoElement.height = options.height;
174
+ this.videoElement.style.height = `${options.height}px`;
73
175
  }
74
- if (options.x) {
176
+ // Handle positioning - center if x or y not provided
177
+ const centerX = options.x === undefined;
178
+ const centerY = options.y === undefined;
179
+ // Always set position to absolute for proper positioning
180
+ this.videoElement.style.position = "absolute";
181
+ console.log("Initial positioning flags:", {
182
+ centerX,
183
+ centerY,
184
+ x: options.x,
185
+ y: options.y,
186
+ });
187
+ if (options.x !== undefined) {
75
188
  this.videoElement.style.left = `${options.x}px`;
76
189
  }
190
+ if (options.y !== undefined) {
191
+ this.videoElement.style.top = `${options.y}px`;
192
+ }
77
193
  // Create and add grid overlay if needed
78
194
  if (gridMode !== "none") {
79
195
  const gridOverlay = this.createGridOverlay(gridMode);
80
196
  gridOverlay.id = "camera-grid-overlay";
81
197
  parent === null || parent === void 0 ? void 0 : parent.appendChild(gridOverlay);
82
198
  }
83
- if (options.y) {
84
- this.videoElement.style.top = `${options.y}px`;
85
- }
86
- if (options.aspectRatio) {
87
- const [widthRatio, heightRatio] = options.aspectRatio.split(':').map(Number);
88
- const ratio = widthRatio / heightRatio;
89
- if (options.width) {
90
- this.videoElement.height = options.width / ratio;
91
- }
92
- else if (options.height) {
93
- this.videoElement.width = options.height * ratio;
94
- }
95
- }
96
- else {
97
- this.videoElement.style.objectFit = 'cover';
98
- }
199
+ // Aspect ratio handling is now done after getting camera stream
200
+ // Store centering flags for later use
201
+ const needsCenterX = centerX;
202
+ const needsCenterY = centerY;
203
+ console.log("Centering flags stored:", { needsCenterX, needsCenterY });
204
+ // First get the camera stream with basic constraints
99
205
  const constraints = {
100
206
  video: {
101
- width: { ideal: this.videoElement.width || 640 },
102
- height: { ideal: this.videoElement.height || window.innerHeight },
103
207
  facingMode: this.isBackCamera ? "environment" : "user",
104
208
  },
105
209
  };
@@ -110,16 +214,226 @@ class CameraPreviewWeb extends core.WebPlugin {
110
214
  if (!this.videoElement) {
111
215
  throw new Error("video element not found");
112
216
  }
217
+ // Get the actual camera dimensions from the video track
218
+ const videoTrack = stream.getVideoTracks()[0];
219
+ const settings = videoTrack.getSettings();
220
+ const cameraWidth = settings.width || 640;
221
+ const cameraHeight = settings.height || 480;
222
+ const cameraAspectRatio = cameraWidth / cameraHeight;
223
+ console.log("Camera native dimensions:", {
224
+ width: cameraWidth,
225
+ height: cameraHeight,
226
+ aspectRatio: cameraAspectRatio,
227
+ });
228
+ console.log("Container dimensions:", {
229
+ width: container.offsetWidth,
230
+ height: container.offsetHeight,
231
+ id: container.id,
232
+ });
233
+ // Now adjust video element size based on camera's native aspect ratio
234
+ if (!options.width && !options.height && !options.aspectRatio) {
235
+ // No size specified, fit camera view within container bounds
236
+ const containerWidth = container.offsetWidth || window.innerWidth;
237
+ const containerHeight = container.offsetHeight || window.innerHeight;
238
+ // Calculate dimensions that fit within container while maintaining camera aspect ratio
239
+ let targetWidth, targetHeight;
240
+ // Try fitting to container width first
241
+ targetWidth = containerWidth;
242
+ targetHeight = targetWidth / cameraAspectRatio;
243
+ // If height exceeds container, fit to height instead
244
+ if (targetHeight > containerHeight) {
245
+ targetHeight = containerHeight;
246
+ targetWidth = targetHeight * cameraAspectRatio;
247
+ }
248
+ console.log("Video element dimensions:", {
249
+ width: targetWidth,
250
+ height: targetHeight,
251
+ container: { width: containerWidth, height: containerHeight },
252
+ });
253
+ this.videoElement.width = targetWidth;
254
+ this.videoElement.height = targetHeight;
255
+ this.videoElement.style.width = `${targetWidth}px`;
256
+ this.videoElement.style.height = `${targetHeight}px`;
257
+ // Center the video element within its parent container
258
+ if (needsCenterX || options.x === undefined) {
259
+ const x = Math.round((containerWidth - targetWidth) / 2);
260
+ this.videoElement.style.left = `${x}px`;
261
+ }
262
+ if (needsCenterY || options.y === undefined) {
263
+ let y;
264
+ switch (positioning) {
265
+ case "top":
266
+ y = 0;
267
+ break;
268
+ case "bottom":
269
+ y = window.innerHeight - targetHeight;
270
+ break;
271
+ case "center":
272
+ default:
273
+ y = Math.round((window.innerHeight - targetHeight) / 2);
274
+ break;
275
+ }
276
+ this.videoElement.style.setProperty("top", `${y}px`, "important");
277
+ // Force a style recalculation
278
+ this.videoElement.offsetHeight;
279
+ console.log("Positioning video:", {
280
+ positioning,
281
+ viewportHeight: window.innerHeight,
282
+ targetHeight,
283
+ calculatedY: y,
284
+ actualTop: this.videoElement.style.top,
285
+ position: this.videoElement.style.position,
286
+ });
287
+ }
288
+ }
289
+ else if (effectiveAspectRatio && !options.width && !options.height) {
290
+ // Aspect ratio specified but no size
291
+ const [widthRatio, heightRatio] = effectiveAspectRatio
292
+ .split(":")
293
+ .map(Number);
294
+ const targetRatio = widthRatio / heightRatio;
295
+ const viewportWidth = window.innerWidth;
296
+ const viewportHeight = window.innerHeight;
297
+ let targetWidth, targetHeight;
298
+ // Try fitting to viewport width first
299
+ targetWidth = viewportWidth;
300
+ targetHeight = targetWidth / targetRatio;
301
+ // If height exceeds viewport, fit to height instead
302
+ if (targetHeight > viewportHeight) {
303
+ targetHeight = viewportHeight;
304
+ targetWidth = targetHeight * targetRatio;
305
+ }
306
+ this.videoElement.width = targetWidth;
307
+ this.videoElement.height = targetHeight;
308
+ this.videoElement.style.width = `${targetWidth}px`;
309
+ this.videoElement.style.height = `${targetHeight}px`;
310
+ // Center the video element within its parent container
311
+ if (needsCenterX || options.x === undefined) {
312
+ const parentWidth = container.offsetWidth || viewportWidth;
313
+ const x = Math.round((parentWidth - targetWidth) / 2);
314
+ this.videoElement.style.left = `${x}px`;
315
+ }
316
+ if (needsCenterY || options.y === undefined) {
317
+ const parentHeight = container.offsetHeight || viewportHeight;
318
+ let y;
319
+ switch (positioning) {
320
+ case "top":
321
+ y = 0;
322
+ break;
323
+ case "bottom":
324
+ y = parentHeight - targetHeight;
325
+ break;
326
+ case "center":
327
+ default:
328
+ y = Math.round((parentHeight - targetHeight) / 2);
329
+ break;
330
+ }
331
+ this.videoElement.style.top = `${y}px`;
332
+ }
333
+ }
113
334
  this.videoElement.srcObject = stream;
114
335
  if (!this.isBackCamera) {
115
336
  this.videoElement.style.transform = "scaleX(-1)";
116
337
  }
338
+ // Set initial zoom level if specified and supported
339
+ if (options.initialZoomLevel !== undefined &&
340
+ options.initialZoomLevel !== 1.0) {
341
+ // videoTrack already declared above
342
+ if (videoTrack) {
343
+ const capabilities = videoTrack.getCapabilities();
344
+ if (capabilities.zoom) {
345
+ const zoomLevel = options.initialZoomLevel;
346
+ const minZoom = capabilities.zoom.min || 1;
347
+ const maxZoom = capabilities.zoom.max || 1;
348
+ if (zoomLevel < minZoom || zoomLevel > maxZoom) {
349
+ stream.getTracks().forEach((track) => track.stop());
350
+ throw new Error(`Initial zoom level ${zoomLevel} is not available. Valid range is ${minZoom} to ${maxZoom}`);
351
+ }
352
+ try {
353
+ await videoTrack.applyConstraints({
354
+ advanced: [{ zoom: zoomLevel }],
355
+ });
356
+ }
357
+ catch (error) {
358
+ console.warn(`Failed to set initial zoom level: ${error}`);
359
+ // Don't throw, just continue without zoom
360
+ }
361
+ }
362
+ }
363
+ }
117
364
  this.isStarted = true;
365
+ this.ensureOrientationListener();
366
+ // Wait for video to be ready and get actual dimensions
367
+ await new Promise((resolve) => {
368
+ const videoEl = this.videoElement;
369
+ if (!videoEl) {
370
+ throw new Error("video element not found");
371
+ }
372
+ if (videoEl.readyState >= 2) {
373
+ resolve();
374
+ }
375
+ else {
376
+ videoEl.addEventListener("loadeddata", () => resolve(), {
377
+ once: true,
378
+ });
379
+ }
380
+ });
381
+ // Ensure centering is applied after DOM updates
382
+ await new Promise((resolve) => requestAnimationFrame(resolve));
383
+ console.log("About to re-center, flags:", { needsCenterX, needsCenterY });
384
+ // Re-apply centering with correct parent dimensions
385
+ if (needsCenterX) {
386
+ const parentWidth = container.offsetWidth;
387
+ const x = Math.round((parentWidth - this.videoElement.offsetWidth) / 2);
388
+ this.videoElement.style.left = `${x}px`;
389
+ console.log("Re-centering X:", {
390
+ parentWidth,
391
+ videoWidth: this.videoElement.offsetWidth,
392
+ x,
393
+ });
394
+ }
395
+ if (needsCenterY) {
396
+ let y;
397
+ switch (positioning) {
398
+ case "top":
399
+ y = 0;
400
+ break;
401
+ case "bottom":
402
+ y = window.innerHeight - this.videoElement.offsetHeight;
403
+ break;
404
+ case "center":
405
+ default:
406
+ y = Math.round((window.innerHeight - this.videoElement.offsetHeight) / 2);
407
+ break;
408
+ }
409
+ this.videoElement.style.setProperty("top", `${y}px`, "important");
410
+ console.log("Re-positioning Y:", {
411
+ positioning,
412
+ viewportHeight: window.innerHeight,
413
+ videoHeight: this.videoElement.offsetHeight,
414
+ y,
415
+ position: this.videoElement.style.position,
416
+ top: this.videoElement.style.top,
417
+ });
418
+ }
419
+ // Get the actual rendered dimensions after video is loaded
420
+ const rect = this.videoElement.getBoundingClientRect();
421
+ const computedStyle = window.getComputedStyle(this.videoElement);
422
+ console.log("Final video element state:", {
423
+ rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
424
+ style: {
425
+ position: computedStyle.position,
426
+ left: computedStyle.left,
427
+ top: computedStyle.top,
428
+ width: computedStyle.width,
429
+ height: computedStyle.height,
430
+ },
431
+ });
118
432
  return {
119
- width: this.videoElement.width,
120
- height: this.videoElement.height,
121
- x: this.videoElement.getBoundingClientRect().x,
122
- y: this.videoElement.getBoundingClientRect().y,
433
+ width: Math.round(rect.width),
434
+ height: Math.round(rect.height),
435
+ x: Math.round(rect.x),
436
+ y: Math.round(rect.y),
123
437
  };
124
438
  }
125
439
  stopStream(stream) {
@@ -153,14 +467,57 @@ class CameraPreviewWeb extends core.WebPlugin {
153
467
  if (video && video.videoWidth > 0 && video.videoHeight > 0) {
154
468
  const canvas = document.createElement("canvas");
155
469
  const context = canvas.getContext("2d");
156
- canvas.width = video.videoWidth;
157
- canvas.height = video.videoHeight;
470
+ // Calculate capture dimensions
471
+ let captureWidth = video.videoWidth;
472
+ let captureHeight = video.videoHeight;
473
+ let sourceX = 0;
474
+ let sourceY = 0;
475
+ // Check for conflicting parameters
476
+ if (options.aspectRatio && (options.width || options.height)) {
477
+ reject(new Error("Cannot set both aspectRatio and size (width/height). Use setPreviewSize after start."));
478
+ return;
479
+ }
480
+ // Handle aspect ratio if no width/height specified
481
+ if (!options.width && !options.height && options.aspectRatio) {
482
+ const [widthRatio, heightRatio] = options.aspectRatio
483
+ .split(":")
484
+ .map(Number);
485
+ if (widthRatio && heightRatio) {
486
+ // For capture in portrait orientation, swap the aspect ratio (16:9 becomes 9:16)
487
+ const isPortrait = video.videoHeight > video.videoWidth;
488
+ const targetAspectRatio = isPortrait
489
+ ? heightRatio / widthRatio
490
+ : widthRatio / heightRatio;
491
+ const videoAspectRatio = video.videoWidth / video.videoHeight;
492
+ if (videoAspectRatio > targetAspectRatio) {
493
+ // Video is wider than target - crop sides
494
+ captureWidth = video.videoHeight * targetAspectRatio;
495
+ captureHeight = video.videoHeight;
496
+ sourceX = (video.videoWidth - captureWidth) / 2;
497
+ }
498
+ else {
499
+ // Video is taller than target - crop top/bottom
500
+ captureWidth = video.videoWidth;
501
+ captureHeight = video.videoWidth / targetAspectRatio;
502
+ sourceY = (video.videoHeight - captureHeight) / 2;
503
+ }
504
+ }
505
+ }
506
+ else if (options.width || options.height) {
507
+ // If width or height is specified, use them
508
+ if (options.width)
509
+ captureWidth = options.width;
510
+ if (options.height)
511
+ captureHeight = options.height;
512
+ }
513
+ canvas.width = captureWidth;
514
+ canvas.height = captureHeight;
158
515
  // flip horizontally back camera isn't used
159
516
  if (!this.isBackCamera) {
160
- context === null || context === void 0 ? void 0 : context.translate(video.videoWidth, 0);
517
+ context === null || context === void 0 ? void 0 : context.translate(captureWidth, 0);
161
518
  context === null || context === void 0 ? void 0 : context.scale(-1, 1);
162
519
  }
163
- context === null || context === void 0 ? void 0 : context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
520
+ context === null || context === void 0 ? void 0 : context.drawImage(video, sourceX, sourceY, captureWidth, captureHeight, 0, 0, captureWidth, captureHeight);
164
521
  if (options.saveToGallery) ;
165
522
  if (options.withExifLocation) ;
166
523
  if ((options.format || "jpeg") === "jpeg") {
@@ -254,7 +611,7 @@ class CameraPreviewWeb extends core.WebPlugin {
254
611
  throw new Error("getAvailableDevices not supported under the web platform");
255
612
  }
256
613
  const devices = await navigator.mediaDevices.enumerateDevices();
257
- const videoDevices = devices.filter(device => device.kind === 'videoinput');
614
+ const videoDevices = devices.filter((device) => device.kind === "videoinput");
258
615
  // Group devices by position (front/back)
259
616
  const frontDevices = [];
260
617
  const backDevices = [];
@@ -264,15 +621,19 @@ class CameraPreviewWeb extends core.WebPlugin {
264
621
  // Determine device type based on label
265
622
  let deviceType = exports.DeviceType.WIDE_ANGLE;
266
623
  let baseZoomRatio = 1.0;
267
- if (labelLower.includes('ultra') || labelLower.includes('0.5')) {
624
+ if (labelLower.includes("ultra") || labelLower.includes("0.5")) {
268
625
  deviceType = exports.DeviceType.ULTRA_WIDE;
269
626
  baseZoomRatio = 0.5;
270
627
  }
271
- else if (labelLower.includes('telephoto') || labelLower.includes('tele') || labelLower.includes('2x') || labelLower.includes('3x')) {
628
+ else if (labelLower.includes("telephoto") ||
629
+ labelLower.includes("tele") ||
630
+ labelLower.includes("2x") ||
631
+ labelLower.includes("3x")) {
272
632
  deviceType = exports.DeviceType.TELEPHOTO;
273
633
  baseZoomRatio = 2.0;
274
634
  }
275
- else if (labelLower.includes('depth') || labelLower.includes('truedepth')) {
635
+ else if (labelLower.includes("depth") ||
636
+ labelLower.includes("truedepth")) {
276
637
  deviceType = exports.DeviceType.TRUE_DEPTH;
277
638
  baseZoomRatio = 1.0;
278
639
  }
@@ -283,10 +644,10 @@ class CameraPreviewWeb extends core.WebPlugin {
283
644
  focalLength: 4.25,
284
645
  baseZoomRatio,
285
646
  minZoom: 1.0,
286
- maxZoom: 1.0
647
+ maxZoom: 1.0,
287
648
  };
288
649
  // Determine position and add to appropriate array
289
- if (labelLower.includes('back') || labelLower.includes('rear')) {
650
+ if (labelLower.includes("back") || labelLower.includes("rear")) {
290
651
  backDevices.push(lensInfo);
291
652
  }
292
653
  else {
@@ -301,8 +662,8 @@ class CameraPreviewWeb extends core.WebPlugin {
301
662
  position: "front",
302
663
  lenses: frontDevices,
303
664
  isLogical: false,
304
- minZoom: Math.min(...frontDevices.map(d => d.minZoom)),
305
- maxZoom: Math.max(...frontDevices.map(d => d.maxZoom))
665
+ minZoom: Math.min(...frontDevices.map((d) => d.minZoom)),
666
+ maxZoom: Math.max(...frontDevices.map((d) => d.maxZoom)),
306
667
  });
307
668
  }
308
669
  if (backDevices.length > 0) {
@@ -312,8 +673,8 @@ class CameraPreviewWeb extends core.WebPlugin {
312
673
  position: "rear",
313
674
  lenses: backDevices,
314
675
  isLogical: false,
315
- minZoom: Math.min(...backDevices.map(d => d.minZoom)),
316
- maxZoom: Math.max(...backDevices.map(d => d.maxZoom))
676
+ minZoom: Math.min(...backDevices.map((d) => d.minZoom)),
677
+ maxZoom: Math.max(...backDevices.map((d) => d.maxZoom)),
317
678
  });
318
679
  }
319
680
  return { devices: result };
@@ -338,18 +699,22 @@ class CameraPreviewWeb extends core.WebPlugin {
338
699
  let baseZoomRatio = 1.0;
339
700
  if (this.currentDeviceId) {
340
701
  const devices = await navigator.mediaDevices.enumerateDevices();
341
- const device = devices.find(d => d.deviceId === this.currentDeviceId);
702
+ const device = devices.find((d) => d.deviceId === this.currentDeviceId);
342
703
  if (device) {
343
704
  const labelLower = device.label.toLowerCase();
344
- if (labelLower.includes('ultra') || labelLower.includes('0.5')) {
705
+ if (labelLower.includes("ultra") || labelLower.includes("0.5")) {
345
706
  deviceType = exports.DeviceType.ULTRA_WIDE;
346
707
  baseZoomRatio = 0.5;
347
708
  }
348
- else if (labelLower.includes('telephoto') || labelLower.includes('tele') || labelLower.includes('2x') || labelLower.includes('3x')) {
709
+ else if (labelLower.includes("telephoto") ||
710
+ labelLower.includes("tele") ||
711
+ labelLower.includes("2x") ||
712
+ labelLower.includes("3x")) {
349
713
  deviceType = exports.DeviceType.TELEPHOTO;
350
714
  baseZoomRatio = 2.0;
351
715
  }
352
- else if (labelLower.includes('depth') || labelLower.includes('truedepth')) {
716
+ else if (labelLower.includes("depth") ||
717
+ labelLower.includes("truedepth")) {
353
718
  deviceType = exports.DeviceType.TRUE_DEPTH;
354
719
  baseZoomRatio = 1.0;
355
720
  }
@@ -360,7 +725,7 @@ class CameraPreviewWeb extends core.WebPlugin {
360
725
  focalLength: 4.25,
361
726
  deviceType,
362
727
  baseZoomRatio,
363
- digitalZoom: currentZoom / baseZoomRatio
728
+ digitalZoom: currentZoom / baseZoomRatio,
364
729
  };
365
730
  return {
366
731
  min: capabilities.zoom.min || 1,
@@ -384,9 +749,10 @@ class CameraPreviewWeb extends core.WebPlugin {
384
749
  throw new Error("zoom not supported by this device");
385
750
  }
386
751
  const zoomLevel = Math.max(capabilities.zoom.min || 1, Math.min(capabilities.zoom.max || 1, options.level));
752
+ // Note: autoFocus is not supported on web platform
387
753
  try {
388
754
  await videoTrack.applyConstraints({
389
- advanced: [{ zoom: zoomLevel }]
755
+ advanced: [{ zoom: zoomLevel }],
390
756
  });
391
757
  }
392
758
  catch (error) {
@@ -419,8 +785,11 @@ class CameraPreviewWeb extends core.WebPlugin {
419
785
  try {
420
786
  // Try to determine camera position from device
421
787
  const devices = await navigator.mediaDevices.enumerateDevices();
422
- const device = devices.find(d => d.deviceId === options.deviceId);
423
- this.isBackCamera = (device === null || device === void 0 ? void 0 : device.label.toLowerCase().includes('back')) || (device === null || device === void 0 ? void 0 : device.label.toLowerCase().includes('rear')) || false;
788
+ const device = devices.find((d) => d.deviceId === options.deviceId);
789
+ this.isBackCamera =
790
+ (device === null || device === void 0 ? void 0 : device.label.toLowerCase().includes("back")) ||
791
+ (device === null || device === void 0 ? void 0 : device.label.toLowerCase().includes("rear")) ||
792
+ false;
424
793
  const stream = await navigator.mediaDevices.getUserMedia(constraints);
425
794
  video.srcObject = stream;
426
795
  // Update video transform based on camera
@@ -448,15 +817,15 @@ class CameraPreviewWeb extends core.WebPlugin {
448
817
  if (width && height) {
449
818
  const ratio = width / height;
450
819
  // Check for portrait camera ratios: 4:3 -> 3:4, 16:9 -> 9:16
451
- if (Math.abs(ratio - (3 / 4)) < 0.01) {
452
- return { aspectRatio: '4:3' };
820
+ if (Math.abs(ratio - 3 / 4) < 0.01) {
821
+ return { aspectRatio: "4:3" };
453
822
  }
454
- if (Math.abs(ratio - (9 / 16)) < 0.01) {
455
- return { aspectRatio: '16:9' };
823
+ if (Math.abs(ratio - 9 / 16) < 0.01) {
824
+ return { aspectRatio: "16:9" };
456
825
  }
457
826
  }
458
827
  // Default to 4:3 if no specific aspect ratio is matched
459
- return { aspectRatio: '4:3' };
828
+ return { aspectRatio: "4:3" };
460
829
  }
461
830
  async setAspectRatio(options) {
462
831
  const video = document.getElementById(DEFAULT_VIDEO_ID);
@@ -464,7 +833,9 @@ class CameraPreviewWeb extends core.WebPlugin {
464
833
  throw new Error("camera is not running");
465
834
  }
466
835
  if (options.aspectRatio) {
467
- const [widthRatio, heightRatio] = options.aspectRatio.split(':').map(Number);
836
+ const [widthRatio, heightRatio] = options.aspectRatio
837
+ .split(":")
838
+ .map(Number);
468
839
  // For camera, use portrait orientation: 4:3 becomes 3:4, 16:9 becomes 9:16
469
840
  const ratio = heightRatio / widthRatio;
470
841
  // Get current position and size
@@ -500,22 +871,26 @@ class CameraPreviewWeb extends core.WebPlugin {
500
871
  video.style.height = `${newHeight}px`;
501
872
  video.style.left = `${x}px`;
502
873
  video.style.top = `${y}px`;
503
- video.style.position = 'absolute';
874
+ video.style.position = "absolute";
875
+ const offsetX = newWidth / 8;
876
+ const offsetY = newHeight / 8;
504
877
  return {
505
878
  width: Math.round(newWidth),
506
879
  height: Math.round(newHeight),
507
- x: Math.round(x),
508
- y: Math.round(y)
880
+ x: Math.round(x + offsetX),
881
+ y: Math.round(y + offsetY),
509
882
  };
510
883
  }
511
884
  else {
512
- video.style.objectFit = 'cover';
885
+ video.style.objectFit = "cover";
513
886
  const rect = video.getBoundingClientRect();
887
+ const offsetX = rect.width / 8;
888
+ const offsetY = rect.height / 8;
514
889
  return {
515
890
  width: Math.round(rect.width),
516
891
  height: Math.round(rect.height),
517
- x: Math.round(rect.left),
518
- y: Math.round(rect.top)
892
+ x: Math.round(rect.left + offsetX),
893
+ y: Math.round(rect.top + offsetY),
519
894
  };
520
895
  }
521
896
  }
@@ -567,18 +942,20 @@ class CameraPreviewWeb extends core.WebPlugin {
567
942
  }
568
943
  async getGridMode() {
569
944
  // Web implementation - default to none
570
- return { gridMode: 'none' };
945
+ return { gridMode: "none" };
571
946
  }
572
947
  async getPreviewSize() {
573
948
  const video = document.getElementById(DEFAULT_VIDEO_ID);
574
949
  if (!video) {
575
950
  throw new Error("camera is not running");
576
951
  }
952
+ const offsetX = video.width / 8;
953
+ const offsetY = video.height / 8;
577
954
  return {
578
- x: video.offsetLeft,
579
- y: video.offsetTop,
955
+ x: video.offsetLeft + offsetX,
956
+ y: video.offsetTop + offsetY,
580
957
  width: video.width,
581
- height: video.height
958
+ height: video.height,
582
959
  };
583
960
  }
584
961
  async setPreviewSize(options) {
@@ -590,13 +967,55 @@ class CameraPreviewWeb extends core.WebPlugin {
590
967
  video.style.top = `${options.y}px`;
591
968
  video.width = options.width;
592
969
  video.height = options.height;
970
+ const offsetX = options.width / 8;
971
+ const offsetY = options.height / 8;
593
972
  return {
594
973
  width: options.width,
595
974
  height: options.height,
596
- x: options.x,
597
- y: options.y
975
+ x: options.x + offsetX,
976
+ y: options.y + offsetY,
598
977
  };
599
978
  }
979
+ async setFocus(options) {
980
+ // Reject if values are outside 0-1 range
981
+ if (options.x < 0 || options.x > 1 || options.y < 0 || options.y > 1) {
982
+ throw new Error("Focus coordinates must be between 0 and 1");
983
+ }
984
+ const video = document.getElementById(DEFAULT_VIDEO_ID);
985
+ if (!(video === null || video === void 0 ? void 0 : video.srcObject)) {
986
+ throw new Error("camera is not running");
987
+ }
988
+ const stream = video.srcObject;
989
+ const videoTrack = stream.getVideoTracks()[0];
990
+ if (!videoTrack) {
991
+ throw new Error("no video track found");
992
+ }
993
+ const capabilities = videoTrack.getCapabilities();
994
+ // Check if focusing is supported
995
+ if (capabilities.focusMode) {
996
+ try {
997
+ // Web API supports focus mode settings but not coordinate-based focus
998
+ // Setting to manual mode allows for coordinate focus if supported
999
+ await videoTrack.applyConstraints({
1000
+ advanced: [
1001
+ {
1002
+ focusMode: "manual",
1003
+ focusDistance: 0.5, // Mid-range focus as fallback
1004
+ },
1005
+ ],
1006
+ });
1007
+ }
1008
+ catch (error) {
1009
+ console.warn(`setFocus is not fully supported on this device: ${error}. Focus coordinates (${options.x}, ${options.y}) were provided but cannot be applied.`);
1010
+ }
1011
+ }
1012
+ else {
1013
+ console.warn("Focus control is not supported on this device. Focus coordinates were provided but cannot be applied.");
1014
+ }
1015
+ }
1016
+ async deleteFile(_options) {
1017
+ throw new Error("deleteFile not supported under the web platform");
1018
+ }
600
1019
  }
601
1020
 
602
1021
  var web = /*#__PURE__*/Object.freeze({
@@ -605,4 +1024,6 @@ var web = /*#__PURE__*/Object.freeze({
605
1024
  });
606
1025
 
607
1026
  exports.CameraPreview = CameraPreview;
1027
+ exports.deleteFile = deleteFile;
1028
+ exports.getBase64FromFilePath = getBase64FromFilePath;
608
1029
  //# sourceMappingURL=plugin.cjs.js.map