Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
dantheman2865 committed Sep 8, 2023
1 parent 131a111 commit bac4f6e
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 84 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ Starts the camera preview instance.
| lockAndroidOrientation | boolean | (optional) Locks device orientation when camera is showing, default false. (applicable to Android only) |
| enableOpacity | boolean | (optional) Make the camera preview see-through. Ideal for augmented reality uses. Default false (applicable to Android and web only)
| enableZoom | boolean | (optional) Set if you can pinch to zoom. Default false (applicable to the android and ios platforms only)
| mirrorVideo | boolean | (optional) Set if the video should be mirrored to match the preview. Defaults to false (applicable to the iOS platform only)

<!-- <strong>Options:</strong>
All options stated are optional and will default to values here
Expand Down Expand Up @@ -168,6 +169,12 @@ Ex: VueJS >> App.vue component
CameraPreview.stop();
```
### resume() ---- iOS only
<info>Resumes the camera preview without having to reinitialize. (in case it was interrupted)</info>
```javascript
CameraPreview.resume()
```
### flip()
<info>Switch between rear and front camera only for android and ios, web is not supported</info>
```javascript
Expand Down Expand Up @@ -284,6 +291,7 @@ CameraPreview.startRecordVideo(cameraPreviewOptions);
```javascript
const resultRecordVideo = await CameraPreview.stopRecordVideo();
const videoPath = resultRecordVideo.videoFilePath;
```
### setOpacity(options: CameraOpacityOptions): Promise<{}>; ---- ANDROID only
Expand Down
124 changes: 53 additions & 71 deletions ios/Plugin/CameraController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class CameraController: NSObject {
var frontCamera: AVCaptureDevice?
var frontCameraInput: AVCaptureDeviceInput?

var dataOutput: AVCaptureVideoDataOutput?
var videoOutput: AVCaptureMovieFileOutput?
var photoOutput: AVCapturePhotoOutput?

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

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

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

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

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

func configureDataOutput() throws {
func configureVideoOutput() throws {
guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing }

self.dataOutput = AVCaptureVideoDataOutput()
self.dataOutput?.videoSettings = [
(kCVPixelBufferPixelFormatTypeKey as String): NSNumber(value: kCVPixelFormatType_32BGRA as UInt32)
]
self.dataOutput?.alwaysDiscardsLateVideoFrames = true
if captureSession.canAddOutput(self.dataOutput!) {
captureSession.addOutput(self.dataOutput!)
}

captureSession.commitConfiguration()
self.videoOutput = AVCaptureMovieFileOutput()

let queue = DispatchQueue(label: "DataOutput", attributes: [])
self.dataOutput?.setSampleBufferDelegate(self, queue: queue)
if captureSession.canAddOutput(self.videoOutput!) {
captureSession.addOutput(self.videoOutput!)
} else {
throw CameraControllerError.invalidOperation
}
}

DispatchQueue(label: "prepare").async {
Expand All @@ -138,7 +133,9 @@ extension CameraController {
try configureDeviceInputs()
try configurePhotoOutput()
try configureDataOutput()
// try configureVideoOutput()
try configureVideoOutput()
self.captureSession?.commitConfiguration()
self.captureSession?.startRunning()
} catch {
DispatchQueue.main.async {
completionHandler(error)
Expand All @@ -153,6 +150,21 @@ extension CameraController {
}
}

func resume(completionHandler: @escaping (Error?) -> Void) {
guard let captureSession = self.captureSession else {
completionHandler(CameraControllerError.captureSessionIsMissing)
return
}
DispatchQueue(label: "prepare").async {
if(!captureSession.isRunning){
captureSession.startRunning()
}
DispatchQueue.main.async {
completionHandler(nil)
}
}
}

func displayPreview(on view: UIView) throws {
guard let captureSession = self.captureSession, captureSession.isRunning else { throw CameraControllerError.captureSessionIsMissing }

Expand Down Expand Up @@ -204,7 +216,8 @@ extension CameraController {
}

previewLayer?.connection?.videoOrientation = videoOrientation
dataOutput?.connections.forEach { $0.videoOrientation = videoOrientation }
//Orientation is not supported for video connections
//videoOutput?.connections.forEach { $0.videoOrientation = videoOrientation }
photoOutput?.connections.forEach { $0.videoOrientation = videoOrientation }
}

Expand Down Expand Up @@ -396,9 +409,9 @@ extension CameraController {

}

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

let fileUrl = path.appendingPathComponent(fileName)
try? FileManager.default.removeItem(at: fileUrl)
/*videoOutput!.startRecording(to: fileUrl, recordingDelegate: self)
self.videoRecordCompletionBlock = completion*/

if mirror {
if let connection = videoOutput?.connection(with: AVMediaType.video), connection.isVideoOrientationSupported {
connection.isVideoMirrored = true
} else {
completion(CameraControllerError.invalidOperation)
return
}
}

videoOutput!.startRecording(to: fileUrl, recordingDelegate: self)
completion(nil)
}

func stopRecording(completion: @escaping (Error?) -> Void) {
func stopRecording(completion: @escaping (URL?, Error?) -> Void) {
guard let captureSession = self.captureSession, captureSession.isRunning else {
completion(CameraControllerError.captureSessionIsMissing)
completion(nil, CameraControllerError.captureSessionIsMissing)
return
}
// self.videoOutput?.stopRecording()
self.videoCaptureCompletionBlock = completion
self.videoOutput?.stopRecording()
}
}

Expand Down Expand Up @@ -495,48 +519,6 @@ extension CameraController: AVCapturePhotoCaptureDelegate {
}
}

extension CameraController: AVCaptureVideoDataOutputSampleBufferDelegate {
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
guard let completion = sampleBufferCaptureCompletionBlock else { return }

guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
completion(nil, CameraControllerError.unknown)
return
}

CVPixelBufferLockBaseAddress(imageBuffer, .readOnly)
defer { CVPixelBufferUnlockBaseAddress(imageBuffer, .readOnly) }

let baseAddress = CVPixelBufferGetBaseAddress(imageBuffer)
let bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer)
let width = CVPixelBufferGetWidth(imageBuffer)
let height = CVPixelBufferGetHeight(imageBuffer)
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo: UInt32 = CGBitmapInfo.byteOrder32Little.rawValue |
CGImageAlphaInfo.premultipliedFirst.rawValue

let context = CGContext(
data: baseAddress,
width: width,
height: height,
bitsPerComponent: 8,
bytesPerRow: bytesPerRow,
space: colorSpace,
bitmapInfo: bitmapInfo
)

guard let cgImage = context?.makeImage() else {
completion(nil, CameraControllerError.unknown)
return
}

let image = UIImage(cgImage: cgImage)
completion(image.fixedOrientation(), nil)

sampleBufferCaptureCompletionBlock = nil
}
}

enum CameraControllerError: Swift.Error {
case captureSessionAlreadyRunning
case captureSessionIsMissing
Expand Down Expand Up @@ -639,10 +621,10 @@ extension UIImage {

extension CameraController: AVCaptureFileOutputRecordingDelegate {
func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
/*if error == nil {
self.videoRecordCompletionBlock?(outputFileURL, nil)
} else {
self.videoRecordCompletionBlock?(nil, error)
}*/
if error == nil {
self.videoCaptureCompletionBlock?(outputFileURL, nil)
} else {
self.videoCaptureCompletionBlock?(nil, error)
}
}
}
1 change: 1 addition & 0 deletions ios/Plugin/Plugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
CAP_PLUGIN(CameraPreview, "CameraPreview",
CAP_PLUGIN_METHOD(start, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(stop, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(resume, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(capture, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(captureSample, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(flip, CAPPluginReturnPromise);
Expand Down
42 changes: 30 additions & 12 deletions ios/Plugin/Plugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public class CameraPreview: CAPPlugin {
var enableZoom: Bool?
var highResolutionOutput: Bool = false
var disableAudio: Bool = false
var mirrorVideo: Bool = false

@objc func rotated() {
let height = self.paddingBottom != nil ? self.height! - self.paddingBottom!: self.height!
Expand Down Expand Up @@ -67,6 +68,7 @@ public class CameraPreview: CAPPlugin {
self.storeToFile = call.getBool("storeToFile") ?? false
self.enableZoom = call.getBool("enableZoom") ?? false
self.disableAudio = call.getBool("disableAudio") ?? false
self.mirrorVideo = call.getBool("mirrorVideo") ?? false

AVCaptureDevice.requestAccess(for: .video, completionHandler: { (granted: Bool) in
guard granted else {
Expand Down Expand Up @@ -218,6 +220,17 @@ public class CameraPreview: CAPPlugin {
}
}

@objc func resume(_ call: CAPPluginCall) {
self.cameraController.resume() { error in
if let error = error {
print(error)
call.reject(error.localizedDescription)
return
}
call.resolve()
}
}

@objc func getSupportedFlashModes(_ call: CAPPluginCall) {
do {
let supportedFlashModes = try self.cameraController.getSupportedFlashModes()
Expand Down Expand Up @@ -261,30 +274,35 @@ public class CameraPreview: CAPPlugin {
DispatchQueue.main.async {

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

self.cameraController.captureVideo { (image, error) in

guard let image = image else {
print(error ?? "Image capture error")
guard let error = error else {
call.reject("Image capture error")
return
}
call.reject(error.localizedDescription)
guard let error = error else {
call.resolve()
return
}

// self.videoUrl = image

call.reject(error.localizedDescription)
call.resolve(["value": image.absoluteString])
}
}
}

@objc func stopRecordVideo(_ call: CAPPluginCall) {

self.cameraController.stopRecording { (_) in
self.cameraController.stopRecording { (video, error) in
guard let video = video else {
print(error ?? "Video capture error")
guard let error = error else {
call.reject("Video capture error")
return
}
call.reject(error.localizedDescription)
return
}

// self.videoUrl = image

call.resolve(["videoFilePath": video.absoluteString])
}
}

Expand Down
5 changes: 4 additions & 1 deletion src/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ export interface CameraPreviewOptions {
enableOpacity?: boolean;
/** Defaults to false - Android only. Set if camera preview will support pinch to zoom. */
enableZoom?: boolean;
/** Defaults to false - iOS only. Set if video recording will look the same as the preview. */
mirrorVideo?: boolean;
}
export interface CameraPreviewPictureOptions {
/** The picture height, optional, default 0 (Device default) */
Expand Down Expand Up @@ -62,7 +64,8 @@ export interface CameraPreviewPlugin {
start(options: CameraPreviewOptions): Promise<{}>;
startRecordVideo(options: CameraPreviewOptions): Promise<{}>;
stop(): Promise<{}>;
stopRecordVideo(): Promise<{}>;
resume(): Promise<{} | never>;
stopRecordVideo(): Promise<{ videoFilePath: string } | never>;
capture(options: CameraPreviewPictureOptions): Promise<{ value: string }>;
captureSample(options: CameraSampleOptions): Promise<{ value: string }>;
getSupportedFlashModes(): Promise<{
Expand Down
4 changes: 4 additions & 0 deletions src/web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ export class CameraPreviewWeb extends WebPlugin implements CameraPreviewPlugin {
});
}

async resume(): Promise<{}> {
throw this.unimplemented('Not implemented on web.');
}

async startRecordVideo(): Promise<{}> {
throw this.unimplemented('Not implemented on web.');
}
Expand Down

0 comments on commit bac4f6e

Please sign in to comment.