@capgo/camera-preview 7.3.11 → 7.4.0-alpha.1

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