@capgo/camera-preview 7.4.0-beta.1 → 7.4.0-beta.11
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 +195 -31
- 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/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/build.gradle +3 -1
- package/android/src/main/AndroidManifest.xml +5 -3
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +473 -88
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +2065 -704
- package/android/src/main/java/com/ahm/capacitor/camera/preview/GridOverlayView.java +95 -0
- 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 +152 -59
- 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 +235 -6
- package/dist/esm/definitions.d.ts +119 -3
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +47 -3
- package/dist/esm/web.js +297 -96
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +293 -96
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +293 -96
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapgoCameraPreview/CameraController.swift +364 -218
- package/ios/Sources/CapgoCameraPreview/GridOverlayView.swift +65 -0
- package/ios/Sources/CapgoCameraPreview/Plugin.swift +886 -242
- package/package.json +1 -1
package/dist/esm/web.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { WebPlugin } from "@capacitor/core";
|
|
2
2
|
import { DeviceType } from "./definitions";
|
|
3
|
+
const DEFAULT_VIDEO_ID = "capgo_video";
|
|
3
4
|
export class CameraPreviewWeb extends WebPlugin {
|
|
4
5
|
constructor() {
|
|
5
6
|
super();
|
|
@@ -9,87 +10,104 @@ export class CameraPreviewWeb extends WebPlugin {
|
|
|
9
10
|
*/
|
|
10
11
|
this.isBackCamera = false;
|
|
11
12
|
this.currentDeviceId = null;
|
|
13
|
+
this.videoElement = null;
|
|
14
|
+
this.isStarted = false;
|
|
12
15
|
}
|
|
13
16
|
async getSupportedPictureSizes() {
|
|
14
17
|
throw new Error("getSupportedPictureSizes not supported under the web platform");
|
|
15
18
|
}
|
|
16
19
|
async start(options) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
stream.getTracks().forEach((track) => track.stop());
|
|
26
|
-
})
|
|
27
|
-
.catch((error) => {
|
|
28
|
-
Promise.reject(error);
|
|
29
|
-
});
|
|
30
|
-
const video = document.getElementById("video");
|
|
20
|
+
if (options.aspectRatio && (options.width || options.height)) {
|
|
21
|
+
throw new Error("Cannot set both aspectRatio and size (width/height). Use setPreviewSize after start.");
|
|
22
|
+
}
|
|
23
|
+
if (this.isStarted) {
|
|
24
|
+
throw new Error("camera already started");
|
|
25
|
+
}
|
|
26
|
+
this.isBackCamera = true;
|
|
27
|
+
this.isStarted = false;
|
|
31
28
|
const parent = document.getElementById((options === null || options === void 0 ? void 0 : options.parent) || "");
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
29
|
+
const gridMode = (options === null || options === void 0 ? void 0 : options.gridMode) || "none";
|
|
30
|
+
if (options.position) {
|
|
31
|
+
this.isBackCamera = options.position === "rear";
|
|
32
|
+
}
|
|
33
|
+
const video = document.getElementById(DEFAULT_VIDEO_ID);
|
|
34
|
+
if (video) {
|
|
35
|
+
video.remove();
|
|
36
|
+
}
|
|
37
|
+
const container = options.parent
|
|
38
|
+
? document.getElementById(options.parent)
|
|
39
|
+
: document.body;
|
|
40
|
+
if (!container) {
|
|
41
|
+
throw new Error("container not found");
|
|
42
|
+
}
|
|
43
|
+
this.videoElement = document.createElement("video");
|
|
44
|
+
this.videoElement.id = DEFAULT_VIDEO_ID;
|
|
45
|
+
this.videoElement.className = options.className || "";
|
|
46
|
+
this.videoElement.playsInline = true;
|
|
47
|
+
this.videoElement.muted = true;
|
|
48
|
+
this.videoElement.autoplay = true;
|
|
49
|
+
container.appendChild(this.videoElement);
|
|
50
|
+
if (options.toBack) {
|
|
51
|
+
this.videoElement.style.zIndex = "-1";
|
|
52
|
+
}
|
|
53
|
+
if (options.width) {
|
|
54
|
+
this.videoElement.width = options.width;
|
|
55
|
+
}
|
|
56
|
+
if (options.height) {
|
|
57
|
+
this.videoElement.height = options.height;
|
|
58
|
+
}
|
|
59
|
+
if (options.x) {
|
|
60
|
+
this.videoElement.style.left = `${options.x}px`;
|
|
61
|
+
}
|
|
62
|
+
// Create and add grid overlay if needed
|
|
63
|
+
if (gridMode !== "none") {
|
|
64
|
+
const gridOverlay = this.createGridOverlay(gridMode);
|
|
65
|
+
gridOverlay.id = "camera-grid-overlay";
|
|
66
|
+
parent === null || parent === void 0 ? void 0 : parent.appendChild(gridOverlay);
|
|
67
|
+
}
|
|
68
|
+
if (options.y) {
|
|
69
|
+
this.videoElement.style.top = `${options.y}px`;
|
|
70
|
+
}
|
|
71
|
+
if (options.aspectRatio) {
|
|
72
|
+
const [widthRatio, heightRatio] = options.aspectRatio
|
|
73
|
+
.split(":")
|
|
74
|
+
.map(Number);
|
|
75
|
+
const ratio = widthRatio / heightRatio;
|
|
76
|
+
if (options.width) {
|
|
77
|
+
this.videoElement.height = options.width / ratio;
|
|
49
78
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const constraints = {
|
|
53
|
-
video: {
|
|
54
|
-
width: { ideal: options.width },
|
|
55
|
-
height: { ideal: options.height },
|
|
56
|
-
},
|
|
57
|
-
};
|
|
58
|
-
if (options.deviceId) {
|
|
59
|
-
constraints.video.deviceId = { exact: options.deviceId };
|
|
60
|
-
this.currentDeviceId = options.deviceId;
|
|
61
|
-
// Try to determine camera position from device
|
|
62
|
-
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
63
|
-
const device = devices.find(d => d.deviceId === options.deviceId);
|
|
64
|
-
this.isBackCamera = (device === null || device === void 0 ? void 0 : device.label.toLowerCase().includes('back')) || (device === null || device === void 0 ? void 0 : device.label.toLowerCase().includes('rear')) || false;
|
|
65
|
-
}
|
|
66
|
-
else if (options.position === "rear") {
|
|
67
|
-
constraints.video.facingMode = "environment";
|
|
68
|
-
this.isBackCamera = true;
|
|
69
|
-
}
|
|
70
|
-
else {
|
|
71
|
-
this.isBackCamera = false;
|
|
72
|
-
}
|
|
73
|
-
const self = this;
|
|
74
|
-
await navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
|
|
75
|
-
if (document.getElementById("video")) {
|
|
76
|
-
// video.src = window.URL.createObjectURL(stream);
|
|
77
|
-
videoElement.srcObject = stream;
|
|
78
|
-
videoElement.play();
|
|
79
|
-
Promise.resolve({});
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
self.stopStream(stream);
|
|
83
|
-
Promise.reject(new Error("camera already stopped"));
|
|
84
|
-
}
|
|
85
|
-
}, (err) => {
|
|
86
|
-
Promise.reject(new Error(err));
|
|
87
|
-
});
|
|
79
|
+
else if (options.height) {
|
|
80
|
+
this.videoElement.width = options.height * ratio;
|
|
88
81
|
}
|
|
89
82
|
}
|
|
90
83
|
else {
|
|
91
|
-
|
|
84
|
+
this.videoElement.style.objectFit = "cover";
|
|
85
|
+
}
|
|
86
|
+
const constraints = {
|
|
87
|
+
video: {
|
|
88
|
+
width: { ideal: this.videoElement.width || 640 },
|
|
89
|
+
height: { ideal: this.videoElement.height || window.innerHeight },
|
|
90
|
+
facingMode: this.isBackCamera ? "environment" : "user",
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
const stream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
94
|
+
if (!stream) {
|
|
95
|
+
throw new Error("could not acquire stream");
|
|
92
96
|
}
|
|
97
|
+
if (!this.videoElement) {
|
|
98
|
+
throw new Error("video element not found");
|
|
99
|
+
}
|
|
100
|
+
this.videoElement.srcObject = stream;
|
|
101
|
+
if (!this.isBackCamera) {
|
|
102
|
+
this.videoElement.style.transform = "scaleX(-1)";
|
|
103
|
+
}
|
|
104
|
+
this.isStarted = true;
|
|
105
|
+
return {
|
|
106
|
+
width: this.videoElement.width,
|
|
107
|
+
height: this.videoElement.height,
|
|
108
|
+
x: this.videoElement.getBoundingClientRect().x,
|
|
109
|
+
y: this.videoElement.getBoundingClientRect().y,
|
|
110
|
+
};
|
|
93
111
|
}
|
|
94
112
|
stopStream(stream) {
|
|
95
113
|
if (stream) {
|
|
@@ -99,16 +117,20 @@ export class CameraPreviewWeb extends WebPlugin {
|
|
|
99
117
|
}
|
|
100
118
|
}
|
|
101
119
|
async stop() {
|
|
102
|
-
const video = document.getElementById(
|
|
120
|
+
const video = document.getElementById(DEFAULT_VIDEO_ID);
|
|
103
121
|
if (video) {
|
|
104
122
|
video.pause();
|
|
105
123
|
this.stopStream(video.srcObject);
|
|
106
124
|
video.remove();
|
|
125
|
+
this.isStarted = false;
|
|
107
126
|
}
|
|
127
|
+
// Remove grid overlay if it exists
|
|
128
|
+
const gridOverlay = document.getElementById("camera-grid-overlay");
|
|
129
|
+
gridOverlay === null || gridOverlay === void 0 ? void 0 : gridOverlay.remove();
|
|
108
130
|
}
|
|
109
131
|
async capture(options) {
|
|
110
132
|
return new Promise((resolve, reject) => {
|
|
111
|
-
const video = document.getElementById(
|
|
133
|
+
const video = document.getElementById(DEFAULT_VIDEO_ID);
|
|
112
134
|
if (!(video === null || video === void 0 ? void 0 : video.srcObject)) {
|
|
113
135
|
reject(new Error("camera is not running"));
|
|
114
136
|
return;
|
|
@@ -126,6 +148,12 @@ export class CameraPreviewWeb extends WebPlugin {
|
|
|
126
148
|
context === null || context === void 0 ? void 0 : context.scale(-1, 1);
|
|
127
149
|
}
|
|
128
150
|
context === null || context === void 0 ? void 0 : context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
|
|
151
|
+
if (options.saveToGallery) {
|
|
152
|
+
// saveToGallery is not supported on web
|
|
153
|
+
}
|
|
154
|
+
if (options.withExifLocation) {
|
|
155
|
+
// withExifLocation is not supported on web
|
|
156
|
+
}
|
|
129
157
|
if ((options.format || "jpeg") === "jpeg") {
|
|
130
158
|
base64EncodedImage = canvas
|
|
131
159
|
.toDataURL("image/jpeg", (options.quality || 85) / 100.0)
|
|
@@ -139,6 +167,7 @@ export class CameraPreviewWeb extends WebPlugin {
|
|
|
139
167
|
}
|
|
140
168
|
resolve({
|
|
141
169
|
value: base64EncodedImage,
|
|
170
|
+
exif: {},
|
|
142
171
|
});
|
|
143
172
|
});
|
|
144
173
|
}
|
|
@@ -162,7 +191,7 @@ export class CameraPreviewWeb extends WebPlugin {
|
|
|
162
191
|
throw new Error(`setFlashMode not supported under the web platform${_options}`);
|
|
163
192
|
}
|
|
164
193
|
async flip() {
|
|
165
|
-
const video = document.getElementById(
|
|
194
|
+
const video = document.getElementById(DEFAULT_VIDEO_ID);
|
|
166
195
|
if (!(video === null || video === void 0 ? void 0 : video.srcObject)) {
|
|
167
196
|
throw new Error("camera is not running");
|
|
168
197
|
}
|
|
@@ -202,12 +231,12 @@ export class CameraPreviewWeb extends WebPlugin {
|
|
|
202
231
|
}
|
|
203
232
|
}
|
|
204
233
|
async setOpacity(_options) {
|
|
205
|
-
const video = document.getElementById(
|
|
234
|
+
const video = document.getElementById(DEFAULT_VIDEO_ID);
|
|
206
235
|
if (!!video && !!_options.opacity)
|
|
207
236
|
video.style.setProperty("opacity", _options.opacity.toString());
|
|
208
237
|
}
|
|
209
238
|
async isRunning() {
|
|
210
|
-
const video = document.getElementById(
|
|
239
|
+
const video = document.getElementById(DEFAULT_VIDEO_ID);
|
|
211
240
|
return { isRunning: !!video && !!video.srcObject };
|
|
212
241
|
}
|
|
213
242
|
async getAvailableDevices() {
|
|
@@ -216,7 +245,7 @@ export class CameraPreviewWeb extends WebPlugin {
|
|
|
216
245
|
throw new Error("getAvailableDevices not supported under the web platform");
|
|
217
246
|
}
|
|
218
247
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
219
|
-
const videoDevices = devices.filter(device => device.kind ===
|
|
248
|
+
const videoDevices = devices.filter((device) => device.kind === "videoinput");
|
|
220
249
|
// Group devices by position (front/back)
|
|
221
250
|
const frontDevices = [];
|
|
222
251
|
const backDevices = [];
|
|
@@ -226,15 +255,19 @@ export class CameraPreviewWeb extends WebPlugin {
|
|
|
226
255
|
// Determine device type based on label
|
|
227
256
|
let deviceType = DeviceType.WIDE_ANGLE;
|
|
228
257
|
let baseZoomRatio = 1.0;
|
|
229
|
-
if (labelLower.includes(
|
|
258
|
+
if (labelLower.includes("ultra") || labelLower.includes("0.5")) {
|
|
230
259
|
deviceType = DeviceType.ULTRA_WIDE;
|
|
231
260
|
baseZoomRatio = 0.5;
|
|
232
261
|
}
|
|
233
|
-
else if (labelLower.includes(
|
|
262
|
+
else if (labelLower.includes("telephoto") ||
|
|
263
|
+
labelLower.includes("tele") ||
|
|
264
|
+
labelLower.includes("2x") ||
|
|
265
|
+
labelLower.includes("3x")) {
|
|
234
266
|
deviceType = DeviceType.TELEPHOTO;
|
|
235
267
|
baseZoomRatio = 2.0;
|
|
236
268
|
}
|
|
237
|
-
else if (labelLower.includes(
|
|
269
|
+
else if (labelLower.includes("depth") ||
|
|
270
|
+
labelLower.includes("truedepth")) {
|
|
238
271
|
deviceType = DeviceType.TRUE_DEPTH;
|
|
239
272
|
baseZoomRatio = 1.0;
|
|
240
273
|
}
|
|
@@ -245,10 +278,10 @@ export class CameraPreviewWeb extends WebPlugin {
|
|
|
245
278
|
focalLength: 4.25,
|
|
246
279
|
baseZoomRatio,
|
|
247
280
|
minZoom: 1.0,
|
|
248
|
-
maxZoom: 1.0
|
|
281
|
+
maxZoom: 1.0,
|
|
249
282
|
};
|
|
250
283
|
// Determine position and add to appropriate array
|
|
251
|
-
if (labelLower.includes(
|
|
284
|
+
if (labelLower.includes("back") || labelLower.includes("rear")) {
|
|
252
285
|
backDevices.push(lensInfo);
|
|
253
286
|
}
|
|
254
287
|
else {
|
|
@@ -263,8 +296,8 @@ export class CameraPreviewWeb extends WebPlugin {
|
|
|
263
296
|
position: "front",
|
|
264
297
|
lenses: frontDevices,
|
|
265
298
|
isLogical: false,
|
|
266
|
-
minZoom: Math.min(...frontDevices.map(d => d.minZoom)),
|
|
267
|
-
maxZoom: Math.max(...frontDevices.map(d => d.maxZoom))
|
|
299
|
+
minZoom: Math.min(...frontDevices.map((d) => d.minZoom)),
|
|
300
|
+
maxZoom: Math.max(...frontDevices.map((d) => d.maxZoom)),
|
|
268
301
|
});
|
|
269
302
|
}
|
|
270
303
|
if (backDevices.length > 0) {
|
|
@@ -274,14 +307,14 @@ export class CameraPreviewWeb extends WebPlugin {
|
|
|
274
307
|
position: "rear",
|
|
275
308
|
lenses: backDevices,
|
|
276
309
|
isLogical: false,
|
|
277
|
-
minZoom: Math.min(...backDevices.map(d => d.minZoom)),
|
|
278
|
-
maxZoom: Math.max(...backDevices.map(d => d.maxZoom))
|
|
310
|
+
minZoom: Math.min(...backDevices.map((d) => d.minZoom)),
|
|
311
|
+
maxZoom: Math.max(...backDevices.map((d) => d.maxZoom)),
|
|
279
312
|
});
|
|
280
313
|
}
|
|
281
314
|
return { devices: result };
|
|
282
315
|
}
|
|
283
316
|
async getZoom() {
|
|
284
|
-
const video = document.getElementById(
|
|
317
|
+
const video = document.getElementById(DEFAULT_VIDEO_ID);
|
|
285
318
|
if (!(video === null || video === void 0 ? void 0 : video.srcObject)) {
|
|
286
319
|
throw new Error("camera is not running");
|
|
287
320
|
}
|
|
@@ -300,18 +333,22 @@ export class CameraPreviewWeb extends WebPlugin {
|
|
|
300
333
|
let baseZoomRatio = 1.0;
|
|
301
334
|
if (this.currentDeviceId) {
|
|
302
335
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
303
|
-
const device = devices.find(d => d.deviceId === this.currentDeviceId);
|
|
336
|
+
const device = devices.find((d) => d.deviceId === this.currentDeviceId);
|
|
304
337
|
if (device) {
|
|
305
338
|
const labelLower = device.label.toLowerCase();
|
|
306
|
-
if (labelLower.includes(
|
|
339
|
+
if (labelLower.includes("ultra") || labelLower.includes("0.5")) {
|
|
307
340
|
deviceType = DeviceType.ULTRA_WIDE;
|
|
308
341
|
baseZoomRatio = 0.5;
|
|
309
342
|
}
|
|
310
|
-
else if (labelLower.includes(
|
|
343
|
+
else if (labelLower.includes("telephoto") ||
|
|
344
|
+
labelLower.includes("tele") ||
|
|
345
|
+
labelLower.includes("2x") ||
|
|
346
|
+
labelLower.includes("3x")) {
|
|
311
347
|
deviceType = DeviceType.TELEPHOTO;
|
|
312
348
|
baseZoomRatio = 2.0;
|
|
313
349
|
}
|
|
314
|
-
else if (labelLower.includes(
|
|
350
|
+
else if (labelLower.includes("depth") ||
|
|
351
|
+
labelLower.includes("truedepth")) {
|
|
315
352
|
deviceType = DeviceType.TRUE_DEPTH;
|
|
316
353
|
baseZoomRatio = 1.0;
|
|
317
354
|
}
|
|
@@ -322,7 +359,7 @@ export class CameraPreviewWeb extends WebPlugin {
|
|
|
322
359
|
focalLength: 4.25,
|
|
323
360
|
deviceType,
|
|
324
361
|
baseZoomRatio,
|
|
325
|
-
digitalZoom: currentZoom / baseZoomRatio
|
|
362
|
+
digitalZoom: currentZoom / baseZoomRatio,
|
|
326
363
|
};
|
|
327
364
|
return {
|
|
328
365
|
min: capabilities.zoom.min || 1,
|
|
@@ -332,7 +369,7 @@ export class CameraPreviewWeb extends WebPlugin {
|
|
|
332
369
|
};
|
|
333
370
|
}
|
|
334
371
|
async setZoom(options) {
|
|
335
|
-
const video = document.getElementById(
|
|
372
|
+
const video = document.getElementById(DEFAULT_VIDEO_ID);
|
|
336
373
|
if (!(video === null || video === void 0 ? void 0 : video.srcObject)) {
|
|
337
374
|
throw new Error("camera is not running");
|
|
338
375
|
}
|
|
@@ -348,7 +385,7 @@ export class CameraPreviewWeb extends WebPlugin {
|
|
|
348
385
|
const zoomLevel = Math.max(capabilities.zoom.min || 1, Math.min(capabilities.zoom.max || 1, options.level));
|
|
349
386
|
try {
|
|
350
387
|
await videoTrack.applyConstraints({
|
|
351
|
-
advanced: [{ zoom: zoomLevel }]
|
|
388
|
+
advanced: [{ zoom: zoomLevel }],
|
|
352
389
|
});
|
|
353
390
|
}
|
|
354
391
|
catch (error) {
|
|
@@ -362,7 +399,7 @@ export class CameraPreviewWeb extends WebPlugin {
|
|
|
362
399
|
return { deviceId: this.currentDeviceId || "" };
|
|
363
400
|
}
|
|
364
401
|
async setDeviceId(options) {
|
|
365
|
-
const video = document.getElementById(
|
|
402
|
+
const video = document.getElementById(DEFAULT_VIDEO_ID);
|
|
366
403
|
if (!(video === null || video === void 0 ? void 0 : video.srcObject)) {
|
|
367
404
|
throw new Error("camera is not running");
|
|
368
405
|
}
|
|
@@ -381,8 +418,11 @@ export class CameraPreviewWeb extends WebPlugin {
|
|
|
381
418
|
try {
|
|
382
419
|
// Try to determine camera position from device
|
|
383
420
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
384
|
-
const device = devices.find(d => d.deviceId === options.deviceId);
|
|
385
|
-
this.isBackCamera =
|
|
421
|
+
const device = devices.find((d) => d.deviceId === options.deviceId);
|
|
422
|
+
this.isBackCamera =
|
|
423
|
+
(device === null || device === void 0 ? void 0 : device.label.toLowerCase().includes("back")) ||
|
|
424
|
+
(device === null || device === void 0 ? void 0 : device.label.toLowerCase().includes("rear")) ||
|
|
425
|
+
false;
|
|
386
426
|
const stream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
387
427
|
video.srcObject = stream;
|
|
388
428
|
// Update video transform based on camera
|
|
@@ -400,5 +440,166 @@ export class CameraPreviewWeb extends WebPlugin {
|
|
|
400
440
|
throw new Error(`Failed to swap to device ${options.deviceId}: ${error}`);
|
|
401
441
|
}
|
|
402
442
|
}
|
|
443
|
+
async getAspectRatio() {
|
|
444
|
+
const video = document.getElementById(DEFAULT_VIDEO_ID);
|
|
445
|
+
if (!video) {
|
|
446
|
+
throw new Error("camera is not running");
|
|
447
|
+
}
|
|
448
|
+
const width = video.offsetWidth;
|
|
449
|
+
const height = video.offsetHeight;
|
|
450
|
+
if (width && height) {
|
|
451
|
+
const ratio = width / height;
|
|
452
|
+
// Check for portrait camera ratios: 4:3 -> 3:4, 16:9 -> 9:16
|
|
453
|
+
if (Math.abs(ratio - 3 / 4) < 0.01) {
|
|
454
|
+
return { aspectRatio: "4:3" };
|
|
455
|
+
}
|
|
456
|
+
if (Math.abs(ratio - 9 / 16) < 0.01) {
|
|
457
|
+
return { aspectRatio: "16:9" };
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
// Default to 4:3 if no specific aspect ratio is matched
|
|
461
|
+
return { aspectRatio: "4:3" };
|
|
462
|
+
}
|
|
463
|
+
async setAspectRatio(options) {
|
|
464
|
+
const video = document.getElementById(DEFAULT_VIDEO_ID);
|
|
465
|
+
if (!video) {
|
|
466
|
+
throw new Error("camera is not running");
|
|
467
|
+
}
|
|
468
|
+
if (options.aspectRatio) {
|
|
469
|
+
const [widthRatio, heightRatio] = options.aspectRatio
|
|
470
|
+
.split(":")
|
|
471
|
+
.map(Number);
|
|
472
|
+
// For camera, use portrait orientation: 4:3 becomes 3:4, 16:9 becomes 9:16
|
|
473
|
+
const ratio = heightRatio / widthRatio;
|
|
474
|
+
// Get current position and size
|
|
475
|
+
const rect = video.getBoundingClientRect();
|
|
476
|
+
const currentWidth = rect.width;
|
|
477
|
+
const currentHeight = rect.height;
|
|
478
|
+
const currentRatio = currentWidth / currentHeight;
|
|
479
|
+
let newWidth;
|
|
480
|
+
let newHeight;
|
|
481
|
+
if (currentRatio > ratio) {
|
|
482
|
+
// Width is larger, fit by height and center horizontally
|
|
483
|
+
newWidth = currentHeight * ratio;
|
|
484
|
+
newHeight = currentHeight;
|
|
485
|
+
}
|
|
486
|
+
else {
|
|
487
|
+
// Height is larger, fit by width and center vertically
|
|
488
|
+
newWidth = currentWidth;
|
|
489
|
+
newHeight = currentWidth / ratio;
|
|
490
|
+
}
|
|
491
|
+
// Calculate position
|
|
492
|
+
let x, y;
|
|
493
|
+
if (options.x !== undefined && options.y !== undefined) {
|
|
494
|
+
// Use provided coordinates, ensuring they stay within screen boundaries
|
|
495
|
+
x = Math.max(0, Math.min(options.x, window.innerWidth - newWidth));
|
|
496
|
+
y = Math.max(0, Math.min(options.y, window.innerHeight - newHeight));
|
|
497
|
+
}
|
|
498
|
+
else {
|
|
499
|
+
// Auto-center the view
|
|
500
|
+
x = (window.innerWidth - newWidth) / 2;
|
|
501
|
+
y = (window.innerHeight - newHeight) / 2;
|
|
502
|
+
}
|
|
503
|
+
video.style.width = `${newWidth}px`;
|
|
504
|
+
video.style.height = `${newHeight}px`;
|
|
505
|
+
video.style.left = `${x}px`;
|
|
506
|
+
video.style.top = `${y}px`;
|
|
507
|
+
video.style.position = "absolute";
|
|
508
|
+
return {
|
|
509
|
+
width: Math.round(newWidth),
|
|
510
|
+
height: Math.round(newHeight),
|
|
511
|
+
x: Math.round(x),
|
|
512
|
+
y: Math.round(y),
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
else {
|
|
516
|
+
video.style.objectFit = "cover";
|
|
517
|
+
const rect = video.getBoundingClientRect();
|
|
518
|
+
return {
|
|
519
|
+
width: Math.round(rect.width),
|
|
520
|
+
height: Math.round(rect.height),
|
|
521
|
+
x: Math.round(rect.left),
|
|
522
|
+
y: Math.round(rect.top),
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
createGridOverlay(gridMode) {
|
|
527
|
+
const overlay = document.createElement("div");
|
|
528
|
+
overlay.style.position = "absolute";
|
|
529
|
+
overlay.style.top = "0";
|
|
530
|
+
overlay.style.left = "0";
|
|
531
|
+
overlay.style.width = "100%";
|
|
532
|
+
overlay.style.height = "100%";
|
|
533
|
+
overlay.style.pointerEvents = "none";
|
|
534
|
+
overlay.style.zIndex = "10";
|
|
535
|
+
const divisions = gridMode === "3x3" ? 3 : 4;
|
|
536
|
+
// Create SVG for grid lines
|
|
537
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
538
|
+
svg.style.width = "100%";
|
|
539
|
+
svg.style.height = "100%";
|
|
540
|
+
svg.style.position = "absolute";
|
|
541
|
+
svg.style.top = "0";
|
|
542
|
+
svg.style.left = "0";
|
|
543
|
+
// Create grid lines
|
|
544
|
+
for (let i = 1; i < divisions; i++) {
|
|
545
|
+
// Vertical lines
|
|
546
|
+
const verticalLine = document.createElementNS("http://www.w3.org/2000/svg", "line");
|
|
547
|
+
verticalLine.setAttribute("x1", `${(i / divisions) * 100}%`);
|
|
548
|
+
verticalLine.setAttribute("y1", "0%");
|
|
549
|
+
verticalLine.setAttribute("x2", `${(i / divisions) * 100}%`);
|
|
550
|
+
verticalLine.setAttribute("y2", "100%");
|
|
551
|
+
verticalLine.setAttribute("stroke", "rgba(255, 255, 255, 0.5)");
|
|
552
|
+
verticalLine.setAttribute("stroke-width", "1");
|
|
553
|
+
svg.appendChild(verticalLine);
|
|
554
|
+
// Horizontal lines
|
|
555
|
+
const horizontalLine = document.createElementNS("http://www.w3.org/2000/svg", "line");
|
|
556
|
+
horizontalLine.setAttribute("x1", "0%");
|
|
557
|
+
horizontalLine.setAttribute("y1", `${(i / divisions) * 100}%`);
|
|
558
|
+
horizontalLine.setAttribute("x2", "100%");
|
|
559
|
+
horizontalLine.setAttribute("y2", `${(i / divisions) * 100}%`);
|
|
560
|
+
horizontalLine.setAttribute("stroke", "rgba(255, 255, 255, 0.5)");
|
|
561
|
+
horizontalLine.setAttribute("stroke-width", "1");
|
|
562
|
+
svg.appendChild(horizontalLine);
|
|
563
|
+
}
|
|
564
|
+
overlay.appendChild(svg);
|
|
565
|
+
return overlay;
|
|
566
|
+
}
|
|
567
|
+
async setGridMode(options) {
|
|
568
|
+
// Web implementation of grid mode would need to be implemented
|
|
569
|
+
// For now, just resolve as a no-op
|
|
570
|
+
console.warn(`Grid mode '${options.gridMode}' is not yet implemented for web platform`);
|
|
571
|
+
}
|
|
572
|
+
async getGridMode() {
|
|
573
|
+
// Web implementation - default to none
|
|
574
|
+
return { gridMode: "none" };
|
|
575
|
+
}
|
|
576
|
+
async getPreviewSize() {
|
|
577
|
+
const video = document.getElementById(DEFAULT_VIDEO_ID);
|
|
578
|
+
if (!video) {
|
|
579
|
+
throw new Error("camera is not running");
|
|
580
|
+
}
|
|
581
|
+
return {
|
|
582
|
+
x: video.offsetLeft,
|
|
583
|
+
y: video.offsetTop,
|
|
584
|
+
width: video.width,
|
|
585
|
+
height: video.height,
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
async setPreviewSize(options) {
|
|
589
|
+
const video = document.getElementById(DEFAULT_VIDEO_ID);
|
|
590
|
+
if (!video) {
|
|
591
|
+
throw new Error("camera is not running");
|
|
592
|
+
}
|
|
593
|
+
video.style.left = `${options.x}px`;
|
|
594
|
+
video.style.top = `${options.y}px`;
|
|
595
|
+
video.width = options.width;
|
|
596
|
+
video.height = options.height;
|
|
597
|
+
return {
|
|
598
|
+
width: options.width,
|
|
599
|
+
height: options.height,
|
|
600
|
+
x: options.x,
|
|
601
|
+
y: options.y,
|
|
602
|
+
};
|
|
603
|
+
}
|
|
403
604
|
}
|
|
404
605
|
//# sourceMappingURL=web.js.map
|