capacitor-camera-view 2.0.2 → 2.2.0-rc.1
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 +215 -19
- package/android/build.gradle +9 -5
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/CameraView.kt +491 -116
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/CameraViewPlugin.kt +181 -31
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/CameraResult.kt +47 -0
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/CameraSessionConfiguration.kt +11 -1
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/VideoRecordingQuality.kt +10 -0
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/utils.kt +114 -5
- package/dist/docs.json +281 -8
- package/dist/esm/definitions.d.ts +128 -6
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +26 -4
- package/dist/esm/web.js +218 -18
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +219 -18
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +219 -18
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CameraViewPlugin/CameraError.swift +125 -2
- package/ios/Sources/CameraViewPlugin/CameraEvents.swift +109 -0
- package/ios/Sources/CameraViewPlugin/CameraSessionConfiguration.swift +28 -1
- package/ios/Sources/CameraViewPlugin/CameraViewManager+BarcodeScan.swift +30 -41
- package/ios/Sources/CameraViewPlugin/CameraViewManager+PhotoCapture.swift +38 -7
- package/ios/Sources/CameraViewPlugin/CameraViewManager+VideoDataOutput.swift +4 -3
- package/ios/Sources/CameraViewPlugin/CameraViewManager+VideoRecording.swift +302 -0
- package/ios/Sources/CameraViewPlugin/CameraViewManager.swift +246 -166
- package/ios/Sources/CameraViewPlugin/CameraViewPlugin.swift +194 -96
- package/ios/Sources/CameraViewPlugin/TempFileManager.swift +215 -0
- package/ios/Sources/CameraViewPlugin/Utils.swift +102 -0
- package/package.json +17 -17
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
/// Manages temporary files created during camera capture operations.
|
|
5
|
+
/// Provides automatic cleanup to prevent disk space leaks.
|
|
6
|
+
///
|
|
7
|
+
/// This singleton tracks all temporary files created by the camera plugin
|
|
8
|
+
/// and provides cleanup at various lifecycle points:
|
|
9
|
+
/// - On app activation (cleans up stale files older than 30 minutes)
|
|
10
|
+
/// - On app termination (cleans up all tracked files)
|
|
11
|
+
public final class TempFileManager: @unchecked Sendable {
|
|
12
|
+
/// Shared singleton instance
|
|
13
|
+
public static let shared = TempFileManager()
|
|
14
|
+
|
|
15
|
+
/// Serial queue for thread-safe file tracking
|
|
16
|
+
private let queue = DispatchQueue(label: "com.michaelwolz.capacitorcameraview.tempFileManager")
|
|
17
|
+
|
|
18
|
+
/// Set of tracked temporary file URLs
|
|
19
|
+
private var trackedFiles = Set<URL>()
|
|
20
|
+
|
|
21
|
+
/// Directory prefix used to identify camera capture temp files
|
|
22
|
+
private let tempFilePrefix = "camera_capture_"
|
|
23
|
+
|
|
24
|
+
/// Stale file threshold in seconds (30 minutes)
|
|
25
|
+
private let staleThresholdSeconds: TimeInterval = 1800
|
|
26
|
+
|
|
27
|
+
private init() {
|
|
28
|
+
setupAppLifecycleObservers()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
deinit {
|
|
32
|
+
NotificationCenter.default.removeObserver(self)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// MARK: - Public API
|
|
36
|
+
|
|
37
|
+
/// Creates a temporary file URL for storing captured images and tracks it for cleanup.
|
|
38
|
+
///
|
|
39
|
+
/// - Returns: A URL pointing to the temporary file location
|
|
40
|
+
/// - Throws: Never throws, always returns a valid temp directory URL
|
|
41
|
+
public func createTempImageFile() -> URL {
|
|
42
|
+
let timestamp = Int(Date().timeIntervalSince1970 * 1000)
|
|
43
|
+
let fileName = "\(tempFilePrefix)\(timestamp).jpg"
|
|
44
|
+
let tempDir = FileManager.default.temporaryDirectory
|
|
45
|
+
let fileURL = tempDir.appendingPathComponent(fileName)
|
|
46
|
+
|
|
47
|
+
queue.sync {
|
|
48
|
+
_ = trackedFiles.insert(fileURL)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return fileURL
|
|
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
|
+
|
|
88
|
+
/// Cleans up all tracked temporary files.
|
|
89
|
+
/// Call this when the camera session stops.
|
|
90
|
+
public func cleanupSessionFiles() {
|
|
91
|
+
queue.async { [weak self] in
|
|
92
|
+
guard let self = self else { return }
|
|
93
|
+
|
|
94
|
+
let filesToDelete = self.trackedFiles
|
|
95
|
+
self.trackedFiles.removeAll()
|
|
96
|
+
|
|
97
|
+
for fileURL in filesToDelete {
|
|
98
|
+
self.deleteFile(at: fileURL)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// Cleans up stale temporary files that are older than the threshold.
|
|
104
|
+
/// This helps recover from cases where cleanup was missed.
|
|
105
|
+
public func cleanupStaleFiles() {
|
|
106
|
+
queue.async { [weak self] in
|
|
107
|
+
guard let self = self else { return }
|
|
108
|
+
|
|
109
|
+
let fileManager = FileManager.default
|
|
110
|
+
let tempDir = fileManager.temporaryDirectory
|
|
111
|
+
|
|
112
|
+
guard let contents = try? fileManager.contentsOfDirectory(
|
|
113
|
+
at: tempDir,
|
|
114
|
+
includingPropertiesForKeys: [.creationDateKey],
|
|
115
|
+
options: .skipsHiddenFiles
|
|
116
|
+
) else { return }
|
|
117
|
+
|
|
118
|
+
let staleDate = Date().addingTimeInterval(-self.staleThresholdSeconds)
|
|
119
|
+
|
|
120
|
+
for fileURL in contents {
|
|
121
|
+
// Only process our camera capture files
|
|
122
|
+
guard fileURL.lastPathComponent.hasPrefix(self.tempFilePrefix) else { continue }
|
|
123
|
+
|
|
124
|
+
// Check file age
|
|
125
|
+
if let attributes = try? fileManager.attributesOfItem(atPath: fileURL.path),
|
|
126
|
+
let creationDate = attributes[.creationDate] as? Date,
|
|
127
|
+
creationDate < staleDate {
|
|
128
|
+
self.deleteFile(at: fileURL)
|
|
129
|
+
self.trackedFiles.remove(fileURL)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/// Cleans up all camera capture temporary files in the temp directory.
|
|
136
|
+
/// Use this for aggressive cleanup on app termination.
|
|
137
|
+
public func cleanupAllCaptureFiles() {
|
|
138
|
+
queue.async { [weak self] in
|
|
139
|
+
guard let self = self else { return }
|
|
140
|
+
|
|
141
|
+
let fileManager = FileManager.default
|
|
142
|
+
let tempDir = fileManager.temporaryDirectory
|
|
143
|
+
|
|
144
|
+
guard let contents = try? fileManager.contentsOfDirectory(
|
|
145
|
+
at: tempDir,
|
|
146
|
+
includingPropertiesForKeys: nil,
|
|
147
|
+
options: .skipsHiddenFiles
|
|
148
|
+
) else { return }
|
|
149
|
+
|
|
150
|
+
for fileURL in contents {
|
|
151
|
+
if fileURL.lastPathComponent.hasPrefix(self.tempFilePrefix) {
|
|
152
|
+
self.deleteFile(at: fileURL)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
self.trackedFiles.removeAll()
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// MARK: - Private Methods
|
|
161
|
+
|
|
162
|
+
private func deleteFile(at url: URL) {
|
|
163
|
+
do {
|
|
164
|
+
try FileManager.default.removeItem(at: url)
|
|
165
|
+
} catch {
|
|
166
|
+
// Silently ignore - file may already be deleted or inaccessible
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private func setupAppLifecycleObservers() {
|
|
171
|
+
NotificationCenter.default.addObserver(
|
|
172
|
+
self,
|
|
173
|
+
selector: #selector(handleAppDidBecomeActive),
|
|
174
|
+
name: UIApplication.didBecomeActiveNotification,
|
|
175
|
+
object: nil
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
NotificationCenter.default.addObserver(
|
|
179
|
+
self,
|
|
180
|
+
selector: #selector(handleAppWillTerminate),
|
|
181
|
+
name: UIApplication.willTerminateNotification,
|
|
182
|
+
object: nil
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
@objc private func handleAppDidBecomeActive() {
|
|
187
|
+
// Clean up any stale files when app becomes active
|
|
188
|
+
cleanupStaleFiles()
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
@objc private func handleAppWillTerminate() {
|
|
192
|
+
// Clean up all capture files on termination
|
|
193
|
+
// Note: This is synchronous to ensure cleanup happens before termination
|
|
194
|
+
queue.sync { [weak self] in
|
|
195
|
+
guard let self = self else { return }
|
|
196
|
+
|
|
197
|
+
let fileManager = FileManager.default
|
|
198
|
+
let tempDir = fileManager.temporaryDirectory
|
|
199
|
+
|
|
200
|
+
if let contents = try? fileManager.contentsOfDirectory(
|
|
201
|
+
at: tempDir,
|
|
202
|
+
includingPropertiesForKeys: nil,
|
|
203
|
+
options: .skipsHiddenFiles
|
|
204
|
+
) {
|
|
205
|
+
for fileURL in contents {
|
|
206
|
+
if fileURL.lastPathComponent.hasPrefix(self.tempFilePrefix) {
|
|
207
|
+
try? fileManager.removeItem(at: fileURL)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
self.trackedFiles.removeAll()
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import AVFoundation
|
|
2
2
|
|
|
3
|
+
// MARK: - Camera Type Conversion
|
|
4
|
+
|
|
3
5
|
/// Converts string camera type identifiers from JavaScript to native AVCaptureDevice.DeviceType values
|
|
4
6
|
/// - Parameter stringType: The string camera type from JavaScript
|
|
5
7
|
/// - Returns: The corresponding AVCaptureDevice.DeviceType or nil if no match
|
|
@@ -56,9 +58,109 @@ public func convertToNativeCameraTypes(_ stringTypes: [String]) -> [AVCaptureDev
|
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
/// Creates a temporary file URL for storing captured images
|
|
61
|
+
/// @deprecated Use TempFileManager.shared.createTempImageFile() for automatic cleanup
|
|
59
62
|
public func createTempImageFile() throws -> URL {
|
|
60
63
|
let timestamp = Int(Date().timeIntervalSince1970 * 1000)
|
|
61
64
|
let fileName = "camera_capture_\(timestamp).jpg"
|
|
62
65
|
let tempDir = FileManager.default.temporaryDirectory
|
|
63
66
|
return tempDir.appendingPathComponent(fileName)
|
|
64
67
|
}
|
|
68
|
+
|
|
69
|
+
// MARK: - Barcode Type Conversion
|
|
70
|
+
|
|
71
|
+
/// All supported barcode types for detection.
|
|
72
|
+
/// These are enabled by default when no specific types are configured.
|
|
73
|
+
public let ALL_SUPPORTED_BARCODE_TYPES: [AVMetadataObject.ObjectType] = [
|
|
74
|
+
.qr,
|
|
75
|
+
.code128,
|
|
76
|
+
.code39,
|
|
77
|
+
.code39Mod43,
|
|
78
|
+
.code93,
|
|
79
|
+
.ean8,
|
|
80
|
+
.ean13,
|
|
81
|
+
.interleaved2of5,
|
|
82
|
+
.itf14,
|
|
83
|
+
.pdf417,
|
|
84
|
+
.aztec,
|
|
85
|
+
.dataMatrix,
|
|
86
|
+
.upce
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
/// Converts a string barcode type identifier from JavaScript to native AVMetadataObject.ObjectType
|
|
90
|
+
/// - Parameter stringType: The string barcode type from JavaScript
|
|
91
|
+
/// - Returns: The corresponding AVMetadataObject.ObjectType or nil if no match
|
|
92
|
+
public func convertToNativeBarcodeType(_ stringType: String) -> AVMetadataObject.ObjectType? {
|
|
93
|
+
switch stringType {
|
|
94
|
+
case "qr":
|
|
95
|
+
return .qr
|
|
96
|
+
case "code128":
|
|
97
|
+
return .code128
|
|
98
|
+
case "code39":
|
|
99
|
+
return .code39
|
|
100
|
+
case "code39Mod43":
|
|
101
|
+
return .code39Mod43
|
|
102
|
+
case "code93":
|
|
103
|
+
return .code93
|
|
104
|
+
case "ean8":
|
|
105
|
+
return .ean8
|
|
106
|
+
case "ean13":
|
|
107
|
+
return .ean13
|
|
108
|
+
case "interleaved2of5":
|
|
109
|
+
return .interleaved2of5
|
|
110
|
+
case "itf14":
|
|
111
|
+
return .itf14
|
|
112
|
+
case "pdf417":
|
|
113
|
+
return .pdf417
|
|
114
|
+
case "aztec":
|
|
115
|
+
return .aztec
|
|
116
|
+
case "dataMatrix":
|
|
117
|
+
return .dataMatrix
|
|
118
|
+
case "upce":
|
|
119
|
+
return .upce
|
|
120
|
+
default:
|
|
121
|
+
return nil
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/// Converts AVMetadataObject.ObjectType to JavaScript string value
|
|
126
|
+
/// - Parameter barcodeType: The AVMetadataObject.ObjectType
|
|
127
|
+
/// - Returns: The corresponding string or nil if no match
|
|
128
|
+
public func convertToStringBarcodeType(_ barcodeType: AVMetadataObject.ObjectType) -> String? {
|
|
129
|
+
switch barcodeType {
|
|
130
|
+
case .qr:
|
|
131
|
+
return "qr"
|
|
132
|
+
case .code128:
|
|
133
|
+
return "code128"
|
|
134
|
+
case .code39:
|
|
135
|
+
return "code39"
|
|
136
|
+
case .code39Mod43:
|
|
137
|
+
return "code39Mod43"
|
|
138
|
+
case .code93:
|
|
139
|
+
return "code93"
|
|
140
|
+
case .ean8:
|
|
141
|
+
return "ean8"
|
|
142
|
+
case .ean13:
|
|
143
|
+
return "ean13"
|
|
144
|
+
case .interleaved2of5:
|
|
145
|
+
return "interleaved2of5"
|
|
146
|
+
case .itf14:
|
|
147
|
+
return "itf14"
|
|
148
|
+
case .pdf417:
|
|
149
|
+
return "pdf417"
|
|
150
|
+
case .aztec:
|
|
151
|
+
return "aztec"
|
|
152
|
+
case .dataMatrix:
|
|
153
|
+
return "dataMatrix"
|
|
154
|
+
case .upce:
|
|
155
|
+
return "upce"
|
|
156
|
+
default:
|
|
157
|
+
return nil
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/// Converts an array of string barcode type identifiers to native AVMetadataObject.ObjectType values
|
|
162
|
+
/// - Parameter stringTypes: Array of string barcode types from JavaScript
|
|
163
|
+
/// - Returns: Array of AVMetadataObject.ObjectType values (invalid types are filtered out)
|
|
164
|
+
public func convertToNativeBarcodeTypes(_ stringTypes: [String]) -> [AVMetadataObject.ObjectType] {
|
|
165
|
+
return stringTypes.compactMap { convertToNativeBarcodeType($0) }
|
|
166
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "capacitor-camera-view",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.2.0-rc.1",
|
|
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,33 +48,33 @@
|
|
|
48
48
|
"prepublishOnly": "npm run build"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@capacitor/android": "^8.
|
|
52
|
-
"@capacitor/core": "^8.
|
|
51
|
+
"@capacitor/android": "^8.1.0",
|
|
52
|
+
"@capacitor/core": "^8.1.0",
|
|
53
53
|
"@capacitor/docgen": "^0.3.1",
|
|
54
|
-
"@capacitor/ios": "^8.
|
|
55
|
-
"@commitlint/config-conventional": "^20.
|
|
54
|
+
"@capacitor/ios": "^8.1.0",
|
|
55
|
+
"@commitlint/config-conventional": "^20.4.2",
|
|
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
|
-
"@semantic-release/github": "^12.0.
|
|
62
|
-
"@semantic-release/npm": "^13.1.
|
|
61
|
+
"@semantic-release/github": "^12.0.6",
|
|
62
|
+
"@semantic-release/npm": "^13.1.4",
|
|
63
63
|
"@semantic-release/release-notes-generator": "^14.1.0",
|
|
64
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
65
|
-
"@typescript-eslint/parser": "^8.
|
|
66
|
-
"commitlint": "^20.
|
|
64
|
+
"@typescript-eslint/eslint-plugin": "^8.56.0",
|
|
65
|
+
"@typescript-eslint/parser": "^8.56.0",
|
|
66
|
+
"commitlint": "^20.4.2",
|
|
67
67
|
"eslint": "^9.39.2",
|
|
68
68
|
"eslint-config-prettier": "^10.1.8",
|
|
69
|
-
"eslint-plugin-prettier": "^5.5.
|
|
69
|
+
"eslint-plugin-prettier": "^5.5.5",
|
|
70
70
|
"husky": "^9.1.7",
|
|
71
|
-
"prettier": "^3.
|
|
72
|
-
"prettier-plugin-java": "^2.
|
|
71
|
+
"prettier": "^3.8.1",
|
|
72
|
+
"prettier-plugin-java": "^2.8.1",
|
|
73
73
|
"prettier-plugin-organize-imports": "^4.3.0",
|
|
74
|
-
"prettier-plugin-packagejson": "^
|
|
75
|
-
"rimraf": "^6.1.
|
|
76
|
-
"rollup": "^4.
|
|
77
|
-
"semantic-release": "^25.0.
|
|
74
|
+
"prettier-plugin-packagejson": "^3.0.0",
|
|
75
|
+
"rimraf": "^6.1.3",
|
|
76
|
+
"rollup": "^4.58.0",
|
|
77
|
+
"semantic-release": "^25.0.3",
|
|
78
78
|
"swiftlint": "^2.0.0",
|
|
79
79
|
"typescript": "^5.9.3"
|
|
80
80
|
},
|