From c7fd130a815218d3b18c9026556895e5c74ec741 Mon Sep 17 00:00:00 2001 From: Jonny Banana Date: Wed, 8 Aug 2018 18:42:42 +0200 Subject: [PATCH] Add files via upload --- BlackCamera/SCBlackCameraDetector.h | 56 +++++ BlackCamera/SCBlackCameraDetector.m | 134 +++++++++++ BlackCamera/SCBlackCameraNoOutputDetector.h | 26 ++ BlackCamera/SCBlackCameraNoOutputDetector.m | 137 +++++++++++ BlackCamera/SCBlackCameraPreviewDetector.h | 20 ++ BlackCamera/SCBlackCameraPreviewDetector.m | 92 +++++++ BlackCamera/SCBlackCameraReporter.h | 35 +++ BlackCamera/SCBlackCameraReporter.m | 86 +++++++ BlackCamera/SCBlackCameraRunningDetector.h | 27 +++ BlackCamera/SCBlackCameraRunningDetector.m | 84 +++++++ .../SCBlackCameraSessionBlockDetector.h | 23 ++ .../SCBlackCameraSessionBlockDetector.m | 82 +++++++ BlackCamera/SCBlackCameraViewDetector.h | 31 +++ BlackCamera/SCBlackCameraViewDetector.m | 136 +++++++++++ BlackCamera/SCCaptureSessionFixer.h | 14 ++ BlackCamera/SCCaptureSessionFixer.m | 21 ++ ContextAwareTaskManagement/OWNERS | 13 + ...CContextAwareSnapCreationThrottleRequest.h | 16 ++ ...CContextAwareSnapCreationThrottleRequest.m | 70 ++++++ .../Triggers/SCSnapCreationTriggers.h | 22 ++ .../Triggers/SCSnapCreationTriggers.m | 83 +++++++ Features/Core/SCFeature.h | 26 ++ Features/Core/SCFeatureContainerView.h | 13 + Features/Core/SCFeatureCoordinator.h | 44 ++++ Features/Core/SCFeatureCoordinator.m | 117 +++++++++ Features/Core/SCFeatureProvider.h | 50 ++++ Features/Flash/SCFeatureFlash.h | 20 ++ Features/Flash/SCFeatureFlashImpl.h | 23 ++ Features/Flash/SCFeatureFlashImpl.m | 226 ++++++++++++++++++ Features/Flash/SCFlashButton.h | 15 ++ Features/Flash/SCFlashButton.m | 35 +++ Features/HandsFree/SCFeatureHandsFree.h | 30 +++ Features/ImageCapture/SCFeatureImageCapture.h | 27 +++ .../ImageCapture/SCFeatureImageCaptureImpl.h | 21 ++ .../ImageCapture/SCFeatureImageCaptureImpl.m | 184 ++++++++++++++ Features/NightMode/SCFeatureNightMode.h | 22 ++ Features/NightMode/SCNightModeButton.h | 18 ++ Features/NightMode/SCNightModeButton.m | 95 ++++++++ Features/Scanning/SCFeatureScanning.h | 26 ++ Features/Shazam/SCFeatureShazam.h | 23 ++ Features/SnapKit/SCFeatureSnapKit.h | 14 ++ .../SCFeatureTapToFocusAndExposure.h | 17 ++ .../SCFeatureTapToFocusAndExposureImpl.h | 49 ++++ .../SCFeatureTapToFocusAndExposureImpl.m | 118 +++++++++ Features/TapToFocus/SCTapAnimationView.h | 21 ++ Features/TapToFocus/SCTapAnimationView.m | 178 ++++++++++++++ Features/ToggleCamera/SCFeatureToggleCamera.h | 37 +++ Features/Zooming/SCFeatureZooming.h | 34 +++ .../SCManagedCapturerARImageCaptureProvider.h | 23 ++ Lens/SCManagedCapturerGLViewManagerAPI.h | 27 +++ .../SCManagedCapturerLSAComponentTrackerAPI.h | 19 ++ Lens/SCManagedCapturerLensAPI.h | 67 ++++++ Lens/SCManagedCapturerLensAPIProvider.h | 20 ++ 53 files changed, 2847 insertions(+) create mode 100644 BlackCamera/SCBlackCameraDetector.h create mode 100644 BlackCamera/SCBlackCameraDetector.m create mode 100644 BlackCamera/SCBlackCameraNoOutputDetector.h create mode 100644 BlackCamera/SCBlackCameraNoOutputDetector.m create mode 100644 BlackCamera/SCBlackCameraPreviewDetector.h create mode 100644 BlackCamera/SCBlackCameraPreviewDetector.m create mode 100644 BlackCamera/SCBlackCameraReporter.h create mode 100644 BlackCamera/SCBlackCameraReporter.m create mode 100644 BlackCamera/SCBlackCameraRunningDetector.h create mode 100644 BlackCamera/SCBlackCameraRunningDetector.m create mode 100644 BlackCamera/SCBlackCameraSessionBlockDetector.h create mode 100644 BlackCamera/SCBlackCameraSessionBlockDetector.m create mode 100644 BlackCamera/SCBlackCameraViewDetector.h create mode 100644 BlackCamera/SCBlackCameraViewDetector.m create mode 100644 BlackCamera/SCCaptureSessionFixer.h create mode 100644 BlackCamera/SCCaptureSessionFixer.m create mode 100644 ContextAwareTaskManagement/OWNERS create mode 100644 ContextAwareTaskManagement/Requests/SCContextAwareSnapCreationThrottleRequest.h create mode 100644 ContextAwareTaskManagement/Requests/SCContextAwareSnapCreationThrottleRequest.m create mode 100644 ContextAwareTaskManagement/Triggers/SCSnapCreationTriggers.h create mode 100644 ContextAwareTaskManagement/Triggers/SCSnapCreationTriggers.m create mode 100644 Features/Core/SCFeature.h create mode 100644 Features/Core/SCFeatureContainerView.h create mode 100644 Features/Core/SCFeatureCoordinator.h create mode 100644 Features/Core/SCFeatureCoordinator.m create mode 100644 Features/Core/SCFeatureProvider.h create mode 100644 Features/Flash/SCFeatureFlash.h create mode 100644 Features/Flash/SCFeatureFlashImpl.h create mode 100644 Features/Flash/SCFeatureFlashImpl.m create mode 100644 Features/Flash/SCFlashButton.h create mode 100644 Features/Flash/SCFlashButton.m create mode 100644 Features/HandsFree/SCFeatureHandsFree.h create mode 100644 Features/ImageCapture/SCFeatureImageCapture.h create mode 100644 Features/ImageCapture/SCFeatureImageCaptureImpl.h create mode 100644 Features/ImageCapture/SCFeatureImageCaptureImpl.m create mode 100644 Features/NightMode/SCFeatureNightMode.h create mode 100644 Features/NightMode/SCNightModeButton.h create mode 100644 Features/NightMode/SCNightModeButton.m create mode 100644 Features/Scanning/SCFeatureScanning.h create mode 100644 Features/Shazam/SCFeatureShazam.h create mode 100644 Features/SnapKit/SCFeatureSnapKit.h create mode 100644 Features/TapToFocus/SCFeatureTapToFocusAndExposure.h create mode 100644 Features/TapToFocus/SCFeatureTapToFocusAndExposureImpl.h create mode 100644 Features/TapToFocus/SCFeatureTapToFocusAndExposureImpl.m create mode 100644 Features/TapToFocus/SCTapAnimationView.h create mode 100644 Features/TapToFocus/SCTapAnimationView.m create mode 100644 Features/ToggleCamera/SCFeatureToggleCamera.h create mode 100644 Features/Zooming/SCFeatureZooming.h create mode 100644 Lens/SCManagedCapturerARImageCaptureProvider.h create mode 100644 Lens/SCManagedCapturerGLViewManagerAPI.h create mode 100644 Lens/SCManagedCapturerLSAComponentTrackerAPI.h create mode 100644 Lens/SCManagedCapturerLensAPI.h create mode 100644 Lens/SCManagedCapturerLensAPIProvider.h diff --git a/BlackCamera/SCBlackCameraDetector.h b/BlackCamera/SCBlackCameraDetector.h new file mode 100644 index 0000000..bc64092 --- /dev/null +++ b/BlackCamera/SCBlackCameraDetector.h @@ -0,0 +1,56 @@ +// +// SCBlackCameraDetector.h +// Snapchat +// +// Created by Derek Wang on 24/01/2018. +// + +#import "SCBlackCameraReporter.h" + +#import +#import + +@class SCBlackCameraNoOutputDetector; + +@interface SCBlackCameraDetector : NSObject + +@property (nonatomic, strong) SCBlackCameraNoOutputDetector *blackCameraNoOutputDetector; + +SC_INIT_AND_NEW_UNAVAILABLE +- (instancetype)initWithTicketCreator:(id)ticketCreator; + +// CameraView visible/invisible +- (void)onCameraViewVisible:(BOOL)visible; + +- (void)onCameraViewVisibleWithTouch:(UIGestureRecognizer *)touch; + +// Call this when [AVCaptureSession startRunning] is called +- (void)sessionWillCallStartRunning; +- (void)sessionDidCallStartRunning; + +// Call this when [AVCaptureSession stopRunning] is called +- (void)sessionWillCallStopRunning; +- (void)sessionDidCallStopRunning; + +// Call this when [AVCaptureSession commitConfiguration] is called +- (void)sessionWillCommitConfiguration; +- (void)sessionDidCommitConfiguration; + +- (void)sessionDidChangeIsRunning:(BOOL)running; + +// For CapturePreview visibility detector +- (void)capturePreviewDidBecomeVisible:(BOOL)visible; + +/** + Mark the start of creating new session + When we fix black camera by creating new session, some detector may report black camera because we called + [AVCaptureSession stopRunning] on old AVCaptureSession, so we need to tell the detector the session is recreating, so + it is fine to call [AVCaptureSession stopRunning] on old AVCaptureSession. + */ +- (void)sessionWillRecreate; +/** + Mark the end of creating new session + */ +- (void)sessionDidRecreate; + +@end diff --git a/BlackCamera/SCBlackCameraDetector.m b/BlackCamera/SCBlackCameraDetector.m new file mode 100644 index 0000000..4911000 --- /dev/null +++ b/BlackCamera/SCBlackCameraDetector.m @@ -0,0 +1,134 @@ +// +// SCBlackCameraDetector.m +// Snapchat +// +// Created by Derek Wang on 24/01/2018. +// + +#import "SCBlackCameraDetector.h" + +#import "SCBlackCameraNoOutputDetector.h" +#import "SCBlackCameraPreviewDetector.h" +#import "SCBlackCameraRunningDetector.h" +#import "SCBlackCameraSessionBlockDetector.h" +#import "SCBlackCameraViewDetector.h" + +#import + +#if !TARGET_IPHONE_SIMULATOR +static char *const kSCBlackCameraDetectorQueueLabel = "com.snapchat.black-camera-detector"; +#endif +@interface SCBlackCameraDetector () { + BOOL _sessionIsRunning; + BOOL _cameraIsVisible; + BOOL _previewIsVisible; +} +@property (nonatomic, strong) SCQueuePerformer *queuePerformer; +@property (nonatomic, strong) SCBlackCameraViewDetector *cameraViewDetector; +@property (nonatomic, strong) SCBlackCameraRunningDetector *sessionRunningDetector; +@property (nonatomic, strong) SCBlackCameraPreviewDetector *previewDetector; +@property (nonatomic, strong) SCBlackCameraSessionBlockDetector *sessionBlockDetector; + +@end + +@implementation SCBlackCameraDetector + +- (instancetype)initWithTicketCreator:(id)ticketCreator +{ +#if !TARGET_IPHONE_SIMULATOR + + self = [super init]; + if (self) { + _queuePerformer = [[SCQueuePerformer alloc] initWithLabel:kSCBlackCameraDetectorQueueLabel + qualityOfService:QOS_CLASS_BACKGROUND + queueType:DISPATCH_QUEUE_SERIAL + context:SCQueuePerformerContextCamera]; + + SCBlackCameraReporter *reporter = [[SCBlackCameraReporter alloc] initWithTicketCreator:ticketCreator]; + _cameraViewDetector = [[SCBlackCameraViewDetector alloc] initWithPerformer:_queuePerformer reporter:reporter]; + _sessionRunningDetector = + [[SCBlackCameraRunningDetector alloc] initWithPerformer:_queuePerformer reporter:reporter]; + _previewDetector = [[SCBlackCameraPreviewDetector alloc] initWithPerformer:_queuePerformer reporter:reporter]; + _sessionBlockDetector = [[SCBlackCameraSessionBlockDetector alloc] initWithReporter:reporter]; + _blackCameraNoOutputDetector = [[SCBlackCameraNoOutputDetector alloc] initWithReporter:reporter]; + } + return self; +#else + return nil; +#endif +} + +#pragma mark - Camera view visibility detector +- (void)onCameraViewVisible:(BOOL)visible +{ + SC_GUARD_ELSE_RETURN(visible != _cameraIsVisible); + _cameraIsVisible = visible; + [_cameraViewDetector onCameraViewVisible:visible]; +} + +- (void)onCameraViewVisibleWithTouch:(UIGestureRecognizer *)gesture +{ + [_cameraViewDetector onCameraViewVisibleWithTouch:gesture]; +} + +#pragma mark - Track [AVCaptureSession startRunning] call +- (void)sessionWillCallStartRunning +{ + [_cameraViewDetector sessionWillCallStartRunning]; + [_sessionBlockDetector sessionWillCallStartRunning]; +} + +- (void)sessionDidCallStartRunning +{ + [_sessionRunningDetector sessionDidCallStartRunning]; + [_sessionBlockDetector sessionDidCallStartRunning]; +} + +#pragma mark - Track [AVCaptureSession stopRunning] call +- (void)sessionWillCallStopRunning +{ + [_cameraViewDetector sessionWillCallStopRunning]; + [_sessionRunningDetector sessionWillCallStopRunning]; +} + +- (void)sessionDidCallStopRunning +{ +} + +- (void)sessionDidChangeIsRunning:(BOOL)running +{ + SC_GUARD_ELSE_RETURN(running != _sessionIsRunning); + _sessionIsRunning = running; + [_sessionRunningDetector sessionDidChangeIsRunning:running]; + [_previewDetector sessionDidChangeIsRunning:running]; +} + +#pragma mark - Capture preview visibility detector +- (void)capturePreviewDidBecomeVisible:(BOOL)visible +{ + SC_GUARD_ELSE_RETURN(visible != _previewIsVisible); + _previewIsVisible = visible; + [_previewDetector capturePreviewDidBecomeVisible:visible]; +} + +#pragma mark - AVCaptureSession block detector +- (void)sessionWillCommitConfiguration +{ + [_sessionBlockDetector sessionWillCommitConfiguration]; +} + +- (void)sessionDidCommitConfiguration +{ + [_sessionBlockDetector sessionDidCommitConfiguration]; +} + +- (void)sessionWillRecreate +{ + [_cameraViewDetector sessionWillRecreate]; +} + +- (void)sessionDidRecreate +{ + [_cameraViewDetector sessionDidRecreate]; +} +@end diff --git a/BlackCamera/SCBlackCameraNoOutputDetector.h b/BlackCamera/SCBlackCameraNoOutputDetector.h new file mode 100644 index 0000000..d9de8c7 --- /dev/null +++ b/BlackCamera/SCBlackCameraNoOutputDetector.h @@ -0,0 +1,26 @@ +// +// SCBlackCameraNoOutputDetector.h +// Snapchat +// +// Created by Derek Wang on 05/12/2017. +// + +#import "SCManagedCapturerListener.h" + +#import + +#import + +@class SCBlackCameraNoOutputDetector, SCBlackCameraReporter; +@protocol SCManiphestTicketCreator; + +@protocol SCBlackCameraDetectorDelegate +- (void)detector:(SCBlackCameraNoOutputDetector *)detector didDetectBlackCamera:(id)capture; +@end + +@interface SCBlackCameraNoOutputDetector : NSObject + +@property (nonatomic, weak) id delegate; +- (instancetype)initWithReporter:(SCBlackCameraReporter *)reporter; + +@end diff --git a/BlackCamera/SCBlackCameraNoOutputDetector.m b/BlackCamera/SCBlackCameraNoOutputDetector.m new file mode 100644 index 0000000..2b11b01 --- /dev/null +++ b/BlackCamera/SCBlackCameraNoOutputDetector.m @@ -0,0 +1,137 @@ +// +// SCBlackCameraDetectorNoOutput.m +// Snapchat +// +// Created by Derek Wang on 05/12/2017. +// +// This detector is used to detect the case that session is running, but there is no sample buffer output + +#import "SCBlackCameraNoOutputDetector.h" + +#import "SCBlackCameraReporter.h" + +#import +#import +#import +#import +#import +#import +#import + +static CGFloat const kShortCheckingDelay = 0.5f; +static CGFloat const kLongCheckingDelay = 3.0f; +static char *const kSCBlackCameraDetectorQueueLabel = "com.snapchat.black-camera-detector"; + +@interface SCBlackCameraNoOutputDetector () { + BOOL _sampleBufferReceived; + BOOL _blackCameraDetected; + // Whether we receive first frame after we detected black camera, that's maybe because the checking delay is too + // short, and we will switch to kLongCheckingDelay next time we do the checking + BOOL _blackCameraRecovered; + // Whether checking is scheduled, to avoid duplicated checking + BOOL _checkingScheduled; + // Whether AVCaptureSession is stopped, if stopped, we don't need to check black camera any more + // It is set on main thread, read on background queue + BOOL _sessionStoppedRunning; +} +@property (nonatomic) SCQueuePerformer *queuePerformer; +@property (nonatomic) SCBlackCameraReporter *reporter; +@end + +@implementation SCBlackCameraNoOutputDetector + +- (instancetype)initWithReporter:(SCBlackCameraReporter *)reporter +{ + self = [super init]; + if (self) { + _queuePerformer = [[SCQueuePerformer alloc] initWithLabel:kSCBlackCameraDetectorQueueLabel + qualityOfService:QOS_CLASS_BACKGROUND + queueType:DISPATCH_QUEUE_SERIAL + context:SCQueuePerformerContextCamera]; + _reporter = reporter; + } + return self; +} + +- (void)managedVideoDataSource:(id)managedVideoDataSource + didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer + devicePosition:(SCManagedCaptureDevicePosition)devicePosition +{ + // The block is very light-weight + [self.queuePerformer perform:^{ + if (_blackCameraDetected) { + // Detected a black camera case + _blackCameraDetected = NO; + _blackCameraRecovered = YES; + SCLogCoreCameraInfo(@"[BlackCamera] Black camera recovered"); + if (SCExperimentWithBlackCameraReporting()) { + [[SCLogger sharedInstance] logUnsampledEvent:KSCCameraBlackCamera + parameters:@{ + @"type" : @"RECOVERED" + } + secretParameters:nil + metrics:nil]; + } + } + + // Received buffer! + _sampleBufferReceived = YES; + }]; +} + +- (void)managedCapturer:(id)managedCapturer didStartRunning:(SCManagedCapturerState *)state +{ + SCAssertMainThread(); + if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) { + SCLogCoreCameraInfo(@"[BlackCamera] In background, skip checking"); + return; + } + _sessionStoppedRunning = NO; + [self.queuePerformer perform:^{ + SCTraceODPCompatibleStart(2); + if (_checkingScheduled) { + SCLogCoreCameraInfo(@"[BlackCamera] Checking is scheduled, skip"); + return; + } + if (_sessionStoppedRunning) { + SCLogCoreCameraInfo(@"[BlackCamera] AVCaptureSession stopped, should not check"); + return; + } + _sampleBufferReceived = NO; + if (_blackCameraRecovered) { + SCLogCoreCameraInfo(@"[BlackCamera] Last black camera recovered, let's wait longer to check this time"); + } + SCLogCoreCameraInfo(@"[BlackCamera] Schedule black camera checking"); + [self.queuePerformer perform:^{ + SCTraceODPCompatibleStart(2); + if (!_sessionStoppedRunning) { + if (!_sampleBufferReceived) { + _blackCameraDetected = YES; + [_reporter reportBlackCameraWithCause:SCBlackCameraNoOutputData]; + [self.delegate detector:self didDetectBlackCamera:managedCapturer]; + } else { + SCLogCoreCameraInfo(@"[BlackCamera] No black camera"); + _blackCameraDetected = NO; + } + } else { + SCLogCoreCameraInfo(@"[BlackCamera] AVCaptureSession stopped"); + _blackCameraDetected = NO; + } + _blackCameraRecovered = NO; + _checkingScheduled = NO; + } + after:_blackCameraRecovered ? kLongCheckingDelay : kShortCheckingDelay]; + _checkingScheduled = YES; + }]; +} +- (void)managedCapturer:(id)managedCapturer didStopRunning:(SCManagedCapturerState *)state +{ + SCAssertMainThread(); + _sessionStoppedRunning = YES; + [self.queuePerformer perform:^{ + SCTraceODPCompatibleStart(2); + _sampleBufferReceived = NO; + }]; +} + +@end diff --git a/BlackCamera/SCBlackCameraPreviewDetector.h b/BlackCamera/SCBlackCameraPreviewDetector.h new file mode 100644 index 0000000..f628320 --- /dev/null +++ b/BlackCamera/SCBlackCameraPreviewDetector.h @@ -0,0 +1,20 @@ +// +// SCBlackCameraPreviewDetector.h +// Snapchat +// +// Created by Derek Wang on 25/01/2018. +// + +#import + +@class SCQueuePerformer, SCBlackCameraReporter; +@protocol SCManiphestTicketCreator; + +@interface SCBlackCameraPreviewDetector : NSObject + +- (instancetype)initWithPerformer:(SCQueuePerformer *)performer reporter:(SCBlackCameraReporter *)reporter; + +- (void)sessionDidChangeIsRunning:(BOOL)running; +- (void)capturePreviewDidBecomeVisible:(BOOL)visible; + +@end diff --git a/BlackCamera/SCBlackCameraPreviewDetector.m b/BlackCamera/SCBlackCameraPreviewDetector.m new file mode 100644 index 0000000..5d8785e --- /dev/null +++ b/BlackCamera/SCBlackCameraPreviewDetector.m @@ -0,0 +1,92 @@ +// +// SCBlackCameraPreviewDetector.m +// Snapchat +// +// Created by Derek Wang on 25/01/2018. +// + +#import "SCBlackCameraPreviewDetector.h" + +#import "SCBlackCameraReporter.h" +#import "SCMetalUtils.h" + +#import +#import +#import +#import + +// Check whether preview is visible when AVCaptureSession is running +static CGFloat const kSCBlackCameraCheckingDelay = 0.5; + +@interface SCBlackCameraPreviewDetector () { + BOOL _previewVisible; + dispatch_block_t _checkingBlock; +} +@property (nonatomic) SCQueuePerformer *queuePerformer; +@property (nonatomic) SCBlackCameraReporter *reporter; + +@end + +@implementation SCBlackCameraPreviewDetector + +- (instancetype)initWithPerformer:(SCQueuePerformer *)performer reporter:(SCBlackCameraReporter *)reporter +{ + self = [super init]; + if (self) { + _queuePerformer = performer; + _reporter = reporter; + } + return self; +} + +- (void)capturePreviewDidBecomeVisible:(BOOL)visible +{ + [_queuePerformer perform:^{ + _previewVisible = visible; + }]; +} + +- (void)sessionDidChangeIsRunning:(BOOL)running +{ + if (running) { + [self _scheduleCheck]; + } else { + [_queuePerformer perform:^{ + if (_checkingBlock) { + dispatch_block_cancel(_checkingBlock); + _checkingBlock = nil; + } + }]; + } +} + +- (void)_scheduleCheck +{ + [_queuePerformer perform:^{ + @weakify(self); + _checkingBlock = dispatch_block_create(0, ^{ + @strongify(self); + SC_GUARD_ELSE_RETURN(self); + self->_checkingBlock = nil; + [self _checkPreviewState]; + }); + [_queuePerformer perform:_checkingBlock after:kSCBlackCameraCheckingDelay]; + }]; +} + +- (void)_checkPreviewState +{ + if (!_previewVisible) { + runOnMainThreadAsynchronously(^{ + // Make sure the app is in foreground + SC_GUARD_ELSE_RETURN([UIApplication sharedApplication].applicationState == UIApplicationStateActive); + + SCBlackCameraCause cause = + SCDeviceSupportsMetal() ? SCBlackCameraRenderingPaused : SCBlackCameraPreviewIsHidden; + [_reporter reportBlackCameraWithCause:cause]; + [_reporter fileShakeTicketWithCause:cause]; + }); + } +} + +@end diff --git a/BlackCamera/SCBlackCameraReporter.h b/BlackCamera/SCBlackCameraReporter.h new file mode 100644 index 0000000..f7c36dc --- /dev/null +++ b/BlackCamera/SCBlackCameraReporter.h @@ -0,0 +1,35 @@ +// +// SCBlackCameraReporter.h +// Snapchat +// +// Created by Derek Wang on 09/01/2018. +// + +#import + +#import + +typedef NS_ENUM(NSInteger, SCBlackCameraCause) { + SCBlackCameraStartRunningNotCalled, // 1. View is visible, but session startRunning is not called + SCBlackCameraSessionNotRunning, // 2. Session startRunning is called, but isRunning is still false + SCBlackCameraRenderingPaused, // 3.1 View is visible, but capture preview rendering is paused + SCBlackCameraPreviewIsHidden, // 3.2 For non-metal devices, capture preview is hidden + SCBlackCameraSessionStartRunningBlocked, // 4.1 AVCaptureSession is blocked at startRunning + SCBlackCameraSessionConfigurationBlocked, // 4.2 AVCaptureSession is blocked at commitConfiguration + + SCBlackCameraNoOutputData, // 5. Session is running, but no data output +}; + +@protocol SCManiphestTicketCreator; + +@interface SCBlackCameraReporter : NSObject + +SC_INIT_AND_NEW_UNAVAILABLE +- (instancetype)initWithTicketCreator:(id)ticketCreator; + +- (NSString *)causeNameFor:(SCBlackCameraCause)cause; + +- (void)reportBlackCameraWithCause:(SCBlackCameraCause)cause; +- (void)fileShakeTicketWithCause:(SCBlackCameraCause)cause; + +@end diff --git a/BlackCamera/SCBlackCameraReporter.m b/BlackCamera/SCBlackCameraReporter.m new file mode 100644 index 0000000..e997818 --- /dev/null +++ b/BlackCamera/SCBlackCameraReporter.m @@ -0,0 +1,86 @@ +// +// SCBlackCameraReporter.m +// Snapchat +// +// Created by Derek Wang on 09/01/2018. +// + +#import "SCBlackCameraReporter.h" + +#import "SCManiphestTicketCreator.h" + +#import +#import +#import +#import +#import +#import + +@interface SCBlackCameraReporter () + +@property (nonatomic) id ticketCreator; + +@end + +@implementation SCBlackCameraReporter + +- (instancetype)initWithTicketCreator:(id)ticketCreator +{ + if (self = [super init]) { + _ticketCreator = ticketCreator; + } + return self; +} + +- (NSString *)causeNameFor:(SCBlackCameraCause)cause +{ + switch (cause) { + case SCBlackCameraStartRunningNotCalled: + return @"StartRunningNotCalled"; + case SCBlackCameraSessionNotRunning: + return @"SessionNotRunning"; + case SCBlackCameraRenderingPaused: + return @"RenderingPause"; + case SCBlackCameraPreviewIsHidden: + return @"PreviewIsHidden"; + case SCBlackCameraSessionStartRunningBlocked: + return @"SessionStartRunningBlocked"; + case SCBlackCameraSessionConfigurationBlocked: + return @"SessionConfigurationBlocked"; + case SCBlackCameraNoOutputData: + return @"NoOutputData"; + default: + SCAssert(NO, @"illegate cause"); + break; + } + return nil; +} + +- (void)reportBlackCameraWithCause:(SCBlackCameraCause)cause +{ + NSString *causeStr = [self causeNameFor:cause]; + SCLogCoreCameraError(@"[BlackCamera] Detected black camera, cause: %@", causeStr); + + NSDictionary *parameters = @{ @"type" : @"DETECTED", @"cause" : causeStr }; + + [_ticketCreator createAndFileBetaReport:JSONStringSerializeObjectForLogging(parameters)]; + + if (SCExperimentWithBlackCameraReporting()) { + [[SCLogger sharedInstance] logUnsampledEvent:KSCCameraBlackCamera + parameters:parameters + secretParameters:nil + metrics:nil]; + } +} + +- (void)fileShakeTicketWithCause:(SCBlackCameraCause)cause +{ + if (SCExperimentWithBlackCameraExceptionLogging()) { + // Log exception with auto S2R + NSString *errMsg = + [NSString sc_stringWithFormat:@"[BlackCamera] Detected black camera, cause: %@", [self causeNameFor:cause]]; + [_ticketCreator createAndFile:nil creationTime:0 description:errMsg email:nil project:@"Camera" subproject:nil]; + } +} + +@end diff --git a/BlackCamera/SCBlackCameraRunningDetector.h b/BlackCamera/SCBlackCameraRunningDetector.h new file mode 100644 index 0000000..462cabb --- /dev/null +++ b/BlackCamera/SCBlackCameraRunningDetector.h @@ -0,0 +1,27 @@ +// +// SCBlackCameraRunningDetector.h +// Snapchat +// +// Created by Derek Wang on 30/01/2018. +// + +#import + +#import + +@class SCQueuePerformer, SCBlackCameraReporter; +@protocol SCManiphestTicketCreator; + +@interface SCBlackCameraRunningDetector : NSObject + +SC_INIT_AND_NEW_UNAVAILABLE +- (instancetype)initWithPerformer:(SCQueuePerformer *)performer reporter:(SCBlackCameraReporter *)reporter; + +// When session isRunning changed +- (void)sessionDidChangeIsRunning:(BOOL)running; +// Call this after [AVCaptureSession startRunning] is called +- (void)sessionDidCallStartRunning; +// Call this before [AVCaptureSession stopRunning] is called +- (void)sessionWillCallStopRunning; + +@end diff --git a/BlackCamera/SCBlackCameraRunningDetector.m b/BlackCamera/SCBlackCameraRunningDetector.m new file mode 100644 index 0000000..a690d5b --- /dev/null +++ b/BlackCamera/SCBlackCameraRunningDetector.m @@ -0,0 +1,84 @@ +// +// SCBlackCameraRunningDetector.m +// Snapchat +// +// Created by Derek Wang on 30/01/2018. +// + +#import "SCBlackCameraRunningDetector.h" + +#import "SCBlackCameraReporter.h" + +#import +#import +#import +#import + +// Check whether we called AVCaptureSession isRunning within this period +static CGFloat const kSCBlackCameraCheckingDelay = 5; + +@interface SCBlackCameraRunningDetector () { + BOOL _isSessionRunning; + dispatch_block_t _checkSessionBlock; +} +@property (nonatomic) SCQueuePerformer *queuePerformer; +@property (nonatomic) SCBlackCameraReporter *reporter; +@end + +@implementation SCBlackCameraRunningDetector + +- (instancetype)initWithPerformer:(SCQueuePerformer *)performer reporter:(SCBlackCameraReporter *)reporter +{ + self = [super init]; + if (self) { + _queuePerformer = performer; + _reporter = reporter; + } + return self; +} + +- (void)sessionDidChangeIsRunning:(BOOL)running +{ + [_queuePerformer perform:^{ + _isSessionRunning = running; + }]; +} + +- (void)sessionDidCallStartRunning +{ + [self _scheduleCheck]; +} + +- (void)sessionWillCallStopRunning +{ + [_queuePerformer perform:^{ + if (_checkSessionBlock) { + dispatch_block_cancel(_checkSessionBlock); + _checkSessionBlock = nil; + } + }]; +} + +- (void)_scheduleCheck +{ + [_queuePerformer perform:^{ + @weakify(self); + _checkSessionBlock = dispatch_block_create(0, ^{ + @strongify(self); + SC_GUARD_ELSE_RETURN(self); + self->_checkSessionBlock = nil; + [self _checkSessionState]; + }); + + [_queuePerformer perform:_checkSessionBlock after:kSCBlackCameraCheckingDelay]; + }]; +} + +- (void)_checkSessionState +{ + if (!_isSessionRunning) { + [_reporter reportBlackCameraWithCause:SCBlackCameraSessionNotRunning]; + } +} + +@end diff --git a/BlackCamera/SCBlackCameraSessionBlockDetector.h b/BlackCamera/SCBlackCameraSessionBlockDetector.h new file mode 100644 index 0000000..3da00ca --- /dev/null +++ b/BlackCamera/SCBlackCameraSessionBlockDetector.h @@ -0,0 +1,23 @@ +// +// SCBlackCameraSessionBlockDetector.h +// Snapchat +// +// Created by Derek Wang on 25/01/2018. +// + +#import "SCBlackCameraReporter.h" + +#import + +@interface SCBlackCameraSessionBlockDetector : NSObject + +SC_INIT_AND_NEW_UNAVAILABLE +- (instancetype)initWithReporter:(SCBlackCameraReporter *)reporter; + +- (void)sessionWillCallStartRunning; +- (void)sessionDidCallStartRunning; + +- (void)sessionWillCommitConfiguration; +- (void)sessionDidCommitConfiguration; + +@end diff --git a/BlackCamera/SCBlackCameraSessionBlockDetector.m b/BlackCamera/SCBlackCameraSessionBlockDetector.m new file mode 100644 index 0000000..962f086 --- /dev/null +++ b/BlackCamera/SCBlackCameraSessionBlockDetector.m @@ -0,0 +1,82 @@ +// +// SCBlackCameraSessionBlockDetector.m +// Snapchat +// +// Created by Derek Wang on 25/01/2018. +// + +#import "SCBlackCameraSessionBlockDetector.h" + +#import "SCBlackCameraReporter.h" + +#import +#import + +@import CoreGraphics; + +// Longer than 5 seconds is considerred as black camera +static CGFloat const kSCBlackCameraBlockingThreshold = 5; +// Will report if session blocks longer than 1 second +static CGFloat const kSCSessionBlockingLogThreshold = 1; + +@interface SCBlackCameraSessionBlockDetector () { + NSTimeInterval _startTime; +} +@property (nonatomic) SCBlackCameraReporter *reporter; + +@end + +@implementation SCBlackCameraSessionBlockDetector + +- (instancetype)initWithReporter:(SCBlackCameraReporter *)reporter +{ + if (self = [super init]) { + _reporter = reporter; + } + return self; +} + +- (void)sessionWillCallStartRunning +{ + _startTime = [NSDate timeIntervalSinceReferenceDate]; +} + +- (void)sessionDidCallStartRunning +{ + [self _reportBlackCameraIfNeededWithCause:SCBlackCameraSessionStartRunningBlocked]; + [self _reportBlockingIfNeededWithCause:SCBlackCameraSessionStartRunningBlocked]; +} + +- (void)sessionWillCommitConfiguration +{ + _startTime = [NSDate timeIntervalSinceReferenceDate]; +} + +- (void)sessionDidCommitConfiguration +{ + [self _reportBlackCameraIfNeededWithCause:SCBlackCameraSessionConfigurationBlocked]; + [self _reportBlockingIfNeededWithCause:SCBlackCameraSessionConfigurationBlocked]; +} + +- (void)_reportBlockingIfNeededWithCause:(SCBlackCameraCause)cause +{ + NSTimeInterval duration = [NSDate timeIntervalSinceReferenceDate] - _startTime; + if (duration >= kSCSessionBlockingLogThreshold) { + NSString *causeStr = [_reporter causeNameFor:cause]; + [[SCLogger sharedInstance] logEvent:KSCCameraCaptureSessionBlocked + parameters:@{ + @"cause" : causeStr, + @"duration" : @(duration) + }]; + } +} + +- (void)_reportBlackCameraIfNeededWithCause:(SCBlackCameraCause)cause +{ + NSTimeInterval endTime = [NSDate timeIntervalSinceReferenceDate]; + if (endTime - _startTime >= kSCBlackCameraBlockingThreshold) { + [_reporter reportBlackCameraWithCause:cause]; + } +} + +@end diff --git a/BlackCamera/SCBlackCameraViewDetector.h b/BlackCamera/SCBlackCameraViewDetector.h new file mode 100644 index 0000000..912d514 --- /dev/null +++ b/BlackCamera/SCBlackCameraViewDetector.h @@ -0,0 +1,31 @@ +// +// SCBlackCameraDetectorCameraView.h +// Snapchat +// +// Created by Derek Wang on 24/01/2018. +// + +#import +#import + +@class SCQueuePerformer, SCBlackCameraReporter; +@protocol SCManiphestTicketCreator; + +@interface SCBlackCameraViewDetector : NSObject + +- (instancetype)initWithPerformer:(SCQueuePerformer *)performer reporter:(SCBlackCameraReporter *)reporter; + +// CameraView visible/invisible +- (void)onCameraViewVisible:(BOOL)visible; + +- (void)onCameraViewVisibleWithTouch:(UIGestureRecognizer *)gesture; + +// Call this when [AVCaptureSession startRunning] is called +- (void)sessionWillCallStartRunning; +// Call this when [AVCaptureSession stopRunning] is called +- (void)sessionWillCallStopRunning; + +- (void)sessionWillRecreate; +- (void)sessionDidRecreate; + +@end diff --git a/BlackCamera/SCBlackCameraViewDetector.m b/BlackCamera/SCBlackCameraViewDetector.m new file mode 100644 index 0000000..3815715 --- /dev/null +++ b/BlackCamera/SCBlackCameraViewDetector.m @@ -0,0 +1,136 @@ +// +// SCBlackCameraDetectorCameraView.m +// Snapchat +// +// Created by Derek Wang on 24/01/2018. +// + +#import "SCBlackCameraViewDetector.h" + +#import "SCBlackCameraReporter.h" +#import "SCCaptureDeviceAuthorization.h" + +#import +#import +#import +#import +#import + +// Check whether we called [AVCaptureSession startRunning] within this period +static CGFloat const kSCBlackCameraCheckingDelay = 0.5; + +@interface SCBlackCameraViewDetector () { + BOOL _startRunningCalled; + BOOL _sessionIsRecreating; + dispatch_block_t _checkSessionBlock; +} +@property (nonatomic) SCQueuePerformer *queuePerformer; +@property (nonatomic) SCBlackCameraReporter *reporter; +@property (nonatomic, weak) UIGestureRecognizer *cameraViewGesture; +@end + +@implementation SCBlackCameraViewDetector + +- (instancetype)initWithPerformer:(SCQueuePerformer *)performer reporter:(SCBlackCameraReporter *)reporter +{ + self = [super init]; + if (self) { + _queuePerformer = performer; + _reporter = reporter; + } + return self; +} + +#pragma mark - Camera view visibility change trigger +- (void)onCameraViewVisible:(BOOL)visible +{ + SCTraceODPCompatibleStart(2); + SCLogCoreCameraInfo(@"[BlackCamera] onCameraViewVisible: %d", visible); + BOOL firstTimeAccess = [SCCaptureDeviceAuthorization notDeterminedForVideoCapture]; + if (firstTimeAccess) { + // We don't want to check black camera for firstTimeAccess + return; + } + // Visible and application is active + if (visible && [UIApplication sharedApplication].applicationState == UIApplicationStateActive) { + // Since this method is usually called before the view is actually visible, leave some margin to check + [self _scheduleCheckDelayed:YES]; + } else { + [_queuePerformer perform:^{ + if (_checkSessionBlock) { + dispatch_block_cancel(_checkSessionBlock); + _checkSessionBlock = nil; + } + }]; + } +} + +// Call this when [AVCaptureSession startRunning] is called +- (void)sessionWillCallStartRunning +{ + [_queuePerformer perform:^{ + _startRunningCalled = YES; + }]; +} + +- (void)sessionWillCallStopRunning +{ + [_queuePerformer perform:^{ + _startRunningCalled = NO; + }]; +} + +- (void)_scheduleCheckDelayed:(BOOL)delay +{ + [_queuePerformer perform:^{ + SC_GUARD_ELSE_RETURN(!_checkSessionBlock); + @weakify(self); + _checkSessionBlock = dispatch_block_create(0, ^{ + @strongify(self); + SC_GUARD_ELSE_RETURN(self); + self->_checkSessionBlock = nil; + [self _checkSessionState]; + }); + + if (delay) { + [_queuePerformer perform:_checkSessionBlock after:kSCBlackCameraCheckingDelay]; + } else { + [_queuePerformer perform:_checkSessionBlock]; + } + }]; +} + +- (void)_checkSessionState +{ + SCLogCoreCameraInfo(@"[BlackCamera] checkSessionState startRunning: %d, sessionIsRecreating: %d", + _startRunningCalled, _sessionIsRecreating); + if (!_startRunningCalled && !_sessionIsRecreating) { + [_reporter reportBlackCameraWithCause:SCBlackCameraStartRunningNotCalled]; + [_reporter fileShakeTicketWithCause:SCBlackCameraStartRunningNotCalled]; + } +} + +- (void)sessionWillRecreate +{ + [_queuePerformer perform:^{ + _sessionIsRecreating = YES; + }]; +} + +- (void)sessionDidRecreate +{ + [_queuePerformer perform:^{ + _sessionIsRecreating = NO; + }]; +} + +- (void)onCameraViewVisibleWithTouch:(UIGestureRecognizer *)gesture +{ + if (gesture != _cameraViewGesture) { + // Skip repeating gesture + self.cameraViewGesture = gesture; + [self _scheduleCheckDelayed:NO]; + } +} + +@end diff --git a/BlackCamera/SCCaptureSessionFixer.h b/BlackCamera/SCCaptureSessionFixer.h new file mode 100644 index 0000000..450a482 --- /dev/null +++ b/BlackCamera/SCCaptureSessionFixer.h @@ -0,0 +1,14 @@ +// +// SCCaptureSessionFixer.h +// Snapchat +// +// Created by Derek Wang on 05/12/2017. +// + +#import "SCBlackCameraNoOutputDetector.h" + +#import + +@interface SCCaptureSessionFixer : NSObject + +@end diff --git a/BlackCamera/SCCaptureSessionFixer.m b/BlackCamera/SCCaptureSessionFixer.m new file mode 100644 index 0000000..6c87362 --- /dev/null +++ b/BlackCamera/SCCaptureSessionFixer.m @@ -0,0 +1,21 @@ +// +// SCCaptureSessionFixer.m +// Snapchat +// +// Created by Derek Wang on 05/12/2017. +// + +#import "SCCaptureSessionFixer.h" + +#import "SCCameraTweaks.h" + +@implementation SCCaptureSessionFixer + +- (void)detector:(SCBlackCameraNoOutputDetector *)detector didDetectBlackCamera:(id)capture +{ + if (SCCameraTweaksBlackCameraRecoveryEnabled()) { + [capture recreateAVCaptureSession]; + } +} + +@end diff --git a/ContextAwareTaskManagement/OWNERS b/ContextAwareTaskManagement/OWNERS new file mode 100644 index 0000000..fb5cee1 --- /dev/null +++ b/ContextAwareTaskManagement/OWNERS @@ -0,0 +1,13 @@ +--- !OWNERS + +version: 2 + +default: + jira_project: CCAM + owners: + num_required_reviewers: 1 + teams: + - Snapchat/core-camera-ios + users: + - cjiang + - ljia diff --git a/ContextAwareTaskManagement/Requests/SCContextAwareSnapCreationThrottleRequest.h b/ContextAwareTaskManagement/Requests/SCContextAwareSnapCreationThrottleRequest.h new file mode 100644 index 0000000..f1c16e3 --- /dev/null +++ b/ContextAwareTaskManagement/Requests/SCContextAwareSnapCreationThrottleRequest.h @@ -0,0 +1,16 @@ +// +// SCContextAwareSnapCreationThrottleRequest.h +// SCCamera +// +// Created by Cheng Jiang on 4/24/18. +// + +#import + +#import + +@interface SCContextAwareSnapCreationThrottleRequest : NSObject + +- (instancetype)init; + +@end diff --git a/ContextAwareTaskManagement/Requests/SCContextAwareSnapCreationThrottleRequest.m b/ContextAwareTaskManagement/Requests/SCContextAwareSnapCreationThrottleRequest.m new file mode 100644 index 0000000..a2a53ef --- /dev/null +++ b/ContextAwareTaskManagement/Requests/SCContextAwareSnapCreationThrottleRequest.m @@ -0,0 +1,70 @@ +// +// SCContextAwareSnapCreationThrottleRequest.m +// SCCamera +// +// Created by Cheng Jiang on 4/24/18. +// + +#import "SCContextAwareSnapCreationThrottleRequest.h" + +#import +#import +#import + +#import + +BOOL SCCATMSnapCreationEnabled(void) +{ + static dispatch_once_t capturingOnceToken; + static BOOL capturingImprovementEnabled; + dispatch_once(&capturingOnceToken, ^{ + BOOL enabledWithAB = SCExperimentWithContextAwareTaskManagementCapturingImprovementEnabled(); + NSInteger tweakOption = [FBTweakValue(@"CATM", @"Performance Improvement", @"Capturing", (id) @0, + (@{ @0 : @"Respect A/B", + @1 : @"YES", + @2 : @"NO" })) integerValue]; + switch (tweakOption) { + case 0: + capturingImprovementEnabled = enabledWithAB; + break; + case 1: + capturingImprovementEnabled = YES; + break; + case 2: + capturingImprovementEnabled = NO; + break; + default: + SCCAssertFail(@"Illegal option"); + } + }); + return capturingImprovementEnabled; +} + +@implementation SCContextAwareSnapCreationThrottleRequest { + NSString *_requestID; +} + +- (instancetype)init +{ + if (self = [super init]) { + _requestID = @"SCContextAwareSnapCreationThrottleRequest"; + } + return self; +} + +- (BOOL)shouldThrottle:(SCApplicationContextState)context +{ + return SCCATMSnapCreationEnabled() && context != SCApplicationContextStateCamera; +} + +- (NSString *)requestID +{ + return _requestID; +} + +- (BOOL)isEqual:(id)object +{ + return [[object requestID] isEqualToString:_requestID]; +} + +@end diff --git a/ContextAwareTaskManagement/Triggers/SCSnapCreationTriggers.h b/ContextAwareTaskManagement/Triggers/SCSnapCreationTriggers.h new file mode 100644 index 0000000..aaf1c89 --- /dev/null +++ b/ContextAwareTaskManagement/Triggers/SCSnapCreationTriggers.h @@ -0,0 +1,22 @@ +// +// SCSnapCreationTriggers.h +// Snapchat +// +// Created by Cheng Jiang on 4/1/18. +// + +#import + +@interface SCSnapCreationTriggers : NSObject + +- (void)markSnapCreationStart; + +- (void)markSnapCreationPreviewAnimationFinish; + +- (void)markSnapCreationPreviewImageSetupFinish; + +- (void)markSnapCreationPreviewVideoFirstFrameRenderFinish; + +- (void)markSnapCreationEndWithContext:(NSString *)context; + +@end diff --git a/ContextAwareTaskManagement/Triggers/SCSnapCreationTriggers.m b/ContextAwareTaskManagement/Triggers/SCSnapCreationTriggers.m new file mode 100644 index 0000000..252067a --- /dev/null +++ b/ContextAwareTaskManagement/Triggers/SCSnapCreationTriggers.m @@ -0,0 +1,83 @@ +// +// SCSnapCreationTriggers.m +// Snapchat +// +// Created by Cheng Jiang on 3/30/18. +// + +#import "SCSnapCreationTriggers.h" + +#import "SCContextAwareSnapCreationThrottleRequest.h" + +#import +#import +#import +#import + +@implementation SCSnapCreationTriggers { + BOOL _snapCreationStarted; + BOOL _previewAnimationFinished; + BOOL _previewImageSetupFinished; + BOOL _previewVideoFirstFrameRendered; +} + +- (void)markSnapCreationStart +{ + SC_GUARD_ELSE_RUN_AND_RETURN( + !_snapCreationStarted, + SCLogCoreCameraWarning(@"markSnapCreationStart skipped because previous SnapCreation session is not complete")); + @synchronized(self) + { + _snapCreationStarted = YES; + } + [[SCContextAwareThrottleRequester shared] submitSuspendRequest:[SCContextAwareSnapCreationThrottleRequest new]]; +} + +- (void)markSnapCreationPreviewAnimationFinish +{ + @synchronized(self) + { + _previewAnimationFinished = YES; + if (_previewImageSetupFinished || _previewVideoFirstFrameRendered) { + [self markSnapCreationEndWithContext:@"markSnapCreationPreviewAnimationFinish"]; + } + } +} + +- (void)markSnapCreationPreviewImageSetupFinish +{ + @synchronized(self) + { + _previewImageSetupFinished = YES; + if (_previewAnimationFinished) { + [self markSnapCreationEndWithContext:@"markSnapCreationPreviewImageSetupFinish"]; + } + } +} + +- (void)markSnapCreationPreviewVideoFirstFrameRenderFinish +{ + @synchronized(self) + { + _previewVideoFirstFrameRendered = YES; + if (_previewAnimationFinished) { + [self markSnapCreationEndWithContext:@"markSnapCreationPreviewVideoFirstFrameRenderFinish"]; + } + } +} + +- (void)markSnapCreationEndWithContext:(NSString *)context +{ + SC_GUARD_ELSE_RETURN(_snapCreationStarted); + SCLogCoreCameraInfo(@"markSnapCreationEnd triggered with context: %@", context); + @synchronized(self) + { + _snapCreationStarted = NO; + _previewAnimationFinished = NO; + _previewImageSetupFinished = NO; + _previewVideoFirstFrameRendered = NO; + } + [[SCContextAwareThrottleRequester shared] submitResumeRequest:[SCContextAwareSnapCreationThrottleRequest new]]; +} + +@end diff --git a/Features/Core/SCFeature.h b/Features/Core/SCFeature.h new file mode 100644 index 0000000..8720103 --- /dev/null +++ b/Features/Core/SCFeature.h @@ -0,0 +1,26 @@ +// +// SCFeature.h +// SCCamera +// +// Created by Kristian Bauer on 1/4/18. +// + +#import + +/** + * Top level protocol for UI features + */ +#define SCLogCameraFeatureInfo(fmt, ...) SCLogCoreCameraInfo(@"[SCFeature] " fmt, ##__VA_ARGS__) +@protocol SCFeatureContainerView; +@protocol SCFeature + +@optional +- (void)configureWithView:(UIView *)view; +- (void)forwardCameraTimerGesture:(UIGestureRecognizer *)gestureRecognizer; +- (void)forwardCameraOverlayTapGesture:(UIGestureRecognizer *)gestureRecognizer; +- (void)forwardLongPressGesture:(UIGestureRecognizer *)gestureRecognizer; +- (void)forwardPinchGesture:(UIPinchGestureRecognizer *)gestureRecognizer; +- (void)forwardPanGesture:(UIPanGestureRecognizer *)gestureRecognizer; +- (BOOL)shouldBlockTouchAtPoint:(CGPoint)point; + +@end diff --git a/Features/Core/SCFeatureContainerView.h b/Features/Core/SCFeatureContainerView.h new file mode 100644 index 0000000..fe5befa --- /dev/null +++ b/Features/Core/SCFeatureContainerView.h @@ -0,0 +1,13 @@ +// +// SCFeatureContainerView.h +// SCCamera +// +// Created by Kristian Bauer on 4/17/18. +// + +#import + +@protocol SCFeatureContainerView +- (BOOL)isTapGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer; +- (CGRect)initialCameraTimerFrame; +@end diff --git a/Features/Core/SCFeatureCoordinator.h b/Features/Core/SCFeatureCoordinator.h new file mode 100644 index 0000000..db13f26 --- /dev/null +++ b/Features/Core/SCFeatureCoordinator.h @@ -0,0 +1,44 @@ +// +// SCFeatureCoordinator.h +// SCCamera +// +// Created by Kristian Bauer on 1/4/18. +// + +#import "SCFeature.h" + +#import + +@protocol SCFeatureProvider; +@class SCCameraOverlayView; + +/** + * Handles creation of SCFeatures and communication between owner and features. + */ +@interface SCFeatureCoordinator : NSObject + +SC_INIT_AND_NEW_UNAVAILABLE; +- (instancetype)initWithFeatureContainerView:(SCCameraOverlayView *)containerView + provider:(id)provider; + +/** + * Asks provider for features with given featureTypes specified in initializer. + */ +- (void)reloadFeatures; + +/** + * Eventually won't need this, but in order to use new framework w/ existing architecture, need a way to forward + * gestures to individual features. + */ +- (void)forwardCameraTimerGesture:(UIGestureRecognizer *)gestureRecognizer; +- (void)forwardCameraOverlayTapGesture:(UIGestureRecognizer *)gestureRecognizer; +- (void)forwardLongPressGesture:(UIGestureRecognizer *)gestureRecognizer; +- (void)forwardPinchGesture:(UIPinchGestureRecognizer *)recognizer; +- (void)forwardPanGesture:(UIPanGestureRecognizer *)recognizer; +/** + * To prevent gestures on AVCameraViewController from triggering at the same time as feature controls, need to provide a + * way for features to indicate that they will block a touch with given point. + */ +- (BOOL)shouldBlockTouchAtPoint:(CGPoint)point; + +@end diff --git a/Features/Core/SCFeatureCoordinator.m b/Features/Core/SCFeatureCoordinator.m new file mode 100644 index 0000000..971e11f --- /dev/null +++ b/Features/Core/SCFeatureCoordinator.m @@ -0,0 +1,117 @@ +// +// SCFeatureCoordinator.m +// SCCamera +// +// Created by Kristian Bauer on 1/4/18. +// + +#import "SCFeatureCoordinator.h" + +#import "SCFeature.h" +#import "SCFeatureProvider.h" + +#import +#import + +typedef NSString SCFeatureDictionaryKey; + +@interface SCFeatureCoordinator () +@property (nonatomic, weak) UIView *containerView; +@property (nonatomic, strong) id provider; +@end + +@implementation SCFeatureCoordinator + +- (instancetype)initWithFeatureContainerView:(UIView *)containerView + provider:(id)provider +{ + SCTraceODPCompatibleStart(2); + SCAssert(containerView, @"SCFeatureCoordinator containerView must be non-nil"); + SCAssert(provider, @"SCFeatureCoordinator provider must be non-nil"); + self = [super init]; + if (self) { + _containerView = containerView; + _provider = provider; + [self reloadFeatures]; + } + return self; +} + +- (void)reloadFeatures +{ + SCTraceODPCompatibleStart(2); + [_provider resetInstances]; + NSMutableArray *features = [NSMutableArray array]; + for (id feature in _provider.supportedFeatures) { + if ([feature respondsToSelector:@selector(configureWithView:)]) { + [feature configureWithView:_containerView]; + } + if (feature) { + [features addObject:feature]; + } + } +} + +- (void)forwardCameraTimerGesture:(UIGestureRecognizer *)gestureRecognizer +{ + SCTraceODPCompatibleStart(2); + for (id feature in _provider.supportedFeatures) { + if ([feature respondsToSelector:@selector(forwardCameraTimerGesture:)]) { + [feature forwardCameraTimerGesture:gestureRecognizer]; + } + } +} + +- (void)forwardCameraOverlayTapGesture:(UIGestureRecognizer *)gestureRecognizer +{ + SCTraceODPCompatibleStart(2); + for (id feature in _provider.supportedFeatures) { + if ([feature respondsToSelector:@selector(forwardCameraOverlayTapGesture:)]) { + [feature forwardCameraOverlayTapGesture:gestureRecognizer]; + } + } +} + +- (void)forwardLongPressGesture:(UIGestureRecognizer *)gestureRecognizer +{ + SCTraceODPCompatibleStart(2); + for (id feature in _provider.supportedFeatures) { + if ([feature respondsToSelector:@selector(forwardLongPressGesture:)]) { + [feature forwardLongPressGesture:gestureRecognizer]; + } + } +} + +- (void)forwardPinchGesture:(UIPinchGestureRecognizer *)gestureRecognizer +{ + SCTraceODPCompatibleStart(2); + for (id feature in _provider.supportedFeatures) { + if ([feature respondsToSelector:@selector(forwardPinchGesture:)]) { + [feature forwardPinchGesture:gestureRecognizer]; + } + } +} + +- (void)forwardPanGesture:(UIPanGestureRecognizer *)gestureRecognizer +{ + SCTraceODPCompatibleStart(2); + for (id feature in _provider.supportedFeatures) { + if ([feature respondsToSelector:@selector(forwardPanGesture:)]) { + [feature forwardPanGesture:gestureRecognizer]; + } + } +} + +- (BOOL)shouldBlockTouchAtPoint:(CGPoint)point +{ + SCTraceODPCompatibleStart(2); + for (id feature in _provider.supportedFeatures) { + if ([feature respondsToSelector:@selector(shouldBlockTouchAtPoint:)] && + [feature shouldBlockTouchAtPoint:point]) { + return YES; + } + } + return NO; +} + +@end diff --git a/Features/Core/SCFeatureProvider.h b/Features/Core/SCFeatureProvider.h new file mode 100644 index 0000000..e60474f --- /dev/null +++ b/Features/Core/SCFeatureProvider.h @@ -0,0 +1,50 @@ +// +// SCFeatureProvider.h +// SCCamera +// +// Created by Kristian Bauer on 1/4/18. +// + +#import + +#import + +@class SCFeatureSettingsManager, SCCapturerToken, SCUserSession; + +@protocol SCFeature +, SCCapturer, SCFeatureFlash, SCFeatureHandsFree, SCFeatureLensSideButton, SCFeatureLensButtonZ, SCFeatureMemories, + SCFeatureNightMode, SCFeatureSnapKit, SCFeatureTapToFocusAndExposure, SCFeatureToggleCamera, SCFeatureShazam, + SCFeatureImageCapture, SCFeatureScanning, SCFeatureZooming; + +/** + * Provides single location for creating and configuring SCFeatures. + */ +@protocol SCFeatureProvider + +@property (nonatomic) AVCameraViewType cameraViewType; + +@property (nonatomic, readonly) id capturer; +@property (nonatomic, strong, readwrite) SCCapturerToken *token; +@property (nonatomic, readonly) SCUserSession *userSession; +// TODO: We should not be reusing AVCameraViewController so eventually the +// context should be removed. +@property (nonatomic, readonly) AVCameraViewControllerContext context; +@property (nonatomic) id handsFreeRecording; +@property (nonatomic) id snapKit; +@property (nonatomic) id tapToFocusAndExposure; +@property (nonatomic) id memories; +@property (nonatomic) id flash; +@property (nonatomic) id lensSideButton; +@property (nonatomic) id lensZButton; +@property (nonatomic) id nightMode; +@property (nonatomic) id toggleCamera; +@property (nonatomic) id shazam; +@property (nonatomic) id scanning; +@property (nonatomic) id imageCapture; +@property (nonatomic) id zooming; + +@property (nonatomic, readonly) NSArray> *supportedFeatures; + +- (void)resetInstances; + +@end diff --git a/Features/Flash/SCFeatureFlash.h b/Features/Flash/SCFeatureFlash.h new file mode 100644 index 0000000..c579de1 --- /dev/null +++ b/Features/Flash/SCFeatureFlash.h @@ -0,0 +1,20 @@ +// +// SCFeatureFlash.h +// SCCamera +// +// Created by Kristian Bauer on 3/27/18. +// + +#import "SCFeature.h" + +@class SCNavigationBarButtonItem; + +/** + * Public interface for interacting with camera flash feature. + */ +@protocol SCFeatureFlash +@property (nonatomic, readonly) SCNavigationBarButtonItem *navigationBarButtonItem; + +- (void)interruptGestures; + +@end diff --git a/Features/Flash/SCFeatureFlashImpl.h b/Features/Flash/SCFeatureFlashImpl.h new file mode 100644 index 0000000..95f9c3d --- /dev/null +++ b/Features/Flash/SCFeatureFlashImpl.h @@ -0,0 +1,23 @@ +// +// SCFeatureFlashImpl.h +// SCCamera +// +// Created by Kristian Bauer on 3/27/18. +// + +#import "SCFeatureFlash.h" + +#import + +@class SCLogger; +@protocol SCCapturer; + +/** + * Interface for camera flash feature. Handles enabling/disabling of camera flash via SCCapturer and UI for displaying + * flash button. + * Should only expose initializer. All other vars and methods should be declared in SCFeatureFlash protocol. + */ +@interface SCFeatureFlashImpl : NSObject +SC_INIT_AND_NEW_UNAVAILABLE +- (instancetype)initWithCapturer:(id)capturer logger:(SCLogger *)logger NS_DESIGNATED_INITIALIZER; +@end diff --git a/Features/Flash/SCFeatureFlashImpl.m b/Features/Flash/SCFeatureFlashImpl.m new file mode 100644 index 0000000..5cfdb8b --- /dev/null +++ b/Features/Flash/SCFeatureFlashImpl.m @@ -0,0 +1,226 @@ +// +// SCFeatureFlashImpl.m +// SCCamera +// +// Created by Kristian Bauer on 3/27/18. +// + +#import "SCFeatureFlashImpl.h" + +#import "SCCapturer.h" +#import "SCFlashButton.h" +#import "SCManagedCapturerListener.h" +#import "SCManagedCapturerState.h" + +#import +#import +#import +#import +#import + +static CGFloat const kSCFlashButtonInsets = -2.f; +static CGRect const kSCFlashButtonFrame = {0, 0, 36, 44}; + +static NSString *const kSCFlashEventName = @"TOGGLE_CAMERA_FLASH_BUTTON"; +static NSString *const kSCFlashEventParameterFlashName = @"flash_on"; +static NSString *const kSCFlashEventParameterCameraName = @"front_facing_camera_on"; + +@interface SCFeatureFlashImpl () +@property (nonatomic, strong, readwrite) id capturer; +@property (nonatomic, strong, readwrite) SCLogger *logger; +@property (nonatomic, strong, readwrite) SCFlashButton *flashButton; +@property (nonatomic, weak, readwrite) UIView *containerView; +@property (nonatomic, strong, readwrite) SCManagedCapturerState *managedCapturerState; +@property (nonatomic, assign, readwrite) BOOL canEnable; +@end + +@interface SCFeatureFlashImpl (SCManagedCapturerListener) +@end + +@implementation SCFeatureFlashImpl +@synthesize navigationBarButtonItem = _navigationBarButtonItem; + +- (instancetype)initWithCapturer:(id)capturer logger:(SCLogger *)logger +{ + SCTraceODPCompatibleStart(2); + self = [super init]; + if (self) { + _capturer = capturer; + [_capturer addListener:self]; + _logger = logger; + } + return self; +} + +- (void)dealloc +{ + SCTraceODPCompatibleStart(2); + [_capturer removeListener:self]; +} + +#pragma mark - SCFeature + +- (void)configureWithView:(UIView *)view +{ + SCTraceODPCompatibleStart(2); + _containerView = view; +} + +- (BOOL)shouldBlockTouchAtPoint:(CGPoint)point +{ + SCTraceODPCompatibleStart(2); + SC_GUARD_ELSE_RETURN_VALUE(_flashButton.userInteractionEnabled && !_flashButton.hidden, NO); + CGPoint convertedPoint = [_flashButton convertPoint:point fromView:_containerView]; + return [_flashButton pointInside:convertedPoint withEvent:nil]; +} + +#pragma mark - SCFeatureFlash + +- (void)interruptGestures +{ + SCTraceODPCompatibleStart(2); + [_flashButton interruptGestures]; +} + +- (SCNavigationBarButtonItem *)navigationBarButtonItem +{ + SCTraceODPCompatibleStart(2); + SC_GUARD_ELSE_RETURN_VALUE(!_navigationBarButtonItem, _navigationBarButtonItem); + _navigationBarButtonItem = [[SCNavigationBarButtonItem alloc] initWithCustomView:self.flashButton]; + return _navigationBarButtonItem; +} + +#pragma mark - Getters + +- (SCFlashButton *)flashButton +{ + SCTraceODPCompatibleStart(2); + SC_GUARD_ELSE_RETURN_VALUE(!_flashButton, _flashButton); + _flashButton = [[SCFlashButton alloc] initWithFrame:kSCFlashButtonFrame]; + _flashButton.layer.sublayerTransform = CATransform3DMakeTranslation(kSCFlashButtonInsets, 0, 0); + _flashButton.buttonState = SCFlashButtonStateOff; + _flashButton.maximumScale = 1.1111f; + [_flashButton addTarget:self action:@selector(_flashTapped)]; + + _flashButton.accessibilityIdentifier = @"flash"; + _flashButton.accessibilityLabel = SCLocalizedString(@"flash", 0); + return _flashButton; +} + +#pragma mark - Setters + +- (void)setCanEnable:(BOOL)canEnable +{ + SCTraceODPCompatibleStart(2); + SCLogCameraFeatureInfo(@"[%@] setCanEnable new: %@ old: %@", NSStringFromClass([self class]), + canEnable ? @"YES" : @"NO", _canEnable ? @"YES" : @"NO"); + self.flashButton.userInteractionEnabled = canEnable; +} + +#pragma mark - Internal Helpers + +- (void)_flashTapped +{ + SCTraceODPCompatibleStart(2); + BOOL flashActive = !_managedCapturerState.flashActive; + + SCLogCameraFeatureInfo(@"[%@] _flashTapped flashActive new: %@ old: %@", NSStringFromClass([self class]), + flashActive ? @"YES" : @"NO", !flashActive ? @"YES" : @"NO"); + _containerView.userInteractionEnabled = NO; + @weakify(self); + [_capturer setFlashActive:flashActive + completionHandler:^{ + @strongify(self); + SCLogCameraFeatureInfo(@"[%@] _flashTapped setFlashActive completion", NSStringFromClass([self class])); + self.containerView.userInteractionEnabled = YES; + } + context:SCCapturerContext]; + + NSDictionary *loggingParameters = @{ + kSCFlashEventParameterFlashName : @(flashActive), + kSCFlashEventParameterCameraName : + @(_managedCapturerState.devicePosition == SCManagedCaptureDevicePositionFront) + }; + [_logger logEvent:kSCFlashEventName parameters:loggingParameters]; +} + +- (BOOL)_shouldHideForState:(SCManagedCapturerState *)state +{ + SCTraceODPCompatibleStart(2); + return (!state.flashSupported && !state.torchSupported && + state.devicePosition != SCManagedCaptureDevicePositionFront) || + state.arSessionActive; +} + +@end + +@implementation SCFeatureFlashImpl (SCManagedCapturerListener) + +- (void)managedCapturer:(id)managedCapturer didChangeFlashActive:(SCManagedCapturerState *)state +{ + SCTraceODPCompatibleStart(2); + SCLogCameraFeatureInfo(@"[%@] didChangeFlashActive flashActive: %@", NSStringFromClass([self class]), + state.flashActive ? @"YES" : @"NO"); + self.flashButton.buttonState = state.flashActive ? SCFlashButtonStateOn : SCFlashButtonStateOff; +} + +- (void)managedCapturer:(id)managedCapturer + didChangeFlashSupportedAndTorchSupported:(SCManagedCapturerState *)state +{ + SCTraceODPCompatibleStart(2); + SCLogCameraFeatureInfo( + @"[%@] didChangeFlashSupportedAndTorchSupported flashSupported: %@ torchSupported: %@ devicePosition: %@", + NSStringFromClass([self class]), state.flashSupported ? @"YES" : @"NO", state.torchSupported ? @"YES" : @"NO", + state.devicePosition == SCManagedCaptureDevicePositionFront ? @"front" : @"back"); + self.flashButton.hidden = [self _shouldHideForState:state]; +} + +- (void)managedCapturer:(id)managedCapturer didChangeState:(SCManagedCapturerState *)state +{ + SCTraceODPCompatibleStart(2); + _managedCapturerState = [state copy]; +} + +- (void)managedCapturer:(id)managedCapturer didChangeARSessionActive:(SCManagedCapturerState *)state +{ + SCTraceODPCompatibleStart(2); + SCLogCameraFeatureInfo(@"[%@] didChangeARSessionActive: %@", NSStringFromClass([self class]), + state.arSessionActive ? @"YES" : @"NO"); + self.flashButton.hidden = [self _shouldHideForState:state]; +} + +- (void)managedCapturer:(id)managedCapturer + didBeginVideoRecording:(SCManagedCapturerState *)state + session:(SCVideoCaptureSessionInfo)session +{ + SCTraceODPCompatibleStart(2); + self.canEnable = NO; +} + +- (void)managedCapturer:(id)managedCapturer + didFinishRecording:(SCManagedCapturerState *)state + session:(SCVideoCaptureSessionInfo)session + recordedVideo:(SCManagedRecordedVideo *)recordedVideo +{ + SCTraceODPCompatibleStart(2); + self.canEnable = YES; +} + +- (void)managedCapturer:(id)managedCapturer + didFailRecording:(SCManagedCapturerState *)state + session:(SCVideoCaptureSessionInfo)session + error:(NSError *)error +{ + SCTraceODPCompatibleStart(2); + self.canEnable = YES; +} + +- (void)managedCapturer:(id)managedCapturer + didCancelRecording:(SCManagedCapturerState *)state + session:(SCVideoCaptureSessionInfo)session +{ + SCTraceODPCompatibleStart(2); + self.canEnable = YES; +} + +@end diff --git a/Features/Flash/SCFlashButton.h b/Features/Flash/SCFlashButton.h new file mode 100644 index 0000000..b287930 --- /dev/null +++ b/Features/Flash/SCFlashButton.h @@ -0,0 +1,15 @@ +// +// SCFlashButton.h +// SCCamera +// +// Created by Will Wu on 2/13/14. +// Copyright (c) 2014 Snapchat, Inc. All rights reserved. +// + +#import + +typedef NS_ENUM(NSInteger, SCFlashButtonState) { SCFlashButtonStateOn = 0, SCFlashButtonStateOff = 1 }; + +@interface SCFlashButton : SCGrowingButton +@property (nonatomic, assign) SCFlashButtonState buttonState; +@end diff --git a/Features/Flash/SCFlashButton.m b/Features/Flash/SCFlashButton.m new file mode 100644 index 0000000..daeca88 --- /dev/null +++ b/Features/Flash/SCFlashButton.m @@ -0,0 +1,35 @@ +// +// SCFlashButton.m +// SCCamera +// +// Created by Will Wu on 2/13/14. +// Copyright (c) 2014 Snapchat, Inc. All rights reserved. +// + +#import "SCFlashButton.h" + +#import + +@implementation SCFlashButton + +- (void)setButtonState:(SCFlashButtonState)buttonState +{ + // Don't reset flash button state if it doesn't change. + if (_buttonState == buttonState) { + return; + } + _buttonState = buttonState; + + if (buttonState == SCFlashButtonStateOn) { + self.image = [UIImage imageNamed:@"camera_flash_on_v10"]; + self.accessibilityValue = @"on"; + } else { + self.image = [UIImage imageNamed:@"camera_flash_off_v10"]; + self.accessibilityValue = @"off"; + } + + self.imageInset = SCRoundSizeToPixels(CGSizeMake((CGRectGetWidth(self.bounds) - self.image.size.width) / 2, + (CGRectGetHeight(self.bounds) - self.image.size.height) / 2)); +} + +@end diff --git a/Features/HandsFree/SCFeatureHandsFree.h b/Features/HandsFree/SCFeatureHandsFree.h new file mode 100644 index 0000000..f981241 --- /dev/null +++ b/Features/HandsFree/SCFeatureHandsFree.h @@ -0,0 +1,30 @@ +// +// SCFeatureHandsFree.h +// SCCamera +// +// Created by Kristian Bauer on 2/26/18. +// + +#import "SCFeature.h" + +#import + +@class SCLongPressGestureRecognizer, SCPreviewPresenter; + +@protocol SCFeatureHandsFree +@property (nonatomic, weak) SCPreviewPresenter *previewPresenter; +@property (nonatomic, strong, readonly) SCLongPressGestureRecognizer *longPressGestureRecognizer; + +/** + * Whether the feature is enabled or not. + */ +@property (nonatomic) BOOL enabled; +- (void)setupRecordLifecycleEventsWithMethod:(SCCameraRecordingMethod)method; +- (BOOL)shouldDisplayMultiSnapTooltip; + +/** + * Block called when user cancels hands-free recording via X button. + */ +- (void)setCancelBlock:(dispatch_block_t)cancelBlock; + +@end diff --git a/Features/ImageCapture/SCFeatureImageCapture.h b/Features/ImageCapture/SCFeatureImageCapture.h new file mode 100644 index 0000000..fc53bff --- /dev/null +++ b/Features/ImageCapture/SCFeatureImageCapture.h @@ -0,0 +1,27 @@ +// +// SCFeatureImageCapture.h +// SCCamera +// +// Created by Kristian Bauer on 4/18/18. +// + +#import "SCFeature.h" + +#import + +@protocol SCFeatureImageCapture; + +@protocol SCFeatureImageCaptureDelegate +- (void)featureImageCapture:(id)featureImageCapture willCompleteWithImage:(UIImage *)image; +- (void)featureImageCapture:(id)featureImageCapture didCompleteWithError:(NSError *)error; +- (void)featureImageCapturedDidComplete:(id)featureImageCapture; +@end + +/** + SCFeature protocol for capturing an image. + */ +@protocol SCFeatureImageCapture +@property (nonatomic, weak, readwrite) id delegate; +@property (nonatomic, strong, readonly) SCPromise *imagePromise; +- (void)captureImage:(NSString *)captureSessionID; +@end diff --git a/Features/ImageCapture/SCFeatureImageCaptureImpl.h b/Features/ImageCapture/SCFeatureImageCaptureImpl.h new file mode 100644 index 0000000..9ad1b50 --- /dev/null +++ b/Features/ImageCapture/SCFeatureImageCaptureImpl.h @@ -0,0 +1,21 @@ +// +// SCFeatureImageCaptureImpl.h +// SCCamera +// +// Created by Kristian Bauer on 4/18/18. +// + +#import "AVCameraViewEnums.h" +#import "SCFeatureImageCapture.h" + +#import + +@protocol SCCapturer; +@class SCLogger; + +@interface SCFeatureImageCaptureImpl : NSObject +SC_INIT_AND_NEW_UNAVAILABLE +- (instancetype)initWithCapturer:(id)capturer + logger:(SCLogger *)logger + cameraViewType:(AVCameraViewType)cameraViewType NS_DESIGNATED_INITIALIZER; +@end diff --git a/Features/ImageCapture/SCFeatureImageCaptureImpl.m b/Features/ImageCapture/SCFeatureImageCaptureImpl.m new file mode 100644 index 0000000..1762b37 --- /dev/null +++ b/Features/ImageCapture/SCFeatureImageCaptureImpl.m @@ -0,0 +1,184 @@ +// +// SCFeatureImageCaptureImpl.m +// SCCamera +// +// Created by Kristian Bauer on 4/18/18. +// + +#import "SCFeatureImageCaptureImpl.h" + +#import "SCLogger+Camera.h" +#import "SCManagedCapturePreviewLayerController.h" +#import "SCManagedCapturerLensAPI.h" +#import "SCManagedCapturerListener.h" +#import "SCManagedCapturerUtils.h" +#import "SCManagedStillImageCapturer.h" + +#import +#import +#import +#import +#import +#import +#import + +@interface SCFeatureImageCaptureImpl () +@property (nonatomic, strong, readwrite) id capturer; +@property (nonatomic, strong, readwrite) SCLogger *logger; +@property (nonatomic, assign) AVCameraViewType cameraViewType; +@property (nonatomic, strong, readwrite) SCManagedCapturerState *managedCapturerState; + +/** + * Whether user has attempted image capture in current session. Reset on foreground of app. + */ +@property (nonatomic, assign) BOOL hasTriedCapturing; +@end + +@interface SCFeatureImageCaptureImpl (SCManagedCapturerListener) +@end + +@implementation SCFeatureImageCaptureImpl +@synthesize delegate = _delegate; +@synthesize imagePromise = _imagePromise; + +- (instancetype)initWithCapturer:(id)capturer + logger:(SCLogger *)logger + cameraViewType:(AVCameraViewType)cameraViewType +{ + SCTraceODPCompatibleStart(2); + self = [super init]; + if (self) { + _capturer = capturer; + [_capturer addListener:self]; + _logger = logger; + _cameraViewType = cameraViewType; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(_viewWillEnterForeground) + name:UIApplicationWillEnterForegroundNotification + object:nil]; + } + return self; +} + +- (void)dealloc +{ + [_capturer removeListener:self]; +} + +#pragma mark - SCFeatureImageCapture + +- (void)captureImage:(NSString *)captureSessionID +{ + SCTraceODPCompatibleStart(2); + [_logger logTimedEventStart:kSCCameraMetricsRecordingDelay uniqueId:@"IMAGE" isUniqueEvent:NO]; + BOOL asyncCaptureEnabled = [self _asynchronousCaptureEnabled:_managedCapturerState]; + SCLogCameraFeatureInfo(@"[%@] takeImage begin async: %@", NSStringFromClass([self class]), + asyncCaptureEnabled ? @"YES" : @"NO"); + + if (asyncCaptureEnabled) { + SCQueuePerformer *performer = [[SCQueuePerformer alloc] initWithLabel:"com.snapchat.image-capture-promise" + qualityOfService:QOS_CLASS_USER_INTERACTIVE + queueType:DISPATCH_QUEUE_SERIAL + context:SCQueuePerformerContextCoreCamera]; + _imagePromise = [[SCPromise alloc] initWithPerformer:performer]; + } + + @weakify(self); + [_capturer captureStillImageAsynchronouslyWithAspectRatio:SCManagedCapturedImageAndVideoAspectRatio() + captureSessionID:captureSessionID + completionHandler:^(UIImage *fullScreenImage, NSDictionary *metadata, + NSError *error, SCManagedCapturerState *state) { + @strongify(self); + SC_GUARD_ELSE_RETURN(self); + [self _takeImageCallback:fullScreenImage + metadata:metadata + error:error + state:state]; + } + context:SCCapturerContext]; + [_logger logCameraCaptureFinishedWithDuration:0]; +} + +#pragma mark - Private + +- (void)_viewWillEnterForeground +{ + SCTraceODPCompatibleStart(2); + _hasTriedCapturing = NO; +} + +- (void)_takeImageCallback:(UIImage *)image + metadata:(NSDictionary *)metadata + error:(NSError *)error + state:(SCManagedCapturerState *)state +{ + SCTraceODPCompatibleStart(2); + [self _logCaptureComplete:state]; + + if (image) { + [_delegate featureImageCapture:self willCompleteWithImage:image]; + if (_imagePromise) { + [_imagePromise completeWithValue:image]; + } + } else { + if (_imagePromise) { + [_imagePromise completeWithError:[NSError errorWithDomain:@"" code:-1 userInfo:nil]]; + } + [_delegate featureImageCapture:self didCompleteWithError:error]; + } + _imagePromise = nil; + [_delegate featureImageCapturedDidComplete:self]; +} + +- (BOOL)_asynchronousCaptureEnabled:(SCManagedCapturerState *)state +{ + SCTraceODPCompatibleStart(2); + BOOL shouldCaptureImageFromVideoBuffer = + [SCDeviceName isSimilarToIphone5orNewer] && ![SCDeviceName isSimilarToIphone6orNewer]; + // Fast image capture is disabled in following cases + // (1) flash is on; + // (2) lenses are active; + // (3) SCPhotoCapturer is not supported; + // (4) not main camera for iPhoneX; + return !state.flashActive && !state.lensesActive && !_capturer.lensProcessingCore.appliedLens && + (SCPhotoCapturerIsEnabled() || shouldCaptureImageFromVideoBuffer) && + (![SCDeviceName isIphoneX] || (_cameraViewType == AVCameraViewNoReply)); +} + +- (void)_logCaptureComplete:(SCManagedCapturerState *)state +{ + SCTraceODPCompatibleStart(2); + NSDictionary *params = @{ + @"type" : @"image", + @"lenses_active" : @(state.lensesActive), + @"is_back_camera" : @(state.devicePosition != SCManagedCaptureDevicePositionFront), + @"is_main_camera" : @(_cameraViewType == AVCameraViewNoReply), + @"is_first_attempt_after_app_startup" : @(!_hasTriedCapturing), + @"app_startup_type" : SCLaunchType(), + @"app_startup_time" : @(SCAppStartupTimeMicros() / 1000.0), + @"time_elapse_after_app_startup" : @(SCTimeElapseAfterAppStartupMicros() / 1000.0), + }; + [_logger logTimedEventEnd:kSCCameraMetricsRecordingDelay uniqueId:@"IMAGE" parameters:params]; + _hasTriedCapturing = YES; +} + +@end + +@implementation SCFeatureImageCaptureImpl (SCManagedCapturerListener) + +- (void)managedCapturer:(id)managedCapturer didChangeState:(SCManagedCapturerState *)state +{ + SCTraceODPCompatibleStart(2); + _managedCapturerState = [state copy]; +} + +- (void)managedCapturer:(id)managedCapturer didCapturePhoto:(SCManagedCapturerState *)state +{ + SCTraceODPCompatibleStart(2); + if (_imagePromise) { + [[SCManagedCapturePreviewLayerController sharedInstance] pause]; + } +} + +@end diff --git a/Features/NightMode/SCFeatureNightMode.h b/Features/NightMode/SCFeatureNightMode.h new file mode 100644 index 0000000..e0bc488 --- /dev/null +++ b/Features/NightMode/SCFeatureNightMode.h @@ -0,0 +1,22 @@ +// +// SCFeatureNightMode.h +// SCCamera +// +// Created by Kristian Bauer on 4/9/18. +// + +#import "SCFeature.h" + +@class SCNavigationBarButtonItem, SCPreviewPresenter; + +/** + * Public interface for interacting with camera night mode feature. + * User spec: https://snapchat.quip.com/w4h4ArzcmXCS + */ +@protocol SCFeatureNightMode +@property (nonatomic, weak, readwrite) SCPreviewPresenter *previewPresenter; +@property (nonatomic, readonly) SCNavigationBarButtonItem *navigationBarButtonItem; + +- (void)interruptGestures; +- (void)hideWithDelayIfNeeded; +@end diff --git a/Features/NightMode/SCNightModeButton.h b/Features/NightMode/SCNightModeButton.h new file mode 100644 index 0000000..2fe1398 --- /dev/null +++ b/Features/NightMode/SCNightModeButton.h @@ -0,0 +1,18 @@ +// +// SCNightModeButton.h +// SCCamera +// +// Created by Liu Liu on 3/19/15. +// Copyright (c) 2015 Snapchat, Inc. All rights reserved. +// + +#import +#import + +@interface SCNightModeButton : SCGrowingButton +@property (nonatomic, assign, getter=isSelected) BOOL selected; +SC_INIT_AND_NEW_UNAVAILABLE +- (void)show; +- (void)hideWithDelay:(BOOL)delay; +- (BOOL)willHideAfterDelay; +@end diff --git a/Features/NightMode/SCNightModeButton.m b/Features/NightMode/SCNightModeButton.m new file mode 100644 index 0000000..4606a16 --- /dev/null +++ b/Features/NightMode/SCNightModeButton.m @@ -0,0 +1,95 @@ +// +// SCNightModeButton.m +// SCCamera +// +// Created by Liu Liu on 3/19/15. +// Copyright (c) 2015 Snapchat, Inc. All rights reserved. +// + +#import "SCNightModeButton.h" + +#import + +static NSTimeInterval const kSCNightModeButtonHiddenDelay = 2.5; + +@implementation SCNightModeButton { + dispatch_block_t _delayedHideBlock; +} + +- (instancetype)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + if (self) { + self.image = [UIImage imageNamed:@"camera_nightmode_off_v10"]; + self.imageInset = CGSizeMake((CGRectGetWidth(self.bounds) - self.image.size.width) / 2, + (CGRectGetHeight(self.bounds) - self.image.size.height) / 2); + } + return self; +} + +- (void)setSelected:(BOOL)selected +{ + SC_GUARD_ELSE_RETURN(_selected != selected); + if (selected) { + [self _cancelDelayedHideAnimation]; + self.image = [UIImage imageNamed:@"camera_nightmode_on_v10"]; + } else { + self.image = [UIImage imageNamed:@"camera_nightmode_off_v10"]; + } + self.imageInset = CGSizeMake((CGRectGetWidth(self.bounds) - self.image.size.width) / 2, + (CGRectGetHeight(self.bounds) - self.image.size.height) / 2); + _selected = selected; +} + +- (void)show +{ + SC_GUARD_ELSE_RETURN(self.hidden); + SCAssertMainThread(); + [self _cancelDelayedHideAnimation]; + self.hidden = NO; + [self animate]; +} + +- (void)hideWithDelay:(BOOL)delay +{ + SC_GUARD_ELSE_RETURN(!self.hidden); + SCAssertMainThread(); + [self _cancelDelayedHideAnimation]; + if (delay) { + @weakify(self); + _delayedHideBlock = dispatch_block_create(0, ^{ + @strongify(self); + SC_GUARD_ELSE_RETURN(self); + [UIView animateWithDuration:0.3 + animations:^{ + self.alpha = 0; + } + completion:^(BOOL finished) { + self.alpha = 1; + self.hidden = YES; + _delayedHideBlock = nil; + }]; + }); + dispatch_time_t delayTime = + dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kSCNightModeButtonHiddenDelay * NSEC_PER_SEC)); + dispatch_after(delayTime, dispatch_get_main_queue(), _delayedHideBlock); + } else { + self.hidden = YES; + } +} + +- (BOOL)willHideAfterDelay +{ + return _delayedHideBlock != nil; +} + +#pragma mark - Private + +- (void)_cancelDelayedHideAnimation +{ + SC_GUARD_ELSE_RETURN(_delayedHideBlock); + dispatch_cancel(_delayedHideBlock); + _delayedHideBlock = nil; +} + +@end diff --git a/Features/Scanning/SCFeatureScanning.h b/Features/Scanning/SCFeatureScanning.h new file mode 100644 index 0000000..8dc5ab5 --- /dev/null +++ b/Features/Scanning/SCFeatureScanning.h @@ -0,0 +1,26 @@ +// +// SCFeatureScanning.h +// Snapchat +// +// Created by Xiaokang Liu on 2018/4/19. +// + +#import "SCFeature.h" + +@protocol SCFeatureScanning; + +@protocol SCFeatureScanningDelegate +- (void)featureScanning:(id)featureScanning didFinishWithResult:(NSObject *)resultObject; +@end + +/** + This SCFeature allows the user to long press on the screen to scan a snapcode. + */ +@protocol SCFeatureScanning +@property (nonatomic, weak) id delegate; +@property (nonatomic, assign) NSTimeInterval lastSuccessfulScanTime; +- (void)startScanning; +- (void)stopScanning; + +- (void)stopSearch; +@end diff --git a/Features/Shazam/SCFeatureShazam.h b/Features/Shazam/SCFeatureShazam.h new file mode 100644 index 0000000..4a19cf2 --- /dev/null +++ b/Features/Shazam/SCFeatureShazam.h @@ -0,0 +1,23 @@ +// +// SCFeatureShazam.h +// SCCamera +// +// Created by Xiaokang Liu on 2018/4/18. +// + +#import "SCFeature.h" + +@class SCLens; +@protocol SCFeatureShazam; + +@protocol SCFeatureShazamDelegate +- (void)featureShazam:(id)featureShazam didFinishWithResult:(NSObject *)result; +- (void)featureShazamDidSubmitSearchRequest:(id)featureShazam; +- (SCLens *)filterLensForFeatureShazam:(id)featureShazam; +@end + +@protocol SCFeatureShazam +@property (nonatomic, weak) id delegate; +- (void)stopAudioRecordingAsynchronously; +- (void)resetInfo; +@end diff --git a/Features/SnapKit/SCFeatureSnapKit.h b/Features/SnapKit/SCFeatureSnapKit.h new file mode 100644 index 0000000..64ff824 --- /dev/null +++ b/Features/SnapKit/SCFeatureSnapKit.h @@ -0,0 +1,14 @@ +// +// SCFeatureSnapKit.h +// SCCamera +// +// Created by Michel Loenngren on 3/19/18. +// + +#import "SCFeature.h" + +@class SCCameraDeepLinkMetadata; + +@protocol SCFeatureSnapKit +- (void)setDeepLinkMetadata:(SCCameraDeepLinkMetadata *)metadata; +@end diff --git a/Features/TapToFocus/SCFeatureTapToFocusAndExposure.h b/Features/TapToFocus/SCFeatureTapToFocusAndExposure.h new file mode 100644 index 0000000..30ef02d --- /dev/null +++ b/Features/TapToFocus/SCFeatureTapToFocusAndExposure.h @@ -0,0 +1,17 @@ +// +// SCFeatureTapToFocusAndExposure.h +// SCCamera +// +// Created by Michel Loenngren on 4/5/18. +// + +#import "SCFeature.h" + +/** + This SCFeature allows the user to tap on the screen to adjust focus and exposure. + */ +@protocol SCFeatureTapToFocusAndExposure + +- (void)reset; + +@end diff --git a/Features/TapToFocus/SCFeatureTapToFocusAndExposureImpl.h b/Features/TapToFocus/SCFeatureTapToFocusAndExposureImpl.h new file mode 100644 index 0000000..3a9efad --- /dev/null +++ b/Features/TapToFocus/SCFeatureTapToFocusAndExposureImpl.h @@ -0,0 +1,49 @@ +// +// SCFeatureTapToFocusAndExposureImpl.h +// SCCamera +// +// Created by Michel Loenngren on 4/5/18. +// + +#import "SCFeatureTapToFocusAndExposure.h" + +#import + +#import + +@protocol SCCapturer; + +/** + Protocol describing unique camera commands to run when the user taps on screen. These could be focus, exposure or tap + to portrait mode. + */ +@protocol SCFeatureCameraTapCommand +- (void)execute:(CGPoint)pointOfInterest capturer:(id)capturer; +@end + +/** + This is the default implementation of SCFeatureTapToFocusAndExposure allowing the user to tap on the camera overlay + view in order to adjust focus and exposure. + */ +@interface SCFeatureTapToFocusAndExposureImpl : NSObject +SC_INIT_AND_NEW_UNAVAILABLE +- (instancetype)initWithCapturer:(id)capturer commands:(NSArray> *)commands; +@end + +/** + Adjust focus on tap. + */ +@interface SCFeatureCameraFocusTapCommand : NSObject +@end + +/** + Adjust exposure on tap. + */ +@interface SCFeatureCameraExposureTapCommand : NSObject +@end + +/** + Adjust portrait mode point of interest on tap. + */ +@interface SCFeatureCameraPortraitTapCommand : NSObject +@end diff --git a/Features/TapToFocus/SCFeatureTapToFocusAndExposureImpl.m b/Features/TapToFocus/SCFeatureTapToFocusAndExposureImpl.m new file mode 100644 index 0000000..b11bc5a --- /dev/null +++ b/Features/TapToFocus/SCFeatureTapToFocusAndExposureImpl.m @@ -0,0 +1,118 @@ +// +// SCFeatureTapToFocusImpl.m +// SCCamera +// +// Created by Michel Loenngren on 4/5/18. +// + +#import "SCFeatureTapToFocusAndExposureImpl.h" + +#import "SCCameraTweaks.h" +#import "SCCapturer.h" +#import "SCFeatureContainerView.h" +#import "SCTapAnimationView.h" + +#import +#import + +@interface SCFeatureTapToFocusAndExposureImpl () +@property (nonatomic, weak) id capturer; +@property (nonatomic, weak) UIView *containerView; +@property (nonatomic) BOOL userTappedToFocusAndExposure; +@property (nonatomic) NSArray> *commands; +@end + +@implementation SCFeatureTapToFocusAndExposureImpl + +- (instancetype)initWithCapturer:(id)capturer commands:(NSArray> *)commands +{ + if (self = [super init]) { + _capturer = capturer; + _commands = commands; + } + return self; +} + +- (void)reset +{ + SC_GUARD_ELSE_RETURN(_userTappedToFocusAndExposure); + _userTappedToFocusAndExposure = NO; + [_capturer continuousAutofocusAndExposureAsynchronouslyWithCompletionHandler:nil context:SCCapturerContext]; +} + +#pragma mark - SCFeature + +- (void)configureWithView:(UIView *)view +{ + SCTraceODPCompatibleStart(2); + _containerView = view; +} + +- (void)forwardCameraOverlayTapGesture:(UIGestureRecognizer *)gestureRecognizer +{ + SCTraceODPCompatibleStart(2); + CGPoint point = [gestureRecognizer locationInView:gestureRecognizer.view]; + @weakify(self); + [_capturer convertViewCoordinates:[gestureRecognizer locationInView:_containerView] + completionHandler:^(CGPoint pointOfInterest) { + @strongify(self); + SC_GUARD_ELSE_RETURN(self); + SCLogCameraFeatureInfo(@"Tapped to focus: %@", NSStringFromCGPoint(pointOfInterest)); + [self _applyTapCommands:pointOfInterest]; + [self _showTapAnimationAtPoint:point forGesture:gestureRecognizer]; + } + context:SCCapturerContext]; +} + +#pragma mark - Private helpers + +- (void)_applyTapCommands:(CGPoint)pointOfInterest +{ + SCTraceODPCompatibleStart(2); + for (id command in _commands) { + [command execute:pointOfInterest capturer:_capturer]; + } + self.userTappedToFocusAndExposure = YES; +} + +- (void)_showTapAnimationAtPoint:(CGPoint)point forGesture:(UIGestureRecognizer *)gestureRecognizer +{ + SCTraceODPCompatibleStart(2); + SC_GUARD_ELSE_RETURN([self.containerView isTapGestureRecognizer:gestureRecognizer]) + SCTapAnimationView *tapAnimationView = [SCTapAnimationView tapAnimationView]; + [_containerView addSubview:tapAnimationView]; + tapAnimationView.center = point; + [tapAnimationView showWithCompletion:^(SCTapAnimationView *view) { + [view removeFromSuperview]; + }]; +} + +@end + +@implementation SCFeatureCameraFocusTapCommand +- (void)execute:(CGPoint)pointOfInterest capturer:(id)capturer +{ + [capturer setAutofocusPointOfInterestAsynchronously:pointOfInterest + completionHandler:nil + context:SCCapturerContext]; +} +@end + +@implementation SCFeatureCameraExposureTapCommand +- (void)execute:(CGPoint)pointOfInterest capturer:(id)capturer +{ + [capturer setExposurePointOfInterestAsynchronously:pointOfInterest + fromUser:YES + completionHandler:nil + context:SCCapturerContext]; +} +@end + +@implementation SCFeatureCameraPortraitTapCommand +- (void)execute:(CGPoint)pointOfInterest capturer:(id)capturer +{ + [capturer setPortraitModePointOfInterestAsynchronously:pointOfInterest + completionHandler:nil + context:SCCapturerContext]; +} +@end diff --git a/Features/TapToFocus/SCTapAnimationView.h b/Features/TapToFocus/SCTapAnimationView.h new file mode 100644 index 0000000..4e1a903 --- /dev/null +++ b/Features/TapToFocus/SCTapAnimationView.h @@ -0,0 +1,21 @@ +// +// SCTapAnimationView.h +// SCCamera +// +// Created by Alexander Grytsiuk on 8/26/15. +// Copyright (c) 2015 Snapchat, Inc. All rights reserved. +// + +#import + +@class SCTapAnimationView; + +typedef void (^SCTapAnimationViewCompletion)(SCTapAnimationView *); + +@interface SCTapAnimationView : UIView + ++ (instancetype)tapAnimationView; + +- (void)showWithCompletion:(SCTapAnimationViewCompletion)completion; + +@end diff --git a/Features/TapToFocus/SCTapAnimationView.m b/Features/TapToFocus/SCTapAnimationView.m new file mode 100644 index 0000000..477c496 --- /dev/null +++ b/Features/TapToFocus/SCTapAnimationView.m @@ -0,0 +1,178 @@ +// +// SCTapAnimationView.m +// SCCamera +// +// Created by Alexander Grytsiuk on 8/26/15. +// Copyright (c) 2015 Snapchat, Inc. All rights reserved. +// + +#import "SCTapAnimationView.h" + +#import + +@import QuartzCore; + +static const CGFloat kSCAnimationStep = 0.167; +static const CGFloat kSCInnerCirclePadding = 2.5; +static const CGFloat kSCTapAnimationViewWidth = 55; +static const CGFloat kSCOuterRingBorderWidth = 1; + +static NSString *const kSCOpacityAnimationKey = @"opacity"; +static NSString *const kSCScaleAnimationKey = @"scale"; + +@implementation SCTapAnimationView { + CALayer *_outerRing; + CALayer *_innerCircle; +} + +#pragma mark Class Methods + ++ (instancetype)tapAnimationView +{ + return [[self alloc] initWithFrame:CGRectMake(0, 0, kSCTapAnimationViewWidth, kSCTapAnimationViewWidth)]; +} + +#pragma mark Life Cycle + +- (instancetype)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + if (self) { + self.userInteractionEnabled = NO; + _outerRing = [CALayer layer]; + _outerRing.backgroundColor = [UIColor clearColor].CGColor; + _outerRing.borderColor = [UIColor whiteColor].CGColor; + _outerRing.borderWidth = kSCOuterRingBorderWidth; + _outerRing.shadowColor = [UIColor blackColor].CGColor; + _outerRing.shadowOpacity = 0.4; + _outerRing.shadowOffset = CGSizeMake(0.5, 0.5); + _outerRing.opacity = 0.0; + _outerRing.frame = self.bounds; + _outerRing.cornerRadius = CGRectGetMidX(_outerRing.bounds); + [self.layer addSublayer:_outerRing]; + + _innerCircle = [CALayer layer]; + _innerCircle.backgroundColor = [UIColor whiteColor].CGColor; + _innerCircle.opacity = 0.0; + _innerCircle.frame = CGRectInset(self.bounds, kSCInnerCirclePadding, kSCInnerCirclePadding); + _innerCircle.cornerRadius = CGRectGetMidX(_innerCircle.bounds); + [self.layer addSublayer:_innerCircle]; + } + return self; +} + +#pragma mark Public + +- (void)showWithCompletion:(SCTapAnimationViewCompletion)completion +{ + [_outerRing removeAllAnimations]; + [_innerCircle removeAllAnimations]; + + [CATransaction begin]; + [CATransaction setCompletionBlock:^{ + if (completion) { + completion(self); + } + }]; + [self addOuterRingOpacityAnimation]; + [self addOuterRingScaleAnimation]; + [self addInnerCircleOpacityAnimation]; + [self addInnerCircleScaleAnimation]; + [CATransaction commit]; +} + +#pragma mark Private + +- (CAKeyframeAnimation *)keyFrameAnimationWithKeyPath:(NSString *)keyPath + duration:(CGFloat)duration + values:(NSArray *)values + keyTimes:(NSArray *)keyTimes + timingFunctions:(NSArray *)timingFunctions +{ + CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:keyPath]; + keyframeAnimation.duration = duration; + keyframeAnimation.values = values; + keyframeAnimation.keyTimes = keyTimes; + keyframeAnimation.timingFunctions = timingFunctions; + keyframeAnimation.fillMode = kCAFillModeForwards; + keyframeAnimation.removedOnCompletion = NO; + + return keyframeAnimation; +} + +- (CABasicAnimation *)animationWithKeyPath:(NSString *)keyPath + duration:(CGFloat)duration + fromValue:(NSValue *)fromValue + toValue:(NSValue *)toValue + timingFunction:(CAMediaTimingFunction *)timingFunction +{ + CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:keyPath]; + animation.duration = duration; + animation.fromValue = fromValue; + animation.toValue = toValue; + animation.timingFunction = timingFunction; + animation.fillMode = kCAFillModeForwards; + animation.removedOnCompletion = NO; + + return animation; +} + +- (void)addOuterRingOpacityAnimation +{ + CAKeyframeAnimation *animation = + [self keyFrameAnimationWithKeyPath:@keypath(_outerRing, opacity) + duration:kSCAnimationStep * 5 + values:@[ @0.0, @1.0, @1.0, @0.0 ] + keyTimes:@[ @0.0, @0.2, @0.8, @1.0 ] + timingFunctions:@[ + [CAMediaTimingFunction functionWithControlPoints:0.0:0.0:0.0:1.0], + [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear], + [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut], + ]]; + [_outerRing addAnimation:animation forKey:kSCOpacityAnimationKey]; +} + +- (void)addOuterRingScaleAnimation +{ + CAKeyframeAnimation *animation = + [self keyFrameAnimationWithKeyPath:@keypath(_innerCircle, transform) + duration:kSCAnimationStep * 3 + values:@[ + [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.50, 0.50, 1.0)], + [NSValue valueWithCATransform3D:CATransform3DIdentity], + [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.83, 0.83, 1.0)], + ] + keyTimes:@[ @0.0, @0.66, @1.0 ] + timingFunctions:@[ + [CAMediaTimingFunction functionWithControlPoints:0.0:0.0:0.0:1.0], + [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut], + ]]; + [_outerRing addAnimation:animation forKey:kSCScaleAnimationKey]; +} + +- (void)addInnerCircleOpacityAnimation +{ + CAKeyframeAnimation *animation = + [self keyFrameAnimationWithKeyPath:@keypath(_innerCircle, opacity) + duration:kSCAnimationStep * 3 + values:@[ @0.0, @0.40, @0.0 ] + keyTimes:@[ @0.0, @0.33, @1.0 ] + timingFunctions:@[ + [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn], + [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut], + ]]; + [_innerCircle addAnimation:animation forKey:kSCOpacityAnimationKey]; +} + +- (void)addInnerCircleScaleAnimation +{ + CABasicAnimation *animation = + [self animationWithKeyPath:@keypath(_innerCircle, transform) + duration:kSCAnimationStep * 2 + fromValue:[NSValue valueWithCATransform3D:CATransform3DMakeScale(0.0, 0.0, 1.0)] + toValue:[NSValue valueWithCATransform3D:CATransform3DIdentity] + timingFunction:[CAMediaTimingFunction functionWithControlPoints:0.0:0.0:0.0:1.0]]; + [_innerCircle addAnimation:animation forKey:kSCScaleAnimationKey]; +} + +@end diff --git a/Features/ToggleCamera/SCFeatureToggleCamera.h b/Features/ToggleCamera/SCFeatureToggleCamera.h new file mode 100644 index 0000000..539d2c9 --- /dev/null +++ b/Features/ToggleCamera/SCFeatureToggleCamera.h @@ -0,0 +1,37 @@ +// +// SCFeatureToggleCamera.h +// SCCamera +// +// Created by Michel Loenngren on 4/17/18. +// + +#import +#import + +@protocol SCCapturer +, SCFeatureToggleCamera, SCLensCameraScreenDataProviderProtocol; + +@protocol SCFeatureToggleCameraDelegate + +- (void)featureToggleCamera:(id)feature + willToggleToDevicePosition:(SCManagedCaptureDevicePosition)devicePosition; +- (void)featureToggleCamera:(id)feature + didToggleToDevicePosition:(SCManagedCaptureDevicePosition)devicePosition; + +@end + +/** + SCFeature protocol for toggling the camera. + */ +@protocol SCFeatureToggleCamera + +@property (nonatomic, weak) id delegate; + +- (void)toggleCameraWithRecording:(BOOL)isRecording + takingPicture:(BOOL)isTakingPicture + lensDataProvider:(id)lensDataProvider + completion:(void (^)(BOOL success))completion; + +- (void)reset; + +@end diff --git a/Features/Zooming/SCFeatureZooming.h b/Features/Zooming/SCFeatureZooming.h new file mode 100644 index 0000000..b7de823 --- /dev/null +++ b/Features/Zooming/SCFeatureZooming.h @@ -0,0 +1,34 @@ +// +// SCFeatureZooming.h +// SCCamera +// +// Created by Xiaokang Liu on 2018/4/19. +// + +#import "SCFeature.h" + +#import +#import + +@class SCPreviewPresenter; +@protocol SCFeatureZooming; + +@protocol SCFeatureZoomingDelegate +- (void)featureZoomingForceTouchedWhileRecording:(id)featureZooming; +- (BOOL)featureZoomingIsInitiatedRecording:(id)featureZooming; +@end + +@protocol SCFeatureZooming +@property (nonatomic, weak) id delegate; +@property (nonatomic, weak) SCPreviewPresenter *previewPresenter; + +- (void)resetOffset; +- (void)resetScale; + +- (void)cancelPreview; +- (void)flipOffset; + +- (void)resetBeginningScale; +- (void)toggleCameraForReset:(SCManagedCaptureDevicePosition)devicePosition; +- (void)recordCurrentZoomStateForReset; +@end diff --git a/Lens/SCManagedCapturerARImageCaptureProvider.h b/Lens/SCManagedCapturerARImageCaptureProvider.h new file mode 100644 index 0000000..12e5839 --- /dev/null +++ b/Lens/SCManagedCapturerARImageCaptureProvider.h @@ -0,0 +1,23 @@ +// +// SCManagedCapturerARImageCaptureProvider.h +// SCCamera +// +// Created by Michel Loenngren on 4/11/18. +// + +#import + +@class SCManagedStillImageCapturer; +@protocol SCManagedCapturerLensAPI +, SCPerforming; + +/** + Bridging protocol providing the ARImageCapturer subclass of SCManagedStillImageCapturer + to capture core. + */ +@protocol SCManagedCapturerARImageCaptureProvider + +- (SCManagedStillImageCapturer *)arImageCapturerWith:(id)performer + lensProcessingCore:(id)lensProcessingCore; + +@end diff --git a/Lens/SCManagedCapturerGLViewManagerAPI.h b/Lens/SCManagedCapturerGLViewManagerAPI.h new file mode 100644 index 0000000..46c7894 --- /dev/null +++ b/Lens/SCManagedCapturerGLViewManagerAPI.h @@ -0,0 +1,27 @@ +// +// SCManagedCapturerGLViewManagerAPI.h +// SCCamera +// +// Created by Michel Loenngren on 4/11/18. +// + +#import + +#import + +@class SCCaptureResource; + +/** + Bridging protocol for providing a glViewManager to capture core. + */ +@protocol SCManagedCapturerGLViewManagerAPI + +@property (nonatomic, readonly, strong) LSAGLView *view; + +- (void)configureWithCaptureResource:(SCCaptureResource *)captureResource; + +- (void)setLensesActive:(BOOL)active; + +- (void)prepareViewIfNecessary; + +@end diff --git a/Lens/SCManagedCapturerLSAComponentTrackerAPI.h b/Lens/SCManagedCapturerLSAComponentTrackerAPI.h new file mode 100644 index 0000000..7191fed --- /dev/null +++ b/Lens/SCManagedCapturerLSAComponentTrackerAPI.h @@ -0,0 +1,19 @@ +// +// SCManagedCapturerLSAComponentTrackerAPI.h +// SCCamera +// +// Created by Michel Loenngren on 4/11/18. +// + +#import + +@class SCCaptureResource; + +/** + SCCamera protocol providing LSA tracking logic. + */ +@protocol SCManagedCapturerLSAComponentTrackerAPI + +- (void)configureWithCaptureResource:(SCCaptureResource *)captureResource; + +@end diff --git a/Lens/SCManagedCapturerLensAPI.h b/Lens/SCManagedCapturerLensAPI.h new file mode 100644 index 0000000..22ad5a9 --- /dev/null +++ b/Lens/SCManagedCapturerLensAPI.h @@ -0,0 +1,67 @@ +// +// SCManagedCapturerLensAPI.h +// SCCamera +// +// Created by Michel Loenngren on 4/11/18. +// + +#import "SCManagedCapturerListener.h" +#import "SCManagedVideoARDataSource.h" + +#import +#import + +#import + +@protocol SCManagedAudioDataSourceListener +, SCManagedVideoARDataSource; +@class LSAComponentManager; + +/** + Encapsulation of LensesProcessingCore for use in SCCamera. + */ +@protocol SCManagedCapturerLensAPI + +@property (nonatomic, strong, readonly) LSAComponentManager *componentManager; +@property (nonatomic, strong) NSString *activeLensId; +@property (nonatomic, readonly) BOOL isLensApplied; +@property (nonatomic, strong, readonly) + id capturerListener; + +typedef void (^SCManagedCapturerLensAPIPointOfInterestCompletion)(SCLensCategory *category, NSInteger categoriesCount); + +- (void)setAspectRatio:(BOOL)isLiveStreaming; + +- (SCLens *)appliedLens; + +- (void)setFieldOfView:(float)fieldOfView; + +- (void)setAsFieldOfViewListenerForDevice:(SCManagedCaptureDevice *)captureDevice; + +- (void)setAsFieldOfViewListenerForARDataSource:(id)arDataSource NS_AVAILABLE_IOS(11_0); + +- (void)removeFieldOfViewListener; + +- (void)setModifySource:(BOOL)modifySource; + +- (void)setLensesActive:(BOOL)lensesActive + videoOrientation:(AVCaptureVideoOrientation)videoOrientation + filterFactory:(SCLookseryFilterFactory *)filterFactory; + +- (void)detectLensCategoryOnNextFrame:(CGPoint)point + videoOrientation:(AVCaptureVideoOrientation)videoOrientation + lenses:(NSArray *)lenses + completion:(SCManagedCapturerLensAPIPointOfInterestCompletion)completion; + +- (void)setShouldMuteAllSounds:(BOOL)shouldMuteAllSounds; + +- (UIImage *)processImage:(UIImage *)image + maxPixelSize:(NSInteger)maxPixelSize + devicePosition:(SCManagedCaptureDevicePosition)position + fieldOfView:(float)fieldOfView; + +- (void)setShouldProcessARFrames:(BOOL)shouldProcessARFrames; + +- (NSInteger)maxPixelSize; + +@end diff --git a/Lens/SCManagedCapturerLensAPIProvider.h b/Lens/SCManagedCapturerLensAPIProvider.h new file mode 100644 index 0000000..d98c5d5 --- /dev/null +++ b/Lens/SCManagedCapturerLensAPIProvider.h @@ -0,0 +1,20 @@ +// +// SCManagedCapturerLensAPIProvider.h +// SCCamera +// +// Created by Michel Loenngren on 4/12/18. +// + +#import + +@protocol SCManagedCapturerLensAPI; +@class SCCaptureResource; + +/** + Provider for creating new instances of SCManagedCapturerLensAPI within SCCamera. + */ +@protocol SCManagedCapturerLensAPIProvider + +- (id)lensAPIForCaptureResource:(SCCaptureResource *)captureResouce; + +@end