Jonny Banana
6 years ago
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 2847 additions and 0 deletions
-
56BlackCamera/SCBlackCameraDetector.h
-
134BlackCamera/SCBlackCameraDetector.m
-
26BlackCamera/SCBlackCameraNoOutputDetector.h
-
137BlackCamera/SCBlackCameraNoOutputDetector.m
-
20BlackCamera/SCBlackCameraPreviewDetector.h
-
92BlackCamera/SCBlackCameraPreviewDetector.m
-
35BlackCamera/SCBlackCameraReporter.h
-
86BlackCamera/SCBlackCameraReporter.m
-
27BlackCamera/SCBlackCameraRunningDetector.h
-
84BlackCamera/SCBlackCameraRunningDetector.m
-
23BlackCamera/SCBlackCameraSessionBlockDetector.h
-
82BlackCamera/SCBlackCameraSessionBlockDetector.m
-
31BlackCamera/SCBlackCameraViewDetector.h
-
136BlackCamera/SCBlackCameraViewDetector.m
-
14BlackCamera/SCCaptureSessionFixer.h
-
21BlackCamera/SCCaptureSessionFixer.m
-
13ContextAwareTaskManagement/OWNERS
-
16ContextAwareTaskManagement/Requests/SCContextAwareSnapCreationThrottleRequest.h
-
70ContextAwareTaskManagement/Requests/SCContextAwareSnapCreationThrottleRequest.m
-
22ContextAwareTaskManagement/Triggers/SCSnapCreationTriggers.h
-
83ContextAwareTaskManagement/Triggers/SCSnapCreationTriggers.m
-
26Features/Core/SCFeature.h
-
13Features/Core/SCFeatureContainerView.h
-
44Features/Core/SCFeatureCoordinator.h
-
117Features/Core/SCFeatureCoordinator.m
-
50Features/Core/SCFeatureProvider.h
-
20Features/Flash/SCFeatureFlash.h
-
23Features/Flash/SCFeatureFlashImpl.h
-
226Features/Flash/SCFeatureFlashImpl.m
-
15Features/Flash/SCFlashButton.h
-
35Features/Flash/SCFlashButton.m
-
30Features/HandsFree/SCFeatureHandsFree.h
-
27Features/ImageCapture/SCFeatureImageCapture.h
-
21Features/ImageCapture/SCFeatureImageCaptureImpl.h
-
184Features/ImageCapture/SCFeatureImageCaptureImpl.m
-
22Features/NightMode/SCFeatureNightMode.h
-
18Features/NightMode/SCNightModeButton.h
-
95Features/NightMode/SCNightModeButton.m
-
26Features/Scanning/SCFeatureScanning.h
-
23Features/Shazam/SCFeatureShazam.h
-
14Features/SnapKit/SCFeatureSnapKit.h
-
17Features/TapToFocus/SCFeatureTapToFocusAndExposure.h
-
49Features/TapToFocus/SCFeatureTapToFocusAndExposureImpl.h
-
118Features/TapToFocus/SCFeatureTapToFocusAndExposureImpl.m
-
21Features/TapToFocus/SCTapAnimationView.h
-
178Features/TapToFocus/SCTapAnimationView.m
-
37Features/ToggleCamera/SCFeatureToggleCamera.h
-
34Features/Zooming/SCFeatureZooming.h
-
23Lens/SCManagedCapturerARImageCaptureProvider.h
-
27Lens/SCManagedCapturerGLViewManagerAPI.h
-
19Lens/SCManagedCapturerLSAComponentTrackerAPI.h
-
67Lens/SCManagedCapturerLensAPI.h
-
20Lens/SCManagedCapturerLensAPIProvider.h
@ -0,0 +1,56 @@ |
|||||
|
// |
||||
|
// SCBlackCameraDetector.h |
||||
|
// Snapchat |
||||
|
// |
||||
|
// Created by Derek Wang on 24/01/2018. |
||||
|
// |
||||
|
|
||||
|
#import "SCBlackCameraReporter.h" |
||||
|
|
||||
|
#import <Foundation/Foundation.h> |
||||
|
#import <UIKit/UIKit.h> |
||||
|
|
||||
|
@class SCBlackCameraNoOutputDetector; |
||||
|
|
||||
|
@interface SCBlackCameraDetector : NSObject |
||||
|
|
||||
|
@property (nonatomic, strong) SCBlackCameraNoOutputDetector *blackCameraNoOutputDetector; |
||||
|
|
||||
|
SC_INIT_AND_NEW_UNAVAILABLE |
||||
|
- (instancetype)initWithTicketCreator:(id<SCManiphestTicketCreator>)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 |
@ -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 <SCFoundation/SCQueuePerformer.h> |
||||
|
|
||||
|
#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<SCManiphestTicketCreator>)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 |
@ -0,0 +1,26 @@ |
|||||
|
// |
||||
|
// SCBlackCameraNoOutputDetector.h |
||||
|
// Snapchat |
||||
|
// |
||||
|
// Created by Derek Wang on 05/12/2017. |
||||
|
// |
||||
|
|
||||
|
#import "SCManagedCapturerListener.h" |
||||
|
|
||||
|
#import <SCCameraFoundation/SCManagedVideoDataSourceListener.h> |
||||
|
|
||||
|
#import <Foundation/Foundation.h> |
||||
|
|
||||
|
@class SCBlackCameraNoOutputDetector, SCBlackCameraReporter; |
||||
|
@protocol SCManiphestTicketCreator; |
||||
|
|
||||
|
@protocol SCBlackCameraDetectorDelegate |
||||
|
- (void)detector:(SCBlackCameraNoOutputDetector *)detector didDetectBlackCamera:(id<SCCapturer>)capture; |
||||
|
@end |
||||
|
|
||||
|
@interface SCBlackCameraNoOutputDetector : NSObject <SCManagedVideoDataSourceListener, SCManagedCapturerListener> |
||||
|
|
||||
|
@property (nonatomic, weak) id<SCBlackCameraDetectorDelegate> delegate; |
||||
|
- (instancetype)initWithReporter:(SCBlackCameraReporter *)reporter; |
||||
|
|
||||
|
@end |
@ -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 <SCFoundation/SCAssertWrapper.h> |
||||
|
#import <SCFoundation/SCLog.h> |
||||
|
#import <SCFoundation/SCQueuePerformer.h> |
||||
|
#import <SCFoundation/SCTraceODPCompatible.h> |
||||
|
#import <SCFoundation/SCZeroDependencyExperiments.h> |
||||
|
#import <SCLogger/SCCameraMetrics.h> |
||||
|
#import <SCLogger/SCLogger.h> |
||||
|
|
||||
|
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<SCManagedVideoDataSource>)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<SCCapturer>)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<SCCapturer>)managedCapturer didStopRunning:(SCManagedCapturerState *)state |
||||
|
{ |
||||
|
SCAssertMainThread(); |
||||
|
_sessionStoppedRunning = YES; |
||||
|
[self.queuePerformer perform:^{ |
||||
|
SCTraceODPCompatibleStart(2); |
||||
|
_sampleBufferReceived = NO; |
||||
|
}]; |
||||
|
} |
||||
|
|
||||
|
@end |
@ -0,0 +1,20 @@ |
|||||
|
// |
||||
|
// SCBlackCameraPreviewDetector.h |
||||
|
// Snapchat |
||||
|
// |
||||
|
// Created by Derek Wang on 25/01/2018. |
||||
|
// |
||||
|
|
||||
|
#import <Foundation/Foundation.h> |
||||
|
|
||||
|
@class SCQueuePerformer, SCBlackCameraReporter; |
||||
|
@protocol SCManiphestTicketCreator; |
||||
|
|
||||
|
@interface SCBlackCameraPreviewDetector : NSObject |
||||
|
|
||||
|
- (instancetype)initWithPerformer:(SCQueuePerformer *)performer reporter:(SCBlackCameraReporter *)reporter; |
||||
|
|
||||
|
- (void)sessionDidChangeIsRunning:(BOOL)running; |
||||
|
- (void)capturePreviewDidBecomeVisible:(BOOL)visible; |
||||
|
|
||||
|
@end |
@ -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 <SCCrashLogger/SCCrashLogger.h> |
||||
|
#import <SCFoundation/SCQueuePerformer.h> |
||||
|
#import <SCFoundation/SCThreadHelpers.h> |
||||
|
#import <SCFoundation/SCZeroDependencyExperiments.h> |
||||
|
|
||||
|
// 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 |
@ -0,0 +1,35 @@ |
|||||
|
// |
||||
|
// SCBlackCameraReporter.h |
||||
|
// Snapchat |
||||
|
// |
||||
|
// Created by Derek Wang on 09/01/2018. |
||||
|
// |
||||
|
|
||||
|
#import <SCBase/SCMacros.h> |
||||
|
|
||||
|
#import <Foundation/Foundation.h> |
||||
|
|
||||
|
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<SCManiphestTicketCreator>)ticketCreator; |
||||
|
|
||||
|
- (NSString *)causeNameFor:(SCBlackCameraCause)cause; |
||||
|
|
||||
|
- (void)reportBlackCameraWithCause:(SCBlackCameraCause)cause; |
||||
|
- (void)fileShakeTicketWithCause:(SCBlackCameraCause)cause; |
||||
|
|
||||
|
@end |
@ -0,0 +1,86 @@ |
|||||
|
// |
||||
|
// SCBlackCameraReporter.m |
||||
|
// Snapchat |
||||
|
// |
||||
|
// Created by Derek Wang on 09/01/2018. |
||||
|
// |
||||
|
|
||||
|
#import "SCBlackCameraReporter.h" |
||||
|
|
||||
|
#import "SCManiphestTicketCreator.h" |
||||
|
|
||||
|
#import <SCFoundation/SCAssertWrapper.h> |
||||
|
#import <SCFoundation/SCLog.h> |
||||
|
#import <SCFoundation/SCLogHelper.h> |
||||
|
#import <SCFoundation/SCZeroDependencyExperiments.h> |
||||
|
#import <SCLogger/SCCameraMetrics.h> |
||||
|
#import <SCLogger/SCLogger.h> |
||||
|
|
||||
|
@interface SCBlackCameraReporter () |
||||
|
|
||||
|
@property (nonatomic) id<SCManiphestTicketCreator> ticketCreator; |
||||
|
|
||||
|
@end |
||||
|
|
||||
|
@implementation SCBlackCameraReporter |
||||
|
|
||||
|
- (instancetype)initWithTicketCreator:(id<SCManiphestTicketCreator>)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 |
@ -0,0 +1,27 @@ |
|||||
|
// |
||||
|
// SCBlackCameraRunningDetector.h |
||||
|
// Snapchat |
||||
|
// |
||||
|
// Created by Derek Wang on 30/01/2018. |
||||
|
// |
||||
|
|
||||
|
#import <SCBase/SCMacros.h> |
||||
|
|
||||
|
#import <Foundation/Foundation.h> |
||||
|
|
||||
|
@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 |
@ -0,0 +1,84 @@ |
|||||
|
// |
||||
|
// SCBlackCameraRunningDetector.m |
||||
|
// Snapchat |
||||
|
// |
||||
|
// Created by Derek Wang on 30/01/2018. |
||||
|
// |
||||
|
|
||||
|
#import "SCBlackCameraRunningDetector.h" |
||||
|
|
||||
|
#import "SCBlackCameraReporter.h" |
||||
|
|
||||
|
#import <SCFoundation/SCAssertWrapper.h> |
||||
|
#import <SCFoundation/SCQueuePerformer.h> |
||||
|
#import <SCFoundation/SCTraceODPCompatible.h> |
||||
|
#import <SCLogger/SCCameraMetrics.h> |
||||
|
|
||||
|
// 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 |
@ -0,0 +1,23 @@ |
|||||
|
// |
||||
|
// SCBlackCameraSessionBlockDetector.h |
||||
|
// Snapchat |
||||
|
// |
||||
|
// Created by Derek Wang on 25/01/2018. |
||||
|
// |
||||
|
|
||||
|
#import "SCBlackCameraReporter.h" |
||||
|
|
||||
|
#import <Foundation/Foundation.h> |
||||
|
|
||||
|
@interface SCBlackCameraSessionBlockDetector : NSObject |
||||
|
|
||||
|
SC_INIT_AND_NEW_UNAVAILABLE |
||||
|
- (instancetype)initWithReporter:(SCBlackCameraReporter *)reporter; |
||||
|
|
||||
|
- (void)sessionWillCallStartRunning; |
||||
|
- (void)sessionDidCallStartRunning; |
||||
|
|
||||
|
- (void)sessionWillCommitConfiguration; |
||||
|
- (void)sessionDidCommitConfiguration; |
||||
|
|
||||
|
@end |
@ -0,0 +1,82 @@ |
|||||
|
// |
||||
|
// SCBlackCameraSessionBlockDetector.m |
||||
|
// Snapchat |
||||
|
// |
||||
|
// Created by Derek Wang on 25/01/2018. |
||||
|
// |
||||
|
|
||||
|
#import "SCBlackCameraSessionBlockDetector.h" |
||||
|
|
||||
|
#import "SCBlackCameraReporter.h" |
||||
|
|
||||
|
#import <SCLogger/SCCameraMetrics.h> |
||||
|
#import <SCLogger/SCLogger.h> |
||||
|
|
||||
|
@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 |
@ -0,0 +1,31 @@ |
|||||
|
// |
||||
|
// SCBlackCameraDetectorCameraView.h |
||||
|
// Snapchat |
||||
|
// |
||||
|
// Created by Derek Wang on 24/01/2018. |
||||
|
// |
||||
|
|
||||
|
#import <Foundation/Foundation.h> |
||||
|
#import <UIKit/UIKit.h> |
||||
|
|
||||
|
@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 |
@ -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 <SCFoundation/SCAssertWrapper.h> |
||||
|
#import <SCFoundation/SCLog.h> |
||||
|
#import <SCFoundation/SCQueuePerformer.h> |
||||
|
#import <SCFoundation/SCTraceODPCompatible.h> |
||||
|
#import <SCLogger/SCCameraMetrics.h> |
||||
|
|
||||
|
// 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 |
@ -0,0 +1,14 @@ |
|||||
|
// |
||||
|
// SCCaptureSessionFixer.h |
||||
|
// Snapchat |
||||
|
// |
||||
|
// Created by Derek Wang on 05/12/2017. |
||||
|
// |
||||
|
|
||||
|
#import "SCBlackCameraNoOutputDetector.h" |
||||
|
|
||||
|
#import <Foundation/Foundation.h> |
||||
|
|
||||
|
@interface SCCaptureSessionFixer : NSObject <SCBlackCameraDetectorDelegate> |
||||
|
|
||||
|
@end |
@ -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<SCCapturer>)capture |
||||
|
{ |
||||
|
if (SCCameraTweaksBlackCameraRecoveryEnabled()) { |
||||
|
[capture recreateAVCaptureSession]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@end |
@ -0,0 +1,13 @@ |
|||||
|
--- !OWNERS |
||||
|
|
||||
|
version: 2 |
||||
|
|
||||
|
default: |
||||
|
jira_project: CCAM |
||||
|
owners: |
||||
|
num_required_reviewers: 1 |
||||
|
teams: |
||||
|
- Snapchat/core-camera-ios |
||||
|
users: |
||||
|
- cjiang |
||||
|
- ljia |
@ -0,0 +1,16 @@ |
|||||
|
// |
||||
|
// SCContextAwareSnapCreationThrottleRequest.h |
||||
|
// SCCamera |
||||
|
// |
||||
|
// Created by Cheng Jiang on 4/24/18. |
||||
|
// |
||||
|
|
||||
|
#import <SCFoundation/SCContextAwareThrottleRequester.h> |
||||
|
|
||||
|
#import <Foundation/Foundation.h> |
||||
|
|
||||
|
@interface SCContextAwareSnapCreationThrottleRequest : NSObject <SCContextAwareThrottleRequest> |
||||
|
|
||||
|
- (instancetype)init; |
||||
|
|
||||
|
@end |
@ -0,0 +1,70 @@ |
|||||
|
// |
||||
|
// SCContextAwareSnapCreationThrottleRequest.m |
||||
|
// SCCamera |
||||
|
// |
||||
|
// Created by Cheng Jiang on 4/24/18. |
||||
|
// |
||||
|
|
||||
|
#import "SCContextAwareSnapCreationThrottleRequest.h" |
||||
|
|
||||
|
#import <SCFoundation/SCAssertWrapper.h> |
||||
|
#import <SCFoundation/SCContextAwareTaskManagementResourceProvider.h> |
||||
|
#import <SCFoundation/SCZeroDependencyExperiments.h> |
||||
|
|
||||
|
#import <Tweaks/FBTweakInline.h> |
||||
|
|
||||
|
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<SCContextAwareThrottleRequest>)object |
||||
|
{ |
||||
|
return [[object requestID] isEqualToString:_requestID]; |
||||
|
} |
||||
|
|
||||
|
@end |
@ -0,0 +1,22 @@ |
|||||
|
// |
||||
|
// SCSnapCreationTriggers.h |
||||
|
// Snapchat |
||||
|
// |
||||
|
// Created by Cheng Jiang on 4/1/18. |
||||
|
// |
||||
|
|
||||
|
#import <Foundation/Foundation.h> |
||||
|
|
||||
|
@interface SCSnapCreationTriggers : NSObject |
||||
|
|
||||
|
- (void)markSnapCreationStart; |
||||
|
|
||||
|
- (void)markSnapCreationPreviewAnimationFinish; |
||||
|
|
||||
|
- (void)markSnapCreationPreviewImageSetupFinish; |
||||
|
|
||||
|
- (void)markSnapCreationPreviewVideoFirstFrameRenderFinish; |
||||
|
|
||||
|
- (void)markSnapCreationEndWithContext:(NSString *)context; |
||||
|
|
||||
|
@end |
@ -0,0 +1,83 @@ |
|||||
|
// |
||||
|
// SCSnapCreationTriggers.m |
||||
|
// Snapchat |
||||
|
// |
||||
|
// Created by Cheng Jiang on 3/30/18. |
||||
|
// |
||||
|
|
||||
|
#import "SCSnapCreationTriggers.h" |
||||
|
|
||||
|
#import "SCContextAwareSnapCreationThrottleRequest.h" |
||||
|
|
||||
|
#import <SCBase/SCMacros.h> |
||||
|
#import <SCFoundation/SCContextAwareThrottleRequester.h> |
||||
|
#import <SCFoundation/SCLog.h> |
||||
|
#import <SCFoundation/SCQueuePerformer.h> |
||||
|
|
||||
|
@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 |
@ -0,0 +1,26 @@ |
|||||
|
// |
||||
|
// SCFeature.h |
||||
|
// SCCamera |
||||
|
// |
||||
|
// Created by Kristian Bauer on 1/4/18. |
||||
|
// |
||||
|
|
||||
|
#import <UIKit/UIKit.h> |
||||
|
|
||||
|
/** |
||||
|
* Top level protocol for UI features |
||||
|
*/ |
||||
|
#define SCLogCameraFeatureInfo(fmt, ...) SCLogCoreCameraInfo(@"[SCFeature] " fmt, ##__VA_ARGS__) |
||||
|
@protocol SCFeatureContainerView; |
||||
|
@protocol SCFeature <NSObject> |
||||
|
|
||||
|
@optional |
||||
|
- (void)configureWithView:(UIView<SCFeatureContainerView> *)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 |
@ -0,0 +1,13 @@ |
|||||
|
// |
||||
|
// SCFeatureContainerView.h |
||||
|
// SCCamera |
||||
|
// |
||||
|
// Created by Kristian Bauer on 4/17/18. |
||||
|
// |
||||
|
|
||||
|
#import <UIKit/UIKit.h> |
||||
|
|
||||
|
@protocol SCFeatureContainerView |
||||
|
- (BOOL)isTapGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer; |
||||
|
- (CGRect)initialCameraTimerFrame; |
||||
|
@end |
@ -0,0 +1,44 @@ |
|||||
|
// |
||||
|
// SCFeatureCoordinator.h |
||||
|
// SCCamera |
||||
|
// |
||||
|
// Created by Kristian Bauer on 1/4/18. |
||||
|
// |
||||
|
|
||||
|
#import "SCFeature.h" |
||||
|
|
||||
|
#import <SCBase/SCMacros.h> |
||||
|
|
||||
|
@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<SCFeatureProvider>)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 |
@ -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 <SCFoundation/SCAssertWrapper.h> |
||||
|
#import <SCFoundation/SCTraceODPCompatible.h> |
||||
|
|
||||
|
typedef NSString SCFeatureDictionaryKey; |
||||
|
|
||||
|
@interface SCFeatureCoordinator () |
||||
|
@property (nonatomic, weak) UIView<SCFeatureContainerView> *containerView; |
||||
|
@property (nonatomic, strong) id<SCFeatureProvider> provider; |
||||
|
@end |
||||
|
|
||||
|
@implementation SCFeatureCoordinator |
||||
|
|
||||
|
- (instancetype)initWithFeatureContainerView:(UIView<SCFeatureContainerView> *)containerView |
||||
|
provider:(id<SCFeatureProvider>)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<SCFeature> 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<SCFeature> feature in _provider.supportedFeatures) { |
||||
|
if ([feature respondsToSelector:@selector(forwardCameraTimerGesture:)]) { |
||||
|
[feature forwardCameraTimerGesture:gestureRecognizer]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
- (void)forwardCameraOverlayTapGesture:(UIGestureRecognizer *)gestureRecognizer |
||||
|
{ |
||||
|
SCTraceODPCompatibleStart(2); |
||||
|
for (id<SCFeature> feature in _provider.supportedFeatures) { |
||||
|
if ([feature respondsToSelector:@selector(forwardCameraOverlayTapGesture:)]) { |
||||
|
[feature forwardCameraOverlayTapGesture:gestureRecognizer]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
- (void)forwardLongPressGesture:(UIGestureRecognizer *)gestureRecognizer |
||||
|
{ |
||||
|
SCTraceODPCompatibleStart(2); |
||||
|
for (id<SCFeature> feature in _provider.supportedFeatures) { |
||||
|
if ([feature respondsToSelector:@selector(forwardLongPressGesture:)]) { |
||||
|
[feature forwardLongPressGesture:gestureRecognizer]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
- (void)forwardPinchGesture:(UIPinchGestureRecognizer *)gestureRecognizer |
||||
|
{ |
||||
|
SCTraceODPCompatibleStart(2); |
||||
|
for (id<SCFeature> feature in _provider.supportedFeatures) { |
||||
|
if ([feature respondsToSelector:@selector(forwardPinchGesture:)]) { |
||||
|
[feature forwardPinchGesture:gestureRecognizer]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
- (void)forwardPanGesture:(UIPanGestureRecognizer *)gestureRecognizer |
||||
|
{ |
||||
|
SCTraceODPCompatibleStart(2); |
||||
|
for (id<SCFeature> feature in _provider.supportedFeatures) { |
||||
|
if ([feature respondsToSelector:@selector(forwardPanGesture:)]) { |
||||
|
[feature forwardPanGesture:gestureRecognizer]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
- (BOOL)shouldBlockTouchAtPoint:(CGPoint)point |
||||
|
{ |
||||
|
SCTraceODPCompatibleStart(2); |
||||
|
for (id<SCFeature> feature in _provider.supportedFeatures) { |
||||
|
if ([feature respondsToSelector:@selector(shouldBlockTouchAtPoint:)] && |
||||
|
[feature shouldBlockTouchAtPoint:point]) { |
||||
|
return YES; |
||||
|
} |
||||
|
} |
||||
|
return NO; |
||||
|
} |
||||
|
|
||||
|
@end |
@ -0,0 +1,50 @@ |
|||||
|
// |
||||
|
// SCFeatureProvider.h |
||||
|
// SCCamera |
||||
|
// |
||||
|
// Created by Kristian Bauer on 1/4/18. |
||||
|
// |
||||
|
|
||||
|
#import <SCCamera/AVCameraViewEnums.h> |
||||
|
|
||||
|
#import <Foundation/Foundation.h> |
||||
|
|
||||
|
@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 <NSObject> |
||||
|
|
||||
|
@property (nonatomic) AVCameraViewType cameraViewType; |
||||
|
|
||||
|
@property (nonatomic, readonly) id<SCCapturer> 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<SCFeatureHandsFree> handsFreeRecording; |
||||
|
@property (nonatomic) id<SCFeatureSnapKit> snapKit; |
||||
|
@property (nonatomic) id<SCFeatureTapToFocusAndExposure> tapToFocusAndExposure; |
||||
|
@property (nonatomic) id<SCFeatureMemories> memories; |
||||
|
@property (nonatomic) id<SCFeatureFlash> flash; |
||||
|
@property (nonatomic) id<SCFeatureLensSideButton> lensSideButton; |
||||
|
@property (nonatomic) id<SCFeatureLensButtonZ> lensZButton; |
||||
|
@property (nonatomic) id<SCFeatureNightMode> nightMode; |
||||
|
@property (nonatomic) id<SCFeatureToggleCamera> toggleCamera; |
||||
|
@property (nonatomic) id<SCFeatureShazam> shazam; |
||||
|
@property (nonatomic) id<SCFeatureScanning> scanning; |
||||
|
@property (nonatomic) id<SCFeatureImageCapture> imageCapture; |
||||
|
@property (nonatomic) id<SCFeatureZooming> zooming; |
||||
|
|
||||
|
@property (nonatomic, readonly) NSArray<id<SCFeature>> *supportedFeatures; |
||||
|
|
||||
|
- (void)resetInstances; |
||||
|
|
||||
|
@end |
@ -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 <SCFeature> |
||||
|
@property (nonatomic, readonly) SCNavigationBarButtonItem *navigationBarButtonItem; |
||||
|
|
||||
|
- (void)interruptGestures; |
||||
|
|
||||
|
@end |
@ -0,0 +1,23 @@ |
|||||
|
// |
||||
|
// SCFeatureFlashImpl.h |
||||
|
// SCCamera |
||||
|
// |
||||
|
// Created by Kristian Bauer on 3/27/18. |
||||
|
// |
||||
|
|
||||
|
#import "SCFeatureFlash.h" |
||||
|
|
||||
|
#import <SCBase/SCMacros.h> |
||||
|
|
||||
|
@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 <SCFeatureFlash> |
||||
|
SC_INIT_AND_NEW_UNAVAILABLE |
||||
|
- (instancetype)initWithCapturer:(id<SCCapturer>)capturer logger:(SCLogger *)logger NS_DESIGNATED_INITIALIZER; |
||||
|
@end |
@ -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 <SCFoundation/SCLocale.h> |
||||
|
#import <SCFoundation/SCLog.h> |
||||
|
#import <SCFoundation/SCTraceODPCompatible.h> |
||||
|
#import <SCLogger/SCLogger.h> |
||||
|
#import <SCUIKit/SCNavigationBarButtonItem.h> |
||||
|
|
||||
|
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<SCCapturer> capturer; |
||||
|
@property (nonatomic, strong, readwrite) SCLogger *logger; |
||||
|
@property (nonatomic, strong, readwrite) SCFlashButton *flashButton; |
||||
|
@property (nonatomic, weak, readwrite) UIView<SCFeatureContainerView> *containerView; |
||||
|
@property (nonatomic, strong, readwrite) SCManagedCapturerState *managedCapturerState; |
||||
|
@property (nonatomic, assign, readwrite) BOOL canEnable; |
||||
|
@end |
||||
|
|
||||
|
@interface SCFeatureFlashImpl (SCManagedCapturerListener) <SCManagedCapturerListener> |
||||
|
@end |
||||
|
|
||||
|
@implementation SCFeatureFlashImpl |
||||
|
@synthesize navigationBarButtonItem = _navigationBarButtonItem; |
||||
|
|
||||
|
- (instancetype)initWithCapturer:(id<SCCapturer>)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<SCFeatureContainerView> *)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<SCCapturer>)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<SCCapturer>)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<SCCapturer>)managedCapturer didChangeState:(SCManagedCapturerState *)state |
||||
|
{ |
||||
|
SCTraceODPCompatibleStart(2); |
||||
|
_managedCapturerState = [state copy]; |
||||
|
} |
||||
|
|
||||
|
- (void)managedCapturer:(id<SCCapturer>)managedCapturer didChangeARSessionActive:(SCManagedCapturerState *)state |
||||
|
{ |
||||
|
SCTraceODPCompatibleStart(2); |
||||
|
SCLogCameraFeatureInfo(@"[%@] didChangeARSessionActive: %@", NSStringFromClass([self class]), |
||||
|
state.arSessionActive ? @"YES" : @"NO"); |
||||
|
self.flashButton.hidden = [self _shouldHideForState:state]; |
||||
|
} |
||||
|
|
||||
|
- (void)managedCapturer:(id<SCCapturer>)managedCapturer |
||||
|
didBeginVideoRecording:(SCManagedCapturerState *)state |
||||
|
session:(SCVideoCaptureSessionInfo)session |
||||
|
{ |
||||
|
SCTraceODPCompatibleStart(2); |
||||
|
self.canEnable = NO; |
||||
|
} |
||||
|
|
||||
|
- (void)managedCapturer:(id<SCCapturer>)managedCapturer |
||||
|
didFinishRecording:(SCManagedCapturerState *)state |
||||
|
session:(SCVideoCaptureSessionInfo)session |
||||
|
recordedVideo:(SCManagedRecordedVideo *)recordedVideo |
||||
|
{ |
||||
|
SCTraceODPCompatibleStart(2); |
||||
|
self.canEnable = YES; |
||||
|
} |
||||
|
|
||||
|
- (void)managedCapturer:(id<SCCapturer>)managedCapturer |
||||
|
didFailRecording:(SCManagedCapturerState *)state |
||||
|
session:(SCVideoCaptureSessionInfo)session |
||||
|
error:(NSError *)error |
||||
|
{ |
||||
|
SCTraceODPCompatibleStart(2); |
||||
|
self.canEnable = YES; |
||||
|
} |
||||
|
|
||||
|
- (void)managedCapturer:(id<SCCapturer>)managedCapturer |
||||
|
didCancelRecording:(SCManagedCapturerState *)state |
||||
|
session:(SCVideoCaptureSessionInfo)session |
||||
|
{ |
||||
|
SCTraceODPCompatibleStart(2); |
||||
|
self.canEnable = YES; |
||||
|
} |
||||
|
|
||||
|
@end |
@ -0,0 +1,15 @@ |
|||||
|
// |
||||
|
// SCFlashButton.h |
||||
|
// SCCamera |
||||
|
// |
||||
|
// Created by Will Wu on 2/13/14. |
||||
|
// Copyright (c) 2014 Snapchat, Inc. All rights reserved. |
||||
|
// |
||||
|
|
||||
|
#import <SCUIKit/SCGrowingButton.h> |
||||
|
|
||||
|
typedef NS_ENUM(NSInteger, SCFlashButtonState) { SCFlashButtonStateOn = 0, SCFlashButtonStateOff = 1 }; |
||||
|
|
||||
|
@interface SCFlashButton : SCGrowingButton |
||||
|
@property (nonatomic, assign) SCFlashButtonState buttonState; |
||||
|
@end |
@ -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 <SCUIKit/SCPixelRounding.h> |
||||
|
|
||||
|
@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 |
@ -0,0 +1,30 @@ |
|||||
|
// |
||||
|
// SCFeatureHandsFree.h |
||||
|
// SCCamera |
||||
|
// |
||||
|
// Created by Kristian Bauer on 2/26/18. |
||||
|
// |
||||
|
|
||||
|
#import "SCFeature.h" |
||||
|
|
||||
|
#import <SCCamera/AVCameraViewEnums.h> |
||||
|
|
||||
|
@class SCLongPressGestureRecognizer, SCPreviewPresenter; |
||||
|
|
||||
|
@protocol SCFeatureHandsFree <SCFeature> |
||||
|
@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 |
@ -0,0 +1,27 @@ |
|||||
|
// |
||||
|
// SCFeatureImageCapture.h |
||||
|
// SCCamera |
||||
|
// |
||||
|
// Created by Kristian Bauer on 4/18/18. |
||||
|
// |
||||
|
|
||||
|
#import "SCFeature.h" |
||||
|
|
||||
|
#import <SCFoundation/SCFuture.h> |
||||
|
|
||||
|
@protocol SCFeatureImageCapture; |
||||
|
|
||||
|
@protocol SCFeatureImageCaptureDelegate <NSObject> |
||||
|
- (void)featureImageCapture:(id<SCFeatureImageCapture>)featureImageCapture willCompleteWithImage:(UIImage *)image; |
||||
|
- (void)featureImageCapture:(id<SCFeatureImageCapture>)featureImageCapture didCompleteWithError:(NSError *)error; |
||||
|
- (void)featureImageCapturedDidComplete:(id<SCFeatureImageCapture>)featureImageCapture; |
||||
|
@end |
||||
|
|
||||
|
/** |
||||
|
SCFeature protocol for capturing an image. |
||||
|
*/ |
||||
|
@protocol SCFeatureImageCapture <SCFeature> |
||||
|
@property (nonatomic, weak, readwrite) id<SCFeatureImageCaptureDelegate> delegate; |
||||
|
@property (nonatomic, strong, readonly) SCPromise<UIImage *> *imagePromise; |
||||
|
- (void)captureImage:(NSString *)captureSessionID; |
||||
|
@end |
@ -0,0 +1,21 @@ |
|||||
|
// |
||||
|
// SCFeatureImageCaptureImpl.h |
||||
|
// SCCamera |
||||
|
// |
||||
|
// Created by Kristian Bauer on 4/18/18. |
||||
|
// |
||||
|
|
||||
|
#import "AVCameraViewEnums.h" |
||||
|
#import "SCFeatureImageCapture.h" |
||||
|
|
||||
|
#import <SCBase/SCMacros.h> |
||||
|
|
||||
|
@protocol SCCapturer; |
||||
|
@class SCLogger; |
||||
|
|
||||
|
@interface SCFeatureImageCaptureImpl : NSObject <SCFeatureImageCapture> |
||||
|
SC_INIT_AND_NEW_UNAVAILABLE |
||||
|
- (instancetype)initWithCapturer:(id<SCCapturer>)capturer |
||||
|
logger:(SCLogger *)logger |
||||
|
cameraViewType:(AVCameraViewType)cameraViewType NS_DESIGNATED_INITIALIZER; |
||||
|
@end |
@ -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 <SCFoundation/SCDeviceName.h> |
||||
|
#import <SCFoundation/SCLog.h> |
||||
|
#import <SCFoundation/SCQueuePerformer.h> |
||||
|
#import <SCFoundation/SCTraceODPCompatible.h> |
||||
|
#import <SCGhostToSnappable/SCGhostToSnappableSignal.h> |
||||
|
#import <SCLogger/SCCameraMetrics.h> |
||||
|
#import <SCLogger/SCLogger+Performance.h> |
||||
|
|
||||
|
@interface SCFeatureImageCaptureImpl () |
||||
|
@property (nonatomic, strong, readwrite) id<SCCapturer> 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) <SCManagedCapturerListener> |
||||
|
@end |
||||
|
|
||||
|
@implementation SCFeatureImageCaptureImpl |
||||
|
@synthesize delegate = _delegate; |
||||
|
@synthesize imagePromise = _imagePromise; |
||||
|
|
||||
|
- (instancetype)initWithCapturer:(id<SCCapturer>)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<SCCapturer>)managedCapturer didChangeState:(SCManagedCapturerState *)state |
||||
|
{ |
||||
|
SCTraceODPCompatibleStart(2); |
||||
|
_managedCapturerState = [state copy]; |
||||
|
} |
||||
|
|
||||
|
- (void)managedCapturer:(id<SCCapturer>)managedCapturer didCapturePhoto:(SCManagedCapturerState *)state |
||||
|
{ |
||||
|
SCTraceODPCompatibleStart(2); |
||||
|
if (_imagePromise) { |
||||
|
[[SCManagedCapturePreviewLayerController sharedInstance] pause]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@end |
@ -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 <SCFeature> |
||||
|
@property (nonatomic, weak, readwrite) SCPreviewPresenter *previewPresenter; |
||||
|
@property (nonatomic, readonly) SCNavigationBarButtonItem *navigationBarButtonItem; |
||||
|
|
||||
|
- (void)interruptGestures; |
||||
|
- (void)hideWithDelayIfNeeded; |
||||
|
@end |
@ -0,0 +1,18 @@ |
|||||
|
// |
||||
|
// SCNightModeButton.h |
||||
|
// SCCamera |
||||
|
// |
||||
|
// Created by Liu Liu on 3/19/15. |
||||
|
// Copyright (c) 2015 Snapchat, Inc. All rights reserved. |
||||
|
// |
||||
|
|
||||
|
#import <SCBase/SCMacros.h> |
||||
|
#import <SCUIKit/SCGrowingButton.h> |
||||
|
|
||||
|
@interface SCNightModeButton : SCGrowingButton |
||||
|
@property (nonatomic, assign, getter=isSelected) BOOL selected; |
||||
|
SC_INIT_AND_NEW_UNAVAILABLE |
||||
|
- (void)show; |
||||
|
- (void)hideWithDelay:(BOOL)delay; |
||||
|
- (BOOL)willHideAfterDelay; |
||||
|
@end |
@ -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 <SCFoundation/SCAssertWrapper.h> |
||||
|
|
||||
|
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 |
@ -0,0 +1,26 @@ |
|||||
|
// |
||||
|
// SCFeatureScanning.h |
||||
|
// Snapchat |
||||
|
// |
||||
|
// Created by Xiaokang Liu on 2018/4/19. |
||||
|
// |
||||
|
|
||||
|
#import "SCFeature.h" |
||||
|
|
||||
|
@protocol SCFeatureScanning; |
||||
|
|
||||
|
@protocol SCFeatureScanningDelegate <NSObject> |
||||
|
- (void)featureScanning:(id<SCFeatureScanning>)featureScanning didFinishWithResult:(NSObject *)resultObject; |
||||
|
@end |
||||
|
|
||||
|
/** |
||||
|
This SCFeature allows the user to long press on the screen to scan a snapcode. |
||||
|
*/ |
||||
|
@protocol SCFeatureScanning <SCFeature> |
||||
|
@property (nonatomic, weak) id<SCFeatureScanningDelegate> delegate; |
||||
|
@property (nonatomic, assign) NSTimeInterval lastSuccessfulScanTime; |
||||
|
- (void)startScanning; |
||||
|
- (void)stopScanning; |
||||
|
|
||||
|
- (void)stopSearch; |
||||
|
@end |
@ -0,0 +1,23 @@ |
|||||
|
// |
||||
|
// SCFeatureShazam.h |
||||
|
// SCCamera |
||||
|
// |
||||
|
// Created by Xiaokang Liu on 2018/4/18. |
||||
|
// |
||||
|
|
||||
|
#import "SCFeature.h" |
||||
|
|
||||
|
@class SCLens; |
||||
|
@protocol SCFeatureShazam; |
||||
|
|
||||
|
@protocol SCFeatureShazamDelegate <NSObject> |
||||
|
- (void)featureShazam:(id<SCFeatureShazam>)featureShazam didFinishWithResult:(NSObject *)result; |
||||
|
- (void)featureShazamDidSubmitSearchRequest:(id<SCFeatureShazam>)featureShazam; |
||||
|
- (SCLens *)filterLensForFeatureShazam:(id<SCFeatureShazam>)featureShazam; |
||||
|
@end |
||||
|
|
||||
|
@protocol SCFeatureShazam <SCFeature> |
||||
|
@property (nonatomic, weak) id<SCFeatureShazamDelegate> delegate; |
||||
|
- (void)stopAudioRecordingAsynchronously; |
||||
|
- (void)resetInfo; |
||||
|
@end |
@ -0,0 +1,14 @@ |
|||||
|
// |
||||
|
// SCFeatureSnapKit.h |
||||
|
// SCCamera |
||||
|
// |
||||
|
// Created by Michel Loenngren on 3/19/18. |
||||
|
// |
||||
|
|
||||
|
#import "SCFeature.h" |
||||
|
|
||||
|
@class SCCameraDeepLinkMetadata; |
||||
|
|
||||
|
@protocol SCFeatureSnapKit <SCFeature> |
||||
|
- (void)setDeepLinkMetadata:(SCCameraDeepLinkMetadata *)metadata; |
||||
|
@end |
@ -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 <SCFeature> |
||||
|
|
||||
|
- (void)reset; |
||||
|
|
||||
|
@end |
@ -0,0 +1,49 @@ |
|||||
|
// |
||||
|
// SCFeatureTapToFocusAndExposureImpl.h |
||||
|
// SCCamera |
||||
|
// |
||||
|
// Created by Michel Loenngren on 4/5/18. |
||||
|
// |
||||
|
|
||||
|
#import "SCFeatureTapToFocusAndExposure.h" |
||||
|
|
||||
|
#import <SCBase/SCMacros.h> |
||||
|
|
||||
|
#import <Foundation/Foundation.h> |
||||
|
|
||||
|
@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 <NSObject> |
||||
|
- (void)execute:(CGPoint)pointOfInterest capturer:(id<SCCapturer>)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 <SCFeatureTapToFocusAndExposure> |
||||
|
SC_INIT_AND_NEW_UNAVAILABLE |
||||
|
- (instancetype)initWithCapturer:(id<SCCapturer>)capturer commands:(NSArray<id<SCFeatureCameraTapCommand>> *)commands; |
||||
|
@end |
||||
|
|
||||
|
/** |
||||
|
Adjust focus on tap. |
||||
|
*/ |
||||
|
@interface SCFeatureCameraFocusTapCommand : NSObject <SCFeatureCameraTapCommand> |
||||
|
@end |
||||
|
|
||||
|
/** |
||||
|
Adjust exposure on tap. |
||||
|
*/ |
||||
|
@interface SCFeatureCameraExposureTapCommand : NSObject <SCFeatureCameraTapCommand> |
||||
|
@end |
||||
|
|
||||
|
/** |
||||
|
Adjust portrait mode point of interest on tap. |
||||
|
*/ |
||||
|
@interface SCFeatureCameraPortraitTapCommand : NSObject <SCFeatureCameraTapCommand> |
||||
|
@end |
@ -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 <SCFoundation/SCLog.h> |
||||
|
#import <SCFoundation/SCTraceODPCompatible.h> |
||||
|
|
||||
|
@interface SCFeatureTapToFocusAndExposureImpl () |
||||
|
@property (nonatomic, weak) id<SCCapturer> capturer; |
||||
|
@property (nonatomic, weak) UIView<SCFeatureContainerView> *containerView; |
||||
|
@property (nonatomic) BOOL userTappedToFocusAndExposure; |
||||
|
@property (nonatomic) NSArray<id<SCFeatureCameraTapCommand>> *commands; |
||||
|
@end |
||||
|
|
||||
|
@implementation SCFeatureTapToFocusAndExposureImpl |
||||
|
|
||||
|
- (instancetype)initWithCapturer:(id<SCCapturer>)capturer commands:(NSArray<id<SCFeatureCameraTapCommand>> *)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<SCFeatureContainerView> *)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<SCFeatureCameraTapCommand> 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<SCCapturer>)capturer |
||||
|
{ |
||||
|
[capturer setAutofocusPointOfInterestAsynchronously:pointOfInterest |
||||
|
completionHandler:nil |
||||
|
context:SCCapturerContext]; |
||||
|
} |
||||
|
@end |
||||
|
|
||||
|
@implementation SCFeatureCameraExposureTapCommand |
||||
|
- (void)execute:(CGPoint)pointOfInterest capturer:(id<SCCapturer>)capturer |
||||
|
{ |
||||
|
[capturer setExposurePointOfInterestAsynchronously:pointOfInterest |
||||
|
fromUser:YES |
||||
|
completionHandler:nil |
||||
|
context:SCCapturerContext]; |
||||
|
} |
||||
|
@end |
||||
|
|
||||
|
@implementation SCFeatureCameraPortraitTapCommand |
||||
|
- (void)execute:(CGPoint)pointOfInterest capturer:(id<SCCapturer>)capturer |
||||
|
{ |
||||
|
[capturer setPortraitModePointOfInterestAsynchronously:pointOfInterest |
||||
|
completionHandler:nil |
||||
|
context:SCCapturerContext]; |
||||
|
} |
||||
|
@end |
@ -0,0 +1,21 @@ |
|||||
|
// |
||||
|
// SCTapAnimationView.h |
||||
|
// SCCamera |
||||
|
// |
||||
|
// Created by Alexander Grytsiuk on 8/26/15. |
||||
|
// Copyright (c) 2015 Snapchat, Inc. All rights reserved. |
||||
|
// |
||||
|
|
||||
|
#import <UIKit/UIKit.h> |
||||
|
|
||||
|
@class SCTapAnimationView; |
||||
|
|
||||
|
typedef void (^SCTapAnimationViewCompletion)(SCTapAnimationView *); |
||||
|
|
||||
|
@interface SCTapAnimationView : UIView |
||||
|
|
||||
|
+ (instancetype)tapAnimationView; |
||||
|
|
||||
|
- (void)showWithCompletion:(SCTapAnimationViewCompletion)completion; |
||||
|
|
||||
|
@end |
@ -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 <SCBase/SCMacros.h> |
||||
|
|
||||
|
@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 |
@ -0,0 +1,37 @@ |
|||||
|
// |
||||
|
// SCFeatureToggleCamera.h |
||||
|
// SCCamera |
||||
|
// |
||||
|
// Created by Michel Loenngren on 4/17/18. |
||||
|
// |
||||
|
|
||||
|
#import <SCCamera/SCFeature.h> |
||||
|
#import <SCCameraFoundation/SCManagedCaptureDevicePosition.h> |
||||
|
|
||||
|
@protocol SCCapturer |
||||
|
, SCFeatureToggleCamera, SCLensCameraScreenDataProviderProtocol; |
||||
|
|
||||
|
@protocol SCFeatureToggleCameraDelegate <NSObject> |
||||
|
|
||||
|
- (void)featureToggleCamera:(id<SCFeatureToggleCamera>)feature |
||||
|
willToggleToDevicePosition:(SCManagedCaptureDevicePosition)devicePosition; |
||||
|
- (void)featureToggleCamera:(id<SCFeatureToggleCamera>)feature |
||||
|
didToggleToDevicePosition:(SCManagedCaptureDevicePosition)devicePosition; |
||||
|
|
||||
|
@end |
||||
|
|
||||
|
/** |
||||
|
SCFeature protocol for toggling the camera. |
||||
|
*/ |
||||
|
@protocol SCFeatureToggleCamera <SCFeature> |
||||
|
|
||||
|
@property (nonatomic, weak) id<SCFeatureToggleCameraDelegate> delegate; |
||||
|
|
||||
|
- (void)toggleCameraWithRecording:(BOOL)isRecording |
||||
|
takingPicture:(BOOL)isTakingPicture |
||||
|
lensDataProvider:(id<SCLensCameraScreenDataProviderProtocol>)lensDataProvider |
||||
|
completion:(void (^)(BOOL success))completion; |
||||
|
|
||||
|
- (void)reset; |
||||
|
|
||||
|
@end |
@ -0,0 +1,34 @@ |
|||||
|
// |
||||
|
// SCFeatureZooming.h |
||||
|
// SCCamera |
||||
|
// |
||||
|
// Created by Xiaokang Liu on 2018/4/19. |
||||
|
// |
||||
|
|
||||
|
#import "SCFeature.h" |
||||
|
|
||||
|
#import <SCCameraFoundation/SCManagedCaptureDevicePosition.h> |
||||
|
#import <SCSearch/SCSearchSnapZoomLevelProviding.h> |
||||
|
|
||||
|
@class SCPreviewPresenter; |
||||
|
@protocol SCFeatureZooming; |
||||
|
|
||||
|
@protocol SCFeatureZoomingDelegate <NSObject> |
||||
|
- (void)featureZoomingForceTouchedWhileRecording:(id<SCFeatureZooming>)featureZooming; |
||||
|
- (BOOL)featureZoomingIsInitiatedRecording:(id<SCFeatureZooming>)featureZooming; |
||||
|
@end |
||||
|
|
||||
|
@protocol SCFeatureZooming <SCFeature, SCSearchSnapZoomLevelProviding> |
||||
|
@property (nonatomic, weak) id<SCFeatureZoomingDelegate> delegate; |
||||
|
@property (nonatomic, weak) SCPreviewPresenter *previewPresenter; |
||||
|
|
||||
|
- (void)resetOffset; |
||||
|
- (void)resetScale; |
||||
|
|
||||
|
- (void)cancelPreview; |
||||
|
- (void)flipOffset; |
||||
|
|
||||
|
- (void)resetBeginningScale; |
||||
|
- (void)toggleCameraForReset:(SCManagedCaptureDevicePosition)devicePosition; |
||||
|
- (void)recordCurrentZoomStateForReset; |
||||
|
@end |
@ -0,0 +1,23 @@ |
|||||
|
// |
||||
|
// SCManagedCapturerARImageCaptureProvider.h |
||||
|
// SCCamera |
||||
|
// |
||||
|
// Created by Michel Loenngren on 4/11/18. |
||||
|
// |
||||
|
|
||||
|
#import <Foundation/Foundation.h> |
||||
|
|
||||
|
@class SCManagedStillImageCapturer; |
||||
|
@protocol SCManagedCapturerLensAPI |
||||
|
, SCPerforming; |
||||
|
|
||||
|
/** |
||||
|
Bridging protocol providing the ARImageCapturer subclass of SCManagedStillImageCapturer |
||||
|
to capture core. |
||||
|
*/ |
||||
|
@protocol SCManagedCapturerARImageCaptureProvider <NSObject> |
||||
|
|
||||
|
- (SCManagedStillImageCapturer *)arImageCapturerWith:(id<SCPerforming>)performer |
||||
|
lensProcessingCore:(id<SCManagedCapturerLensAPI>)lensProcessingCore; |
||||
|
|
||||
|
@end |
@ -0,0 +1,27 @@ |
|||||
|
// |
||||
|
// SCManagedCapturerGLViewManagerAPI.h |
||||
|
// SCCamera |
||||
|
// |
||||
|
// Created by Michel Loenngren on 4/11/18. |
||||
|
// |
||||
|
|
||||
|
#import <Looksery/LSAGLView.h> |
||||
|
|
||||
|
#import <Foundation/Foundation.h> |
||||
|
|
||||
|
@class SCCaptureResource; |
||||
|
|
||||
|
/** |
||||
|
Bridging protocol for providing a glViewManager to capture core. |
||||
|
*/ |
||||
|
@protocol SCManagedCapturerGLViewManagerAPI <NSObject> |
||||
|
|
||||
|
@property (nonatomic, readonly, strong) LSAGLView *view; |
||||
|
|
||||
|
- (void)configureWithCaptureResource:(SCCaptureResource *)captureResource; |
||||
|
|
||||
|
- (void)setLensesActive:(BOOL)active; |
||||
|
|
||||
|
- (void)prepareViewIfNecessary; |
||||
|
|
||||
|
@end |
@ -0,0 +1,19 @@ |
|||||
|
// |
||||
|
// SCManagedCapturerLSAComponentTrackerAPI.h |
||||
|
// SCCamera |
||||
|
// |
||||
|
// Created by Michel Loenngren on 4/11/18. |
||||
|
// |
||||
|
|
||||
|
#import <Foundation/Foundation.h> |
||||
|
|
||||
|
@class SCCaptureResource; |
||||
|
|
||||
|
/** |
||||
|
SCCamera protocol providing LSA tracking logic. |
||||
|
*/ |
||||
|
@protocol SCManagedCapturerLSAComponentTrackerAPI <NSObject> |
||||
|
|
||||
|
- (void)configureWithCaptureResource:(SCCaptureResource *)captureResource; |
||||
|
|
||||
|
@end |
@ -0,0 +1,67 @@ |
|||||
|
// |
||||
|
// SCManagedCapturerLensAPI.h |
||||
|
// SCCamera |
||||
|
// |
||||
|
// Created by Michel Loenngren on 4/11/18. |
||||
|
// |
||||
|
|
||||
|
#import "SCManagedCapturerListener.h" |
||||
|
#import "SCManagedVideoARDataSource.h" |
||||
|
|
||||
|
#import <SCCameraFoundation/SCManagedCaptureDevicePosition.h> |
||||
|
#import <SCLenses/SCLens.h> |
||||
|
|
||||
|
#import <Foundation/Foundation.h> |
||||
|
|
||||
|
@protocol SCManagedAudioDataSourceListener |
||||
|
, SCManagedVideoARDataSource; |
||||
|
@class LSAComponentManager; |
||||
|
|
||||
|
/** |
||||
|
Encapsulation of LensesProcessingCore for use in SCCamera. |
||||
|
*/ |
||||
|
@protocol SCManagedCapturerLensAPI <SCManagedCapturerListener> |
||||
|
|
||||
|
@property (nonatomic, strong, readonly) LSAComponentManager *componentManager; |
||||
|
@property (nonatomic, strong) NSString *activeLensId; |
||||
|
@property (nonatomic, readonly) BOOL isLensApplied; |
||||
|
@property (nonatomic, strong, readonly) |
||||
|
id<SCManagedAudioDataSourceListener, SCManagedVideoDataSourceListener> 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<SCManagedVideoARDataSource>)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<SCLens *> *)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 |
@ -0,0 +1,20 @@ |
|||||
|
// |
||||
|
// SCManagedCapturerLensAPIProvider.h |
||||
|
// SCCamera |
||||
|
// |
||||
|
// Created by Michel Loenngren on 4/12/18. |
||||
|
// |
||||
|
|
||||
|
#import <Foundation/Foundation.h> |
||||
|
|
||||
|
@protocol SCManagedCapturerLensAPI; |
||||
|
@class SCCaptureResource; |
||||
|
|
||||
|
/** |
||||
|
Provider for creating new instances of SCManagedCapturerLensAPI within SCCamera. |
||||
|
*/ |
||||
|
@protocol SCManagedCapturerLensAPIProvider <NSObject> |
||||
|
|
||||
|
- (id<SCManagedCapturerLensAPI>)lensAPIForCaptureResource:(SCCaptureResource *)captureResouce; |
||||
|
|
||||
|
@end |
Write
Preview
Loading…
Cancel
Save
Reference in new issue