@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.
Files changed (35) hide show
  1. package/README.md +195 -31
  2. package/android/.gradle/8.14.2/checksums/checksums.lock +0 -0
  3. package/android/.gradle/8.14.2/checksums/md5-checksums.bin +0 -0
  4. package/android/.gradle/8.14.2/checksums/sha1-checksums.bin +0 -0
  5. package/android/.gradle/8.14.2/executionHistory/executionHistory.bin +0 -0
  6. package/android/.gradle/8.14.2/executionHistory/executionHistory.lock +0 -0
  7. package/android/.gradle/8.14.2/fileHashes/fileHashes.bin +0 -0
  8. package/android/.gradle/8.14.2/fileHashes/fileHashes.lock +0 -0
  9. package/android/.gradle/8.14.2/fileHashes/resourceHashesCache.bin +0 -0
  10. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  11. package/android/.gradle/file-system.probe +0 -0
  12. package/android/build.gradle +3 -1
  13. package/android/src/main/AndroidManifest.xml +5 -3
  14. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +473 -88
  15. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +2065 -704
  16. package/android/src/main/java/com/ahm/capacitor/camera/preview/GridOverlayView.java +95 -0
  17. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraDevice.java +55 -46
  18. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraLens.java +61 -52
  19. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraSessionConfiguration.java +152 -59
  20. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/LensInfo.java +29 -23
  21. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/ZoomFactors.java +24 -23
  22. package/dist/docs.json +235 -6
  23. package/dist/esm/definitions.d.ts +119 -3
  24. package/dist/esm/definitions.js.map +1 -1
  25. package/dist/esm/web.d.ts +47 -3
  26. package/dist/esm/web.js +297 -96
  27. package/dist/esm/web.js.map +1 -1
  28. package/dist/plugin.cjs.js +293 -96
  29. package/dist/plugin.cjs.js.map +1 -1
  30. package/dist/plugin.js +293 -96
  31. package/dist/plugin.js.map +1 -1
  32. package/ios/Sources/CapgoCameraPreview/CameraController.swift +364 -218
  33. package/ios/Sources/CapgoCameraPreview/GridOverlayView.swift +65 -0
  34. package/ios/Sources/CapgoCameraPreview/Plugin.swift +886 -242
  35. 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
- var _a;
18
- await navigator.mediaDevices
19
- .getUserMedia({
20
- audio: !options.disableAudio,
21
- video: true,
22
- })
23
- .then((stream) => {
24
- // Stop any existing stream so we can request media with different constraints based on user input
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
- if (!video) {
33
- const videoElement = document.createElement("video");
34
- videoElement.id = "video";
35
- videoElement.setAttribute("class", (options === null || options === void 0 ? void 0 : options.className) || "");
36
- // Don't flip video feed if camera is rear facing
37
- if (options.position !== "rear") {
38
- videoElement.setAttribute("style", "-webkit-transform: scaleX(-1); transform: scaleX(-1);");
39
- }
40
- const userAgent = navigator.userAgent.toLowerCase();
41
- const isSafari = userAgent.includes("safari") && !userAgent.includes("chrome");
42
- // Safari on iOS needs to have the autoplay, muted and playsinline attributes set for video.play() to be successful
43
- // Without these attributes videoElement.play() will throw a NotAllowedError
44
- // https://developer.apple.com/documentation/webkit/delivering_video_content_for_safari
45
- if (isSafari) {
46
- videoElement.setAttribute("autoplay", "true");
47
- videoElement.setAttribute("muted", "true");
48
- videoElement.setAttribute("playsinline", "true");
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
- parent === null || parent === void 0 ? void 0 : parent.appendChild(videoElement);
51
- if ((_a = navigator === null || navigator === void 0 ? void 0 : navigator.mediaDevices) === null || _a === void 0 ? void 0 : _a.getUserMedia) {
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
- Promise.reject(new Error("camera already started"));
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("video");
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("video");
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("video");
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("video");
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("video");
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 === 'videoinput');
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('ultra') || labelLower.includes('0.5')) {
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('telephoto') || labelLower.includes('tele') || labelLower.includes('2x') || labelLower.includes('3x')) {
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('depth') || labelLower.includes('truedepth')) {
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('back') || labelLower.includes('rear')) {
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("video");
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('ultra') || labelLower.includes('0.5')) {
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('telephoto') || labelLower.includes('tele') || labelLower.includes('2x') || labelLower.includes('3x')) {
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('depth') || labelLower.includes('truedepth')) {
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("video");
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("video");
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 = (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;
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