Skip to content

Commit bac4f6e

Browse files
committed
1 parent 131a111 commit bac4f6e

File tree

6 files changed

+100
-84
lines changed

6 files changed

+100
-84
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ Starts the camera preview instance.
100100
| lockAndroidOrientation | boolean | (optional) Locks device orientation when camera is showing, default false. (applicable to Android only) |
101101
| enableOpacity | boolean | (optional) Make the camera preview see-through. Ideal for augmented reality uses. Default false (applicable to Android and web only)
102102
| enableZoom | boolean | (optional) Set if you can pinch to zoom. Default false (applicable to the android and ios platforms only)
103+
| mirrorVideo | boolean | (optional) Set if the video should be mirrored to match the preview. Defaults to false (applicable to the iOS platform only)
103104

104105
<!-- <strong>Options:</strong>
105106
All options stated are optional and will default to values here
@@ -168,6 +169,12 @@ Ex: VueJS >> App.vue component
168169
CameraPreview.stop();
169170
```
170171
172+
### resume() ---- iOS only
173+
<info>Resumes the camera preview without having to reinitialize. (in case it was interrupted)</info>
174+
```javascript
175+
CameraPreview.resume()
176+
```
177+
171178
### flip()
172179
<info>Switch between rear and front camera only for android and ios, web is not supported</info>
173180
```javascript
@@ -284,6 +291,7 @@ CameraPreview.startRecordVideo(cameraPreviewOptions);
284291
285292
```javascript
286293
const resultRecordVideo = await CameraPreview.stopRecordVideo();
294+
const videoPath = resultRecordVideo.videoFilePath;
287295
```
288296
289297
### setOpacity(options: CameraOpacityOptions): Promise<{}>; ---- ANDROID only

ios/Plugin/CameraController.swift

Lines changed: 53 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class CameraController: NSObject {
1717
var frontCamera: AVCaptureDevice?
1818
var frontCameraInput: AVCaptureDeviceInput?
1919

20-
var dataOutput: AVCaptureVideoDataOutput?
20+
var videoOutput: AVCaptureMovieFileOutput?
2121
var photoOutput: AVCapturePhotoOutput?
2222

2323
var rearCamera: AVCaptureDevice?
@@ -27,6 +27,7 @@ class CameraController: NSObject {
2727

2828
var flashMode = AVCaptureDevice.FlashMode.off
2929
var photoCaptureCompletionBlock: ((UIImage?, Error?) -> Void)?
30+
var videoCaptureCompletionBlock: ((URL?, Error?) -> Void)?
3031

3132
var sampleBufferCaptureCompletionBlock: ((UIImage?, Error?) -> Void)?
3233

@@ -42,6 +43,7 @@ extension CameraController {
4243
func prepare(cameraPosition: String, disableAudio: Bool, completionHandler: @escaping (Error?) -> Void) {
4344
func createCaptureSession() {
4445
self.captureSession = AVCaptureSession()
46+
self.captureSession?.beginConfiguration()
4547
}
4648

4749
func configureCaptureDevices() throws {
@@ -110,25 +112,18 @@ extension CameraController {
110112
self.photoOutput!.setPreparedPhotoSettingsArray([AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.jpeg])], completionHandler: nil)
111113
self.photoOutput?.isHighResolutionCaptureEnabled = self.highResolutionOutput
112114
if captureSession.canAddOutput(self.photoOutput!) { captureSession.addOutput(self.photoOutput!) }
113-
captureSession.startRunning()
114115
}
115116

116-
func configureDataOutput() throws {
117+
func configureVideoOutput() throws {
117118
guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing }
118119

119-
self.dataOutput = AVCaptureVideoDataOutput()
120-
self.dataOutput?.videoSettings = [
121-
(kCVPixelBufferPixelFormatTypeKey as String): NSNumber(value: kCVPixelFormatType_32BGRA as UInt32)
122-
]
123-
self.dataOutput?.alwaysDiscardsLateVideoFrames = true
124-
if captureSession.canAddOutput(self.dataOutput!) {
125-
captureSession.addOutput(self.dataOutput!)
126-
}
127-
128-
captureSession.commitConfiguration()
120+
self.videoOutput = AVCaptureMovieFileOutput()
129121

130-
let queue = DispatchQueue(label: "DataOutput", attributes: [])
131-
self.dataOutput?.setSampleBufferDelegate(self, queue: queue)
122+
if captureSession.canAddOutput(self.videoOutput!) {
123+
captureSession.addOutput(self.videoOutput!)
124+
} else {
125+
throw CameraControllerError.invalidOperation
126+
}
132127
}
133128

134129
DispatchQueue(label: "prepare").async {
@@ -138,7 +133,9 @@ extension CameraController {
138133
try configureDeviceInputs()
139134
try configurePhotoOutput()
140135
try configureDataOutput()
141-
// try configureVideoOutput()
136+
try configureVideoOutput()
137+
self.captureSession?.commitConfiguration()
138+
self.captureSession?.startRunning()
142139
} catch {
143140
DispatchQueue.main.async {
144141
completionHandler(error)
@@ -153,6 +150,21 @@ extension CameraController {
153150
}
154151
}
155152

153+
func resume(completionHandler: @escaping (Error?) -> Void) {
154+
guard let captureSession = self.captureSession else {
155+
completionHandler(CameraControllerError.captureSessionIsMissing)
156+
return
157+
}
158+
DispatchQueue(label: "prepare").async {
159+
if(!captureSession.isRunning){
160+
captureSession.startRunning()
161+
}
162+
DispatchQueue.main.async {
163+
completionHandler(nil)
164+
}
165+
}
166+
}
167+
156168
func displayPreview(on view: UIView) throws {
157169
guard let captureSession = self.captureSession, captureSession.isRunning else { throw CameraControllerError.captureSessionIsMissing }
158170

@@ -204,7 +216,8 @@ extension CameraController {
204216
}
205217

206218
previewLayer?.connection?.videoOrientation = videoOrientation
207-
dataOutput?.connections.forEach { $0.videoOrientation = videoOrientation }
219+
//Orientation is not supported for video connections
220+
//videoOutput?.connections.forEach { $0.videoOrientation = videoOrientation }
208221
photoOutput?.connections.forEach { $0.videoOrientation = videoOrientation }
209222
}
210223

@@ -396,9 +409,9 @@ extension CameraController {
396409

397410
}
398411

399-
func captureVideo(completion: @escaping (URL?, Error?) -> Void) {
412+
func captureVideo(mirror: Bool = false, completion: @escaping (URL?, Error?) -> Void) {
400413
guard let captureSession = self.captureSession, captureSession.isRunning else {
401-
completion(nil, CameraControllerError.captureSessionIsMissing)
414+
completion(CameraControllerError.captureSessionIsMissing)
402415
return
403416
}
404417
let path = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
@@ -409,16 +422,27 @@ extension CameraController {
409422

410423
let fileUrl = path.appendingPathComponent(fileName)
411424
try? FileManager.default.removeItem(at: fileUrl)
412-
/*videoOutput!.startRecording(to: fileUrl, recordingDelegate: self)
413-
self.videoRecordCompletionBlock = completion*/
425+
426+
if mirror {
427+
if let connection = videoOutput?.connection(with: AVMediaType.video), connection.isVideoOrientationSupported {
428+
connection.isVideoMirrored = true
429+
} else {
430+
completion(CameraControllerError.invalidOperation)
431+
return
432+
}
433+
}
434+
435+
videoOutput!.startRecording(to: fileUrl, recordingDelegate: self)
436+
completion(nil)
414437
}
415438

416-
func stopRecording(completion: @escaping (Error?) -> Void) {
439+
func stopRecording(completion: @escaping (URL?, Error?) -> Void) {
417440
guard let captureSession = self.captureSession, captureSession.isRunning else {
418-
completion(CameraControllerError.captureSessionIsMissing)
441+
completion(nil, CameraControllerError.captureSessionIsMissing)
419442
return
420443
}
421-
// self.videoOutput?.stopRecording()
444+
self.videoCaptureCompletionBlock = completion
445+
self.videoOutput?.stopRecording()
422446
}
423447
}
424448

@@ -495,48 +519,6 @@ extension CameraController: AVCapturePhotoCaptureDelegate {
495519
}
496520
}
497521

498-
extension CameraController: AVCaptureVideoDataOutputSampleBufferDelegate {
499-
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
500-
guard let completion = sampleBufferCaptureCompletionBlock else { return }
501-
502-
guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
503-
completion(nil, CameraControllerError.unknown)
504-
return
505-
}
506-
507-
CVPixelBufferLockBaseAddress(imageBuffer, .readOnly)
508-
defer { CVPixelBufferUnlockBaseAddress(imageBuffer, .readOnly) }
509-
510-
let baseAddress = CVPixelBufferGetBaseAddress(imageBuffer)
511-
let bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer)
512-
let width = CVPixelBufferGetWidth(imageBuffer)
513-
let height = CVPixelBufferGetHeight(imageBuffer)
514-
let colorSpace = CGColorSpaceCreateDeviceRGB()
515-
let bitmapInfo: UInt32 = CGBitmapInfo.byteOrder32Little.rawValue |
516-
CGImageAlphaInfo.premultipliedFirst.rawValue
517-
518-
let context = CGContext(
519-
data: baseAddress,
520-
width: width,
521-
height: height,
522-
bitsPerComponent: 8,
523-
bytesPerRow: bytesPerRow,
524-
space: colorSpace,
525-
bitmapInfo: bitmapInfo
526-
)
527-
528-
guard let cgImage = context?.makeImage() else {
529-
completion(nil, CameraControllerError.unknown)
530-
return
531-
}
532-
533-
let image = UIImage(cgImage: cgImage)
534-
completion(image.fixedOrientation(), nil)
535-
536-
sampleBufferCaptureCompletionBlock = nil
537-
}
538-
}
539-
540522
enum CameraControllerError: Swift.Error {
541523
case captureSessionAlreadyRunning
542524
case captureSessionIsMissing
@@ -639,10 +621,10 @@ extension UIImage {
639621

640622
extension CameraController: AVCaptureFileOutputRecordingDelegate {
641623
func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
642-
/*if error == nil {
643-
self.videoRecordCompletionBlock?(outputFileURL, nil)
644-
} else {
645-
self.videoRecordCompletionBlock?(nil, error)
646-
}*/
624+
if error == nil {
625+
self.videoCaptureCompletionBlock?(outputFileURL, nil)
626+
} else {
627+
self.videoCaptureCompletionBlock?(nil, error)
628+
}
647629
}
648630
}

ios/Plugin/Plugin.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
CAP_PLUGIN(CameraPreview, "CameraPreview",
77
CAP_PLUGIN_METHOD(start, CAPPluginReturnPromise);
88
CAP_PLUGIN_METHOD(stop, CAPPluginReturnPromise);
9+
CAP_PLUGIN_METHOD(resume, CAPPluginReturnPromise);
910
CAP_PLUGIN_METHOD(capture, CAPPluginReturnPromise);
1011
CAP_PLUGIN_METHOD(captureSample, CAPPluginReturnPromise);
1112
CAP_PLUGIN_METHOD(flip, CAPPluginReturnPromise);

ios/Plugin/Plugin.swift

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public class CameraPreview: CAPPlugin {
2222
var enableZoom: Bool?
2323
var highResolutionOutput: Bool = false
2424
var disableAudio: Bool = false
25+
var mirrorVideo: Bool = false
2526

2627
@objc func rotated() {
2728
let height = self.paddingBottom != nil ? self.height! - self.paddingBottom!: self.height!
@@ -67,6 +68,7 @@ public class CameraPreview: CAPPlugin {
6768
self.storeToFile = call.getBool("storeToFile") ?? false
6869
self.enableZoom = call.getBool("enableZoom") ?? false
6970
self.disableAudio = call.getBool("disableAudio") ?? false
71+
self.mirrorVideo = call.getBool("mirrorVideo") ?? false
7072

7173
AVCaptureDevice.requestAccess(for: .video, completionHandler: { (granted: Bool) in
7274
guard granted else {
@@ -218,6 +220,17 @@ public class CameraPreview: CAPPlugin {
218220
}
219221
}
220222

223+
@objc func resume(_ call: CAPPluginCall) {
224+
self.cameraController.resume() { error in
225+
if let error = error {
226+
print(error)
227+
call.reject(error.localizedDescription)
228+
return
229+
}
230+
call.resolve()
231+
}
232+
}
233+
221234
@objc func getSupportedFlashModes(_ call: CAPPluginCall) {
222235
do {
223236
let supportedFlashModes = try self.cameraController.getSupportedFlashModes()
@@ -261,30 +274,35 @@ public class CameraPreview: CAPPlugin {
261274
DispatchQueue.main.async {
262275

263276
let quality: Int? = call.getInt("quality", 85)
277+
self.mirrorVideo = call.getBool("mirrorVideo") ?? self.mirrorVideo
264278

265279
self.cameraController.captureVideo { (image, error) in
266-
267-
guard let image = image else {
268-
print(error ?? "Image capture error")
269-
guard let error = error else {
270-
call.reject("Image capture error")
271-
return
272-
}
273-
call.reject(error.localizedDescription)
280+
guard let error = error else {
281+
call.resolve()
274282
return
275283
}
276-
277-
// self.videoUrl = image
278-
284+
call.reject(error.localizedDescription)
279285
call.resolve(["value": image.absoluteString])
280286
}
281287
}
282288
}
283289

284290
@objc func stopRecordVideo(_ call: CAPPluginCall) {
285291

286-
self.cameraController.stopRecording { (_) in
292+
self.cameraController.stopRecording { (video, error) in
293+
guard let video = video else {
294+
print(error ?? "Video capture error")
295+
guard let error = error else {
296+
call.reject("Video capture error")
297+
return
298+
}
299+
call.reject(error.localizedDescription)
300+
return
301+
}
287302

303+
// self.videoUrl = image
304+
305+
call.resolve(["videoFilePath": video.absoluteString])
288306
}
289307
}
290308

src/definitions.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ export interface CameraPreviewOptions {
3434
enableOpacity?: boolean;
3535
/** Defaults to false - Android only. Set if camera preview will support pinch to zoom. */
3636
enableZoom?: boolean;
37+
/** Defaults to false - iOS only. Set if video recording will look the same as the preview. */
38+
mirrorVideo?: boolean;
3739
}
3840
export interface CameraPreviewPictureOptions {
3941
/** The picture height, optional, default 0 (Device default) */
@@ -62,7 +64,8 @@ export interface CameraPreviewPlugin {
6264
start(options: CameraPreviewOptions): Promise<{}>;
6365
startRecordVideo(options: CameraPreviewOptions): Promise<{}>;
6466
stop(): Promise<{}>;
65-
stopRecordVideo(): Promise<{}>;
67+
resume(): Promise<{} | never>;
68+
stopRecordVideo(): Promise<{ videoFilePath: string } | never>;
6669
capture(options: CameraPreviewPictureOptions): Promise<{ value: string }>;
6770
captureSample(options: CameraSampleOptions): Promise<{ value: string }>;
6871
getSupportedFlashModes(): Promise<{

src/web.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ export class CameraPreviewWeb extends WebPlugin implements CameraPreviewPlugin {
9898
});
9999
}
100100

101+
async resume(): Promise<{}> {
102+
throw this.unimplemented('Not implemented on web.');
103+
}
104+
101105
async startRecordVideo(): Promise<{}> {
102106
throw this.unimplemented('Not implemented on web.');
103107
}

0 commit comments

Comments
 (0)