@capgo/camera-preview 7.4.0-beta.13 → 7.4.0-beta.16

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.
package/dist/plugin.js CHANGED
@@ -62,47 +62,57 @@ var capacitorCapacitorCameraView = (function (exports, core) {
62
62
  this.videoElement.playsInline = true;
63
63
  this.videoElement.muted = true;
64
64
  this.videoElement.autoplay = true;
65
+ // Remove objectFit as we'll match camera's native aspect ratio
66
+ this.videoElement.style.backgroundColor = "transparent";
67
+ // Reset any default margins that might interfere
68
+ this.videoElement.style.margin = "0";
69
+ this.videoElement.style.padding = "0";
65
70
  container.appendChild(this.videoElement);
66
71
  if (options.toBack) {
67
72
  this.videoElement.style.zIndex = "-1";
68
73
  }
74
+ // Default to 16:9 vertical (9:16 for portrait) if no aspect ratio or size specified
75
+ const useDefaultAspectRatio = !options.aspectRatio && !options.width && !options.height;
76
+ const effectiveAspectRatio = options.aspectRatio || (useDefaultAspectRatio ? "16:9" : null);
69
77
  if (options.width) {
70
78
  this.videoElement.width = options.width;
79
+ this.videoElement.style.width = `${options.width}px`;
71
80
  }
72
81
  if (options.height) {
73
82
  this.videoElement.height = options.height;
74
- }
75
- if (options.x) {
83
+ this.videoElement.style.height = `${options.height}px`;
84
+ }
85
+ // Handle positioning - center if x or y not provided
86
+ const centerX = options.x === undefined;
87
+ const centerY = options.y === undefined;
88
+ // Always set position to absolute for proper positioning
89
+ this.videoElement.style.position = "absolute";
90
+ console.log("Initial positioning flags:", {
91
+ centerX,
92
+ centerY,
93
+ x: options.x,
94
+ y: options.y,
95
+ });
96
+ if (options.x !== undefined) {
76
97
  this.videoElement.style.left = `${options.x}px`;
77
98
  }
99
+ if (options.y !== undefined) {
100
+ this.videoElement.style.top = `${options.y}px`;
101
+ }
78
102
  // Create and add grid overlay if needed
79
103
  if (gridMode !== "none") {
80
104
  const gridOverlay = this.createGridOverlay(gridMode);
81
105
  gridOverlay.id = "camera-grid-overlay";
82
106
  parent === null || parent === void 0 ? void 0 : parent.appendChild(gridOverlay);
83
107
  }
84
- if (options.y) {
85
- this.videoElement.style.top = `${options.y}px`;
86
- }
87
- if (options.aspectRatio) {
88
- const [widthRatio, heightRatio] = options.aspectRatio
89
- .split(":")
90
- .map(Number);
91
- const ratio = widthRatio / heightRatio;
92
- if (options.width) {
93
- this.videoElement.height = options.width / ratio;
94
- }
95
- else if (options.height) {
96
- this.videoElement.width = options.height * ratio;
97
- }
98
- }
99
- else {
100
- this.videoElement.style.objectFit = "cover";
101
- }
108
+ // Aspect ratio handling is now done after getting camera stream
109
+ // Store centering flags for later use
110
+ const needsCenterX = centerX;
111
+ const needsCenterY = centerY;
112
+ console.log("Centering flags stored:", { needsCenterX, needsCenterY });
113
+ // First get the camera stream with basic constraints
102
114
  const constraints = {
103
115
  video: {
104
- width: { ideal: this.videoElement.width || 640 },
105
- height: { ideal: this.videoElement.height || window.innerHeight },
106
116
  facingMode: this.isBackCamera ? "environment" : "user",
107
117
  },
108
118
  };
@@ -113,16 +123,182 @@ var capacitorCapacitorCameraView = (function (exports, core) {
113
123
  if (!this.videoElement) {
114
124
  throw new Error("video element not found");
115
125
  }
126
+ // Get the actual camera dimensions from the video track
127
+ const videoTrack = stream.getVideoTracks()[0];
128
+ const settings = videoTrack.getSettings();
129
+ const cameraWidth = settings.width || 640;
130
+ const cameraHeight = settings.height || 480;
131
+ const cameraAspectRatio = cameraWidth / cameraHeight;
132
+ console.log("Camera native dimensions:", {
133
+ width: cameraWidth,
134
+ height: cameraHeight,
135
+ aspectRatio: cameraAspectRatio,
136
+ });
137
+ console.log("Container dimensions:", {
138
+ width: container.offsetWidth,
139
+ height: container.offsetHeight,
140
+ id: container.id,
141
+ });
142
+ // Now adjust video element size based on camera's native aspect ratio
143
+ if (!options.width && !options.height && !options.aspectRatio) {
144
+ // No size specified, fit camera view within container bounds
145
+ const containerWidth = container.offsetWidth || window.innerWidth;
146
+ const containerHeight = container.offsetHeight || window.innerHeight;
147
+ // Calculate dimensions that fit within container while maintaining camera aspect ratio
148
+ let targetWidth, targetHeight;
149
+ // Try fitting to container width first
150
+ targetWidth = containerWidth;
151
+ targetHeight = targetWidth / cameraAspectRatio;
152
+ // If height exceeds container, fit to height instead
153
+ if (targetHeight > containerHeight) {
154
+ targetHeight = containerHeight;
155
+ targetWidth = targetHeight * cameraAspectRatio;
156
+ }
157
+ console.log("Video element dimensions:", {
158
+ width: targetWidth,
159
+ height: targetHeight,
160
+ container: { width: containerWidth, height: containerHeight },
161
+ });
162
+ this.videoElement.width = targetWidth;
163
+ this.videoElement.height = targetHeight;
164
+ this.videoElement.style.width = `${targetWidth}px`;
165
+ this.videoElement.style.height = `${targetHeight}px`;
166
+ // Center the video element within its parent container
167
+ if (needsCenterX || options.x === undefined) {
168
+ const x = Math.round((containerWidth - targetWidth) / 2);
169
+ this.videoElement.style.left = `${x}px`;
170
+ }
171
+ if (needsCenterY || options.y === undefined) {
172
+ const y = Math.round((window.innerHeight - targetHeight) / 2);
173
+ this.videoElement.style.setProperty("top", `${y}px`, "important");
174
+ // Force a style recalculation
175
+ this.videoElement.offsetHeight;
176
+ console.log("Centering video:", {
177
+ viewportHeight: window.innerHeight,
178
+ targetHeight,
179
+ calculatedY: y,
180
+ actualTop: this.videoElement.style.top,
181
+ position: this.videoElement.style.position,
182
+ });
183
+ }
184
+ }
185
+ else if (effectiveAspectRatio && !options.width && !options.height) {
186
+ // Aspect ratio specified but no size
187
+ const [widthRatio, heightRatio] = effectiveAspectRatio
188
+ .split(":")
189
+ .map(Number);
190
+ const targetRatio = widthRatio / heightRatio;
191
+ const viewportWidth = window.innerWidth;
192
+ const viewportHeight = window.innerHeight;
193
+ let targetWidth, targetHeight;
194
+ // Try fitting to viewport width first
195
+ targetWidth = viewportWidth;
196
+ targetHeight = targetWidth / targetRatio;
197
+ // If height exceeds viewport, fit to height instead
198
+ if (targetHeight > viewportHeight) {
199
+ targetHeight = viewportHeight;
200
+ targetWidth = targetHeight * targetRatio;
201
+ }
202
+ this.videoElement.width = targetWidth;
203
+ this.videoElement.height = targetHeight;
204
+ this.videoElement.style.width = `${targetWidth}px`;
205
+ this.videoElement.style.height = `${targetHeight}px`;
206
+ // Center the video element within its parent container
207
+ if (needsCenterX || options.x === undefined) {
208
+ const parentWidth = container.offsetWidth || viewportWidth;
209
+ const x = Math.round((parentWidth - targetWidth) / 2);
210
+ this.videoElement.style.left = `${x}px`;
211
+ }
212
+ if (needsCenterY || options.y === undefined) {
213
+ const parentHeight = container.offsetHeight || viewportHeight;
214
+ const y = Math.round((parentHeight - targetHeight) / 2);
215
+ this.videoElement.style.top = `${y}px`;
216
+ }
217
+ }
116
218
  this.videoElement.srcObject = stream;
117
219
  if (!this.isBackCamera) {
118
220
  this.videoElement.style.transform = "scaleX(-1)";
119
221
  }
222
+ // Set initial zoom level if specified and supported
223
+ if (options.initialZoomLevel && options.initialZoomLevel !== 1.0) {
224
+ // videoTrack already declared above
225
+ if (videoTrack) {
226
+ const capabilities = videoTrack.getCapabilities();
227
+ if (capabilities.zoom) {
228
+ const zoomLevel = options.initialZoomLevel;
229
+ const minZoom = capabilities.zoom.min || 1;
230
+ const maxZoom = capabilities.zoom.max || 1;
231
+ if (zoomLevel < minZoom || zoomLevel > maxZoom) {
232
+ stream.getTracks().forEach((track) => track.stop());
233
+ throw new Error(`Initial zoom level ${zoomLevel} is not available. Valid range is ${minZoom} to ${maxZoom}`);
234
+ }
235
+ try {
236
+ await videoTrack.applyConstraints({
237
+ advanced: [{ zoom: zoomLevel }],
238
+ });
239
+ }
240
+ catch (error) {
241
+ console.warn(`Failed to set initial zoom level: ${error}`);
242
+ // Don't throw, just continue without zoom
243
+ }
244
+ }
245
+ }
246
+ }
120
247
  this.isStarted = true;
248
+ // Wait for video to be ready and get actual dimensions
249
+ await new Promise((resolve) => {
250
+ if (this.videoElement.readyState >= 2) {
251
+ resolve();
252
+ }
253
+ else {
254
+ this.videoElement.addEventListener("loadeddata", () => resolve(), {
255
+ once: true,
256
+ });
257
+ }
258
+ });
259
+ // Ensure centering is applied after DOM updates
260
+ await new Promise((resolve) => requestAnimationFrame(resolve));
261
+ console.log("About to re-center, flags:", { needsCenterX, needsCenterY });
262
+ // Re-apply centering with correct parent dimensions
263
+ if (needsCenterX) {
264
+ const parentWidth = container.offsetWidth;
265
+ const x = Math.round((parentWidth - this.videoElement.offsetWidth) / 2);
266
+ this.videoElement.style.left = `${x}px`;
267
+ console.log("Re-centering X:", {
268
+ parentWidth,
269
+ videoWidth: this.videoElement.offsetWidth,
270
+ x,
271
+ });
272
+ }
273
+ if (needsCenterY) {
274
+ const y = Math.round((window.innerHeight - this.videoElement.offsetHeight) / 2);
275
+ this.videoElement.style.setProperty("top", `${y}px`, "important");
276
+ console.log("Re-centering Y:", {
277
+ viewportHeight: window.innerHeight,
278
+ videoHeight: this.videoElement.offsetHeight,
279
+ y,
280
+ position: this.videoElement.style.position,
281
+ top: this.videoElement.style.top,
282
+ });
283
+ }
284
+ // Get the actual rendered dimensions after video is loaded
285
+ const rect = this.videoElement.getBoundingClientRect();
286
+ const computedStyle = window.getComputedStyle(this.videoElement);
287
+ console.log("Final video element state:", {
288
+ rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
289
+ style: {
290
+ position: computedStyle.position,
291
+ left: computedStyle.left,
292
+ top: computedStyle.top,
293
+ width: computedStyle.width,
294
+ height: computedStyle.height,
295
+ },
296
+ });
121
297
  return {
122
- width: this.videoElement.width,
123
- height: this.videoElement.height,
124
- x: this.videoElement.getBoundingClientRect().x,
125
- y: this.videoElement.getBoundingClientRect().y,
298
+ width: Math.round(rect.width),
299
+ height: Math.round(rect.height),
300
+ x: Math.round(rect.x),
301
+ y: Math.round(rect.y),
126
302
  };
127
303
  }
128
304
  stopStream(stream) {
@@ -156,14 +332,53 @@ var capacitorCapacitorCameraView = (function (exports, core) {
156
332
  if (video && video.videoWidth > 0 && video.videoHeight > 0) {
157
333
  const canvas = document.createElement("canvas");
158
334
  const context = canvas.getContext("2d");
159
- canvas.width = video.videoWidth;
160
- canvas.height = video.videoHeight;
335
+ // Calculate capture dimensions
336
+ let captureWidth = video.videoWidth;
337
+ let captureHeight = video.videoHeight;
338
+ let sourceX = 0;
339
+ let sourceY = 0;
340
+ // Check for conflicting parameters
341
+ if (options.aspectRatio && (options.width || options.height)) {
342
+ reject(new Error("Cannot set both aspectRatio and size (width/height). Use setPreviewSize after start."));
343
+ return;
344
+ }
345
+ // Handle aspect ratio if no width/height specified
346
+ if (!options.width && !options.height && options.aspectRatio) {
347
+ const [widthRatio, heightRatio] = options.aspectRatio.split(':').map(Number);
348
+ if (widthRatio && heightRatio) {
349
+ // For capture in portrait orientation, swap the aspect ratio (16:9 becomes 9:16)
350
+ const isPortrait = video.videoHeight > video.videoWidth;
351
+ const targetAspectRatio = isPortrait ? heightRatio / widthRatio : widthRatio / heightRatio;
352
+ const videoAspectRatio = video.videoWidth / video.videoHeight;
353
+ if (videoAspectRatio > targetAspectRatio) {
354
+ // Video is wider than target - crop sides
355
+ captureWidth = video.videoHeight * targetAspectRatio;
356
+ captureHeight = video.videoHeight;
357
+ sourceX = (video.videoWidth - captureWidth) / 2;
358
+ }
359
+ else {
360
+ // Video is taller than target - crop top/bottom
361
+ captureWidth = video.videoWidth;
362
+ captureHeight = video.videoWidth / targetAspectRatio;
363
+ sourceY = (video.videoHeight - captureHeight) / 2;
364
+ }
365
+ }
366
+ }
367
+ else if (options.width || options.height) {
368
+ // If width or height is specified, use them
369
+ if (options.width)
370
+ captureWidth = options.width;
371
+ if (options.height)
372
+ captureHeight = options.height;
373
+ }
374
+ canvas.width = captureWidth;
375
+ canvas.height = captureHeight;
161
376
  // flip horizontally back camera isn't used
162
377
  if (!this.isBackCamera) {
163
- context === null || context === void 0 ? void 0 : context.translate(video.videoWidth, 0);
378
+ context === null || context === void 0 ? void 0 : context.translate(captureWidth, 0);
164
379
  context === null || context === void 0 ? void 0 : context.scale(-1, 1);
165
380
  }
166
- context === null || context === void 0 ? void 0 : context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
381
+ context === null || context === void 0 ? void 0 : context.drawImage(video, sourceX, sourceY, captureWidth, captureHeight, 0, 0, captureWidth, captureHeight);
167
382
  if (options.saveToGallery) ;
168
383
  if (options.withExifLocation) ;
169
384
  if ((options.format || "jpeg") === "jpeg") {
@@ -395,6 +610,7 @@ var capacitorCapacitorCameraView = (function (exports, core) {
395
610
  throw new Error("zoom not supported by this device");
396
611
  }
397
612
  const zoomLevel = Math.max(capabilities.zoom.min || 1, Math.min(capabilities.zoom.max || 1, options.level));
613
+ // Note: autoFocus is not supported on web platform
398
614
  try {
399
615
  await videoTrack.applyConstraints({
400
616
  advanced: [{ zoom: zoomLevel }],
@@ -517,21 +733,25 @@ var capacitorCapacitorCameraView = (function (exports, core) {
517
733
  video.style.left = `${x}px`;
518
734
  video.style.top = `${y}px`;
519
735
  video.style.position = "absolute";
736
+ const offsetX = newWidth / 8;
737
+ const offsetY = newHeight / 8;
520
738
  return {
521
739
  width: Math.round(newWidth),
522
740
  height: Math.round(newHeight),
523
- x: Math.round(x),
524
- y: Math.round(y),
741
+ x: Math.round(x + offsetX),
742
+ y: Math.round(y + offsetY),
525
743
  };
526
744
  }
527
745
  else {
528
746
  video.style.objectFit = "cover";
529
747
  const rect = video.getBoundingClientRect();
748
+ const offsetX = rect.width / 8;
749
+ const offsetY = rect.height / 8;
530
750
  return {
531
751
  width: Math.round(rect.width),
532
752
  height: Math.round(rect.height),
533
- x: Math.round(rect.left),
534
- y: Math.round(rect.top),
753
+ x: Math.round(rect.left + offsetX),
754
+ y: Math.round(rect.top + offsetY),
535
755
  };
536
756
  }
537
757
  }
@@ -590,9 +810,11 @@ var capacitorCapacitorCameraView = (function (exports, core) {
590
810
  if (!video) {
591
811
  throw new Error("camera is not running");
592
812
  }
813
+ const offsetX = video.width / 8;
814
+ const offsetY = video.height / 8;
593
815
  return {
594
- x: video.offsetLeft,
595
- y: video.offsetTop,
816
+ x: video.offsetLeft + offsetX,
817
+ y: video.offsetTop + offsetY,
596
818
  width: video.width,
597
819
  height: video.height,
598
820
  };
@@ -606,14 +828,20 @@ var capacitorCapacitorCameraView = (function (exports, core) {
606
828
  video.style.top = `${options.y}px`;
607
829
  video.width = options.width;
608
830
  video.height = options.height;
831
+ const offsetX = options.width / 8;
832
+ const offsetY = options.height / 8;
609
833
  return {
610
834
  width: options.width,
611
835
  height: options.height,
612
- x: options.x,
613
- y: options.y,
836
+ x: options.x + offsetX,
837
+ y: options.y + offsetY,
614
838
  };
615
839
  }
616
840
  async setFocus(options) {
841
+ // Reject if values are outside 0-1 range
842
+ if (options.x < 0 || options.x > 1 || options.y < 0 || options.y > 1) {
843
+ throw new Error("Focus coordinates must be between 0 and 1");
844
+ }
617
845
  const video = document.getElementById(DEFAULT_VIDEO_ID);
618
846
  if (!(video === null || video === void 0 ? void 0 : video.srcObject)) {
619
847
  throw new Error("camera is not running");