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
|
@@ -5,30 +5,32 @@ import Foundation
|
|
|
5
5
|
/// Please read the Capacitor iOS Plugin Development Guide
|
|
6
6
|
/// here: https://capacitorjs.com/docs/plugins/ios
|
|
7
7
|
@objc(CameraViewPlugin)
|
|
8
|
-
public class CameraViewPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
8
|
+
public class CameraViewPlugin: CAPPlugin, CAPBridgedPlugin, CameraEventDelegate {
|
|
9
9
|
public let identifier = "CameraViewPlugin"
|
|
10
10
|
public let jsName = "CameraView"
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
/// Maps string flash mode values to AVCaptureDevice.FlashMode enum values.
|
|
13
13
|
private let strToFlashModeMap: [String: AVCaptureDevice.FlashMode] = [
|
|
14
14
|
"off": .off,
|
|
15
15
|
"on": .on,
|
|
16
16
|
"auto": .auto
|
|
17
17
|
]
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
/// Maps AVCaptureDevice.FlashMode enum values to string values.
|
|
20
20
|
private let flashModeToStrMap: [AVCaptureDevice.FlashMode: String] = [
|
|
21
21
|
.off: "off",
|
|
22
22
|
.on: "on",
|
|
23
23
|
.auto: "auto"
|
|
24
24
|
]
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
public let pluginMethods: [CAPPluginMethod] = [
|
|
27
27
|
CAPPluginMethod(name: "start", returnType: CAPPluginReturnPromise),
|
|
28
28
|
CAPPluginMethod(name: "stop", returnType: CAPPluginReturnPromise),
|
|
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),
|
|
32
34
|
CAPPluginMethod(name: "getAvailableDevices", returnType: CAPPluginReturnPromise),
|
|
33
35
|
CAPPluginMethod(name: "flipCamera", returnType: CAPPluginReturnPromise),
|
|
34
36
|
CAPPluginMethod(name: "getZoom", returnType: CAPPluginReturnPromise),
|
|
@@ -42,45 +44,29 @@ public class CameraViewPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
42
44
|
CAPPluginMethod(name: "checkPermissions", returnType: CAPPluginReturnPromise),
|
|
43
45
|
CAPPluginMethod(name: "requestPermissions", returnType: CAPPluginReturnPromise)
|
|
44
46
|
]
|
|
45
|
-
|
|
47
|
+
|
|
46
48
|
private let implementation = CameraViewManager()
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
|
|
49
50
|
override public func load() {
|
|
50
|
-
|
|
51
|
-
notificationObserver = NotificationCenter.default.addObserver(
|
|
52
|
-
forName: Notification.Name("barcodeDetected"),
|
|
53
|
-
object: nil,
|
|
54
|
-
queue: .main
|
|
55
|
-
) { [weak self] notification in
|
|
56
|
-
guard let self = self,
|
|
57
|
-
let barcodeData = notification.userInfo as? [String: Any] else {
|
|
58
|
-
return
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Emit event to JS
|
|
62
|
-
self.notifyListeners("barcodeDetected", data: barcodeData)
|
|
63
|
-
}
|
|
51
|
+
implementation.eventEmitter.delegate = self
|
|
64
52
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
NotificationCenter.default.removeObserver(observer)
|
|
69
|
-
}
|
|
53
|
+
|
|
54
|
+
public func cameraDidDetectBarcode(_ event: BarcodeDetectedEvent) {
|
|
55
|
+
notifyListeners("barcodeDetected", data: event.toDictionary())
|
|
70
56
|
}
|
|
71
|
-
|
|
57
|
+
|
|
72
58
|
@objc func start(_ call: CAPPluginCall) {
|
|
73
59
|
guard let webView = self.webView else {
|
|
74
60
|
call.reject("Cannot find web view")
|
|
75
61
|
return
|
|
76
62
|
}
|
|
77
|
-
|
|
63
|
+
|
|
78
64
|
maybeRequestCameraAccess { [weak self] granted in
|
|
79
65
|
guard granted else {
|
|
80
66
|
call.reject("Camera access denied")
|
|
81
67
|
return
|
|
82
68
|
}
|
|
83
|
-
|
|
69
|
+
|
|
84
70
|
self?.implementation.startSession(
|
|
85
71
|
configuration: sessionConfigFromPluginCall(call),
|
|
86
72
|
webView: webView,
|
|
@@ -93,55 +79,69 @@ public class CameraViewPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
93
79
|
})
|
|
94
80
|
}
|
|
95
81
|
}
|
|
96
|
-
|
|
82
|
+
|
|
97
83
|
@objc func stop(_ call: CAPPluginCall) {
|
|
98
84
|
implementation.stopSession {
|
|
99
85
|
call.resolve()
|
|
100
86
|
}
|
|
101
87
|
}
|
|
102
|
-
|
|
88
|
+
|
|
103
89
|
@objc func isRunning(_ call: CAPPluginCall) {
|
|
104
90
|
call.resolve([
|
|
105
91
|
"isRunning": implementation.isRunning()
|
|
106
92
|
])
|
|
107
93
|
}
|
|
108
|
-
|
|
94
|
+
|
|
109
95
|
@objc func capture(_ call: CAPPluginCall) {
|
|
110
96
|
let quality = call.getDouble("quality", 90.0)
|
|
111
97
|
let saveToFile = call.getBool("saveToFile", false)
|
|
112
|
-
|
|
98
|
+
|
|
113
99
|
guard quality >= 0.0 && quality <= 100.0 else {
|
|
114
100
|
call.reject("Quality must be between 0 and 100")
|
|
115
101
|
return
|
|
116
102
|
}
|
|
117
|
-
|
|
118
|
-
|
|
103
|
+
|
|
104
|
+
// Use optimized Data-based capture to avoid double JPEG encoding
|
|
105
|
+
implementation.capturePhotoData(completion: { [weak self] (data, error) in
|
|
119
106
|
if let error = error {
|
|
120
107
|
call.reject("Failed to capture image", nil, error)
|
|
121
108
|
return
|
|
122
109
|
}
|
|
123
|
-
|
|
124
|
-
guard let
|
|
110
|
+
|
|
111
|
+
guard let originalData = data else {
|
|
125
112
|
call.reject("No image data")
|
|
126
113
|
return
|
|
127
114
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
115
|
+
|
|
116
|
+
// Determine final image data based on quality setting
|
|
117
|
+
// For quality >= 90%, use original camera JPEG data to avoid re-encoding
|
|
118
|
+
// For lower quality, re-encode to reduce file size
|
|
119
|
+
let imageData: Data
|
|
120
|
+
if quality >= 90.0 {
|
|
121
|
+
// Use original JPEG data from camera (avoids quality loss and CPU overhead)
|
|
122
|
+
imageData = originalData
|
|
123
|
+
} else {
|
|
124
|
+
// Re-encode at lower quality for smaller file size
|
|
125
|
+
guard let image = UIImage(data: originalData),
|
|
126
|
+
let compressedData = image.jpegData(compressionQuality: quality / 100.0) else {
|
|
127
|
+
call.reject("Failed to compress image")
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
imageData = compressedData
|
|
132
131
|
}
|
|
133
|
-
|
|
132
|
+
|
|
134
133
|
if saveToFile {
|
|
134
|
+
// Use TempFileManager for tracked temp files with automatic cleanup
|
|
135
|
+
let tempFileURL = TempFileManager.shared.createTempImageFile()
|
|
135
136
|
do {
|
|
136
|
-
let tempFileURL = try createTempImageFile()
|
|
137
137
|
try imageData.write(to: tempFileURL)
|
|
138
|
-
|
|
138
|
+
|
|
139
139
|
// Convert file URL to webView-accessible path using Capacitor bridge
|
|
140
|
-
guard let webPath = self
|
|
140
|
+
guard let webPath = self?.bridge?.portablePath(fromLocalURL: tempFileURL)?.absoluteString else {
|
|
141
141
|
call.reject("Failed to create web-accessible path")
|
|
142
142
|
return
|
|
143
143
|
}
|
|
144
|
-
|
|
144
|
+
|
|
145
145
|
call.resolve(["webPath": webPath])
|
|
146
146
|
} catch {
|
|
147
147
|
call.reject("Failed to save image to file", nil, error)
|
|
@@ -154,43 +154,44 @@ public class CameraViewPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
154
154
|
}
|
|
155
155
|
})
|
|
156
156
|
}
|
|
157
|
-
|
|
157
|
+
|
|
158
158
|
@objc func captureSample(_ call: CAPPluginCall) {
|
|
159
159
|
let quality = call.getDouble("quality", 90.0)
|
|
160
160
|
let saveToFile = call.getBool("saveToFile", false)
|
|
161
|
-
|
|
161
|
+
|
|
162
162
|
guard quality >= 0.0 && quality <= 100.0 else {
|
|
163
163
|
call.reject("Quality must be between 0 and 100")
|
|
164
164
|
return
|
|
165
165
|
}
|
|
166
|
-
|
|
167
|
-
implementation.captureSnapshot { (image, error) in
|
|
166
|
+
|
|
167
|
+
implementation.captureSnapshot { [weak self] (image, error) in
|
|
168
168
|
if let error = error {
|
|
169
169
|
call.reject("Failed to capture frame", nil, error)
|
|
170
170
|
return
|
|
171
171
|
}
|
|
172
|
-
|
|
172
|
+
|
|
173
173
|
guard let image = image else {
|
|
174
174
|
call.reject("No frame data")
|
|
175
175
|
return
|
|
176
176
|
}
|
|
177
|
-
|
|
177
|
+
|
|
178
178
|
guard let imageData = image.jpegData(compressionQuality: quality / 100.0) else {
|
|
179
179
|
call.reject("Failed to compress image")
|
|
180
180
|
return
|
|
181
181
|
}
|
|
182
|
-
|
|
182
|
+
|
|
183
183
|
if saveToFile {
|
|
184
|
+
// Use TempFileManager for tracked temp files with automatic cleanup
|
|
185
|
+
let tempFileURL = TempFileManager.shared.createTempImageFile()
|
|
184
186
|
do {
|
|
185
|
-
let tempFileURL = try createTempImageFile()
|
|
186
187
|
try imageData.write(to: tempFileURL)
|
|
187
|
-
|
|
188
|
+
|
|
188
189
|
// Convert file URL to webView-accessible path using Capacitor bridge
|
|
189
|
-
guard let webPath = self
|
|
190
|
+
guard let webPath = self?.bridge?.portablePath(fromLocalURL: tempFileURL)?.absoluteString else {
|
|
190
191
|
call.reject("Failed to create web-accessible path")
|
|
191
192
|
return
|
|
192
193
|
}
|
|
193
|
-
|
|
194
|
+
|
|
194
195
|
call.resolve(["webPath": webPath])
|
|
195
196
|
} catch {
|
|
196
197
|
call.reject("Failed to save sample to file", nil, error)
|
|
@@ -203,10 +204,67 @@ public class CameraViewPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
203
204
|
}
|
|
204
205
|
}
|
|
205
206
|
}
|
|
207
|
+
|
|
208
|
+
@objc func startRecording(_ call: CAPPluginCall) {
|
|
209
|
+
let enableAudio = call.getBool("enableAudio") ?? false
|
|
210
|
+
let videoQuality = call.getString("videoQuality") ?? "highest"
|
|
206
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
|
+
|
|
207
265
|
@objc func getAvailableDevices(_ call: CAPPluginCall) {
|
|
208
266
|
let devices = implementation.getAvailableDevices()
|
|
209
|
-
|
|
267
|
+
|
|
210
268
|
var result = JSArray()
|
|
211
269
|
for device in devices {
|
|
212
270
|
var deviceInfo = JSObject()
|
|
@@ -216,12 +274,12 @@ public class CameraViewPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
216
274
|
deviceInfo["deviceType"] = convertToStringCameraType(device.deviceType)
|
|
217
275
|
result.append(deviceInfo)
|
|
218
276
|
}
|
|
219
|
-
|
|
277
|
+
|
|
220
278
|
call.resolve([
|
|
221
279
|
"devices": result
|
|
222
280
|
])
|
|
223
281
|
}
|
|
224
|
-
|
|
282
|
+
|
|
225
283
|
@objc func flipCamera(_ call: CAPPluginCall) {
|
|
226
284
|
do {
|
|
227
285
|
try implementation.flipCamera()
|
|
@@ -231,25 +289,25 @@ public class CameraViewPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
231
289
|
return
|
|
232
290
|
}
|
|
233
291
|
}
|
|
234
|
-
|
|
292
|
+
|
|
235
293
|
@objc func getZoom(_ call: CAPPluginCall) {
|
|
236
294
|
let zoom = implementation.getSupportedZoomFactors()
|
|
237
|
-
|
|
295
|
+
|
|
238
296
|
call.resolve([
|
|
239
297
|
"min": zoom.min,
|
|
240
298
|
"max": zoom.max,
|
|
241
299
|
"current": zoom.current
|
|
242
300
|
])
|
|
243
301
|
}
|
|
244
|
-
|
|
302
|
+
|
|
245
303
|
@objc func setZoom(_ call: CAPPluginCall) {
|
|
246
304
|
guard let level = call.getDouble("level") else {
|
|
247
305
|
call.reject("Zoom level must be provided")
|
|
248
306
|
return
|
|
249
307
|
}
|
|
250
|
-
|
|
308
|
+
|
|
251
309
|
let ramp = call.getBool("ramp") ?? false
|
|
252
|
-
|
|
310
|
+
|
|
253
311
|
do {
|
|
254
312
|
try implementation.setZoomFactor(level, ramp: ramp)
|
|
255
313
|
call.resolve()
|
|
@@ -258,35 +316,35 @@ public class CameraViewPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
258
316
|
return
|
|
259
317
|
}
|
|
260
318
|
}
|
|
261
|
-
|
|
319
|
+
|
|
262
320
|
@objc func getFlashMode(_ call: CAPPluginCall) {
|
|
263
321
|
let flashMode = implementation.getFlashMode()
|
|
264
|
-
|
|
322
|
+
|
|
265
323
|
call.resolve([
|
|
266
324
|
"flashMode": flashModeToStrMap[flashMode] ?? "off"
|
|
267
325
|
])
|
|
268
326
|
}
|
|
269
|
-
|
|
327
|
+
|
|
270
328
|
@objc func getSupportedFlashModes(_ call: CAPPluginCall) {
|
|
271
329
|
let supportedFlashModes = implementation.getSupportedFlashModes()
|
|
272
330
|
let supportedFlashModeStrArr = supportedFlashModes.map { flashModeToStrMap[$0] }
|
|
273
|
-
|
|
331
|
+
|
|
274
332
|
call.resolve([
|
|
275
333
|
"flashModes": supportedFlashModeStrArr
|
|
276
334
|
])
|
|
277
335
|
}
|
|
278
|
-
|
|
336
|
+
|
|
279
337
|
@objc func setFlashMode(_ call: CAPPluginCall) {
|
|
280
338
|
guard let mode = call.getString("mode") else {
|
|
281
339
|
call.reject("Flash mode must be provided")
|
|
282
340
|
return
|
|
283
341
|
}
|
|
284
|
-
|
|
342
|
+
|
|
285
343
|
guard let flashMode = strToFlashModeMap[mode] else {
|
|
286
344
|
call.reject("Invalid flash mode")
|
|
287
345
|
return
|
|
288
346
|
}
|
|
289
|
-
|
|
347
|
+
|
|
290
348
|
do {
|
|
291
349
|
try implementation.setFlashMode(flashMode)
|
|
292
350
|
call.resolve()
|
|
@@ -294,14 +352,14 @@ public class CameraViewPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
294
352
|
call.reject("Failed to set flash mode", nil, error)
|
|
295
353
|
}
|
|
296
354
|
}
|
|
297
|
-
|
|
355
|
+
|
|
298
356
|
@objc func isTorchAvailable(_ call: CAPPluginCall) {
|
|
299
357
|
let available = implementation.isTorchAvailable()
|
|
300
358
|
call.resolve([
|
|
301
359
|
"available": available
|
|
302
360
|
])
|
|
303
361
|
}
|
|
304
|
-
|
|
362
|
+
|
|
305
363
|
@objc func getTorchMode(_ call: CAPPluginCall) {
|
|
306
364
|
let torchState = implementation.getTorchMode()
|
|
307
365
|
call.resolve([
|
|
@@ -309,20 +367,20 @@ public class CameraViewPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
309
367
|
"level": torchState.level
|
|
310
368
|
])
|
|
311
369
|
}
|
|
312
|
-
|
|
370
|
+
|
|
313
371
|
@objc func setTorchMode(_ call: CAPPluginCall) {
|
|
314
372
|
guard let enabled = call.getBool("enabled") else {
|
|
315
373
|
call.reject("Enabled parameter is required")
|
|
316
374
|
return
|
|
317
375
|
}
|
|
318
|
-
|
|
376
|
+
|
|
319
377
|
let level = call.getFloat("level") ?? 1.0
|
|
320
|
-
|
|
378
|
+
|
|
321
379
|
guard level >= 0.0 && level <= 1.0 else {
|
|
322
380
|
call.reject("Level must be between 0.0 and 1.0")
|
|
323
381
|
return
|
|
324
382
|
}
|
|
325
|
-
|
|
383
|
+
|
|
326
384
|
do {
|
|
327
385
|
try implementation.setTorchMode(enabled: enabled, level: level)
|
|
328
386
|
call.resolve()
|
|
@@ -330,32 +388,57 @@ public class CameraViewPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
330
388
|
call.reject("Failed to set torch mode", nil, error)
|
|
331
389
|
}
|
|
332
390
|
}
|
|
333
|
-
|
|
391
|
+
|
|
334
392
|
@objc override public func checkPermissions(_ call: CAPPluginCall) {
|
|
335
|
-
let cameraState: String
|
|
336
|
-
|
|
337
|
-
switch AVCaptureDevice.authorizationStatus(for: .video) {
|
|
338
|
-
case .notDetermined:
|
|
339
|
-
cameraState = "prompt"
|
|
340
|
-
case .restricted, .denied:
|
|
341
|
-
cameraState = "denied"
|
|
342
|
-
case .authorized:
|
|
343
|
-
cameraState = "granted"
|
|
344
|
-
@unknown default:
|
|
345
|
-
cameraState = "prompt"
|
|
346
|
-
}
|
|
347
|
-
|
|
348
393
|
call.resolve([
|
|
349
|
-
"camera":
|
|
394
|
+
"camera": authorizationStateString(for: .video),
|
|
395
|
+
"microphone": authorizationStateString(for: .audio)
|
|
350
396
|
])
|
|
351
397
|
}
|
|
352
|
-
|
|
398
|
+
|
|
353
399
|
@objc override public func requestPermissions(_ call: CAPPluginCall) {
|
|
354
|
-
|
|
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
|
|
355
406
|
self?.checkPermissions(call)
|
|
356
407
|
}
|
|
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
|
+
}
|
|
357
426
|
}
|
|
358
|
-
|
|
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
|
+
}
|
|
441
|
+
|
|
359
442
|
private func maybeRequestCameraAccess(completion: @escaping (Bool) -> Void) {
|
|
360
443
|
let status = AVCaptureDevice.authorizationStatus(for: .video)
|
|
361
444
|
if status == .authorized {
|
|
@@ -370,4 +453,19 @@ public class CameraViewPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
370
453
|
completion(false)
|
|
371
454
|
}
|
|
372
455
|
}
|
|
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
|
+
}
|
|
373
471
|
}
|