capacitor-camera-view 1.0.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 (42) hide show
  1. package/CapacitorCameraView.podspec +17 -0
  2. package/LICENSE +201 -0
  3. package/Package.swift +28 -0
  4. package/README.md +654 -0
  5. package/android/build.gradle +79 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/java/com/michaelwolz/capacitorcameraview/CameraView.kt +555 -0
  8. package/android/src/main/java/com/michaelwolz/capacitorcameraview/CameraViewPlugin.kt +227 -0
  9. package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/BarcodeDetectionResult.kt +11 -0
  10. package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/CameraDevice.kt +14 -0
  11. package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/CameraSessionConfiguration.kt +10 -0
  12. package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/WebBoundingRect.kt +16 -0
  13. package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/ZoomFactors.kt +14 -0
  14. package/android/src/main/java/com/michaelwolz/capacitorcameraview/utils.kt +86 -0
  15. package/android/src/main/res/.gitkeep +0 -0
  16. package/dist/docs.json +968 -0
  17. package/dist/esm/definitions.d.ts +378 -0
  18. package/dist/esm/definitions.js +2 -0
  19. package/dist/esm/definitions.js.map +1 -0
  20. package/dist/esm/index.d.ts +7 -0
  21. package/dist/esm/index.js +10 -0
  22. package/dist/esm/index.js.map +1 -0
  23. package/dist/esm/utils.d.ts +45 -0
  24. package/dist/esm/utils.js +108 -0
  25. package/dist/esm/utils.js.map +1 -0
  26. package/dist/esm/web.d.ts +108 -0
  27. package/dist/esm/web.js +406 -0
  28. package/dist/esm/web.js.map +1 -0
  29. package/dist/plugin.cjs.js +530 -0
  30. package/dist/plugin.cjs.js.map +1 -0
  31. package/dist/plugin.js +533 -0
  32. package/dist/plugin.js.map +1 -0
  33. package/ios/Sources/CameraViewPlugin/CameraError.swift +39 -0
  34. package/ios/Sources/CameraViewPlugin/CameraSessionConfiguration.swift +32 -0
  35. package/ios/Sources/CameraViewPlugin/CameraViewManager+BarcodeScan.swift +91 -0
  36. package/ios/Sources/CameraViewPlugin/CameraViewManager+PhotoCapture.swift +52 -0
  37. package/ios/Sources/CameraViewPlugin/CameraViewManager+VideoDataOutput.swift +78 -0
  38. package/ios/Sources/CameraViewPlugin/CameraViewManager.swift +633 -0
  39. package/ios/Sources/CameraViewPlugin/CameraViewPlugin.swift +295 -0
  40. package/ios/Sources/CameraViewPlugin/Utils.swift +56 -0
  41. package/ios/Tests/CameraViewPluginTests/CameraViewPluginTests.swift +15 -0
  42. package/package.json +94 -0
@@ -0,0 +1,295 @@
1
+ import AVFoundation
2
+ import Capacitor
3
+ import Foundation
4
+
5
+ /// Please read the Capacitor iOS Plugin Development Guide
6
+ /// here: https://capacitorjs.com/docs/plugins/ios
7
+ @objc(CameraViewPlugin)
8
+ public class CameraViewPlugin: CAPPlugin, CAPBridgedPlugin {
9
+ public let identifier = "CameraViewPlugin"
10
+ public let jsName = "CameraView"
11
+
12
+ /// Maps string flash mode values to AVCaptureDevice.FlashMode enum values.
13
+ private let strToFlashModeMap: [String: AVCaptureDevice.FlashMode] = [
14
+ "off": .off,
15
+ "on": .on,
16
+ "auto": .auto,
17
+ ]
18
+
19
+ /// Maps AVCaptureDevice.FlashMode enum values to string values.
20
+ private let flashModeToStrMap: [AVCaptureDevice.FlashMode: String] = [
21
+ .off: "off",
22
+ .on: "on",
23
+ .auto: "auto",
24
+ ]
25
+
26
+ public let pluginMethods: [CAPPluginMethod] = [
27
+ CAPPluginMethod(name: "start", returnType: CAPPluginReturnPromise),
28
+ CAPPluginMethod(name: "stop", returnType: CAPPluginReturnPromise),
29
+ CAPPluginMethod(name: "isRunning", returnType: CAPPluginReturnPromise),
30
+ CAPPluginMethod(name: "capture", returnType: CAPPluginReturnPromise),
31
+ CAPPluginMethod(name: "captureSample", returnType: CAPPluginReturnPromise),
32
+ CAPPluginMethod(name: "getAvailableDevices", returnType: CAPPluginReturnPromise),
33
+ CAPPluginMethod(name: "flipCamera", returnType: CAPPluginReturnPromise),
34
+ CAPPluginMethod(name: "getZoom", returnType: CAPPluginReturnPromise),
35
+ CAPPluginMethod(name: "setZoom", returnType: CAPPluginReturnPromise),
36
+ CAPPluginMethod(name: "getFlashMode", returnType: CAPPluginReturnPromise),
37
+ CAPPluginMethod(name: "getSupportedFlashModes", returnType: CAPPluginReturnPromise),
38
+ CAPPluginMethod(name: "setFlashMode", returnType: CAPPluginReturnPromise),
39
+ CAPPluginMethod(name: "checkPermissions", returnType: CAPPluginReturnPromise),
40
+ CAPPluginMethod(name: "requestPermissions", returnType: CAPPluginReturnPromise),
41
+ ]
42
+
43
+ private let implementation = CameraViewManager()
44
+ private var notificationObserver: NSObjectProtocol?
45
+
46
+ override public func load() {
47
+ // Add observer for barcode detection events
48
+ notificationObserver = NotificationCenter.default.addObserver(
49
+ forName: Notification.Name("barcodeDetected"),
50
+ object: nil,
51
+ queue: .main
52
+ ) { [weak self] notification in
53
+ guard let self = self,
54
+ let barcodeData = notification.userInfo as? [String: Any] else {
55
+ return
56
+ }
57
+
58
+ // Emit event to JS
59
+ self.notifyListeners("barcodeDetected", data: barcodeData)
60
+ }
61
+ }
62
+
63
+ deinit {
64
+ if let observer = notificationObserver {
65
+ NotificationCenter.default.removeObserver(observer)
66
+ }
67
+ }
68
+
69
+ @objc func start(_ call: CAPPluginCall) {
70
+ guard let webView = self.webView else {
71
+ call.reject("Cannot find web view")
72
+ return
73
+ }
74
+
75
+ maybeRequestCameraAccess { [weak self] granted in
76
+ guard granted else {
77
+ call.reject("Camera access denied")
78
+ return
79
+ }
80
+
81
+ self?.implementation.startSession(
82
+ configuration: sessionConfigFromPluginCall(call),
83
+ webView: webView,
84
+ completion: { (error) in
85
+ if let error = error {
86
+ call.reject("Failed to start camera preview", nil, error)
87
+ return
88
+ }
89
+ call.resolve()
90
+ })
91
+ }
92
+ }
93
+
94
+ @objc func stop(_ call: CAPPluginCall) {
95
+ implementation.stopSession()
96
+ call.resolve()
97
+ }
98
+
99
+ @objc func isRunning(_ call: CAPPluginCall) {
100
+ call.resolve([
101
+ "isRunning": implementation.isRunning()
102
+ ])
103
+ }
104
+
105
+ @objc func capture(_ call: CAPPluginCall) {
106
+ let quality = call.getDouble("quality", 90.0)
107
+
108
+ guard quality >= 0.0 && quality <= 100.0 else {
109
+ call.reject("Quality must be between 0 and 100")
110
+ return
111
+ }
112
+
113
+ implementation.capturePhoto(completion: { (image, error) in
114
+ if let error = error {
115
+ call.reject("Failed to capture image", nil, error)
116
+ return
117
+ }
118
+
119
+ guard let image = image else {
120
+ call.reject("No image data", nil, nil)
121
+ return
122
+ }
123
+
124
+ guard let imageData = image.jpegData(compressionQuality: quality / 100.0) else {
125
+ call.reject("Failed to compress image", nil, nil)
126
+ return
127
+ }
128
+
129
+ call.resolve([
130
+ "photo": imageData.base64EncodedString()
131
+ ])
132
+ })
133
+ }
134
+
135
+ @objc func captureSample(_ call: CAPPluginCall) {
136
+ let quality = call.getDouble("quality", 90.0)
137
+
138
+ guard quality >= 0.0 && quality <= 100.0 else {
139
+ call.reject("Quality must be between 0 and 100")
140
+ return
141
+ }
142
+
143
+ implementation.captureSnapshot() { (image, error) in
144
+ if let error = error {
145
+ call.reject("Failed to capture frame", nil, error)
146
+ return
147
+ }
148
+
149
+ guard let image = image else {
150
+ call.reject("No frame data", nil, nil)
151
+ return
152
+ }
153
+
154
+ guard let imageData = image.jpegData(compressionQuality: quality / 100.0) else {
155
+ call.reject("Failed to compress image", nil, nil)
156
+ return
157
+ }
158
+
159
+ call.resolve([
160
+ "photo": imageData.base64EncodedString()
161
+ ])
162
+ }
163
+ }
164
+
165
+ @objc func getAvailableDevices(_ call: CAPPluginCall) {
166
+ let devices = implementation.getAvailableDevices()
167
+
168
+ var result = JSArray()
169
+ for device in devices {
170
+ var deviceInfo = JSObject()
171
+ deviceInfo["id"] = device.uniqueID
172
+ deviceInfo["name"] = device.localizedName
173
+ deviceInfo["position"] = device.position == .front ? "front" : "back"
174
+ deviceInfo["deviceType"] = convertToStringCameraType(device.deviceType)
175
+ result.append(deviceInfo)
176
+ }
177
+
178
+ call.resolve([
179
+ "devices": result
180
+ ])
181
+ }
182
+
183
+ @objc func flipCamera(_ call: CAPPluginCall) {
184
+ do {
185
+ try implementation.flipCamera()
186
+ call.resolve()
187
+ } catch {
188
+ call.reject("Failed to switch camera", nil, error)
189
+ return
190
+ }
191
+ }
192
+
193
+ @objc func getZoom(_ call: CAPPluginCall) {
194
+ let zoom = implementation.getSupportedZoomFactors()
195
+
196
+ call.resolve([
197
+ "min": zoom.min,
198
+ "max": zoom.max,
199
+ "current": zoom.current,
200
+ ])
201
+ }
202
+
203
+ @objc func setZoom(_ call: CAPPluginCall) {
204
+ guard let level = call.getDouble("level") else {
205
+ call.reject("Zoom level must be provided")
206
+ return
207
+ }
208
+
209
+ let ramp = call.getBool("ramp") ?? false
210
+
211
+ do {
212
+ try implementation.setZoomFactor(level, ramp: ramp)
213
+ call.resolve()
214
+ } catch {
215
+ call.reject("Failed to set zoom level", nil, error)
216
+ return
217
+ }
218
+ }
219
+
220
+ @objc func getFlashMode(_ call: CAPPluginCall) {
221
+ let flashMode = implementation.getFlashMode()
222
+
223
+ call.resolve([
224
+ "flashMode": flashMode
225
+ ])
226
+ }
227
+
228
+ @objc func getSupportedFlashModes(_ call: CAPPluginCall) {
229
+ let supportedFlashModes = implementation.getSupportedFlashModes()
230
+ let supportedFlashModeStrArr = supportedFlashModes.map { flashModeToStrMap[$0] }
231
+
232
+ call.resolve([
233
+ "flashModes": supportedFlashModeStrArr
234
+ ])
235
+ }
236
+
237
+ @objc func setFlashMode(_ call: CAPPluginCall) {
238
+ guard let mode = call.getString("mode") else {
239
+ call.reject("Flash mode must be provided")
240
+ return
241
+ }
242
+
243
+ guard let flashMode = strToFlashModeMap[mode] else {
244
+ call.reject("Invalid flash mode")
245
+ return
246
+ }
247
+
248
+ do {
249
+ try implementation.setFlashMode(flashMode)
250
+ call.resolve()
251
+ } catch {
252
+ call.reject("Failed to set flash mode", nil, error)
253
+ }
254
+ }
255
+
256
+ @objc override public func checkPermissions(_ call: CAPPluginCall) {
257
+ let cameraState: String
258
+
259
+ switch AVCaptureDevice.authorizationStatus(for: .video) {
260
+ case .notDetermined:
261
+ cameraState = "prompt"
262
+ case .restricted, .denied:
263
+ cameraState = "denied"
264
+ case .authorized:
265
+ cameraState = "granted"
266
+ @unknown default:
267
+ cameraState = "prompt"
268
+ }
269
+
270
+ call.resolve([
271
+ "camera": cameraState
272
+ ])
273
+ }
274
+
275
+ @objc override public func requestPermissions(_ call: CAPPluginCall) {
276
+ AVCaptureDevice.requestAccess(for: .video) { [weak self] _ in
277
+ self?.checkPermissions(call)
278
+ }
279
+ }
280
+
281
+ private func maybeRequestCameraAccess(completion: @escaping (Bool) -> Void) {
282
+ let status = AVCaptureDevice.authorizationStatus(for: .video)
283
+ if status == .authorized {
284
+ completion(true)
285
+ } else if status == .notDetermined {
286
+ AVCaptureDevice.requestAccess(for: .video) { granted in
287
+ DispatchQueue.main.async {
288
+ completion(granted)
289
+ }
290
+ }
291
+ } else {
292
+ completion(false)
293
+ }
294
+ }
295
+ }
@@ -0,0 +1,56 @@
1
+ import AVFoundation
2
+
3
+ /// Converts string camera type identifiers from JavaScript to native AVCaptureDevice.DeviceType values
4
+ /// - Parameter stringType: The string camera type from JavaScript
5
+ /// - Returns: The corresponding AVCaptureDevice.DeviceType or nil if no match
6
+ public func convertToNativeCameraType(_ stringType: String) -> AVCaptureDevice.DeviceType? {
7
+ switch stringType {
8
+ case "wideAngle":
9
+ return .builtInWideAngleCamera
10
+ case "ultraWide":
11
+ return .builtInUltraWideCamera
12
+ case "telephoto":
13
+ return .builtInTelephotoCamera
14
+ case "dual":
15
+ return .builtInDualCamera
16
+ case "dualWide":
17
+ return .builtInDualWideCamera
18
+ case "triple":
19
+ return .builtInTripleCamera
20
+ case "trueDepth":
21
+ return .builtInTrueDepthCamera
22
+ default:
23
+ return nil
24
+ }
25
+ }
26
+
27
+ /// Converts AVCaptureDevice.DeviceType to JavaScript string value
28
+ /// - Parameter deviceType: The AVCaptureDevice.DeviceType
29
+ /// - Returns: The corresponding string or nil if no match
30
+ public func convertToStringCameraType(_ deviceType: AVCaptureDevice.DeviceType) -> String? {
31
+ switch deviceType {
32
+ case .builtInWideAngleCamera:
33
+ return "wideAngle"
34
+ case .builtInUltraWideCamera:
35
+ return "ultraWide"
36
+ case .builtInTelephotoCamera:
37
+ return "telephoto"
38
+ case .builtInDualCamera:
39
+ return "dual"
40
+ case .builtInDualWideCamera:
41
+ return "dualWide"
42
+ case .builtInTripleCamera:
43
+ return "triple"
44
+ case .builtInTrueDepthCamera:
45
+ return "trueDepth"
46
+ default:
47
+ return nil
48
+ }
49
+ }
50
+
51
+ /// Converts an array of string camera type identifiers to native AVCaptureDevice.DeviceType values
52
+ /// - Parameter stringTypes: Array of string camera types from JavaScript
53
+ /// - Returns: Array of AVCaptureDevice.DeviceType values (invalid types are filtered out)
54
+ public func convertToNativeCameraTypes(_ stringTypes: [String]) -> [AVCaptureDevice.DeviceType] {
55
+ return stringTypes.compactMap { convertToNativeCameraType($0) }
56
+ }
@@ -0,0 +1,15 @@
1
+ import XCTest
2
+ @testable import CameraViewPlugin
3
+
4
+ class CameraViewTests: XCTestCase {
5
+ func testEcho() {
6
+ // This is an example of a functional test case for a plugin.
7
+ // Use XCTAssert and related functions to verify your tests produce the correct results.
8
+
9
+ let implementation = CameraView()
10
+ let value = "Hello, World!"
11
+ let result = implementation.echo(value)
12
+
13
+ XCTAssertEqual(value, result)
14
+ }
15
+ }
package/package.json ADDED
@@ -0,0 +1,94 @@
1
+ {
2
+ "name": "capacitor-camera-view",
3
+ "version": "1.0.0",
4
+ "description": "A Capacitor plugin for embedding a live camera feed directly into your app.",
5
+ "main": "dist/plugin.cjs.js",
6
+ "module": "dist/esm/index.js",
7
+ "types": "dist/esm/index.d.ts",
8
+ "unpkg": "dist/plugin.js",
9
+ "files": [
10
+ "android/src/main/",
11
+ "android/build.gradle",
12
+ "dist/",
13
+ "ios/Sources",
14
+ "ios/Tests",
15
+ "Package.swift",
16
+ "CapacitorCameraView.podspec"
17
+ ],
18
+ "author": "Michael Wolz",
19
+ "license": "Apache-2.0",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/michaelwolz/capacitor-camera-view.git"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/michaelwolz/capacitor-camera-view/issues"
26
+ },
27
+ "keywords": [
28
+ "capacitor",
29
+ "plugin",
30
+ "native"
31
+ ],
32
+ "scripts": {
33
+ "verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
34
+ "verify:ios": "xcodebuild -scheme CapacitorCameraView -destination generic/platform=iOS",
35
+ "verify:android": "cd android && ./gradlew clean build test && cd ..",
36
+ "verify:web": "npm run build",
37
+ "lint": "npm run eslint",
38
+ "lint:ios": "npm run swiftlint -- lint",
39
+ "fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --fix --format",
40
+ "eslint": "eslint . --ext ts",
41
+ "prettier": "prettier \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
42
+ "prettier:check": "prettier --check \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
43
+ "swiftlint": "node-swiftlint",
44
+ "docgen": "docgen --api CameraViewPlugin --output-readme README.md --output-json dist/docs.json",
45
+ "build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs",
46
+ "clean": "rimraf ./dist",
47
+ "watch": "tsc --watch",
48
+ "prepublishOnly": "npm run build"
49
+ },
50
+ "devDependencies": {
51
+ "@capacitor/android": "^7.3.0",
52
+ "@capacitor/core": "^7.3.0",
53
+ "@capacitor/docgen": "^0.3.0",
54
+ "@capacitor/ios": "^7.3.0",
55
+ "@commitlint/config-conventional": "^19.8.1",
56
+ "@ionic/eslint-config": "^0.4.0",
57
+ "@ionic/prettier-config": "^4.0.0",
58
+ "@ionic/swiftlint-config": "^2.0.0",
59
+ "@semantic-release/changelog": "^6.0.3",
60
+ "@semantic-release/commit-analyzer": "^13.0.1",
61
+ "@semantic-release/git": "^10.0.1",
62
+ "@semantic-release/github": "^11.0.3",
63
+ "@semantic-release/npm": "^12.0.1",
64
+ "@semantic-release/release-notes-generator": "^14.0.3",
65
+ "commitlint": "^19.8.1",
66
+ "eslint": "^8.57.0",
67
+ "husky": "^9.1.7",
68
+ "prettier": "^3.5.3",
69
+ "prettier-plugin-java": "^2.6.8",
70
+ "prettier-plugin-organize-imports": "^4.1.0",
71
+ "prettier-plugin-packagejson": "^2.5.15",
72
+ "rimraf": "^6.0.1",
73
+ "rollup": "^4.41.1",
74
+ "semantic-release": "^24.2.5",
75
+ "swiftlint": "^2.0.0",
76
+ "typescript": "~4.1.5"
77
+ },
78
+ "peerDependencies": {
79
+ "@capacitor/core": ">=7.0.0"
80
+ },
81
+ "prettier": "@ionic/prettier-config",
82
+ "swiftlint": "@ionic/swiftlint-config",
83
+ "eslintConfig": {
84
+ "extends": "@ionic/eslint-config/recommended"
85
+ },
86
+ "capacitor": {
87
+ "ios": {
88
+ "src": "ios"
89
+ },
90
+ "android": {
91
+ "src": "android"
92
+ }
93
+ }
94
+ }