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

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