Skip to content

Commit

Permalink
Merge pull request #86 from spoonconsulting/ios-video-logic
Browse files Browse the repository at this point in the history
Ios video logic
  • Loading branch information
YushraJewon authored Oct 3, 2024
2 parents c8c9f63 + c360b81 commit 1e24fd9
Show file tree
Hide file tree
Showing 9 changed files with 150 additions and 5 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## [2.0.28](https://github.com/spoonconsulting/cordova-plugin-simple-camera-preview/compare/2.0.27...2.0.28) (2024-09-10)
* **Android:** Add video ability for iOS

## [2.0.27](https://github.com/spoonconsulting/cordova-plugin-simple-camera-preview/compare/2.0.26...2.0.27) (2024-09-10)
* **Android:** Generate and return thumbnail path when capturing video

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@spoonconsulting/cordova-plugin-simple-camera-preview",
"version": "2.0.27",
"version": "2.0.28",
"description": "Cordova plugin that allows camera interaction from HTML code for showing camera preview below or on top of the HTML.",
"keywords": [
"cordova",
Expand Down
2 changes: 1 addition & 1 deletion plugin.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>

<plugin id="@spoonconsulting/cordova-plugin-simple-camera-preview" version="2.0.26" xmlns="http://apache.org/cordova/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android">
<plugin id="@spoonconsulting/cordova-plugin-simple-camera-preview" version="2.0.28" xmlns="http://apache.org/cordova/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android">

<name>cordova-plugin-simple-camera-preview</name>
<description>Cordova plugin that allows camera interaction from HTML code. Show camera preview popup on top of the HTML.</description>
Expand Down
8 changes: 8 additions & 0 deletions src/ios/CameraRenderController.m
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ - (void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appplicationIsActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationEnteredForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppWillResignActive) name:UIApplicationWillResignActiveNotification object:nil];

dispatch_async(self.sessionManager.sessionQueue, ^{
if (!self.sessionManager.session.running){
Expand All @@ -56,6 +57,13 @@ - (void) viewWillAppear:(BOOL)animated {
});
}

- (void)onAppWillResignActive {
if (!self.sessionManager.session.running){
[self.sessionManager stopRecording];
}
}


- (void) appplicationIsActive:(NSNotification *)notification {
dispatch_async(self.sessionManager.sessionQueue, ^{
if (!self.sessionManager.session.running){
Expand Down
3 changes: 3 additions & 0 deletions src/ios/CameraSessionManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
- (BOOL) deviceHasUltraWideCamera;
- (void) deallocSession;
- (void) updateOrientation:(AVCaptureVideoOrientation)orientation;
- (void) startRecording:(NSURL *)fileURL recordingDelegate:(id<AVCaptureFileOutputRecordingDelegate>)recordingDelegate;
- (void) stopRecording;
- (AVCaptureVideoOrientation) getCurrentOrientation:(UIInterfaceOrientation)toInterfaceOrientation;
+ (AVCaptureSessionPreset) calculateResolution:(NSInteger)targetSize;
- (UIInterfaceOrientation) getOrientation;
Expand All @@ -27,4 +29,5 @@
@property (nonatomic) AVCapturePhotoOutput *imageOutput;
@property (nonatomic) AVCaptureVideoDataOutput *dataOutput;
@property (nonatomic, weak) id delegate;
@property (nonatomic) AVCaptureMovieFileOutput *movieFileOutput;
@end
30 changes: 30 additions & 0 deletions src/ios/CameraSessionManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ - (CameraSessionManager *)init {
[self.session setSessionPreset:AVCaptureSessionPresetPhoto];
}
self.filterLock = [[NSLock alloc] init];
self.movieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
}
return self;
}
Expand Down Expand Up @@ -101,6 +102,19 @@ - (void) setupSession:(NSString *)defaultCamera completion:(void(^)(BOOL started
}

AVCaptureVideoDataOutput *dataOutput = [[AVCaptureVideoDataOutput alloc] init];

AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
NSError *audioError = nil;
AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:&audioError];
if (audioInput && [self.session canAddInput:audioInput]) {
[self.session addInput:audioInput];
} else {
NSLog(@"Error adding audio input: %@", audioError.localizedDescription);
}

if ([self.session canAddOutput:self.movieFileOutput]) {
[self.session addOutput:self.movieFileOutput];
}
if ([self.session canAddOutput:dataOutput]) {
self.dataOutput = dataOutput;
[dataOutput setAlwaysDiscardsLateVideoFrames:YES];
Expand Down Expand Up @@ -225,6 +239,22 @@ - (void)switchCameraTo:(NSString*)cameraMode completion:(void (^)(BOOL success))
});
}

- (void)startRecording:(NSURL *)fileURL recordingDelegate:(id<AVCaptureFileOutputRecordingDelegate>)recordingDelegate {
if (!self.movieFileOutput.isRecording) {
AVCaptureConnection *connection = [self.movieFileOutput connectionWithMediaType:AVMediaTypeVideo];
if ([connection isVideoOrientationSupported]) {
connection.videoOrientation = [self getCurrentOrientation];
}
[self.movieFileOutput startRecordingToOutputFileURL:fileURL recordingDelegate:recordingDelegate];
}
}

- (void)stopRecording {
if (self.movieFileOutput.isRecording) {
[self.movieFileOutput stopRecording];
}
}

- (BOOL)deviceHasUltraWideCamera {
if (@available(iOS 13.0, *)) {
AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInUltraWideCamera] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified];
Expand Down
3 changes: 2 additions & 1 deletion src/ios/SimpleCameraPreview.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#import "CameraSessionManager.h"
#import "CameraRenderController.h"
#import <CoreLocation/CoreLocation.h>
@interface SimpleCameraPreview : CDVPlugin <AVCapturePhotoCaptureDelegate, CLLocationManagerDelegate>{
@interface SimpleCameraPreview : CDVPlugin <AVCapturePhotoCaptureDelegate, CLLocationManagerDelegate, AVCaptureFileOutputRecordingDelegate>{
CLLocationManager *locationManager;
CLLocation* currentLocation;
}
Expand All @@ -19,6 +19,7 @@
- (void) switchCameraTo: (CDVInvokedUrlCommand*) command;
- (void) deviceHasUltraWideCamera: (CDVInvokedUrlCommand*) command;
- (void) deviceHasFlash: (CDVInvokedUrlCommand*)command;
@property (nonatomic) CDVInvokedUrlCommand *videoCallbackContext;
@property (nonatomic) CameraSessionManager *sessionManager;
@property (nonatomic) CameraRenderController *cameraRenderController;
@property (nonatomic) NSString *onPictureTakenHandlerId;
Expand Down
100 changes: 100 additions & 0 deletions src/ios/SimpleCameraPreview.m
Original file line number Diff line number Diff line change
Expand Up @@ -358,4 +358,104 @@ - (void)deallocateMemory {
locationManager = nil;
}

- (void) initVideoCallback:(CDVInvokedUrlCommand*)command {
self.videoCallbackContext = command;
NSDictionary *data = @{ @"videoCallbackInitialized" : @true };

CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:data];
[pluginResult setKeepCallbackAsBool:true];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}

- (void)startVideoCapture:(CDVInvokedUrlCommand*)command {
if (self.sessionManager != nil && !self.sessionManager.movieFileOutput.isRecording) {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString *libraryDirectory = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"NoCloud"];
NSString* uniqueFileName = [NSString stringWithFormat:@"%@.mp4",[[NSUUID UUID] UUIDString]];
NSString *dataPath = [libraryDirectory stringByAppendingPathComponent:uniqueFileName];
NSURL *fileURL = [NSURL fileURLWithPath:dataPath];
[self.sessionManager startRecordingToOutputFileURL:fileURL recordingDelegate:self];
} else {
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Session not initialized or already recording"];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
}

- (void)stopVideoCapture:(CDVInvokedUrlCommand*)command {
if (self.sessionManager != nil && self.sessionManager.movieFileOutput.isRecording) {
[self.sessionManager stopRecording];
} else {
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Session not initialized or not recording"];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
}

- (NSString*)generateThumbnailForVideoAtURL:(NSURL *)videoURL {
AVAsset *asset = [AVAsset assetWithURL:videoURL];
AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:asset];
imageGenerator.appliesPreferredTrackTransform = YES;
CMTime time = CMTimeMakeWithSeconds(1.0, 600);
NSError *error = nil;
CMTime actualTime;
CGImageRef imageRef = [imageGenerator copyCGImageAtTime:time actualTime:&actualTime error:&error];

if (error) {
NSLog(@"Error generating thumbnail: %@", error.localizedDescription);
return nil;
}

UIImage *thumbnail = [[UIImage alloc] initWithCGImage:imageRef];
CGImageRelease(imageRef);

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString *libraryDirectory = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"NoCloud"];

NSError *directoryError = nil;
if (![[NSFileManager defaultManager] fileExistsAtPath:libraryDirectory]) {
[[NSFileManager defaultManager] createDirectoryAtPath:libraryDirectory withIntermediateDirectories:YES attributes:nil error:&directoryError];

if (directoryError) {
NSLog(@"Error creating NoCloud directory: %@", directoryError.localizedDescription);
return nil;
}
}

NSString *uniqueFileName = [NSString stringWithFormat:@"video_thumb_%@.jpg", [[NSUUID UUID] UUIDString]];
NSString *filePath = [libraryDirectory stringByAppendingPathComponent:uniqueFileName];

NSData *jpegData = UIImageJPEGRepresentation(thumbnail, 1.0);

if ([jpegData writeToFile:filePath atomically:YES]) {
NSLog(@"Thumbnail saved successfully at path: %@", filePath);
} else {
NSLog(@"Failed to save thumbnail.");
return nil;
}
return filePath;
}


- (void)captureOutput:(AVCaptureFileOutput *)output didStartRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections {
NSDictionary *result = @{@"recording": @TRUE};

CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:result];
[pluginResult setKeepCallbackAsBool:true];
[self.commandDelegate sendPluginResult:pluginResult callbackId:self.videoCallbackContext.callbackId];
}

- (void)captureOutput:(AVCaptureFileOutput *)output didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error {
if (error) {
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:error.localizedDescription];
[self.commandDelegate sendPluginResult:pluginResult callbackId:self.videoCallbackContext.callbackId];
} else {
NSString *thumbnail = [self generateThumbnailForVideoAtURL:outputFileURL];
NSString *filePath = [outputFileURL path];
NSDictionary *result = @{@"nativePath": filePath, @"thumbnail": thumbnail};

CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:result];
[pluginResult setKeepCallbackAsBool:true];
[self.commandDelegate sendPluginResult:pluginResult callbackId:self.videoCallbackContext.callbackId];
}
}

@end

0 comments on commit 1e24fd9

Please sign in to comment.