@capgo/camera-preview 7.4.0-beta.2 → 7.4.0-beta.20

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 (35) hide show
  1. package/README.md +212 -35
  2. package/android/.gradle/8.14.2/checksums/checksums.lock +0 -0
  3. package/android/.gradle/8.14.2/checksums/md5-checksums.bin +0 -0
  4. package/android/.gradle/8.14.2/checksums/sha1-checksums.bin +0 -0
  5. package/android/.gradle/8.14.2/executionHistory/executionHistory.bin +0 -0
  6. package/android/.gradle/8.14.2/executionHistory/executionHistory.lock +0 -0
  7. package/android/.gradle/8.14.2/fileHashes/fileHashes.bin +0 -0
  8. package/android/.gradle/8.14.2/fileHashes/fileHashes.lock +0 -0
  9. package/android/.gradle/8.14.2/fileHashes/resourceHashesCache.bin +0 -0
  10. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  11. package/android/.gradle/file-system.probe +0 -0
  12. package/android/build.gradle +3 -1
  13. package/android/src/main/AndroidManifest.xml +1 -4
  14. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +731 -83
  15. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +2813 -805
  16. package/android/src/main/java/com/ahm/capacitor/camera/preview/GridOverlayView.java +112 -0
  17. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraDevice.java +55 -46
  18. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraLens.java +61 -52
  19. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraSessionConfiguration.java +161 -59
  20. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/LensInfo.java +29 -23
  21. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/ZoomFactors.java +24 -23
  22. package/dist/docs.json +292 -29
  23. package/dist/esm/definitions.d.ts +148 -13
  24. package/dist/esm/definitions.js.map +1 -1
  25. package/dist/esm/web.d.ts +52 -3
  26. package/dist/esm/web.js +555 -97
  27. package/dist/esm/web.js.map +1 -1
  28. package/dist/plugin.cjs.js +553 -97
  29. package/dist/plugin.cjs.js.map +1 -1
  30. package/dist/plugin.js +553 -97
  31. package/dist/plugin.js.map +1 -1
  32. package/ios/Sources/CapgoCameraPreview/CameraController.swift +888 -214
  33. package/ios/Sources/CapgoCameraPreview/GridOverlayView.swift +65 -0
  34. package/ios/Sources/CapgoCameraPreview/Plugin.swift +967 -250
  35. package/package.json +2 -2
package/dist/plugin.js CHANGED
@@ -16,6 +16,7 @@ var capacitorCapacitorCameraView = (function (exports, core) {
16
16
  web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.CameraPreviewWeb()),
17
17
  });
18
18
 
19
+ const DEFAULT_VIDEO_ID = "capgo_video";
19
20
  class CameraPreviewWeb extends core.WebPlugin {
20
21
  constructor() {
21
22
  super();
@@ -25,87 +26,280 @@ var capacitorCapacitorCameraView = (function (exports, core) {
25
26
  */
26
27
  this.isBackCamera = false;
27
28
  this.currentDeviceId = null;
29
+ this.videoElement = null;
30
+ this.isStarted = false;
28
31
  }
29
32
  async getSupportedPictureSizes() {
30
33
  throw new Error("getSupportedPictureSizes not supported under the web platform");
31
34
  }
32
35
  async start(options) {
33
- var _a;
34
- await navigator.mediaDevices
35
- .getUserMedia({
36
- audio: !options.disableAudio,
37
- video: true,
38
- })
39
- .then((stream) => {
40
- // Stop any existing stream so we can request media with different constraints based on user input
41
- stream.getTracks().forEach((track) => track.stop());
42
- })
43
- .catch((error) => {
44
- Promise.reject(error);
45
- });
46
- const video = document.getElementById("video");
36
+ if (options.aspectRatio && (options.width || options.height)) {
37
+ throw new Error("Cannot set both aspectRatio and size (width/height). Use setPreviewSize after start.");
38
+ }
39
+ if (this.isStarted) {
40
+ throw new Error("camera already started");
41
+ }
42
+ this.isBackCamera = true;
43
+ this.isStarted = false;
47
44
  const parent = document.getElementById((options === null || options === void 0 ? void 0 : options.parent) || "");
48
- if (!video) {
49
- const videoElement = document.createElement("video");
50
- videoElement.id = "video";
51
- videoElement.setAttribute("class", (options === null || options === void 0 ? void 0 : options.className) || "");
52
- // Don't flip video feed if camera is rear facing
53
- if (options.position !== "rear") {
54
- videoElement.setAttribute("style", "-webkit-transform: scaleX(-1); transform: scaleX(-1);");
45
+ const gridMode = (options === null || options === void 0 ? void 0 : options.gridMode) || "none";
46
+ if (options.position) {
47
+ this.isBackCamera = options.position === "rear";
48
+ }
49
+ const video = document.getElementById(DEFAULT_VIDEO_ID);
50
+ if (video) {
51
+ video.remove();
52
+ }
53
+ const container = options.parent
54
+ ? document.getElementById(options.parent)
55
+ : document.body;
56
+ if (!container) {
57
+ throw new Error("container not found");
58
+ }
59
+ this.videoElement = document.createElement("video");
60
+ this.videoElement.id = DEFAULT_VIDEO_ID;
61
+ this.videoElement.className = options.className || "";
62
+ this.videoElement.playsInline = true;
63
+ this.videoElement.muted = true;
64
+ this.videoElement.autoplay = true;
65
+ // Remove objectFit as we'll match camera's native aspect ratio
66
+ this.videoElement.style.backgroundColor = "transparent";
67
+ // Reset any default margins that might interfere
68
+ this.videoElement.style.margin = "0";
69
+ this.videoElement.style.padding = "0";
70
+ container.appendChild(this.videoElement);
71
+ if (options.toBack) {
72
+ this.videoElement.style.zIndex = "-1";
73
+ }
74
+ // Default to 16:9 vertical (9:16 for portrait) if no aspect ratio or size specified
75
+ const useDefaultAspectRatio = !options.aspectRatio && !options.width && !options.height;
76
+ const effectiveAspectRatio = options.aspectRatio || (useDefaultAspectRatio ? "16:9" : null);
77
+ if (options.width) {
78
+ this.videoElement.width = options.width;
79
+ this.videoElement.style.width = `${options.width}px`;
80
+ }
81
+ if (options.height) {
82
+ this.videoElement.height = options.height;
83
+ this.videoElement.style.height = `${options.height}px`;
84
+ }
85
+ // Handle positioning - center if x or y not provided
86
+ const centerX = options.x === undefined;
87
+ const centerY = options.y === undefined;
88
+ // Always set position to absolute for proper positioning
89
+ this.videoElement.style.position = "absolute";
90
+ console.log("Initial positioning flags:", {
91
+ centerX,
92
+ centerY,
93
+ x: options.x,
94
+ y: options.y,
95
+ });
96
+ if (options.x !== undefined) {
97
+ this.videoElement.style.left = `${options.x}px`;
98
+ }
99
+ if (options.y !== undefined) {
100
+ this.videoElement.style.top = `${options.y}px`;
101
+ }
102
+ // Create and add grid overlay if needed
103
+ if (gridMode !== "none") {
104
+ const gridOverlay = this.createGridOverlay(gridMode);
105
+ gridOverlay.id = "camera-grid-overlay";
106
+ parent === null || parent === void 0 ? void 0 : parent.appendChild(gridOverlay);
107
+ }
108
+ // Aspect ratio handling is now done after getting camera stream
109
+ // Store centering flags for later use
110
+ const needsCenterX = centerX;
111
+ const needsCenterY = centerY;
112
+ console.log("Centering flags stored:", { needsCenterX, needsCenterY });
113
+ // First get the camera stream with basic constraints
114
+ const constraints = {
115
+ video: {
116
+ facingMode: this.isBackCamera ? "environment" : "user",
117
+ },
118
+ };
119
+ const stream = await navigator.mediaDevices.getUserMedia(constraints);
120
+ if (!stream) {
121
+ throw new Error("could not acquire stream");
122
+ }
123
+ if (!this.videoElement) {
124
+ throw new Error("video element not found");
125
+ }
126
+ // Get the actual camera dimensions from the video track
127
+ const videoTrack = stream.getVideoTracks()[0];
128
+ const settings = videoTrack.getSettings();
129
+ const cameraWidth = settings.width || 640;
130
+ const cameraHeight = settings.height || 480;
131
+ const cameraAspectRatio = cameraWidth / cameraHeight;
132
+ console.log("Camera native dimensions:", {
133
+ width: cameraWidth,
134
+ height: cameraHeight,
135
+ aspectRatio: cameraAspectRatio,
136
+ });
137
+ console.log("Container dimensions:", {
138
+ width: container.offsetWidth,
139
+ height: container.offsetHeight,
140
+ id: container.id,
141
+ });
142
+ // Now adjust video element size based on camera's native aspect ratio
143
+ if (!options.width && !options.height && !options.aspectRatio) {
144
+ // No size specified, fit camera view within container bounds
145
+ const containerWidth = container.offsetWidth || window.innerWidth;
146
+ const containerHeight = container.offsetHeight || window.innerHeight;
147
+ // Calculate dimensions that fit within container while maintaining camera aspect ratio
148
+ let targetWidth, targetHeight;
149
+ // Try fitting to container width first
150
+ targetWidth = containerWidth;
151
+ targetHeight = targetWidth / cameraAspectRatio;
152
+ // If height exceeds container, fit to height instead
153
+ if (targetHeight > containerHeight) {
154
+ targetHeight = containerHeight;
155
+ targetWidth = targetHeight * cameraAspectRatio;
156
+ }
157
+ console.log("Video element dimensions:", {
158
+ width: targetWidth,
159
+ height: targetHeight,
160
+ container: { width: containerWidth, height: containerHeight },
161
+ });
162
+ this.videoElement.width = targetWidth;
163
+ this.videoElement.height = targetHeight;
164
+ this.videoElement.style.width = `${targetWidth}px`;
165
+ this.videoElement.style.height = `${targetHeight}px`;
166
+ // Center the video element within its parent container
167
+ if (needsCenterX || options.x === undefined) {
168
+ const x = Math.round((containerWidth - targetWidth) / 2);
169
+ this.videoElement.style.left = `${x}px`;
55
170
  }
56
- const userAgent = navigator.userAgent.toLowerCase();
57
- const isSafari = userAgent.includes("safari") && !userAgent.includes("chrome");
58
- // Safari on iOS needs to have the autoplay, muted and playsinline attributes set for video.play() to be successful
59
- // Without these attributes videoElement.play() will throw a NotAllowedError
60
- // https://developer.apple.com/documentation/webkit/delivering_video_content_for_safari
61
- if (isSafari) {
62
- videoElement.setAttribute("autoplay", "true");
63
- videoElement.setAttribute("muted", "true");
64
- videoElement.setAttribute("playsinline", "true");
171
+ if (needsCenterY || options.y === undefined) {
172
+ const y = Math.round((window.innerHeight - targetHeight) / 2);
173
+ this.videoElement.style.setProperty("top", `${y}px`, "important");
174
+ // Force a style recalculation
175
+ this.videoElement.offsetHeight;
176
+ console.log("Centering video:", {
177
+ viewportHeight: window.innerHeight,
178
+ targetHeight,
179
+ calculatedY: y,
180
+ actualTop: this.videoElement.style.top,
181
+ position: this.videoElement.style.position,
182
+ });
65
183
  }
66
- parent === null || parent === void 0 ? void 0 : parent.appendChild(videoElement);
67
- if ((_a = navigator === null || navigator === void 0 ? void 0 : navigator.mediaDevices) === null || _a === void 0 ? void 0 : _a.getUserMedia) {
68
- const constraints = {
69
- video: {
70
- width: { ideal: options.width },
71
- height: { ideal: options.height },
72
- },
73
- };
74
- if (options.deviceId) {
75
- constraints.video.deviceId = { exact: options.deviceId };
76
- this.currentDeviceId = options.deviceId;
77
- // Try to determine camera position from device
78
- const devices = await navigator.mediaDevices.enumerateDevices();
79
- const device = devices.find(d => d.deviceId === options.deviceId);
80
- 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;
81
- }
82
- else if (options.position === "rear") {
83
- constraints.video.facingMode = "environment";
84
- this.isBackCamera = true;
85
- }
86
- else {
87
- this.isBackCamera = false;
88
- }
89
- const self = this;
90
- await navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
91
- if (document.getElementById("video")) {
92
- // video.src = window.URL.createObjectURL(stream);
93
- videoElement.srcObject = stream;
94
- videoElement.play();
95
- Promise.resolve({});
184
+ }
185
+ else if (effectiveAspectRatio && !options.width && !options.height) {
186
+ // Aspect ratio specified but no size
187
+ const [widthRatio, heightRatio] = effectiveAspectRatio
188
+ .split(":")
189
+ .map(Number);
190
+ const targetRatio = widthRatio / heightRatio;
191
+ const viewportWidth = window.innerWidth;
192
+ const viewportHeight = window.innerHeight;
193
+ let targetWidth, targetHeight;
194
+ // Try fitting to viewport width first
195
+ targetWidth = viewportWidth;
196
+ targetHeight = targetWidth / targetRatio;
197
+ // If height exceeds viewport, fit to height instead
198
+ if (targetHeight > viewportHeight) {
199
+ targetHeight = viewportHeight;
200
+ targetWidth = targetHeight * targetRatio;
201
+ }
202
+ this.videoElement.width = targetWidth;
203
+ this.videoElement.height = targetHeight;
204
+ this.videoElement.style.width = `${targetWidth}px`;
205
+ this.videoElement.style.height = `${targetHeight}px`;
206
+ // Center the video element within its parent container
207
+ if (needsCenterX || options.x === undefined) {
208
+ const parentWidth = container.offsetWidth || viewportWidth;
209
+ const x = Math.round((parentWidth - targetWidth) / 2);
210
+ this.videoElement.style.left = `${x}px`;
211
+ }
212
+ if (needsCenterY || options.y === undefined) {
213
+ const parentHeight = container.offsetHeight || viewportHeight;
214
+ const y = Math.round((parentHeight - targetHeight) / 2);
215
+ this.videoElement.style.top = `${y}px`;
216
+ }
217
+ }
218
+ this.videoElement.srcObject = stream;
219
+ if (!this.isBackCamera) {
220
+ this.videoElement.style.transform = "scaleX(-1)";
221
+ }
222
+ // Set initial zoom level if specified and supported
223
+ if (options.initialZoomLevel && options.initialZoomLevel !== 1.0) {
224
+ // videoTrack already declared above
225
+ if (videoTrack) {
226
+ const capabilities = videoTrack.getCapabilities();
227
+ if (capabilities.zoom) {
228
+ const zoomLevel = options.initialZoomLevel;
229
+ const minZoom = capabilities.zoom.min || 1;
230
+ const maxZoom = capabilities.zoom.max || 1;
231
+ if (zoomLevel < minZoom || zoomLevel > maxZoom) {
232
+ stream.getTracks().forEach((track) => track.stop());
233
+ throw new Error(`Initial zoom level ${zoomLevel} is not available. Valid range is ${minZoom} to ${maxZoom}`);
234
+ }
235
+ try {
236
+ await videoTrack.applyConstraints({
237
+ advanced: [{ zoom: zoomLevel }],
238
+ });
96
239
  }
97
- else {
98
- self.stopStream(stream);
99
- Promise.reject(new Error("camera already stopped"));
240
+ catch (error) {
241
+ console.warn(`Failed to set initial zoom level: ${error}`);
242
+ // Don't throw, just continue without zoom
100
243
  }
101
- }, (err) => {
102
- Promise.reject(new Error(err));
244
+ }
245
+ }
246
+ }
247
+ this.isStarted = true;
248
+ // Wait for video to be ready and get actual dimensions
249
+ await new Promise((resolve) => {
250
+ if (this.videoElement.readyState >= 2) {
251
+ resolve();
252
+ }
253
+ else {
254
+ this.videoElement.addEventListener("loadeddata", () => resolve(), {
255
+ once: true,
103
256
  });
104
257
  }
258
+ });
259
+ // Ensure centering is applied after DOM updates
260
+ await new Promise((resolve) => requestAnimationFrame(resolve));
261
+ console.log("About to re-center, flags:", { needsCenterX, needsCenterY });
262
+ // Re-apply centering with correct parent dimensions
263
+ if (needsCenterX) {
264
+ const parentWidth = container.offsetWidth;
265
+ const x = Math.round((parentWidth - this.videoElement.offsetWidth) / 2);
266
+ this.videoElement.style.left = `${x}px`;
267
+ console.log("Re-centering X:", {
268
+ parentWidth,
269
+ videoWidth: this.videoElement.offsetWidth,
270
+ x,
271
+ });
105
272
  }
106
- else {
107
- Promise.reject(new Error("camera already started"));
273
+ if (needsCenterY) {
274
+ const y = Math.round((window.innerHeight - this.videoElement.offsetHeight) / 2);
275
+ this.videoElement.style.setProperty("top", `${y}px`, "important");
276
+ console.log("Re-centering Y:", {
277
+ viewportHeight: window.innerHeight,
278
+ videoHeight: this.videoElement.offsetHeight,
279
+ y,
280
+ position: this.videoElement.style.position,
281
+ top: this.videoElement.style.top,
282
+ });
108
283
  }
284
+ // Get the actual rendered dimensions after video is loaded
285
+ const rect = this.videoElement.getBoundingClientRect();
286
+ const computedStyle = window.getComputedStyle(this.videoElement);
287
+ console.log("Final video element state:", {
288
+ rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
289
+ style: {
290
+ position: computedStyle.position,
291
+ left: computedStyle.left,
292
+ top: computedStyle.top,
293
+ width: computedStyle.width,
294
+ height: computedStyle.height,
295
+ },
296
+ });
297
+ return {
298
+ width: Math.round(rect.width),
299
+ height: Math.round(rect.height),
300
+ x: Math.round(rect.x),
301
+ y: Math.round(rect.y),
302
+ };
109
303
  }
110
304
  stopStream(stream) {
111
305
  if (stream) {
@@ -115,16 +309,20 @@ var capacitorCapacitorCameraView = (function (exports, core) {
115
309
  }
116
310
  }
117
311
  async stop() {
118
- const video = document.getElementById("video");
312
+ const video = document.getElementById(DEFAULT_VIDEO_ID);
119
313
  if (video) {
120
314
  video.pause();
121
315
  this.stopStream(video.srcObject);
122
316
  video.remove();
317
+ this.isStarted = false;
123
318
  }
319
+ // Remove grid overlay if it exists
320
+ const gridOverlay = document.getElementById("camera-grid-overlay");
321
+ gridOverlay === null || gridOverlay === void 0 ? void 0 : gridOverlay.remove();
124
322
  }
125
323
  async capture(options) {
126
324
  return new Promise((resolve, reject) => {
127
- const video = document.getElementById("video");
325
+ const video = document.getElementById(DEFAULT_VIDEO_ID);
128
326
  if (!(video === null || video === void 0 ? void 0 : video.srcObject)) {
129
327
  reject(new Error("camera is not running"));
130
328
  return;
@@ -134,15 +332,55 @@ var capacitorCapacitorCameraView = (function (exports, core) {
134
332
  if (video && video.videoWidth > 0 && video.videoHeight > 0) {
135
333
  const canvas = document.createElement("canvas");
136
334
  const context = canvas.getContext("2d");
137
- canvas.width = video.videoWidth;
138
- canvas.height = video.videoHeight;
335
+ // Calculate capture dimensions
336
+ let captureWidth = video.videoWidth;
337
+ let captureHeight = video.videoHeight;
338
+ let sourceX = 0;
339
+ let sourceY = 0;
340
+ // Check for conflicting parameters
341
+ if (options.aspectRatio && (options.width || options.height)) {
342
+ reject(new Error("Cannot set both aspectRatio and size (width/height). Use setPreviewSize after start."));
343
+ return;
344
+ }
345
+ // Handle aspect ratio if no width/height specified
346
+ if (!options.width && !options.height && options.aspectRatio) {
347
+ const [widthRatio, heightRatio] = options.aspectRatio.split(':').map(Number);
348
+ if (widthRatio && heightRatio) {
349
+ // For capture in portrait orientation, swap the aspect ratio (16:9 becomes 9:16)
350
+ const isPortrait = video.videoHeight > video.videoWidth;
351
+ const targetAspectRatio = isPortrait ? heightRatio / widthRatio : widthRatio / heightRatio;
352
+ const videoAspectRatio = video.videoWidth / video.videoHeight;
353
+ if (videoAspectRatio > targetAspectRatio) {
354
+ // Video is wider than target - crop sides
355
+ captureWidth = video.videoHeight * targetAspectRatio;
356
+ captureHeight = video.videoHeight;
357
+ sourceX = (video.videoWidth - captureWidth) / 2;
358
+ }
359
+ else {
360
+ // Video is taller than target - crop top/bottom
361
+ captureWidth = video.videoWidth;
362
+ captureHeight = video.videoWidth / targetAspectRatio;
363
+ sourceY = (video.videoHeight - captureHeight) / 2;
364
+ }
365
+ }
366
+ }
367
+ else if (options.width || options.height) {
368
+ // If width or height is specified, use them
369
+ if (options.width)
370
+ captureWidth = options.width;
371
+ if (options.height)
372
+ captureHeight = options.height;
373
+ }
374
+ canvas.width = captureWidth;
375
+ canvas.height = captureHeight;
139
376
  // flip horizontally back camera isn't used
140
377
  if (!this.isBackCamera) {
141
- context === null || context === void 0 ? void 0 : context.translate(video.videoWidth, 0);
378
+ context === null || context === void 0 ? void 0 : context.translate(captureWidth, 0);
142
379
  context === null || context === void 0 ? void 0 : context.scale(-1, 1);
143
380
  }
144
- context === null || context === void 0 ? void 0 : context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
381
+ context === null || context === void 0 ? void 0 : context.drawImage(video, sourceX, sourceY, captureWidth, captureHeight, 0, 0, captureWidth, captureHeight);
145
382
  if (options.saveToGallery) ;
383
+ if (options.withExifLocation) ;
146
384
  if ((options.format || "jpeg") === "jpeg") {
147
385
  base64EncodedImage = canvas
148
386
  .toDataURL("image/jpeg", (options.quality || 85) / 100.0)
@@ -180,7 +418,7 @@ var capacitorCapacitorCameraView = (function (exports, core) {
180
418
  throw new Error(`setFlashMode not supported under the web platform${_options}`);
181
419
  }
182
420
  async flip() {
183
- const video = document.getElementById("video");
421
+ const video = document.getElementById(DEFAULT_VIDEO_ID);
184
422
  if (!(video === null || video === void 0 ? void 0 : video.srcObject)) {
185
423
  throw new Error("camera is not running");
186
424
  }
@@ -220,12 +458,12 @@ var capacitorCapacitorCameraView = (function (exports, core) {
220
458
  }
221
459
  }
222
460
  async setOpacity(_options) {
223
- const video = document.getElementById("video");
461
+ const video = document.getElementById(DEFAULT_VIDEO_ID);
224
462
  if (!!video && !!_options.opacity)
225
463
  video.style.setProperty("opacity", _options.opacity.toString());
226
464
  }
227
465
  async isRunning() {
228
- const video = document.getElementById("video");
466
+ const video = document.getElementById(DEFAULT_VIDEO_ID);
229
467
  return { isRunning: !!video && !!video.srcObject };
230
468
  }
231
469
  async getAvailableDevices() {
@@ -234,7 +472,7 @@ var capacitorCapacitorCameraView = (function (exports, core) {
234
472
  throw new Error("getAvailableDevices not supported under the web platform");
235
473
  }
236
474
  const devices = await navigator.mediaDevices.enumerateDevices();
237
- const videoDevices = devices.filter(device => device.kind === 'videoinput');
475
+ const videoDevices = devices.filter((device) => device.kind === "videoinput");
238
476
  // Group devices by position (front/back)
239
477
  const frontDevices = [];
240
478
  const backDevices = [];
@@ -244,15 +482,19 @@ var capacitorCapacitorCameraView = (function (exports, core) {
244
482
  // Determine device type based on label
245
483
  let deviceType = exports.DeviceType.WIDE_ANGLE;
246
484
  let baseZoomRatio = 1.0;
247
- if (labelLower.includes('ultra') || labelLower.includes('0.5')) {
485
+ if (labelLower.includes("ultra") || labelLower.includes("0.5")) {
248
486
  deviceType = exports.DeviceType.ULTRA_WIDE;
249
487
  baseZoomRatio = 0.5;
250
488
  }
251
- else if (labelLower.includes('telephoto') || labelLower.includes('tele') || labelLower.includes('2x') || labelLower.includes('3x')) {
489
+ else if (labelLower.includes("telephoto") ||
490
+ labelLower.includes("tele") ||
491
+ labelLower.includes("2x") ||
492
+ labelLower.includes("3x")) {
252
493
  deviceType = exports.DeviceType.TELEPHOTO;
253
494
  baseZoomRatio = 2.0;
254
495
  }
255
- else if (labelLower.includes('depth') || labelLower.includes('truedepth')) {
496
+ else if (labelLower.includes("depth") ||
497
+ labelLower.includes("truedepth")) {
256
498
  deviceType = exports.DeviceType.TRUE_DEPTH;
257
499
  baseZoomRatio = 1.0;
258
500
  }
@@ -263,10 +505,10 @@ var capacitorCapacitorCameraView = (function (exports, core) {
263
505
  focalLength: 4.25,
264
506
  baseZoomRatio,
265
507
  minZoom: 1.0,
266
- maxZoom: 1.0
508
+ maxZoom: 1.0,
267
509
  };
268
510
  // Determine position and add to appropriate array
269
- if (labelLower.includes('back') || labelLower.includes('rear')) {
511
+ if (labelLower.includes("back") || labelLower.includes("rear")) {
270
512
  backDevices.push(lensInfo);
271
513
  }
272
514
  else {
@@ -281,8 +523,8 @@ var capacitorCapacitorCameraView = (function (exports, core) {
281
523
  position: "front",
282
524
  lenses: frontDevices,
283
525
  isLogical: false,
284
- minZoom: Math.min(...frontDevices.map(d => d.minZoom)),
285
- maxZoom: Math.max(...frontDevices.map(d => d.maxZoom))
526
+ minZoom: Math.min(...frontDevices.map((d) => d.minZoom)),
527
+ maxZoom: Math.max(...frontDevices.map((d) => d.maxZoom)),
286
528
  });
287
529
  }
288
530
  if (backDevices.length > 0) {
@@ -292,14 +534,14 @@ var capacitorCapacitorCameraView = (function (exports, core) {
292
534
  position: "rear",
293
535
  lenses: backDevices,
294
536
  isLogical: false,
295
- minZoom: Math.min(...backDevices.map(d => d.minZoom)),
296
- maxZoom: Math.max(...backDevices.map(d => d.maxZoom))
537
+ minZoom: Math.min(...backDevices.map((d) => d.minZoom)),
538
+ maxZoom: Math.max(...backDevices.map((d) => d.maxZoom)),
297
539
  });
298
540
  }
299
541
  return { devices: result };
300
542
  }
301
543
  async getZoom() {
302
- const video = document.getElementById("video");
544
+ const video = document.getElementById(DEFAULT_VIDEO_ID);
303
545
  if (!(video === null || video === void 0 ? void 0 : video.srcObject)) {
304
546
  throw new Error("camera is not running");
305
547
  }
@@ -318,18 +560,22 @@ var capacitorCapacitorCameraView = (function (exports, core) {
318
560
  let baseZoomRatio = 1.0;
319
561
  if (this.currentDeviceId) {
320
562
  const devices = await navigator.mediaDevices.enumerateDevices();
321
- const device = devices.find(d => d.deviceId === this.currentDeviceId);
563
+ const device = devices.find((d) => d.deviceId === this.currentDeviceId);
322
564
  if (device) {
323
565
  const labelLower = device.label.toLowerCase();
324
- if (labelLower.includes('ultra') || labelLower.includes('0.5')) {
566
+ if (labelLower.includes("ultra") || labelLower.includes("0.5")) {
325
567
  deviceType = exports.DeviceType.ULTRA_WIDE;
326
568
  baseZoomRatio = 0.5;
327
569
  }
328
- else if (labelLower.includes('telephoto') || labelLower.includes('tele') || labelLower.includes('2x') || labelLower.includes('3x')) {
570
+ else if (labelLower.includes("telephoto") ||
571
+ labelLower.includes("tele") ||
572
+ labelLower.includes("2x") ||
573
+ labelLower.includes("3x")) {
329
574
  deviceType = exports.DeviceType.TELEPHOTO;
330
575
  baseZoomRatio = 2.0;
331
576
  }
332
- else if (labelLower.includes('depth') || labelLower.includes('truedepth')) {
577
+ else if (labelLower.includes("depth") ||
578
+ labelLower.includes("truedepth")) {
333
579
  deviceType = exports.DeviceType.TRUE_DEPTH;
334
580
  baseZoomRatio = 1.0;
335
581
  }
@@ -340,7 +586,7 @@ var capacitorCapacitorCameraView = (function (exports, core) {
340
586
  focalLength: 4.25,
341
587
  deviceType,
342
588
  baseZoomRatio,
343
- digitalZoom: currentZoom / baseZoomRatio
589
+ digitalZoom: currentZoom / baseZoomRatio,
344
590
  };
345
591
  return {
346
592
  min: capabilities.zoom.min || 1,
@@ -350,7 +596,7 @@ var capacitorCapacitorCameraView = (function (exports, core) {
350
596
  };
351
597
  }
352
598
  async setZoom(options) {
353
- const video = document.getElementById("video");
599
+ const video = document.getElementById(DEFAULT_VIDEO_ID);
354
600
  if (!(video === null || video === void 0 ? void 0 : video.srcObject)) {
355
601
  throw new Error("camera is not running");
356
602
  }
@@ -364,9 +610,10 @@ var capacitorCapacitorCameraView = (function (exports, core) {
364
610
  throw new Error("zoom not supported by this device");
365
611
  }
366
612
  const zoomLevel = Math.max(capabilities.zoom.min || 1, Math.min(capabilities.zoom.max || 1, options.level));
613
+ // Note: autoFocus is not supported on web platform
367
614
  try {
368
615
  await videoTrack.applyConstraints({
369
- advanced: [{ zoom: zoomLevel }]
616
+ advanced: [{ zoom: zoomLevel }],
370
617
  });
371
618
  }
372
619
  catch (error) {
@@ -380,7 +627,7 @@ var capacitorCapacitorCameraView = (function (exports, core) {
380
627
  return { deviceId: this.currentDeviceId || "" };
381
628
  }
382
629
  async setDeviceId(options) {
383
- const video = document.getElementById("video");
630
+ const video = document.getElementById(DEFAULT_VIDEO_ID);
384
631
  if (!(video === null || video === void 0 ? void 0 : video.srcObject)) {
385
632
  throw new Error("camera is not running");
386
633
  }
@@ -399,8 +646,11 @@ var capacitorCapacitorCameraView = (function (exports, core) {
399
646
  try {
400
647
  // Try to determine camera position from device
401
648
  const devices = await navigator.mediaDevices.enumerateDevices();
402
- const device = devices.find(d => d.deviceId === options.deviceId);
403
- 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;
649
+ const device = devices.find((d) => d.deviceId === options.deviceId);
650
+ this.isBackCamera =
651
+ (device === null || device === void 0 ? void 0 : device.label.toLowerCase().includes("back")) ||
652
+ (device === null || device === void 0 ? void 0 : device.label.toLowerCase().includes("rear")) ||
653
+ false;
404
654
  const stream = await navigator.mediaDevices.getUserMedia(constraints);
405
655
  video.srcObject = stream;
406
656
  // Update video transform based on camera
@@ -418,6 +668,212 @@ var capacitorCapacitorCameraView = (function (exports, core) {
418
668
  throw new Error(`Failed to swap to device ${options.deviceId}: ${error}`);
419
669
  }
420
670
  }
671
+ async getAspectRatio() {
672
+ const video = document.getElementById(DEFAULT_VIDEO_ID);
673
+ if (!video) {
674
+ throw new Error("camera is not running");
675
+ }
676
+ const width = video.offsetWidth;
677
+ const height = video.offsetHeight;
678
+ if (width && height) {
679
+ const ratio = width / height;
680
+ // Check for portrait camera ratios: 4:3 -> 3:4, 16:9 -> 9:16
681
+ if (Math.abs(ratio - 3 / 4) < 0.01) {
682
+ return { aspectRatio: "4:3" };
683
+ }
684
+ if (Math.abs(ratio - 9 / 16) < 0.01) {
685
+ return { aspectRatio: "16:9" };
686
+ }
687
+ }
688
+ // Default to 4:3 if no specific aspect ratio is matched
689
+ return { aspectRatio: "4:3" };
690
+ }
691
+ async setAspectRatio(options) {
692
+ const video = document.getElementById(DEFAULT_VIDEO_ID);
693
+ if (!video) {
694
+ throw new Error("camera is not running");
695
+ }
696
+ if (options.aspectRatio) {
697
+ const [widthRatio, heightRatio] = options.aspectRatio
698
+ .split(":")
699
+ .map(Number);
700
+ // For camera, use portrait orientation: 4:3 becomes 3:4, 16:9 becomes 9:16
701
+ const ratio = heightRatio / widthRatio;
702
+ // Get current position and size
703
+ const rect = video.getBoundingClientRect();
704
+ const currentWidth = rect.width;
705
+ const currentHeight = rect.height;
706
+ const currentRatio = currentWidth / currentHeight;
707
+ let newWidth;
708
+ let newHeight;
709
+ if (currentRatio > ratio) {
710
+ // Width is larger, fit by height and center horizontally
711
+ newWidth = currentHeight * ratio;
712
+ newHeight = currentHeight;
713
+ }
714
+ else {
715
+ // Height is larger, fit by width and center vertically
716
+ newWidth = currentWidth;
717
+ newHeight = currentWidth / ratio;
718
+ }
719
+ // Calculate position
720
+ let x, y;
721
+ if (options.x !== undefined && options.y !== undefined) {
722
+ // Use provided coordinates, ensuring they stay within screen boundaries
723
+ x = Math.max(0, Math.min(options.x, window.innerWidth - newWidth));
724
+ y = Math.max(0, Math.min(options.y, window.innerHeight - newHeight));
725
+ }
726
+ else {
727
+ // Auto-center the view
728
+ x = (window.innerWidth - newWidth) / 2;
729
+ y = (window.innerHeight - newHeight) / 2;
730
+ }
731
+ video.style.width = `${newWidth}px`;
732
+ video.style.height = `${newHeight}px`;
733
+ video.style.left = `${x}px`;
734
+ video.style.top = `${y}px`;
735
+ video.style.position = "absolute";
736
+ const offsetX = newWidth / 8;
737
+ const offsetY = newHeight / 8;
738
+ return {
739
+ width: Math.round(newWidth),
740
+ height: Math.round(newHeight),
741
+ x: Math.round(x + offsetX),
742
+ y: Math.round(y + offsetY),
743
+ };
744
+ }
745
+ else {
746
+ video.style.objectFit = "cover";
747
+ const rect = video.getBoundingClientRect();
748
+ const offsetX = rect.width / 8;
749
+ const offsetY = rect.height / 8;
750
+ return {
751
+ width: Math.round(rect.width),
752
+ height: Math.round(rect.height),
753
+ x: Math.round(rect.left + offsetX),
754
+ y: Math.round(rect.top + offsetY),
755
+ };
756
+ }
757
+ }
758
+ createGridOverlay(gridMode) {
759
+ const overlay = document.createElement("div");
760
+ overlay.style.position = "absolute";
761
+ overlay.style.top = "0";
762
+ overlay.style.left = "0";
763
+ overlay.style.width = "100%";
764
+ overlay.style.height = "100%";
765
+ overlay.style.pointerEvents = "none";
766
+ overlay.style.zIndex = "10";
767
+ const divisions = gridMode === "3x3" ? 3 : 4;
768
+ // Create SVG for grid lines
769
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
770
+ svg.style.width = "100%";
771
+ svg.style.height = "100%";
772
+ svg.style.position = "absolute";
773
+ svg.style.top = "0";
774
+ svg.style.left = "0";
775
+ // Create grid lines
776
+ for (let i = 1; i < divisions; i++) {
777
+ // Vertical lines
778
+ const verticalLine = document.createElementNS("http://www.w3.org/2000/svg", "line");
779
+ verticalLine.setAttribute("x1", `${(i / divisions) * 100}%`);
780
+ verticalLine.setAttribute("y1", "0%");
781
+ verticalLine.setAttribute("x2", `${(i / divisions) * 100}%`);
782
+ verticalLine.setAttribute("y2", "100%");
783
+ verticalLine.setAttribute("stroke", "rgba(255, 255, 255, 0.5)");
784
+ verticalLine.setAttribute("stroke-width", "1");
785
+ svg.appendChild(verticalLine);
786
+ // Horizontal lines
787
+ const horizontalLine = document.createElementNS("http://www.w3.org/2000/svg", "line");
788
+ horizontalLine.setAttribute("x1", "0%");
789
+ horizontalLine.setAttribute("y1", `${(i / divisions) * 100}%`);
790
+ horizontalLine.setAttribute("x2", "100%");
791
+ horizontalLine.setAttribute("y2", `${(i / divisions) * 100}%`);
792
+ horizontalLine.setAttribute("stroke", "rgba(255, 255, 255, 0.5)");
793
+ horizontalLine.setAttribute("stroke-width", "1");
794
+ svg.appendChild(horizontalLine);
795
+ }
796
+ overlay.appendChild(svg);
797
+ return overlay;
798
+ }
799
+ async setGridMode(options) {
800
+ // Web implementation of grid mode would need to be implemented
801
+ // For now, just resolve as a no-op
802
+ console.warn(`Grid mode '${options.gridMode}' is not yet implemented for web platform`);
803
+ }
804
+ async getGridMode() {
805
+ // Web implementation - default to none
806
+ return { gridMode: "none" };
807
+ }
808
+ async getPreviewSize() {
809
+ const video = document.getElementById(DEFAULT_VIDEO_ID);
810
+ if (!video) {
811
+ throw new Error("camera is not running");
812
+ }
813
+ const offsetX = video.width / 8;
814
+ const offsetY = video.height / 8;
815
+ return {
816
+ x: video.offsetLeft + offsetX,
817
+ y: video.offsetTop + offsetY,
818
+ width: video.width,
819
+ height: video.height,
820
+ };
821
+ }
822
+ async setPreviewSize(options) {
823
+ const video = document.getElementById(DEFAULT_VIDEO_ID);
824
+ if (!video) {
825
+ throw new Error("camera is not running");
826
+ }
827
+ video.style.left = `${options.x}px`;
828
+ video.style.top = `${options.y}px`;
829
+ video.width = options.width;
830
+ video.height = options.height;
831
+ const offsetX = options.width / 8;
832
+ const offsetY = options.height / 8;
833
+ return {
834
+ width: options.width,
835
+ height: options.height,
836
+ x: options.x + offsetX,
837
+ y: options.y + offsetY,
838
+ };
839
+ }
840
+ async setFocus(options) {
841
+ // Reject if values are outside 0-1 range
842
+ if (options.x < 0 || options.x > 1 || options.y < 0 || options.y > 1) {
843
+ throw new Error("Focus coordinates must be between 0 and 1");
844
+ }
845
+ const video = document.getElementById(DEFAULT_VIDEO_ID);
846
+ if (!(video === null || video === void 0 ? void 0 : video.srcObject)) {
847
+ throw new Error("camera is not running");
848
+ }
849
+ const stream = video.srcObject;
850
+ const videoTrack = stream.getVideoTracks()[0];
851
+ if (!videoTrack) {
852
+ throw new Error("no video track found");
853
+ }
854
+ const capabilities = videoTrack.getCapabilities();
855
+ // Check if focusing is supported
856
+ if (capabilities.focusMode) {
857
+ try {
858
+ // Web API supports focus mode settings but not coordinate-based focus
859
+ // Setting to manual mode allows for coordinate focus if supported
860
+ await videoTrack.applyConstraints({
861
+ advanced: [
862
+ {
863
+ focusMode: "manual",
864
+ focusDistance: 0.5, // Mid-range focus as fallback
865
+ },
866
+ ],
867
+ });
868
+ }
869
+ catch (error) {
870
+ console.warn(`setFocus is not fully supported on this device: ${error}. Focus coordinates (${options.x}, ${options.y}) were provided but cannot be applied.`);
871
+ }
872
+ }
873
+ else {
874
+ console.warn("Focus control is not supported on this device. Focus coordinates were provided but cannot be applied.");
875
+ }
876
+ }
421
877
  }
422
878
 
423
879
  var web = /*#__PURE__*/Object.freeze({