diff --git a/ASScreenRecorder/ASScreenRecorder.h b/ASScreenRecorder/ASScreenRecorder.h index 2f463f3..26c285a 100644 --- a/ASScreenRecorder/ASScreenRecorder.h +++ b/ASScreenRecorder/ASScreenRecorder.h @@ -13,16 +13,31 @@ typedef void (^VideoCompletionBlock)(void); @interface ASScreenRecorder : NSObject @property (nonatomic, readonly) BOOL isRecording; -// delegate is only required when implementing ASScreenRecorderDelegate - see below +/* + *delegate is only required when implementing ASScreenRecorderDelegate - see below + */ @property (nonatomic, weak) id delegate; -// if saveURL is nil, video will be saved into camera roll -// this property can not be changed whilst recording is in progress +/** +* if saveURL is nil, video will be saved into camera roll +* this property can not be changed whilst recording is in progress +*/ @property (strong, nonatomic) NSURL *videoURL; +@property (nonatomic, getter = isPaused) BOOL paused; + +/** + * Default value is 60. + * Set this property before calling -startRecording; + */ +@property (nonatomic) NSInteger fps; + + (instancetype)sharedInstance; - (BOOL)startRecording; +- (void)pauseRecording; +- (void)resumeRecording; - (void)stopRecordingWithCompletion:(VideoCompletionBlock)completionBlock; + @end diff --git a/ASScreenRecorder/ASScreenRecorder.m b/ASScreenRecorder/ASScreenRecorder.m index 3b92d5e..63d7e8c 100644 --- a/ASScreenRecorder/ASScreenRecorder.m +++ b/ASScreenRecorder/ASScreenRecorder.m @@ -11,6 +11,9 @@ #import #import +#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending) +#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending) + @interface ASScreenRecorder() @property (strong, nonatomic) AVAssetWriter *videoWriter; @property (strong, nonatomic) AVAssetWriterInput *videoWriterInput; @@ -19,6 +22,9 @@ @interface ASScreenRecorder() @property (strong, nonatomic) NSDictionary *outputBufferPoolAuxAttributes; @property (nonatomic) CFTimeInterval firstTimeStamp; @property (nonatomic) BOOL isRecording; + +@property (strong, nonatomic) NSMutableArray *pauseResumeTimeRanges; + @end @implementation ASScreenRecorder @@ -63,6 +69,7 @@ - (instancetype)init dispatch_set_target_queue(_render_queue, dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_HIGH, 0)); _frameRenderingSemaphore = dispatch_semaphore_create(1); _pixelAppendSemaphore = dispatch_semaphore_create(1); + _fps = 60; } return self; } @@ -81,20 +88,54 @@ - (BOOL)startRecording [self setUpWriter]; _isRecording = (_videoWriter.status == AVAssetWriterStatusWriting); _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(writeVideoFrame)]; + _displayLink.frameInterval = 60 / self.fps; [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; } return _isRecording; } +- (void)pauseRecording +{ + if (_displayLink.paused) { + return; + } + + if (!self.pauseResumeTimeRanges) { + self.pauseResumeTimeRanges = [NSMutableArray new]; + } + + [self.pauseResumeTimeRanges addObject:@(_displayLink.timestamp + 0.001)]; //adding a small delay + + _displayLink.paused = YES; +} + +- (void)resumeRecording +{ + if (_displayLink && _displayLink.isPaused) { + _displayLink.paused = NO; + } +} + - (void)stopRecordingWithCompletion:(VideoCompletionBlock)completionBlock; { if (_isRecording) { _isRecording = NO; [_displayLink removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; [self completeRecordingSession:completionBlock]; + self.pauseResumeTimeRanges = nil; } } +- (BOOL)isPaused +{ + return _displayLink.paused; +} + +- (void)setPaused:(BOOL)paused +{ + [self pauseRecording]; +} + #pragma mark - private -(void)setUpWriter @@ -231,10 +272,21 @@ - (void)writeVideoFrame dispatch_async(_render_queue, ^{ if (![_videoWriterInput isReadyForMoreMediaData]) return; + if (self.pauseResumeTimeRanges.count % 2 != 0) { + [self.pauseResumeTimeRanges addObject:@(_displayLink.timestamp)]; + } + if (!self.firstTimeStamp) { self.firstTimeStamp = _displayLink.timestamp; } CFTimeInterval elapsed = (_displayLink.timestamp - self.firstTimeStamp); + if (self.pauseResumeTimeRanges.count) { + for (int i = 0; i < self.pauseResumeTimeRanges.count; i += 2) { + double pausedTime = [self.pauseResumeTimeRanges[i] doubleValue]; + double resumeTime = [self.pauseResumeTimeRanges[i+1] doubleValue]; + elapsed -= resumeTime - pausedTime; + } + } CMTime time = CMTimeMakeWithSeconds(elapsed, 1000); CVPixelBufferRef pixelBuffer = NULL; @@ -243,12 +295,20 @@ - (void)writeVideoFrame if (self.delegate) { [self.delegate writeBackgroundFrameInContext:&bitmapContext]; } + + CGFloat width = _viewSize.width; + CGFloat height = _viewSize.height; + if(SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8") && UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) { + width = MAX(_viewSize.width, _viewSize.height); + height = MIN(_viewSize.width, _viewSize.height); + } + // draw each window into the context (other windows include UIKeyboard, UIAlert) // FIX: UIKeyboard is currently only rendered correctly in portrait orientation dispatch_sync(dispatch_get_main_queue(), ^{ UIGraphicsPushContext(bitmapContext); { for (UIWindow *window in [[UIApplication sharedApplication] windows]) { - [window drawViewHierarchyInRect:CGRectMake(0, 0, _viewSize.width, _viewSize.height) afterScreenUpdates:NO]; + [window drawViewHierarchyInRect:CGRectMake(0, 0, width, height) afterScreenUpdates:NO]; } } UIGraphicsPopContext(); }); @@ -295,6 +355,14 @@ - (CGContextRef)createPixelBufferAndBitmapContext:(CVPixelBufferRef *)pixelBuffe CGAffineTransform flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, _viewSize.height); CGContextConcatCTM(bitmapContext, flipVertical); + if(SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8") && UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) { + CGContextRotateCTM(bitmapContext, M_PI_2); + CGContextTranslateCTM(bitmapContext, 0, -_viewSize.width); + } + if(SYSTEM_VERSION_LESS_THAN(@"8") && [UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationLandscapeLeft) { + CGContextRotateCTM(bitmapContext, M_PI); + } + return bitmapContext; } diff --git a/ExampleRecorder/ExampleRecorder/Base.lproj/Main.storyboard b/ExampleRecorder/ExampleRecorder/Base.lproj/Main.storyboard index 618acb7..f149050 100644 --- a/ExampleRecorder/ExampleRecorder/Base.lproj/Main.storyboard +++ b/ExampleRecorder/ExampleRecorder/Base.lproj/Main.storyboard @@ -1,7 +1,8 @@ - + - + + @@ -19,7 +20,7 @@ - + @@ -27,12 +28,26 @@ - - + + - - + + + + + + + + + + + + + + + + @@ -67,7 +82,7 @@ - + diff --git a/ExampleRecorder/ExampleRecorder/UIViewController+ScreenRecorder.m b/ExampleRecorder/ExampleRecorder/UIViewController+ScreenRecorder.m index c6d6df7..43c5321 100644 --- a/ExampleRecorder/ExampleRecorder/UIViewController+ScreenRecorder.m +++ b/ExampleRecorder/ExampleRecorder/UIViewController+ScreenRecorder.m @@ -18,6 +18,9 @@ - (void)prepareScreenRecorder; tapGesture.numberOfTapsRequired = 2; tapGesture.delaysTouchesBegan = YES; [self.view addGestureRecognizer:tapGesture]; + + UILongPressGestureRecognizer *longGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)]; + [self.view addGestureRecognizer:longGesture]; } - (void)recorderGesture:(UIGestureRecognizer *)recognizer @@ -36,6 +39,30 @@ - (void)recorderGesture:(UIGestureRecognizer *)recognizer } } +- (void)handleLongPress:(UILongPressGestureRecognizer *)recognizer +{ + ASScreenRecorder *recorder = [ASScreenRecorder sharedInstance]; + + if (!recorder.isRecording) { + return; + } + + if (recognizer.state != UIGestureRecognizerStateBegan) { + return; + } + + + if (recorder.isPaused) { + [recorder resumeRecording]; + NSLog(@"Resume recording"); + [self playStartSound]; + } else { + [recorder pauseRecording]; + NSLog(@"Pause recording"); + [self playStartSound]; + } +} + - (void)playStartSound { NSURL *url = [NSURL URLWithString:@"/System/Library/Audio/UISounds/begin_record.caf"];