From bbb595561222cbad9cd90844f2e0b3745e36ac21 Mon Sep 17 00:00:00 2001 From: Khaled Alshehry <39571180+KhaledAlshehri@users.noreply.github.com> Date: Thu, 24 May 2018 00:48:43 +0300 Subject: [PATCH] Add files via upload --- ManagedCapturer/SCSingleFrameStreamCapturer.m | 103 ++++++ .../SCStillImageCaptureVideoInputMethod.h | 19 ++ .../SCStillImageCaptureVideoInputMethod.m | 140 ++++++++ ManagedCapturer/SCTimedTask.h | 28 ++ ManagedCapturer/SCTimedTask.m | 32 ++ ManagedCapturer/SCVideoCaptureSessionInfo.h | 83 +++++ .../StateMachine/SCCaptureBaseState.h | 103 ++++++ .../StateMachine/SCCaptureBaseState.m | 169 ++++++++++ .../StateMachine/SCCaptureStateDelegate.h | 30 ++ .../SCCaptureStateMachineBookKeeper.h | 29 ++ .../SCCaptureStateMachineBookKeeper.m | 63 ++++ .../SCCaptureStateMachineContext.h | 76 +++++ .../SCCaptureStateMachineContext.m | 301 ++++++++++++++++++ .../StateMachine/SCCaptureStateUtil.h | 37 +++ .../StateMachine/SCCaptureStateUtil.m | 38 +++ .../StateMachine/SCManagedCapturerLogging.h | 12 + .../StateMachine/States/SCCaptureImageState.h | 22 ++ .../StateMachine/States/SCCaptureImageState.m | 65 ++++ .../SCCaptureImageStateTransitionPayload.h | 29 ++ .../SCCaptureImageStateTransitionPayload.m | 27 ++ .../SCCaptureImageWhileRecordingState.h | 22 ++ .../SCCaptureImageWhileRecordingState.m | 85 +++++ ...mageWhileRecordingStateTransitionPayload.h | 29 ++ ...mageWhileRecordingStateTransitionPayload.m | 27 ++ .../States/SCCaptureInitializedState.h | 22 ++ .../States/SCCaptureInitializedState.m | 68 ++++ .../States/SCCaptureRecordingState.h | 22 ++ .../States/SCCaptureRecordingState.m | 114 +++++++ ...SCCaptureRecordingStateTransitionPayload.h | 41 +++ ...SCCaptureRecordingStateTransitionPayload.m | 33 ++ .../States/SCCaptureRunningState.h | 22 ++ .../States/SCCaptureRunningState.m | 176 ++++++++++ .../States/SCCaptureScanningState.h | 18 ++ .../States/SCCaptureScanningState.m | 75 +++++ .../States/SCCaptureUninitializedState.h | 26 ++ .../States/SCCaptureUninitializedState.m | 70 ++++ .../States/SCStateTransitionPayload.h | 22 ++ .../States/SCStateTransitionPayload.m | 27 ++ ManagedCapturer/UIScreen+Debug.h | 13 + ManagedCapturer/UIScreen+Debug.m | 28 ++ 40 files changed, 2346 insertions(+) create mode 100644 ManagedCapturer/SCSingleFrameStreamCapturer.m create mode 100644 ManagedCapturer/SCStillImageCaptureVideoInputMethod.h create mode 100644 ManagedCapturer/SCStillImageCaptureVideoInputMethod.m create mode 100644 ManagedCapturer/SCTimedTask.h create mode 100644 ManagedCapturer/SCTimedTask.m create mode 100644 ManagedCapturer/SCVideoCaptureSessionInfo.h create mode 100644 ManagedCapturer/StateMachine/SCCaptureBaseState.h create mode 100644 ManagedCapturer/StateMachine/SCCaptureBaseState.m create mode 100644 ManagedCapturer/StateMachine/SCCaptureStateDelegate.h create mode 100644 ManagedCapturer/StateMachine/SCCaptureStateMachineBookKeeper.h create mode 100644 ManagedCapturer/StateMachine/SCCaptureStateMachineBookKeeper.m create mode 100644 ManagedCapturer/StateMachine/SCCaptureStateMachineContext.h create mode 100644 ManagedCapturer/StateMachine/SCCaptureStateMachineContext.m create mode 100644 ManagedCapturer/StateMachine/SCCaptureStateUtil.h create mode 100644 ManagedCapturer/StateMachine/SCCaptureStateUtil.m create mode 100644 ManagedCapturer/StateMachine/SCManagedCapturerLogging.h create mode 100644 ManagedCapturer/StateMachine/States/SCCaptureImageState.h create mode 100644 ManagedCapturer/StateMachine/States/SCCaptureImageState.m create mode 100644 ManagedCapturer/StateMachine/States/SCCaptureImageStateTransitionPayload.h create mode 100644 ManagedCapturer/StateMachine/States/SCCaptureImageStateTransitionPayload.m create mode 100644 ManagedCapturer/StateMachine/States/SCCaptureImageWhileRecordingState.h create mode 100644 ManagedCapturer/StateMachine/States/SCCaptureImageWhileRecordingState.m create mode 100644 ManagedCapturer/StateMachine/States/SCCaptureImageWhileRecordingStateTransitionPayload.h create mode 100644 ManagedCapturer/StateMachine/States/SCCaptureImageWhileRecordingStateTransitionPayload.m create mode 100644 ManagedCapturer/StateMachine/States/SCCaptureInitializedState.h create mode 100644 ManagedCapturer/StateMachine/States/SCCaptureInitializedState.m create mode 100644 ManagedCapturer/StateMachine/States/SCCaptureRecordingState.h create mode 100644 ManagedCapturer/StateMachine/States/SCCaptureRecordingState.m create mode 100644 ManagedCapturer/StateMachine/States/SCCaptureRecordingStateTransitionPayload.h create mode 100644 ManagedCapturer/StateMachine/States/SCCaptureRecordingStateTransitionPayload.m create mode 100644 ManagedCapturer/StateMachine/States/SCCaptureRunningState.h create mode 100644 ManagedCapturer/StateMachine/States/SCCaptureRunningState.m create mode 100644 ManagedCapturer/StateMachine/States/SCCaptureScanningState.h create mode 100644 ManagedCapturer/StateMachine/States/SCCaptureScanningState.m create mode 100644 ManagedCapturer/StateMachine/States/SCCaptureUninitializedState.h create mode 100644 ManagedCapturer/StateMachine/States/SCCaptureUninitializedState.m create mode 100644 ManagedCapturer/StateMachine/States/SCStateTransitionPayload.h create mode 100644 ManagedCapturer/StateMachine/States/SCStateTransitionPayload.m create mode 100644 ManagedCapturer/UIScreen+Debug.h create mode 100644 ManagedCapturer/UIScreen+Debug.m diff --git a/ManagedCapturer/SCSingleFrameStreamCapturer.m b/ManagedCapturer/SCSingleFrameStreamCapturer.m new file mode 100644 index 0000000..38813b5 --- /dev/null +++ b/ManagedCapturer/SCSingleFrameStreamCapturer.m @@ -0,0 +1,103 @@ +// +// SCSingleFrameStreamCapturer.m +// Snapchat +// +// Created by Benjamin Hollis on 5/3/16. +// Copyright © 2016 Snapchat, Inc. All rights reserved. +// + +#import "SCSingleFrameStreamCapturer.h" + +#import "SCManagedCapturer.h" + +@implementation SCSingleFrameStreamCapturer { + sc_managed_capturer_capture_video_frame_completion_handler_t _callback; +} + +- (instancetype)initWithCompletion:(sc_managed_capturer_capture_video_frame_completion_handler_t)completionHandler +{ + self = [super init]; + if (self) { + _callback = completionHandler; + } + return self; +} + +#pragma mark - SCManagedVideoDataSourceListener + +- (void)managedVideoDataSource:(id)managedVideoDataSource + didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer + devicePosition:(SCManagedCaptureDevicePosition)devicePosition +{ + if (_callback) { + UIImage *image = [self imageFromSampleBuffer:sampleBuffer]; + _callback(image); + } + _callback = nil; +} + +/** + * Decode a CMSampleBufferRef to our native camera format (kCVPixelFormatType_420YpCbCr8BiPlanarFullRange, + * as set in SCManagedVideoStreamer) to a UIImage. + * + * Code from http://stackoverflow.com/a/31553521/11284 + */ +#define clamp(a) (a > 255 ? 255 : (a < 0 ? 0 : a)) +// TODO: Use the transform code from SCImageProcessIdentityYUVCommand +- (UIImage *)imageFromSampleBuffer:(CMSampleBufferRef)sampleBuffer +{ + CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); + CVPixelBufferLockBaseAddress(imageBuffer, 0); + + size_t width = CVPixelBufferGetWidth(imageBuffer); + size_t height = CVPixelBufferGetHeight(imageBuffer); + uint8_t *yBuffer = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0); + size_t yPitch = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 0); + uint8_t *cbCrBuffer = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 1); + size_t cbCrPitch = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 1); + + int bytesPerPixel = 4; + uint8_t *rgbBuffer = malloc(width * height * bytesPerPixel); + + for (int y = 0; y < height; y++) { + uint8_t *rgbBufferLine = &rgbBuffer[y * width * bytesPerPixel]; + uint8_t *yBufferLine = &yBuffer[y * yPitch]; + uint8_t *cbCrBufferLine = &cbCrBuffer[(y >> 1) * cbCrPitch]; + + for (int x = 0; x < width; x++) { + int16_t y = yBufferLine[x]; + int16_t cb = cbCrBufferLine[x & ~1] - 128; + int16_t cr = cbCrBufferLine[x | 1] - 128; + + uint8_t *rgbOutput = &rgbBufferLine[x * bytesPerPixel]; + + int16_t r = (int16_t)roundf(y + cr * 1.4); + int16_t g = (int16_t)roundf(y + cb * -0.343 + cr * -0.711); + int16_t b = (int16_t)roundf(y + cb * 1.765); + + rgbOutput[0] = 0xff; + rgbOutput[1] = clamp(b); + rgbOutput[2] = clamp(g); + rgbOutput[3] = clamp(r); + } + } + + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGContextRef context = CGBitmapContextCreate(rgbBuffer, width, height, 8, width * bytesPerPixel, colorSpace, + kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipLast); + CGImageRef quartzImage = CGBitmapContextCreateImage(context); + + // TODO: Hardcoding UIImageOrientationRight seems cheesy + UIImage *image = [UIImage imageWithCGImage:quartzImage scale:1.0 orientation:UIImageOrientationRight]; + + CGContextRelease(context); + CGColorSpaceRelease(colorSpace); + CGImageRelease(quartzImage); + free(rgbBuffer); + + CVPixelBufferUnlockBaseAddress(imageBuffer, 0); + + return image; +} + +@end diff --git a/ManagedCapturer/SCStillImageCaptureVideoInputMethod.h b/ManagedCapturer/SCStillImageCaptureVideoInputMethod.h new file mode 100644 index 0000000..1704e53 --- /dev/null +++ b/ManagedCapturer/SCStillImageCaptureVideoInputMethod.h @@ -0,0 +1,19 @@ +// +// SCStillImageCaptureVideoInputMethod.h +// Snapchat +// +// Created by Alexander Grytsiuk on 3/16/16. +// Copyright © 2016 Snapchat, Inc. All rights reserved. +// + +#import "SCManagedCapturerState.h" + +#import + +@interface SCStillImageCaptureVideoInputMethod : NSObject + +- (void)captureStillImageWithCapturerState:(SCManagedCapturerState *)state + successBlock:(void (^)(NSData *imageData, NSDictionary *cameraInfo, + NSError *error))successBlock + failureBlock:(void (^)(NSError *error))failureBlock; +@end diff --git a/ManagedCapturer/SCStillImageCaptureVideoInputMethod.m b/ManagedCapturer/SCStillImageCaptureVideoInputMethod.m new file mode 100644 index 0000000..ea6cb05 --- /dev/null +++ b/ManagedCapturer/SCStillImageCaptureVideoInputMethod.m @@ -0,0 +1,140 @@ +// +// SCStillImageCaptureVideoInputMethod.m +// Snapchat +// +// Created by Alexander Grytsiuk on 3/16/16. +// Copyright © 2016 Snapchat, Inc. All rights reserved. +// + +#import "SCStillImageCaptureVideoInputMethod.h" + +#import "SCManagedCapturer.h" +#import "SCManagedVideoFileStreamer.h" + +typedef unsigned char uchar_t; +int clamp(int val, int low, int high) +{ + if (val < low) + val = low; + if (val > high) + val = high; + return val; +} + +void yuv2rgb(uchar_t yValue, uchar_t uValue, uchar_t vValue, uchar_t *r, uchar_t *g, uchar_t *b) +{ + double red = yValue + (1.370705 * (vValue - 128)); + double green = yValue - (0.698001 * (vValue - 128)) - (0.337633 * (uValue - 128)); + double blue = yValue + (1.732446 * (uValue - 128)); + *r = clamp(red, 0, 255); + *g = clamp(green, 0, 255); + *b = clamp(blue, 0, 255); +} + +void convertNV21DataToRGBData(int width, int height, uchar_t *nv21Data, uchar_t *rgbData, int rgbBytesPerPixel, + int rgbBytesPerRow) +{ + uchar_t *uvData = nv21Data + height * width; + for (int h = 0; h < height; h++) { + uchar_t *yRowBegin = nv21Data + h * width; + uchar_t *uvRowBegin = uvData + h / 2 * width; + uchar_t *rgbRowBegin = rgbData + rgbBytesPerRow * h; + for (int w = 0; w < width; w++) { + uchar_t *rgbPixelBegin = rgbRowBegin + rgbBytesPerPixel * w; + yuv2rgb(yRowBegin[w], uvRowBegin[w / 2 * 2], uvRowBegin[w / 2 * 2 + 1], &(rgbPixelBegin[0]), + &(rgbPixelBegin[1]), &(rgbPixelBegin[2])); + } + } +} + +@implementation SCStillImageCaptureVideoInputMethod + +- (void)captureStillImageWithCapturerState:(SCManagedCapturerState *)state + successBlock:(void (^)(NSData *imageData, NSDictionary *cameraInfo, + NSError *error))successBlock + failureBlock:(void (^)(NSError *error))failureBlock +{ + id videoDataSource = [[SCManagedCapturer sharedInstance] currentVideoDataSource]; + if ([videoDataSource isKindOfClass:[SCManagedVideoFileStreamer class]]) { + SCManagedVideoFileStreamer *videoFileStreamer = (SCManagedVideoFileStreamer *)videoDataSource; + [videoFileStreamer getNextPixelBufferWithCompletion:^(CVPixelBufferRef pixelBuffer) { + BOOL shouldFlip = state.devicePosition == SCManagedCaptureDevicePositionFront; +#if TARGET_IPHONE_SIMULATOR + UIImage *uiImage = [self imageWithCVPixelBuffer:pixelBuffer]; + CGImageRef videoImage = uiImage.CGImage; + UIImage *capturedImage = [UIImage + imageWithCGImage:shouldFlip ? [self flipCGImage:videoImage size:uiImage.size].CGImage : videoImage + scale:1.0 + orientation:UIImageOrientationRight]; +#else + CIImage *ciImage = [CIImage imageWithCVPixelBuffer:pixelBuffer]; + CIContext *temporaryContext = [CIContext contextWithOptions:nil]; + + CGSize size = CGSizeMake(CVPixelBufferGetWidth(pixelBuffer), CVPixelBufferGetHeight(pixelBuffer)); + CGImageRef videoImage = + [temporaryContext createCGImage:ciImage fromRect:CGRectMake(0, 0, size.width, size.height)]; + + UIImage *capturedImage = + [UIImage imageWithCGImage:shouldFlip ? [self flipCGImage:videoImage size:size].CGImage : videoImage + scale:1.0 + orientation:UIImageOrientationRight]; + + CGImageRelease(videoImage); +#endif + if (successBlock) { + successBlock(UIImageJPEGRepresentation(capturedImage, 1.0), nil, nil); + } + }]; + } else { + if (failureBlock) { + failureBlock([NSError errorWithDomain:NSStringFromClass(self.class) code:-1 userInfo:nil]); + } + } +} + +- (UIImage *)flipCGImage:(CGImageRef)cgImage size:(CGSize)size +{ + UIGraphicsBeginImageContext(size); + CGContextDrawImage(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, size.width, size.height), cgImage); + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return image; +} + +- (UIImage *)imageWithCVPixelBuffer:(CVPixelBufferRef)imageBuffer +{ + CVPixelBufferLockBaseAddress(imageBuffer, 0); + + size_t width = CVPixelBufferGetWidth(imageBuffer); + size_t height = CVPixelBufferGetHeight(imageBuffer); + size_t rgbBytesPerPixel = 4; + size_t rgbBytesPerRow = width * rgbBytesPerPixel; + + uchar_t *nv21Data = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0); + uchar_t *rgbData = malloc(rgbBytesPerRow * height); + + convertNV21DataToRGBData((int)width, (int)height, nv21Data, rgbData, (int)rgbBytesPerPixel, (int)rgbBytesPerRow); + + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGContextRef context = + CGBitmapContextCreate(rgbData, width, height, 8, rgbBytesPerRow, colorSpace, kCGImageAlphaNoneSkipLast); + CGImageRef cgImage = CGBitmapContextCreateImage(context); + + UIImage *result = [UIImage imageWithCGImage:cgImage]; + + CGImageRelease(cgImage); + CGContextRelease(context); + CGColorSpaceRelease(colorSpace); + free(rgbData); + + CVPixelBufferUnlockBaseAddress(imageBuffer, 0); + + return result; +} + +- (NSString *)methodName +{ + return @"VideoInput"; +} + +@end diff --git a/ManagedCapturer/SCTimedTask.h b/ManagedCapturer/SCTimedTask.h new file mode 100644 index 0000000..f5a4e15 --- /dev/null +++ b/ManagedCapturer/SCTimedTask.h @@ -0,0 +1,28 @@ +// +// SCTimedTask.h +// Snapchat +// +// Created by Michel Loenngren on 4/2/17. +// Copyright © 2017 Snapchat, Inc. All rights reserved. +// + +#import +#import + +/* + Block based timed task + */ +@interface SCTimedTask : NSObject + +@property (nonatomic, assign) CMTime targetTime; +@property (nonatomic, copy) void (^task)(CMTime relativePresentationTime, CGFloat sessionStartTimeDelayInSecond); + +- (instancetype)init NS_UNAVAILABLE; + +- (instancetype)initWithTargetTime:(CMTime)targetTime + task:(void (^)(CMTime relativePresentationTime, + CGFloat sessionStartTimeDelayInSecond))task; + +- (NSString *)description; + +@end diff --git a/ManagedCapturer/SCTimedTask.m b/ManagedCapturer/SCTimedTask.m new file mode 100644 index 0000000..babf445 --- /dev/null +++ b/ManagedCapturer/SCTimedTask.m @@ -0,0 +1,32 @@ +// +// SCTimedTask.m +// Snapchat +// +// Created by Michel Loenngren on 4/2/17. +// Copyright © 2017 Snapchat, Inc. All rights reserved. +// + +#import "SCTimedTask.h" + +#import + +@implementation SCTimedTask + +- (instancetype)initWithTargetTime:(CMTime)targetTime + task: + (void (^)(CMTime relativePresentationTime, CGFloat sessionStartTimeDelayInSecond))task +{ + if (self = [super init]) { + _targetTime = targetTime; + _task = task; + } + return self; +} + +- (NSString *)description +{ + return [NSString + sc_stringWithFormat:@"<%@: %p, targetTime: %lld>", NSStringFromClass([self class]), self, _targetTime.value]; +} + +@end diff --git a/ManagedCapturer/SCVideoCaptureSessionInfo.h b/ManagedCapturer/SCVideoCaptureSessionInfo.h new file mode 100644 index 0000000..b89da3e --- /dev/null +++ b/ManagedCapturer/SCVideoCaptureSessionInfo.h @@ -0,0 +1,83 @@ +// +// SCVideoCaptureSessionInfo.h +// Snapchat +// +// Created by Michel Loenngren on 3/27/17. +// Copyright © 2017 Snapchat, Inc. All rights reserved. +// + +#import + +#import +#import + +typedef NS_ENUM(NSInteger, SCManagedVideoCapturerInfoType) { + SCManagedVideoCapturerInfoAudioQueueError, + SCManagedVideoCapturerInfoAssetWriterError, + SCManagedVideoCapturerInfoAudioSessionError, + SCManagedVideoCapturerInfoAudioQueueRetrySuccess, + SCManagedVideoCapturerInfoAudioQueueRetryDataSourceSuccess_audioQueue, + SCManagedVideoCapturerInfoAudioQueueRetryDataSourceSuccess_hardware +}; + +typedef u_int32_t sc_managed_capturer_recording_session_t; + +/* + Container object holding information about the + current recording session. + */ +typedef struct { + CMTime startTime; + CMTime endTime; + CMTime duration; + sc_managed_capturer_recording_session_t sessionId; +} SCVideoCaptureSessionInfo; + +static inline SCVideoCaptureSessionInfo SCVideoCaptureSessionInfoMake(CMTime startTime, CMTime endTime, + sc_managed_capturer_recording_session_t sessionId) +{ + SCVideoCaptureSessionInfo session; + session.startTime = startTime; + session.endTime = endTime; + if (CMTIME_IS_VALID(startTime) && CMTIME_IS_VALID(endTime)) { + session.duration = CMTimeSubtract(endTime, startTime); + } else { + session.duration = kCMTimeInvalid; + } + session.sessionId = sessionId; + return session; +} + +static inline NSTimeInterval SCVideoCaptureSessionInfoGetCurrentDuration(SCVideoCaptureSessionInfo sessionInfo) +{ + if (CMTIME_IS_VALID(sessionInfo.startTime)) { + if (CMTIME_IS_VALID(sessionInfo.endTime)) { + return CMTimeGetSeconds(sessionInfo.duration); + } + return CACurrentMediaTime() - CMTimeGetSeconds(sessionInfo.startTime); + } + return 0; +} + +static inline NSString *SCVideoCaptureSessionInfoGetDebugString(CMTime time, NSString *label) +{ + if (CMTIME_IS_VALID(time)) { + return [NSString sc_stringWithFormat:@"%@: %f", label, CMTimeGetSeconds(time)]; + } else { + return [NSString sc_stringWithFormat:@"%@: Invalid", label]; + } +} + +static inline NSString *SCVideoCaptureSessionInfoGetDebugDescription(SCVideoCaptureSessionInfo sessionInfo) +{ + NSMutableString *description = [NSMutableString new]; + [description appendString:SCVideoCaptureSessionInfoGetDebugString(sessionInfo.startTime, @"StartTime")]; + [description appendString:@", "]; + [description appendString:SCVideoCaptureSessionInfoGetDebugString(sessionInfo.endTime, @"EndTime")]; + [description appendString:@", "]; + [description appendString:SCVideoCaptureSessionInfoGetDebugString(sessionInfo.duration, @"Duration")]; + [description appendString:@", "]; + [description appendString:[NSString sc_stringWithFormat:@"Id: %u", sessionInfo.sessionId]]; + + return [description copy]; +} diff --git a/ManagedCapturer/StateMachine/SCCaptureBaseState.h b/ManagedCapturer/StateMachine/SCCaptureBaseState.h new file mode 100644 index 0000000..ef18f00 --- /dev/null +++ b/ManagedCapturer/StateMachine/SCCaptureBaseState.h @@ -0,0 +1,103 @@ +// +// SCCaptureBaseState.h +// Snapchat +// +// Created by Lin Jia on 10/19/17. +// +// + +#import "SCCaptureCommon.h" +#import "SCCaptureStateDelegate.h" +#import "SCCaptureStateMachineBookKeeper.h" +#import "SCCaptureStateUtil.h" +#import "SCCaptureWorker.h" +#import "SCManagedCaptureDevice.h" +#import "SCManagedCapturerState.h" +#import "SCStateTransitionPayload.h" + +#import + +@class SCCaptureResource; + +@class SCCapturerToken; + +@class SCAudioConfiguration; + +@class SCQueuePerformer; +/* + Every state machine state needs to inherent SCCaptureBaseState to have the APIs. State machine state in general will + only implement APIs which are legal for itself. If illegal APIs are invoked, SCCaptureBaseState will handle it. + The intended behavior: + 1) crash using SCAssert in Debug build, + 2) ignore api call, and log the call, for alpha/master/production. + 3) in the future, we will introduce dangerous API call concept, and restart camera in such case, to avoid bad state. + + Every state machine state is going to be built to follow functional programming as more as possible. The shared + resources between them will be passed into the API via SCCaptureResource. + */ + +@interface SCCaptureBaseState : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +- (instancetype)initWithPerformer:(SCQueuePerformer *)performer + bookKeeper:(SCCaptureStateMachineBookKeeper *)bookKeeper + delegate:(id)delegate; + +/* The following API will be invoked at the moment state context promote the state to be current state. State use this + * chance to do something, such as start recording for recording state. + */ +- (void)didBecomeCurrentState:(SCStateTransitionPayload *)payload + resource:(SCCaptureResource *)resource + context:(NSString *)context; + +- (SCCaptureStateMachineStateId)stateId; + +- (void)initializeCaptureWithDevicePosition:(SCManagedCaptureDevicePosition)devicePosition + resource:(SCCaptureResource *)resource + completionHandler:(dispatch_block_t)completionHandler + context:(NSString *)context; + +- (void)startRunningWithCapturerToken:(SCCapturerToken *)token + resource:(SCCaptureResource *)resource + completionHandler:(dispatch_block_t)completionHandler + context:(NSString *)context; + +- (void)stopRunningWithCapturerToken:(SCCapturerToken *)token + resource:(SCCaptureResource *)resource + completionHandler:(sc_managed_capturer_stop_running_completion_handler_t)completionHandler + context:(NSString *)context; + +- (void)prepareForRecordingWithResource:(SCCaptureResource *)resource + audioConfiguration:(SCAudioConfiguration *)configuration + context:(NSString *)context; + +- (void)startRecordingWithResource:(SCCaptureResource *)resource + audioConfiguration:(SCAudioConfiguration *)configuration + outputSettings:(SCManagedVideoCapturerOutputSettings *)outputSettings + maxDuration:(NSTimeInterval)maxDuration + fileURL:(NSURL *)fileURL + captureSessionID:(NSString *)captureSessionID + completionHandler:(sc_managed_capturer_start_recording_completion_handler_t)completionHandler + context:(NSString *)context; + +- (void)stopRecordingWithResource:(SCCaptureResource *)resource context:(NSString *)context; + +- (void)cancelRecordingWithResource:(SCCaptureResource *)resource context:(NSString *)context; + +- (void)captureStillImageWithResource:(SCCaptureResource *)resource + aspectRatio:(CGFloat)aspectRatio + captureSessionID:(NSString *)captureSessionID + completionHandler:(sc_managed_capturer_capture_still_image_completion_handler_t)completionHandler + context:(NSString *)context; + +- (void)startScanWithScanConfiguration:(SCScanConfiguration *)configuration + resource:(SCCaptureResource *)resource + context:(NSString *)context; + +- (void)stopScanWithCompletionHandler:(dispatch_block_t)completionHandler + resource:(SCCaptureResource *)resource + context:(NSString *)context; + +@property (nonatomic, strong, readonly) SCCaptureStateMachineBookKeeper *bookKeeper; +@end diff --git a/ManagedCapturer/StateMachine/SCCaptureBaseState.m b/ManagedCapturer/StateMachine/SCCaptureBaseState.m new file mode 100644 index 0000000..569ab54 --- /dev/null +++ b/ManagedCapturer/StateMachine/SCCaptureBaseState.m @@ -0,0 +1,169 @@ +// +// SCCaptureBaseState.m +// Snapchat +// +// Created by Lin Jia on 10/19/17. +// +// + +#import "SCCaptureBaseState.h" + +#import "SCCaptureStateMachineBookKeeper.h" +#import "SCCapturerToken.h" +#import "SCManagedCapturerV1_Private.h" + +#import +#import +#import + +@implementation SCCaptureBaseState { + SCCaptureStateMachineBookKeeper *_bookKeeper; + SCQueuePerformer *_performer; + __weak id _delegate; +} + +- (instancetype)initWithPerformer:(SCQueuePerformer *)performer + bookKeeper:(SCCaptureStateMachineBookKeeper *)bookKeeper + delegate:(id)delegate +{ + self = [super init]; + if (self) { + SCAssert(performer, @""); + SCAssert(bookKeeper, @""); + _bookKeeper = bookKeeper; + _performer = performer; + _delegate = delegate; + } + return self; +} + +- (SCCaptureStateMachineStateId)stateId +{ + return SCCaptureBaseStateId; +} + +- (void)didBecomeCurrentState:(SCStateTransitionPayload *)payload + resource:(SCCaptureResource *)resource + context:(NSString *)context +{ + [self _handleBaseStateBehavior:@"didBecomeCurrentState" context:context]; +} + +- (void)initializeCaptureWithDevicePosition:(SCManagedCaptureDevicePosition)devicePosition + resource:(SCCaptureResource *)resource + completionHandler:(dispatch_block_t)completionHandler + context:(NSString *)context +{ + [self _handleBaseStateBehavior:@"initializeCaptureWithDevicePosition" context:context]; +} + +- (void)startRunningWithCapturerToken:(SCCapturerToken *)token + resource:(SCCaptureResource *)resource + completionHandler:(dispatch_block_t)completionHandler + context:(NSString *)context +{ + [self _handleBaseStateBehavior:@"startRunningWithCapturerToken" context:context]; +} + +- (void)stopRunningWithCapturerToken:(SCCapturerToken *)token + resource:(SCCaptureResource *)resource + completionHandler:(sc_managed_capturer_stop_running_completion_handler_t)completionHandler + context:(NSString *)context +{ + SCAssertPerformer(_performer); + BOOL actuallyStopped = [[SCManagedCapturerV1 sharedInstance] stopRunningWithCaptureToken:token + completionHandler:completionHandler + context:context]; + // TODO: Fix CCAM-14450 + // This is a temporary solution for https://jira.sc-corp.net/browse/CCAM-14450 + // It is caused by switching from scanning state to stop running state when the view is disappearing in the scanning + // state, which can be reproduced by triggering scanning and then switch to maps page. + // We remove SCAssert to ingore the crashes in master branch and will find a solution for the illegal call for the + // state machine later + + if (self.stateId != SCCaptureScanningStateId) { + SCAssert(!actuallyStopped, @"actuallyStopped in state: %@ with context: %@", SCCaptureStateName([self stateId]), + context); + } else { + SCLogCaptureStateMachineInfo(@"actuallyStopped:%d in state: %@ with context: %@", actuallyStopped, + SCCaptureStateName([self stateId]), context); + } + + if (actuallyStopped) { + [_delegate currentState:self + requestToTransferToNewState:SCCaptureInitializedStateId + payload:nil + context:context]; + } +} + +- (void)prepareForRecordingWithResource:(SCCaptureResource *)resource + audioConfiguration:(SCAudioConfiguration *)configuration + context:(NSString *)context +{ + [self _handleBaseStateBehavior:@"prepareForRecordingWithResource" context:context]; +} + +- (void)startRecordingWithResource:(SCCaptureResource *)resource + audioConfiguration:(SCAudioConfiguration *)configuration + outputSettings:(SCManagedVideoCapturerOutputSettings *)outputSettings + maxDuration:(NSTimeInterval)maxDuration + fileURL:(NSURL *)fileURL + captureSessionID:(NSString *)captureSessionID + completionHandler:(sc_managed_capturer_start_recording_completion_handler_t)completionHandler + context:(NSString *)context +{ + [self _handleBaseStateBehavior:@"startRecordingWithResource" context:context]; +} + +- (void)stopRecordingWithResource:(SCCaptureResource *)resource context:(NSString *)context +{ + [self _handleBaseStateBehavior:@"stopRecordingWithResource" context:context]; +} + +- (void)cancelRecordingWithResource:(SCCaptureResource *)resource context:(NSString *)context +{ + [self _handleBaseStateBehavior:@"cancelRecordingWithResource" context:context]; +} + +- (void)captureStillImageWithResource:(SCCaptureResource *)resource + aspectRatio:(CGFloat)aspectRatio + captureSessionID:(NSString *)captureSessionID + completionHandler:(sc_managed_capturer_capture_still_image_completion_handler_t)completionHandler + context:(NSString *)context +{ + [self _handleBaseStateBehavior:@"captureStillImageWithResource" context:context]; +} + +- (void)startScanWithScanConfiguration:(SCScanConfiguration *)configuration + resource:(SCCaptureResource *)resource + context:(NSString *)context +{ + [self _handleBaseStateBehavior:@"startScanWithScanConfiguration" context:context]; +} + +- (void)stopScanWithCompletionHandler:(dispatch_block_t)completionHandler + resource:(SCCaptureResource *)resource + context:(NSString *)context +{ + // Temporary solution until IDT-12520 is resolved. + [SCCaptureWorker stopScanWithCompletionHandler:completionHandler resource:resource]; + //[self _handleBaseStateBehavior:@"stopScanWithCompletionHandler"]; +} + +- (void)_handleBaseStateBehavior:(NSString *)illegalAPIName context:(NSString *)context +{ + [_bookKeeper state:[self stateId] + illegalAPIcalled:illegalAPIName + callStack:[NSThread callStackSymbols] + context:context]; + if (SCIsDebugBuild()) { + SCAssertFail(@"illegal API invoked on capture state machine"); + } +} + +- (SCCaptureStateMachineBookKeeper *)bookKeeper +{ + return _bookKeeper; +} +@end diff --git a/ManagedCapturer/StateMachine/SCCaptureStateDelegate.h b/ManagedCapturer/StateMachine/SCCaptureStateDelegate.h new file mode 100644 index 0000000..f07766f --- /dev/null +++ b/ManagedCapturer/StateMachine/SCCaptureStateDelegate.h @@ -0,0 +1,30 @@ +// +// SCCaptureStateDelegate.h +// Snapchat +// +// Created by Lin Jia on 10/27/17. +// +// + +#import "SCCaptureStateUtil.h" + +#import + +@class SCCaptureBaseState; +@class SCStateTransitionPayload; +/* + The state machine state delegate is used by state machine states to hint to the system that "I am done, now transfer + to other state". + + Currently, SCCaptureStateMachineContext is the central piece that glues all states together, and it is the delegate for + those states. + */ + +@protocol SCCaptureStateDelegate + +- (void)currentState:(SCCaptureBaseState *)state + requestToTransferToNewState:(SCCaptureStateMachineStateId)newState + payload:(SCStateTransitionPayload *)payload + context:(NSString *)context; + +@end diff --git a/ManagedCapturer/StateMachine/SCCaptureStateMachineBookKeeper.h b/ManagedCapturer/StateMachine/SCCaptureStateMachineBookKeeper.h new file mode 100644 index 0000000..24ea585 --- /dev/null +++ b/ManagedCapturer/StateMachine/SCCaptureStateMachineBookKeeper.h @@ -0,0 +1,29 @@ +// +// SCCaptureStateTransitionBookKeeper.h +// Snapchat +// +// Created by Lin Jia on 10/27/17. +// +// + +#import "SCCaptureStateUtil.h" + +#import + +/* + Book keeper is used to record every state transition, and every illegal API call. + */ + +@interface SCCaptureStateMachineBookKeeper : NSObject + +- (void)stateTransitionFrom:(SCCaptureStateMachineStateId)fromId + to:(SCCaptureStateMachineStateId)toId + context:(NSString *)context; + +- (void)state:(SCCaptureStateMachineStateId)captureState + illegalAPIcalled:(NSString *)illegalAPIName + callStack:(NSArray *)callStack + context:(NSString *)context; + +- (void)logAPICalled:(NSString *)apiName context:(NSString *)context; +@end diff --git a/ManagedCapturer/StateMachine/SCCaptureStateMachineBookKeeper.m b/ManagedCapturer/StateMachine/SCCaptureStateMachineBookKeeper.m new file mode 100644 index 0000000..7d9c466 --- /dev/null +++ b/ManagedCapturer/StateMachine/SCCaptureStateMachineBookKeeper.m @@ -0,0 +1,63 @@ +// +// SCCaptureStateTransitionBookKeeper.m +// Snapchat +// +// Created by Lin Jia on 10/27/17. +// +// + +#import "SCCaptureStateMachineBookKeeper.h" + +#import "SCCaptureStateUtil.h" +#import "SCLogger+Camera.h" + +#import +#import + +@interface SCCaptureStateMachineBookKeeper () { + NSDate *_lastStateStartTime; +} +@end + +@implementation SCCaptureStateMachineBookKeeper + +- (void)stateTransitionFrom:(SCCaptureStateMachineStateId)fromId + to:(SCCaptureStateMachineStateId)toId + context:(NSString *)context +{ + NSDate *date = [NSDate date]; + SCLogCaptureStateMachineInfo(@"State %@ life span: %f seconds, transition to: %@, in context:%@, at: %@ \n", + SCCaptureStateName(fromId), [date timeIntervalSinceDate:_lastStateStartTime], + SCCaptureStateName(toId), context, date); + _lastStateStartTime = date; +} + +- (void)state:(SCCaptureStateMachineStateId)captureState + illegalAPIcalled:(NSString *)illegalAPIName + callStack:(NSArray *)callStack + context:(NSString *)context + +{ + SCAssert(callStack, @"call stack empty"); + SCAssert(illegalAPIName, @""); + SCAssert(context, @"Context is empty"); + SCLogCaptureStateMachineError(@"State: %@, illegal API invoke: %@, at: %@, callstack: %@ \n", + SCCaptureStateName(captureState), illegalAPIName, [NSDate date], callStack); + NSArray *reportedArray = + [callStack count] > 15 ? [callStack subarrayWithRange:NSMakeRange(0, 15)] : callStack; + [[SCLogger sharedInstance] logEvent:kSCCameraStateMachineIllegalAPICall + parameters:@{ + @"state" : SCCaptureStateName(captureState), + @"API" : illegalAPIName, + @"call_stack" : reportedArray, + @"context" : context + }]; +} + +- (void)logAPICalled:(NSString *)apiName context:(NSString *)context +{ + SCAssert(apiName, @"API name is empty"); + SCAssert(context, @"Context is empty"); + SCLogCaptureStateMachineInfo(@"api: %@ context: %@", apiName, context); +} +@end diff --git a/ManagedCapturer/StateMachine/SCCaptureStateMachineContext.h b/ManagedCapturer/StateMachine/SCCaptureStateMachineContext.h new file mode 100644 index 0000000..1e98943 --- /dev/null +++ b/ManagedCapturer/StateMachine/SCCaptureStateMachineContext.h @@ -0,0 +1,76 @@ +// +// SCCaptureStateMachineContext.h +// Snapchat +// +// Created by Lin Jia on 10/18/17. +// +// + +#import "SCCaptureCommon.h" +#import "SCManagedCaptureDevice.h" + +#import + +#import + +/* + SCCaptureStateMachineContext is the central piece that glues all states together. + + It will pass API calls to the current state. + + The classic state machine design pattern: + https://en.wikipedia.org/wiki/State_pattern + + It is also the delegate for the states it manages, so that those states can tell stateMachineContext to transit to next + state. + */ + +@class SCCaptureResource; + +@class SCCapturerToken; + +@interface SCCaptureStateMachineContext : NSObject + +- (instancetype)initWithResource:(SCCaptureResource *)resource; + +- (void)initializeCaptureWithDevicePositionAsynchronously:(SCManagedCaptureDevicePosition)devicePosition + completionHandler:(dispatch_block_t)completionHandler + context:(NSString *)context; + +- (SCCapturerToken *)startRunningWithContext:(NSString *)context completionHandler:(dispatch_block_t)completionHandler; + +- (void)stopRunningWithCapturerToken:(SCCapturerToken *)token + completionHandler:(sc_managed_capturer_stop_running_completion_handler_t)completionHandler + context:(NSString *)context; + +- (void)stopRunningWithCapturerToken:(SCCapturerToken *)token + after:(NSTimeInterval)delay + completionHandler:(sc_managed_capturer_stop_running_completion_handler_t)completionHandler + context:(NSString *)context; + +- (void)prepareForRecordingAsynchronouslyWithAudioConfiguration:(SCAudioConfiguration *)configuration + context:(NSString *)context; + +- (void)startRecordingWithOutputSettings:(SCManagedVideoCapturerOutputSettings *)outputSettings + audioConfiguration:(SCAudioConfiguration *)configuration + maxDuration:(NSTimeInterval)maxDuration + fileURL:(NSURL *)fileURL + captureSessionID:(NSString *)captureSessionID + completionHandler:(sc_managed_capturer_start_recording_completion_handler_t)completionHandler + context:(NSString *)context; + +- (void)stopRecordingWithContext:(NSString *)context; + +- (void)cancelRecordingWithContext:(NSString *)context; + +- (void)captureStillImageAsynchronouslyWithAspectRatio:(CGFloat)aspectRatio + captureSessionID:(NSString *)captureSessionID + completionHandler: + (sc_managed_capturer_capture_still_image_completion_handler_t)completionHandler + context:(NSString *)context; + +#pragma mark - Scanning +- (void)startScanAsynchronouslyWithScanConfiguration:(SCScanConfiguration *)configuration context:(NSString *)context; +- (void)stopScanAsynchronouslyWithCompletionHandler:(dispatch_block_t)completionHandler context:(NSString *)context; + +@end diff --git a/ManagedCapturer/StateMachine/SCCaptureStateMachineContext.m b/ManagedCapturer/StateMachine/SCCaptureStateMachineContext.m new file mode 100644 index 0000000..5fd1b7a --- /dev/null +++ b/ManagedCapturer/StateMachine/SCCaptureStateMachineContext.m @@ -0,0 +1,301 @@ +// +// SCCaptureStateMachineContext.m +// Snapchat +// +// Created by Lin Jia on 10/18/17. +// +// + +#import "SCCaptureStateMachineContext.h" + +#import "SCCaptureBaseState.h" +#import "SCCaptureImageState.h" +#import "SCCaptureImageWhileRecordingState.h" +#import "SCCaptureInitializedState.h" +#import "SCCaptureRecordingState.h" +#import "SCCaptureResource.h" +#import "SCCaptureRunningState.h" +#import "SCCaptureScanningState.h" +#import "SCCaptureStateMachineBookKeeper.h" +#import "SCCaptureStateUtil.h" +#import "SCCaptureUninitializedState.h" +#import "SCCaptureWorker.h" +#import "SCCapturerToken.h" +#import "SCStateTransitionPayload.h" + +#import +#import +#import +#import +#import +#import + +@interface SCCaptureStateMachineContext () { + SCQueuePerformer *_queuePerformer; + + // Cache all the states. + NSMutableDictionary *_states; + SCCaptureBaseState *_currentState; + SCCaptureStateMachineBookKeeper *_bookKeeper; + SCCaptureResource *_captureResource; +} +@end + +@implementation SCCaptureStateMachineContext + +- (instancetype)initWithResource:(SCCaptureResource *)resource +{ + self = [super init]; + if (self) { + SCAssert(resource, @""); + SCAssert(resource.queuePerformer, @""); + _captureResource = resource; + _queuePerformer = resource.queuePerformer; + _states = [[NSMutableDictionary alloc] init]; + _bookKeeper = [[SCCaptureStateMachineBookKeeper alloc] init]; + [self _setCurrentState:SCCaptureUninitializedStateId payload:nil context:SCCapturerContext]; + } + return self; +} + +- (void)_setCurrentState:(SCCaptureStateMachineStateId)stateId + payload:(SCStateTransitionPayload *)payload + context:(NSString *)context +{ + switch (stateId) { + case SCCaptureUninitializedStateId: + if (![_states objectForKey:@(stateId)]) { + SCCaptureUninitializedState *uninitializedState = + [[SCCaptureUninitializedState alloc] initWithPerformer:_queuePerformer + bookKeeper:_bookKeeper + delegate:self]; + [_states setObject:uninitializedState forKey:@(stateId)]; + } + _currentState = [_states objectForKey:@(stateId)]; + break; + case SCCaptureInitializedStateId: + if (![_states objectForKey:@(stateId)]) { + SCCaptureInitializedState *initializedState = + [[SCCaptureInitializedState alloc] initWithPerformer:_queuePerformer + bookKeeper:_bookKeeper + delegate:self]; + [_states setObject:initializedState forKey:@(stateId)]; + } + _currentState = [_states objectForKey:@(stateId)]; + break; + case SCCaptureRunningStateId: + if (![_states objectForKey:@(stateId)]) { + SCCaptureRunningState *runningState = + [[SCCaptureRunningState alloc] initWithPerformer:_queuePerformer bookKeeper:_bookKeeper delegate:self]; + [_states setObject:runningState forKey:@(stateId)]; + } + _currentState = [_states objectForKey:@(stateId)]; + break; + case SCCaptureImageStateId: + if (![_states objectForKey:@(stateId)]) { + SCCaptureImageState *captureImageState = + [[SCCaptureImageState alloc] initWithPerformer:_queuePerformer bookKeeper:_bookKeeper delegate:self]; + [_states setObject:captureImageState forKey:@(stateId)]; + } + _currentState = [_states objectForKey:@(stateId)]; + break; + case SCCaptureImageWhileRecordingStateId: + if (![_states objectForKey:@(stateId)]) { + SCCaptureImageWhileRecordingState *captureImageWhileRecordingState = + [[SCCaptureImageWhileRecordingState alloc] initWithPerformer:_queuePerformer + bookKeeper:_bookKeeper + delegate:self]; + [_states setObject:captureImageWhileRecordingState forKey:@(stateId)]; + } + _currentState = [_states objectForKey:@(stateId)]; + break; + case SCCaptureScanningStateId: + if (![_states objectForKey:@(stateId)]) { + SCCaptureScanningState *scanningState = + [[SCCaptureScanningState alloc] initWithPerformer:_queuePerformer bookKeeper:_bookKeeper delegate:self]; + [_states setObject:scanningState forKey:@(stateId)]; + } + _currentState = [_states objectForKey:@(stateId)]; + break; + case SCCaptureRecordingStateId: + if (![_states objectForKey:@(stateId)]) { + SCCaptureRecordingState *recordingState = [[SCCaptureRecordingState alloc] initWithPerformer:_queuePerformer + bookKeeper:_bookKeeper + delegate:self]; + [_states setObject:recordingState forKey:@(stateId)]; + } + _currentState = [_states objectForKey:@(stateId)]; + break; + default: + SCAssert(NO, @"illigal state Id"); + break; + } + [_currentState didBecomeCurrentState:payload resource:_captureResource context:context]; +} + +- (void)initializeCaptureWithDevicePositionAsynchronously:(SCManagedCaptureDevicePosition)devicePosition + completionHandler:(dispatch_block_t)completionHandler + context:(NSString *)context +{ + [SCCaptureWorker setupCapturePreviewLayerController]; + + SCTraceResumeToken resumeToken = SCTraceCapture(); + [_queuePerformer perform:^{ + SCTraceResume(resumeToken); + [_currentState initializeCaptureWithDevicePosition:devicePosition + resource:_captureResource + completionHandler:completionHandler + context:context]; + }]; +} + +- (SCCapturerToken *)startRunningWithContext:(NSString *)context completionHandler:(dispatch_block_t)completionHandler +{ + [[SCLogger sharedInstance] updateLogTimedEventStart:kSCCameraMetricsOpen uniqueId:@""]; + + SCCapturerToken *token = [[SCCapturerToken alloc] initWithIdentifier:context]; + SCTraceResumeToken resumeToken = SCTraceCapture(); + [_queuePerformer perform:^{ + SCTraceResume(resumeToken); + [_currentState startRunningWithCapturerToken:token + resource:_captureResource + completionHandler:completionHandler + context:context]; + }]; + + return token; +} + +- (void)stopRunningWithCapturerToken:(SCCapturerToken *)token + completionHandler:(sc_managed_capturer_stop_running_completion_handler_t)completionHandler + context:(NSString *)context +{ + SCTraceResumeToken resumeToken = SCTraceCapture(); + [_queuePerformer perform:^{ + SCTraceResume(resumeToken); + [_currentState stopRunningWithCapturerToken:token + resource:_captureResource + completionHandler:completionHandler + context:context]; + }]; +} + +- (void)stopRunningWithCapturerToken:(SCCapturerToken *)token + after:(NSTimeInterval)delay + completionHandler:(sc_managed_capturer_stop_running_completion_handler_t)completionHandler + context:(NSString *)context +{ + SCTraceResumeToken resumeToken = SCTraceCapture(); + [_queuePerformer perform:^{ + SCTraceResume(resumeToken); + [_currentState stopRunningWithCapturerToken:token + resource:_captureResource + completionHandler:completionHandler + context:context]; + } + after:delay]; +} + +- (void)prepareForRecordingAsynchronouslyWithAudioConfiguration:(SCAudioConfiguration *)configuration + context:(NSString *)context +{ + SCTraceResumeToken resumeToken = SCTraceCapture(); + [_queuePerformer perform:^{ + SCTraceResume(resumeToken); + [_currentState prepareForRecordingWithResource:_captureResource + audioConfiguration:configuration + context:context]; + }]; +} + +- (void)startRecordingWithOutputSettings:(SCManagedVideoCapturerOutputSettings *)outputSettings + audioConfiguration:(SCAudioConfiguration *)configuration + maxDuration:(NSTimeInterval)maxDuration + fileURL:(NSURL *)fileURL + captureSessionID:(NSString *)captureSessionID + completionHandler:(sc_managed_capturer_start_recording_completion_handler_t)completionHandler + context:(NSString *)context +{ + SCTraceResumeToken resumeToken = SCTraceCapture(); + [_queuePerformer perform:^{ + SCTraceResume(resumeToken); + [_currentState startRecordingWithResource:_captureResource + audioConfiguration:configuration + outputSettings:outputSettings + maxDuration:maxDuration + fileURL:fileURL + captureSessionID:captureSessionID + completionHandler:completionHandler + context:context]; + }]; +} + +- (void)stopRecordingWithContext:(NSString *)context +{ + SCTraceResumeToken resumeToken = SCTraceCapture(); + [_queuePerformer perform:^{ + SCTraceResume(resumeToken); + [_currentState stopRecordingWithResource:_captureResource context:context]; + }]; +} + +- (void)cancelRecordingWithContext:(NSString *)context +{ + SCTraceResumeToken resumeToken = SCTraceCapture(); + [_queuePerformer perform:^{ + SCTraceResume(resumeToken); + [_currentState cancelRecordingWithResource:_captureResource context:context]; + }]; +} + +- (void)captureStillImageAsynchronouslyWithAspectRatio:(CGFloat)aspectRatio + captureSessionID:(NSString *)captureSessionID + completionHandler: + (sc_managed_capturer_capture_still_image_completion_handler_t)completionHandler + context:(NSString *)context +{ + [_queuePerformer perform:^() { + [_currentState captureStillImageWithResource:_captureResource + aspectRatio:aspectRatio + captureSessionID:captureSessionID + completionHandler:completionHandler + context:context]; + }]; +} + +- (void)startScanAsynchronouslyWithScanConfiguration:(SCScanConfiguration *)configuration context:(NSString *)context +{ + [_queuePerformer perform:^() { + [_currentState startScanWithScanConfiguration:configuration resource:_captureResource context:context]; + }]; +} + +- (void)stopScanAsynchronouslyWithCompletionHandler:(dispatch_block_t)completionHandler context:(NSString *)context +{ + [_queuePerformer perform:^() { + [_currentState stopScanWithCompletionHandler:completionHandler resource:_captureResource context:context]; + }]; +} + +- (void)currentState:(SCCaptureBaseState *)state + requestToTransferToNewState:(SCCaptureStateMachineStateId)newState + payload:(SCStateTransitionPayload *)payload + context:(NSString *)context +{ + SCAssertPerformer(_queuePerformer); + SCAssert(_currentState == state, @"state: %@ newState: %@ context:%@", SCCaptureStateName([state stateId]), + SCCaptureStateName(newState), context); + if (payload) { + SCAssert(payload.fromState == [state stateId], @"From state id check"); + SCAssert(payload.toState == newState, @"To state id check"); + } + + if (_currentState != state) { + return; + } + + [_bookKeeper stateTransitionFrom:[state stateId] to:newState context:context]; + [self _setCurrentState:newState payload:payload context:context]; +} + +@end diff --git a/ManagedCapturer/StateMachine/SCCaptureStateUtil.h b/ManagedCapturer/StateMachine/SCCaptureStateUtil.h new file mode 100644 index 0000000..1b8ca4a --- /dev/null +++ b/ManagedCapturer/StateMachine/SCCaptureStateUtil.h @@ -0,0 +1,37 @@ +// +// SCCaptureStateUtil.h +// Snapchat +// +// Created by Lin Jia on 10/27/17. +// +// + +#import "SCLogger+Camera.h" + +#import +#import + +#import + +#define SCLogCaptureStateMachineInfo(fmt, ...) SCLogCoreCameraInfo(@"[SCCaptureStateMachine] " fmt, ##__VA_ARGS__) +#define SCLogCaptureStateMachineError(fmt, ...) SCLogCoreCameraError(@"[SCCaptureStateMachine] " fmt, ##__VA_ARGS__) + +typedef NSNumber SCCaptureStateKey; + +typedef NS_ENUM(NSUInteger, SCCaptureStateMachineStateId) { + SCCaptureBaseStateId = 0, + SCCaptureUninitializedStateId, + SCCaptureInitializedStateId, + SCCaptureImageStateId, + SCCaptureImageWhileRecordingStateId, + SCCaptureRunningStateId, + SCCaptureRecordingStateId, + SCCaptureScanningStateId, + SCCaptureStateMachineStateIdCount +}; + +SC_EXTERN_C_BEGIN + +NSString *SCCaptureStateName(SCCaptureStateMachineStateId stateId); + +SC_EXTERN_C_END diff --git a/ManagedCapturer/StateMachine/SCCaptureStateUtil.m b/ManagedCapturer/StateMachine/SCCaptureStateUtil.m new file mode 100644 index 0000000..deb20a7 --- /dev/null +++ b/ManagedCapturer/StateMachine/SCCaptureStateUtil.m @@ -0,0 +1,38 @@ +// +// SCCaptureStateUtil.m +// Snapchat +// +// Created by Lin Jia on 10/27/17. +// +// + +#import "SCCaptureStateUtil.h" + +#import +#import + +NSString *SCCaptureStateName(SCCaptureStateMachineStateId stateId) +{ + switch (stateId) { + case SCCaptureBaseStateId: + return @"SCCaptureBaseStateId"; + case SCCaptureUninitializedStateId: + return @"SCCaptureUninitializedStateId"; + case SCCaptureInitializedStateId: + return @"SCCaptureInitializedStateId"; + case SCCaptureImageStateId: + return @"SCCaptureImageStateId"; + case SCCaptureImageWhileRecordingStateId: + return @"SCCaptureImageWhileRecordingStateId"; + case SCCaptureRunningStateId: + return @"SCCaptureRunningStateId"; + case SCCaptureRecordingStateId: + return @"SCCaptureRecordingStateId"; + case SCCaptureScanningStateId: + return @"SCCaptureScanningStateId"; + default: + SCCAssert(NO, @"illegate state id"); + break; + } + return @"SCIllegalStateId"; +} diff --git a/ManagedCapturer/StateMachine/SCManagedCapturerLogging.h b/ManagedCapturer/StateMachine/SCManagedCapturerLogging.h new file mode 100644 index 0000000..069b438 --- /dev/null +++ b/ManagedCapturer/StateMachine/SCManagedCapturerLogging.h @@ -0,0 +1,12 @@ +// +// SCManagedCapturerLogging.h +// Snapchat +// +// Created by Lin Jia on 11/13/17. +// + +#import + +#define SCLogCapturerInfo(fmt, ...) SCLogCoreCameraInfo(@"[SCManagedCapturer] " fmt, ##__VA_ARGS__) +#define SCLogCapturerWarning(fmt, ...) SCLogCoreCameraWarning(@"[SCManagedCapturer] " fmt, ##__VA_ARGS__) +#define SCLogCapturerError(fmt, ...) SCLogCoreCameraError(@"[SCManagedCapturer] " fmt, ##__VA_ARGS__) diff --git a/ManagedCapturer/StateMachine/States/SCCaptureImageState.h b/ManagedCapturer/StateMachine/States/SCCaptureImageState.h new file mode 100644 index 0000000..561b43f --- /dev/null +++ b/ManagedCapturer/StateMachine/States/SCCaptureImageState.h @@ -0,0 +1,22 @@ +// +// SCCaptureImageState.h +// Snapchat +// +// Created by Lin Jia on 1/8/18. +// + +#import "SCCaptureBaseState.h" + +#import + +@class SCQueuePerformer; + +@interface SCCaptureImageState : SCCaptureBaseState + +SC_INIT_AND_NEW_UNAVAILABLE + +- (instancetype)initWithPerformer:(SCQueuePerformer *)performer + bookKeeper:(SCCaptureStateMachineBookKeeper *)bookKeeper + delegate:(id)delegate; + +@end diff --git a/ManagedCapturer/StateMachine/States/SCCaptureImageState.m b/ManagedCapturer/StateMachine/States/SCCaptureImageState.m new file mode 100644 index 0000000..d26a0f4 --- /dev/null +++ b/ManagedCapturer/StateMachine/States/SCCaptureImageState.m @@ -0,0 +1,65 @@ +// +// SCCaptureImageState.m +// Snapchat +// +// Created by Lin Jia on 1/8/18. +// + +#import "SCCaptureImageState.h" + +#import "SCCaptureImageStateTransitionPayload.h" +#import "SCManagedCapturerV1_Private.h" +#import "SCStateTransitionPayload.h" + +#import +#import + +@interface SCCaptureImageState () { + __weak id _delegate; + SCQueuePerformer *_performer; +} +@end + +@implementation SCCaptureImageState + +- (instancetype)initWithPerformer:(SCQueuePerformer *)performer + bookKeeper:(SCCaptureStateMachineBookKeeper *)bookKeeper + delegate:(id)delegate +{ + self = [super initWithPerformer:performer bookKeeper:bookKeeper delegate:delegate]; + if (self) { + _delegate = delegate; + _performer = performer; + } + return self; +} + +- (void)didBecomeCurrentState:(SCStateTransitionPayload *)payload + resource:(SCCaptureResource *)resource + context:(NSString *)context +{ + SCAssertPerformer(_performer); + SCAssert(payload.toState == [self stateId], @""); + if (![payload isKindOfClass:[SCCaptureImageStateTransitionPayload class]]) { + SCAssertFail(@"wrong payload pass in"); + [_delegate currentState:self requestToTransferToNewState:payload.fromState payload:nil context:context]; + return; + } + SCCaptureImageStateTransitionPayload *captureImagePayload = (SCCaptureImageStateTransitionPayload *)payload; + + [SCCaptureWorker + captureStillImageWithCaptureResource:resource + aspectRatio:captureImagePayload.aspectRatio + captureSessionID:captureImagePayload.captureSessionID + shouldCaptureFromVideo:[SCCaptureWorker shouldCaptureImageFromVideoWithResource:resource] + completionHandler:captureImagePayload.block + context:context]; + + [_delegate currentState:self requestToTransferToNewState:SCCaptureRunningStateId payload:nil context:context]; +} + +- (SCCaptureStateMachineStateId)stateId +{ + return SCCaptureImageStateId; +} +@end diff --git a/ManagedCapturer/StateMachine/States/SCCaptureImageStateTransitionPayload.h b/ManagedCapturer/StateMachine/States/SCCaptureImageStateTransitionPayload.h new file mode 100644 index 0000000..ea82816 --- /dev/null +++ b/ManagedCapturer/StateMachine/States/SCCaptureImageStateTransitionPayload.h @@ -0,0 +1,29 @@ +// +// SCCaptureImageStateTransitionPayload.h +// Snapchat +// +// Created by Lin Jia on 1/9/18. +// + +#import "SCCaptureCommon.h" +#import "SCStateTransitionPayload.h" + +#import + +@interface SCCaptureImageStateTransitionPayload : SCStateTransitionPayload + +@property (nonatomic, readonly, strong) NSString *captureSessionID; + +@property (nonatomic, readonly, copy) sc_managed_capturer_capture_still_image_completion_handler_t block; + +@property (nonatomic, readonly, assign) CGFloat aspectRatio; + +SC_INIT_AND_NEW_UNAVAILABLE + +- (instancetype)initWithFromState:(SCCaptureStateMachineStateId)fromState + toState:(SCCaptureStateMachineStateId)toState + captureSessionId:(NSString *)captureSessionID + aspectRatio:(CGFloat)aspectRatio + completionHandler:(sc_managed_capturer_capture_still_image_completion_handler_t)block; + +@end diff --git a/ManagedCapturer/StateMachine/States/SCCaptureImageStateTransitionPayload.m b/ManagedCapturer/StateMachine/States/SCCaptureImageStateTransitionPayload.m new file mode 100644 index 0000000..45ba345 --- /dev/null +++ b/ManagedCapturer/StateMachine/States/SCCaptureImageStateTransitionPayload.m @@ -0,0 +1,27 @@ +// +// SCCaptureImageStateTransitionPayload.m +// Snapchat +// +// Created by Lin Jia on 1/9/18. +// + +#import "SCCaptureImageStateTransitionPayload.h" + +@implementation SCCaptureImageStateTransitionPayload + +- (instancetype)initWithFromState:(SCCaptureStateMachineStateId)fromState + toState:(SCCaptureStateMachineStateId)toState + captureSessionId:(NSString *)captureSessionID + aspectRatio:(CGFloat)aspectRatio + completionHandler:(sc_managed_capturer_capture_still_image_completion_handler_t)block +{ + self = [super initWithFromState:fromState toState:toState]; + if (self) { + _captureSessionID = captureSessionID; + _aspectRatio = aspectRatio; + _block = block; + } + return self; +} + +@end diff --git a/ManagedCapturer/StateMachine/States/SCCaptureImageWhileRecordingState.h b/ManagedCapturer/StateMachine/States/SCCaptureImageWhileRecordingState.h new file mode 100644 index 0000000..281b0a4 --- /dev/null +++ b/ManagedCapturer/StateMachine/States/SCCaptureImageWhileRecordingState.h @@ -0,0 +1,22 @@ +// +// SCCaptureImageWhileRecordingState.h +// Snapchat +// +// Created by Sun Lei on 22/02/2018. +// + +#import "SCCaptureBaseState.h" + +#import + +@class SCQueuePerformer; + +@interface SCCaptureImageWhileRecordingState : SCCaptureBaseState + +SC_INIT_AND_NEW_UNAVAILABLE + +- (instancetype)initWithPerformer:(SCQueuePerformer *)performer + bookKeeper:(SCCaptureStateMachineBookKeeper *)bookKeeper + delegate:(id)delegate; + +@end diff --git a/ManagedCapturer/StateMachine/States/SCCaptureImageWhileRecordingState.m b/ManagedCapturer/StateMachine/States/SCCaptureImageWhileRecordingState.m new file mode 100644 index 0000000..eb1e4e1 --- /dev/null +++ b/ManagedCapturer/StateMachine/States/SCCaptureImageWhileRecordingState.m @@ -0,0 +1,85 @@ +// +// SCCaptureImageWhileRecordingState.m +// Snapchat +// +// Created by Sun Lei on 22/02/2018. +// + +#import "SCCaptureImageWhileRecordingState.h" + +#import "SCCaptureImageWhileRecordingStateTransitionPayload.h" +#import "SCManagedCapturerV1_Private.h" + +#import +#import + +@interface SCCaptureImageWhileRecordingState () { + __weak id _delegate; + SCQueuePerformer *_performer; +} +@end + +@implementation SCCaptureImageWhileRecordingState + +- (instancetype)initWithPerformer:(SCQueuePerformer *)performer + bookKeeper:(SCCaptureStateMachineBookKeeper *)bookKeeper + delegate:(id)delegate +{ + self = [super initWithPerformer:performer bookKeeper:bookKeeper delegate:delegate]; + if (self) { + _delegate = delegate; + _performer = performer; + } + return self; +} + +- (SCCaptureStateMachineStateId)stateId +{ + return SCCaptureImageWhileRecordingStateId; +} + +- (void)didBecomeCurrentState:(SCStateTransitionPayload *)payload + resource:(SCCaptureResource *)resource + context:(NSString *)context +{ + SCAssertPerformer(_performer); + SCAssert(payload.fromState == SCCaptureRecordingStateId, @""); + SCAssert(payload.toState == [self stateId], @""); + SCAssert([payload isKindOfClass:[SCCaptureImageWhileRecordingStateTransitionPayload class]], @""); + ; + SCCaptureImageWhileRecordingStateTransitionPayload *captureImagePayload = + (SCCaptureImageWhileRecordingStateTransitionPayload *)payload; + + @weakify(self); + sc_managed_capturer_capture_still_image_completion_handler_t block = + ^(UIImage *fullScreenImage, NSDictionary *metadata, NSError *error, SCManagedCapturerState *state) { + captureImagePayload.block(fullScreenImage, metadata, error, state); + [_performer perform:^{ + @strongify(self); + [self _cancelRecordingWithContext:context resource:resource]; + }]; + }; + + [SCCaptureWorker + captureStillImageWithCaptureResource:resource + aspectRatio:captureImagePayload.aspectRatio + captureSessionID:captureImagePayload.captureSessionID + shouldCaptureFromVideo:[SCCaptureWorker shouldCaptureImageFromVideoWithResource:resource] + completionHandler:block + context:context]; + + [_delegate currentState:self requestToTransferToNewState:SCCaptureRunningStateId payload:nil context:context]; +} + +- (void)_cancelRecordingWithContext:(NSString *)context resource:(SCCaptureResource *)resource +{ + SCTraceODPCompatibleStart(2); + SCAssertPerformer(_performer); + + [SCCaptureWorker cancelRecordingWithCaptureResource:resource]; + + NSString *apiName = + [NSString sc_stringWithFormat:@"%@/%@", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; + [self.bookKeeper logAPICalled:apiName context:context]; +} +@end diff --git a/ManagedCapturer/StateMachine/States/SCCaptureImageWhileRecordingStateTransitionPayload.h b/ManagedCapturer/StateMachine/States/SCCaptureImageWhileRecordingStateTransitionPayload.h new file mode 100644 index 0000000..7079a10 --- /dev/null +++ b/ManagedCapturer/StateMachine/States/SCCaptureImageWhileRecordingStateTransitionPayload.h @@ -0,0 +1,29 @@ +// +// SCCaptureImageWhileRecordingStateTransitionPayload.h +// Snapchat +// +// Created by Sun Lei on 22/02/2018. +// + +#import "SCCaptureCommon.h" +#import "SCStateTransitionPayload.h" + +#import + +@interface SCCaptureImageWhileRecordingStateTransitionPayload : SCStateTransitionPayload + +@property (nonatomic, readonly, strong) NSString *captureSessionID; + +@property (nonatomic, readonly, copy) sc_managed_capturer_capture_still_image_completion_handler_t block; + +@property (nonatomic, readonly, assign) CGFloat aspectRatio; + +SC_INIT_AND_NEW_UNAVAILABLE + +- (instancetype)initWithFromState:(SCCaptureStateMachineStateId)fromState + toState:(SCCaptureStateMachineStateId)toState + captureSessionId:(NSString *)captureSessionID + aspectRatio:(CGFloat)aspectRatio + completionHandler:(sc_managed_capturer_capture_still_image_completion_handler_t)block; + +@end diff --git a/ManagedCapturer/StateMachine/States/SCCaptureImageWhileRecordingStateTransitionPayload.m b/ManagedCapturer/StateMachine/States/SCCaptureImageWhileRecordingStateTransitionPayload.m new file mode 100644 index 0000000..ae4f271 --- /dev/null +++ b/ManagedCapturer/StateMachine/States/SCCaptureImageWhileRecordingStateTransitionPayload.m @@ -0,0 +1,27 @@ +// +// SCCaptureImageWhileRecordingStateTransitionPayload.m +// Snapchat +// +// Created by Sun Lei on 22/02/2018. +// + +#import "SCCaptureImageWhileRecordingStateTransitionPayload.h" + +@implementation SCCaptureImageWhileRecordingStateTransitionPayload + +- (instancetype)initWithFromState:(SCCaptureStateMachineStateId)fromState + toState:(SCCaptureStateMachineStateId)toState + captureSessionId:(NSString *)captureSessionID + aspectRatio:(CGFloat)aspectRatio + completionHandler:(sc_managed_capturer_capture_still_image_completion_handler_t)block +{ + self = [super initWithFromState:fromState toState:toState]; + if (self) { + _captureSessionID = captureSessionID; + _aspectRatio = aspectRatio; + _block = block; + } + return self; +} + +@end diff --git a/ManagedCapturer/StateMachine/States/SCCaptureInitializedState.h b/ManagedCapturer/StateMachine/States/SCCaptureInitializedState.h new file mode 100644 index 0000000..5d5876c --- /dev/null +++ b/ManagedCapturer/StateMachine/States/SCCaptureInitializedState.h @@ -0,0 +1,22 @@ +// +// SCCaptureInitializedState.h +// Snapchat +// +// Created by Jingtian Yang on 20/12/2017. +// + +#import "SCCaptureBaseState.h" + +#import + +@class SCQueuePerformer; + +@interface SCCaptureInitializedState : SCCaptureBaseState + +- (instancetype)init NS_UNAVAILABLE; + +- (instancetype)initWithPerformer:(SCQueuePerformer *)performer + bookKeeper:(SCCaptureStateMachineBookKeeper *)bookKeeper + delegate:(id)delegate; + +@end diff --git a/ManagedCapturer/StateMachine/States/SCCaptureInitializedState.m b/ManagedCapturer/StateMachine/States/SCCaptureInitializedState.m new file mode 100644 index 0000000..7a687a6 --- /dev/null +++ b/ManagedCapturer/StateMachine/States/SCCaptureInitializedState.m @@ -0,0 +1,68 @@ +// +// SCCaptureInitializedState.m +// Snapchat +// +// Created by Jingtian Yang on 20/12/2017. +// + +#import "SCCaptureInitializedState.h" + +#import "SCCapturerToken.h" +#import "SCManagedCapturerLogging.h" +#import "SCManagedCapturerV1_Private.h" + +#import +#import + +@interface SCCaptureInitializedState () { + __weak id _delegate; + SCQueuePerformer *_performer; +} + +@end + +@implementation SCCaptureInitializedState + +- (instancetype)initWithPerformer:(SCQueuePerformer *)performer + bookKeeper:(SCCaptureStateMachineBookKeeper *)bookKeeper + delegate:(id)delegate +{ + self = [super initWithPerformer:performer bookKeeper:bookKeeper delegate:delegate]; + if (self) { + _delegate = delegate; + _performer = performer; + } + return self; +} + +- (void)didBecomeCurrentState:(SCStateTransitionPayload *)payload + resource:(SCCaptureResource *)resource + context:(NSString *)context +{ + // No op. +} + +- (SCCaptureStateMachineStateId)stateId +{ + return SCCaptureInitializedStateId; +} + +- (void)startRunningWithCapturerToken:(SCCapturerToken *)token + resource:(SCCaptureResource *)resource + completionHandler:(dispatch_block_t)completionHandler + context:(NSString *)context +{ + SCAssertPerformer(_performer); + SCTraceODPCompatibleStart(2); + SCLogCapturerInfo(@"startRunningAsynchronouslyWithCompletionHandler called. token: %@", token); + + [SCCaptureWorker startRunningWithCaptureResource:resource token:token completionHandler:completionHandler]; + + [_delegate currentState:self requestToTransferToNewState:SCCaptureRunningStateId payload:nil context:context]; + + NSString *apiName = + [NSString sc_stringWithFormat:@"%@/%@", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; + [self.bookKeeper logAPICalled:apiName context:context]; +} + +@end diff --git a/ManagedCapturer/StateMachine/States/SCCaptureRecordingState.h b/ManagedCapturer/StateMachine/States/SCCaptureRecordingState.h new file mode 100644 index 0000000..a6bbbf0 --- /dev/null +++ b/ManagedCapturer/StateMachine/States/SCCaptureRecordingState.h @@ -0,0 +1,22 @@ +// +// SCCaptureRecordingState.h +// Snapchat +// +// Created by Jingtian Yang on 12/01/2018. +// + +#import "SCCaptureBaseState.h" + +#import + +@class SCQueuePerformer; + +@interface SCCaptureRecordingState : SCCaptureBaseState + +SC_INIT_AND_NEW_UNAVAILABLE + +- (instancetype)initWithPerformer:(SCQueuePerformer *)performer + bookKeeper:(SCCaptureStateMachineBookKeeper *)bookKeeper + delegate:(id)delegate; + +@end diff --git a/ManagedCapturer/StateMachine/States/SCCaptureRecordingState.m b/ManagedCapturer/StateMachine/States/SCCaptureRecordingState.m new file mode 100644 index 0000000..fb7513c --- /dev/null +++ b/ManagedCapturer/StateMachine/States/SCCaptureRecordingState.m @@ -0,0 +1,114 @@ +// +// SCCaptureRecordingState.m +// Snapchat +// +// Created by Jingtian Yang on 12/01/2018. +// + +#import "SCCaptureRecordingState.h" + +#import "SCCaptureImageWhileRecordingStateTransitionPayload.h" +#import "SCCaptureRecordingStateTransitionPayload.h" +#import "SCManagedCapturerV1_Private.h" +#import "SCStateTransitionPayload.h" + +#import +#import + +@interface SCCaptureRecordingState () { + __weak id _delegate; + SCQueuePerformer *_performer; +} +@end + +@implementation SCCaptureRecordingState + +- (instancetype)initWithPerformer:(SCQueuePerformer *)performer + bookKeeper:(SCCaptureStateMachineBookKeeper *)bookKeeper + delegate:(id)delegate +{ + self = [super initWithPerformer:performer bookKeeper:bookKeeper delegate:delegate]; + if (self) { + _delegate = delegate; + _performer = performer; + } + return self; +} + +- (void)didBecomeCurrentState:(SCStateTransitionPayload *)payload + resource:(SCCaptureResource *)resource + context:(NSString *)context +{ + SCAssertPerformer(resource.queuePerformer); + SCAssert(payload.toState == [self stateId], @""); + if (![payload isKindOfClass:[SCCaptureRecordingStateTransitionPayload class]]) { + SCAssertFail(@"wrong payload pass in"); + [_delegate currentState:self requestToTransferToNewState:payload.fromState payload:nil context:context]; + return; + } + + SCCaptureRecordingStateTransitionPayload *recordingPayload = (SCCaptureRecordingStateTransitionPayload *)payload; + [SCCaptureWorker startRecordingWithCaptureResource:resource + outputSettings:recordingPayload.outputSettings + audioConfiguration:recordingPayload.configuration + maxDuration:recordingPayload.maxDuration + fileURL:recordingPayload.fileURL + captureSessionID:recordingPayload.captureSessionID + completionHandler:recordingPayload.block]; +} + +- (void)stopRecordingWithResource:(SCCaptureResource *)resource context:(NSString *)context +{ + SCTraceODPCompatibleStart(2); + SCAssertPerformer(_performer); + + [SCCaptureWorker stopRecordingWithCaptureResource:resource]; + [_delegate currentState:self requestToTransferToNewState:SCCaptureRunningStateId payload:nil context:context]; + + NSString *apiName = + [NSString sc_stringWithFormat:@"%@/%@", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; + [self.bookKeeper logAPICalled:apiName context:context]; +} + +- (void)cancelRecordingWithResource:(SCCaptureResource *)resource context:(NSString *)context +{ + SCTraceODPCompatibleStart(2); + SCAssertPerformer(_performer); + + [SCCaptureWorker cancelRecordingWithCaptureResource:resource]; + [_delegate currentState:self requestToTransferToNewState:SCCaptureRunningStateId payload:nil context:context]; + + NSString *apiName = + [NSString sc_stringWithFormat:@"%@/%@", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; + [self.bookKeeper logAPICalled:apiName context:context]; +} + +- (SCCaptureStateMachineStateId)stateId +{ + return SCCaptureRecordingStateId; +} + +- (void)captureStillImageWithResource:(SCCaptureResource *)resource + aspectRatio:(CGFloat)aspectRatio + captureSessionID:(NSString *)captureSessionID + completionHandler:(sc_managed_capturer_capture_still_image_completion_handler_t)completionHandler + context:(NSString *)context +{ + SCAssertPerformer(_performer); + SCCaptureImageWhileRecordingStateTransitionPayload *payload = [ + [SCCaptureImageWhileRecordingStateTransitionPayload alloc] initWithFromState:SCCaptureRecordingStateId + toState:SCCaptureImageWhileRecordingStateId + captureSessionId:captureSessionID + aspectRatio:aspectRatio + completionHandler:completionHandler]; + [_delegate currentState:self + requestToTransferToNewState:SCCaptureImageWhileRecordingStateId + payload:payload + context:context]; + + NSString *apiName = + [NSString sc_stringWithFormat:@"%@/%@", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; + [self.bookKeeper logAPICalled:apiName context:context]; +} + +@end diff --git a/ManagedCapturer/StateMachine/States/SCCaptureRecordingStateTransitionPayload.h b/ManagedCapturer/StateMachine/States/SCCaptureRecordingStateTransitionPayload.h new file mode 100644 index 0000000..4995daa --- /dev/null +++ b/ManagedCapturer/StateMachine/States/SCCaptureRecordingStateTransitionPayload.h @@ -0,0 +1,41 @@ +// +// SCCaptureRecordingStateTransitionPayload.h +// Snapchat +// +// Created by Jingtian Yang on 12/01/2018. +// + +#import "SCCaptureCommon.h" +#import "SCManagedVideoCapturerOutputSettings.h" +#import "SCStateTransitionPayload.h" + +#import + +#import + +@interface SCCaptureRecordingStateTransitionPayload : SCStateTransitionPayload + +@property (nonatomic, readonly, strong) SCManagedVideoCapturerOutputSettings *outputSettings; + +@property (nonatomic, readonly, strong) SCAudioConfiguration *configuration; + +@property (nonatomic, readonly, assign) NSTimeInterval maxDuration; + +@property (nonatomic, readonly, strong) NSURL *fileURL; + +@property (nonatomic, readonly, strong) NSString *captureSessionID; + +@property (nonatomic, readonly, copy) sc_managed_capturer_start_recording_completion_handler_t block; + +SC_INIT_AND_NEW_UNAVAILABLE + +- (instancetype)initWithFromState:(SCCaptureStateMachineStateId)fromState + toState:(SCCaptureStateMachineStateId)toState + outputSettings:(SCManagedVideoCapturerOutputSettings *)outputSettings + audioConfiguration:(SCAudioConfiguration *)configuration + maxDuration:(NSTimeInterval)maxDuration + fileURL:(NSURL *)fileURL + captureSessionID:(NSString *)captureSessionID + completionHandler:(sc_managed_capturer_start_recording_completion_handler_t)block; + +@end diff --git a/ManagedCapturer/StateMachine/States/SCCaptureRecordingStateTransitionPayload.m b/ManagedCapturer/StateMachine/States/SCCaptureRecordingStateTransitionPayload.m new file mode 100644 index 0000000..167031a --- /dev/null +++ b/ManagedCapturer/StateMachine/States/SCCaptureRecordingStateTransitionPayload.m @@ -0,0 +1,33 @@ +// +// SCCaptureRecordingStateTransitionPayload.m +// Snapchat +// +// Created by Jingtian Yang on 12/01/2018. +// + +#import "SCCaptureRecordingStateTransitionPayload.h" + +@implementation SCCaptureRecordingStateTransitionPayload + +- (instancetype)initWithFromState:(SCCaptureStateMachineStateId)fromState + toState:(SCCaptureStateMachineStateId)toState + outputSettings:(SCManagedVideoCapturerOutputSettings *)outputSettings + audioConfiguration:configuration + maxDuration:(NSTimeInterval)maxDuration + fileURL:(NSURL *)fileURL + captureSessionID:(NSString *)captureSessionID + completionHandler:(sc_managed_capturer_start_recording_completion_handler_t)block +{ + self = [super initWithFromState:fromState toState:toState]; + if (self) { + _outputSettings = outputSettings; + _configuration = configuration; + _maxDuration = maxDuration; + _fileURL = fileURL; + _captureSessionID = captureSessionID; + _block = block; + } + return self; +} + +@end diff --git a/ManagedCapturer/StateMachine/States/SCCaptureRunningState.h b/ManagedCapturer/StateMachine/States/SCCaptureRunningState.h new file mode 100644 index 0000000..4912a4a --- /dev/null +++ b/ManagedCapturer/StateMachine/States/SCCaptureRunningState.h @@ -0,0 +1,22 @@ +// +// SCCaptureRunningState.h +// Snapchat +// +// Created by Jingtian Yang on 08/01/2018. +// + +#import "SCCaptureBaseState.h" + +#import + +@class SCQueuePerformer; + +@interface SCCaptureRunningState : SCCaptureBaseState + +- (instancetype)init NS_UNAVAILABLE; + +- (instancetype)initWithPerformer:(SCQueuePerformer *)performer + bookKeeper:(SCCaptureStateMachineBookKeeper *)bookKeeper + delegate:(id)delegate; + +@end diff --git a/ManagedCapturer/StateMachine/States/SCCaptureRunningState.m b/ManagedCapturer/StateMachine/States/SCCaptureRunningState.m new file mode 100644 index 0000000..3fd665e --- /dev/null +++ b/ManagedCapturer/StateMachine/States/SCCaptureRunningState.m @@ -0,0 +1,176 @@ +// +// SCCaptureRunningState.m +// Snapchat +// +// Created by Jingtian Yang on 08/01/2018. +// + +#import "SCCaptureRunningState.h" + +#import "SCCaptureImageStateTransitionPayload.h" +#import "SCCaptureRecordingStateTransitionPayload.h" +#import "SCCaptureWorker.h" +#import "SCManagedCapturerLogging.h" +#import "SCManagedCapturerV1_Private.h" +#import "SCScanConfiguration.h" + +#import +#import +#import + +@interface SCCaptureRunningState () { + __weak id _delegate; + SCQueuePerformer *_performer; +} + +@end + +@implementation SCCaptureRunningState + +- (instancetype)initWithPerformer:(SCQueuePerformer *)performer + bookKeeper:(SCCaptureStateMachineBookKeeper *)bookKeeper + delegate:(id)delegate +{ + self = [super initWithPerformer:performer bookKeeper:bookKeeper delegate:delegate]; + if (self) { + _delegate = delegate; + _performer = performer; + } + return self; +} + +- (void)didBecomeCurrentState:(SCStateTransitionPayload *)payload + resource:(SCCaptureResource *)resource + context:(NSString *)context +{ + // No op. +} + +- (void)captureStillImageWithResource:(SCCaptureResource *)resource + aspectRatio:(CGFloat)aspectRatio + captureSessionID:(NSString *)captureSessionID + completionHandler:(sc_managed_capturer_capture_still_image_completion_handler_t)completionHandler + context:(NSString *)context +{ + SCAssertPerformer(_performer); + SCCaptureImageStateTransitionPayload *payload = + [[SCCaptureImageStateTransitionPayload alloc] initWithFromState:SCCaptureRunningStateId + toState:SCCaptureImageStateId + captureSessionId:captureSessionID + aspectRatio:aspectRatio + completionHandler:completionHandler]; + [_delegate currentState:self requestToTransferToNewState:SCCaptureImageStateId payload:payload context:context]; + + NSString *apiName = + [NSString sc_stringWithFormat:@"%@/%@", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; + [self.bookKeeper logAPICalled:apiName context:context]; +} + +- (SCCaptureStateMachineStateId)stateId +{ + return SCCaptureRunningStateId; +} + +- (void)startRunningWithCapturerToken:(SCCapturerToken *)token + resource:(SCCaptureResource *)resource + completionHandler:(dispatch_block_t)completionHandler + context:(NSString *)context +{ + SCAssertPerformer(_performer); + SCTraceODPCompatibleStart(2); + SCLogCapturerInfo(@"startRunningAsynchronouslyWithCompletionHandler called. token: %@", token); + [SCCaptureWorker startRunningWithCaptureResource:resource token:token completionHandler:completionHandler]; + + NSString *apiName = + [NSString sc_stringWithFormat:@"%@/%@", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; + [self.bookKeeper logAPICalled:apiName context:context]; +} + +- (void)stopRunningWithCapturerToken:(SCCapturerToken *)token + resource:(SCCaptureResource *)resource + completionHandler:(sc_managed_capturer_stop_running_completion_handler_t)completionHandler + context:(NSString *)context +{ + SCTraceODPCompatibleStart(2); + SCAssertPerformer(_performer); + + SCLogCapturerInfo(@"Stop running asynchronously. token:%@", token); + if ([[SCManagedCapturerV1 sharedInstance] stopRunningWithCaptureToken:token + completionHandler:completionHandler + context:context]) { + [_delegate currentState:self + requestToTransferToNewState:SCCaptureInitializedStateId + payload:nil + context:context]; + } + + NSString *apiName = + [NSString sc_stringWithFormat:@"%@/%@", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; + [self.bookKeeper logAPICalled:apiName context:context]; +} + +- (void)startScanWithScanConfiguration:(SCScanConfiguration *)configuration + resource:(SCCaptureResource *)resource + context:(NSString *)context +{ + SCTraceODPCompatibleStart(2); + SCLogCapturerInfo(@"Start scan on preview asynchronously. configuration:%@", configuration); + SCAssertPerformer(_performer); + [SCCaptureWorker startScanWithScanConfiguration:configuration resource:resource]; + [_delegate currentState:self requestToTransferToNewState:SCCaptureScanningStateId payload:nil context:context]; + + NSString *apiName = + [NSString sc_stringWithFormat:@"%@/%@", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; + [self.bookKeeper logAPICalled:apiName context:context]; +} + +- (void)prepareForRecordingWithResource:(SCCaptureResource *)resource + audioConfiguration:(SCAudioConfiguration *)configuration + context:(NSString *)context +{ + SCAssertPerformer(_performer); + SCTraceODPCompatibleStart(2); + [SCCaptureWorker prepareForRecordingWithAudioConfiguration:configuration resource:resource]; + + NSString *apiName = + [NSString sc_stringWithFormat:@"%@/%@", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; + [self.bookKeeper logAPICalled:apiName context:context]; +} + +- (void)startRecordingWithResource:(SCCaptureResource *)resource + audioConfiguration:(SCAudioConfiguration *)configuration + outputSettings:(SCManagedVideoCapturerOutputSettings *)outputSettings + maxDuration:(NSTimeInterval)maxDuration + fileURL:(NSURL *)fileURL + captureSessionID:(NSString *)captureSessionID + completionHandler:(sc_managed_capturer_start_recording_completion_handler_t)completionHandler + context:(NSString *)context +{ + SCTraceODPCompatibleStart(2); + SCAssertPerformer(_performer); + + SCCaptureRecordingStateTransitionPayload *payload = + [[SCCaptureRecordingStateTransitionPayload alloc] initWithFromState:SCCaptureRunningStateId + toState:SCCaptureRecordingStateId + outputSettings:outputSettings + audioConfiguration:configuration + maxDuration:maxDuration + fileURL:fileURL + captureSessionID:captureSessionID + completionHandler:completionHandler]; + [_delegate currentState:self requestToTransferToNewState:SCCaptureRecordingStateId payload:payload context:context]; + + NSString *apiName = + [NSString sc_stringWithFormat:@"%@/%@", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; + [self.bookKeeper logAPICalled:apiName context:context]; +} + +- (void)cancelRecordingWithResource:(SCCaptureResource *)resource context:(NSString *)context +{ + // Intentionally No Op, this will be removed once CCAM-13851 gets resolved. + NSString *apiName = + [NSString sc_stringWithFormat:@"%@/%@", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; + [self.bookKeeper logAPICalled:apiName context:context]; +} + +@end diff --git a/ManagedCapturer/StateMachine/States/SCCaptureScanningState.h b/ManagedCapturer/StateMachine/States/SCCaptureScanningState.h new file mode 100644 index 0000000..0e60f79 --- /dev/null +++ b/ManagedCapturer/StateMachine/States/SCCaptureScanningState.h @@ -0,0 +1,18 @@ +// +// SCCaptureScanningState.h +// Snapchat +// +// Created by Xiaokang Liu on 09/01/2018. +// + +#import "SCCaptureBaseState.h" + +@class SCQueuePerformer; + +@interface SCCaptureScanningState : SCCaptureBaseState +- (instancetype)init NS_UNAVAILABLE; + +- (instancetype)initWithPerformer:(SCQueuePerformer *)performer + bookKeeper:(SCCaptureStateMachineBookKeeper *)bookKeeper + delegate:(id)delegate; +@end diff --git a/ManagedCapturer/StateMachine/States/SCCaptureScanningState.m b/ManagedCapturer/StateMachine/States/SCCaptureScanningState.m new file mode 100644 index 0000000..7b6f0e7 --- /dev/null +++ b/ManagedCapturer/StateMachine/States/SCCaptureScanningState.m @@ -0,0 +1,75 @@ +// +// SCCaptureScanningState.m +// Snapchat +// +// Created by Xiaokang Liu on 09/01/2018. +// + +#import "SCCaptureScanningState.h" + +#import "SCManagedCapturerLogging.h" +#import "SCManagedCapturerV1_Private.h" + +#import +#import +#import + +@interface SCCaptureScanningState () { + __weak id _delegate; + SCQueuePerformer *_performer; +} + +@end + +@implementation SCCaptureScanningState +- (instancetype)initWithPerformer:(SCQueuePerformer *)performer + bookKeeper:(SCCaptureStateMachineBookKeeper *)bookKeeper + delegate:(id)delegate +{ + self = [super initWithPerformer:performer bookKeeper:bookKeeper delegate:delegate]; + if (self) { + SCAssert(delegate, @""); + SCAssert(performer, @""); + SCAssert(bookKeeper, @""); + _delegate = delegate; + _performer = performer; + } + return self; +} + +- (void)didBecomeCurrentState:(SCStateTransitionPayload *)payload + resource:(SCCaptureResource *)resource + context:(NSString *)context +{ + // No op. +} + +- (SCCaptureStateMachineStateId)stateId +{ + return SCCaptureScanningStateId; +} + +- (void)stopScanWithCompletionHandler:(dispatch_block_t)completionHandler + resource:(SCCaptureResource *)resource + context:(NSString *)context +{ + SCAssertPerformer(_performer); + SCTraceODPCompatibleStart(2); + SCLogCapturerInfo(@"stop scan asynchronously."); + [SCCaptureWorker stopScanWithCompletionHandler:completionHandler resource:resource]; + [_delegate currentState:self requestToTransferToNewState:SCCaptureRunningStateId payload:nil context:context]; + + NSString *apiName = + [NSString sc_stringWithFormat:@"%@/%@", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; + [self.bookKeeper logAPICalled:apiName context:context]; +} + +- (void)cancelRecordingWithResource:(SCCaptureResource *)resource context:(NSString *)context +{ + // Intentionally No Op, this will be removed once CCAM-13851 gets resolved. + NSString *apiName = + [NSString sc_stringWithFormat:@"%@/%@", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; + [self.bookKeeper logAPICalled:apiName context:context]; +} + +@end diff --git a/ManagedCapturer/StateMachine/States/SCCaptureUninitializedState.h b/ManagedCapturer/StateMachine/States/SCCaptureUninitializedState.h new file mode 100644 index 0000000..0809581 --- /dev/null +++ b/ManagedCapturer/StateMachine/States/SCCaptureUninitializedState.h @@ -0,0 +1,26 @@ +// +// SCCaptureUninitializedState.h +// Snapchat +// +// Created by Lin Jia on 10/19/17. +// +// + +#import "SCCaptureBaseState.h" + +#import + +/* + State which handles capture initialialization, which should be used only once for every app life span. +*/ +@class SCQueuePerformer; + +@interface SCCaptureUninitializedState : SCCaptureBaseState + +- (instancetype)init NS_UNAVAILABLE; + +- (instancetype)initWithPerformer:(SCQueuePerformer *)performer + bookKeeper:(SCCaptureStateMachineBookKeeper *)bookKeeper + delegate:(id)delegate; + +@end diff --git a/ManagedCapturer/StateMachine/States/SCCaptureUninitializedState.m b/ManagedCapturer/StateMachine/States/SCCaptureUninitializedState.m new file mode 100644 index 0000000..ffe99bf --- /dev/null +++ b/ManagedCapturer/StateMachine/States/SCCaptureUninitializedState.m @@ -0,0 +1,70 @@ +// +// SCCaptureUninitializedState.m +// Snapchat +// +// Created by Lin Jia on 10/19/17. +// +// + +#import "SCCaptureUninitializedState.h" + +#import "SCManagedCapturerLogging.h" +#import "SCManagedCapturerV1_Private.h" + +#import +#import +#import + +@interface SCCaptureUninitializedState () { + __weak id _delegate; + SCQueuePerformer *_performer; +} + +@end + +@implementation SCCaptureUninitializedState + +- (instancetype)initWithPerformer:(SCQueuePerformer *)performer + bookKeeper:(SCCaptureStateMachineBookKeeper *)bookKeeper + delegate:(id)delegate +{ + self = [super initWithPerformer:performer bookKeeper:bookKeeper delegate:delegate]; + if (self) { + _delegate = delegate; + _performer = performer; + } + return self; +} + +- (void)didBecomeCurrentState:(SCStateTransitionPayload *)payload + resource:(SCCaptureResource *)resource + context:(NSString *)context +{ + // No op. +} + +- (SCCaptureStateMachineStateId)stateId +{ + return SCCaptureUninitializedStateId; +} + +- (void)initializeCaptureWithDevicePosition:(SCManagedCaptureDevicePosition)devicePosition + resource:(SCCaptureResource *)resource + completionHandler:(dispatch_block_t)completionHandler + context:(NSString *)context +{ + SCAssertPerformer(_performer); + SCTraceODPCompatibleStart(2); + SCLogCapturerInfo(@"Setting up with devicePosition:%lu", (unsigned long)devicePosition); + + // TODO: we need to push completionHandler to a payload and let intializedState handle. + [[SCManagedCapturerV1 sharedInstance] setupWithDevicePosition:devicePosition completionHandler:completionHandler]; + + [_delegate currentState:self requestToTransferToNewState:SCCaptureInitializedStateId payload:nil context:context]; + + NSString *apiName = + [NSString sc_stringWithFormat:@"%@/%@", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; + [self.bookKeeper logAPICalled:apiName context:context]; +} + +@end diff --git a/ManagedCapturer/StateMachine/States/SCStateTransitionPayload.h b/ManagedCapturer/StateMachine/States/SCStateTransitionPayload.h new file mode 100644 index 0000000..8fca174 --- /dev/null +++ b/ManagedCapturer/StateMachine/States/SCStateTransitionPayload.h @@ -0,0 +1,22 @@ +// +// SCStateTransitionPayload.h +// Snapchat +// +// Created by Lin Jia on 1/8/18. +// + +#import "SCCaptureStateUtil.h" + +#import + +@interface SCStateTransitionPayload : NSObject + +@property (nonatomic, readonly, assign) SCCaptureStateMachineStateId fromState; + +@property (nonatomic, readonly, assign) SCCaptureStateMachineStateId toState; + +SC_INIT_AND_NEW_UNAVAILABLE + +- (instancetype)initWithFromState:(SCCaptureStateMachineStateId)fromState toState:(SCCaptureStateMachineStateId)toState; + +@end diff --git a/ManagedCapturer/StateMachine/States/SCStateTransitionPayload.m b/ManagedCapturer/StateMachine/States/SCStateTransitionPayload.m new file mode 100644 index 0000000..d4df2bd --- /dev/null +++ b/ManagedCapturer/StateMachine/States/SCStateTransitionPayload.m @@ -0,0 +1,27 @@ +// +// SCStateTransitionPayload.m +// Snapchat +// +// Created by Lin Jia on 1/8/18. +// + +#import "SCStateTransitionPayload.h" + +#import + +@implementation SCStateTransitionPayload + +- (instancetype)initWithFromState:(SCCaptureStateMachineStateId)fromState toState:(SCCaptureStateMachineStateId)toState +{ + self = [super init]; + if (self) { + SCAssert(fromState != toState, @""); + SCAssert(fromState > SCCaptureBaseStateId && fromState < SCCaptureStateMachineStateIdCount, @""); + SCAssert(toState > SCCaptureBaseStateId && toState < SCCaptureStateMachineStateIdCount, @""); + _fromState = fromState; + _toState = toState; + } + return self; +} + +@end diff --git a/ManagedCapturer/UIScreen+Debug.h b/ManagedCapturer/UIScreen+Debug.h new file mode 100644 index 0000000..58d54a1 --- /dev/null +++ b/ManagedCapturer/UIScreen+Debug.h @@ -0,0 +1,13 @@ +// +// UIScreen+Debug.h +// Snapchat +// +// Created by Derek Peirce on 6/1/17. +// Copyright © 2017 Snapchat, Inc. All rights reserved. +// + +#import + +@interface UIScreen (Debug) + +@end diff --git a/ManagedCapturer/UIScreen+Debug.m b/ManagedCapturer/UIScreen+Debug.m new file mode 100644 index 0000000..26a121c --- /dev/null +++ b/ManagedCapturer/UIScreen+Debug.m @@ -0,0 +1,28 @@ + +#import "UIScreen+Debug.h" + +#import +#import + +#import + +@implementation UIScreen (Debug) ++ (void)load +{ + if (SCIsPerformanceLoggingEnabled()) { + static dispatch_once_t once_token; + dispatch_once(&once_token, ^{ + SEL setBrightnessSelector = @selector(setBrightness:); + SEL setBrightnessLoggerSelector = @selector(logged_setBrightness:); + Method originalMethod = class_getInstanceMethod(self, setBrightnessSelector); + Method extendedMethod = class_getInstanceMethod(self, setBrightnessLoggerSelector); + method_exchangeImplementations(originalMethod, extendedMethod); + }); + } +} +- (void)logged_setBrightness:(CGFloat)brightness +{ + SCLogGeneralInfo(@"Setting brightness from %f to %f", self.brightness, brightness); + [self logged_setBrightness:brightness]; +} +@end