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