@capgo/camera-preview 7.4.0-beta.12 → 7.4.0-beta.15
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/README.md +28 -10
- package/android/.gradle/8.14.2/executionHistory/executionHistory.bin +0 -0
- package/android/.gradle/8.14.2/executionHistory/executionHistory.lock +0 -0
- package/android/.gradle/8.14.2/fileHashes/fileHashes.bin +0 -0
- package/android/.gradle/8.14.2/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.14.2/fileHashes/resourceHashesCache.bin +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/file-system.probe +0 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +200 -15
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +770 -98
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraSessionConfiguration.java +9 -0
- package/dist/docs.json +56 -23
- package/dist/esm/definitions.d.ts +26 -10
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +5 -0
- package/dist/esm/web.js +256 -34
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +256 -34
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +256 -34
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapgoCameraPreview/CameraController.swift +418 -66
- package/ios/Sources/CapgoCameraPreview/Plugin.swift +69 -18
- package/package.json +2 -2
package/dist/plugin.cjs.js
CHANGED
|
@@ -63,47 +63,57 @@ class CameraPreviewWeb extends core.WebPlugin {
|
|
|
63
63
|
this.videoElement.playsInline = true;
|
|
64
64
|
this.videoElement.muted = true;
|
|
65
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";
|
|
66
71
|
container.appendChild(this.videoElement);
|
|
67
72
|
if (options.toBack) {
|
|
68
73
|
this.videoElement.style.zIndex = "-1";
|
|
69
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);
|
|
70
78
|
if (options.width) {
|
|
71
79
|
this.videoElement.width = options.width;
|
|
80
|
+
this.videoElement.style.width = `${options.width}px`;
|
|
72
81
|
}
|
|
73
82
|
if (options.height) {
|
|
74
83
|
this.videoElement.height = options.height;
|
|
75
|
-
|
|
76
|
-
|
|
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) {
|
|
77
98
|
this.videoElement.style.left = `${options.x}px`;
|
|
78
99
|
}
|
|
100
|
+
if (options.y !== undefined) {
|
|
101
|
+
this.videoElement.style.top = `${options.y}px`;
|
|
102
|
+
}
|
|
79
103
|
// Create and add grid overlay if needed
|
|
80
104
|
if (gridMode !== "none") {
|
|
81
105
|
const gridOverlay = this.createGridOverlay(gridMode);
|
|
82
106
|
gridOverlay.id = "camera-grid-overlay";
|
|
83
107
|
parent === null || parent === void 0 ? void 0 : parent.appendChild(gridOverlay);
|
|
84
108
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
.map(Number);
|
|
92
|
-
const ratio = widthRatio / heightRatio;
|
|
93
|
-
if (options.width) {
|
|
94
|
-
this.videoElement.height = options.width / ratio;
|
|
95
|
-
}
|
|
96
|
-
else if (options.height) {
|
|
97
|
-
this.videoElement.width = options.height * ratio;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
else {
|
|
101
|
-
this.videoElement.style.objectFit = "cover";
|
|
102
|
-
}
|
|
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
|
|
103
115
|
const constraints = {
|
|
104
116
|
video: {
|
|
105
|
-
width: { ideal: this.videoElement.width || 640 },
|
|
106
|
-
height: { ideal: this.videoElement.height || window.innerHeight },
|
|
107
117
|
facingMode: this.isBackCamera ? "environment" : "user",
|
|
108
118
|
},
|
|
109
119
|
};
|
|
@@ -114,16 +124,182 @@ class CameraPreviewWeb extends core.WebPlugin {
|
|
|
114
124
|
if (!this.videoElement) {
|
|
115
125
|
throw new Error("video element not found");
|
|
116
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
|
+
const y = Math.round((window.innerHeight - targetHeight) / 2);
|
|
174
|
+
this.videoElement.style.setProperty("top", `${y}px`, "important");
|
|
175
|
+
// Force a style recalculation
|
|
176
|
+
this.videoElement.offsetHeight;
|
|
177
|
+
console.log("Centering video:", {
|
|
178
|
+
viewportHeight: window.innerHeight,
|
|
179
|
+
targetHeight,
|
|
180
|
+
calculatedY: y,
|
|
181
|
+
actualTop: this.videoElement.style.top,
|
|
182
|
+
position: this.videoElement.style.position,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
else if (effectiveAspectRatio && !options.width && !options.height) {
|
|
187
|
+
// Aspect ratio specified but no size
|
|
188
|
+
const [widthRatio, heightRatio] = effectiveAspectRatio
|
|
189
|
+
.split(":")
|
|
190
|
+
.map(Number);
|
|
191
|
+
const targetRatio = widthRatio / heightRatio;
|
|
192
|
+
const viewportWidth = window.innerWidth;
|
|
193
|
+
const viewportHeight = window.innerHeight;
|
|
194
|
+
let targetWidth, targetHeight;
|
|
195
|
+
// Try fitting to viewport width first
|
|
196
|
+
targetWidth = viewportWidth;
|
|
197
|
+
targetHeight = targetWidth / targetRatio;
|
|
198
|
+
// If height exceeds viewport, fit to height instead
|
|
199
|
+
if (targetHeight > viewportHeight) {
|
|
200
|
+
targetHeight = viewportHeight;
|
|
201
|
+
targetWidth = targetHeight * targetRatio;
|
|
202
|
+
}
|
|
203
|
+
this.videoElement.width = targetWidth;
|
|
204
|
+
this.videoElement.height = targetHeight;
|
|
205
|
+
this.videoElement.style.width = `${targetWidth}px`;
|
|
206
|
+
this.videoElement.style.height = `${targetHeight}px`;
|
|
207
|
+
// Center the video element within its parent container
|
|
208
|
+
if (needsCenterX || options.x === undefined) {
|
|
209
|
+
const parentWidth = container.offsetWidth || viewportWidth;
|
|
210
|
+
const x = Math.round((parentWidth - targetWidth) / 2);
|
|
211
|
+
this.videoElement.style.left = `${x}px`;
|
|
212
|
+
}
|
|
213
|
+
if (needsCenterY || options.y === undefined) {
|
|
214
|
+
const parentHeight = container.offsetHeight || viewportHeight;
|
|
215
|
+
const y = Math.round((parentHeight - targetHeight) / 2);
|
|
216
|
+
this.videoElement.style.top = `${y}px`;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
117
219
|
this.videoElement.srcObject = stream;
|
|
118
220
|
if (!this.isBackCamera) {
|
|
119
221
|
this.videoElement.style.transform = "scaleX(-1)";
|
|
120
222
|
}
|
|
223
|
+
// Set initial zoom level if specified and supported
|
|
224
|
+
if (options.initialZoomLevel && options.initialZoomLevel !== 1.0) {
|
|
225
|
+
// videoTrack already declared above
|
|
226
|
+
if (videoTrack) {
|
|
227
|
+
const capabilities = videoTrack.getCapabilities();
|
|
228
|
+
if (capabilities.zoom) {
|
|
229
|
+
const zoomLevel = options.initialZoomLevel;
|
|
230
|
+
const minZoom = capabilities.zoom.min || 1;
|
|
231
|
+
const maxZoom = capabilities.zoom.max || 1;
|
|
232
|
+
if (zoomLevel < minZoom || zoomLevel > maxZoom) {
|
|
233
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
234
|
+
throw new Error(`Initial zoom level ${zoomLevel} is not available. Valid range is ${minZoom} to ${maxZoom}`);
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
await videoTrack.applyConstraints({
|
|
238
|
+
advanced: [{ zoom: zoomLevel }],
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
console.warn(`Failed to set initial zoom level: ${error}`);
|
|
243
|
+
// Don't throw, just continue without zoom
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
121
248
|
this.isStarted = true;
|
|
249
|
+
// Wait for video to be ready and get actual dimensions
|
|
250
|
+
await new Promise((resolve) => {
|
|
251
|
+
if (this.videoElement.readyState >= 2) {
|
|
252
|
+
resolve();
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
this.videoElement.addEventListener("loadeddata", () => resolve(), {
|
|
256
|
+
once: true,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
// Ensure centering is applied after DOM updates
|
|
261
|
+
await new Promise((resolve) => requestAnimationFrame(resolve));
|
|
262
|
+
console.log("About to re-center, flags:", { needsCenterX, needsCenterY });
|
|
263
|
+
// Re-apply centering with correct parent dimensions
|
|
264
|
+
if (needsCenterX) {
|
|
265
|
+
const parentWidth = container.offsetWidth;
|
|
266
|
+
const x = Math.round((parentWidth - this.videoElement.offsetWidth) / 2);
|
|
267
|
+
this.videoElement.style.left = `${x}px`;
|
|
268
|
+
console.log("Re-centering X:", {
|
|
269
|
+
parentWidth,
|
|
270
|
+
videoWidth: this.videoElement.offsetWidth,
|
|
271
|
+
x,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
if (needsCenterY) {
|
|
275
|
+
const y = Math.round((window.innerHeight - this.videoElement.offsetHeight) / 2);
|
|
276
|
+
this.videoElement.style.setProperty("top", `${y}px`, "important");
|
|
277
|
+
console.log("Re-centering Y:", {
|
|
278
|
+
viewportHeight: window.innerHeight,
|
|
279
|
+
videoHeight: this.videoElement.offsetHeight,
|
|
280
|
+
y,
|
|
281
|
+
position: this.videoElement.style.position,
|
|
282
|
+
top: this.videoElement.style.top,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
// Get the actual rendered dimensions after video is loaded
|
|
286
|
+
const rect = this.videoElement.getBoundingClientRect();
|
|
287
|
+
const computedStyle = window.getComputedStyle(this.videoElement);
|
|
288
|
+
console.log("Final video element state:", {
|
|
289
|
+
rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
|
|
290
|
+
style: {
|
|
291
|
+
position: computedStyle.position,
|
|
292
|
+
left: computedStyle.left,
|
|
293
|
+
top: computedStyle.top,
|
|
294
|
+
width: computedStyle.width,
|
|
295
|
+
height: computedStyle.height,
|
|
296
|
+
},
|
|
297
|
+
});
|
|
122
298
|
return {
|
|
123
|
-
width:
|
|
124
|
-
height:
|
|
125
|
-
x:
|
|
126
|
-
y:
|
|
299
|
+
width: Math.round(rect.width),
|
|
300
|
+
height: Math.round(rect.height),
|
|
301
|
+
x: Math.round(rect.x),
|
|
302
|
+
y: Math.round(rect.y),
|
|
127
303
|
};
|
|
128
304
|
}
|
|
129
305
|
stopStream(stream) {
|
|
@@ -396,6 +572,7 @@ class CameraPreviewWeb extends core.WebPlugin {
|
|
|
396
572
|
throw new Error("zoom not supported by this device");
|
|
397
573
|
}
|
|
398
574
|
const zoomLevel = Math.max(capabilities.zoom.min || 1, Math.min(capabilities.zoom.max || 1, options.level));
|
|
575
|
+
// Note: autoFocus is not supported on web platform
|
|
399
576
|
try {
|
|
400
577
|
await videoTrack.applyConstraints({
|
|
401
578
|
advanced: [{ zoom: zoomLevel }],
|
|
@@ -518,21 +695,25 @@ class CameraPreviewWeb extends core.WebPlugin {
|
|
|
518
695
|
video.style.left = `${x}px`;
|
|
519
696
|
video.style.top = `${y}px`;
|
|
520
697
|
video.style.position = "absolute";
|
|
698
|
+
const offsetX = newWidth / 8;
|
|
699
|
+
const offsetY = newHeight / 8;
|
|
521
700
|
return {
|
|
522
701
|
width: Math.round(newWidth),
|
|
523
702
|
height: Math.round(newHeight),
|
|
524
|
-
x: Math.round(x),
|
|
525
|
-
y: Math.round(y),
|
|
703
|
+
x: Math.round(x + offsetX),
|
|
704
|
+
y: Math.round(y + offsetY),
|
|
526
705
|
};
|
|
527
706
|
}
|
|
528
707
|
else {
|
|
529
708
|
video.style.objectFit = "cover";
|
|
530
709
|
const rect = video.getBoundingClientRect();
|
|
710
|
+
const offsetX = rect.width / 8;
|
|
711
|
+
const offsetY = rect.height / 8;
|
|
531
712
|
return {
|
|
532
713
|
width: Math.round(rect.width),
|
|
533
714
|
height: Math.round(rect.height),
|
|
534
|
-
x: Math.round(rect.left),
|
|
535
|
-
y: Math.round(rect.top),
|
|
715
|
+
x: Math.round(rect.left + offsetX),
|
|
716
|
+
y: Math.round(rect.top + offsetY),
|
|
536
717
|
};
|
|
537
718
|
}
|
|
538
719
|
}
|
|
@@ -591,9 +772,11 @@ class CameraPreviewWeb extends core.WebPlugin {
|
|
|
591
772
|
if (!video) {
|
|
592
773
|
throw new Error("camera is not running");
|
|
593
774
|
}
|
|
775
|
+
const offsetX = video.width / 8;
|
|
776
|
+
const offsetY = video.height / 8;
|
|
594
777
|
return {
|
|
595
|
-
x: video.offsetLeft,
|
|
596
|
-
y: video.offsetTop,
|
|
778
|
+
x: video.offsetLeft + offsetX,
|
|
779
|
+
y: video.offsetTop + offsetY,
|
|
597
780
|
width: video.width,
|
|
598
781
|
height: video.height,
|
|
599
782
|
};
|
|
@@ -607,13 +790,52 @@ class CameraPreviewWeb extends core.WebPlugin {
|
|
|
607
790
|
video.style.top = `${options.y}px`;
|
|
608
791
|
video.width = options.width;
|
|
609
792
|
video.height = options.height;
|
|
793
|
+
const offsetX = options.width / 8;
|
|
794
|
+
const offsetY = options.height / 8;
|
|
610
795
|
return {
|
|
611
796
|
width: options.width,
|
|
612
797
|
height: options.height,
|
|
613
|
-
x: options.x,
|
|
614
|
-
y: options.y,
|
|
798
|
+
x: options.x + offsetX,
|
|
799
|
+
y: options.y + offsetY,
|
|
615
800
|
};
|
|
616
801
|
}
|
|
802
|
+
async setFocus(options) {
|
|
803
|
+
// Reject if values are outside 0-1 range
|
|
804
|
+
if (options.x < 0 || options.x > 1 || options.y < 0 || options.y > 1) {
|
|
805
|
+
throw new Error("Focus coordinates must be between 0 and 1");
|
|
806
|
+
}
|
|
807
|
+
const video = document.getElementById(DEFAULT_VIDEO_ID);
|
|
808
|
+
if (!(video === null || video === void 0 ? void 0 : video.srcObject)) {
|
|
809
|
+
throw new Error("camera is not running");
|
|
810
|
+
}
|
|
811
|
+
const stream = video.srcObject;
|
|
812
|
+
const videoTrack = stream.getVideoTracks()[0];
|
|
813
|
+
if (!videoTrack) {
|
|
814
|
+
throw new Error("no video track found");
|
|
815
|
+
}
|
|
816
|
+
const capabilities = videoTrack.getCapabilities();
|
|
817
|
+
// Check if focusing is supported
|
|
818
|
+
if (capabilities.focusMode) {
|
|
819
|
+
try {
|
|
820
|
+
// Web API supports focus mode settings but not coordinate-based focus
|
|
821
|
+
// Setting to manual mode allows for coordinate focus if supported
|
|
822
|
+
await videoTrack.applyConstraints({
|
|
823
|
+
advanced: [
|
|
824
|
+
{
|
|
825
|
+
focusMode: "manual",
|
|
826
|
+
focusDistance: 0.5, // Mid-range focus as fallback
|
|
827
|
+
},
|
|
828
|
+
],
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
catch (error) {
|
|
832
|
+
console.warn(`setFocus is not fully supported on this device: ${error}. Focus coordinates (${options.x}, ${options.y}) were provided but cannot be applied.`);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
else {
|
|
836
|
+
console.warn("Focus control is not supported on this device. Focus coordinates were provided but cannot be applied.");
|
|
837
|
+
}
|
|
838
|
+
}
|
|
617
839
|
}
|
|
618
840
|
|
|
619
841
|
var web = /*#__PURE__*/Object.freeze({
|