capacitor-camera-view 2.2.0-rc.1 → 2.2.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.
Files changed (28) hide show
  1. package/README.md +17 -202
  2. package/android/build.gradle +0 -1
  3. package/android/src/main/AndroidManifest.xml +0 -1
  4. package/android/src/main/java/com/michaelwolz/capacitorcameraview/CameraView.kt +4 -287
  5. package/android/src/main/java/com/michaelwolz/capacitorcameraview/CameraViewPlugin.kt +8 -112
  6. package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/BarcodeDetectionResult.kt +1 -0
  7. package/android/src/main/java/com/michaelwolz/capacitorcameraview/utils.kt +1 -21
  8. package/dist/docs.json +21 -201
  9. package/dist/esm/definitions.d.ts +23 -85
  10. package/dist/esm/definitions.js.map +1 -1
  11. package/dist/esm/web.d.ts +4 -20
  12. package/dist/esm/web.js +16 -151
  13. package/dist/esm/web.js.map +1 -1
  14. package/dist/plugin.cjs.js +16 -151
  15. package/dist/plugin.cjs.js.map +1 -1
  16. package/dist/plugin.js +16 -151
  17. package/dist/plugin.js.map +1 -1
  18. package/ios/Sources/CameraViewPlugin/CameraError.swift +0 -28
  19. package/ios/Sources/CameraViewPlugin/CameraEvents.swift +27 -3
  20. package/ios/Sources/CameraViewPlugin/CameraSessionConfiguration.swift +8 -8
  21. package/ios/Sources/CameraViewPlugin/CameraViewManager+BarcodeScan.swift +34 -3
  22. package/ios/Sources/CameraViewPlugin/CameraViewManager+PhotoCapture.swift +14 -13
  23. package/ios/Sources/CameraViewPlugin/CameraViewManager.swift +150 -159
  24. package/ios/Sources/CameraViewPlugin/CameraViewPlugin.swift +15 -114
  25. package/ios/Sources/CameraViewPlugin/TempFileManager.swift +34 -68
  26. package/package.json +12 -12
  27. package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/VideoRecordingQuality.kt +0 -10
  28. package/ios/Sources/CameraViewPlugin/CameraViewManager+VideoRecording.swift +0 -302
@@ -29,8 +29,6 @@ public class CameraViewPlugin: CAPPlugin, CAPBridgedPlugin, CameraEventDelegate
29
29
  CAPPluginMethod(name: "isRunning", returnType: CAPPluginReturnPromise),
30
30
  CAPPluginMethod(name: "capture", returnType: CAPPluginReturnPromise),
31
31
  CAPPluginMethod(name: "captureSample", returnType: CAPPluginReturnPromise),
32
- CAPPluginMethod(name: "startRecording", returnType: CAPPluginReturnPromise),
33
- CAPPluginMethod(name: "stopRecording", returnType: CAPPluginReturnPromise),
34
32
  CAPPluginMethod(name: "getAvailableDevices", returnType: CAPPluginReturnPromise),
35
33
  CAPPluginMethod(name: "flipCamera", returnType: CAPPluginReturnPromise),
36
34
  CAPPluginMethod(name: "getZoom", returnType: CAPPluginReturnPromise),
@@ -205,63 +203,6 @@ public class CameraViewPlugin: CAPPlugin, CAPBridgedPlugin, CameraEventDelegate
205
203
  }
206
204
  }
207
205
 
208
- @objc func startRecording(_ call: CAPPluginCall) {
209
- let enableAudio = call.getBool("enableAudio") ?? false
210
- let videoQuality = call.getString("videoQuality") ?? "highest"
211
-
212
- guard let parsedVideoQuality = VideoRecordingQuality(rawValue: videoQuality) else {
213
- call.reject("Invalid videoQuality. Use one of: lowest, sd, hd, fhd, uhd, highest")
214
- return
215
- }
216
-
217
- if enableAudio {
218
- maybeRequestMicrophoneAccess { [weak self] granted in
219
- guard granted else {
220
- call.reject("Microphone access denied")
221
- return
222
- }
223
- self?.doStartRecording(call: call, enableAudio: true, videoQuality: parsedVideoQuality)
224
- }
225
- } else {
226
- doStartRecording(call: call, enableAudio: false, videoQuality: parsedVideoQuality)
227
- }
228
- }
229
-
230
- private func doStartRecording(
231
- call: CAPPluginCall,
232
- enableAudio: Bool,
233
- videoQuality: VideoRecordingQuality
234
- ) {
235
- implementation.startRecording(enableAudio: enableAudio, videoQuality: videoQuality) { error in
236
- if let error = error {
237
- call.reject("Failed to start recording", nil, error)
238
- return
239
- }
240
- call.resolve()
241
- }
242
- }
243
-
244
- @objc func stopRecording(_ call: CAPPluginCall) {
245
- implementation.stopRecording { [weak self] (outputURL, error) in
246
- if let error = error {
247
- call.reject("Failed to stop recording", nil, error)
248
- return
249
- }
250
-
251
- guard let outputURL = outputURL else {
252
- call.reject("No output file URL")
253
- return
254
- }
255
-
256
- guard let webPath = self?.bridge?.portablePath(fromLocalURL: outputURL)?.absoluteString else {
257
- call.reject("Failed to create web-accessible path")
258
- return
259
- }
260
-
261
- call.resolve(["webPath": webPath])
262
- }
263
- }
264
-
265
206
  @objc func getAvailableDevices(_ call: CAPPluginCall) {
266
207
  let devices = implementation.getAvailableDevices()
267
208
 
@@ -390,53 +331,28 @@ public class CameraViewPlugin: CAPPlugin, CAPBridgedPlugin, CameraEventDelegate
390
331
  }
391
332
 
392
333
  @objc override public func checkPermissions(_ call: CAPPluginCall) {
334
+ let cameraState: String
335
+
336
+ switch AVCaptureDevice.authorizationStatus(for: .video) {
337
+ case .notDetermined:
338
+ cameraState = "prompt"
339
+ case .restricted, .denied:
340
+ cameraState = "denied"
341
+ case .authorized:
342
+ cameraState = "granted"
343
+ @unknown default:
344
+ cameraState = "prompt"
345
+ }
346
+
393
347
  call.resolve([
394
- "camera": authorizationStateString(for: .video),
395
- "microphone": authorizationStateString(for: .audio)
348
+ "camera": cameraState
396
349
  ])
397
350
  }
398
351
 
399
352
  @objc override public func requestPermissions(_ call: CAPPluginCall) {
400
- let permissionsList = call.getArray("permissions", String.self) ?? ["camera"]
401
-
402
- let requestCamera = permissionsList.contains("camera")
403
- let requestMicrophone = permissionsList.contains("microphone")
404
-
405
- let completionHandler: () -> Void = { [weak self] in
353
+ AVCaptureDevice.requestAccess(for: .video) { [weak self] _ in
406
354
  self?.checkPermissions(call)
407
355
  }
408
-
409
- if requestCamera {
410
- AVCaptureDevice.requestAccess(for: .video) { _ in
411
- if requestMicrophone {
412
- AVCaptureDevice.requestAccess(for: .audio) { _ in
413
- completionHandler()
414
- }
415
- } else {
416
- completionHandler()
417
- }
418
- }
419
- } else if requestMicrophone {
420
- AVCaptureDevice.requestAccess(for: .audio) { _ in
421
- completionHandler()
422
- }
423
- } else {
424
- completionHandler()
425
- }
426
- }
427
-
428
- /// Maps AVFoundation authorization status to the Capacitor permission state string.
429
- private func authorizationStateString(for mediaType: AVMediaType) -> String {
430
- switch AVCaptureDevice.authorizationStatus(for: mediaType) {
431
- case .notDetermined:
432
- return "prompt"
433
- case .restricted, .denied:
434
- return "denied"
435
- case .authorized:
436
- return "granted"
437
- @unknown default:
438
- return "prompt"
439
- }
440
356
  }
441
357
 
442
358
  private func maybeRequestCameraAccess(completion: @escaping (Bool) -> Void) {
@@ -453,19 +369,4 @@ public class CameraViewPlugin: CAPPlugin, CAPBridgedPlugin, CameraEventDelegate
453
369
  completion(false)
454
370
  }
455
371
  }
456
-
457
- private func maybeRequestMicrophoneAccess(completion: @escaping (Bool) -> Void) {
458
- let status = AVCaptureDevice.authorizationStatus(for: .audio)
459
- if status == .authorized {
460
- completion(true)
461
- } else if status == .notDetermined {
462
- AVCaptureDevice.requestAccess(for: .audio) { granted in
463
- DispatchQueue.main.async {
464
- completion(granted)
465
- }
466
- }
467
- } else {
468
- completion(false)
469
- }
470
- }
471
372
  }
@@ -11,29 +11,29 @@ import UIKit
11
11
  public final class TempFileManager: @unchecked Sendable {
12
12
  /// Shared singleton instance
13
13
  public static let shared = TempFileManager()
14
-
14
+
15
15
  /// Serial queue for thread-safe file tracking
16
16
  private let queue = DispatchQueue(label: "com.michaelwolz.capacitorcameraview.tempFileManager")
17
-
17
+
18
18
  /// Set of tracked temporary file URLs
19
19
  private var trackedFiles = Set<URL>()
20
-
20
+
21
21
  /// Directory prefix used to identify camera capture temp files
22
22
  private let tempFilePrefix = "camera_capture_"
23
-
23
+
24
24
  /// Stale file threshold in seconds (30 minutes)
25
25
  private let staleThresholdSeconds: TimeInterval = 1800
26
-
26
+
27
27
  private init() {
28
28
  setupAppLifecycleObservers()
29
29
  }
30
-
30
+
31
31
  deinit {
32
32
  NotificationCenter.default.removeObserver(self)
33
33
  }
34
-
34
+
35
35
  // MARK: - Public API
36
-
36
+
37
37
  /// Creates a temporary file URL for storing captured images and tracks it for cleanup.
38
38
  ///
39
39
  /// - Returns: A URL pointing to the temporary file location
@@ -43,84 +43,50 @@ public final class TempFileManager: @unchecked Sendable {
43
43
  let fileName = "\(tempFilePrefix)\(timestamp).jpg"
44
44
  let tempDir = FileManager.default.temporaryDirectory
45
45
  let fileURL = tempDir.appendingPathComponent(fileName)
46
-
46
+
47
47
  queue.sync {
48
- _ = trackedFiles.insert(fileURL)
48
+ trackedFiles.insert(fileURL)
49
49
  }
50
-
50
+
51
51
  return fileURL
52
52
  }
53
-
54
- /// Creates a temporary file URL for storing recorded videos and tracks it for cleanup.
55
- ///
56
- /// - Returns: A URL pointing to the temporary video file location
57
- public func createTempVideoFile() -> URL {
58
- let timestamp = Int(Date().timeIntervalSince1970 * 1000)
59
- let fileName = "\(tempFilePrefix)\(timestamp).mp4"
60
- let tempDir = FileManager.default.temporaryDirectory
61
- let fileURL = tempDir.appendingPathComponent(fileName)
62
-
63
- queue.sync {
64
- _ = trackedFiles.insert(fileURL)
65
- }
66
-
67
- return fileURL
68
- }
69
-
70
- /// Registers an externally created file for tracking.
71
- ///
72
- /// - Parameter url: The URL of the file to track
73
- public func trackFile(_ url: URL) {
74
- queue.sync {
75
- _ = trackedFiles.insert(url)
76
- }
77
- }
78
-
79
- /// Removes a file from tracking without deleting it.
80
- ///
81
- /// - Parameter url: The URL of the file to untrack
82
- public func untrackFile(_ url: URL) {
83
- queue.sync {
84
- _ = trackedFiles.remove(url)
85
- }
86
- }
87
-
53
+
88
54
  /// Cleans up all tracked temporary files.
89
55
  /// Call this when the camera session stops.
90
56
  public func cleanupSessionFiles() {
91
57
  queue.async { [weak self] in
92
58
  guard let self = self else { return }
93
-
59
+
94
60
  let filesToDelete = self.trackedFiles
95
61
  self.trackedFiles.removeAll()
96
-
62
+
97
63
  for fileURL in filesToDelete {
98
64
  self.deleteFile(at: fileURL)
99
65
  }
100
66
  }
101
67
  }
102
-
68
+
103
69
  /// Cleans up stale temporary files that are older than the threshold.
104
70
  /// This helps recover from cases where cleanup was missed.
105
71
  public func cleanupStaleFiles() {
106
72
  queue.async { [weak self] in
107
73
  guard let self = self else { return }
108
-
74
+
109
75
  let fileManager = FileManager.default
110
76
  let tempDir = fileManager.temporaryDirectory
111
-
77
+
112
78
  guard let contents = try? fileManager.contentsOfDirectory(
113
79
  at: tempDir,
114
80
  includingPropertiesForKeys: [.creationDateKey],
115
81
  options: .skipsHiddenFiles
116
82
  ) else { return }
117
-
83
+
118
84
  let staleDate = Date().addingTimeInterval(-self.staleThresholdSeconds)
119
-
85
+
120
86
  for fileURL in contents {
121
87
  // Only process our camera capture files
122
88
  guard fileURL.lastPathComponent.hasPrefix(self.tempFilePrefix) else { continue }
123
-
89
+
124
90
  // Check file age
125
91
  if let attributes = try? fileManager.attributesOfItem(atPath: fileURL.path),
126
92
  let creationDate = attributes[.creationDate] as? Date,
@@ -131,34 +97,34 @@ public final class TempFileManager: @unchecked Sendable {
131
97
  }
132
98
  }
133
99
  }
134
-
100
+
135
101
  /// Cleans up all camera capture temporary files in the temp directory.
136
102
  /// Use this for aggressive cleanup on app termination.
137
103
  public func cleanupAllCaptureFiles() {
138
104
  queue.async { [weak self] in
139
105
  guard let self = self else { return }
140
-
106
+
141
107
  let fileManager = FileManager.default
142
108
  let tempDir = fileManager.temporaryDirectory
143
-
109
+
144
110
  guard let contents = try? fileManager.contentsOfDirectory(
145
111
  at: tempDir,
146
112
  includingPropertiesForKeys: nil,
147
113
  options: .skipsHiddenFiles
148
114
  ) else { return }
149
-
115
+
150
116
  for fileURL in contents {
151
117
  if fileURL.lastPathComponent.hasPrefix(self.tempFilePrefix) {
152
118
  self.deleteFile(at: fileURL)
153
119
  }
154
120
  }
155
-
121
+
156
122
  self.trackedFiles.removeAll()
157
123
  }
158
124
  }
159
-
125
+
160
126
  // MARK: - Private Methods
161
-
127
+
162
128
  private func deleteFile(at url: URL) {
163
129
  do {
164
130
  try FileManager.default.removeItem(at: url)
@@ -166,7 +132,7 @@ public final class TempFileManager: @unchecked Sendable {
166
132
  // Silently ignore - file may already be deleted or inaccessible
167
133
  }
168
134
  }
169
-
135
+
170
136
  private func setupAppLifecycleObservers() {
171
137
  NotificationCenter.default.addObserver(
172
138
  self,
@@ -174,7 +140,7 @@ public final class TempFileManager: @unchecked Sendable {
174
140
  name: UIApplication.didBecomeActiveNotification,
175
141
  object: nil
176
142
  )
177
-
143
+
178
144
  NotificationCenter.default.addObserver(
179
145
  self,
180
146
  selector: #selector(handleAppWillTerminate),
@@ -182,21 +148,21 @@ public final class TempFileManager: @unchecked Sendable {
182
148
  object: nil
183
149
  )
184
150
  }
185
-
151
+
186
152
  @objc private func handleAppDidBecomeActive() {
187
153
  // Clean up any stale files when app becomes active
188
154
  cleanupStaleFiles()
189
155
  }
190
-
156
+
191
157
  @objc private func handleAppWillTerminate() {
192
158
  // Clean up all capture files on termination
193
159
  // Note: This is synchronous to ensure cleanup happens before termination
194
160
  queue.sync { [weak self] in
195
161
  guard let self = self else { return }
196
-
162
+
197
163
  let fileManager = FileManager.default
198
164
  let tempDir = fileManager.temporaryDirectory
199
-
165
+
200
166
  if let contents = try? fileManager.contentsOfDirectory(
201
167
  at: tempDir,
202
168
  includingPropertiesForKeys: nil,
@@ -208,7 +174,7 @@ public final class TempFileManager: @unchecked Sendable {
208
174
  }
209
175
  }
210
176
  }
211
-
177
+
212
178
  self.trackedFiles.removeAll()
213
179
  }
214
180
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "capacitor-camera-view",
3
- "version": "2.2.0-rc.1",
3
+ "version": "2.2.0",
4
4
  "description": "A Capacitor plugin for embedding a live camera feed directly into your app.",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",
@@ -48,32 +48,32 @@
48
48
  "prepublishOnly": "npm run build"
49
49
  },
50
50
  "devDependencies": {
51
- "@capacitor/android": "^8.1.0",
52
- "@capacitor/core": "^8.1.0",
51
+ "@capacitor/android": "^8.3.1",
52
+ "@capacitor/core": "^8.3.1",
53
53
  "@capacitor/docgen": "^0.3.1",
54
- "@capacitor/ios": "^8.1.0",
55
- "@commitlint/config-conventional": "^20.4.2",
54
+ "@capacitor/ios": "^8.3.1",
55
+ "@commitlint/config-conventional": "^20.5.0",
56
56
  "@ionic/prettier-config": "^4.0.0",
57
57
  "@ionic/swiftlint-config": "^2.0.0",
58
58
  "@semantic-release/changelog": "^6.0.3",
59
59
  "@semantic-release/commit-analyzer": "^13.0.1",
60
60
  "@semantic-release/git": "^10.0.1",
61
61
  "@semantic-release/github": "^12.0.6",
62
- "@semantic-release/npm": "^13.1.4",
62
+ "@semantic-release/npm": "^13.1.5",
63
63
  "@semantic-release/release-notes-generator": "^14.1.0",
64
- "@typescript-eslint/eslint-plugin": "^8.56.0",
65
- "@typescript-eslint/parser": "^8.56.0",
66
- "commitlint": "^20.4.2",
64
+ "@typescript-eslint/eslint-plugin": "^8.59.1",
65
+ "@typescript-eslint/parser": "^8.59.1",
66
+ "commitlint": "^20.5.2",
67
67
  "eslint": "^9.39.2",
68
68
  "eslint-config-prettier": "^10.1.8",
69
69
  "eslint-plugin-prettier": "^5.5.5",
70
70
  "husky": "^9.1.7",
71
- "prettier": "^3.8.1",
71
+ "prettier": "^3.8.3",
72
72
  "prettier-plugin-java": "^2.8.1",
73
73
  "prettier-plugin-organize-imports": "^4.3.0",
74
- "prettier-plugin-packagejson": "^3.0.0",
74
+ "prettier-plugin-packagejson": "^3.0.2",
75
75
  "rimraf": "^6.1.3",
76
- "rollup": "^4.58.0",
76
+ "rollup": "^4.60.2",
77
77
  "semantic-release": "^25.0.3",
78
78
  "swiftlint": "^2.0.0",
79
79
  "typescript": "^5.9.3"
@@ -1,10 +0,0 @@
1
- package com.michaelwolz.capacitorcameraview.model
2
-
3
- enum class VideoRecordingQuality {
4
- LOWEST,
5
- SD,
6
- HD,
7
- FHD,
8
- UHD,
9
- HIGHEST,
10
- }