@carviz/capacitor-camera-preview 7.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.
- package/CarvizCapacitorCameraPreview.podspec +17 -0
- package/LICENSE +22 -0
- package/README.md +229 -0
- package/android/.project +17 -0
- package/android/build.gradle +57 -0
- package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +8 -0
- package/android/gradle.properties +18 -0
- package/android/gradlew +248 -0
- package/android/gradlew.bat +92 -0
- package/android/proguard-rules.pro +21 -0
- package/android/settings.gradle +2 -0
- package/android/src/androidTest/java/com/getcapacitor/android/ExampleInstrumentedTest.java +26 -0
- package/android/src/main/AndroidManifest.xml +5 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraActivity.java +831 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +396 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CustomSurfaceView.java +23 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CustomTextureView.java +29 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/Preview.java +384 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/TapGestureDetector.java +24 -0
- package/android/src/main/res/layout/bridge_layout_main.xml +15 -0
- package/android/src/main/res/layout/camera_activity.xml +68 -0
- package/android/src/main/res/values/camera_ids.xml +4 -0
- package/android/src/main/res/values/camera_theme.xml +9 -0
- package/android/src/main/res/values/colors.xml +3 -0
- package/android/src/main/res/values/strings.xml +3 -0
- package/android/src/main/res/values/styles.xml +3 -0
- package/android/src/test/java/com/getcapacitor/ExampleUnitTest.java +18 -0
- package/dist/esm/definitions.d.ts +86 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +24 -0
- package/dist/esm/web.js +120 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +134 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +137 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Plugin/CameraController.swift +483 -0
- package/ios/Plugin/Info.plist +24 -0
- package/ios/Plugin/Plugin.h +10 -0
- package/ios/Plugin/Plugin.m +16 -0
- package/ios/Plugin/Plugin.swift +324 -0
- package/ios/Plugin/UIImage.swift +56 -0
- package/ios/Plugin.xcodeproj/project.pbxproj +599 -0
- package/ios/Plugin.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/ios/Plugin.xcworkspace/contents.xcworkspacedata +10 -0
- package/ios/Plugin.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/ios/PluginTests/Info.plist +22 -0
- package/ios/PluginTests/PluginTests.swift +16 -0
- package/ios/Podfile +16 -0
- package/ios/Podfile.lock +22 -0
- package/package.json +85 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Capacitor
|
|
3
|
+
import AVFoundation
|
|
4
|
+
/**
|
|
5
|
+
* Please read the Capacitor iOS Plugin Development Guide
|
|
6
|
+
* here: https://capacitor.ionicframework.com/docs/plugins/ios
|
|
7
|
+
*/
|
|
8
|
+
@objc(CameraPreview)
|
|
9
|
+
public class CameraPreview: CAPPlugin {
|
|
10
|
+
let cameraController = CameraController()
|
|
11
|
+
var cameraPosition: CameraPosition = .rear
|
|
12
|
+
var x: CGFloat = 0.0
|
|
13
|
+
var y: CGFloat = 0.0
|
|
14
|
+
var previewView: UIView?
|
|
15
|
+
var previewWidth: CGFloat = UIScreen.main.bounds.size.width
|
|
16
|
+
var previewHeight: CGFloat = UIScreen.main.bounds.size.height
|
|
17
|
+
var paddingBottom: CGFloat = 0
|
|
18
|
+
var rotateWhenOrientationChanged = true
|
|
19
|
+
var toBack = false
|
|
20
|
+
var storeToFile = false
|
|
21
|
+
var enableZoom = false
|
|
22
|
+
var enableHighResolution = false
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
Start the camera preview in a new UIView
|
|
26
|
+
*/
|
|
27
|
+
@objc public func start(_ call: CAPPluginCall) {
|
|
28
|
+
AVCaptureDevice.requestAccess(for: AVMediaType.video) { [weak self] granted in
|
|
29
|
+
guard granted else {
|
|
30
|
+
call.reject("camera access not granted")
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Initialize settings provided via API call
|
|
35
|
+
self?.initializePluginSettings(call: call)
|
|
36
|
+
|
|
37
|
+
if let captureSession = self?.cameraController.captureSession, captureSession.isRunning {
|
|
38
|
+
call.reject("camera already started")
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
self?.cameraController.prepare(cameraPosition: self?.cameraPosition, enableHighResolution: self?.enableHighResolution ?? false) { error in
|
|
43
|
+
if let error = error {
|
|
44
|
+
call.reject(error.localizedDescription)
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
DispatchQueue.main.async {
|
|
49
|
+
self?.displayCameraPreviewView()
|
|
50
|
+
call.resolve()
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
Stops any currently running capture session
|
|
58
|
+
*/
|
|
59
|
+
@objc public func stop(_ call: CAPPluginCall) {
|
|
60
|
+
guard self.cameraController.captureSession?.isRunning ?? false else {
|
|
61
|
+
call.reject("camera already stopped")
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
DispatchQueue.main.async {
|
|
66
|
+
self.cameraController.stop()
|
|
67
|
+
self.previewView?.removeFromSuperview()
|
|
68
|
+
self.webView?.isOpaque = true
|
|
69
|
+
call.resolve()
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
Capture a photo with the currently active capture device
|
|
75
|
+
*/
|
|
76
|
+
@objc func capture(_ call: CAPPluginCall) {
|
|
77
|
+
DispatchQueue.main.async {
|
|
78
|
+
let quality: Int = call.getInt("quality", 85)
|
|
79
|
+
|
|
80
|
+
self.cameraController.captureImage { (image, error) in
|
|
81
|
+
guard let image = image else {
|
|
82
|
+
print(error ?? "Image capture error")
|
|
83
|
+
guard let error = error else {
|
|
84
|
+
call.reject("Image capture error")
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
call.reject(error.localizedDescription)
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let imageData = image.jpegData(compressionQuality: CGFloat(quality / 100))
|
|
93
|
+
|
|
94
|
+
if self.storeToFile == false {
|
|
95
|
+
let imageBase64 = imageData?.base64EncodedString()
|
|
96
|
+
call.resolve(["value": imageBase64!])
|
|
97
|
+
} else {
|
|
98
|
+
do {
|
|
99
|
+
let fileUrl = self.getTempFilePath()
|
|
100
|
+
try imageData?.write(to: fileUrl)
|
|
101
|
+
call.resolve(["value": fileUrl.absoluteString])
|
|
102
|
+
} catch {
|
|
103
|
+
call.reject("error writing image to file")
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@objc public func flip(_ call: CAPPluginCall) {
|
|
111
|
+
do {
|
|
112
|
+
try self.cameraController.switchCameras()
|
|
113
|
+
call.resolve()
|
|
114
|
+
} catch {
|
|
115
|
+
call.reject("failed to flip camera")
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
Captures a sample image from the video stream.
|
|
121
|
+
*/
|
|
122
|
+
@objc func captureSample(_ call: CAPPluginCall) {
|
|
123
|
+
DispatchQueue.main.async {
|
|
124
|
+
let quality: Int = call.getInt("quality", 85)
|
|
125
|
+
|
|
126
|
+
self.cameraController.captureSample { image, error in
|
|
127
|
+
guard let image = image else {
|
|
128
|
+
print("Image capture error: \(String(describing: error))")
|
|
129
|
+
call.reject("Image capture error: \(String(describing: error))")
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let imageData: Data?
|
|
134
|
+
if self.cameraPosition == .front {
|
|
135
|
+
let flippedImage = image.withHorizontallyFlippedOrientation()
|
|
136
|
+
imageData = flippedImage.jpegData(compressionQuality: CGFloat(quality / 100))
|
|
137
|
+
} else {
|
|
138
|
+
imageData = image.jpegData(compressionQuality: CGFloat(quality / 100))
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if self.storeToFile == false {
|
|
142
|
+
let imageBase64 = imageData?.base64EncodedString()
|
|
143
|
+
call.resolve(["value": imageBase64!])
|
|
144
|
+
} else {
|
|
145
|
+
do {
|
|
146
|
+
let fileUrl = self.getTempFilePath()
|
|
147
|
+
try imageData?.write(to: fileUrl)
|
|
148
|
+
call.resolve(["value": fileUrl.absoluteString])
|
|
149
|
+
} catch {
|
|
150
|
+
call.reject("Error writing image to file")
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
Return an array of supported flash modes of the currently active capture device
|
|
159
|
+
*/
|
|
160
|
+
@objc func getSupportedFlashModes(_ call: CAPPluginCall) {
|
|
161
|
+
do {
|
|
162
|
+
let supportedFlashModes = try self.cameraController.getSupportedFlashModes()
|
|
163
|
+
call.resolve(["result": supportedFlashModes])
|
|
164
|
+
} catch {
|
|
165
|
+
call.reject("failed to get supported flash modes")
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
Set the flash mode for the currently active capture device
|
|
171
|
+
*/
|
|
172
|
+
@objc func setFlashMode(_ call: CAPPluginCall) {
|
|
173
|
+
guard let flashMode = call.getString("flashMode") else {
|
|
174
|
+
call.reject("failed to set flash mode. required parameter flashMode is missing")
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
var flashModeAsEnum: AVCaptureDevice.FlashMode?
|
|
179
|
+
switch flashMode {
|
|
180
|
+
case "off":
|
|
181
|
+
flashModeAsEnum = AVCaptureDevice.FlashMode.off
|
|
182
|
+
case "on":
|
|
183
|
+
flashModeAsEnum = AVCaptureDevice.FlashMode.on
|
|
184
|
+
case "auto":
|
|
185
|
+
flashModeAsEnum = AVCaptureDevice.FlashMode.auto
|
|
186
|
+
default: break
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
do {
|
|
190
|
+
if flashModeAsEnum != nil {
|
|
191
|
+
try self.cameraController.setFlashMode(flashMode: flashModeAsEnum!)
|
|
192
|
+
} else if flashMode == "torch" {
|
|
193
|
+
try self.cameraController.setTorchMode()
|
|
194
|
+
} else {
|
|
195
|
+
call.reject("Flash Mode not supported")
|
|
196
|
+
return
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
call.resolve()
|
|
200
|
+
} catch {
|
|
201
|
+
call.reject("failed to set flash mode")
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
Helper method for initializing the plugin settings based on the Capacitor call
|
|
207
|
+
*/
|
|
208
|
+
private func initializePluginSettings(call: CAPPluginCall) {
|
|
209
|
+
self.cameraPosition = call.getString("position") == "front" ? .front : .rear
|
|
210
|
+
|
|
211
|
+
if let previewWidth = call.getInt("width") {
|
|
212
|
+
self.previewWidth = CGFloat(previewWidth)
|
|
213
|
+
} else {
|
|
214
|
+
self.previewWidth = UIScreen.main.bounds.size.width
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if let previewHeight = call.getInt("height") {
|
|
218
|
+
self.previewHeight = CGFloat(previewHeight)
|
|
219
|
+
} else {
|
|
220
|
+
self.previewHeight = UIScreen.main.bounds.size.height
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
self.x = CGFloat(call.getInt("x", 0)) / 2
|
|
224
|
+
self.y = CGFloat(call.getInt("y", 0)) / 2
|
|
225
|
+
|
|
226
|
+
self.paddingBottom = CGFloat(call.getInt("paddingBottom", 0))
|
|
227
|
+
|
|
228
|
+
self.rotateWhenOrientationChanged = call.getBool("rotateWhenOrientationChanged") ?? true
|
|
229
|
+
|
|
230
|
+
self.toBack = call.getBool("toBack") ?? false
|
|
231
|
+
|
|
232
|
+
self.storeToFile = call.getBool("storeToFile") ?? false
|
|
233
|
+
|
|
234
|
+
self.enableZoom = call.getBool("enableZoom") ?? false
|
|
235
|
+
|
|
236
|
+
self.enableHighResolution = call.getBool("enableHighResolution", false)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
@objc override public func checkPermissions(_ call: CAPPluginCall) {
|
|
240
|
+
let cameraState: String
|
|
241
|
+
|
|
242
|
+
switch AVCaptureDevice.authorizationStatus(for: .video) {
|
|
243
|
+
case .notDetermined:
|
|
244
|
+
cameraState = "prompt"
|
|
245
|
+
case .restricted, .denied:
|
|
246
|
+
cameraState = "denied"
|
|
247
|
+
case .authorized:
|
|
248
|
+
cameraState = "granted"
|
|
249
|
+
@unknown default:
|
|
250
|
+
cameraState = "prompt"
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
call.resolve(["camera": cameraState])
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
@objc public override func requestPermissions(_ call: CAPPluginCall) {
|
|
257
|
+
AVCaptureDevice.requestAccess(for: .video) { [weak self] _ in
|
|
258
|
+
self?.checkPermissions(call)
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
Displays the camera preview view in the UI
|
|
264
|
+
*/
|
|
265
|
+
private func displayCameraPreviewView() {
|
|
266
|
+
self.previewView = UIView(frame: CGRect(x: self.x, y: self.y, width: self.previewWidth, height: self.previewHeight - self.paddingBottom))
|
|
267
|
+
self.webView?.isOpaque = false
|
|
268
|
+
self.webView?.backgroundColor = UIColor.clear
|
|
269
|
+
self.webView?.scrollView.backgroundColor = UIColor.clear
|
|
270
|
+
self.webView?.superview?.addSubview(self.previewView!)
|
|
271
|
+
|
|
272
|
+
if self.toBack {
|
|
273
|
+
self.webView?.superview?.bringSubviewToFront(self.webView!)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if self.rotateWhenOrientationChanged {
|
|
277
|
+
NotificationCenter.default.addObserver(self, selector: #selector(CameraPreview.rotated), name: UIDevice.orientationDidChangeNotification, object: nil)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
self.cameraController.displayPreview(on: self.previewView!)
|
|
281
|
+
|
|
282
|
+
let frontView = self.toBack ? self.webView : self.previewView!
|
|
283
|
+
self.cameraController.setupGestures(target: frontView ?? self.previewView!, enableZoom: self.enableZoom)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
Handler funciton for updating the previewLayer frame based on plugin settings and the current device orientation
|
|
288
|
+
*/
|
|
289
|
+
@objc private func rotated() {
|
|
290
|
+
guard let previewView = self.previewView else {
|
|
291
|
+
return
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
|
|
295
|
+
return
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
let interfaceOrientation = windowScene.interfaceOrientation
|
|
299
|
+
let height = self.previewHeight - self.paddingBottom
|
|
300
|
+
|
|
301
|
+
if interfaceOrientation.isLandscape {
|
|
302
|
+
previewView.frame = CGRect(x: self.y, y: self.x, width: max(height, self.previewWidth), height: min(height, self.previewWidth))
|
|
303
|
+
cameraController.previewLayer.frame = previewView.frame
|
|
304
|
+
} else if interfaceOrientation.isPortrait {
|
|
305
|
+
previewView.frame = CGRect(x: self.x, y: self.y, width: min(height, self.previewWidth), height: max(height, self.previewWidth))
|
|
306
|
+
cameraController.previewLayer.frame = previewView.frame
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
cameraController.updateVideoOrientation()
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
Get user's cache directory path
|
|
314
|
+
*/
|
|
315
|
+
private func getTempFilePath() -> URL {
|
|
316
|
+
let path = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
|
|
317
|
+
let randomIdentifier = UUID().uuidString.replacingOccurrences(of: "-", with: "")
|
|
318
|
+
let finalIdentifier = String(randomIdentifier.prefix(8))
|
|
319
|
+
let fileName="cpcp_capture_"+finalIdentifier+".jpg"
|
|
320
|
+
let fileUrl=path.appendingPathComponent(fileName)
|
|
321
|
+
|
|
322
|
+
return fileUrl
|
|
323
|
+
}
|
|
324
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
extension UIImage {
|
|
2
|
+
func fixedOrientation() -> UIImage? {
|
|
3
|
+
guard imageOrientation != UIImage.Orientation.up else {
|
|
4
|
+
// This is default orientation, don't need to do anything
|
|
5
|
+
return self.copy() as? UIImage
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
guard let cgImage = self.cgImage else {
|
|
9
|
+
// CGImage is not available
|
|
10
|
+
return nil
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
guard let colorSpace = cgImage.colorSpace, let ctx = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: cgImage.bitsPerComponent, bytesPerRow: 0, space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else {
|
|
14
|
+
// Not able to create CGContext
|
|
15
|
+
return nil
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
var transform: CGAffineTransform = CGAffineTransform.identity
|
|
19
|
+
|
|
20
|
+
switch imageOrientation {
|
|
21
|
+
case .down, .downMirrored:
|
|
22
|
+
transform = transform.translatedBy(x: size.width, y: size.height)
|
|
23
|
+
transform = transform.rotated(by: CGFloat.pi)
|
|
24
|
+
print("down")
|
|
25
|
+
break
|
|
26
|
+
case .left, .leftMirrored:
|
|
27
|
+
transform = transform.translatedBy(x: size.width, y: 0)
|
|
28
|
+
transform = transform.rotated(by: CGFloat.pi / 2.0)
|
|
29
|
+
print("left")
|
|
30
|
+
break
|
|
31
|
+
case .right, .rightMirrored:
|
|
32
|
+
transform = transform.translatedBy(x: 0, y: size.height)
|
|
33
|
+
transform = transform.rotated(by: CGFloat.pi / -2.0)
|
|
34
|
+
print("right")
|
|
35
|
+
break
|
|
36
|
+
case .up, .upMirrored:
|
|
37
|
+
break
|
|
38
|
+
@unknown default:
|
|
39
|
+
break
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
ctx.concatenate(transform)
|
|
43
|
+
|
|
44
|
+
switch imageOrientation {
|
|
45
|
+
case .left, .leftMirrored, .right, .rightMirrored:
|
|
46
|
+
ctx.draw(self.cgImage!, in: CGRect(x: 0, y: 0, width: size.height, height: size.width))
|
|
47
|
+
default:
|
|
48
|
+
ctx.draw(self.cgImage!, in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
|
|
49
|
+
break
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
guard let newCGImage = ctx.makeImage() else { return nil }
|
|
53
|
+
|
|
54
|
+
return UIImage.init(cgImage: newCGImage, scale: 1, orientation: .up)
|
|
55
|
+
}
|
|
56
|
+
}
|