@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
package/dist/esm/web.js CHANGED
@@ -12,6 +12,72 @@ export class CameraPreviewWeb extends WebPlugin {
12
12
  this.currentDeviceId = null;
13
13
  this.videoElement = null;
14
14
  this.isStarted = false;
15
+ this.orientationListenerBound = false;
16
+ }
17
+ getCurrentOrientation() {
18
+ var _a, _b;
19
+ try {
20
+ const so = screen.orientation;
21
+ 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);
22
+ if (typeof type === "string") {
23
+ if (type.includes("portrait-primary"))
24
+ return "portrait";
25
+ if (type.includes("portrait-secondary"))
26
+ return "portrait-upside-down";
27
+ if (type.includes("landscape-primary"))
28
+ return "landscape-left";
29
+ if (type.includes("landscape-secondary"))
30
+ return "landscape-right";
31
+ if (type.includes("landscape"))
32
+ return "landscape";
33
+ if (type.includes("portrait"))
34
+ return "portrait";
35
+ }
36
+ const angle = window.orientation;
37
+ if (typeof angle === "number") {
38
+ if (angle === 0)
39
+ return "portrait";
40
+ if (angle === 180)
41
+ return "portrait-upside-down";
42
+ if (angle === 90)
43
+ return "landscape-right";
44
+ if (angle === -90)
45
+ return "landscape-left";
46
+ if (angle === 270)
47
+ return "landscape-left";
48
+ }
49
+ if ((_a = window.matchMedia("(orientation: portrait)")) === null || _a === void 0 ? void 0 : _a.matches) {
50
+ return "portrait";
51
+ }
52
+ if ((_b = window.matchMedia("(orientation: landscape)")) === null || _b === void 0 ? void 0 : _b.matches) {
53
+ return "landscape";
54
+ }
55
+ }
56
+ catch (e) {
57
+ console.error(e);
58
+ }
59
+ return "unknown";
60
+ }
61
+ ensureOrientationListener() {
62
+ if (this.orientationListenerBound)
63
+ return;
64
+ const emit = () => {
65
+ this.notifyListeners("orientationChange", {
66
+ orientation: this.getCurrentOrientation(),
67
+ });
68
+ };
69
+ window.addEventListener("orientationchange", emit);
70
+ window.addEventListener("resize", emit);
71
+ this.orientationListenerBound = true;
72
+ }
73
+ async getOrientation() {
74
+ return { orientation: this.getCurrentOrientation() };
75
+ }
76
+ getSafeAreaInsets() {
77
+ throw new Error("Method not implemented.");
78
+ }
79
+ async getZoomButtonValues() {
80
+ throw new Error("getZoomButtonValues not supported under the web platform");
15
81
  }
16
82
  async getSupportedPictureSizes() {
17
83
  throw new Error("getSupportedPictureSizes not supported under the web platform");
@@ -27,6 +93,7 @@ export class CameraPreviewWeb extends WebPlugin {
27
93
  this.isStarted = false;
28
94
  const parent = document.getElementById((options === null || options === void 0 ? void 0 : options.parent) || "");
29
95
  const gridMode = (options === null || options === void 0 ? void 0 : options.gridMode) || "none";
96
+ const positioning = (options === null || options === void 0 ? void 0 : options.positioning) || "top";
30
97
  if (options.position) {
31
98
  this.isBackCamera = options.position === "rear";
32
99
  }
@@ -34,7 +101,9 @@ export class CameraPreviewWeb extends WebPlugin {
34
101
  if (video) {
35
102
  video.remove();
36
103
  }
37
- const container = options.parent ? document.getElementById(options.parent) : document.body;
104
+ const container = options.parent
105
+ ? document.getElementById(options.parent)
106
+ : document.body;
38
107
  if (!container) {
39
108
  throw new Error("container not found");
40
109
  }
@@ -44,45 +113,57 @@ export class CameraPreviewWeb extends WebPlugin {
44
113
  this.videoElement.playsInline = true;
45
114
  this.videoElement.muted = true;
46
115
  this.videoElement.autoplay = true;
116
+ // Remove objectFit as we'll match camera's native aspect ratio
117
+ this.videoElement.style.backgroundColor = "transparent";
118
+ // Reset any default margins that might interfere
119
+ this.videoElement.style.margin = "0";
120
+ this.videoElement.style.padding = "0";
47
121
  container.appendChild(this.videoElement);
48
122
  if (options.toBack) {
49
123
  this.videoElement.style.zIndex = "-1";
50
124
  }
125
+ // Default to 4:3 if no aspect ratio or size specified
126
+ const useDefaultAspectRatio = !options.aspectRatio && !options.width && !options.height;
127
+ const effectiveAspectRatio = options.aspectRatio || (useDefaultAspectRatio ? "4:3" : null);
51
128
  if (options.width) {
52
129
  this.videoElement.width = options.width;
130
+ this.videoElement.style.width = `${options.width}px`;
53
131
  }
54
132
  if (options.height) {
55
133
  this.videoElement.height = options.height;
56
- }
57
- if (options.x) {
134
+ this.videoElement.style.height = `${options.height}px`;
135
+ }
136
+ // Handle positioning - center if x or y not provided
137
+ const centerX = options.x === undefined;
138
+ const centerY = options.y === undefined;
139
+ // Always set position to absolute for proper positioning
140
+ this.videoElement.style.position = "absolute";
141
+ console.log("Initial positioning flags:", {
142
+ centerX,
143
+ centerY,
144
+ x: options.x,
145
+ y: options.y,
146
+ });
147
+ if (options.x !== undefined) {
58
148
  this.videoElement.style.left = `${options.x}px`;
59
149
  }
150
+ if (options.y !== undefined) {
151
+ this.videoElement.style.top = `${options.y}px`;
152
+ }
60
153
  // Create and add grid overlay if needed
61
154
  if (gridMode !== "none") {
62
155
  const gridOverlay = this.createGridOverlay(gridMode);
63
156
  gridOverlay.id = "camera-grid-overlay";
64
157
  parent === null || parent === void 0 ? void 0 : parent.appendChild(gridOverlay);
65
158
  }
66
- if (options.y) {
67
- this.videoElement.style.top = `${options.y}px`;
68
- }
69
- if (options.aspectRatio) {
70
- const [widthRatio, heightRatio] = options.aspectRatio.split(':').map(Number);
71
- const ratio = widthRatio / heightRatio;
72
- if (options.width) {
73
- this.videoElement.height = options.width / ratio;
74
- }
75
- else if (options.height) {
76
- this.videoElement.width = options.height * ratio;
77
- }
78
- }
79
- else {
80
- this.videoElement.style.objectFit = 'cover';
81
- }
159
+ // Aspect ratio handling is now done after getting camera stream
160
+ // Store centering flags for later use
161
+ const needsCenterX = centerX;
162
+ const needsCenterY = centerY;
163
+ console.log("Centering flags stored:", { needsCenterX, needsCenterY });
164
+ // First get the camera stream with basic constraints
82
165
  const constraints = {
83
166
  video: {
84
- width: { ideal: this.videoElement.width || 640 },
85
- height: { ideal: this.videoElement.height || window.innerHeight },
86
167
  facingMode: this.isBackCamera ? "environment" : "user",
87
168
  },
88
169
  };
@@ -93,16 +174,226 @@ export class CameraPreviewWeb extends WebPlugin {
93
174
  if (!this.videoElement) {
94
175
  throw new Error("video element not found");
95
176
  }
177
+ // Get the actual camera dimensions from the video track
178
+ const videoTrack = stream.getVideoTracks()[0];
179
+ const settings = videoTrack.getSettings();
180
+ const cameraWidth = settings.width || 640;
181
+ const cameraHeight = settings.height || 480;
182
+ const cameraAspectRatio = cameraWidth / cameraHeight;
183
+ console.log("Camera native dimensions:", {
184
+ width: cameraWidth,
185
+ height: cameraHeight,
186
+ aspectRatio: cameraAspectRatio,
187
+ });
188
+ console.log("Container dimensions:", {
189
+ width: container.offsetWidth,
190
+ height: container.offsetHeight,
191
+ id: container.id,
192
+ });
193
+ // Now adjust video element size based on camera's native aspect ratio
194
+ if (!options.width && !options.height && !options.aspectRatio) {
195
+ // No size specified, fit camera view within container bounds
196
+ const containerWidth = container.offsetWidth || window.innerWidth;
197
+ const containerHeight = container.offsetHeight || window.innerHeight;
198
+ // Calculate dimensions that fit within container while maintaining camera aspect ratio
199
+ let targetWidth, targetHeight;
200
+ // Try fitting to container width first
201
+ targetWidth = containerWidth;
202
+ targetHeight = targetWidth / cameraAspectRatio;
203
+ // If height exceeds container, fit to height instead
204
+ if (targetHeight > containerHeight) {
205
+ targetHeight = containerHeight;
206
+ targetWidth = targetHeight * cameraAspectRatio;
207
+ }
208
+ console.log("Video element dimensions:", {
209
+ width: targetWidth,
210
+ height: targetHeight,
211
+ container: { width: containerWidth, height: containerHeight },
212
+ });
213
+ this.videoElement.width = targetWidth;
214
+ this.videoElement.height = targetHeight;
215
+ this.videoElement.style.width = `${targetWidth}px`;
216
+ this.videoElement.style.height = `${targetHeight}px`;
217
+ // Center the video element within its parent container
218
+ if (needsCenterX || options.x === undefined) {
219
+ const x = Math.round((containerWidth - targetWidth) / 2);
220
+ this.videoElement.style.left = `${x}px`;
221
+ }
222
+ if (needsCenterY || options.y === undefined) {
223
+ let y;
224
+ switch (positioning) {
225
+ case "top":
226
+ y = 0;
227
+ break;
228
+ case "bottom":
229
+ y = window.innerHeight - targetHeight;
230
+ break;
231
+ case "center":
232
+ default:
233
+ y = Math.round((window.innerHeight - targetHeight) / 2);
234
+ break;
235
+ }
236
+ this.videoElement.style.setProperty("top", `${y}px`, "important");
237
+ // Force a style recalculation
238
+ this.videoElement.offsetHeight;
239
+ console.log("Positioning video:", {
240
+ positioning,
241
+ viewportHeight: window.innerHeight,
242
+ targetHeight,
243
+ calculatedY: y,
244
+ actualTop: this.videoElement.style.top,
245
+ position: this.videoElement.style.position,
246
+ });
247
+ }
248
+ }
249
+ else if (effectiveAspectRatio && !options.width && !options.height) {
250
+ // Aspect ratio specified but no size
251
+ const [widthRatio, heightRatio] = effectiveAspectRatio
252
+ .split(":")
253
+ .map(Number);
254
+ const targetRatio = widthRatio / heightRatio;
255
+ const viewportWidth = window.innerWidth;
256
+ const viewportHeight = window.innerHeight;
257
+ let targetWidth, targetHeight;
258
+ // Try fitting to viewport width first
259
+ targetWidth = viewportWidth;
260
+ targetHeight = targetWidth / targetRatio;
261
+ // If height exceeds viewport, fit to height instead
262
+ if (targetHeight > viewportHeight) {
263
+ targetHeight = viewportHeight;
264
+ targetWidth = targetHeight * targetRatio;
265
+ }
266
+ this.videoElement.width = targetWidth;
267
+ this.videoElement.height = targetHeight;
268
+ this.videoElement.style.width = `${targetWidth}px`;
269
+ this.videoElement.style.height = `${targetHeight}px`;
270
+ // Center the video element within its parent container
271
+ if (needsCenterX || options.x === undefined) {
272
+ const parentWidth = container.offsetWidth || viewportWidth;
273
+ const x = Math.round((parentWidth - targetWidth) / 2);
274
+ this.videoElement.style.left = `${x}px`;
275
+ }
276
+ if (needsCenterY || options.y === undefined) {
277
+ const parentHeight = container.offsetHeight || viewportHeight;
278
+ let y;
279
+ switch (positioning) {
280
+ case "top":
281
+ y = 0;
282
+ break;
283
+ case "bottom":
284
+ y = parentHeight - targetHeight;
285
+ break;
286
+ case "center":
287
+ default:
288
+ y = Math.round((parentHeight - targetHeight) / 2);
289
+ break;
290
+ }
291
+ this.videoElement.style.top = `${y}px`;
292
+ }
293
+ }
96
294
  this.videoElement.srcObject = stream;
97
295
  if (!this.isBackCamera) {
98
296
  this.videoElement.style.transform = "scaleX(-1)";
99
297
  }
298
+ // Set initial zoom level if specified and supported
299
+ if (options.initialZoomLevel !== undefined &&
300
+ options.initialZoomLevel !== 1.0) {
301
+ // videoTrack already declared above
302
+ if (videoTrack) {
303
+ const capabilities = videoTrack.getCapabilities();
304
+ if (capabilities.zoom) {
305
+ const zoomLevel = options.initialZoomLevel;
306
+ const minZoom = capabilities.zoom.min || 1;
307
+ const maxZoom = capabilities.zoom.max || 1;
308
+ if (zoomLevel < minZoom || zoomLevel > maxZoom) {
309
+ stream.getTracks().forEach((track) => track.stop());
310
+ throw new Error(`Initial zoom level ${zoomLevel} is not available. Valid range is ${minZoom} to ${maxZoom}`);
311
+ }
312
+ try {
313
+ await videoTrack.applyConstraints({
314
+ advanced: [{ zoom: zoomLevel }],
315
+ });
316
+ }
317
+ catch (error) {
318
+ console.warn(`Failed to set initial zoom level: ${error}`);
319
+ // Don't throw, just continue without zoom
320
+ }
321
+ }
322
+ }
323
+ }
100
324
  this.isStarted = true;
325
+ this.ensureOrientationListener();
326
+ // Wait for video to be ready and get actual dimensions
327
+ await new Promise((resolve) => {
328
+ const videoEl = this.videoElement;
329
+ if (!videoEl) {
330
+ throw new Error("video element not found");
331
+ }
332
+ if (videoEl.readyState >= 2) {
333
+ resolve();
334
+ }
335
+ else {
336
+ videoEl.addEventListener("loadeddata", () => resolve(), {
337
+ once: true,
338
+ });
339
+ }
340
+ });
341
+ // Ensure centering is applied after DOM updates
342
+ await new Promise((resolve) => requestAnimationFrame(resolve));
343
+ console.log("About to re-center, flags:", { needsCenterX, needsCenterY });
344
+ // Re-apply centering with correct parent dimensions
345
+ if (needsCenterX) {
346
+ const parentWidth = container.offsetWidth;
347
+ const x = Math.round((parentWidth - this.videoElement.offsetWidth) / 2);
348
+ this.videoElement.style.left = `${x}px`;
349
+ console.log("Re-centering X:", {
350
+ parentWidth,
351
+ videoWidth: this.videoElement.offsetWidth,
352
+ x,
353
+ });
354
+ }
355
+ if (needsCenterY) {
356
+ let y;
357
+ switch (positioning) {
358
+ case "top":
359
+ y = 0;
360
+ break;
361
+ case "bottom":
362
+ y = window.innerHeight - this.videoElement.offsetHeight;
363
+ break;
364
+ case "center":
365
+ default:
366
+ y = Math.round((window.innerHeight - this.videoElement.offsetHeight) / 2);
367
+ break;
368
+ }
369
+ this.videoElement.style.setProperty("top", `${y}px`, "important");
370
+ console.log("Re-positioning Y:", {
371
+ positioning,
372
+ viewportHeight: window.innerHeight,
373
+ videoHeight: this.videoElement.offsetHeight,
374
+ y,
375
+ position: this.videoElement.style.position,
376
+ top: this.videoElement.style.top,
377
+ });
378
+ }
379
+ // Get the actual rendered dimensions after video is loaded
380
+ const rect = this.videoElement.getBoundingClientRect();
381
+ const computedStyle = window.getComputedStyle(this.videoElement);
382
+ console.log("Final video element state:", {
383
+ rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
384
+ style: {
385
+ position: computedStyle.position,
386
+ left: computedStyle.left,
387
+ top: computedStyle.top,
388
+ width: computedStyle.width,
389
+ height: computedStyle.height,
390
+ },
391
+ });
101
392
  return {
102
- width: this.videoElement.width,
103
- height: this.videoElement.height,
104
- x: this.videoElement.getBoundingClientRect().x,
105
- y: this.videoElement.getBoundingClientRect().y,
393
+ width: Math.round(rect.width),
394
+ height: Math.round(rect.height),
395
+ x: Math.round(rect.x),
396
+ y: Math.round(rect.y),
106
397
  };
107
398
  }
108
399
  stopStream(stream) {
@@ -136,14 +427,57 @@ export class CameraPreviewWeb extends WebPlugin {
136
427
  if (video && video.videoWidth > 0 && video.videoHeight > 0) {
137
428
  const canvas = document.createElement("canvas");
138
429
  const context = canvas.getContext("2d");
139
- canvas.width = video.videoWidth;
140
- canvas.height = video.videoHeight;
430
+ // Calculate capture dimensions
431
+ let captureWidth = video.videoWidth;
432
+ let captureHeight = video.videoHeight;
433
+ let sourceX = 0;
434
+ let sourceY = 0;
435
+ // Check for conflicting parameters
436
+ if (options.aspectRatio && (options.width || options.height)) {
437
+ reject(new Error("Cannot set both aspectRatio and size (width/height). Use setPreviewSize after start."));
438
+ return;
439
+ }
440
+ // Handle aspect ratio if no width/height specified
441
+ if (!options.width && !options.height && options.aspectRatio) {
442
+ const [widthRatio, heightRatio] = options.aspectRatio
443
+ .split(":")
444
+ .map(Number);
445
+ if (widthRatio && heightRatio) {
446
+ // For capture in portrait orientation, swap the aspect ratio (16:9 becomes 9:16)
447
+ const isPortrait = video.videoHeight > video.videoWidth;
448
+ const targetAspectRatio = isPortrait
449
+ ? heightRatio / widthRatio
450
+ : widthRatio / heightRatio;
451
+ const videoAspectRatio = video.videoWidth / video.videoHeight;
452
+ if (videoAspectRatio > targetAspectRatio) {
453
+ // Video is wider than target - crop sides
454
+ captureWidth = video.videoHeight * targetAspectRatio;
455
+ captureHeight = video.videoHeight;
456
+ sourceX = (video.videoWidth - captureWidth) / 2;
457
+ }
458
+ else {
459
+ // Video is taller than target - crop top/bottom
460
+ captureWidth = video.videoWidth;
461
+ captureHeight = video.videoWidth / targetAspectRatio;
462
+ sourceY = (video.videoHeight - captureHeight) / 2;
463
+ }
464
+ }
465
+ }
466
+ else if (options.width || options.height) {
467
+ // If width or height is specified, use them
468
+ if (options.width)
469
+ captureWidth = options.width;
470
+ if (options.height)
471
+ captureHeight = options.height;
472
+ }
473
+ canvas.width = captureWidth;
474
+ canvas.height = captureHeight;
141
475
  // flip horizontally back camera isn't used
142
476
  if (!this.isBackCamera) {
143
- context === null || context === void 0 ? void 0 : context.translate(video.videoWidth, 0);
477
+ context === null || context === void 0 ? void 0 : context.translate(captureWidth, 0);
144
478
  context === null || context === void 0 ? void 0 : context.scale(-1, 1);
145
479
  }
146
- context === null || context === void 0 ? void 0 : context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
480
+ context === null || context === void 0 ? void 0 : context.drawImage(video, sourceX, sourceY, captureWidth, captureHeight, 0, 0, captureWidth, captureHeight);
147
481
  if (options.saveToGallery) {
148
482
  // saveToGallery is not supported on web
149
483
  }
@@ -241,7 +575,7 @@ export class CameraPreviewWeb extends WebPlugin {
241
575
  throw new Error("getAvailableDevices not supported under the web platform");
242
576
  }
243
577
  const devices = await navigator.mediaDevices.enumerateDevices();
244
- const videoDevices = devices.filter(device => device.kind === 'videoinput');
578
+ const videoDevices = devices.filter((device) => device.kind === "videoinput");
245
579
  // Group devices by position (front/back)
246
580
  const frontDevices = [];
247
581
  const backDevices = [];
@@ -251,15 +585,19 @@ export class CameraPreviewWeb extends WebPlugin {
251
585
  // Determine device type based on label
252
586
  let deviceType = DeviceType.WIDE_ANGLE;
253
587
  let baseZoomRatio = 1.0;
254
- if (labelLower.includes('ultra') || labelLower.includes('0.5')) {
588
+ if (labelLower.includes("ultra") || labelLower.includes("0.5")) {
255
589
  deviceType = DeviceType.ULTRA_WIDE;
256
590
  baseZoomRatio = 0.5;
257
591
  }
258
- else if (labelLower.includes('telephoto') || labelLower.includes('tele') || labelLower.includes('2x') || labelLower.includes('3x')) {
592
+ else if (labelLower.includes("telephoto") ||
593
+ labelLower.includes("tele") ||
594
+ labelLower.includes("2x") ||
595
+ labelLower.includes("3x")) {
259
596
  deviceType = DeviceType.TELEPHOTO;
260
597
  baseZoomRatio = 2.0;
261
598
  }
262
- else if (labelLower.includes('depth') || labelLower.includes('truedepth')) {
599
+ else if (labelLower.includes("depth") ||
600
+ labelLower.includes("truedepth")) {
263
601
  deviceType = DeviceType.TRUE_DEPTH;
264
602
  baseZoomRatio = 1.0;
265
603
  }
@@ -270,10 +608,10 @@ export class CameraPreviewWeb extends WebPlugin {
270
608
  focalLength: 4.25,
271
609
  baseZoomRatio,
272
610
  minZoom: 1.0,
273
- maxZoom: 1.0
611
+ maxZoom: 1.0,
274
612
  };
275
613
  // Determine position and add to appropriate array
276
- if (labelLower.includes('back') || labelLower.includes('rear')) {
614
+ if (labelLower.includes("back") || labelLower.includes("rear")) {
277
615
  backDevices.push(lensInfo);
278
616
  }
279
617
  else {
@@ -288,8 +626,8 @@ export class CameraPreviewWeb extends WebPlugin {
288
626
  position: "front",
289
627
  lenses: frontDevices,
290
628
  isLogical: false,
291
- minZoom: Math.min(...frontDevices.map(d => d.minZoom)),
292
- maxZoom: Math.max(...frontDevices.map(d => d.maxZoom))
629
+ minZoom: Math.min(...frontDevices.map((d) => d.minZoom)),
630
+ maxZoom: Math.max(...frontDevices.map((d) => d.maxZoom)),
293
631
  });
294
632
  }
295
633
  if (backDevices.length > 0) {
@@ -299,8 +637,8 @@ export class CameraPreviewWeb extends WebPlugin {
299
637
  position: "rear",
300
638
  lenses: backDevices,
301
639
  isLogical: false,
302
- minZoom: Math.min(...backDevices.map(d => d.minZoom)),
303
- maxZoom: Math.max(...backDevices.map(d => d.maxZoom))
640
+ minZoom: Math.min(...backDevices.map((d) => d.minZoom)),
641
+ maxZoom: Math.max(...backDevices.map((d) => d.maxZoom)),
304
642
  });
305
643
  }
306
644
  return { devices: result };
@@ -325,18 +663,22 @@ export class CameraPreviewWeb extends WebPlugin {
325
663
  let baseZoomRatio = 1.0;
326
664
  if (this.currentDeviceId) {
327
665
  const devices = await navigator.mediaDevices.enumerateDevices();
328
- const device = devices.find(d => d.deviceId === this.currentDeviceId);
666
+ const device = devices.find((d) => d.deviceId === this.currentDeviceId);
329
667
  if (device) {
330
668
  const labelLower = device.label.toLowerCase();
331
- if (labelLower.includes('ultra') || labelLower.includes('0.5')) {
669
+ if (labelLower.includes("ultra") || labelLower.includes("0.5")) {
332
670
  deviceType = DeviceType.ULTRA_WIDE;
333
671
  baseZoomRatio = 0.5;
334
672
  }
335
- else if (labelLower.includes('telephoto') || labelLower.includes('tele') || labelLower.includes('2x') || labelLower.includes('3x')) {
673
+ else if (labelLower.includes("telephoto") ||
674
+ labelLower.includes("tele") ||
675
+ labelLower.includes("2x") ||
676
+ labelLower.includes("3x")) {
336
677
  deviceType = DeviceType.TELEPHOTO;
337
678
  baseZoomRatio = 2.0;
338
679
  }
339
- else if (labelLower.includes('depth') || labelLower.includes('truedepth')) {
680
+ else if (labelLower.includes("depth") ||
681
+ labelLower.includes("truedepth")) {
340
682
  deviceType = DeviceType.TRUE_DEPTH;
341
683
  baseZoomRatio = 1.0;
342
684
  }
@@ -347,7 +689,7 @@ export class CameraPreviewWeb extends WebPlugin {
347
689
  focalLength: 4.25,
348
690
  deviceType,
349
691
  baseZoomRatio,
350
- digitalZoom: currentZoom / baseZoomRatio
692
+ digitalZoom: currentZoom / baseZoomRatio,
351
693
  };
352
694
  return {
353
695
  min: capabilities.zoom.min || 1,
@@ -371,9 +713,10 @@ export class CameraPreviewWeb extends WebPlugin {
371
713
  throw new Error("zoom not supported by this device");
372
714
  }
373
715
  const zoomLevel = Math.max(capabilities.zoom.min || 1, Math.min(capabilities.zoom.max || 1, options.level));
716
+ // Note: autoFocus is not supported on web platform
374
717
  try {
375
718
  await videoTrack.applyConstraints({
376
- advanced: [{ zoom: zoomLevel }]
719
+ advanced: [{ zoom: zoomLevel }],
377
720
  });
378
721
  }
379
722
  catch (error) {
@@ -406,8 +749,11 @@ export class CameraPreviewWeb extends WebPlugin {
406
749
  try {
407
750
  // Try to determine camera position from device
408
751
  const devices = await navigator.mediaDevices.enumerateDevices();
409
- const device = devices.find(d => d.deviceId === options.deviceId);
410
- 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;
752
+ const device = devices.find((d) => d.deviceId === options.deviceId);
753
+ this.isBackCamera =
754
+ (device === null || device === void 0 ? void 0 : device.label.toLowerCase().includes("back")) ||
755
+ (device === null || device === void 0 ? void 0 : device.label.toLowerCase().includes("rear")) ||
756
+ false;
411
757
  const stream = await navigator.mediaDevices.getUserMedia(constraints);
412
758
  video.srcObject = stream;
413
759
  // Update video transform based on camera
@@ -435,15 +781,15 @@ export class CameraPreviewWeb extends WebPlugin {
435
781
  if (width && height) {
436
782
  const ratio = width / height;
437
783
  // Check for portrait camera ratios: 4:3 -> 3:4, 16:9 -> 9:16
438
- if (Math.abs(ratio - (3 / 4)) < 0.01) {
439
- return { aspectRatio: '4:3' };
784
+ if (Math.abs(ratio - 3 / 4) < 0.01) {
785
+ return { aspectRatio: "4:3" };
440
786
  }
441
- if (Math.abs(ratio - (9 / 16)) < 0.01) {
442
- return { aspectRatio: '16:9' };
787
+ if (Math.abs(ratio - 9 / 16) < 0.01) {
788
+ return { aspectRatio: "16:9" };
443
789
  }
444
790
  }
445
791
  // Default to 4:3 if no specific aspect ratio is matched
446
- return { aspectRatio: '4:3' };
792
+ return { aspectRatio: "4:3" };
447
793
  }
448
794
  async setAspectRatio(options) {
449
795
  const video = document.getElementById(DEFAULT_VIDEO_ID);
@@ -451,7 +797,9 @@ export class CameraPreviewWeb extends WebPlugin {
451
797
  throw new Error("camera is not running");
452
798
  }
453
799
  if (options.aspectRatio) {
454
- const [widthRatio, heightRatio] = options.aspectRatio.split(':').map(Number);
800
+ const [widthRatio, heightRatio] = options.aspectRatio
801
+ .split(":")
802
+ .map(Number);
455
803
  // For camera, use portrait orientation: 4:3 becomes 3:4, 16:9 becomes 9:16
456
804
  const ratio = heightRatio / widthRatio;
457
805
  // Get current position and size
@@ -487,22 +835,26 @@ export class CameraPreviewWeb extends WebPlugin {
487
835
  video.style.height = `${newHeight}px`;
488
836
  video.style.left = `${x}px`;
489
837
  video.style.top = `${y}px`;
490
- video.style.position = 'absolute';
838
+ video.style.position = "absolute";
839
+ const offsetX = newWidth / 8;
840
+ const offsetY = newHeight / 8;
491
841
  return {
492
842
  width: Math.round(newWidth),
493
843
  height: Math.round(newHeight),
494
- x: Math.round(x),
495
- y: Math.round(y)
844
+ x: Math.round(x + offsetX),
845
+ y: Math.round(y + offsetY),
496
846
  };
497
847
  }
498
848
  else {
499
- video.style.objectFit = 'cover';
849
+ video.style.objectFit = "cover";
500
850
  const rect = video.getBoundingClientRect();
851
+ const offsetX = rect.width / 8;
852
+ const offsetY = rect.height / 8;
501
853
  return {
502
854
  width: Math.round(rect.width),
503
855
  height: Math.round(rect.height),
504
- x: Math.round(rect.left),
505
- y: Math.round(rect.top)
856
+ x: Math.round(rect.left + offsetX),
857
+ y: Math.round(rect.top + offsetY),
506
858
  };
507
859
  }
508
860
  }
@@ -554,18 +906,20 @@ export class CameraPreviewWeb extends WebPlugin {
554
906
  }
555
907
  async getGridMode() {
556
908
  // Web implementation - default to none
557
- return { gridMode: 'none' };
909
+ return { gridMode: "none" };
558
910
  }
559
911
  async getPreviewSize() {
560
912
  const video = document.getElementById(DEFAULT_VIDEO_ID);
561
913
  if (!video) {
562
914
  throw new Error("camera is not running");
563
915
  }
916
+ const offsetX = video.width / 8;
917
+ const offsetY = video.height / 8;
564
918
  return {
565
- x: video.offsetLeft,
566
- y: video.offsetTop,
919
+ x: video.offsetLeft + offsetX,
920
+ y: video.offsetTop + offsetY,
567
921
  width: video.width,
568
- height: video.height
922
+ height: video.height,
569
923
  };
570
924
  }
571
925
  async setPreviewSize(options) {
@@ -577,12 +931,56 @@ export class CameraPreviewWeb extends WebPlugin {
577
931
  video.style.top = `${options.y}px`;
578
932
  video.width = options.width;
579
933
  video.height = options.height;
934
+ const offsetX = options.width / 8;
935
+ const offsetY = options.height / 8;
580
936
  return {
581
937
  width: options.width,
582
938
  height: options.height,
583
- x: options.x,
584
- y: options.y
939
+ x: options.x + offsetX,
940
+ y: options.y + offsetY,
585
941
  };
586
942
  }
943
+ async setFocus(options) {
944
+ // Reject if values are outside 0-1 range
945
+ if (options.x < 0 || options.x > 1 || options.y < 0 || options.y > 1) {
946
+ throw new Error("Focus coordinates must be between 0 and 1");
947
+ }
948
+ const video = document.getElementById(DEFAULT_VIDEO_ID);
949
+ if (!(video === null || video === void 0 ? void 0 : video.srcObject)) {
950
+ throw new Error("camera is not running");
951
+ }
952
+ const stream = video.srcObject;
953
+ const videoTrack = stream.getVideoTracks()[0];
954
+ if (!videoTrack) {
955
+ throw new Error("no video track found");
956
+ }
957
+ const capabilities = videoTrack.getCapabilities();
958
+ // Check if focusing is supported
959
+ if (capabilities.focusMode) {
960
+ try {
961
+ // Web API supports focus mode settings but not coordinate-based focus
962
+ // Setting to manual mode allows for coordinate focus if supported
963
+ await videoTrack.applyConstraints({
964
+ advanced: [
965
+ {
966
+ focusMode: "manual",
967
+ focusDistance: 0.5, // Mid-range focus as fallback
968
+ },
969
+ ],
970
+ });
971
+ }
972
+ catch (error) {
973
+ console.warn(`setFocus is not fully supported on this device: ${error}. Focus coordinates (${options.x}, ${options.y}) were provided but cannot be applied.`);
974
+ }
975
+ }
976
+ else {
977
+ console.warn("Focus control is not supported on this device. Focus coordinates were provided but cannot be applied.");
978
+ }
979
+ }
980
+ async deleteFile(_options) {
981
+ // Mark parameter as intentionally unused to satisfy linter
982
+ void _options;
983
+ throw new Error("deleteFile not supported under the web platform");
984
+ }
587
985
  }
588
986
  //# sourceMappingURL=web.js.map