@capgo/camera-preview 7.4.0-beta.9 → 7.4.0
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 +242 -50
- package/android/gradle/wrapper/gradle-wrapper.properties +1 -1
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +1249 -143
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +3400 -1432
- package/android/src/main/java/com/ahm/capacitor/camera/preview/GridOverlayView.java +95 -58
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraDevice.java +55 -46
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraLens.java +61 -52
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraSessionConfiguration.java +160 -72
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/LensInfo.java +29 -23
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/ZoomFactors.java +24 -23
- package/dist/docs.json +441 -40
- package/dist/esm/definitions.d.ts +167 -25
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +24 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/web.d.ts +23 -3
- package/dist/esm/web.js +463 -65
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +485 -64
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +485 -64
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/{CapgoCameraPreview → CapgoCameraPreviewPlugin}/CameraController.swift +731 -315
- package/ios/Sources/CapgoCameraPreviewPlugin/Plugin.swift +1902 -0
- package/package.json +11 -3
- package/android/.gradle/8.14.2/checksums/checksums.lock +0 -0
- package/android/.gradle/8.14.2/checksums/md5-checksums.bin +0 -0
- package/android/.gradle/8.14.2/checksums/sha1-checksums.bin +0 -0
- 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/fileChanges/last-build.bin +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/8.14.2/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +0 -2
- package/android/.gradle/buildOutputCleanup/outputFiles.bin +0 -0
- package/android/.gradle/file-system.probe +0 -0
- package/android/.gradle/vcs-1/gc.properties +0 -0
- package/ios/Sources/CapgoCameraPreview/Plugin.swift +0 -1369
- /package/ios/Sources/{CapgoCameraPreview → CapgoCameraPreviewPlugin}/GridOverlayView.swift +0 -0
package/dist/plugin.cjs.js
CHANGED
|
@@ -16,6 +16,29 @@ exports.DeviceType = void 0;
|
|
|
16
16
|
const CameraPreview = core.registerPlugin("CameraPreview", {
|
|
17
17
|
web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.CameraPreviewWeb()),
|
|
18
18
|
});
|
|
19
|
+
async function getBase64FromFilePath(filePath) {
|
|
20
|
+
const url = core.Capacitor.convertFileSrc(filePath);
|
|
21
|
+
const response = await fetch(url);
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
throw new Error(`Failed to read file at path: ${filePath} (status ${response.status})`);
|
|
24
|
+
}
|
|
25
|
+
const blob = await response.blob();
|
|
26
|
+
return await new Promise((resolve, reject) => {
|
|
27
|
+
const reader = new FileReader();
|
|
28
|
+
reader.onloadend = () => {
|
|
29
|
+
const dataUrl = reader.result;
|
|
30
|
+
const commaIndex = dataUrl.indexOf(",");
|
|
31
|
+
resolve(commaIndex >= 0 ? dataUrl.substring(commaIndex + 1) : dataUrl);
|
|
32
|
+
};
|
|
33
|
+
reader.onerror = () => reject(reader.error);
|
|
34
|
+
reader.readAsDataURL(blob);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
async function deleteFile(path) {
|
|
38
|
+
// Use native bridge to delete file to handle platform-specific permissions/URIs
|
|
39
|
+
const { success } = await CameraPreview.deleteFile({ path });
|
|
40
|
+
return !!success;
|
|
41
|
+
}
|
|
19
42
|
|
|
20
43
|
const DEFAULT_VIDEO_ID = "capgo_video";
|
|
21
44
|
class CameraPreviewWeb extends core.WebPlugin {
|
|
@@ -29,6 +52,72 @@ class CameraPreviewWeb extends core.WebPlugin {
|
|
|
29
52
|
this.currentDeviceId = null;
|
|
30
53
|
this.videoElement = null;
|
|
31
54
|
this.isStarted = false;
|
|
55
|
+
this.orientationListenerBound = false;
|
|
56
|
+
}
|
|
57
|
+
getCurrentOrientation() {
|
|
58
|
+
var _a, _b;
|
|
59
|
+
try {
|
|
60
|
+
const so = screen.orientation;
|
|
61
|
+
const type = (so === null || so === void 0 ? void 0 : so.type) || (so === null || so === void 0 ? void 0 : so.mozOrientation) || (so === null || so === void 0 ? void 0 : so.msOrientation);
|
|
62
|
+
if (typeof type === "string") {
|
|
63
|
+
if (type.includes("portrait-primary"))
|
|
64
|
+
return "portrait";
|
|
65
|
+
if (type.includes("portrait-secondary"))
|
|
66
|
+
return "portrait-upside-down";
|
|
67
|
+
if (type.includes("landscape-primary"))
|
|
68
|
+
return "landscape-left";
|
|
69
|
+
if (type.includes("landscape-secondary"))
|
|
70
|
+
return "landscape-right";
|
|
71
|
+
if (type.includes("landscape"))
|
|
72
|
+
return "landscape";
|
|
73
|
+
if (type.includes("portrait"))
|
|
74
|
+
return "portrait";
|
|
75
|
+
}
|
|
76
|
+
const angle = window.orientation;
|
|
77
|
+
if (typeof angle === "number") {
|
|
78
|
+
if (angle === 0)
|
|
79
|
+
return "portrait";
|
|
80
|
+
if (angle === 180)
|
|
81
|
+
return "portrait-upside-down";
|
|
82
|
+
if (angle === 90)
|
|
83
|
+
return "landscape-right";
|
|
84
|
+
if (angle === -90)
|
|
85
|
+
return "landscape-left";
|
|
86
|
+
if (angle === 270)
|
|
87
|
+
return "landscape-left";
|
|
88
|
+
}
|
|
89
|
+
if ((_a = window.matchMedia("(orientation: portrait)")) === null || _a === void 0 ? void 0 : _a.matches) {
|
|
90
|
+
return "portrait";
|
|
91
|
+
}
|
|
92
|
+
if ((_b = window.matchMedia("(orientation: landscape)")) === null || _b === void 0 ? void 0 : _b.matches) {
|
|
93
|
+
return "landscape";
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
console.error(e);
|
|
98
|
+
}
|
|
99
|
+
return "unknown";
|
|
100
|
+
}
|
|
101
|
+
ensureOrientationListener() {
|
|
102
|
+
if (this.orientationListenerBound)
|
|
103
|
+
return;
|
|
104
|
+
const emit = () => {
|
|
105
|
+
this.notifyListeners("orientationChange", {
|
|
106
|
+
orientation: this.getCurrentOrientation(),
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
window.addEventListener("orientationchange", emit);
|
|
110
|
+
window.addEventListener("resize", emit);
|
|
111
|
+
this.orientationListenerBound = true;
|
|
112
|
+
}
|
|
113
|
+
async getOrientation() {
|
|
114
|
+
return { orientation: this.getCurrentOrientation() };
|
|
115
|
+
}
|
|
116
|
+
getSafeAreaInsets() {
|
|
117
|
+
throw new Error("Method not implemented.");
|
|
118
|
+
}
|
|
119
|
+
async getZoomButtonValues() {
|
|
120
|
+
throw new Error("getZoomButtonValues not supported under the web platform");
|
|
32
121
|
}
|
|
33
122
|
async getSupportedPictureSizes() {
|
|
34
123
|
throw new Error("getSupportedPictureSizes not supported under the web platform");
|
|
@@ -44,6 +133,7 @@ class CameraPreviewWeb extends core.WebPlugin {
|
|
|
44
133
|
this.isStarted = false;
|
|
45
134
|
const parent = document.getElementById((options === null || options === void 0 ? void 0 : options.parent) || "");
|
|
46
135
|
const gridMode = (options === null || options === void 0 ? void 0 : options.gridMode) || "none";
|
|
136
|
+
const positioning = (options === null || options === void 0 ? void 0 : options.positioning) || "top";
|
|
47
137
|
if (options.position) {
|
|
48
138
|
this.isBackCamera = options.position === "rear";
|
|
49
139
|
}
|
|
@@ -51,7 +141,9 @@ class CameraPreviewWeb extends core.WebPlugin {
|
|
|
51
141
|
if (video) {
|
|
52
142
|
video.remove();
|
|
53
143
|
}
|
|
54
|
-
const container = options.parent
|
|
144
|
+
const container = options.parent
|
|
145
|
+
? document.getElementById(options.parent)
|
|
146
|
+
: document.body;
|
|
55
147
|
if (!container) {
|
|
56
148
|
throw new Error("container not found");
|
|
57
149
|
}
|
|
@@ -61,45 +153,57 @@ class CameraPreviewWeb extends core.WebPlugin {
|
|
|
61
153
|
this.videoElement.playsInline = true;
|
|
62
154
|
this.videoElement.muted = true;
|
|
63
155
|
this.videoElement.autoplay = true;
|
|
156
|
+
// Remove objectFit as we'll match camera's native aspect ratio
|
|
157
|
+
this.videoElement.style.backgroundColor = "transparent";
|
|
158
|
+
// Reset any default margins that might interfere
|
|
159
|
+
this.videoElement.style.margin = "0";
|
|
160
|
+
this.videoElement.style.padding = "0";
|
|
64
161
|
container.appendChild(this.videoElement);
|
|
65
162
|
if (options.toBack) {
|
|
66
163
|
this.videoElement.style.zIndex = "-1";
|
|
67
164
|
}
|
|
165
|
+
// Default to 4:3 if no aspect ratio or size specified
|
|
166
|
+
const useDefaultAspectRatio = !options.aspectRatio && !options.width && !options.height;
|
|
167
|
+
const effectiveAspectRatio = options.aspectRatio || (useDefaultAspectRatio ? "4:3" : null);
|
|
68
168
|
if (options.width) {
|
|
69
169
|
this.videoElement.width = options.width;
|
|
170
|
+
this.videoElement.style.width = `${options.width}px`;
|
|
70
171
|
}
|
|
71
172
|
if (options.height) {
|
|
72
173
|
this.videoElement.height = options.height;
|
|
174
|
+
this.videoElement.style.height = `${options.height}px`;
|
|
73
175
|
}
|
|
74
|
-
if
|
|
176
|
+
// Handle positioning - center if x or y not provided
|
|
177
|
+
const centerX = options.x === undefined;
|
|
178
|
+
const centerY = options.y === undefined;
|
|
179
|
+
// Always set position to absolute for proper positioning
|
|
180
|
+
this.videoElement.style.position = "absolute";
|
|
181
|
+
console.log("Initial positioning flags:", {
|
|
182
|
+
centerX,
|
|
183
|
+
centerY,
|
|
184
|
+
x: options.x,
|
|
185
|
+
y: options.y,
|
|
186
|
+
});
|
|
187
|
+
if (options.x !== undefined) {
|
|
75
188
|
this.videoElement.style.left = `${options.x}px`;
|
|
76
189
|
}
|
|
190
|
+
if (options.y !== undefined) {
|
|
191
|
+
this.videoElement.style.top = `${options.y}px`;
|
|
192
|
+
}
|
|
77
193
|
// Create and add grid overlay if needed
|
|
78
194
|
if (gridMode !== "none") {
|
|
79
195
|
const gridOverlay = this.createGridOverlay(gridMode);
|
|
80
196
|
gridOverlay.id = "camera-grid-overlay";
|
|
81
197
|
parent === null || parent === void 0 ? void 0 : parent.appendChild(gridOverlay);
|
|
82
198
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (options.width) {
|
|
90
|
-
this.videoElement.height = options.width / ratio;
|
|
91
|
-
}
|
|
92
|
-
else if (options.height) {
|
|
93
|
-
this.videoElement.width = options.height * ratio;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
this.videoElement.style.objectFit = 'cover';
|
|
98
|
-
}
|
|
199
|
+
// Aspect ratio handling is now done after getting camera stream
|
|
200
|
+
// Store centering flags for later use
|
|
201
|
+
const needsCenterX = centerX;
|
|
202
|
+
const needsCenterY = centerY;
|
|
203
|
+
console.log("Centering flags stored:", { needsCenterX, needsCenterY });
|
|
204
|
+
// First get the camera stream with basic constraints
|
|
99
205
|
const constraints = {
|
|
100
206
|
video: {
|
|
101
|
-
width: { ideal: this.videoElement.width || 640 },
|
|
102
|
-
height: { ideal: this.videoElement.height || window.innerHeight },
|
|
103
207
|
facingMode: this.isBackCamera ? "environment" : "user",
|
|
104
208
|
},
|
|
105
209
|
};
|
|
@@ -110,16 +214,226 @@ class CameraPreviewWeb extends core.WebPlugin {
|
|
|
110
214
|
if (!this.videoElement) {
|
|
111
215
|
throw new Error("video element not found");
|
|
112
216
|
}
|
|
217
|
+
// Get the actual camera dimensions from the video track
|
|
218
|
+
const videoTrack = stream.getVideoTracks()[0];
|
|
219
|
+
const settings = videoTrack.getSettings();
|
|
220
|
+
const cameraWidth = settings.width || 640;
|
|
221
|
+
const cameraHeight = settings.height || 480;
|
|
222
|
+
const cameraAspectRatio = cameraWidth / cameraHeight;
|
|
223
|
+
console.log("Camera native dimensions:", {
|
|
224
|
+
width: cameraWidth,
|
|
225
|
+
height: cameraHeight,
|
|
226
|
+
aspectRatio: cameraAspectRatio,
|
|
227
|
+
});
|
|
228
|
+
console.log("Container dimensions:", {
|
|
229
|
+
width: container.offsetWidth,
|
|
230
|
+
height: container.offsetHeight,
|
|
231
|
+
id: container.id,
|
|
232
|
+
});
|
|
233
|
+
// Now adjust video element size based on camera's native aspect ratio
|
|
234
|
+
if (!options.width && !options.height && !options.aspectRatio) {
|
|
235
|
+
// No size specified, fit camera view within container bounds
|
|
236
|
+
const containerWidth = container.offsetWidth || window.innerWidth;
|
|
237
|
+
const containerHeight = container.offsetHeight || window.innerHeight;
|
|
238
|
+
// Calculate dimensions that fit within container while maintaining camera aspect ratio
|
|
239
|
+
let targetWidth, targetHeight;
|
|
240
|
+
// Try fitting to container width first
|
|
241
|
+
targetWidth = containerWidth;
|
|
242
|
+
targetHeight = targetWidth / cameraAspectRatio;
|
|
243
|
+
// If height exceeds container, fit to height instead
|
|
244
|
+
if (targetHeight > containerHeight) {
|
|
245
|
+
targetHeight = containerHeight;
|
|
246
|
+
targetWidth = targetHeight * cameraAspectRatio;
|
|
247
|
+
}
|
|
248
|
+
console.log("Video element dimensions:", {
|
|
249
|
+
width: targetWidth,
|
|
250
|
+
height: targetHeight,
|
|
251
|
+
container: { width: containerWidth, height: containerHeight },
|
|
252
|
+
});
|
|
253
|
+
this.videoElement.width = targetWidth;
|
|
254
|
+
this.videoElement.height = targetHeight;
|
|
255
|
+
this.videoElement.style.width = `${targetWidth}px`;
|
|
256
|
+
this.videoElement.style.height = `${targetHeight}px`;
|
|
257
|
+
// Center the video element within its parent container
|
|
258
|
+
if (needsCenterX || options.x === undefined) {
|
|
259
|
+
const x = Math.round((containerWidth - targetWidth) / 2);
|
|
260
|
+
this.videoElement.style.left = `${x}px`;
|
|
261
|
+
}
|
|
262
|
+
if (needsCenterY || options.y === undefined) {
|
|
263
|
+
let y;
|
|
264
|
+
switch (positioning) {
|
|
265
|
+
case "top":
|
|
266
|
+
y = 0;
|
|
267
|
+
break;
|
|
268
|
+
case "bottom":
|
|
269
|
+
y = window.innerHeight - targetHeight;
|
|
270
|
+
break;
|
|
271
|
+
case "center":
|
|
272
|
+
default:
|
|
273
|
+
y = Math.round((window.innerHeight - targetHeight) / 2);
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
this.videoElement.style.setProperty("top", `${y}px`, "important");
|
|
277
|
+
// Force a style recalculation
|
|
278
|
+
this.videoElement.offsetHeight;
|
|
279
|
+
console.log("Positioning video:", {
|
|
280
|
+
positioning,
|
|
281
|
+
viewportHeight: window.innerHeight,
|
|
282
|
+
targetHeight,
|
|
283
|
+
calculatedY: y,
|
|
284
|
+
actualTop: this.videoElement.style.top,
|
|
285
|
+
position: this.videoElement.style.position,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
else if (effectiveAspectRatio && !options.width && !options.height) {
|
|
290
|
+
// Aspect ratio specified but no size
|
|
291
|
+
const [widthRatio, heightRatio] = effectiveAspectRatio
|
|
292
|
+
.split(":")
|
|
293
|
+
.map(Number);
|
|
294
|
+
const targetRatio = widthRatio / heightRatio;
|
|
295
|
+
const viewportWidth = window.innerWidth;
|
|
296
|
+
const viewportHeight = window.innerHeight;
|
|
297
|
+
let targetWidth, targetHeight;
|
|
298
|
+
// Try fitting to viewport width first
|
|
299
|
+
targetWidth = viewportWidth;
|
|
300
|
+
targetHeight = targetWidth / targetRatio;
|
|
301
|
+
// If height exceeds viewport, fit to height instead
|
|
302
|
+
if (targetHeight > viewportHeight) {
|
|
303
|
+
targetHeight = viewportHeight;
|
|
304
|
+
targetWidth = targetHeight * targetRatio;
|
|
305
|
+
}
|
|
306
|
+
this.videoElement.width = targetWidth;
|
|
307
|
+
this.videoElement.height = targetHeight;
|
|
308
|
+
this.videoElement.style.width = `${targetWidth}px`;
|
|
309
|
+
this.videoElement.style.height = `${targetHeight}px`;
|
|
310
|
+
// Center the video element within its parent container
|
|
311
|
+
if (needsCenterX || options.x === undefined) {
|
|
312
|
+
const parentWidth = container.offsetWidth || viewportWidth;
|
|
313
|
+
const x = Math.round((parentWidth - targetWidth) / 2);
|
|
314
|
+
this.videoElement.style.left = `${x}px`;
|
|
315
|
+
}
|
|
316
|
+
if (needsCenterY || options.y === undefined) {
|
|
317
|
+
const parentHeight = container.offsetHeight || viewportHeight;
|
|
318
|
+
let y;
|
|
319
|
+
switch (positioning) {
|
|
320
|
+
case "top":
|
|
321
|
+
y = 0;
|
|
322
|
+
break;
|
|
323
|
+
case "bottom":
|
|
324
|
+
y = parentHeight - targetHeight;
|
|
325
|
+
break;
|
|
326
|
+
case "center":
|
|
327
|
+
default:
|
|
328
|
+
y = Math.round((parentHeight - targetHeight) / 2);
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
this.videoElement.style.top = `${y}px`;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
113
334
|
this.videoElement.srcObject = stream;
|
|
114
335
|
if (!this.isBackCamera) {
|
|
115
336
|
this.videoElement.style.transform = "scaleX(-1)";
|
|
116
337
|
}
|
|
338
|
+
// Set initial zoom level if specified and supported
|
|
339
|
+
if (options.initialZoomLevel !== undefined &&
|
|
340
|
+
options.initialZoomLevel !== 1.0) {
|
|
341
|
+
// videoTrack already declared above
|
|
342
|
+
if (videoTrack) {
|
|
343
|
+
const capabilities = videoTrack.getCapabilities();
|
|
344
|
+
if (capabilities.zoom) {
|
|
345
|
+
const zoomLevel = options.initialZoomLevel;
|
|
346
|
+
const minZoom = capabilities.zoom.min || 1;
|
|
347
|
+
const maxZoom = capabilities.zoom.max || 1;
|
|
348
|
+
if (zoomLevel < minZoom || zoomLevel > maxZoom) {
|
|
349
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
350
|
+
throw new Error(`Initial zoom level ${zoomLevel} is not available. Valid range is ${minZoom} to ${maxZoom}`);
|
|
351
|
+
}
|
|
352
|
+
try {
|
|
353
|
+
await videoTrack.applyConstraints({
|
|
354
|
+
advanced: [{ zoom: zoomLevel }],
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
catch (error) {
|
|
358
|
+
console.warn(`Failed to set initial zoom level: ${error}`);
|
|
359
|
+
// Don't throw, just continue without zoom
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
117
364
|
this.isStarted = true;
|
|
365
|
+
this.ensureOrientationListener();
|
|
366
|
+
// Wait for video to be ready and get actual dimensions
|
|
367
|
+
await new Promise((resolve) => {
|
|
368
|
+
const videoEl = this.videoElement;
|
|
369
|
+
if (!videoEl) {
|
|
370
|
+
throw new Error("video element not found");
|
|
371
|
+
}
|
|
372
|
+
if (videoEl.readyState >= 2) {
|
|
373
|
+
resolve();
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
videoEl.addEventListener("loadeddata", () => resolve(), {
|
|
377
|
+
once: true,
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
// Ensure centering is applied after DOM updates
|
|
382
|
+
await new Promise((resolve) => requestAnimationFrame(resolve));
|
|
383
|
+
console.log("About to re-center, flags:", { needsCenterX, needsCenterY });
|
|
384
|
+
// Re-apply centering with correct parent dimensions
|
|
385
|
+
if (needsCenterX) {
|
|
386
|
+
const parentWidth = container.offsetWidth;
|
|
387
|
+
const x = Math.round((parentWidth - this.videoElement.offsetWidth) / 2);
|
|
388
|
+
this.videoElement.style.left = `${x}px`;
|
|
389
|
+
console.log("Re-centering X:", {
|
|
390
|
+
parentWidth,
|
|
391
|
+
videoWidth: this.videoElement.offsetWidth,
|
|
392
|
+
x,
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
if (needsCenterY) {
|
|
396
|
+
let y;
|
|
397
|
+
switch (positioning) {
|
|
398
|
+
case "top":
|
|
399
|
+
y = 0;
|
|
400
|
+
break;
|
|
401
|
+
case "bottom":
|
|
402
|
+
y = window.innerHeight - this.videoElement.offsetHeight;
|
|
403
|
+
break;
|
|
404
|
+
case "center":
|
|
405
|
+
default:
|
|
406
|
+
y = Math.round((window.innerHeight - this.videoElement.offsetHeight) / 2);
|
|
407
|
+
break;
|
|
408
|
+
}
|
|
409
|
+
this.videoElement.style.setProperty("top", `${y}px`, "important");
|
|
410
|
+
console.log("Re-positioning Y:", {
|
|
411
|
+
positioning,
|
|
412
|
+
viewportHeight: window.innerHeight,
|
|
413
|
+
videoHeight: this.videoElement.offsetHeight,
|
|
414
|
+
y,
|
|
415
|
+
position: this.videoElement.style.position,
|
|
416
|
+
top: this.videoElement.style.top,
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
// Get the actual rendered dimensions after video is loaded
|
|
420
|
+
const rect = this.videoElement.getBoundingClientRect();
|
|
421
|
+
const computedStyle = window.getComputedStyle(this.videoElement);
|
|
422
|
+
console.log("Final video element state:", {
|
|
423
|
+
rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
|
|
424
|
+
style: {
|
|
425
|
+
position: computedStyle.position,
|
|
426
|
+
left: computedStyle.left,
|
|
427
|
+
top: computedStyle.top,
|
|
428
|
+
width: computedStyle.width,
|
|
429
|
+
height: computedStyle.height,
|
|
430
|
+
},
|
|
431
|
+
});
|
|
118
432
|
return {
|
|
119
|
-
width:
|
|
120
|
-
height:
|
|
121
|
-
x:
|
|
122
|
-
y:
|
|
433
|
+
width: Math.round(rect.width),
|
|
434
|
+
height: Math.round(rect.height),
|
|
435
|
+
x: Math.round(rect.x),
|
|
436
|
+
y: Math.round(rect.y),
|
|
123
437
|
};
|
|
124
438
|
}
|
|
125
439
|
stopStream(stream) {
|
|
@@ -153,14 +467,57 @@ class CameraPreviewWeb extends core.WebPlugin {
|
|
|
153
467
|
if (video && video.videoWidth > 0 && video.videoHeight > 0) {
|
|
154
468
|
const canvas = document.createElement("canvas");
|
|
155
469
|
const context = canvas.getContext("2d");
|
|
156
|
-
|
|
157
|
-
|
|
470
|
+
// Calculate capture dimensions
|
|
471
|
+
let captureWidth = video.videoWidth;
|
|
472
|
+
let captureHeight = video.videoHeight;
|
|
473
|
+
let sourceX = 0;
|
|
474
|
+
let sourceY = 0;
|
|
475
|
+
// Check for conflicting parameters
|
|
476
|
+
if (options.aspectRatio && (options.width || options.height)) {
|
|
477
|
+
reject(new Error("Cannot set both aspectRatio and size (width/height). Use setPreviewSize after start."));
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
// Handle aspect ratio if no width/height specified
|
|
481
|
+
if (!options.width && !options.height && options.aspectRatio) {
|
|
482
|
+
const [widthRatio, heightRatio] = options.aspectRatio
|
|
483
|
+
.split(":")
|
|
484
|
+
.map(Number);
|
|
485
|
+
if (widthRatio && heightRatio) {
|
|
486
|
+
// For capture in portrait orientation, swap the aspect ratio (16:9 becomes 9:16)
|
|
487
|
+
const isPortrait = video.videoHeight > video.videoWidth;
|
|
488
|
+
const targetAspectRatio = isPortrait
|
|
489
|
+
? heightRatio / widthRatio
|
|
490
|
+
: widthRatio / heightRatio;
|
|
491
|
+
const videoAspectRatio = video.videoWidth / video.videoHeight;
|
|
492
|
+
if (videoAspectRatio > targetAspectRatio) {
|
|
493
|
+
// Video is wider than target - crop sides
|
|
494
|
+
captureWidth = video.videoHeight * targetAspectRatio;
|
|
495
|
+
captureHeight = video.videoHeight;
|
|
496
|
+
sourceX = (video.videoWidth - captureWidth) / 2;
|
|
497
|
+
}
|
|
498
|
+
else {
|
|
499
|
+
// Video is taller than target - crop top/bottom
|
|
500
|
+
captureWidth = video.videoWidth;
|
|
501
|
+
captureHeight = video.videoWidth / targetAspectRatio;
|
|
502
|
+
sourceY = (video.videoHeight - captureHeight) / 2;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
else if (options.width || options.height) {
|
|
507
|
+
// If width or height is specified, use them
|
|
508
|
+
if (options.width)
|
|
509
|
+
captureWidth = options.width;
|
|
510
|
+
if (options.height)
|
|
511
|
+
captureHeight = options.height;
|
|
512
|
+
}
|
|
513
|
+
canvas.width = captureWidth;
|
|
514
|
+
canvas.height = captureHeight;
|
|
158
515
|
// flip horizontally back camera isn't used
|
|
159
516
|
if (!this.isBackCamera) {
|
|
160
|
-
context === null || context === void 0 ? void 0 : context.translate(
|
|
517
|
+
context === null || context === void 0 ? void 0 : context.translate(captureWidth, 0);
|
|
161
518
|
context === null || context === void 0 ? void 0 : context.scale(-1, 1);
|
|
162
519
|
}
|
|
163
|
-
context === null || context === void 0 ? void 0 : context.drawImage(video, 0, 0,
|
|
520
|
+
context === null || context === void 0 ? void 0 : context.drawImage(video, sourceX, sourceY, captureWidth, captureHeight, 0, 0, captureWidth, captureHeight);
|
|
164
521
|
if (options.saveToGallery) ;
|
|
165
522
|
if (options.withExifLocation) ;
|
|
166
523
|
if ((options.format || "jpeg") === "jpeg") {
|
|
@@ -254,7 +611,7 @@ class CameraPreviewWeb extends core.WebPlugin {
|
|
|
254
611
|
throw new Error("getAvailableDevices not supported under the web platform");
|
|
255
612
|
}
|
|
256
613
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
257
|
-
const videoDevices = devices.filter(device => device.kind ===
|
|
614
|
+
const videoDevices = devices.filter((device) => device.kind === "videoinput");
|
|
258
615
|
// Group devices by position (front/back)
|
|
259
616
|
const frontDevices = [];
|
|
260
617
|
const backDevices = [];
|
|
@@ -264,15 +621,19 @@ class CameraPreviewWeb extends core.WebPlugin {
|
|
|
264
621
|
// Determine device type based on label
|
|
265
622
|
let deviceType = exports.DeviceType.WIDE_ANGLE;
|
|
266
623
|
let baseZoomRatio = 1.0;
|
|
267
|
-
if (labelLower.includes(
|
|
624
|
+
if (labelLower.includes("ultra") || labelLower.includes("0.5")) {
|
|
268
625
|
deviceType = exports.DeviceType.ULTRA_WIDE;
|
|
269
626
|
baseZoomRatio = 0.5;
|
|
270
627
|
}
|
|
271
|
-
else if (labelLower.includes(
|
|
628
|
+
else if (labelLower.includes("telephoto") ||
|
|
629
|
+
labelLower.includes("tele") ||
|
|
630
|
+
labelLower.includes("2x") ||
|
|
631
|
+
labelLower.includes("3x")) {
|
|
272
632
|
deviceType = exports.DeviceType.TELEPHOTO;
|
|
273
633
|
baseZoomRatio = 2.0;
|
|
274
634
|
}
|
|
275
|
-
else if (labelLower.includes(
|
|
635
|
+
else if (labelLower.includes("depth") ||
|
|
636
|
+
labelLower.includes("truedepth")) {
|
|
276
637
|
deviceType = exports.DeviceType.TRUE_DEPTH;
|
|
277
638
|
baseZoomRatio = 1.0;
|
|
278
639
|
}
|
|
@@ -283,10 +644,10 @@ class CameraPreviewWeb extends core.WebPlugin {
|
|
|
283
644
|
focalLength: 4.25,
|
|
284
645
|
baseZoomRatio,
|
|
285
646
|
minZoom: 1.0,
|
|
286
|
-
maxZoom: 1.0
|
|
647
|
+
maxZoom: 1.0,
|
|
287
648
|
};
|
|
288
649
|
// Determine position and add to appropriate array
|
|
289
|
-
if (labelLower.includes(
|
|
650
|
+
if (labelLower.includes("back") || labelLower.includes("rear")) {
|
|
290
651
|
backDevices.push(lensInfo);
|
|
291
652
|
}
|
|
292
653
|
else {
|
|
@@ -301,8 +662,8 @@ class CameraPreviewWeb extends core.WebPlugin {
|
|
|
301
662
|
position: "front",
|
|
302
663
|
lenses: frontDevices,
|
|
303
664
|
isLogical: false,
|
|
304
|
-
minZoom: Math.min(...frontDevices.map(d => d.minZoom)),
|
|
305
|
-
maxZoom: Math.max(...frontDevices.map(d => d.maxZoom))
|
|
665
|
+
minZoom: Math.min(...frontDevices.map((d) => d.minZoom)),
|
|
666
|
+
maxZoom: Math.max(...frontDevices.map((d) => d.maxZoom)),
|
|
306
667
|
});
|
|
307
668
|
}
|
|
308
669
|
if (backDevices.length > 0) {
|
|
@@ -312,8 +673,8 @@ class CameraPreviewWeb extends core.WebPlugin {
|
|
|
312
673
|
position: "rear",
|
|
313
674
|
lenses: backDevices,
|
|
314
675
|
isLogical: false,
|
|
315
|
-
minZoom: Math.min(...backDevices.map(d => d.minZoom)),
|
|
316
|
-
maxZoom: Math.max(...backDevices.map(d => d.maxZoom))
|
|
676
|
+
minZoom: Math.min(...backDevices.map((d) => d.minZoom)),
|
|
677
|
+
maxZoom: Math.max(...backDevices.map((d) => d.maxZoom)),
|
|
317
678
|
});
|
|
318
679
|
}
|
|
319
680
|
return { devices: result };
|
|
@@ -338,18 +699,22 @@ class CameraPreviewWeb extends core.WebPlugin {
|
|
|
338
699
|
let baseZoomRatio = 1.0;
|
|
339
700
|
if (this.currentDeviceId) {
|
|
340
701
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
341
|
-
const device = devices.find(d => d.deviceId === this.currentDeviceId);
|
|
702
|
+
const device = devices.find((d) => d.deviceId === this.currentDeviceId);
|
|
342
703
|
if (device) {
|
|
343
704
|
const labelLower = device.label.toLowerCase();
|
|
344
|
-
if (labelLower.includes(
|
|
705
|
+
if (labelLower.includes("ultra") || labelLower.includes("0.5")) {
|
|
345
706
|
deviceType = exports.DeviceType.ULTRA_WIDE;
|
|
346
707
|
baseZoomRatio = 0.5;
|
|
347
708
|
}
|
|
348
|
-
else if (labelLower.includes(
|
|
709
|
+
else if (labelLower.includes("telephoto") ||
|
|
710
|
+
labelLower.includes("tele") ||
|
|
711
|
+
labelLower.includes("2x") ||
|
|
712
|
+
labelLower.includes("3x")) {
|
|
349
713
|
deviceType = exports.DeviceType.TELEPHOTO;
|
|
350
714
|
baseZoomRatio = 2.0;
|
|
351
715
|
}
|
|
352
|
-
else if (labelLower.includes(
|
|
716
|
+
else if (labelLower.includes("depth") ||
|
|
717
|
+
labelLower.includes("truedepth")) {
|
|
353
718
|
deviceType = exports.DeviceType.TRUE_DEPTH;
|
|
354
719
|
baseZoomRatio = 1.0;
|
|
355
720
|
}
|
|
@@ -360,7 +725,7 @@ class CameraPreviewWeb extends core.WebPlugin {
|
|
|
360
725
|
focalLength: 4.25,
|
|
361
726
|
deviceType,
|
|
362
727
|
baseZoomRatio,
|
|
363
|
-
digitalZoom: currentZoom / baseZoomRatio
|
|
728
|
+
digitalZoom: currentZoom / baseZoomRatio,
|
|
364
729
|
};
|
|
365
730
|
return {
|
|
366
731
|
min: capabilities.zoom.min || 1,
|
|
@@ -384,9 +749,10 @@ class CameraPreviewWeb extends core.WebPlugin {
|
|
|
384
749
|
throw new Error("zoom not supported by this device");
|
|
385
750
|
}
|
|
386
751
|
const zoomLevel = Math.max(capabilities.zoom.min || 1, Math.min(capabilities.zoom.max || 1, options.level));
|
|
752
|
+
// Note: autoFocus is not supported on web platform
|
|
387
753
|
try {
|
|
388
754
|
await videoTrack.applyConstraints({
|
|
389
|
-
advanced: [{ zoom: zoomLevel }]
|
|
755
|
+
advanced: [{ zoom: zoomLevel }],
|
|
390
756
|
});
|
|
391
757
|
}
|
|
392
758
|
catch (error) {
|
|
@@ -419,8 +785,11 @@ class CameraPreviewWeb extends core.WebPlugin {
|
|
|
419
785
|
try {
|
|
420
786
|
// Try to determine camera position from device
|
|
421
787
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
422
|
-
const device = devices.find(d => d.deviceId === options.deviceId);
|
|
423
|
-
this.isBackCamera =
|
|
788
|
+
const device = devices.find((d) => d.deviceId === options.deviceId);
|
|
789
|
+
this.isBackCamera =
|
|
790
|
+
(device === null || device === void 0 ? void 0 : device.label.toLowerCase().includes("back")) ||
|
|
791
|
+
(device === null || device === void 0 ? void 0 : device.label.toLowerCase().includes("rear")) ||
|
|
792
|
+
false;
|
|
424
793
|
const stream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
425
794
|
video.srcObject = stream;
|
|
426
795
|
// Update video transform based on camera
|
|
@@ -448,15 +817,15 @@ class CameraPreviewWeb extends core.WebPlugin {
|
|
|
448
817
|
if (width && height) {
|
|
449
818
|
const ratio = width / height;
|
|
450
819
|
// Check for portrait camera ratios: 4:3 -> 3:4, 16:9 -> 9:16
|
|
451
|
-
if (Math.abs(ratio -
|
|
452
|
-
return { aspectRatio:
|
|
820
|
+
if (Math.abs(ratio - 3 / 4) < 0.01) {
|
|
821
|
+
return { aspectRatio: "4:3" };
|
|
453
822
|
}
|
|
454
|
-
if (Math.abs(ratio -
|
|
455
|
-
return { aspectRatio:
|
|
823
|
+
if (Math.abs(ratio - 9 / 16) < 0.01) {
|
|
824
|
+
return { aspectRatio: "16:9" };
|
|
456
825
|
}
|
|
457
826
|
}
|
|
458
827
|
// Default to 4:3 if no specific aspect ratio is matched
|
|
459
|
-
return { aspectRatio:
|
|
828
|
+
return { aspectRatio: "4:3" };
|
|
460
829
|
}
|
|
461
830
|
async setAspectRatio(options) {
|
|
462
831
|
const video = document.getElementById(DEFAULT_VIDEO_ID);
|
|
@@ -464,7 +833,9 @@ class CameraPreviewWeb extends core.WebPlugin {
|
|
|
464
833
|
throw new Error("camera is not running");
|
|
465
834
|
}
|
|
466
835
|
if (options.aspectRatio) {
|
|
467
|
-
const [widthRatio, heightRatio] = options.aspectRatio
|
|
836
|
+
const [widthRatio, heightRatio] = options.aspectRatio
|
|
837
|
+
.split(":")
|
|
838
|
+
.map(Number);
|
|
468
839
|
// For camera, use portrait orientation: 4:3 becomes 3:4, 16:9 becomes 9:16
|
|
469
840
|
const ratio = heightRatio / widthRatio;
|
|
470
841
|
// Get current position and size
|
|
@@ -500,22 +871,26 @@ class CameraPreviewWeb extends core.WebPlugin {
|
|
|
500
871
|
video.style.height = `${newHeight}px`;
|
|
501
872
|
video.style.left = `${x}px`;
|
|
502
873
|
video.style.top = `${y}px`;
|
|
503
|
-
video.style.position =
|
|
874
|
+
video.style.position = "absolute";
|
|
875
|
+
const offsetX = newWidth / 8;
|
|
876
|
+
const offsetY = newHeight / 8;
|
|
504
877
|
return {
|
|
505
878
|
width: Math.round(newWidth),
|
|
506
879
|
height: Math.round(newHeight),
|
|
507
|
-
x: Math.round(x),
|
|
508
|
-
y: Math.round(y)
|
|
880
|
+
x: Math.round(x + offsetX),
|
|
881
|
+
y: Math.round(y + offsetY),
|
|
509
882
|
};
|
|
510
883
|
}
|
|
511
884
|
else {
|
|
512
|
-
video.style.objectFit =
|
|
885
|
+
video.style.objectFit = "cover";
|
|
513
886
|
const rect = video.getBoundingClientRect();
|
|
887
|
+
const offsetX = rect.width / 8;
|
|
888
|
+
const offsetY = rect.height / 8;
|
|
514
889
|
return {
|
|
515
890
|
width: Math.round(rect.width),
|
|
516
891
|
height: Math.round(rect.height),
|
|
517
|
-
x: Math.round(rect.left),
|
|
518
|
-
y: Math.round(rect.top)
|
|
892
|
+
x: Math.round(rect.left + offsetX),
|
|
893
|
+
y: Math.round(rect.top + offsetY),
|
|
519
894
|
};
|
|
520
895
|
}
|
|
521
896
|
}
|
|
@@ -567,18 +942,20 @@ class CameraPreviewWeb extends core.WebPlugin {
|
|
|
567
942
|
}
|
|
568
943
|
async getGridMode() {
|
|
569
944
|
// Web implementation - default to none
|
|
570
|
-
return { gridMode:
|
|
945
|
+
return { gridMode: "none" };
|
|
571
946
|
}
|
|
572
947
|
async getPreviewSize() {
|
|
573
948
|
const video = document.getElementById(DEFAULT_VIDEO_ID);
|
|
574
949
|
if (!video) {
|
|
575
950
|
throw new Error("camera is not running");
|
|
576
951
|
}
|
|
952
|
+
const offsetX = video.width / 8;
|
|
953
|
+
const offsetY = video.height / 8;
|
|
577
954
|
return {
|
|
578
|
-
x: video.offsetLeft,
|
|
579
|
-
y: video.offsetTop,
|
|
955
|
+
x: video.offsetLeft + offsetX,
|
|
956
|
+
y: video.offsetTop + offsetY,
|
|
580
957
|
width: video.width,
|
|
581
|
-
height: video.height
|
|
958
|
+
height: video.height,
|
|
582
959
|
};
|
|
583
960
|
}
|
|
584
961
|
async setPreviewSize(options) {
|
|
@@ -590,13 +967,55 @@ class CameraPreviewWeb extends core.WebPlugin {
|
|
|
590
967
|
video.style.top = `${options.y}px`;
|
|
591
968
|
video.width = options.width;
|
|
592
969
|
video.height = options.height;
|
|
970
|
+
const offsetX = options.width / 8;
|
|
971
|
+
const offsetY = options.height / 8;
|
|
593
972
|
return {
|
|
594
973
|
width: options.width,
|
|
595
974
|
height: options.height,
|
|
596
|
-
x: options.x,
|
|
597
|
-
y: options.y
|
|
975
|
+
x: options.x + offsetX,
|
|
976
|
+
y: options.y + offsetY,
|
|
598
977
|
};
|
|
599
978
|
}
|
|
979
|
+
async setFocus(options) {
|
|
980
|
+
// Reject if values are outside 0-1 range
|
|
981
|
+
if (options.x < 0 || options.x > 1 || options.y < 0 || options.y > 1) {
|
|
982
|
+
throw new Error("Focus coordinates must be between 0 and 1");
|
|
983
|
+
}
|
|
984
|
+
const video = document.getElementById(DEFAULT_VIDEO_ID);
|
|
985
|
+
if (!(video === null || video === void 0 ? void 0 : video.srcObject)) {
|
|
986
|
+
throw new Error("camera is not running");
|
|
987
|
+
}
|
|
988
|
+
const stream = video.srcObject;
|
|
989
|
+
const videoTrack = stream.getVideoTracks()[0];
|
|
990
|
+
if (!videoTrack) {
|
|
991
|
+
throw new Error("no video track found");
|
|
992
|
+
}
|
|
993
|
+
const capabilities = videoTrack.getCapabilities();
|
|
994
|
+
// Check if focusing is supported
|
|
995
|
+
if (capabilities.focusMode) {
|
|
996
|
+
try {
|
|
997
|
+
// Web API supports focus mode settings but not coordinate-based focus
|
|
998
|
+
// Setting to manual mode allows for coordinate focus if supported
|
|
999
|
+
await videoTrack.applyConstraints({
|
|
1000
|
+
advanced: [
|
|
1001
|
+
{
|
|
1002
|
+
focusMode: "manual",
|
|
1003
|
+
focusDistance: 0.5, // Mid-range focus as fallback
|
|
1004
|
+
},
|
|
1005
|
+
],
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
catch (error) {
|
|
1009
|
+
console.warn(`setFocus is not fully supported on this device: ${error}. Focus coordinates (${options.x}, ${options.y}) were provided but cannot be applied.`);
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
else {
|
|
1013
|
+
console.warn("Focus control is not supported on this device. Focus coordinates were provided but cannot be applied.");
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
async deleteFile(_options) {
|
|
1017
|
+
throw new Error("deleteFile not supported under the web platform");
|
|
1018
|
+
}
|
|
600
1019
|
}
|
|
601
1020
|
|
|
602
1021
|
var web = /*#__PURE__*/Object.freeze({
|
|
@@ -605,4 +1024,6 @@ var web = /*#__PURE__*/Object.freeze({
|
|
|
605
1024
|
});
|
|
606
1025
|
|
|
607
1026
|
exports.CameraPreview = CameraPreview;
|
|
1027
|
+
exports.deleteFile = deleteFile;
|
|
1028
|
+
exports.getBase64FromFilePath = getBase64FromFilePath;
|
|
608
1029
|
//# sourceMappingURL=plugin.cjs.js.map
|