@capacitor-community/camera-preview 1.1.3 → 2.0.0-beta.2
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/CapacitorCommunityCameraPreview.podspec +3 -2
- package/LICENSE +21 -0
- package/README.md +47 -55
- package/android/build.gradle +4 -4
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraActivity.java +11 -1
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +127 -118
- package/dist/esm/definitions.d.ts +13 -5
- package/dist/esm/definitions.js +1 -0
- package/dist/esm/index.d.ts +3 -1
- package/dist/esm/index.js +5 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/web.d.ts +7 -1
- package/dist/esm/web.js +89 -74
- package/dist/esm/web.js.map +1 -1
- package/ios/Plugin/CameraController.swift +203 -90
- package/ios/Plugin/Plugin.m +3 -0
- package/ios/Plugin/Plugin.swift +133 -51
- package/ios/Plugin.xcodeproj/project.pbxproj +4 -8
- package/ios/Podfile +2 -2
- package/ios/Podfile.lock +5 -5
- package/package.json +6 -4
- package/android/.npmignore +0 -1
- package/android/local.properties +0 -8
- package/ios/.DS_Store +0 -0
- package/ios/Plugin.xcodeproj/xcuserdata/nielsvanharen.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
- package/ios/Plugin.xcworkspace/xcuserdata/arielhernandezmusa.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/ios/Plugin.xcworkspace/xcuserdata/nielsvanharen.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
package/dist/esm/web.js
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
-
});
|
|
9
|
-
};
|
|
10
1
|
import { WebPlugin } from "@capacitor/core";
|
|
11
2
|
export class CameraPreviewWeb extends WebPlugin {
|
|
12
3
|
constructor() {
|
|
@@ -15,85 +6,109 @@ export class CameraPreviewWeb extends WebPlugin {
|
|
|
15
6
|
platforms: ["web"],
|
|
16
7
|
});
|
|
17
8
|
}
|
|
18
|
-
start(options) {
|
|
19
|
-
return
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
9
|
+
async start(options) {
|
|
10
|
+
return new Promise(async (resolve, reject) => {
|
|
11
|
+
await navigator.mediaDevices.getUserMedia({
|
|
12
|
+
audio: !options.disableAudio,
|
|
13
|
+
video: true
|
|
14
|
+
}).then((stream) => {
|
|
15
|
+
// Stop any existing stream so we can request media with different constraints based on user input
|
|
16
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
17
|
+
}).catch(error => {
|
|
18
|
+
reject(error);
|
|
19
|
+
});
|
|
20
|
+
const video = document.getElementById("video");
|
|
21
|
+
const parent = document.getElementById(options.parent);
|
|
22
|
+
if (!video) {
|
|
23
|
+
const videoElement = document.createElement("video");
|
|
24
|
+
videoElement.id = "video";
|
|
25
|
+
videoElement.setAttribute("class", options.className || "");
|
|
26
|
+
// Don't flip video feed if camera is rear facing
|
|
27
|
+
if (options.position !== 'rear') {
|
|
28
28
|
videoElement.setAttribute("style", "-webkit-transform: scaleX(-1); transform: scaleX(-1);");
|
|
29
|
-
parent.appendChild(videoElement);
|
|
30
|
-
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
|
31
|
-
// Not adding `{ audio: true }` since we only want video now
|
|
32
|
-
navigator.mediaDevices.getUserMedia({ video: true }).then(function (stream) {
|
|
33
|
-
//video.src = window.URL.createObjectURL(stream);
|
|
34
|
-
videoElement.srcObject = stream;
|
|
35
|
-
videoElement.play();
|
|
36
|
-
resolve();
|
|
37
|
-
}, (err) => {
|
|
38
|
-
reject(err);
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
29
|
}
|
|
42
|
-
|
|
43
|
-
|
|
30
|
+
const userAgent = navigator.userAgent.toLowerCase();
|
|
31
|
+
const isSafari = userAgent.includes('safari') && !userAgent.includes('chrome');
|
|
32
|
+
// Safari on iOS needs to have the autoplay, muted and playsinline attributes set for video.play() to be successful
|
|
33
|
+
// Without these attributes videoElement.play() will throw a NotAllowedError
|
|
34
|
+
// https://developer.apple.com/documentation/webkit/delivering_video_content_for_safari
|
|
35
|
+
if (isSafari) {
|
|
36
|
+
videoElement.setAttribute('autoplay', 'true');
|
|
37
|
+
videoElement.setAttribute('muted', 'true');
|
|
38
|
+
videoElement.setAttribute('playsinline', 'true');
|
|
44
39
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
40
|
+
parent.appendChild(videoElement);
|
|
41
|
+
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
|
42
|
+
const constraints = {
|
|
43
|
+
video: true,
|
|
44
|
+
};
|
|
45
|
+
if (options.position === 'rear') {
|
|
46
|
+
constraints.video = { facingMode: 'environment' };
|
|
47
|
+
this.isBackCamera = true;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
this.isBackCamera = false;
|
|
51
|
+
}
|
|
52
|
+
navigator.mediaDevices.getUserMedia(constraints).then(function (stream) {
|
|
53
|
+
//video.src = window.URL.createObjectURL(stream);
|
|
54
|
+
videoElement.srcObject = stream;
|
|
55
|
+
videoElement.play();
|
|
56
|
+
resolve({});
|
|
57
|
+
}, (err) => {
|
|
58
|
+
reject(err);
|
|
59
|
+
});
|
|
58
60
|
}
|
|
59
|
-
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
reject({ message: "camera already started" });
|
|
60
64
|
}
|
|
61
65
|
});
|
|
62
66
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
async stop() {
|
|
68
|
+
const video = document.getElementById("video");
|
|
69
|
+
if (video) {
|
|
70
|
+
video.pause();
|
|
71
|
+
const st = video.srcObject;
|
|
72
|
+
const tracks = st.getTracks();
|
|
73
|
+
for (var i = 0; i < tracks.length; i++) {
|
|
74
|
+
var track = tracks[i];
|
|
75
|
+
track.stop();
|
|
76
|
+
}
|
|
77
|
+
video.remove();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async capture(_options) {
|
|
81
|
+
return new Promise((resolve, _) => {
|
|
82
|
+
const video = document.getElementById("video");
|
|
83
|
+
const canvas = document.createElement("canvas");
|
|
84
|
+
// video.width = video.offsetWidth;
|
|
85
|
+
const context = canvas.getContext("2d");
|
|
86
|
+
canvas.width = video.videoWidth;
|
|
87
|
+
canvas.height = video.videoHeight;
|
|
88
|
+
// flip horizontally back camera isn't used
|
|
89
|
+
if (!this.isBackCamera) {
|
|
72
90
|
context.translate(video.videoWidth, 0);
|
|
73
91
|
context.scale(-1, 1);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
92
|
+
}
|
|
93
|
+
context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
|
|
94
|
+
resolve({
|
|
95
|
+
value: canvas
|
|
96
|
+
.toDataURL("image/png")
|
|
97
|
+
.replace("data:image/png;base64,", ""),
|
|
80
98
|
});
|
|
81
99
|
});
|
|
82
100
|
}
|
|
83
|
-
|
|
84
|
-
return
|
|
85
|
-
throw new Error('getSupportedFlashModes not supported under the web platform');
|
|
86
|
-
});
|
|
101
|
+
async captureSample(_options) {
|
|
102
|
+
return this.capture(_options);
|
|
87
103
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
throw new Error('setFlashMode not supported under the web platform');
|
|
91
|
-
});
|
|
104
|
+
async getSupportedFlashModes() {
|
|
105
|
+
throw new Error('getSupportedFlashModes not supported under the web platform');
|
|
92
106
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
107
|
+
async setFlashMode(_options) {
|
|
108
|
+
throw new Error('setFlashMode not supported under the web platform');
|
|
109
|
+
}
|
|
110
|
+
async flip() {
|
|
111
|
+
throw new Error('flip not supported under the web platform');
|
|
97
112
|
}
|
|
98
113
|
}
|
|
99
114
|
const CameraPreview = new CameraPreviewWeb();
|
package/dist/esm/web.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"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"}
|
|
@@ -17,6 +17,7 @@ class CameraController: NSObject {
|
|
|
17
17
|
var frontCamera: AVCaptureDevice?
|
|
18
18
|
var frontCameraInput: AVCaptureDeviceInput?
|
|
19
19
|
|
|
20
|
+
var dataOutput: AVCaptureVideoDataOutput?
|
|
20
21
|
var photoOutput: AVCapturePhotoOutput?
|
|
21
22
|
|
|
22
23
|
var rearCamera: AVCaptureDevice?
|
|
@@ -26,6 +27,13 @@ class CameraController: NSObject {
|
|
|
26
27
|
|
|
27
28
|
var flashMode = AVCaptureDevice.FlashMode.off
|
|
28
29
|
var photoCaptureCompletionBlock: ((UIImage?, Error?) -> Void)?
|
|
30
|
+
|
|
31
|
+
var sampleBufferCaptureCompletionBlock: ((UIImage?, Error?) -> Void)?
|
|
32
|
+
|
|
33
|
+
var highResolutionOutput: Bool = false
|
|
34
|
+
|
|
35
|
+
var audioDevice: AVCaptureDevice?
|
|
36
|
+
var audioInput: AVCaptureDeviceInput?
|
|
29
37
|
}
|
|
30
38
|
|
|
31
39
|
extension CameraController {
|
|
@@ -33,209 +41,237 @@ extension CameraController {
|
|
|
33
41
|
func createCaptureSession() {
|
|
34
42
|
self.captureSession = AVCaptureSession()
|
|
35
43
|
}
|
|
36
|
-
|
|
44
|
+
|
|
37
45
|
func configureCaptureDevices() throws {
|
|
38
|
-
|
|
46
|
+
|
|
39
47
|
let session = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaType.video, position: .unspecified)
|
|
40
|
-
|
|
48
|
+
|
|
41
49
|
let cameras = session.devices.compactMap { $0 }
|
|
42
50
|
guard !cameras.isEmpty else { throw CameraControllerError.noCamerasAvailable }
|
|
43
|
-
|
|
51
|
+
|
|
44
52
|
for camera in cameras {
|
|
45
53
|
if camera.position == .front {
|
|
46
54
|
self.frontCamera = camera
|
|
47
55
|
}
|
|
48
|
-
|
|
56
|
+
|
|
49
57
|
if camera.position == .back {
|
|
50
58
|
self.rearCamera = camera
|
|
51
|
-
|
|
59
|
+
|
|
52
60
|
try camera.lockForConfiguration()
|
|
53
61
|
camera.focusMode = .continuousAutoFocus
|
|
54
62
|
camera.unlockForConfiguration()
|
|
55
63
|
}
|
|
56
64
|
}
|
|
65
|
+
self.audioDevice = AVCaptureDevice.default(for: AVMediaType.audio)
|
|
57
66
|
}
|
|
58
|
-
|
|
67
|
+
|
|
59
68
|
func configureDeviceInputs() throws {
|
|
60
69
|
guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing }
|
|
61
|
-
|
|
70
|
+
|
|
62
71
|
if cameraPosition == "rear" {
|
|
63
72
|
if let rearCamera = self.rearCamera {
|
|
64
73
|
self.rearCameraInput = try AVCaptureDeviceInput(device: rearCamera)
|
|
65
|
-
|
|
74
|
+
|
|
66
75
|
if captureSession.canAddInput(self.rearCameraInput!) { captureSession.addInput(self.rearCameraInput!) }
|
|
67
|
-
|
|
76
|
+
|
|
68
77
|
self.currentCameraPosition = .rear
|
|
69
78
|
}
|
|
70
79
|
} else if cameraPosition == "front" {
|
|
71
80
|
if let frontCamera = self.frontCamera {
|
|
72
81
|
self.frontCameraInput = try AVCaptureDeviceInput(device: frontCamera)
|
|
73
|
-
|
|
82
|
+
|
|
74
83
|
if captureSession.canAddInput(self.frontCameraInput!) { captureSession.addInput(self.frontCameraInput!) }
|
|
75
84
|
else { throw CameraControllerError.inputsAreInvalid }
|
|
76
|
-
|
|
85
|
+
|
|
77
86
|
self.currentCameraPosition = .front
|
|
78
87
|
}
|
|
79
88
|
} else { throw CameraControllerError.noCamerasAvailable }
|
|
89
|
+
|
|
90
|
+
// Add audio input
|
|
91
|
+
if let audioDevice = self.audioDevice {
|
|
92
|
+
self.audioInput = try AVCaptureDeviceInput(device: audioDevice)
|
|
93
|
+
if captureSession.canAddInput(self.audioInput!) {
|
|
94
|
+
captureSession.addInput(self.audioInput!)
|
|
95
|
+
} else {
|
|
96
|
+
throw CameraControllerError.inputsAreInvalid
|
|
97
|
+
}
|
|
98
|
+
}
|
|
80
99
|
}
|
|
81
|
-
|
|
100
|
+
|
|
82
101
|
func configurePhotoOutput() throws {
|
|
83
102
|
guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing }
|
|
84
|
-
|
|
103
|
+
|
|
85
104
|
self.photoOutput = AVCapturePhotoOutput()
|
|
86
105
|
self.photoOutput!.setPreparedPhotoSettingsArray([AVCapturePhotoSettings(format: [AVVideoCodecKey : AVVideoCodecType.jpeg])], completionHandler: nil)
|
|
106
|
+
self.photoOutput?.isHighResolutionCaptureEnabled = self.highResolutionOutput
|
|
87
107
|
if captureSession.canAddOutput(self.photoOutput!) { captureSession.addOutput(self.photoOutput!) }
|
|
88
108
|
captureSession.startRunning()
|
|
89
109
|
}
|
|
90
|
-
|
|
110
|
+
|
|
111
|
+
func configureDataOutput() throws {
|
|
112
|
+
guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing }
|
|
113
|
+
|
|
114
|
+
self.dataOutput = AVCaptureVideoDataOutput()
|
|
115
|
+
self.dataOutput?.videoSettings = [
|
|
116
|
+
(kCVPixelBufferPixelFormatTypeKey as String): NSNumber(value: kCVPixelFormatType_32BGRA as UInt32)
|
|
117
|
+
]
|
|
118
|
+
self.dataOutput?.alwaysDiscardsLateVideoFrames = true
|
|
119
|
+
if captureSession.canAddOutput(self.dataOutput!) {
|
|
120
|
+
captureSession.addOutput(self.dataOutput!)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
captureSession.commitConfiguration()
|
|
124
|
+
|
|
125
|
+
let queue = DispatchQueue(label: "DataOutput", attributes: [])
|
|
126
|
+
self.dataOutput?.setSampleBufferDelegate(self, queue: queue)
|
|
127
|
+
}
|
|
128
|
+
|
|
91
129
|
DispatchQueue(label: "prepare").async {
|
|
92
130
|
do {
|
|
93
131
|
createCaptureSession()
|
|
94
132
|
try configureCaptureDevices()
|
|
95
133
|
try configureDeviceInputs()
|
|
96
134
|
try configurePhotoOutput()
|
|
135
|
+
try configureDataOutput()
|
|
136
|
+
//try configureVideoOutput()
|
|
97
137
|
}
|
|
98
|
-
|
|
138
|
+
|
|
99
139
|
catch {
|
|
100
140
|
DispatchQueue.main.async {
|
|
101
141
|
completionHandler(error)
|
|
102
142
|
}
|
|
103
|
-
|
|
143
|
+
|
|
104
144
|
return
|
|
105
145
|
}
|
|
106
|
-
|
|
146
|
+
|
|
107
147
|
DispatchQueue.main.async {
|
|
148
|
+
self.updateVideoOrientation()
|
|
149
|
+
|
|
108
150
|
completionHandler(nil)
|
|
109
151
|
}
|
|
110
152
|
}
|
|
111
153
|
}
|
|
112
|
-
|
|
154
|
+
|
|
113
155
|
func displayPreview(on view: UIView) throws {
|
|
114
156
|
guard let captureSession = self.captureSession, captureSession.isRunning else { throw CameraControllerError.captureSessionIsMissing }
|
|
115
|
-
|
|
157
|
+
|
|
116
158
|
self.previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
|
|
117
159
|
self.previewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
160
|
+
|
|
161
|
+
view.layer.insertSublayer(self.previewLayer!, at: 0)
|
|
162
|
+
self.previewLayer?.frame = view.frame
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
func updateVideoOrientation() {
|
|
166
|
+
assert(Thread.isMainThread) // UIApplication.statusBarOrientation requires the main thread.
|
|
167
|
+
|
|
168
|
+
let videoOrientation: AVCaptureVideoOrientation
|
|
169
|
+
switch UIDevice.current.orientation {
|
|
122
170
|
case .portrait:
|
|
123
|
-
|
|
124
|
-
case .landscapeRight:
|
|
125
|
-
self.previewLayer?.connection?.videoOrientation = .landscapeLeft
|
|
171
|
+
videoOrientation = .portrait
|
|
126
172
|
case .landscapeLeft:
|
|
127
|
-
|
|
173
|
+
videoOrientation = .landscapeRight
|
|
174
|
+
case .landscapeRight:
|
|
175
|
+
videoOrientation = .landscapeLeft
|
|
128
176
|
case .portraitUpsideDown:
|
|
129
|
-
|
|
130
|
-
case .faceUp, .faceDown:
|
|
131
|
-
|
|
177
|
+
videoOrientation = .portraitUpsideDown
|
|
178
|
+
case .faceUp, .faceDown, .unknown:
|
|
179
|
+
fallthrough
|
|
180
|
+
@unknown default:
|
|
181
|
+
switch UIApplication.shared.statusBarOrientation {
|
|
132
182
|
case .portrait:
|
|
133
|
-
|
|
134
|
-
case .landscapeRight:
|
|
135
|
-
self.previewLayer?.connection?.videoOrientation = .landscapeRight
|
|
183
|
+
videoOrientation = .portrait
|
|
136
184
|
case .landscapeLeft:
|
|
137
|
-
|
|
185
|
+
videoOrientation = .landscapeLeft
|
|
186
|
+
case .landscapeRight:
|
|
187
|
+
videoOrientation = .landscapeRight
|
|
138
188
|
case .portraitUpsideDown:
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
189
|
+
videoOrientation = .portraitUpsideDown
|
|
190
|
+
case .unknown:
|
|
191
|
+
fallthrough
|
|
192
|
+
@unknown default:
|
|
193
|
+
videoOrientation = .portrait
|
|
142
194
|
}
|
|
143
|
-
default:
|
|
144
|
-
self.previewLayer?.connection?.videoOrientation = .portrait
|
|
145
195
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
196
|
+
|
|
197
|
+
previewLayer?.connection?.videoOrientation = videoOrientation
|
|
198
|
+
dataOutput?.connections.forEach { $0.videoOrientation = videoOrientation }
|
|
149
199
|
}
|
|
150
|
-
|
|
200
|
+
|
|
151
201
|
func switchCameras() throws {
|
|
152
202
|
guard let currentCameraPosition = currentCameraPosition, let captureSession = self.captureSession, captureSession.isRunning else { throw CameraControllerError.captureSessionIsMissing }
|
|
153
|
-
|
|
203
|
+
|
|
154
204
|
captureSession.beginConfiguration()
|
|
155
|
-
|
|
205
|
+
|
|
156
206
|
func switchToFrontCamera() throws {
|
|
157
|
-
|
|
207
|
+
|
|
158
208
|
guard let rearCameraInput = self.rearCameraInput, captureSession.inputs.contains(rearCameraInput),
|
|
159
209
|
let frontCamera = self.frontCamera else { throw CameraControllerError.invalidOperation }
|
|
160
|
-
|
|
210
|
+
|
|
161
211
|
self.frontCameraInput = try AVCaptureDeviceInput(device: frontCamera)
|
|
162
|
-
|
|
212
|
+
|
|
163
213
|
captureSession.removeInput(rearCameraInput)
|
|
164
|
-
|
|
214
|
+
|
|
165
215
|
if captureSession.canAddInput(self.frontCameraInput!) {
|
|
166
216
|
captureSession.addInput(self.frontCameraInput!)
|
|
167
|
-
|
|
217
|
+
|
|
168
218
|
self.currentCameraPosition = .front
|
|
169
219
|
}
|
|
170
|
-
|
|
220
|
+
|
|
171
221
|
else {
|
|
172
222
|
throw CameraControllerError.invalidOperation
|
|
173
223
|
}
|
|
174
224
|
}
|
|
175
|
-
|
|
225
|
+
|
|
176
226
|
func switchToRearCamera() throws {
|
|
177
|
-
|
|
227
|
+
|
|
178
228
|
guard let frontCameraInput = self.frontCameraInput, captureSession.inputs.contains(frontCameraInput),
|
|
179
229
|
let rearCamera = self.rearCamera else { throw CameraControllerError.invalidOperation }
|
|
180
|
-
|
|
230
|
+
|
|
181
231
|
self.rearCameraInput = try AVCaptureDeviceInput(device: rearCamera)
|
|
182
|
-
|
|
232
|
+
|
|
183
233
|
captureSession.removeInput(frontCameraInput)
|
|
184
|
-
|
|
234
|
+
|
|
185
235
|
if captureSession.canAddInput(self.rearCameraInput!) {
|
|
186
236
|
captureSession.addInput(self.rearCameraInput!)
|
|
187
|
-
|
|
237
|
+
|
|
188
238
|
self.currentCameraPosition = .rear
|
|
189
239
|
}
|
|
190
|
-
|
|
240
|
+
|
|
191
241
|
else { throw CameraControllerError.invalidOperation }
|
|
192
242
|
}
|
|
193
|
-
|
|
243
|
+
|
|
194
244
|
switch currentCameraPosition {
|
|
195
245
|
case .front:
|
|
196
246
|
try switchToRearCamera()
|
|
197
|
-
|
|
247
|
+
|
|
198
248
|
case .rear:
|
|
199
249
|
try switchToFrontCamera()
|
|
200
250
|
}
|
|
201
|
-
|
|
251
|
+
|
|
202
252
|
captureSession.commitConfiguration()
|
|
203
253
|
}
|
|
204
|
-
|
|
254
|
+
|
|
205
255
|
func captureImage(completion: @escaping (UIImage?, Error?) -> Void) {
|
|
206
256
|
guard let captureSession = captureSession, captureSession.isRunning else { completion(nil, CameraControllerError.captureSessionIsMissing); return }
|
|
207
257
|
let settings = AVCapturePhotoSettings()
|
|
258
|
+
|
|
208
259
|
settings.flashMode = self.flashMode
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
let statusBarOrientation = UIApplication.shared.statusBarOrientation
|
|
212
|
-
if deviceOrientation == .portrait {
|
|
213
|
-
self.photoOutput?.connection(with: AVMediaType.video)?.videoOrientation = AVCaptureVideoOrientation.portrait
|
|
214
|
-
}else if (deviceOrientation == .landscapeLeft){
|
|
215
|
-
self.photoOutput?.connection(with: AVMediaType.video)?.videoOrientation = AVCaptureVideoOrientation.landscapeRight
|
|
216
|
-
}else if (deviceOrientation == .landscapeRight){
|
|
217
|
-
self.photoOutput?.connection(with: AVMediaType.video)?.videoOrientation = AVCaptureVideoOrientation.landscapeLeft
|
|
218
|
-
}else if (deviceOrientation == .portraitUpsideDown){
|
|
219
|
-
self.photoOutput?.connection(with: AVMediaType.video)?.videoOrientation = AVCaptureVideoOrientation.portraitUpsideDown
|
|
220
|
-
}else if (deviceOrientation == .faceUp || deviceOrientation == .faceDown){
|
|
221
|
-
switch (statusBarOrientation) {
|
|
222
|
-
case .portrait:
|
|
223
|
-
self.photoOutput?.connection(with: AVMediaType.video)?.videoOrientation = AVCaptureVideoOrientation.portrait
|
|
224
|
-
case .landscapeRight:
|
|
225
|
-
self.photoOutput?.connection(with: AVMediaType.video)?.videoOrientation = AVCaptureVideoOrientation.landscapeRight
|
|
226
|
-
case .landscapeLeft:
|
|
227
|
-
self.photoOutput?.connection(with: AVMediaType.video)?.videoOrientation = AVCaptureVideoOrientation.landscapeLeft
|
|
228
|
-
case .portraitUpsideDown:
|
|
229
|
-
self.photoOutput?.connection(with: AVMediaType.video)?.videoOrientation = AVCaptureVideoOrientation.portraitUpsideDown
|
|
230
|
-
default:
|
|
231
|
-
self.photoOutput?.connection(with: AVMediaType.video)?.videoOrientation = AVCaptureVideoOrientation.portrait
|
|
232
|
-
}
|
|
233
|
-
}else {
|
|
234
|
-
self.photoOutput?.connection(with: AVMediaType.video)?.videoOrientation = AVCaptureVideoOrientation.portrait
|
|
235
|
-
}
|
|
260
|
+
settings.isHighResolutionPhotoEnabled = self.highResolutionOutput;
|
|
261
|
+
|
|
236
262
|
self.photoOutput?.capturePhoto(with: settings, delegate: self)
|
|
237
263
|
self.photoCaptureCompletionBlock = completion
|
|
238
264
|
}
|
|
265
|
+
|
|
266
|
+
func captureSample(completion: @escaping (UIImage?, Error?) -> Void) {
|
|
267
|
+
guard let captureSession = captureSession,
|
|
268
|
+
captureSession.isRunning else {
|
|
269
|
+
completion(nil, CameraControllerError.captureSessionIsMissing)
|
|
270
|
+
return
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
self.sampleBufferCaptureCompletionBlock = completion
|
|
274
|
+
}
|
|
239
275
|
|
|
240
276
|
func getSupportedFlashModes() throws -> [String] {
|
|
241
277
|
var currentCamera: AVCaptureDevice?
|
|
@@ -353,6 +389,31 @@ extension CameraController {
|
|
|
353
389
|
}
|
|
354
390
|
|
|
355
391
|
}
|
|
392
|
+
|
|
393
|
+
func captureVideo(completion: @escaping (URL?, Error?) -> Void) {
|
|
394
|
+
guard let captureSession = self.captureSession, captureSession.isRunning else {
|
|
395
|
+
completion(nil, CameraControllerError.captureSessionIsMissing)
|
|
396
|
+
return
|
|
397
|
+
}
|
|
398
|
+
let path = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
|
|
399
|
+
let identifier = UUID()
|
|
400
|
+
let randomIdentifier = identifier.uuidString.replacingOccurrences(of: "-", with: "")
|
|
401
|
+
let finalIdentifier = String(randomIdentifier.prefix(8))
|
|
402
|
+
let fileName="cpcp_video_"+finalIdentifier+".mp4"
|
|
403
|
+
|
|
404
|
+
let fileUrl = path.appendingPathComponent(fileName)
|
|
405
|
+
try? FileManager.default.removeItem(at: fileUrl)
|
|
406
|
+
/*videoOutput!.startRecording(to: fileUrl, recordingDelegate: self)
|
|
407
|
+
self.videoRecordCompletionBlock = completion*/
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
func stopRecording(completion: @escaping (Error?) -> Void) {
|
|
411
|
+
guard let captureSession = self.captureSession, captureSession.isRunning else {
|
|
412
|
+
completion(CameraControllerError.captureSessionIsMissing)
|
|
413
|
+
return
|
|
414
|
+
}
|
|
415
|
+
//self.videoOutput?.stopRecording()
|
|
416
|
+
}
|
|
356
417
|
}
|
|
357
418
|
|
|
358
419
|
extension CameraController: AVCapturePhotoCaptureDelegate {
|
|
@@ -371,6 +432,48 @@ extension CameraController: AVCapturePhotoCaptureDelegate {
|
|
|
371
432
|
}
|
|
372
433
|
}
|
|
373
434
|
|
|
435
|
+
extension CameraController: AVCaptureVideoDataOutputSampleBufferDelegate {
|
|
436
|
+
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
|
|
437
|
+
guard let completion = sampleBufferCaptureCompletionBlock else { return }
|
|
438
|
+
|
|
439
|
+
guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
|
|
440
|
+
completion(nil, CameraControllerError.unknown)
|
|
441
|
+
return
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
CVPixelBufferLockBaseAddress(imageBuffer, .readOnly)
|
|
445
|
+
defer { CVPixelBufferUnlockBaseAddress(imageBuffer, .readOnly) }
|
|
446
|
+
|
|
447
|
+
let baseAddress = CVPixelBufferGetBaseAddress(imageBuffer)
|
|
448
|
+
let bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer)
|
|
449
|
+
let width = CVPixelBufferGetWidth(imageBuffer)
|
|
450
|
+
let height = CVPixelBufferGetHeight(imageBuffer)
|
|
451
|
+
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
|
452
|
+
let bitmapInfo: UInt32 = CGBitmapInfo.byteOrder32Little.rawValue |
|
|
453
|
+
CGImageAlphaInfo.premultipliedFirst.rawValue
|
|
454
|
+
|
|
455
|
+
let context = CGContext(
|
|
456
|
+
data: baseAddress,
|
|
457
|
+
width: width,
|
|
458
|
+
height: height,
|
|
459
|
+
bitsPerComponent: 8,
|
|
460
|
+
bytesPerRow: bytesPerRow,
|
|
461
|
+
space: colorSpace,
|
|
462
|
+
bitmapInfo: bitmapInfo
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
guard let cgImage = context?.makeImage() else {
|
|
466
|
+
completion(nil, CameraControllerError.unknown)
|
|
467
|
+
return
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
let image = UIImage(cgImage: cgImage)
|
|
471
|
+
completion(image.fixedOrientation(), nil)
|
|
472
|
+
|
|
473
|
+
sampleBufferCaptureCompletionBlock = nil
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
374
477
|
|
|
375
478
|
|
|
376
479
|
|
|
@@ -473,3 +576,13 @@ extension UIImage {
|
|
|
473
576
|
return UIImage.init(cgImage: newCGImage, scale: 1, orientation: .up)
|
|
474
577
|
}
|
|
475
578
|
}
|
|
579
|
+
|
|
580
|
+
extension CameraController: AVCaptureFileOutputRecordingDelegate {
|
|
581
|
+
func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
|
|
582
|
+
/*if error == nil {
|
|
583
|
+
self.videoRecordCompletionBlock?(outputFileURL, nil)
|
|
584
|
+
} else {
|
|
585
|
+
self.videoRecordCompletionBlock?(nil, error)
|
|
586
|
+
}*/
|
|
587
|
+
}
|
|
588
|
+
}
|
package/ios/Plugin/Plugin.m
CHANGED
|
@@ -7,7 +7,10 @@ CAP_PLUGIN(CameraPreview, "CameraPreview",
|
|
|
7
7
|
CAP_PLUGIN_METHOD(start, CAPPluginReturnPromise);
|
|
8
8
|
CAP_PLUGIN_METHOD(stop, CAPPluginReturnPromise);
|
|
9
9
|
CAP_PLUGIN_METHOD(capture, CAPPluginReturnPromise);
|
|
10
|
+
CAP_PLUGIN_METHOD(captureSample, CAPPluginReturnPromise);
|
|
10
11
|
CAP_PLUGIN_METHOD(flip, CAPPluginReturnPromise);
|
|
11
12
|
CAP_PLUGIN_METHOD(getSupportedFlashModes, CAPPluginReturnPromise);
|
|
12
13
|
CAP_PLUGIN_METHOD(setFlashMode, CAPPluginReturnPromise);
|
|
14
|
+
CAP_PLUGIN_METHOD(startRecordVideo, CAPPluginReturnPromise);
|
|
15
|
+
CAP_PLUGIN_METHOD(stopRecordVideo, CAPPluginReturnPromise);
|
|
13
16
|
)
|