@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
@@ -17,6 +17,7 @@ const CameraPreview = core.registerPlugin("CameraPreview", {
17
17
  web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.CameraPreviewWeb()),
18
18
  });
19
19
 
20
+ const DEFAULT_VIDEO_ID = "capgo_video";
20
21
  class CameraPreviewWeb extends core.WebPlugin {
21
22
  constructor() {
22
23
  super();
@@ -26,87 +27,104 @@ class CameraPreviewWeb extends core.WebPlugin {
26
27
  */
27
28
  this.isBackCamera = false;
28
29
  this.currentDeviceId = null;
30
+ this.videoElement = null;
31
+ this.isStarted = false;
29
32
  }
30
33
  async getSupportedPictureSizes() {
31
34
  throw new Error("getSupportedPictureSizes not supported under the web platform");
32
35
  }
33
36
  async start(options) {
34
- var _a;
35
- await navigator.mediaDevices
36
- .getUserMedia({
37
- audio: !options.disableAudio,
38
- video: true,
39
- })
40
- .then((stream) => {
41
- // Stop any existing stream so we can request media with different constraints based on user input
42
- stream.getTracks().forEach((track) => track.stop());
43
- })
44
- .catch((error) => {
45
- Promise.reject(error);
46
- });
47
- const video = document.getElementById("video");
37
+ if (options.aspectRatio && (options.width || options.height)) {
38
+ throw new Error("Cannot set both aspectRatio and size (width/height). Use setPreviewSize after start.");
39
+ }
40
+ if (this.isStarted) {
41
+ throw new Error("camera already started");
42
+ }
43
+ this.isBackCamera = true;
44
+ this.isStarted = false;
48
45
  const parent = document.getElementById((options === null || options === void 0 ? void 0 : options.parent) || "");
49
- if (!video) {
50
- const videoElement = document.createElement("video");
51
- videoElement.id = "video";
52
- videoElement.setAttribute("class", (options === null || options === void 0 ? void 0 : options.className) || "");
53
- // Don't flip video feed if camera is rear facing
54
- if (options.position !== "rear") {
55
- videoElement.setAttribute("style", "-webkit-transform: scaleX(-1); transform: scaleX(-1);");
46
+ const gridMode = (options === null || options === void 0 ? void 0 : options.gridMode) || "none";
47
+ if (options.position) {
48
+ this.isBackCamera = options.position === "rear";
49
+ }
50
+ const video = document.getElementById(DEFAULT_VIDEO_ID);
51
+ if (video) {
52
+ video.remove();
53
+ }
54
+ const container = options.parent
55
+ ? document.getElementById(options.parent)
56
+ : document.body;
57
+ if (!container) {
58
+ throw new Error("container not found");
59
+ }
60
+ this.videoElement = document.createElement("video");
61
+ this.videoElement.id = DEFAULT_VIDEO_ID;
62
+ this.videoElement.className = options.className || "";
63
+ this.videoElement.playsInline = true;
64
+ this.videoElement.muted = true;
65
+ this.videoElement.autoplay = true;
66
+ container.appendChild(this.videoElement);
67
+ if (options.toBack) {
68
+ this.videoElement.style.zIndex = "-1";
69
+ }
70
+ if (options.width) {
71
+ this.videoElement.width = options.width;
72
+ }
73
+ if (options.height) {
74
+ this.videoElement.height = options.height;
75
+ }
76
+ if (options.x) {
77
+ this.videoElement.style.left = `${options.x}px`;
78
+ }
79
+ // Create and add grid overlay if needed
80
+ if (gridMode !== "none") {
81
+ const gridOverlay = this.createGridOverlay(gridMode);
82
+ gridOverlay.id = "camera-grid-overlay";
83
+ parent === null || parent === void 0 ? void 0 : parent.appendChild(gridOverlay);
84
+ }
85
+ if (options.y) {
86
+ this.videoElement.style.top = `${options.y}px`;
87
+ }
88
+ if (options.aspectRatio) {
89
+ const [widthRatio, heightRatio] = options.aspectRatio
90
+ .split(":")
91
+ .map(Number);
92
+ const ratio = widthRatio / heightRatio;
93
+ if (options.width) {
94
+ this.videoElement.height = options.width / ratio;
56
95
  }
57
- const userAgent = navigator.userAgent.toLowerCase();
58
- const isSafari = userAgent.includes("safari") && !userAgent.includes("chrome");
59
- // Safari on iOS needs to have the autoplay, muted and playsinline attributes set for video.play() to be successful
60
- // Without these attributes videoElement.play() will throw a NotAllowedError
61
- // https://developer.apple.com/documentation/webkit/delivering_video_content_for_safari
62
- if (isSafari) {
63
- videoElement.setAttribute("autoplay", "true");
64
- videoElement.setAttribute("muted", "true");
65
- videoElement.setAttribute("playsinline", "true");
66
- }
67
- parent === null || parent === void 0 ? void 0 : parent.appendChild(videoElement);
68
- if ((_a = navigator === null || navigator === void 0 ? void 0 : navigator.mediaDevices) === null || _a === void 0 ? void 0 : _a.getUserMedia) {
69
- const constraints = {
70
- video: {
71
- width: { ideal: options.width },
72
- height: { ideal: options.height },
73
- },
74
- };
75
- if (options.deviceId) {
76
- constraints.video.deviceId = { exact: options.deviceId };
77
- this.currentDeviceId = options.deviceId;
78
- // Try to determine camera position from device
79
- const devices = await navigator.mediaDevices.enumerateDevices();
80
- const device = devices.find(d => d.deviceId === options.deviceId);
81
- 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;
82
- }
83
- else if (options.position === "rear") {
84
- constraints.video.facingMode = "environment";
85
- this.isBackCamera = true;
86
- }
87
- else {
88
- this.isBackCamera = false;
89
- }
90
- const self = this;
91
- await navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
92
- if (document.getElementById("video")) {
93
- // video.src = window.URL.createObjectURL(stream);
94
- videoElement.srcObject = stream;
95
- videoElement.play();
96
- Promise.resolve({});
97
- }
98
- else {
99
- self.stopStream(stream);
100
- Promise.reject(new Error("camera already stopped"));
101
- }
102
- }, (err) => {
103
- Promise.reject(new Error(err));
104
- });
96
+ else if (options.height) {
97
+ this.videoElement.width = options.height * ratio;
105
98
  }
106
99
  }
107
100
  else {
108
- Promise.reject(new Error("camera already started"));
101
+ this.videoElement.style.objectFit = "cover";
102
+ }
103
+ const constraints = {
104
+ video: {
105
+ width: { ideal: this.videoElement.width || 640 },
106
+ height: { ideal: this.videoElement.height || window.innerHeight },
107
+ facingMode: this.isBackCamera ? "environment" : "user",
108
+ },
109
+ };
110
+ const stream = await navigator.mediaDevices.getUserMedia(constraints);
111
+ if (!stream) {
112
+ throw new Error("could not acquire stream");
109
113
  }
114
+ if (!this.videoElement) {
115
+ throw new Error("video element not found");
116
+ }
117
+ this.videoElement.srcObject = stream;
118
+ if (!this.isBackCamera) {
119
+ this.videoElement.style.transform = "scaleX(-1)";
120
+ }
121
+ this.isStarted = true;
122
+ return {
123
+ width: this.videoElement.width,
124
+ height: this.videoElement.height,
125
+ x: this.videoElement.getBoundingClientRect().x,
126
+ y: this.videoElement.getBoundingClientRect().y,
127
+ };
110
128
  }
111
129
  stopStream(stream) {
112
130
  if (stream) {
@@ -116,16 +134,20 @@ class CameraPreviewWeb extends core.WebPlugin {
116
134
  }
117
135
  }
118
136
  async stop() {
119
- const video = document.getElementById("video");
137
+ const video = document.getElementById(DEFAULT_VIDEO_ID);
120
138
  if (video) {
121
139
  video.pause();
122
140
  this.stopStream(video.srcObject);
123
141
  video.remove();
142
+ this.isStarted = false;
124
143
  }
144
+ // Remove grid overlay if it exists
145
+ const gridOverlay = document.getElementById("camera-grid-overlay");
146
+ gridOverlay === null || gridOverlay === void 0 ? void 0 : gridOverlay.remove();
125
147
  }
126
148
  async capture(options) {
127
149
  return new Promise((resolve, reject) => {
128
- const video = document.getElementById("video");
150
+ const video = document.getElementById(DEFAULT_VIDEO_ID);
129
151
  if (!(video === null || video === void 0 ? void 0 : video.srcObject)) {
130
152
  reject(new Error("camera is not running"));
131
153
  return;
@@ -143,6 +165,8 @@ class CameraPreviewWeb extends core.WebPlugin {
143
165
  context === null || context === void 0 ? void 0 : context.scale(-1, 1);
144
166
  }
145
167
  context === null || context === void 0 ? void 0 : context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
168
+ if (options.saveToGallery) ;
169
+ if (options.withExifLocation) ;
146
170
  if ((options.format || "jpeg") === "jpeg") {
147
171
  base64EncodedImage = canvas
148
172
  .toDataURL("image/jpeg", (options.quality || 85) / 100.0)
@@ -156,6 +180,7 @@ class CameraPreviewWeb extends core.WebPlugin {
156
180
  }
157
181
  resolve({
158
182
  value: base64EncodedImage,
183
+ exif: {},
159
184
  });
160
185
  });
161
186
  }
@@ -179,7 +204,7 @@ class CameraPreviewWeb extends core.WebPlugin {
179
204
  throw new Error(`setFlashMode not supported under the web platform${_options}`);
180
205
  }
181
206
  async flip() {
182
- const video = document.getElementById("video");
207
+ const video = document.getElementById(DEFAULT_VIDEO_ID);
183
208
  if (!(video === null || video === void 0 ? void 0 : video.srcObject)) {
184
209
  throw new Error("camera is not running");
185
210
  }
@@ -219,12 +244,12 @@ class CameraPreviewWeb extends core.WebPlugin {
219
244
  }
220
245
  }
221
246
  async setOpacity(_options) {
222
- const video = document.getElementById("video");
247
+ const video = document.getElementById(DEFAULT_VIDEO_ID);
223
248
  if (!!video && !!_options.opacity)
224
249
  video.style.setProperty("opacity", _options.opacity.toString());
225
250
  }
226
251
  async isRunning() {
227
- const video = document.getElementById("video");
252
+ const video = document.getElementById(DEFAULT_VIDEO_ID);
228
253
  return { isRunning: !!video && !!video.srcObject };
229
254
  }
230
255
  async getAvailableDevices() {
@@ -233,7 +258,7 @@ class CameraPreviewWeb extends core.WebPlugin {
233
258
  throw new Error("getAvailableDevices not supported under the web platform");
234
259
  }
235
260
  const devices = await navigator.mediaDevices.enumerateDevices();
236
- const videoDevices = devices.filter(device => device.kind === 'videoinput');
261
+ const videoDevices = devices.filter((device) => device.kind === "videoinput");
237
262
  // Group devices by position (front/back)
238
263
  const frontDevices = [];
239
264
  const backDevices = [];
@@ -243,15 +268,19 @@ class CameraPreviewWeb extends core.WebPlugin {
243
268
  // Determine device type based on label
244
269
  let deviceType = exports.DeviceType.WIDE_ANGLE;
245
270
  let baseZoomRatio = 1.0;
246
- if (labelLower.includes('ultra') || labelLower.includes('0.5')) {
271
+ if (labelLower.includes("ultra") || labelLower.includes("0.5")) {
247
272
  deviceType = exports.DeviceType.ULTRA_WIDE;
248
273
  baseZoomRatio = 0.5;
249
274
  }
250
- else if (labelLower.includes('telephoto') || labelLower.includes('tele') || labelLower.includes('2x') || labelLower.includes('3x')) {
275
+ else if (labelLower.includes("telephoto") ||
276
+ labelLower.includes("tele") ||
277
+ labelLower.includes("2x") ||
278
+ labelLower.includes("3x")) {
251
279
  deviceType = exports.DeviceType.TELEPHOTO;
252
280
  baseZoomRatio = 2.0;
253
281
  }
254
- else if (labelLower.includes('depth') || labelLower.includes('truedepth')) {
282
+ else if (labelLower.includes("depth") ||
283
+ labelLower.includes("truedepth")) {
255
284
  deviceType = exports.DeviceType.TRUE_DEPTH;
256
285
  baseZoomRatio = 1.0;
257
286
  }
@@ -262,10 +291,10 @@ class CameraPreviewWeb extends core.WebPlugin {
262
291
  focalLength: 4.25,
263
292
  baseZoomRatio,
264
293
  minZoom: 1.0,
265
- maxZoom: 1.0
294
+ maxZoom: 1.0,
266
295
  };
267
296
  // Determine position and add to appropriate array
268
- if (labelLower.includes('back') || labelLower.includes('rear')) {
297
+ if (labelLower.includes("back") || labelLower.includes("rear")) {
269
298
  backDevices.push(lensInfo);
270
299
  }
271
300
  else {
@@ -280,8 +309,8 @@ class CameraPreviewWeb extends core.WebPlugin {
280
309
  position: "front",
281
310
  lenses: frontDevices,
282
311
  isLogical: false,
283
- minZoom: Math.min(...frontDevices.map(d => d.minZoom)),
284
- maxZoom: Math.max(...frontDevices.map(d => d.maxZoom))
312
+ minZoom: Math.min(...frontDevices.map((d) => d.minZoom)),
313
+ maxZoom: Math.max(...frontDevices.map((d) => d.maxZoom)),
285
314
  });
286
315
  }
287
316
  if (backDevices.length > 0) {
@@ -291,14 +320,14 @@ class CameraPreviewWeb extends core.WebPlugin {
291
320
  position: "rear",
292
321
  lenses: backDevices,
293
322
  isLogical: false,
294
- minZoom: Math.min(...backDevices.map(d => d.minZoom)),
295
- maxZoom: Math.max(...backDevices.map(d => d.maxZoom))
323
+ minZoom: Math.min(...backDevices.map((d) => d.minZoom)),
324
+ maxZoom: Math.max(...backDevices.map((d) => d.maxZoom)),
296
325
  });
297
326
  }
298
327
  return { devices: result };
299
328
  }
300
329
  async getZoom() {
301
- const video = document.getElementById("video");
330
+ const video = document.getElementById(DEFAULT_VIDEO_ID);
302
331
  if (!(video === null || video === void 0 ? void 0 : video.srcObject)) {
303
332
  throw new Error("camera is not running");
304
333
  }
@@ -317,18 +346,22 @@ class CameraPreviewWeb extends core.WebPlugin {
317
346
  let baseZoomRatio = 1.0;
318
347
  if (this.currentDeviceId) {
319
348
  const devices = await navigator.mediaDevices.enumerateDevices();
320
- const device = devices.find(d => d.deviceId === this.currentDeviceId);
349
+ const device = devices.find((d) => d.deviceId === this.currentDeviceId);
321
350
  if (device) {
322
351
  const labelLower = device.label.toLowerCase();
323
- if (labelLower.includes('ultra') || labelLower.includes('0.5')) {
352
+ if (labelLower.includes("ultra") || labelLower.includes("0.5")) {
324
353
  deviceType = exports.DeviceType.ULTRA_WIDE;
325
354
  baseZoomRatio = 0.5;
326
355
  }
327
- else if (labelLower.includes('telephoto') || labelLower.includes('tele') || labelLower.includes('2x') || labelLower.includes('3x')) {
356
+ else if (labelLower.includes("telephoto") ||
357
+ labelLower.includes("tele") ||
358
+ labelLower.includes("2x") ||
359
+ labelLower.includes("3x")) {
328
360
  deviceType = exports.DeviceType.TELEPHOTO;
329
361
  baseZoomRatio = 2.0;
330
362
  }
331
- else if (labelLower.includes('depth') || labelLower.includes('truedepth')) {
363
+ else if (labelLower.includes("depth") ||
364
+ labelLower.includes("truedepth")) {
332
365
  deviceType = exports.DeviceType.TRUE_DEPTH;
333
366
  baseZoomRatio = 1.0;
334
367
  }
@@ -339,7 +372,7 @@ class CameraPreviewWeb extends core.WebPlugin {
339
372
  focalLength: 4.25,
340
373
  deviceType,
341
374
  baseZoomRatio,
342
- digitalZoom: currentZoom / baseZoomRatio
375
+ digitalZoom: currentZoom / baseZoomRatio,
343
376
  };
344
377
  return {
345
378
  min: capabilities.zoom.min || 1,
@@ -349,7 +382,7 @@ class CameraPreviewWeb extends core.WebPlugin {
349
382
  };
350
383
  }
351
384
  async setZoom(options) {
352
- const video = document.getElementById("video");
385
+ const video = document.getElementById(DEFAULT_VIDEO_ID);
353
386
  if (!(video === null || video === void 0 ? void 0 : video.srcObject)) {
354
387
  throw new Error("camera is not running");
355
388
  }
@@ -365,7 +398,7 @@ class CameraPreviewWeb extends core.WebPlugin {
365
398
  const zoomLevel = Math.max(capabilities.zoom.min || 1, Math.min(capabilities.zoom.max || 1, options.level));
366
399
  try {
367
400
  await videoTrack.applyConstraints({
368
- advanced: [{ zoom: zoomLevel }]
401
+ advanced: [{ zoom: zoomLevel }],
369
402
  });
370
403
  }
371
404
  catch (error) {
@@ -379,7 +412,7 @@ class CameraPreviewWeb extends core.WebPlugin {
379
412
  return { deviceId: this.currentDeviceId || "" };
380
413
  }
381
414
  async setDeviceId(options) {
382
- const video = document.getElementById("video");
415
+ const video = document.getElementById(DEFAULT_VIDEO_ID);
383
416
  if (!(video === null || video === void 0 ? void 0 : video.srcObject)) {
384
417
  throw new Error("camera is not running");
385
418
  }
@@ -398,8 +431,11 @@ class CameraPreviewWeb extends core.WebPlugin {
398
431
  try {
399
432
  // Try to determine camera position from device
400
433
  const devices = await navigator.mediaDevices.enumerateDevices();
401
- const device = devices.find(d => d.deviceId === options.deviceId);
402
- 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;
434
+ const device = devices.find((d) => d.deviceId === options.deviceId);
435
+ this.isBackCamera =
436
+ (device === null || device === void 0 ? void 0 : device.label.toLowerCase().includes("back")) ||
437
+ (device === null || device === void 0 ? void 0 : device.label.toLowerCase().includes("rear")) ||
438
+ false;
403
439
  const stream = await navigator.mediaDevices.getUserMedia(constraints);
404
440
  video.srcObject = stream;
405
441
  // Update video transform based on camera
@@ -417,6 +453,167 @@ class CameraPreviewWeb extends core.WebPlugin {
417
453
  throw new Error(`Failed to swap to device ${options.deviceId}: ${error}`);
418
454
  }
419
455
  }
456
+ async getAspectRatio() {
457
+ const video = document.getElementById(DEFAULT_VIDEO_ID);
458
+ if (!video) {
459
+ throw new Error("camera is not running");
460
+ }
461
+ const width = video.offsetWidth;
462
+ const height = video.offsetHeight;
463
+ if (width && height) {
464
+ const ratio = width / height;
465
+ // Check for portrait camera ratios: 4:3 -> 3:4, 16:9 -> 9:16
466
+ if (Math.abs(ratio - 3 / 4) < 0.01) {
467
+ return { aspectRatio: "4:3" };
468
+ }
469
+ if (Math.abs(ratio - 9 / 16) < 0.01) {
470
+ return { aspectRatio: "16:9" };
471
+ }
472
+ }
473
+ // Default to 4:3 if no specific aspect ratio is matched
474
+ return { aspectRatio: "4:3" };
475
+ }
476
+ async setAspectRatio(options) {
477
+ const video = document.getElementById(DEFAULT_VIDEO_ID);
478
+ if (!video) {
479
+ throw new Error("camera is not running");
480
+ }
481
+ if (options.aspectRatio) {
482
+ const [widthRatio, heightRatio] = options.aspectRatio
483
+ .split(":")
484
+ .map(Number);
485
+ // For camera, use portrait orientation: 4:3 becomes 3:4, 16:9 becomes 9:16
486
+ const ratio = heightRatio / widthRatio;
487
+ // Get current position and size
488
+ const rect = video.getBoundingClientRect();
489
+ const currentWidth = rect.width;
490
+ const currentHeight = rect.height;
491
+ const currentRatio = currentWidth / currentHeight;
492
+ let newWidth;
493
+ let newHeight;
494
+ if (currentRatio > ratio) {
495
+ // Width is larger, fit by height and center horizontally
496
+ newWidth = currentHeight * ratio;
497
+ newHeight = currentHeight;
498
+ }
499
+ else {
500
+ // Height is larger, fit by width and center vertically
501
+ newWidth = currentWidth;
502
+ newHeight = currentWidth / ratio;
503
+ }
504
+ // Calculate position
505
+ let x, y;
506
+ if (options.x !== undefined && options.y !== undefined) {
507
+ // Use provided coordinates, ensuring they stay within screen boundaries
508
+ x = Math.max(0, Math.min(options.x, window.innerWidth - newWidth));
509
+ y = Math.max(0, Math.min(options.y, window.innerHeight - newHeight));
510
+ }
511
+ else {
512
+ // Auto-center the view
513
+ x = (window.innerWidth - newWidth) / 2;
514
+ y = (window.innerHeight - newHeight) / 2;
515
+ }
516
+ video.style.width = `${newWidth}px`;
517
+ video.style.height = `${newHeight}px`;
518
+ video.style.left = `${x}px`;
519
+ video.style.top = `${y}px`;
520
+ video.style.position = "absolute";
521
+ return {
522
+ width: Math.round(newWidth),
523
+ height: Math.round(newHeight),
524
+ x: Math.round(x),
525
+ y: Math.round(y),
526
+ };
527
+ }
528
+ else {
529
+ video.style.objectFit = "cover";
530
+ const rect = video.getBoundingClientRect();
531
+ return {
532
+ width: Math.round(rect.width),
533
+ height: Math.round(rect.height),
534
+ x: Math.round(rect.left),
535
+ y: Math.round(rect.top),
536
+ };
537
+ }
538
+ }
539
+ createGridOverlay(gridMode) {
540
+ const overlay = document.createElement("div");
541
+ overlay.style.position = "absolute";
542
+ overlay.style.top = "0";
543
+ overlay.style.left = "0";
544
+ overlay.style.width = "100%";
545
+ overlay.style.height = "100%";
546
+ overlay.style.pointerEvents = "none";
547
+ overlay.style.zIndex = "10";
548
+ const divisions = gridMode === "3x3" ? 3 : 4;
549
+ // Create SVG for grid lines
550
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
551
+ svg.style.width = "100%";
552
+ svg.style.height = "100%";
553
+ svg.style.position = "absolute";
554
+ svg.style.top = "0";
555
+ svg.style.left = "0";
556
+ // Create grid lines
557
+ for (let i = 1; i < divisions; i++) {
558
+ // Vertical lines
559
+ const verticalLine = document.createElementNS("http://www.w3.org/2000/svg", "line");
560
+ verticalLine.setAttribute("x1", `${(i / divisions) * 100}%`);
561
+ verticalLine.setAttribute("y1", "0%");
562
+ verticalLine.setAttribute("x2", `${(i / divisions) * 100}%`);
563
+ verticalLine.setAttribute("y2", "100%");
564
+ verticalLine.setAttribute("stroke", "rgba(255, 255, 255, 0.5)");
565
+ verticalLine.setAttribute("stroke-width", "1");
566
+ svg.appendChild(verticalLine);
567
+ // Horizontal lines
568
+ const horizontalLine = document.createElementNS("http://www.w3.org/2000/svg", "line");
569
+ horizontalLine.setAttribute("x1", "0%");
570
+ horizontalLine.setAttribute("y1", `${(i / divisions) * 100}%`);
571
+ horizontalLine.setAttribute("x2", "100%");
572
+ horizontalLine.setAttribute("y2", `${(i / divisions) * 100}%`);
573
+ horizontalLine.setAttribute("stroke", "rgba(255, 255, 255, 0.5)");
574
+ horizontalLine.setAttribute("stroke-width", "1");
575
+ svg.appendChild(horizontalLine);
576
+ }
577
+ overlay.appendChild(svg);
578
+ return overlay;
579
+ }
580
+ async setGridMode(options) {
581
+ // Web implementation of grid mode would need to be implemented
582
+ // For now, just resolve as a no-op
583
+ console.warn(`Grid mode '${options.gridMode}' is not yet implemented for web platform`);
584
+ }
585
+ async getGridMode() {
586
+ // Web implementation - default to none
587
+ return { gridMode: "none" };
588
+ }
589
+ async getPreviewSize() {
590
+ const video = document.getElementById(DEFAULT_VIDEO_ID);
591
+ if (!video) {
592
+ throw new Error("camera is not running");
593
+ }
594
+ return {
595
+ x: video.offsetLeft,
596
+ y: video.offsetTop,
597
+ width: video.width,
598
+ height: video.height,
599
+ };
600
+ }
601
+ async setPreviewSize(options) {
602
+ const video = document.getElementById(DEFAULT_VIDEO_ID);
603
+ if (!video) {
604
+ throw new Error("camera is not running");
605
+ }
606
+ video.style.left = `${options.x}px`;
607
+ video.style.top = `${options.y}px`;
608
+ video.width = options.width;
609
+ video.height = options.height;
610
+ return {
611
+ width: options.width,
612
+ height: options.height,
613
+ x: options.x,
614
+ y: options.y,
615
+ };
616
+ }
420
617
  }
421
618
 
422
619
  var web = /*#__PURE__*/Object.freeze({