diff --git a/.gitignore b/.gitignore index edc082c..6e13704 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Xcode -build/* +.DS_Store +*/build/* *.pbxuser !default.pbxuser *.mode1v3 @@ -8,8 +9,13 @@ build/* !default.mode2v3 *.perspectivev3 !default.perspectivev3 -*.xcworkspace -!default.xcworkspace xcuserdata profile *.moved-aside +DerivedData +.idea/ +*.hmap +*.xccheckout + +#CocoaPods +Pods diff --git a/MultiVidCam.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/MultiVidCam.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..77bc7ff --- /dev/null +++ b/MultiVidCam.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/MultiVidCam/AVAssetStitcher.m b/MultiVidCam/AVAssetStitcher.m index 0ae23cf..1c9da04 100644 --- a/MultiVidCam/AVAssetStitcher.m +++ b/MultiVidCam/AVAssetStitcher.m @@ -47,6 +47,12 @@ - (id)initWithOutputSize:(CGSize)outSize - (void)addAsset:(AVURLAsset *)asset withTransform:(CGAffineTransform (^)(AVAssetTrack *videoTrack))transformToApply withErrorHandler:(void (^)(NSError *error))errorHandler { + if ([[asset tracksWithMediaType:AVMediaTypeVideo] count] == 0 || + [[asset tracksWithMediaType:AVMediaTypeAudio] count] == 0) + { + return; + } + AVAssetTrack *videoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]; AVMutableVideoCompositionInstruction *instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction]; diff --git a/MultiVidCam/RecordingViewController.h b/MultiVidCam/RecordingViewController.h index 69816f7..a51d4d0 100644 --- a/MultiVidCam/RecordingViewController.h +++ b/MultiVidCam/RecordingViewController.h @@ -36,5 +36,6 @@ - (IBAction)recordTouchUp:(id)sender; - (IBAction)saveRecording:(id)sender; - (IBAction)cancelAction:(id)sender; +- (IBAction)reverseAction:(id)sender; @end diff --git a/MultiVidCam/RecordingViewController.m b/MultiVidCam/RecordingViewController.m index 7054a5e..f8d89da 100644 --- a/MultiVidCam/RecordingViewController.m +++ b/MultiVidCam/RecordingViewController.m @@ -129,6 +129,9 @@ - (void)didReceiveMemoryWarning } #pragma mark Record button +- (IBAction)reverseAction:(id)sender { + [videoCameraInputManager reverseCamera]; +} - (IBAction)cancelAction:(id)sender { diff --git a/MultiVidCam/VideoCameraInputManager.h b/MultiVidCam/VideoCameraInputManager.h index 055a2a2..af0ef2b 100644 --- a/MultiVidCam/VideoCameraInputManager.h +++ b/MultiVidCam/VideoCameraInputManager.h @@ -35,6 +35,8 @@ typedef void (^ErrorHandlingBlock)(NSError *error); - (void)resumeRecording; - (void)reset; +- (void)reverseCamera; +- (void)cleanTemporaryFiles; - (void)finalizeRecordingToFile:(NSURL *)finalVideoLocationURL withVideoSize:(CGSize)videoSize withPreset:(NSString *)preset withCompletionHandler:(void (^)(NSError *error))completionHandler; diff --git a/MultiVidCam/VideoCameraInputManager.m b/MultiVidCam/VideoCameraInputManager.m index 3b9c684..01a6c54 100644 --- a/MultiVidCam/VideoCameraInputManager.m +++ b/MultiVidCam/VideoCameraInputManager.m @@ -22,7 +22,9 @@ #import -@interface VideoCameraInputManager (Private) +@interface VideoCameraInputManager () + +@property (nonatomic, strong) NSMutableDictionary *segmentFileDict; - (void)startNotificationObservers; - (void)endNotificationObservers; @@ -75,6 +77,7 @@ - (id)init inFlightWrites = 0; movieFileOutput = [[AVCaptureMovieFileOutput alloc] init]; + self.segmentFileDict = [[NSMutableDictionary alloc] init]; [self startNotificationObservers]; } @@ -83,6 +86,7 @@ - (id)init - (void)dealloc { + NSLog(@"Video Camera Input Manager dealloc"); [_captureSession removeOutput:movieFileOutput]; [self endNotificationObservers]; @@ -90,6 +94,7 @@ - (void)dealloc - (void)setupSessionWithPreset:(NSString *)preset withCaptureDevice:(AVCaptureDevicePosition)cd withTorchMode:(AVCaptureTorchMode)tm withError:(NSError **)error { + [self cleanTemporaryFiles]; if(setupComplete) { *error = [NSError errorWithDomain:@"Setup session already complete." code:102 userInfo:nil]; @@ -150,8 +155,6 @@ - (void)setupSessionWithPreset:(NSString *)preset withCaptureDevice:(AVCaptureDe - (void)startRecording { - [temporaryFileURLs removeAllObjects]; - uniqueTimestamp = [[NSDate date] timeIntervalSince1970]; currentRecordingSegment = 0; _isPaused = NO; @@ -163,13 +166,10 @@ - (void)startRecording videoConnection.videoOrientation = orientation; } - NSURL *outputFileURL = [NSURL fileURLWithPath:[self constructCurrentTemporaryFilename]]; - - [temporaryFileURLs addObject:outputFileURL]; - + NSString *filePath = [self constructCurrentTemporaryFilename]; + [self.segmentFileDict setObject:@(0) forKey:filePath]; movieFileOutput.maxRecordedDuration = (_maxDuration > 0) ? CMTimeMakeWithSeconds(_maxDuration, 600) : kCMTimeInvalid; - - [movieFileOutput startRecordingToOutputFileURL:outputFileURL recordingDelegate:self]; + [movieFileOutput startRecordingToOutputFileURL:[NSURL fileURLWithPath:filePath] recordingDelegate:self]; } - (void)pauseRecording @@ -182,33 +182,50 @@ - (void)pauseRecording - (void)resumeRecording { - currentRecordingSegment++; _isPaused = NO; + currentRecordingSegment++; - NSURL *outputFileURL = [NSURL fileURLWithPath:[self constructCurrentTemporaryFilename]]; - - [temporaryFileURLs addObject:outputFileURL]; - - if(_maxDuration > 0) - { - movieFileOutput.maxRecordedDuration = CMTimeSubtract(CMTimeMakeWithSeconds(_maxDuration, 600), currentFinalDurration); - } - else - { - movieFileOutput.maxRecordedDuration = kCMTimeInvalid; - } - - [movieFileOutput startRecordingToOutputFileURL:outputFileURL recordingDelegate:self]; + NSString *filePath = [self constructCurrentTemporaryFilename]; + [self.segmentFileDict setObject:@(0) forKey:filePath]; + movieFileOutput.maxRecordedDuration = (_maxDuration > 0) ? CMTimeSubtract(CMTimeMakeWithSeconds(_maxDuration, 600), currentFinalDurration) : kCMTimeInvalid; + [movieFileOutput startRecordingToOutputFileURL:[NSURL fileURLWithPath:filePath] recordingDelegate:self]; } - (void)reset { - if (movieFileOutput.isRecording) - { - [self pauseRecording]; - } - + [self pauseRecording]; _isPaused = NO; + [_captureSession stopRunning]; +} + +- (void)reverseCamera { + NSArray *inputs = self.captureSession.inputs; + for ( AVCaptureDeviceInput *input in inputs ) { + AVCaptureDevice *device = input.device; + if ([device hasMediaType:AVMediaTypeVideo]) { + AVCaptureDevicePosition position = device.position; + AVCaptureDevice *newCamera = nil; + AVCaptureDeviceInput *newInput = nil; + + if (position == AVCaptureDevicePositionFront) { + newCamera = [self cameraWithPosition:AVCaptureDevicePositionBack]; + } + else { + newCamera = [self cameraWithPosition:AVCaptureDevicePositionFront]; + } + newInput = [AVCaptureDeviceInput deviceInputWithDevice:newCamera error:nil]; + + // beginConfiguration ensures that pending changes are not applied immediately + [self.captureSession beginConfiguration]; + + [self.captureSession removeInput:input]; + [self.captureSession addInput:newInput]; + + // Changes take effect once the outermost commitConfiguration is invoked. + [self.captureSession commitConfiguration]; + break; + } + } } - (void)finalizeRecordingToFile:(NSURL *)finalVideoLocationURL withVideoSize:(CGSize)videoSize withPreset:(NSString *)preset withCompletionHandler:(void (^)(NSError *error))completionHandler @@ -218,13 +235,61 @@ - (void)finalizeRecordingToFile:(NSURL *)finalVideoLocationURL withVideoSize:(CG NSError *error; if([finalVideoLocationURL checkResourceIsReachableAndReturnError:&error]) { - completionHandler([NSError errorWithDomain:@"Output file already exists." code:104 userInfo:nil]); - return; + [[NSFileManager defaultManager] removeItemAtURL:finalVideoLocationURL error:nil]; } - if(inFlightWrites != 0) - { - completionHandler([NSError errorWithDomain:@"Can't finalize recording unless all sub-recorings are finished." code:106 userInfo:nil]); + //----------filter the failed files--------- + [temporaryFileURLs removeAllObjects]; + NSArray *keys = [self.segmentFileDict allKeys]; + NSArray *sortedArray = [keys sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { + return [obj1 compare:obj2 options:NSNumericSearch]; + }]; + + for (NSString *filePath in sortedArray) { + NSInteger fileValue = [self.segmentFileDict[filePath] integerValue]; + if (fileValue == 0) { + NSLog(@"[0]not start and stop! filePath=%@", filePath); + if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) { + [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil]; + } + } + else if (fileValue == 1) { + NSLog(@"[1]started but not stop! filePath=%@", filePath); + if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) { + [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil]; + } + } + else if (fileValue == 2) { + NSLog(@"[2]stoped but not start! filePath=%@", filePath); + if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) { + [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil]; + } + } + else if (fileValue == 3) { + NSLog(@"[3]OK start and stop success!!! filePath=%@", filePath); + if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) { + CGFloat fileSize = [self getFileSize:filePath];//KB + if (fileSize <= 10.0f) { + [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil]; + } + else { + [temporaryFileURLs addObject:[NSURL fileURLWithPath:filePath]]; + } + } + } + else { + NSLog(@"[%d]other error! filepath=%@", fileValue, filePath); + if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) { + [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil]; + } + } + } + //------------------------------------------ + + + if ([temporaryFileURLs count] == 0) { + NSError *error1 = [NSError errorWithDomain:@"have no files!" code:108 userInfo:nil]; + completionHandler(error1); return; } @@ -279,15 +344,25 @@ - (void)finalizeRecordingToFile:(NSURL *)finalVideoLocationURL withVideoSize:(CG } else { - [self cleanTemporaryFiles]; - [temporaryFileURLs removeAllObjects]; - completionHandler(nil); } - + [self cleanTemporaryFiles]; }]; } +- (CGFloat)getFileSize:(NSString *)filePath{ + if ( ! [[NSFileManager defaultManager] fileExistsAtPath:filePath]) { + return 0; + } + NSError *error=nil; + NSDictionary * fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:&error]; + if (fileAttributes == nil || error!=nil) { + return 0; + } + long long fileSizeByte = [fileAttributes fileSize]; + return fileSizeByte / 1024.0f; +} + - (CMTime)totalRecordingDuration { if(CMTimeCompare(kCMTimeZero, currentFinalDurration) == 0) @@ -305,11 +380,17 @@ - (CMTime)totalRecordingDuration - (void)captureOutput:(AVCaptureFileOutput *)captureOutput didStartRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections { - inFlightWrites++; + NSInteger value = [[self.segmentFileDict objectForKey:[fileURL path]] integerValue]; + value += 1; + self.segmentFileDict[[fileURL path]] = @(value); } - (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections error:(NSError *)error { + NSInteger value = [[self.segmentFileDict objectForKey:[fileURL path]] integerValue]; + value += 2; + self.segmentFileDict[[fileURL path]] = @(value); + if(error) { if(self.asyncErrorHandler) @@ -321,8 +402,6 @@ - (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToO NSLog(@"Error capturing output: %@", error); } } - - inFlightWrites--; } #pragma mark - Observer start and stop @@ -499,14 +578,21 @@ - (AVCaptureConnection *)connectionWithMediaType:(NSString *)mediaType fromConne - (NSString *)constructCurrentTemporaryFilename { - return [NSString stringWithFormat:@"%@%@-%ld-%d.mov", NSTemporaryDirectory(), @"recordingsegment", uniqueTimestamp, currentRecordingSegment]; + NSString *tempFolderPath = [NSString stringWithFormat:@"%@temprecordfiles",NSTemporaryDirectory()]; + if ( ! [[NSFileManager defaultManager] fileExistsAtPath:tempFolderPath]) { + [[NSFileManager defaultManager] createDirectoryAtPath:tempFolderPath withIntermediateDirectories:YES attributes:nil error:NULL]; + } + return [NSString stringWithFormat:@"%@/%@-%ld-%d.mov", tempFolderPath, @"recordingsegment", uniqueTimestamp, currentRecordingSegment]; } - (void)cleanTemporaryFiles { - [temporaryFileURLs enumerateObjectsUsingBlock:^(NSURL *temporaryFiles, NSUInteger idx, BOOL *stop) { - [[NSFileManager defaultManager] removeItemAtURL:temporaryFiles error:nil]; - }]; + NSString *tempFolderPath = [NSString stringWithFormat:@"%@temprecordfiles",NSTemporaryDirectory()]; + if ([[NSFileManager defaultManager] fileExistsAtPath:tempFolderPath]) { + [[NSFileManager defaultManager] removeItemAtPath:tempFolderPath error:nil]; + } + + [temporaryFileURLs removeAllObjects]; } @end diff --git a/MultiVidCam/en.lproj/MainStoryboard_iPad.storyboard b/MultiVidCam/en.lproj/MainStoryboard_iPad.storyboard index 06c9edd..f678d7e 100644 --- a/MultiVidCam/en.lproj/MainStoryboard_iPad.storyboard +++ b/MultiVidCam/en.lproj/MainStoryboard_iPad.storyboard @@ -1,26 +1,34 @@ - + - + + + + + + - + - + + - + + - - - - - - - - - - - - - - - - - - - - - - - + - - - - + + - + - - - - + - + - @@ -139,29 +118,8 @@ - - - - - - - - - - - - - - - - - - - - - - + diff --git a/MultiVidCam/en.lproj/MainStoryboard_iPhone.storyboard b/MultiVidCam/en.lproj/MainStoryboard_iPhone.storyboard index d6d2b00..60615b1 100644 --- a/MultiVidCam/en.lproj/MainStoryboard_iPhone.storyboard +++ b/MultiVidCam/en.lproj/MainStoryboard_iPhone.storyboard @@ -1,22 +1,41 @@ - + - + + + + + + - + - + + + + + - - - + - - - + + + - + - - + + - + - - + + - - + + @@ -138,27 +157,6 @@ - - - - - - - - - - - - - - - - - - - - -